diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /comm/third_party/rnp/src | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'comm/third_party/rnp/src')
186 files changed, 64034 insertions, 0 deletions
diff --git a/comm/third_party/rnp/src/common/CMakeLists.txt b/comm/third_party/rnp/src/common/CMakeLists.txt new file mode 100644 index 0000000000..1ea344592a --- /dev/null +++ b/comm/third_party/rnp/src/common/CMakeLists.txt @@ -0,0 +1,64 @@ +# Copyright (c) 2020 Ribose Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS +# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +add_library(rnp-common OBJECT + str-utils.cpp + file-utils.cpp + time-utils.cpp +) + +if(MSVC) + find_path(GETOPT_INCLUDE_DIR + NAMES getopt.h + ) + find_library(GETOPT_LIBRARY + NAMES getopt + ) + find_path(DIRENT_INCLUDE_DIR + NAMES dirent.h + ) +endif() + +target_include_directories(rnp-common + PUBLIC + "$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/src/lib>" + "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>" + "$<INSTALL_INTERFACE:include>" + PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}" + "${PROJECT_SOURCE_DIR}/src" +) +if(MSVC) + target_include_directories(rnp-common + PRIVATE + "${GETOPT_INCLUDE_DIR}" + "${DIRENT_INCLUDE_DIR}" + ) +endif() + +set_target_properties(rnp-common PROPERTIES + POSITION_INDEPENDENT_CODE ON + CXX_VISIBILITY_PRESET hidden +) + diff --git a/comm/third_party/rnp/src/common/file-utils.cpp b/comm/third_party/rnp/src/common/file-utils.cpp new file mode 100644 index 0000000000..d9cdb402dc --- /dev/null +++ b/comm/third_party/rnp/src/common/file-utils.cpp @@ -0,0 +1,359 @@ +/* + * Copyright (c) 2017-2020 [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** File utilities + * @file + */ + +#include "file-utils.h" +#include "config.h" +#ifdef _MSC_VER +#include <stdlib.h> +#include <stdio.h> +#include "uniwin.h" +#include <errno.h> +#else +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#endif // !_MSC_VER +#include "str-utils.h" +#include <algorithm> +#ifdef _WIN32 +#include <random> // for rnp_mkstemp +#define CATCH_AND_RETURN(v) \ + catch (...) \ + { \ + errno = ENOMEM; \ + return v; \ + } +#else +#include <string.h> +#endif +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif +#include <stdarg.h> + +int +rnp_unlink(const char *filename) +{ +#ifdef _WIN32 + try { + return _wunlink(wstr_from_utf8(filename).c_str()); + } + CATCH_AND_RETURN(-1) +#else + return unlink(filename); +#endif +} + +bool +rnp_file_exists(const char *path) +{ + struct stat st; + return rnp_stat(path, &st) == 0 && S_ISREG(st.st_mode); +} + +bool +rnp_dir_exists(const char *path) +{ + struct stat st; + return rnp_stat(path, &st) == 0 && S_ISDIR(st.st_mode); +} + +int +rnp_open(const char *filename, int oflag, int pmode) +{ +#ifdef _WIN32 + try { + return _wopen(wstr_from_utf8(filename).c_str(), oflag, pmode); + } + CATCH_AND_RETURN(-1) +#else + return open(filename, oflag, pmode); +#endif +} + +FILE * +rnp_fopen(const char *filename, const char *mode) +{ +#ifdef _WIN32 + try { + return _wfopen(wstr_from_utf8(filename).c_str(), wstr_from_utf8(mode).c_str()); + } + CATCH_AND_RETURN(NULL) +#else + return fopen(filename, mode); +#endif +} + +FILE * +rnp_fdopen(int fildes, const char *mode) +{ +#ifdef _WIN32 + return _fdopen(fildes, mode); +#else + return fdopen(fildes, mode); +#endif +} + +int +rnp_access(const char *path, int mode) +{ +#ifdef _WIN32 + try { + return _waccess(wstr_from_utf8(path).c_str(), mode); + } + CATCH_AND_RETURN(-1) +#else + return access(path, mode); +#endif +} + +int +rnp_stat(const char *filename, struct stat *statbuf) +{ +#ifdef _WIN32 + static_assert(sizeof(struct stat) == sizeof(struct _stat64i32), + "stat is expected to match _stat64i32"); + try { + return _wstat64i32(wstr_from_utf8(filename).c_str(), (struct _stat64i32 *) statbuf); + } + CATCH_AND_RETURN(-1) +#else + return stat(filename, statbuf); +#endif +} + +#ifdef _WIN32 +int +rnp_mkdir(const char *path) +{ + try { + return _wmkdir(wstr_from_utf8(path).c_str()); + } + CATCH_AND_RETURN(-1) +} +#endif + +int +rnp_rename(const char *oldpath, const char *newpath) +{ +#ifdef _WIN32 + try { + return _wrename(wstr_from_utf8(oldpath).c_str(), wstr_from_utf8(newpath).c_str()); + } + CATCH_AND_RETURN(-1) +#else + return rename(oldpath, newpath); +#endif +} + +#ifdef _WIN32 +_WDIR * +#else +DIR * +#endif +rnp_opendir(const char *path) +{ +#ifdef _WIN32 + try { + return _wopendir(wstr_from_utf8(path).c_str()); + } + CATCH_AND_RETURN(NULL) +#else + return opendir(path); +#endif +} + +std::string +#ifdef _WIN32 +rnp_readdir_name(_WDIR *dir) +{ + _wdirent *ent; + for (;;) { + if ((ent = _wreaddir(dir)) == NULL) { + return std::string(); + } + if (wcscmp(ent->d_name, L".") && wcscmp(ent->d_name, L"..")) { + break; + } + } + try { + return wstr_to_utf8(ent->d_name); + } + CATCH_AND_RETURN(std::string()) +#else +rnp_readdir_name(DIR *dir) +{ + dirent *ent; + for (;;) { + if ((ent = readdir(dir)) == NULL) { + return std::string(); + } + if (strcmp(ent->d_name, ".") && strcmp(ent->d_name, "..")) { + break; + } + } + return std::string(ent->d_name); +#endif +} + +/* return the file modification time */ +int64_t +rnp_filemtime(const char *path) +{ + struct stat st; + + if (rnp_stat(path, &st) != 0) { + return 0; + } else { + return st.st_mtime; + } +} + +#ifdef _WIN32 +static const char letters[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + +/** @private + * generate a temporary file name based on TMPL. + * + * @param tmpl filename template in UTF-8 ending in XXXXXX + * @return file descriptor of newly created and opened file, or -1 on error + **/ +int +rnp_mkstemp(char *tmpl) +{ + try { + int save_errno = errno; + const int mask_length = 6; + int len = strlen(tmpl); + if (len < mask_length || strcmp(&tmpl[len - mask_length], "XXXXXX")) { + errno = EINVAL; + return -1; + } + std::wstring tmpl_w = wstr_from_utf8(tmpl, tmpl + len - mask_length); + + /* This is where the Xs start. */ + char *XXXXXX = &tmpl[len - mask_length]; + + std::random_device rd; + std::mt19937_64 rng(rd()); + + for (unsigned int countdown = TMP_MAX; --countdown;) { + unsigned long long v = rng(); + + XXXXXX[0] = letters[v % 36]; + v /= 36; + XXXXXX[1] = letters[v % 36]; + v /= 36; + XXXXXX[2] = letters[v % 36]; + v /= 36; + XXXXXX[3] = letters[v % 36]; + v /= 36; + XXXXXX[4] = letters[v % 36]; + v /= 36; + XXXXXX[5] = letters[v % 36]; + + int flags = O_WRONLY | O_CREAT | O_EXCL | O_BINARY; + int fd = + _wopen((tmpl_w + wstr_from_utf8(XXXXXX)).c_str(), flags, _S_IREAD | _S_IWRITE); + if (fd != -1) { + errno = save_errno; + return fd; + } else if (errno != EEXIST) + return -1; + } + + // We got out of the loop because we ran out of combinations to try. + errno = EEXIST; + return -1; + } + CATCH_AND_RETURN(-1) +} +#endif // _WIN32 + +namespace rnp { +namespace path { +inline char +separator() +{ +#ifdef _WIN32 + return '\\'; +#else + return '/'; +#endif +} + +bool +exists(const std::string &path, bool is_dir) +{ + return is_dir ? rnp_dir_exists(path.c_str()) : rnp_file_exists(path.c_str()); +} + +bool +empty(const std::string &path) +{ + auto dir = rnp_opendir(path.c_str()); + if (!dir) { + return true; + } + bool empty = rnp_readdir_name(dir).empty(); + rnp_closedir(dir); + return empty; +} + +std::string +HOME(const std::string &sdir) +{ + const char *home = getenv("HOME"); + if (!home) { + return ""; + } + return sdir.empty() ? home : append(home, sdir); +} + +static bool +has_forward_slash(const std::string &path) +{ + return std::find(path.begin(), path.end(), '/') != path.end(); +} + +std::string +append(const std::string &path, const std::string &name) +{ + bool no_sep = path.empty() || name.empty() || (rnp::is_slash(path.back())) || + (rnp::is_slash(name.front())); + if (no_sep) { + return path + name; + } + /* Use forward slash if there is at least one in the path/name. */ + char sep = has_forward_slash(path) || has_forward_slash(name) ? '/' : separator(); + return path + sep + name; +} + +} // namespace path +} // namespace rnp diff --git a/comm/third_party/rnp/src/common/file-utils.h b/comm/third_party/rnp/src/common/file-utils.h new file mode 100644 index 0000000000..1993d173f7 --- /dev/null +++ b/comm/third_party/rnp/src/common/file-utils.h @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2019-2020, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RNP_FILE_UTILS_H_ +#define RNP_FILE_UTILS_H_ + +#include <stdint.h> +#include <stdio.h> +#include <dirent.h> +#include <string> + +bool rnp_file_exists(const char *path); +bool rnp_dir_exists(const char *path); +int64_t rnp_filemtime(const char *path); +int rnp_open(const char *filename, int oflag, int pmode); +FILE * rnp_fopen(const char *filename, const char *mode); +FILE * rnp_fdopen(int fildes, const char *mode); +int rnp_access(const char *path, int mode); +int rnp_stat(const char *filename, struct stat *statbuf); +int rnp_rename(const char *oldpath, const char *newpath); +int rnp_unlink(const char *path); + +#ifdef _WIN32 +#define rnp_closedir _wclosedir +int rnp_mkdir(const char *path); +_WDIR * rnp_opendir(const char *path); +std::string rnp_readdir_name(_WDIR *dir); +#else +#define rnp_closedir closedir +DIR * rnp_opendir(const char *path); +std::string rnp_readdir_name(DIR *dir); +#endif +#ifdef _WIN32 +#define RNP_MKDIR(pathname, mode) rnp_mkdir(pathname) +#else +#define RNP_MKDIR(pathname, mode) mkdir(pathname, mode) +#endif + +#ifdef _MSC_VER +#define R_OK 4 /* Test for read permission. */ +#define W_OK 2 /* Test for write permission. */ +#define F_OK 0 /* Test for existence. */ +#endif + +/** @private + * generate a temporary file name based on TMPL. TMPL must match the + * rules for mk[s]temp (i.e. end in "XXXXXX"). The name constructed + * does not exist at the time of the call to mkstemp. TMPL is + * overwritten with the result.get the list item at specified index + * + * @param tmpl filename template + * @return file descriptor of newly created and opened file, or -1 on error + **/ +int rnp_mkstemp(char *tmpl); + +namespace rnp { +namespace path { +inline char separator(); +bool exists(const std::string &path, bool is_dir = false); +bool empty(const std::string &path); +std::string HOME(const std::string &sdir = ""); +std::string append(const std::string &path, const std::string &name); +} // namespace path +} // namespace rnp + +#endif diff --git a/comm/third_party/rnp/src/common/getoptwin.h b/comm/third_party/rnp/src/common/getoptwin.h new file mode 100644 index 0000000000..84a9583a5d --- /dev/null +++ b/comm/third_party/rnp/src/common/getoptwin.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2020 [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef _GETOPTWIN_H +#define _GETOPTWIN_H 1 + +#include <getopt.h> + +#define _BEGIN_EXTERN_C extern "C" { +#define _END_EXTERN_C } + +#endif /* getoptwin.h */
\ No newline at end of file diff --git a/comm/third_party/rnp/src/common/str-utils.cpp b/comm/third_party/rnp/src/common/str-utils.cpp new file mode 100644 index 0000000000..245e31e1f3 --- /dev/null +++ b/comm/third_party/rnp/src/common/str-utils.cpp @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2017 [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** String utilities + * @file + */ + +#include <cstddef> +#include <cstring> +#include <cctype> +#include <stdexcept> +#include "str-utils.h" +#ifdef _WIN32 +#include <locale> +#include <codecvt> +#endif + +using std::size_t; +using std::strlen; + +namespace rnp { +char * +strip_eol(char *s) +{ + size_t len = strlen(s); + + while ((len > 0) && ((s[len - 1] == '\n') || (s[len - 1] == '\r'))) { + s[--len] = '\0'; + } + + return s; +} + +bool +strip_eol(std::string &s) +{ + size_t len = s.size(); + while (len && ((s[len - 1] == '\n') || (s[len - 1] == '\r'))) { + len--; + } + if (len == s.size()) { + return false; + } + s.resize(len); + return true; +} + +bool +is_blank_line(const char *line, size_t len) +{ + for (size_t i = 0; i < len && line[i]; i++) { + if (line[i] != ' ' && line[i] != '\t' && line[i] != '\r') { + return false; + } + } + return true; +} + +bool +str_case_eq(const char *s1, const char *s2) +{ + while (*s1 && *s2) { + if (std::tolower(*s1) != std::tolower(*s2)) { + return false; + } + s1++; + s2++; + } + return !*s1 && !*s2; +} + +bool +str_case_eq(const std::string &s1, const std::string &s2) +{ + if (s1.size() != s2.size()) { + return false; + } + return str_case_eq(s1.c_str(), s2.c_str()); +} + +static size_t +hex_prefix_len(const std::string &str) +{ + if ((str.length() >= 2) && (str[0] == '0') && ((str[1] == 'x') || (str[1] == 'X'))) { + return 2; + } + return 0; +} + +bool +is_hex(const std::string &s) +{ + for (size_t i = hex_prefix_len(s); i < s.length(); i++) { + auto &ch = s[i]; + if ((ch >= '0') && (ch <= '9')) { + continue; + } + if ((ch >= 'a') && (ch <= 'f')) { + continue; + } + if ((ch >= 'A') && (ch <= 'F')) { + continue; + } + if ((ch == ' ') || (ch == '\t')) { + continue; + } + return false; + } + return true; +} + +std::string +strip_hex(const std::string &s) +{ + std::string res = ""; + for (size_t idx = hex_prefix_len(s); idx < s.length(); idx++) { + auto ch = s[idx]; + if ((ch == ' ') || (ch == '\t')) { + continue; + } + res.push_back(ch); + } + return res; +} + +char * +lowercase(char *s) +{ + if (!s) { + return s; + } + for (char *ptr = s; *ptr; ++ptr) { + *ptr = tolower(*ptr); + } + return s; +} + +bool +str_to_int(const std::string &s, int &val) +{ + for (const char &ch : s) { + if ((ch < '0') || (ch > '9')) { + return false; + } + } + try { + val = std::stoi(s); + } catch (std::out_of_range const &ex) { + return false; + } + return true; +} + +bool +is_slash(char c) +{ + return (c == '/') || (c == '\\'); +} + +} // namespace rnp + +#ifdef _WIN32 +std::wstring +wstr_from_utf8(const char *s) +{ + std::wstring_convert<std::codecvt_utf8<wchar_t>> utf8conv; + return utf8conv.from_bytes(s); +} + +std::wstring +wstr_from_utf8(const char *first, const char *last) +{ + std::wstring_convert<std::codecvt_utf8<wchar_t>> utf8conv; + return utf8conv.from_bytes(first, last); +} + +std::wstring +wstr_from_utf8(const std::string &s) +{ + return wstr_from_utf8(s.c_str()); +} + +std::string +wstr_to_utf8(const wchar_t *ws) +{ + std::wstring_convert<std::codecvt_utf8<wchar_t>> utf8conv; + return utf8conv.to_bytes(ws); +} + +std::string +wstr_to_utf8(const std::wstring &ws) +{ + return wstr_to_utf8(ws.c_str()); +} +#endif diff --git a/comm/third_party/rnp/src/common/str-utils.h b/comm/third_party/rnp/src/common/str-utils.h new file mode 100644 index 0000000000..9a4eb734a2 --- /dev/null +++ b/comm/third_party/rnp/src/common/str-utils.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2019-2020 [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RNP_STR_UTILS_H_ +#define RNP_STR_UTILS_H_ + +#include <string> + +namespace rnp { +char *strip_eol(char *s); +/** + * @brief Strip EOL characters from the string's end. + * + * @param s string to check + * @return true if EOL was found and stripped, or false otherwise. + */ +bool strip_eol(std::string &s); +bool is_blank_line(const char *line, size_t len); +bool str_case_eq(const char *s1, const char *s2); +bool str_case_eq(const std::string &s1, const std::string &s2); + +bool is_hex(const std::string &s); +std::string strip_hex(const std::string &s); + +/** + * @brief Convert string to lowercase and return it. + */ +char *lowercase(char *s); +bool str_to_int(const std::string &s, int &val); +bool is_slash(char c); +} // namespace rnp +#ifdef _WIN32 +#include <string> +std::wstring wstr_from_utf8(const char *s); +std::wstring wstr_from_utf8(const char *first, const char *last); +std::wstring wstr_from_utf8(const std::string &s); +std::string wstr_to_utf8(const wchar_t *ws); +std::string wstr_to_utf8(const std::wstring &ws); +#endif // _WIN32 +#endif diff --git a/comm/third_party/rnp/src/common/time-utils.cpp b/comm/third_party/rnp/src/common/time-utils.cpp new file mode 100644 index 0000000000..83d934aff1 --- /dev/null +++ b/comm/third_party/rnp/src/common/time-utils.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2021 [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** Time utilities + * @file + */ + +#include <stdint.h> +#include "time-utils.h" + +static inline time_t +adjust_time32(time_t t) +{ + return (sizeof(t) == 4 && t < 0) ? INT32_MAX : t; +} + +bool +rnp_y2k38_warning(time_t t) +{ + return (sizeof(t) == 4 && (t < 0 || t == INT32_MAX)); +} + +time_t +rnp_mktime(struct tm *tm) +{ + return adjust_time32(mktime(tm)); +} + +void +rnp_gmtime(time_t t, struct tm &tm) +{ + time_t adjusted = adjust_time32(t); +#ifndef _WIN32 + gmtime_r(&adjusted, &tm); +#else + (void) gmtime_s(&tm, &adjusted); +#endif +} + +void +rnp_localtime(time_t t, struct tm &tm) +{ + time_t adjusted = adjust_time32(t); +#ifndef _WIN32 + localtime_r(&adjusted, &tm); +#else + (void) localtime_s(&tm, &adjusted); +#endif +} + +std::string +rnp_ctime(time_t t) +{ + char time_buf[26]; + time_t adjusted = adjust_time32(t); +#ifndef _WIN32 + (void) ctime_r(&adjusted, time_buf); +#else + (void) ctime_s(time_buf, sizeof(time_buf), &adjusted); +#endif + return std::string(time_buf); +} + +time_t +rnp_timeadd(time_t t1, time_t t2) +{ + if (sizeof(time_t) == 4) { + if (t1 < 0 || t2 < 0) { + return INT32_MAX; + } + return adjust_time32(t1 + t2); + } + return t1 + t2; +} diff --git a/comm/third_party/rnp/src/common/time-utils.h b/comm/third_party/rnp/src/common/time-utils.h new file mode 100644 index 0000000000..525309e740 --- /dev/null +++ b/comm/third_party/rnp/src/common/time-utils.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2021 [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RNP_TIME_UTILS_H_ +#define RNP_TIME_UTILS_H_ + +#include <time.h> +#include <string> +time_t rnp_mktime(struct tm *tm); +void rnp_gmtime(time_t t, struct tm &tm); +void rnp_localtime(time_t t, struct tm &tm); +bool rnp_y2k38_warning(time_t t); +std::string rnp_ctime(time_t t); +time_t rnp_timeadd(time_t t1, time_t t2); +#endif diff --git a/comm/third_party/rnp/src/common/uniwin.h b/comm/third_party/rnp/src/common/uniwin.h new file mode 100644 index 0000000000..095c325bcb --- /dev/null +++ b/comm/third_party/rnp/src/common/uniwin.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2020 [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#if !defined(_UNISTD_H) && defined(_MSC_VER) +#define _UNISTD_H 1 + +/* Taken partially from + * https://stackoverflow.com/a/826027/1202830 + */ + +#include <io.h> +#include "getoptwin.h" +#include <direct.h> /* for _getcwd() and _chdir() */ + +#ifdef _WIN64 +#define ssize_t __int64 +#else +#define ssize_t long +#endif + +#define STDIN_FILENO 0 +#define STDOUT_FILENO 1 +#define STDERR_FILENO 2 + +#define NOMINMAX 1 /* to retain std::min and std::max */ +#include <windows.h> +#include <dirent.h> /* for S_ISREG and S_ISDIR */ + +#define strncasecmp strnicmp +#define strcasecmp stricmp + +#ifndef MAXPATHLEN +#define MAXPATHLEN _MAX_PATH +#endif + +typedef unsigned short mode_t; + +#endif
\ No newline at end of file diff --git a/comm/third_party/rnp/src/examples/CMakeLists.txt b/comm/third_party/rnp/src/examples/CMakeLists.txt new file mode 100644 index 0000000000..8cf1e72b25 --- /dev/null +++ b/comm/third_party/rnp/src/examples/CMakeLists.txt @@ -0,0 +1,140 @@ +# Copyright (c) 2018-2020 Ribose Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS +# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +if(MSVC) + # remove extra ${Configuration} subfolder + set(ArchiveOutputDir ${CMAKE_BINARY_DIR}\\src\\examples) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${ArchiveOutputDir}) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_MINSIZEREL ${ArchiveOutputDir}) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE ${ArchiveOutputDir}) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELWITHDEBINFO ${ArchiveOutputDir}) + + set(RuntimeOutputDir ${CMAKE_BINARY_DIR}\\src\\examples) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${RuntimeOutputDir}) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL ${RuntimeOutputDir}) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${RuntimeOutputDir}) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO ${RuntimeOutputDir}) + + find_path(GETOPT_INCLUDE_DIR + NAMES getopt.h + ) + find_library(GETOPT_LIBRARY + NAMES getopt + ) +endif() + +add_executable(generate generate.c) + +target_include_directories(generate + PRIVATE + "${PROJECT_SOURCE_DIR}/src" + "${PROJECT_SOURCE_DIR}/src/lib" +) + +target_link_libraries(generate + PRIVATE + librnp +) + +add_executable(encrypt encrypt.c) + +target_include_directories(encrypt + PRIVATE + "${PROJECT_SOURCE_DIR}/src" + "${PROJECT_SOURCE_DIR}/src/lib" +) + +target_link_libraries(encrypt + PRIVATE + librnp +) + +add_executable(decrypt decrypt.c) + +target_include_directories(decrypt + PRIVATE + "${PROJECT_SOURCE_DIR}/src" + "${PROJECT_SOURCE_DIR}/src/lib" +) + +target_link_libraries(decrypt + PRIVATE + librnp +) + +add_executable(sign sign.c) + +target_include_directories(sign + PRIVATE + "${PROJECT_SOURCE_DIR}/src" + "${PROJECT_SOURCE_DIR}/src/lib" +) + +target_link_libraries(sign + PRIVATE + librnp +) + +add_executable(verify verify.c) + +target_include_directories(verify + PRIVATE + "${PROJECT_SOURCE_DIR}/src" + "${PROJECT_SOURCE_DIR}/src/lib" +) + +target_link_libraries(verify + PRIVATE + librnp +) + +add_executable(dump dump.c) + +target_include_directories(dump + PRIVATE + "${PROJECT_SOURCE_DIR}/src" + "${PROJECT_SOURCE_DIR}/src/lib" +) + +target_link_libraries(dump + PRIVATE + librnp +) + +if(MSVC) + target_include_directories(dump + PRIVATE + "${GETOPT_INCLUDE_DIR}" + ) + target_link_libraries(dump + PRIVATE + "${GETOPT_LIBRARY}" + ) +endif() + +if (ENABLE_SANITIZERS) + foreach(tgt generate encrypt decrypt sign verify dump) + set_target_properties(${tgt} PROPERTIES LINKER_LANGUAGE CXX) + endforeach() +endif() diff --git a/comm/third_party/rnp/src/examples/README.md b/comm/third_party/rnp/src/examples/README.md new file mode 100644 index 0000000000..84bdc2ec7b --- /dev/null +++ b/comm/third_party/rnp/src/examples/README.md @@ -0,0 +1,5 @@ +# RNP C API usage samples + +This folder includes examples of RNP library usage for developers. + +See [Using the RNP C API](/docs/c-usage.adoc) for more details. diff --git a/comm/third_party/rnp/src/examples/decrypt.c b/comm/third_party/rnp/src/examples/decrypt.c new file mode 100644 index 0000000000..5454a4b728 --- /dev/null +++ b/comm/third_party/rnp/src/examples/decrypt.c @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2018, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <rnp/rnp.h> +#include <string.h> + +#define RNP_SUCCESS 0 + +/* sample pass provider implementation, which always return 'password' for key decryption and + * 'encpassword' when password is needed for file decryption. You may ask for password via + * stdin, or choose password based on key properties, whatever else */ +static bool +example_pass_provider(rnp_ffi_t ffi, + void * app_ctx, + rnp_key_handle_t key, + const char * pgp_context, + char buf[], + size_t buf_len) +{ + if (!strcmp(pgp_context, "decrypt (symmetric)")) { + strncpy(buf, "encpassword", buf_len); + return true; + } + if (!strcmp(pgp_context, "decrypt")) { + strncpy(buf, "password", buf_len); + return true; + } + + return false; +} + +static int +ffi_decrypt(bool usekeys) +{ + rnp_ffi_t ffi = NULL; + rnp_input_t keyfile = NULL; + rnp_input_t input = NULL; + rnp_output_t output = NULL; + uint8_t * buf = NULL; + size_t buf_len = 0; + int result = 1; + + /* initialize FFI object */ + if (rnp_ffi_create(&ffi, "GPG", "GPG") != RNP_SUCCESS) { + return result; + } + + /* check whether we want to use key or password for decryption */ + if (usekeys) { + /* load secret keyring, as it is required for public-key decryption. However, you may + * need to load public keyring as well to validate key's signatures. */ + if (rnp_input_from_path(&keyfile, "secring.pgp") != RNP_SUCCESS) { + fprintf(stdout, "failed to open secring.pgp. Did you run ./generate sample?\n"); + goto finish; + } + + /* we may use RNP_LOAD_SAVE_SECRET_KEYS | RNP_LOAD_SAVE_PUBLIC_KEYS as well*/ + if (rnp_load_keys(ffi, "GPG", keyfile, RNP_LOAD_SAVE_SECRET_KEYS) != RNP_SUCCESS) { + fprintf(stdout, "failed to read secring.pgp\n"); + goto finish; + } + rnp_input_destroy(keyfile); + keyfile = NULL; + } + + /* set the password provider */ + rnp_ffi_set_pass_provider(ffi, example_pass_provider, NULL); + + /* create file input and memory output objects for the encrypted message and decrypted + * message */ + if (rnp_input_from_path(&input, "encrypted.asc") != RNP_SUCCESS) { + fprintf(stdout, "failed to create input object\n"); + goto finish; + } + + if (rnp_output_to_memory(&output, 0) != RNP_SUCCESS) { + fprintf(stdout, "failed to create output object\n"); + goto finish; + } + + if (rnp_decrypt(ffi, input, output) != RNP_SUCCESS) { + fprintf(stdout, "public-key decryption failed\n"); + goto finish; + } + + /* get the decrypted message from the output structure */ + if (rnp_output_memory_get_buf(output, &buf, &buf_len, false) != RNP_SUCCESS) { + goto finish; + } + fprintf(stdout, + "Decrypted message (%s):\n%.*s\n", + usekeys ? "with key" : "with password", + (int) buf_len, + buf); + + result = 0; +finish: + rnp_input_destroy(keyfile); + rnp_input_destroy(input); + rnp_output_destroy(output); + rnp_ffi_destroy(ffi); + return result; +} + +int +main(int argc, char **argv) +{ + int res; + res = ffi_decrypt(true); + if (res) { + return res; + } + res = ffi_decrypt(false); + return res; +}
\ No newline at end of file diff --git a/comm/third_party/rnp/src/examples/dump.c b/comm/third_party/rnp/src/examples/dump.c new file mode 100644 index 0000000000..dc24644774 --- /dev/null +++ b/comm/third_party/rnp/src/examples/dump.c @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2019, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdio.h> +#ifdef _MSC_VER +#include "uniwin.h" +#else +#include <unistd.h> /* getopt() */ +#include <libgen.h> /* basename() */ +#endif +#include <rnp/rnp.h> + +#define PFX "dump: " + +static void +print_usage(char *program_name) +{ + const char *program_basename; +#ifdef _MSC_VER + char fname[_MAX_FNAME]; + program_basename = fname; + _splitpath_s(program_name, NULL, 0, NULL, 0, fname, _MAX_FNAME, NULL, 0); +#else + program_basename = basename(program_name); +#endif + + fprintf(stderr, + "dump: " PFX "Program dumps PGP packets. \n\nUsage:\n" + "\t%s [-d|-h] [input.pgp]\n" + "\t -d : indicates whether to print packet content. Data is represented as hex\n" + "\t -m : dump mpi values\n" + "\t -g : dump key fingerprints and grips\n" + "\t -j : JSON output\n" + "\t -h : prints help and exists\n", + program_basename); +} + +static bool +stdin_reader(void *app_ctx, void *buf, size_t len, size_t *readres) +{ + ssize_t res = read(STDIN_FILENO, buf, len); + if (res < 0) { + return false; + } + *readres = res; + return true; +} + +static bool +stdout_writer(void *app_ctx, const void *buf, size_t len) +{ + ssize_t wlen = write(STDOUT_FILENO, buf, len); + return (wlen >= 0) && (size_t) wlen == len; +} + +int +main(int argc, char *const argv[]) +{ + char * input_file = NULL; + uint32_t flags = 0; + uint32_t jflags = 0; + bool json = false; + + /* Parse command line options: + -i input_file [mandatory]: specifies name of the file with PGP packets + -d : indicates whether to dump whole packet content + -m : dump mpi contents + -g : dump key grips and fingerprints + -j : JSON output + -h : prints help and exists + */ + int opt = 0; + while ((opt = getopt(argc, argv, "dmgjh")) != -1) { + switch (opt) { + case 'd': + flags |= RNP_DUMP_RAW; + jflags |= RNP_JSON_DUMP_RAW; + break; + case 'm': + flags |= RNP_DUMP_MPI; + jflags |= RNP_JSON_DUMP_MPI; + break; + case 'g': + flags |= RNP_DUMP_GRIP; + jflags |= RNP_JSON_DUMP_GRIP; + break; + case 'j': + json = true; + break; + default: + print_usage(argv[0]); + return 1; + } + } + + /* Check whether we have input file */ + if (optind < argc) { + input_file = argv[optind]; + } + + rnp_input_t input = NULL; + rnp_result_t ret = 0; + if (input_file) { + ret = rnp_input_from_path(&input, input_file); + } else { + ret = rnp_input_from_callback(&input, stdin_reader, NULL, NULL); + } + if (ret) { + fprintf(stderr, "failed to open source: error 0x%x\n", (int) ret); + return 1; + } + + if (!json) { + rnp_output_t output = NULL; + ret = rnp_output_to_callback(&output, stdout_writer, NULL, NULL); + if (ret) { + fprintf(stderr, "failed to open stdout: error 0x%x\n", (int) ret); + rnp_input_destroy(input); + return 1; + } + ret = rnp_dump_packets_to_output(input, output, flags); + rnp_output_destroy(output); + } else { + char *json = NULL; + ret = rnp_dump_packets_to_json(input, jflags, &json); + if (!ret) { + fprintf(stdout, "%s\n", json); + } + rnp_buffer_destroy(json); + } + rnp_input_destroy(input); + + /* Inform in case of error occurred during parsing */ + if (ret) { + fprintf(stderr, "Operation failed [error code: 0x%X]\n", (int) ret); + return 1; + } + + return 0; +} diff --git a/comm/third_party/rnp/src/examples/encrypt.c b/comm/third_party/rnp/src/examples/encrypt.c new file mode 100644 index 0000000000..0b07fd40fc --- /dev/null +++ b/comm/third_party/rnp/src/examples/encrypt.c @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2018, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <string.h> +#include <time.h> +#include <rnp/rnp.h> + +#define RNP_SUCCESS 0 + +static int +ffi_encrypt() +{ + rnp_ffi_t ffi = NULL; + rnp_op_encrypt_t encrypt = NULL; + rnp_key_handle_t key = NULL; + rnp_input_t keyfile = NULL; + rnp_input_t input = NULL; + rnp_output_t output = NULL; + const char * message = "RNP encryption sample message"; + int result = 1; + + /* initialize FFI object */ + if (rnp_ffi_create(&ffi, "GPG", "GPG") != RNP_SUCCESS) { + return result; + } + + /* load public keyring - we do not need secret for encryption */ + if (rnp_input_from_path(&keyfile, "pubring.pgp") != RNP_SUCCESS) { + fprintf(stdout, "failed to open pubring.pgp. Did you run ./generate sample?\n"); + goto finish; + } + + /* we may use RNP_LOAD_SAVE_SECRET_KEYS | RNP_LOAD_SAVE_PUBLIC_KEYS as well */ + if (rnp_load_keys(ffi, "GPG", keyfile, RNP_LOAD_SAVE_PUBLIC_KEYS) != RNP_SUCCESS) { + fprintf(stdout, "failed to read pubring.pgp\n"); + goto finish; + } + rnp_input_destroy(keyfile); + keyfile = NULL; + + /* create memory input and file output objects for the message and encrypted message */ + if (rnp_input_from_memory(&input, (uint8_t *) message, strlen(message), false) != + RNP_SUCCESS) { + fprintf(stdout, "failed to create input object\n"); + goto finish; + } + + if (rnp_output_to_path(&output, "encrypted.asc") != RNP_SUCCESS) { + fprintf(stdout, "failed to create output object\n"); + goto finish; + } + + /* create encryption operation */ + if (rnp_op_encrypt_create(&encrypt, ffi, input, output) != RNP_SUCCESS) { + fprintf(stdout, "failed to create encrypt operation\n"); + goto finish; + } + + /* setup encryption parameters */ + rnp_op_encrypt_set_armor(encrypt, true); + rnp_op_encrypt_set_file_name(encrypt, "message.txt"); + rnp_op_encrypt_set_file_mtime(encrypt, (uint32_t) time(NULL)); + rnp_op_encrypt_set_compression(encrypt, "ZIP", 6); + rnp_op_encrypt_set_cipher(encrypt, RNP_ALGNAME_AES_256); + rnp_op_encrypt_set_aead(encrypt, "None"); + + /* locate recipient's key and add it to the operation context. While we search by userid + * (which is easier), you can search by keyid, fingerprint or grip. */ + if (rnp_locate_key(ffi, "userid", "rsa@key", &key) != RNP_SUCCESS) { + fprintf(stdout, "failed to locate recipient key rsa@key.\n"); + goto finish; + } + + if (rnp_op_encrypt_add_recipient(encrypt, key) != RNP_SUCCESS) { + fprintf(stdout, "failed to add recipient\n"); + goto finish; + } + rnp_key_handle_destroy(key); + key = NULL; + + /* add encryption password as well */ + if (rnp_op_encrypt_add_password( + encrypt, "encpassword", RNP_ALGNAME_SHA256, 0, RNP_ALGNAME_AES_256) != RNP_SUCCESS) { + fprintf(stdout, "failed to add encryption password\n"); + goto finish; + } + + /* execute encryption operation */ + if (rnp_op_encrypt_execute(encrypt) != RNP_SUCCESS) { + fprintf(stdout, "encryption failed\n"); + goto finish; + } + + fprintf(stdout, "Encryption succeeded. Encrypted message written to file encrypted.asc\n"); + result = 0; +finish: + rnp_op_encrypt_destroy(encrypt); + rnp_input_destroy(keyfile); + rnp_input_destroy(input); + rnp_output_destroy(output); + rnp_key_handle_destroy(key); + rnp_ffi_destroy(ffi); + return result; +} + +int +main(int argc, char **argv) +{ + return ffi_encrypt(); +} diff --git a/comm/third_party/rnp/src/examples/generate.c b/comm/third_party/rnp/src/examples/generate.c new file mode 100644 index 0000000000..979e63e878 --- /dev/null +++ b/comm/third_party/rnp/src/examples/generate.c @@ -0,0 +1,330 @@ +/* + * Copyright (c) 2018, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <string.h> +#include <rnp/rnp.h> + +#define RNP_SUCCESS 0 + +/* RSA key JSON description. 31536000 = 1 year expiration, 15768000 = half year */ +const char *RSA_KEY_DESC = "{\ + 'primary': {\ + 'type': 'RSA',\ + 'length': 2048,\ + 'userid': 'rsa@key',\ + 'expiration': 31536000,\ + 'usage': ['sign'],\ + 'protection': {\ + 'cipher': 'AES256',\ + 'hash': 'SHA256'\ + }\ + },\ + 'sub': {\ + 'type': 'RSA',\ + 'length': 2048,\ + 'expiration': 15768000,\ + 'usage': ['encrypt'],\ + 'protection': {\ + 'cipher': 'AES256',\ + 'hash': 'SHA256'\ + }\ + }\ +}"; + +const char *CURVE_25519_KEY_DESC = "{\ + 'primary': {\ + 'type': 'EDDSA',\ + 'userid': '25519@key',\ + 'expiration': 0,\ + 'usage': ['sign'],\ + 'protection': {\ + 'cipher': 'AES256',\ + 'hash': 'SHA256'\ + }\ + },\ + 'sub': {\ + 'type': 'ECDH',\ + 'curve': 'Curve25519',\ + 'expiration': 15768000,\ + 'usage': ['encrypt'],\ + 'protection': {\ + 'cipher': 'AES256',\ + 'hash': 'SHA256'\ + }\ + }\ +}"; + +/* basic pass provider implementation, which always return 'password' for key protection. +You may ask for password via stdin, or choose password based on key properties, whatever else +*/ +static bool +example_pass_provider(rnp_ffi_t ffi, + void * app_ctx, + rnp_key_handle_t key, + const char * pgp_context, + char buf[], + size_t buf_len) +{ + if (strcmp(pgp_context, "protect")) { + return false; + } + + strncpy(buf, "password", buf_len); + return true; +} + +/* this simple helper function just prints armored key, searched by userid, to stdout */ +static bool +ffi_print_key(rnp_ffi_t ffi, const char *uid, bool secret) +{ + rnp_output_t keydata = NULL; + rnp_key_handle_t key = NULL; + uint32_t flags = RNP_KEY_EXPORT_ARMORED | RNP_KEY_EXPORT_SUBKEYS; + uint8_t * buf = NULL; + size_t buf_len = 0; + bool result = false; + + /* you may search for the key via userid, keyid, fingerprint, grip */ + if (rnp_locate_key(ffi, "userid", uid, &key) != RNP_SUCCESS) { + return false; + } + + if (!key) { + return false; + } + + /* create in-memory output structure to later use buffer */ + if (rnp_output_to_memory(&keydata, 0) != RNP_SUCCESS) { + goto finish; + } + + flags = flags | (secret ? RNP_KEY_EXPORT_SECRET : RNP_KEY_EXPORT_PUBLIC); + if (rnp_key_export(key, keydata, flags) != RNP_SUCCESS) { + goto finish; + } + + /* get key's contents from the output structure */ + if (rnp_output_memory_get_buf(keydata, &buf, &buf_len, false) != RNP_SUCCESS) { + goto finish; + } + fprintf(stdout, "%.*s", (int) buf_len, buf); + + result = true; +finish: + rnp_key_handle_destroy(key); + rnp_output_destroy(keydata); + return result; +} + +static bool +ffi_export_key(rnp_ffi_t ffi, const char *uid, bool secret) +{ + rnp_output_t keyfile = NULL; + rnp_key_handle_t key = NULL; + uint32_t flags = RNP_KEY_EXPORT_ARMORED | RNP_KEY_EXPORT_SUBKEYS; + char filename[32] = {0}; + char * keyid = NULL; + bool result = false; + + /* you may search for the key via userid, keyid, fingerprint, grip */ + if (rnp_locate_key(ffi, "userid", uid, &key) != RNP_SUCCESS) { + return false; + } + + if (!key) { + return false; + } + + /* get key's id and build filename */ + if (rnp_key_get_keyid(key, &keyid) != RNP_SUCCESS) { + goto finish; + } + snprintf(filename, sizeof(filename), "key-%s-%s.asc", keyid, secret ? "sec" : "pub"); + rnp_buffer_destroy(keyid); + + /* create file output structure */ + if (rnp_output_to_path(&keyfile, filename) != RNP_SUCCESS) { + goto finish; + } + + flags = flags | (secret ? RNP_KEY_EXPORT_SECRET : RNP_KEY_EXPORT_PUBLIC); + if (rnp_key_export(key, keyfile, flags) != RNP_SUCCESS) { + goto finish; + } + + result = true; +finish: + rnp_key_handle_destroy(key); + rnp_output_destroy(keyfile); + return result; +} + +/* this example function generates RSA/RSA and Eddsa/X25519 keypairs */ +static int +ffi_generate_keys() +{ + rnp_ffi_t ffi = NULL; + rnp_output_t keyfile = NULL; + char * key_grips = NULL; + int result = 1; + + /* initialize FFI object */ + if (rnp_ffi_create(&ffi, "GPG", "GPG") != RNP_SUCCESS) { + return result; + } + + /* set password provider */ + if (rnp_ffi_set_pass_provider(ffi, example_pass_provider, NULL)) { + goto finish; + } + + /* generate EDDSA/X25519 keypair */ + if (rnp_generate_key_json(ffi, CURVE_25519_KEY_DESC, &key_grips) != RNP_SUCCESS) { + fprintf(stdout, "failed to generate eddsa key\n"); + goto finish; + } + + fprintf(stdout, "Generated 25519 key/subkey:\n%s\n", key_grips); + /* destroying key_grips buffer is our obligation */ + rnp_buffer_destroy(key_grips); + key_grips = NULL; + + /* generate RSA keypair */ + if (rnp_generate_key_json(ffi, RSA_KEY_DESC, &key_grips) != RNP_SUCCESS) { + fprintf(stdout, "failed to generate rsa key\n"); + goto finish; + } + + fprintf(stdout, "Generated RSA key/subkey:\n%s\n", key_grips); + rnp_buffer_destroy(key_grips); + key_grips = NULL; + + /* create file output object and save public keyring with generated keys, overwriting + * previous file if any. You may use rnp_output_to_memory() here as well. */ + if (rnp_output_to_path(&keyfile, "pubring.pgp") != RNP_SUCCESS) { + fprintf(stdout, "failed to initialize pubring.pgp writing\n"); + goto finish; + } + + if (rnp_save_keys(ffi, "GPG", keyfile, RNP_LOAD_SAVE_PUBLIC_KEYS) != RNP_SUCCESS) { + fprintf(stdout, "failed to save pubring\n"); + goto finish; + } + + rnp_output_destroy(keyfile); + keyfile = NULL; + + /* create file output object and save secret keyring with generated keys */ + if (rnp_output_to_path(&keyfile, "secring.pgp") != RNP_SUCCESS) { + fprintf(stdout, "failed to initialize secring.pgp writing\n"); + goto finish; + } + + if (rnp_save_keys(ffi, "GPG", keyfile, RNP_LOAD_SAVE_SECRET_KEYS) != RNP_SUCCESS) { + fprintf(stdout, "failed to save secring\n"); + goto finish; + } + + rnp_output_destroy(keyfile); + keyfile = NULL; + + result = 0; +finish: + rnp_buffer_destroy(key_grips); + rnp_output_destroy(keyfile); + rnp_ffi_destroy(ffi); + return result; +} + +static int +ffi_output_keys() +{ + rnp_ffi_t ffi = NULL; + rnp_input_t keyfile = NULL; + int result = 2; + + /* initialize FFI object */ + if (rnp_ffi_create(&ffi, "GPG", "GPG") != RNP_SUCCESS) { + return result; + } + + /* load keyrings */ + if (rnp_input_from_path(&keyfile, "pubring.pgp") != RNP_SUCCESS) { + fprintf(stdout, "failed to open pubring.pgp\n"); + goto finish; + } + + /* actually, we may use 0 instead of RNP_LOAD_SAVE_PUBLIC_KEYS, to not check key types */ + if (rnp_load_keys(ffi, "GPG", keyfile, RNP_LOAD_SAVE_PUBLIC_KEYS) != RNP_SUCCESS) { + fprintf(stdout, "failed to read pubring.pgp\n"); + goto finish; + } + rnp_input_destroy(keyfile); + keyfile = NULL; + + if (rnp_input_from_path(&keyfile, "secring.pgp") != RNP_SUCCESS) { + fprintf(stdout, "failed to open secring.pgp\n"); + goto finish; + } + + if (rnp_load_keys(ffi, "GPG", keyfile, RNP_LOAD_SAVE_SECRET_KEYS) != RNP_SUCCESS) { + fprintf(stdout, "failed to read secring.pgp\n"); + goto finish; + } + rnp_input_destroy(keyfile); + keyfile = NULL; + + /* print armored keys to the stdout */ + if (!ffi_print_key(ffi, "rsa@key", false) || !ffi_print_key(ffi, "rsa@key", true) || + !ffi_print_key(ffi, "25519@key", false) || !ffi_print_key(ffi, "25519@key", true)) { + fprintf(stdout, "failed to print armored key(s)\n"); + goto finish; + } + + /* write armored keys to the files, named key-<keyid>-pub.asc/named key-<keyid>-sec.asc */ + if (!ffi_export_key(ffi, "rsa@key", false) || !ffi_export_key(ffi, "rsa@key", true) || + !ffi_export_key(ffi, "25519@key", false) || !ffi_export_key(ffi, "25519@key", true)) { + fprintf(stdout, "failed to write armored key(s) to file\n"); + goto finish; + } + + result = 0; +finish: + rnp_input_destroy(keyfile); + rnp_ffi_destroy(ffi); + return result; +} + +int +main(int argc, char **argv) +{ + int res = ffi_generate_keys(); + if (res) { + return res; + } + res = ffi_output_keys(); + return res; +} diff --git a/comm/third_party/rnp/src/examples/sign.c b/comm/third_party/rnp/src/examples/sign.c new file mode 100644 index 0000000000..678dd910e0 --- /dev/null +++ b/comm/third_party/rnp/src/examples/sign.c @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2018, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <rnp/rnp.h> +#include <string.h> +#include <time.h> + +#define RNP_SUCCESS 0 + +/* sample pass provider implementation, which always return 'password' */ +static bool +example_pass_provider(rnp_ffi_t ffi, + void * app_ctx, + rnp_key_handle_t key, + const char * pgp_context, + char buf[], + size_t buf_len) +{ + strncpy(buf, "password", buf_len); + return true; +} + +static int +ffi_sign() +{ + rnp_ffi_t ffi = NULL; + rnp_input_t keyfile = NULL; + rnp_input_t input = NULL; + rnp_output_t output = NULL; + rnp_op_sign_t sign = NULL; + rnp_key_handle_t key = NULL; + const char * message = "RNP signing sample message"; + int result = 1; + + /* initialize FFI object */ + if (rnp_ffi_create(&ffi, "GPG", "GPG") != RNP_SUCCESS) { + return result; + } + + /* load secret keyring, as it is required for signing. However, you may need to load public + * keyring as well to validate key's signatures. */ + if (rnp_input_from_path(&keyfile, "secring.pgp") != RNP_SUCCESS) { + fprintf(stdout, "failed to open secring.pgp. Did you run ./generate sample?\n"); + goto finish; + } + + /* we may use RNP_LOAD_SAVE_SECRET_KEYS | RNP_LOAD_SAVE_PUBLIC_KEYS as well */ + if (rnp_load_keys(ffi, "GPG", keyfile, RNP_LOAD_SAVE_SECRET_KEYS) != RNP_SUCCESS) { + fprintf(stdout, "failed to read secring.pgp\n"); + goto finish; + } + rnp_input_destroy(keyfile); + keyfile = NULL; + + /* set the password provider - we'll need password to unlock secret keys */ + rnp_ffi_set_pass_provider(ffi, example_pass_provider, NULL); + + /* create file input and memory output objects for the encrypted message and decrypted + * message */ + if (rnp_input_from_memory(&input, (uint8_t *) message, strlen(message), false) != + RNP_SUCCESS) { + fprintf(stdout, "failed to create input object\n"); + goto finish; + } + + if (rnp_output_to_path(&output, "signed.asc") != RNP_SUCCESS) { + fprintf(stdout, "failed to create output object\n"); + goto finish; + } + + /* initialize and configure sign operation, use + * rnp_op_sign_create_cleartext/rnp_op_sign_create_detached for cleartext or detached + * signature. */ + if (rnp_op_sign_create(&sign, ffi, input, output) != RNP_SUCCESS) { + fprintf(stdout, "failed to create sign operation\n"); + goto finish; + } + + /* armor, file name, compression */ + rnp_op_sign_set_armor(sign, true); + rnp_op_sign_set_file_name(sign, "message.txt"); + rnp_op_sign_set_file_mtime(sign, (uint32_t) time(NULL)); + rnp_op_sign_set_compression(sign, "ZIP", 6); + /* signatures creation time - by default will be set to the current time as well */ + rnp_op_sign_set_creation_time(sign, (uint32_t) time(NULL)); + /* signatures expiration time - by default will be 0, i.e. never expire */ + rnp_op_sign_set_expiration_time(sign, 365 * 24 * 60 * 60); + /* set hash algorithm - should be compatible for all signatures */ + rnp_op_sign_set_hash(sign, RNP_ALGNAME_SHA256); + + /* now add signatures. First locate the signing key, then add and setup signature */ + /* RSA signature */ + if (rnp_locate_key(ffi, "userid", "rsa@key", &key) != RNP_SUCCESS) { + fprintf(stdout, "failed to locate signing key rsa@key.\n"); + goto finish; + } + + /* we do not need pointer to the signature so passing NULL as the last parameter */ + if (rnp_op_sign_add_signature(sign, key, NULL) != RNP_SUCCESS) { + fprintf(stdout, "failed to add signature for key rsa@key.\n"); + goto finish; + } + + /* do not forget to destroy key handle */ + rnp_key_handle_destroy(key); + key = NULL; + + /* EdDSA signature */ + if (rnp_locate_key(ffi, "userid", "25519@key", &key) != RNP_SUCCESS) { + fprintf(stdout, "failed to locate signing key 25519@key.\n"); + goto finish; + } + + if (rnp_op_sign_add_signature(sign, key, NULL) != RNP_SUCCESS) { + fprintf(stdout, "failed to add signature for key 25519@key.\n"); + goto finish; + } + + rnp_key_handle_destroy(key); + key = NULL; + + /* finally do signing */ + if (rnp_op_sign_execute(sign) != RNP_SUCCESS) { + fprintf(stdout, "failed to sign\n"); + goto finish; + } + + fprintf(stdout, "Signing succeeded. See file signed.asc.\n"); + + result = 0; +finish: + rnp_input_destroy(keyfile); + rnp_key_handle_destroy(key); + rnp_op_sign_destroy(sign); + rnp_input_destroy(input); + rnp_output_destroy(output); + rnp_ffi_destroy(ffi); + return result; +} + +int +main(int argc, char **argv) +{ + return ffi_sign(); +} diff --git a/comm/third_party/rnp/src/examples/verify.c b/comm/third_party/rnp/src/examples/verify.c new file mode 100644 index 0000000000..17f42a82c7 --- /dev/null +++ b/comm/third_party/rnp/src/examples/verify.c @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2018, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <rnp/rnp.h> +#include <string.h> + +#define RNP_SUCCESS 0 + +/* example key provider which loads key from file based on its keyid */ +static void +example_key_provider(rnp_ffi_t ffi, + void * app_ctx, + const char *identifier_type, + const char *identifier, + bool secret) +{ + rnp_input_t input = NULL; + char filename[32] = {0}; + if (strcmp(identifier_type, "keyid")) { + if (strcmp(identifier_type, "fingerprint")) { + fprintf(stdout, "Unsupported key search: %s = %s\n", identifier_type, identifier); + return; + } + /* if we search by fp then keyid is last 16 chars */ + if (strlen(identifier) < 40) { + fprintf(stdout, "Invalid fingerprint: %s\n", identifier); + return; + } + identifier += 24; + } + + snprintf(filename, sizeof(filename), "key-%s-%s.asc", identifier, secret ? "sec" : "pub"); + + if (rnp_input_from_path(&input, filename) != RNP_SUCCESS) { + fprintf(stdout, "failed to open key file %s\n", filename); + return; + } + + if (rnp_load_keys( + ffi, "GPG", input, RNP_LOAD_SAVE_SECRET_KEYS | RNP_LOAD_SAVE_PUBLIC_KEYS) != + RNP_SUCCESS) { + fprintf(stdout, "failed to load key from file %s\n", filename); + } + rnp_input_destroy(input); +} + +static int +ffi_verify() +{ + rnp_ffi_t ffi = NULL; + rnp_op_verify_t verify = NULL; + rnp_input_t input = NULL; + rnp_output_t output = NULL; + uint8_t * buf = NULL; + size_t buf_len = 0; + size_t sigcount = 0; + int result = 1; + + /* initialize FFI object */ + if (rnp_ffi_create(&ffi, "GPG", "GPG") != RNP_SUCCESS) { + return result; + } + + /* we do not load any keys here since we'll use key provider */ + rnp_ffi_set_key_provider(ffi, example_key_provider, NULL); + + /* create file input and memory output objects for the signed message and verified + * message */ + if (rnp_input_from_path(&input, "signed.asc") != RNP_SUCCESS) { + fprintf(stdout, "failed to open file 'signed.asc'. Did you run the sign example?\n"); + goto finish; + } + + if (rnp_output_to_memory(&output, 0) != RNP_SUCCESS) { + fprintf(stdout, "failed to create output object\n"); + goto finish; + } + + if (rnp_op_verify_create(&verify, ffi, input, output) != RNP_SUCCESS) { + fprintf(stdout, "failed to create verification context\n"); + goto finish; + } + + if (rnp_op_verify_execute(verify) != RNP_SUCCESS) { + fprintf(stdout, "failed to execute verification operation\n"); + goto finish; + } + + /* now check signatures and get some info about them */ + if (rnp_op_verify_get_signature_count(verify, &sigcount) != RNP_SUCCESS) { + fprintf(stdout, "failed to get signature count\n"); + goto finish; + } + + for (size_t i = 0; i < sigcount; i++) { + rnp_op_verify_signature_t sig = NULL; + rnp_result_t sigstatus = RNP_SUCCESS; + rnp_key_handle_t key = NULL; + char * keyid = NULL; + + if (rnp_op_verify_get_signature_at(verify, i, &sig) != RNP_SUCCESS) { + fprintf(stdout, "failed to get signature %d\n", (int) i); + goto finish; + } + + if (rnp_op_verify_signature_get_key(sig, &key) != RNP_SUCCESS) { + fprintf(stdout, "failed to get signature's %d key\n", (int) i); + goto finish; + } + + if (rnp_key_get_keyid(key, &keyid) != RNP_SUCCESS) { + fprintf(stdout, "failed to get key id %d\n", (int) i); + rnp_key_handle_destroy(key); + goto finish; + } + + sigstatus = rnp_op_verify_signature_get_status(sig); + fprintf(stdout, "Status for signature from key %s : %d\n", keyid, (int) sigstatus); + rnp_buffer_destroy(keyid); + rnp_key_handle_destroy(key); + } + + /* get the verified message from the output structure */ + if (rnp_output_memory_get_buf(output, &buf, &buf_len, false) != RNP_SUCCESS) { + goto finish; + } + fprintf(stdout, "Verified message:\n%.*s\n", (int) buf_len, buf); + + result = 0; +finish: + rnp_op_verify_destroy(verify); + rnp_input_destroy(input); + rnp_output_destroy(output); + rnp_ffi_destroy(ffi); + return result; +} + +int +main(int argc, char **argv) +{ + return ffi_verify(); +} diff --git a/comm/third_party/rnp/src/fuzzing/CMakeLists.txt b/comm/third_party/rnp/src/fuzzing/CMakeLists.txt new file mode 100644 index 0000000000..c177035e1d --- /dev/null +++ b/comm/third_party/rnp/src/fuzzing/CMakeLists.txt @@ -0,0 +1,145 @@ +# Copyright (c) 2020 Ribose Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS +# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + + +if(NOT DEFINED ENV{LIB_FUZZING_ENGINE}) + add_compile_options(-fsanitize=fuzzer-no-link) + add_link_options(-fsanitize=fuzzer) +else() + # This section is used by OSS-Fuzz + add_link_options($ENV{LIB_FUZZING_ENGINE}) + if($ENV{FUZZING_ENGINE} STREQUAL "afl") + link_libraries(-stdlib=libc++) + endif() +endif() + +add_executable(fuzz_dump dump.c) + +target_include_directories(fuzz_dump + PRIVATE + "${PROJECT_SOURCE_DIR}/src" + "${PROJECT_SOURCE_DIR}/src/lib" +) + +target_link_libraries(fuzz_dump + PRIVATE + librnp +) + +add_executable(fuzz_keyring keyring.c) + +target_include_directories(fuzz_keyring + PRIVATE + "${PROJECT_SOURCE_DIR}/src" + "${PROJECT_SOURCE_DIR}/src/lib" +) + +target_link_libraries(fuzz_keyring + PRIVATE + librnp +) + +add_executable(fuzz_keyimport keyimport.c) + +target_include_directories(fuzz_keyimport + PRIVATE + "${PROJECT_SOURCE_DIR}/src" + "${PROJECT_SOURCE_DIR}/src/lib" +) + +target_link_libraries(fuzz_keyimport + PRIVATE + librnp +) + +add_executable(fuzz_sigimport sigimport.c) + +target_include_directories(fuzz_sigimport + PRIVATE + "${PROJECT_SOURCE_DIR}/src" + "${PROJECT_SOURCE_DIR}/src/lib" +) + +target_link_libraries(fuzz_sigimport + PRIVATE + librnp +) + +add_executable(fuzz_verify verify.c) + +target_include_directories(fuzz_verify + PRIVATE + "${PROJECT_SOURCE_DIR}/src" + "${PROJECT_SOURCE_DIR}/src/lib" +) + +target_link_libraries(fuzz_verify + PRIVATE + librnp +) + +add_executable(fuzz_verify_detached verify_detached.c) + +target_include_directories(fuzz_verify_detached + PRIVATE + "${PROJECT_SOURCE_DIR}/src" + "${PROJECT_SOURCE_DIR}/src/lib" +) + +target_link_libraries(fuzz_verify_detached + PRIVATE + librnp +) + +add_executable(fuzz_keyring_kbx keyring_kbx.c) + +target_include_directories(fuzz_keyring_kbx + PRIVATE + "${PROJECT_SOURCE_DIR}/src" + "${PROJECT_SOURCE_DIR}/src/lib" +) + +target_link_libraries(fuzz_keyring_kbx + PRIVATE + librnp +) + +add_executable(fuzz_keyring_g10 keyring_g10.cpp) + +target_include_directories(fuzz_keyring_g10 + PRIVATE + "${PROJECT_SOURCE_DIR}/src" + "${PROJECT_SOURCE_DIR}/src/lib" +) + +target_link_libraries(fuzz_keyring_g10 + PRIVATE + librnp-static +) + +if (ENABLE_SANITIZERS) + foreach(tgt fuzz_dump fuzz_keyring fuzz_keyimport fuzz_sigimport fuzz_verify fuzz_verify_detached fuzz_keyring_kbx fuzz_keyring_g10) + set_target_properties(${tgt} PROPERTIES LINKER_LANGUAGE CXX) + endforeach() +endif() diff --git a/comm/third_party/rnp/src/fuzzing/dump.c b/comm/third_party/rnp/src/fuzzing/dump.c new file mode 100644 index 0000000000..026bfc2503 --- /dev/null +++ b/comm/third_party/rnp/src/fuzzing/dump.c @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2020, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <rnp/rnp.h> + +#ifdef RNP_RUN_TESTS +int dump_LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); +int +dump_LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +#else +int +LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +#endif +{ + rnp_input_t input = NULL; + (void) rnp_input_from_memory(&input, data, size, false); + rnp_output_t output = NULL; + (void) rnp_output_to_null(&output); + + (void) rnp_dump_packets_to_output(input, output, RNP_DUMP_RAW); + rnp_output_destroy(output); + rnp_input_destroy(input); + + (void) rnp_input_from_memory(&input, data, size, false); + char *json = NULL; + (void) rnp_dump_packets_to_json(input, RNP_DUMP_RAW, &json); + rnp_buffer_destroy(json); + rnp_input_destroy(input); + + return 0; +} diff --git a/comm/third_party/rnp/src/fuzzing/keyimport.c b/comm/third_party/rnp/src/fuzzing/keyimport.c new file mode 100644 index 0000000000..16e1272f8c --- /dev/null +++ b/comm/third_party/rnp/src/fuzzing/keyimport.c @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2020, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <rnp/rnp.h> +#include <rnp/rnp_err.h> + +#ifdef RNP_RUN_TESTS +int keyimport_LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); +int +keyimport_LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +#else +int +LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +#endif +{ + rnp_input_t input = NULL; + rnp_result_t ret = 0; + rnp_ffi_t ffi = NULL; + + /* try non-permissive import */ + ret = rnp_input_from_memory(&input, data, size, false); + ret = rnp_ffi_create(&ffi, "GPG", "GPG"); + char *results = NULL; + ret = rnp_import_keys( + ffi, input, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS, &results); + rnp_buffer_destroy(results); + rnp_input_destroy(input); + rnp_ffi_destroy(ffi); + + /* try permissive import */ + ret = rnp_input_from_memory(&input, data, size, false); + ret = rnp_ffi_create(&ffi, "GPG", "GPG"); + results = NULL; + ret = rnp_import_keys(ffi, + input, + RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS | + RNP_LOAD_SAVE_PERMISSIVE, + &results); + rnp_buffer_destroy(results); + rnp_input_destroy(input); + rnp_ffi_destroy(ffi); + + /* try non-permissive iterative import */ + ret = rnp_input_from_memory(&input, data, size, false); + ret = rnp_ffi_create(&ffi, "GPG", "GPG"); + do { + results = NULL; + ret = rnp_import_keys(ffi, + input, + RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS | + RNP_LOAD_SAVE_SINGLE, + &results); + rnp_buffer_destroy(results); + } while (!ret); + rnp_input_destroy(input); + rnp_ffi_destroy(ffi); + + /* try permissive iterative import */ + ret = rnp_input_from_memory(&input, data, size, false); + ret = rnp_ffi_create(&ffi, "GPG", "GPG"); + do { + results = NULL; + ret = rnp_import_keys(ffi, + input, + RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS | + RNP_LOAD_SAVE_PERMISSIVE | RNP_LOAD_SAVE_SINGLE, + &results); + rnp_buffer_destroy(results); + } while (!ret); + rnp_input_destroy(input); + rnp_ffi_destroy(ffi); + + return 0; +} diff --git a/comm/third_party/rnp/src/fuzzing/keyring.c b/comm/third_party/rnp/src/fuzzing/keyring.c new file mode 100644 index 0000000000..bac4e1325a --- /dev/null +++ b/comm/third_party/rnp/src/fuzzing/keyring.c @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2020, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <rnp/rnp.h> + +#ifdef RNP_RUN_TESTS +int keyring_LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); +int +keyring_LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +#else +int +LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +#endif +{ + rnp_input_t input = NULL; + rnp_result_t ret = 0; + rnp_ffi_t ffi = NULL; + + ret = rnp_input_from_memory(&input, data, size, false); + + ret = rnp_ffi_create(&ffi, "GPG", "GPG"); + ret = + rnp_load_keys(ffi, "GPG", input, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS); + + rnp_input_destroy(input); + rnp_ffi_destroy(ffi); + + return 0; +} diff --git a/comm/third_party/rnp/src/fuzzing/keyring_g10.cpp b/comm/third_party/rnp/src/fuzzing/keyring_g10.cpp new file mode 100644 index 0000000000..f2495a591d --- /dev/null +++ b/comm/third_party/rnp/src/fuzzing/keyring_g10.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2020, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <rnp/rnp.h> +#include "../lib/pgp-key.h" +#include "../librekey/key_store_g10.h" +#include "../librepgp/stream-common.h" +#include "../lib/sec_profile.hpp" + +#ifdef RNP_RUN_TESTS +int keyring_g10_LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); +int +keyring_g10_LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +#else +extern "C" RNP_API int +LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +#endif +{ + rnp::SecurityContext ctx; + rnp_key_store_t ks(ctx); + pgp_source_t memsrc = {}; + + init_mem_src(&memsrc, data, size, false); + rnp_key_store_g10_from_src(&ks, &memsrc, NULL); + src_close(&memsrc); + + return 0; +} diff --git a/comm/third_party/rnp/src/fuzzing/keyring_kbx.c b/comm/third_party/rnp/src/fuzzing/keyring_kbx.c new file mode 100644 index 0000000000..768e66911b --- /dev/null +++ b/comm/third_party/rnp/src/fuzzing/keyring_kbx.c @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2020, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <rnp/rnp.h> + +#ifdef RNP_RUN_TESTS +int keyring_kbx_LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); +int +keyring_kbx_LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +#else +int +LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +#endif +{ + rnp_input_t input = NULL; + rnp_result_t ret = 0; + rnp_ffi_t ffi = NULL; + + ret = rnp_input_from_memory(&input, data, size, false); + + ret = rnp_ffi_create(&ffi, "KBX", "G10"); + ret = rnp_load_keys(ffi, "KBX", input, RNP_LOAD_SAVE_PUBLIC_KEYS); + + rnp_input_destroy(input); + rnp_ffi_destroy(ffi); + return 0; +} diff --git a/comm/third_party/rnp/src/fuzzing/sigimport.c b/comm/third_party/rnp/src/fuzzing/sigimport.c new file mode 100644 index 0000000000..35adeb7343 --- /dev/null +++ b/comm/third_party/rnp/src/fuzzing/sigimport.c @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2020, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <rnp/rnp.h> + +#ifdef RNP_RUN_TESTS +int sigimport_LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); +int +sigimport_LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +#else +int +LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +#endif +{ + rnp_input_t input = NULL; + rnp_result_t ret = 0; + rnp_ffi_t ffi = NULL; + + ret = rnp_input_from_memory(&input, data, size, false); + ret = rnp_ffi_create(&ffi, "GPG", "GPG"); + char *results = NULL; + ret = rnp_import_signatures(ffi, input, 0, &results); + rnp_buffer_destroy(results); + rnp_input_destroy(input); + rnp_ffi_destroy(ffi); + + return 0; +} diff --git a/comm/third_party/rnp/src/fuzzing/verify.c b/comm/third_party/rnp/src/fuzzing/verify.c new file mode 100644 index 0000000000..cd6c849f63 --- /dev/null +++ b/comm/third_party/rnp/src/fuzzing/verify.c @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2020, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <rnp/rnp.h> +#include "stdio.h" + +#ifdef RNP_RUN_TESTS +int verify_LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); +int +verify_LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +#else +int +LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +#endif +{ + rnp_ffi_t ffi = NULL; + rnp_input_t input = NULL; + rnp_output_t output = NULL; + rnp_result_t ret; + + ret = rnp_ffi_create(&ffi, "GPG", "GPG"); + ret = rnp_input_from_memory(&input, data, size, false); + ret = rnp_output_to_null(&output); + + rnp_op_verify_t op = NULL; + ret = rnp_op_verify_create(&op, ffi, input, output); + ret = rnp_op_verify_execute(op); + ret = rnp_op_verify_destroy(op); + + rnp_input_destroy(input); + rnp_output_destroy(output); + rnp_ffi_destroy(ffi); + + return 0; +} diff --git a/comm/third_party/rnp/src/fuzzing/verify_detached.c b/comm/third_party/rnp/src/fuzzing/verify_detached.c new file mode 100644 index 0000000000..2afb59ab44 --- /dev/null +++ b/comm/third_party/rnp/src/fuzzing/verify_detached.c @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2020, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <rnp/rnp.h> +#include "string.h" + +#ifdef RNP_RUN_TESTS +int verify_detached_LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); +int +verify_detached_LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +#else +int +LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +#endif +{ + rnp_ffi_t ffi = NULL; + rnp_input_t input = NULL; + rnp_input_t msg_input = NULL; + rnp_result_t ret; + + ret = rnp_ffi_create(&ffi, "GPG", "GPG"); + ret = rnp_input_from_memory(&input, data, size, false); + const char *msg = "message"; + ret = rnp_input_from_memory(&msg_input, (const uint8_t *) msg, strlen(msg), true); + + rnp_op_verify_t verify = NULL; + ret = rnp_op_verify_detached_create(&verify, ffi, msg_input, input); + ret = rnp_op_verify_execute(verify); + ret = rnp_op_verify_destroy(verify); + + rnp_input_destroy(input); + rnp_input_destroy(msg_input); + rnp_ffi_destroy(ffi); + + return 0; +} diff --git a/comm/third_party/rnp/src/lib/CMakeLists.txt b/comm/third_party/rnp/src/lib/CMakeLists.txt new file mode 100755 index 0000000000..086ac57d8e --- /dev/null +++ b/comm/third_party/rnp/src/lib/CMakeLists.txt @@ -0,0 +1,594 @@ +# Copyright (c) 2018-2020 Ribose Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS +# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +include(GNUInstallDirs) +include(GenerateExportHeader) + +# these could probably be optional but are currently not +find_package(BZip2 REQUIRED) +find_package(ZLIB REQUIRED) + +# required packages +find_package(JSON-C 0.11 REQUIRED) +if (CRYPTO_BACKEND_BOTAN) + find_package(Botan2 2.14.0 REQUIRED) +endif() +if (CRYPTO_BACKEND_OPENSSL) + include(FindOpenSSL) + find_package(OpenSSL 1.1.1 REQUIRED) + include(FindOpenSSLFeatures) + if("${OPENSSL_VERSION}" VERSION_GREATER_EQUAL "3.0.0") + set(CRYPTO_BACKEND_OPENSSL3 1) + endif() +endif() + +# generate a config.h +include(CheckIncludeFileCXX) +include(CheckCXXSymbolExists) +check_include_file_cxx(fcntl.h HAVE_FCNTL_H) +check_include_file_cxx(inttypes.h HAVE_INTTYPES_H) +check_include_file_cxx(limits.h HAVE_LIMITS_H) +check_include_file_cxx(stdint.h HAVE_STDINT_H) +check_include_file_cxx(string.h HAVE_STRING_H) +check_include_file_cxx(sys/cdefs.h HAVE_SYS_CDEFS_H) +check_include_file_cxx(sys/cdefs.h HAVE_SYS_MMAN_H) +check_include_file_cxx(sys/resource.h HAVE_SYS_RESOURCE_H) +check_include_file_cxx(sys/stat.h HAVE_SYS_STAT_H) +check_include_file_cxx(sys/types.h HAVE_SYS_TYPES_H) +check_include_file_cxx(sys/param.h HAVE_SYS_PARAM_H) +check_include_file_cxx(unistd.h HAVE_UNISTD_H) +check_include_file_cxx(sys/wait.h HAVE_SYS_WAIT_H) +check_cxx_symbol_exists(mkdtemp "stdlib.h;unistd.h" HAVE_MKDTEMP) +check_cxx_symbol_exists(mkstemp "stdlib.h;unistd.h" HAVE_MKSTEMP) +check_cxx_symbol_exists(realpath stdlib.h HAVE_REALPATH) +check_cxx_symbol_exists(O_BINARY fcntl.h HAVE_O_BINARY) +check_cxx_symbol_exists(_O_BINARY fcntl.h HAVE__O_BINARY) +check_cxx_symbol_exists(_tempnam stdio.h HAVE__TEMPNAM) +set(HAVE_ZLIB_H "${ZLIB_FOUND}") +set(HAVE_BZLIB_H "${BZIP2_FOUND}") +# generate a version.h +configure_file(version.h.in version.h) + +# Checks feature in CRYPTO_BACKEND and puts the result into variable whose name is stored in RESULT_VARNAME variable. +function(backend_has_feature FEATURE RESULT_VARNAME) + if (CRYPTO_BACKEND_LOWERCASE STREQUAL "botan") + check_cxx_symbol_exists("BOTAN_HAS_${FEATURE}" botan/build.h ${RESULT_VARNAME}) + else() + message(STATUS "Looking for OpenSSL feature ${FEATURE}") + OpenSSLHasFeature(${FEATURE} ${RESULT_VARNAME}) + if (${RESULT_VARNAME}) + message(STATUS "Looking for OpenSSL feature ${FEATURE} - found") + endif() + set(${RESULT_VARNAME} "${${RESULT_VARNAME}}" PARENT_SCOPE) + endif() +endfunction() + +function(resolve_feature_state RNP_FEATURE BACKEND_FEATURES) + if (NOT ${RNP_FEATURE}) # User has explicitly disabled this feature + return() + endif() + + string(TOLOWER "${${RNP_FEATURE}}" ${RNP_FEATURE}) + if (${RNP_FEATURE} STREQUAL "auto") + set(MESSAGE_TYPE "NOTICE") + set(OUTCOME "Disabling") + else() # User has explicitly enabled this feature + set(MESSAGE_TYPE "FATAL_ERROR") + set(OUTCOME "Aborting") + endif() + + foreach(feature ${BACKEND_FEATURES}) + backend_has_feature("${feature}" _has_${feature}) + if (NOT ${_has_${feature}}) + set(${RNP_FEATURE} Off CACHE STRING "Autodetected" FORCE) + message(${MESSAGE_TYPE} "${RNP_FEATURE} requires ${CRYPTO_BACKEND} feature which is missing: ${feature}. ${OUTCOME}.") + return() + endif() + endforeach() + set(${RNP_FEATURE} On CACHE STRING "Autodetected" FORCE) +endfunction() + +function(openssl_nope RNP_FEATURE REASON) + if (NOT ${RNP_FEATURE}) # User has explicitly disabled this feature + return() + endif() + + string(TOLOWER "${${RNP_FEATURE}}" ${RNP_FEATURE}) + if (${RNP_FEATURE} STREQUAL "auto") + set(MESSAGE_TYPE "NOTICE") + set(OUTCOME "Disabling") + else() # User has explicitly enabled this feature + set(MESSAGE_TYPE "FATAL_ERROR") + set(OUTCOME "Aborting") + endif() + + set(${RNP_FEATURE} Off CACHE STRING "Auto -> Off as no support" FORCE) + message(${MESSAGE_TYPE} "${RNP_FEATURE} doesn't work with OpenSSL backend (${REASON}). ${OUTCOME}.") +endfunction() + +if(CRYPTO_BACKEND_BOTAN) + # check botan's enabled features + set(CMAKE_REQUIRED_INCLUDES "${BOTAN2_INCLUDE_DIRS}") + set(_botan_required_features + # base + BIGINT FFI HEX_CODEC PGP_S2K + # symmetric ciphers + BLOCK_CIPHER AES CAMELLIA DES + # cipher modes + MODE_CBC MODE_CFB + # RNG + AUTO_RNG AUTO_SEEDING_RNG HMAC HMAC_DRBG + # hash + CRC24 HASH MD5 SHA1 SHA2_32 SHA2_64 SHA3 + # public-key core + DL_GROUP DL_PUBLIC_KEY_FAMILY ECC_GROUP ECC_PUBLIC_KEY_CRYPTO PUBLIC_KEY_CRYPTO + # public-key algs + CURVE_25519 DSA ECDH ECDSA ED25519 ELGAMAL RSA + # public-key operations etc + EME_PKCS1v15 EMSA_PKCS1 EMSA_RAW KDF_BASE RFC3394_KEYWRAP SP800_56A + ) + foreach(feature ${_botan_required_features}) + check_cxx_symbol_exists("BOTAN_HAS_${feature}" botan/build.h _botan_has_${feature}) + if (NOT _botan_has_${feature}) + message(FATAL_ERROR "A required botan feature is missing: ${feature}") + endif() + endforeach() + + resolve_feature_state(ENABLE_SM2 "SM2;SM3;SM4") + resolve_feature_state(ENABLE_AEAD "AEAD_EAX;AEAD_OCB") + resolve_feature_state(ENABLE_TWOFISH "TWOFISH") + resolve_feature_state(ENABLE_IDEA "IDEA") + # Botan supports Brainpool curves together with SECP via the ECC_GROUP define + resolve_feature_state(ENABLE_BLOWFISH "BLOWFISH") + resolve_feature_state(ENABLE_CAST5 "CAST_128") + resolve_feature_state(ENABLE_RIPEMD160 "RIPEMD_160") + set(CMAKE_REQUIRED_INCLUDES) +endif() +if(CRYPTO_BACKEND_OPENSSL) + # check OpenSSL features + set(_openssl_required_features + # symmetric ciphers + AES-128-ECB AES-192-ECB AES-256-ECB AES-128-CBC AES-192-CBC AES-256-CBC + AES-128-OCB AES-192-OCB AES-256-OCB + CAMELLIA-128-ECB CAMELLIA-192-ECB CAMELLIA-256-ECB + DES-EDE3 + # hashes + MD5 SHA1 SHA224 SHA256 SHA384 SHA512 SHA3-256 SHA3-512 + # curves + PRIME256V1 SECP384R1 SECP521R1 SECP256K1 + # public key + RSAENCRYPTION DSAENCRYPTION DHKEYAGREEMENT ID-ECPUBLICKEY X25519 ED25519 + ) + foreach(feature ${_openssl_required_features}) + message(STATUS "Looking for OpenSSL feature ${feature}") + OpenSSLHasFeature("${feature}" _openssl_has_${feature}) + if (NOT _openssl_has_${feature}) + message(FATAL_ERROR "A required OpenSSL feature is missing: ${feature}") + endif() + message(STATUS "Looking for OpenSSL feature ${feature} - found") + endforeach() + + resolve_feature_state(ENABLE_BRAINPOOL "BRAINPOOLP256R1;BRAINPOOLP384R1;BRAINPOOLP512R1") + resolve_feature_state(ENABLE_IDEA "IDEA-ECB;IDEA-CBC") + resolve_feature_state(ENABLE_BLOWFISH "BF-ECB") + resolve_feature_state(ENABLE_CAST5 "CAST5-ECB") + resolve_feature_state(ENABLE_RIPEMD160 "RIPEMD160") + resolve_feature_state(ENABLE_AEAD "AES-128-OCB;AES-192-OCB;AES-256-OCB") + openssl_nope(ENABLE_SM2 "it's on our roadmap, see https://github.com/rnpgp/rnp/issues/1877") + #resolve_feature_state(ENABLE_SM2 "SM2;SM3;SM4-ECB") + openssl_nope(ENABLE_TWOFISH "Twofish isn't and won't be supported by OpenSSL, see https://github.com/openssl/openssl/issues/2046") +endif() + +configure_file(config.h.in config.h) + +if(CRYPTO_BACKEND_OPENSSL) + set(CRYPTO_SOURCES + crypto/bn_ossl.cpp + crypto/dsa_ossl.cpp + crypto/ec_curves.cpp + crypto/ec_ossl.cpp + crypto/ecdh_utils.cpp + crypto/ecdh_ossl.cpp + crypto/ecdsa_ossl.cpp + crypto/eddsa_ossl.cpp + crypto/dl_ossl.cpp + crypto/elgamal_ossl.cpp + crypto/hash_common.cpp + crypto/hash_ossl.cpp + crypto/hash_crc24.cpp + crypto/mpi.cpp + crypto/rng_ossl.cpp + crypto/rsa_ossl.cpp + crypto/s2k.cpp + crypto/s2k_ossl.cpp + crypto/symmetric_ossl.cpp + crypto/signatures.cpp + crypto/mem_ossl.cpp + crypto/cipher.cpp + crypto/cipher_ossl.cpp + ) + if(ENABLE_SM2) + list(APPEND CRYPTO_SOURCES crypto/sm2_ossl.cpp) + endif() +elseif(CRYPTO_BACKEND_BOTAN) + set(CRYPTO_SOURCES + crypto/bn.cpp + crypto/dsa.cpp + crypto/ec_curves.cpp + crypto/ec.cpp + crypto/ecdh_utils.cpp + crypto/ecdh.cpp + crypto/ecdsa.cpp + crypto/eddsa.cpp + crypto/elgamal.cpp + crypto/hash_common.cpp + crypto/hash.cpp + crypto/mpi.cpp + crypto/rng.cpp + crypto/rsa.cpp + crypto/s2k.cpp + crypto/symmetric.cpp + crypto/signatures.cpp + crypto/mem.cpp + crypto/cipher.cpp + crypto/cipher_botan.cpp + ) + if(ENABLE_SM2) + list(APPEND CRYPTO_SOURCES crypto/sm2.cpp) + endif() +else() + message(FATAL_ERROR "Unknown crypto backend: ${CRYPTO_BACKEND}.") +endif() +list(APPEND CRYPTO_SOURCES crypto/backend_version.cpp) + +# sha11collisiondetection sources +list(APPEND CRYPTO_SOURCES crypto/hash_sha1cd.cpp crypto/sha1cd/sha1.c crypto/sha1cd/ubc_check.c) + +add_library(librnp-obj OBJECT + # librepgp + ../librepgp/stream-armor.cpp + ../librepgp/stream-common.cpp + ../librepgp/stream-ctx.cpp + ../librepgp/stream-dump.cpp + ../librepgp/stream-key.cpp + ../librepgp/stream-packet.cpp + ../librepgp/stream-parse.cpp + ../librepgp/stream-sig.cpp + ../librepgp/stream-write.cpp + + # librekey + ../librekey/key_store_g10.cpp + ../librekey/key_store_kbx.cpp + ../librekey/key_store_pgp.cpp + ../librekey/rnp_key_store.cpp + + # cryptography + ${CRYPTO_SOURCES} + + # other sources + sec_profile.cpp + crypto.cpp + fingerprint.cpp + generate-key.cpp + key-provider.cpp + logging.cpp + json-utils.cpp + utils.cpp + pass-provider.cpp + pgp-key.cpp + rnp.cpp +) + +get_target_property(_comp_options librnp-obj COMPILE_OPTIONS) +string(REGEX MATCH "\\-fsanitize=[a-z,]*undefined" _comp_sanitizers "${_comp_options}" "${CMAKE_C_FLAGS}") +if (ENABLE_SANITIZERS OR _comp_sanitizers) + # sha1cd attempts to use unaligned access for optimisations on intel CPUs + # CFLAGS is checked as sanitizers may be enabled without CMake var + set_source_files_properties(crypto/sha1cd/sha1.c + PROPERTIES COMPILE_DEFINITIONS "SHA1DC_FORCE_ALIGNED_ACCESS" + ) +endif() + +set_target_properties(librnp-obj PROPERTIES POSITION_INDEPENDENT_CODE ON) +target_include_directories(librnp-obj + PUBLIC + "$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/src/lib>" + "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/src/common>" + "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>" + "$<INSTALL_INTERFACE:include>" + PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}" + "${PROJECT_SOURCE_DIR}/src" +) +target_link_libraries(librnp-obj PRIVATE JSON-C::JSON-C) +if (CRYPTO_BACKEND_BOTAN) + target_link_libraries(librnp-obj PRIVATE Botan2::Botan2) +elseif (CRYPTO_BACKEND_OPENSSL) + target_link_libraries(librnp-obj PRIVATE OpenSSL::Crypto) +endif() + +target_link_libraries(librnp-obj PRIVATE sexp) + +set_target_properties(librnp-obj PROPERTIES CXX_VISIBILITY_PRESET hidden) +if (TARGET BZip2::BZip2) + target_link_libraries(librnp-obj PRIVATE BZip2::BZip2) +endif() +if (TARGET ZLIB::ZLIB) + target_link_libraries(librnp-obj PRIVATE ZLIB::ZLIB) +endif() +if (BUILD_SHARED_LIBS) + target_compile_definitions(librnp-obj PRIVATE librnp_EXPORTS) +else() + target_compile_definitions(librnp-obj PRIVATE RNP_STATIC) +endif() + +add_library(librnp $<TARGET_OBJECTS:librnp-obj> $<TARGET_OBJECTS:rnp-common>) +if (OpenSSL::applink) + target_link_libraries(librnp PRIVATE OpenSSL::applink) +endif(OpenSSL::applink) + +set_target_properties(librnp + PROPERTIES + VERSION "${RNP_VERSION}" + SOVERSION "${RNP_VERSION_MAJOR}" + OUTPUT_NAME "rnp" +) + +if (BUILD_SHARED_LIBS) + add_library(librnp-static STATIC $<TARGET_OBJECTS:librnp-obj> $<TARGET_OBJECTS:rnp-common>) + if (OpenSSL::applink) + target_link_libraries(librnp-static PRIVATE OpenSSL::applink) + endif(OpenSSL::applink) + if (WIN32) + set_target_properties(librnp-static PROPERTIES OUTPUT_NAME "rnp-static") + else (WIN32) +# On Unix like systems we will build/install/pack shared and static libraries librnp.so and librnp.a +# On Windows we will build/install/pack dynamic, import and static libraries rnp.dll, rnp.lib and rnp-static.lib + set_target_properties(librnp-static PROPERTIES OUTPUT_NAME "rnp") + endif (WIN32) + # Limit symbols export only to rnp_* functions. + if (APPLE) + # use -export_symbols_list on Apple OSs + target_link_options(librnp PRIVATE -Wl,-exported_symbols_list "${CMAKE_CURRENT_SOURCE_DIR}/librnp.symbols") + set_target_properties(librnp PROPERTIES LINK_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/librnp.symbols") + elseif(NOT WIN32) + target_link_options(librnp PRIVATE "-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/librnp.vsc") + set_target_properties(librnp PROPERTIES LINK_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/librnp.vsc") + endif() +else() + add_library(librnp-static ALIAS librnp) +endif() + +foreach (prop LINK_LIBRARIES INTERFACE_LINK_LIBRARIES INCLUDE_DIRECTORIES INTERFACE_INCLUDE_DIRECTORIES) + get_target_property(val librnp-obj ${prop}) + if (BUILD_SHARED_LIBS) + set_property(TARGET librnp-static PROPERTY ${prop} ${val}) + list(REMOVE_ITEM val "$<LINK_ONLY:sexp>") + set_property(TARGET librnp PROPERTY ${prop} ${val}) + else() + set_property(TARGET librnp PROPERTY ${prop} ${val}) + endif() + +endforeach() + +generate_export_header(librnp + BASE_NAME rnp + EXPORT_MACRO_NAME RNP_API + EXPORT_FILE_NAME rnp/rnp_export.h + STATIC_DEFINE RNP_STATIC + INCLUDE_GUARD_NAME RNP_EXPORT +) + +# This has precedence and can cause some confusion when the binary +# dir one isn't actually being used. To be improved. +if (NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR) + file(REMOVE "${CMAKE_CURRENT_SOURCE_DIR}/config.h") + file(REMOVE "${CMAKE_CURRENT_SOURCE_DIR}/version.h") +endif() + +if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.12.0") + set(namelink_component NAMELINK_COMPONENT development) +else() + set(namelink_component) +endif() + +# add these to the rnp-targets export +# On Unix like systems we will build/install/pack shared and static libraries librnp.so and librnp.a +# On Windows we will build/install/pack dynamic, import and static libraries rnp.dll, rnp.lib and rnp-static.lib + +# If a client application uses shared rnp library, sexp is statically linked to librnp.so +# If a client application uses static rnp library, it still needs libsexp.a + +if (BUILD_SHARED_LIBS) +# both static and shared libraries +install(TARGETS librnp + EXPORT rnp-targets + LIBRARY + DESTINATION "${CMAKE_INSTALL_LIBDIR}" + COMPONENT runtime + ${namelink_component} + ARCHIVE + DESTINATION "${CMAKE_INSTALL_LIBDIR}" + COMPONENT development + ) + + install(TARGETS librnp-static sexp + EXPORT rnp-targets + ARCHIVE + DESTINATION "${CMAKE_INSTALL_LIBDIR}" + COMPONENT development + ) +else(BUILD_SHARED_LIBS) +# static libraries only +install(TARGETS librnp sexp + EXPORT rnp-targets + ARCHIVE + DESTINATION "${CMAKE_INSTALL_LIBDIR}" + COMPONENT development +) +endif(BUILD_SHARED_LIBS) + +# install dll only for windows +if (WIN32) + install(TARGETS librnp + RUNTIME + DESTINATION "${CMAKE_INSTALL_BINDIR}" + COMPONENT runtime + ) +endif(WIN32) + +# install headers +install( + FILES + "${PROJECT_SOURCE_DIR}/include/rnp/rnp.h" + COMPONENT + development + DESTINATION + "${CMAKE_INSTALL_INCLUDEDIR}/rnp" + RENAME + rnp.h +) +install( + FILES + "${PROJECT_SOURCE_DIR}/include/rnp/rnp_err.h" + COMPONENT + development + DESTINATION + "${CMAKE_INSTALL_INCLUDEDIR}/rnp" + RENAME + rnp_err.h +) +install( + FILES + "${PROJECT_BINARY_DIR}/src/lib/rnp/rnp_export.h" + COMPONENT + development + DESTINATION + "${CMAKE_INSTALL_INCLUDEDIR}/rnp" + RENAME + rnp_export.h +) + +# .cmake installs +set(INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/rnp") + +install(EXPORT rnp-targets + FILE rnp-targets.cmake + NAMESPACE rnp:: + DESTINATION "${INSTALL_CMAKEDIR}" + COMPONENT development +) + +include(CMakePackageConfigHelpers) +configure_package_config_file( + "${PROJECT_SOURCE_DIR}/cmake/rnp-config.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/rnp-config.cmake" + INSTALL_DESTINATION "${INSTALL_CMAKEDIR}" +) +write_basic_package_version_file(rnp-config-version.cmake + VERSION "${PROJECT_VERSION}" + COMPATIBILITY SameMajorVersion +) +install ( + FILES + "${CMAKE_CURRENT_BINARY_DIR}/rnp-config.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/rnp-config-version.cmake" + DESTINATION "${INSTALL_CMAKEDIR}" + COMPONENT development +) + +function(get_linked_libs libsvar dirsvar tgt) + get_target_property(imported ${tgt} IMPORTED) + list(APPEND visited_targets ${tgt}) + if (imported) + get_target_property(linkedlibs ${tgt} INTERFACE_LINK_LIBRARIES) + endif() + set(libs) + foreach (lib ${linkedlibs}) + if (TARGET ${lib}) + list(FIND visited_targets ${lib} visited) + if ((${visited} EQUAL -1) AND (${CMAKE_SHARED_LIBRARY_PREFIX})) + # library + get_target_property(liblocation ${lib} LOCATION) + get_filename_component(linkedlib ${liblocation} NAME_WE) + string(REGEX REPLACE "^${CMAKE_SHARED_LIBRARY_PREFIX}" "" linkedlib ${linkedlib}) + get_linked_libs(linkedlibs libdirs ${lib}) + list(APPEND libs ${linkedlib} ${linkedlibs}) + # directory + get_filename_component(libdir ${liblocation} DIRECTORY) + list(FIND ${dirsvar} ${libdir} seendir) + if (${seendir} EQUAL -1) + list(APPEND ${dirsvar} ${libdir} ${libdirs}) + endif() + endif() + endif() + endforeach() + set(visited_targets ${visited_targets} PARENT_SCOPE) + set(${libsvar} ${libs} PARENT_SCOPE) + set(${dirsvar} ${${dirsvar}} PARENT_SCOPE) +endfunction() + +get_linked_libs(libs dirs librnp) +set(linkercmd) +foreach (dir ${dirs}) + string(APPEND linkercmd "-L${dir} ") +endforeach() +foreach (lib ${libs}) + string(APPEND linkercmd "-l${lib} ") +endforeach() +string(STRIP "${linkercmd}" linkercmd) +set(LIBRNP_PRIVATE_LIBS ${linkercmd}) + +# create a pkgconfig .pc too +find_package(PkgConfig) +if (PKG_CONFIG_FOUND) + get_target_property(LIBRNP_OUTPUT_NAME librnp OUTPUT_NAME) + + if(IS_ABSOLUTE "${CMAKE_INSTALL_LIBDIR}") + set(PKGCONFIG_LIBDIR "${CMAKE_INSTALL_LIBDIR}") + else() + set(PKGCONFIG_LIBDIR "\${prefix}/${CMAKE_INSTALL_LIBDIR}") + endif() + if(IS_ABSOLUTE "${CMAKE_INSTALL_INCLUDEDIR}") + set(PKGCONFIG_INCLUDEDIR "${CMAKE_INSTALL_INCLUDEDIR}") + else() + set(PKGCONFIG_INCLUDEDIR "\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}") + endif() + + configure_file( + "${PROJECT_SOURCE_DIR}/cmake/librnp.pc.in" + "${PROJECT_BINARY_DIR}/librnp.pc" + @ONLY + ) + install( + FILES "${PROJECT_BINARY_DIR}/librnp.pc" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig" + COMPONENT development + ) +endif() + +# Build and install man page +if (ENABLE_DOC) + add_adoc_man("${CMAKE_CURRENT_SOURCE_DIR}/librnp.3.adoc" ${RNP_VERSION}) +endif() diff --git a/comm/third_party/rnp/src/lib/config.h.in b/comm/third_party/rnp/src/lib/config.h.in new file mode 100644 index 0000000000..f8880d55d7 --- /dev/null +++ b/comm/third_party/rnp/src/lib/config.h.in @@ -0,0 +1,72 @@ +/*- + * Copyright (c) 2018-2020 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#define PACKAGE_STRING "rnp @RNP_VERSION_FULL@" +#define PACKAGE_BUGREPORT "@BUGREPORT_EMAIL@" + +#cmakedefine HAVE_BZLIB_H +#cmakedefine HAVE_ZLIB_H + +#cmakedefine HAVE_FCNTL_H +#cmakedefine HAVE_INTTYPES_H +#cmakedefine HAVE_LIMITS_H +#cmakedefine HAVE_STDINT_H +#cmakedefine HAVE_STRING_H +#cmakedefine HAVE_SYS_CDEFS_H +#cmakedefine HAVE_SYS_MMAN_H +#cmakedefine HAVE_SYS_RESOURCE_H +#cmakedefine HAVE_SYS_STAT_H +#cmakedefine HAVE_SYS_TYPES_H +#cmakedefine HAVE_UNISTD_H +#cmakedefine HAVE_SYS_WAIT_H +#cmakedefine HAVE_SYS_PARAM_H +#cmakedefine HAVE_MKDTEMP +#cmakedefine HAVE_MKSTEMP +#cmakedefine HAVE_REALPATH +#cmakedefine HAVE_O_BINARY +#cmakedefine HAVE__O_BINARY +#cmakedefine HAVE__TEMPNAM + +#cmakedefine CRYPTO_BACKEND_BOTAN +#cmakedefine CRYPTO_BACKEND_OPENSSL +#cmakedefine CRYPTO_BACKEND_OPENSSL3 + +#cmakedefine ENABLE_SM2 +#cmakedefine ENABLE_AEAD +#cmakedefine ENABLE_TWOFISH +#cmakedefine ENABLE_BRAINPOOL +#cmakedefine ENABLE_IDEA +#cmakedefine ENABLE_BLOWFISH +#cmakedefine ENABLE_CAST5 +#cmakedefine ENABLE_RIPEMD160 + +/* Macro _GLIBCXX_USE_CXX11_ABI was first introduced with GCC 5.0, which + * we assume to be bundled with a sane implementation of std::regex. */ +#if !defined(__GNUC__) || defined(_GLIBCXX_USE_CXX11_ABI) || \ + (defined(WIN32) && !defined(MSYS)) || \ + ((defined(__clang__) && (__clang_major__ >= 4)) ) +#define RNP_USE_STD_REGEX 1 +#endif diff --git a/comm/third_party/rnp/src/lib/crypto.cpp b/comm/third_party/rnp/src/lib/crypto.cpp new file mode 100644 index 0000000000..26346049e8 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto.cpp @@ -0,0 +1,244 @@ +/* + * Copyright (c) 2017-2020, [Ribose Inc](https://www.ribose.com). + * Copyright (c) 2009 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is originally derived from software contributed to + * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and + * carried further by Ribose Inc (https://www.ribose.com). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/* + * Copyright (c) 2005-2008 Nominet UK (www.nic.uk) + * All rights reserved. + * Contributors: Ben Laurie, Rachel Willmer. The Contributors have asserted + * their moral rights under the UK Copyright Design and Patents Act 1988 to + * be recorded as the authors of this copyright work. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. + * + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" + +#ifdef HAVE_SYS_CDEFS_H +#include <sys/cdefs.h> +#endif + +#if defined(__NetBSD__) +__COPYRIGHT("@(#) Copyright (c) 2009 The NetBSD Foundation, Inc. All rights reserved."); +__RCSID("$NetBSD: crypto.c,v 1.36 2014/02/17 07:39:19 agc Exp $"); +#endif + +#include <sys/types.h> +#include <sys/stat.h> + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include <string.h> +#include <time.h> +#include <rnp/rnp_def.h> + +#include <librepgp/stream-packet.h> +#include <librepgp/stream-key.h> + +#include "types.h" +#include "crypto/common.h" +#include "crypto.h" +#include "fingerprint.h" +#include "pgp-key.h" +#include "utils.h" + +bool +pgp_generate_seckey(const rnp_keygen_crypto_params_t &crypto, + pgp_key_pkt_t & seckey, + bool primary) +{ + /* populate pgp key structure */ + seckey = {}; + seckey.version = PGP_V4; + seckey.creation_time = crypto.ctx->time(); + seckey.alg = crypto.key_alg; + seckey.material.alg = crypto.key_alg; + seckey.tag = primary ? PGP_PKT_SECRET_KEY : PGP_PKT_SECRET_SUBKEY; + + switch (seckey.alg) { + case PGP_PKA_RSA: + if (rsa_generate(&crypto.ctx->rng, &seckey.material.rsa, crypto.rsa.modulus_bit_len)) { + RNP_LOG("failed to generate RSA key"); + return false; + } + break; + case PGP_PKA_DSA: + if (dsa_generate(&crypto.ctx->rng, + &seckey.material.dsa, + crypto.dsa.p_bitlen, + crypto.dsa.q_bitlen)) { + RNP_LOG("failed to generate DSA key"); + return false; + } + break; + case PGP_PKA_EDDSA: + if (eddsa_generate(&crypto.ctx->rng, &seckey.material.ec)) { + RNP_LOG("failed to generate EDDSA key"); + return false; + } + break; + case PGP_PKA_ECDH: + if (!ecdh_set_params(&seckey.material.ec, crypto.ecc.curve)) { + RNP_LOG("Unsupported curve [ID=%d]", crypto.ecc.curve); + return false; + } + if (crypto.ecc.curve == PGP_CURVE_25519) { + if (x25519_generate(&crypto.ctx->rng, &seckey.material.ec)) { + RNP_LOG("failed to generate x25519 key"); + return false; + } + seckey.material.ec.curve = crypto.ecc.curve; + break; + } + [[fallthrough]]; + case PGP_PKA_ECDSA: + case PGP_PKA_SM2: + if (!curve_supported(crypto.ecc.curve)) { + RNP_LOG("EC generate: curve %d is not supported.", (int) crypto.ecc.curve); + return false; + } + if (ec_generate(&crypto.ctx->rng, &seckey.material.ec, seckey.alg, crypto.ecc.curve)) { + RNP_LOG("failed to generate EC key"); + return false; + } + seckey.material.ec.curve = crypto.ecc.curve; + break; + case PGP_PKA_ELGAMAL: + if (elgamal_generate( + &crypto.ctx->rng, &seckey.material.eg, crypto.elgamal.key_bitlen)) { + RNP_LOG("failed to generate ElGamal key"); + return false; + } + break; + default: + RNP_LOG("key generation not implemented for PK alg: %d", seckey.alg); + return false; + } + seckey.sec_protection.s2k.usage = PGP_S2KU_NONE; + seckey.material.secret = true; + seckey.material.validity.mark_valid(); + /* fill the sec_data/sec_len */ + if (encrypt_secret_key(&seckey, NULL, crypto.ctx->rng)) { + RNP_LOG("failed to fill sec_data"); + return false; + } + return true; +} + +bool +key_material_equal(const pgp_key_material_t *key1, const pgp_key_material_t *key2) +{ + if (key1->alg != key2->alg) { + return false; + } + + switch (key1->alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + return mpi_equal(&key1->rsa.n, &key2->rsa.n) && mpi_equal(&key1->rsa.e, &key2->rsa.e); + case PGP_PKA_DSA: + return mpi_equal(&key1->dsa.p, &key2->dsa.p) && + mpi_equal(&key1->dsa.q, &key2->dsa.q) && + mpi_equal(&key1->dsa.g, &key2->dsa.g) && mpi_equal(&key1->dsa.y, &key2->dsa.y); + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + return mpi_equal(&key1->eg.p, &key2->eg.p) && mpi_equal(&key1->eg.g, &key2->eg.g) && + mpi_equal(&key1->eg.y, &key2->eg.y); + case PGP_PKA_EDDSA: + case PGP_PKA_ECDH: + case PGP_PKA_ECDSA: + case PGP_PKA_SM2: + return (key1->ec.curve == key2->ec.curve) && mpi_equal(&key1->ec.p, &key2->ec.p); + default: + RNP_LOG("unknown public key algorithm: %d", (int) key1->alg); + return false; + } +} + +rnp_result_t +validate_pgp_key_material(const pgp_key_material_t *material, rnp::RNG *rng) +{ +#ifdef FUZZERS_ENABLED + /* do not timeout on large keys during fuzzing */ + return RNP_SUCCESS; +#else + switch (material->alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + return rsa_validate_key(rng, &material->rsa, material->secret); + case PGP_PKA_DSA: + return dsa_validate_key(rng, &material->dsa, material->secret); + case PGP_PKA_EDDSA: + return eddsa_validate_key(rng, &material->ec, material->secret); + case PGP_PKA_ECDH: + if (!curve_supported(material->ec.curve)) { + /* allow to import key if curve is not supported */ + RNP_LOG("ECDH validate: curve %d is not supported.", (int) material->ec.curve); + return RNP_SUCCESS; + } + return ecdh_validate_key(rng, &material->ec, material->secret); + case PGP_PKA_ECDSA: + if (!curve_supported(material->ec.curve)) { + /* allow to import key if curve is not supported */ + RNP_LOG("ECDH validate: curve %d is not supported.", (int) material->ec.curve); + return RNP_SUCCESS; + } + return ecdsa_validate_key(rng, &material->ec, material->secret); + case PGP_PKA_SM2: +#if defined(ENABLE_SM2) + return sm2_validate_key(rng, &material->ec, material->secret); +#else + RNP_LOG("SM2 key validation is not available."); + return RNP_ERROR_NOT_IMPLEMENTED; +#endif + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + return elgamal_validate_key(&material->eg, material->secret) ? RNP_SUCCESS : + RNP_ERROR_GENERIC; + default: + RNP_LOG("unknown public key algorithm: %d", (int) material->alg); + } + + return RNP_ERROR_BAD_PARAMETERS; +#endif +} diff --git a/comm/third_party/rnp/src/lib/crypto.h b/comm/third_party/rnp/src/lib/crypto.h new file mode 100644 index 0000000000..320daf8d4f --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto.h @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2017, [Ribose Inc](https://www.ribose.com). + * Copyright (c) 2009 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is originally derived from software contributed to + * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and + * carried further by Ribose Inc (https://www.ribose.com). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/* + * Copyright (c) 2005-2008 Nominet UK (www.nic.uk) + * All rights reserved. + * Contributors: Ben Laurie, Rachel Willmer. The Contributors have asserted + * their moral rights under the UK Copyright Design and Patents Act 1988 to + * be recorded as the authors of this copyright work. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. + * + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** \file + */ + +#ifndef CRYPTO_H_ +#define CRYPTO_H_ + +#include <limits.h> +#include "crypto/common.h" +#include <rekey/rnp_key_store.h> + +/* raw key generation */ +bool pgp_generate_seckey(const rnp_keygen_crypto_params_t ¶ms, + pgp_key_pkt_t & seckey, + bool primary); + +/** generate a new primary key + * + * @param desc keygen description + * @param merge_defaults true if you want defaults to be set for unset + * keygen description parameters. + * @param primary_sec pointer to store the generated secret key, must not be NULL + * @param primary_pub pointer to store the generated public key, must not be NULL + * @return true if successful, false otherwise. + **/ +bool pgp_generate_primary_key(rnp_keygen_primary_desc_t &desc, + bool merge_defaults, + pgp_key_t & primary_sec, + pgp_key_t & primary_pub, + pgp_key_store_format_t secformat); + +/** generate a new subkey + * + * @param desc keygen description + * @param merge_defaults true if you want defaults to be set for unset + * keygen description parameters. + * @param primary_sec pointer to the primary secret key that will own this + * subkey, must not be NULL + * @param primary_pub pointer to the primary public key that will own this + * subkey, must not be NULL + * @param subkey_sec pointer to store the generated secret key, must not be NULL + * @param subkey_pub pointer to store the generated public key, must not be NULL + * @param password_provider the password provider that will be used to + * decrypt the primary key, may be NULL if primary key is unlocked + * @return true if successful, false otherwise. + **/ +bool pgp_generate_subkey(rnp_keygen_subkey_desc_t & desc, + bool merge_defaults, + pgp_key_t & primary_sec, + pgp_key_t & primary_pub, + pgp_key_t & subkey_sec, + pgp_key_t & subkey_pub, + const pgp_password_provider_t &password_provider, + pgp_key_store_format_t secformat); + +/** + * @brief Check two key material for equality. Only public part is checked, so this can be + * called on public/secret key material + * + * @param key1 first key material + * @param key2 second key material + * @return true if both key materials are equal or false otherwise + */ +bool key_material_equal(const pgp_key_material_t *key1, const pgp_key_material_t *key2); + +rnp_result_t validate_pgp_key_material(const pgp_key_material_t *material, rnp::RNG *rng); + +#endif /* CRYPTO_H_ */ diff --git a/comm/third_party/rnp/src/lib/crypto/backend_version.cpp b/comm/third_party/rnp/src/lib/crypto/backend_version.cpp new file mode 100644 index 0000000000..859b048c36 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/backend_version.cpp @@ -0,0 +1,184 @@ +/*- + * Copyright (c) 2021 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "backend_version.h" +#include "logging.h" +#if defined(CRYPTO_BACKEND_BOTAN) +#include <botan/version.h> +#elif defined(CRYPTO_BACKEND_OPENSSL) +#include <openssl/opensslv.h> +#include <openssl/crypto.h> +#include "ossl_common.h" +#if defined(CRYPTO_BACKEND_OPENSSL3) +#include <openssl/provider.h> +#endif +#include <string.h> +#include "config.h" +#ifndef RNP_USE_STD_REGEX +#include <regex.h> +#else +#include <regex> +#endif +#endif +#include <cassert> + +namespace rnp { + +const char * +backend_string() +{ +#if defined(CRYPTO_BACKEND_BOTAN) + return "Botan"; +#elif defined(CRYPTO_BACKEND_OPENSSL) + return "OpenSSL"; +#else +#error "Unknown backend" +#endif +} + +const char * +backend_version() +{ +#if defined(CRYPTO_BACKEND_BOTAN) + return Botan::short_version_cstr(); +#elif defined(CRYPTO_BACKEND_OPENSSL) + /* Use regexp to retrieve version (second word) from version string + * like "OpenSSL 1.1.1l 24 Aug 2021" + * */ + static char version[32] = {}; + if (version[0]) { + return version; + } + const char *reg = "OpenSSL (([0-9]\\.[0-9]\\.[0-9])[a-z]*(-beta[0-9])*(-dev)*) "; +#ifndef RNP_USE_STD_REGEX + static regex_t r; + regmatch_t matches[5]; + const char * ver = OpenSSL_version(OPENSSL_VERSION); + + if (!strlen(version)) { + if (regcomp(&r, reg, REG_EXTENDED) != 0) { + RNP_LOG("failed to compile regexp"); + return "unknown"; + } + } + if (regexec(&r, ver, 5, matches, 0) != 0) { + return "unknown"; + } + assert(sizeof(version) > matches[1].rm_eo - matches[1].rm_so); + memcpy(version, ver + matches[1].rm_so, matches[1].rm_eo - matches[1].rm_so); + version[matches[1].rm_eo - matches[1].rm_so] = '\0'; +#else + static std::regex re(reg, std::regex_constants::extended); + std::smatch result; + std::string ver = OpenSSL_version(OPENSSL_VERSION); + if (!std::regex_search(ver, result, re)) { + return "unknown"; + } + assert(sizeof(version) > result[1].str().size()); + strncpy(version, result[1].str().c_str(), sizeof(version) - 1); +#endif + return version; +#else +#error "Unknown backend" +#endif +} + +#if defined(CRYPTO_BACKEND_OPENSSL3) + +#if defined(ENABLE_IDEA) || defined(ENABLE_CAST5) || defined(ENABLE_BLOWFISH) || \ + defined(ENABLE_RIPEMD160) +#define OPENSSL_LOAD_LEGACY +#endif + +typedef struct openssl3_state { +#if defined(OPENSSL_LOAD_LEGACY) + OSSL_PROVIDER *legacy; +#endif + OSSL_PROVIDER *def; +} openssl3_state; + +bool +backend_init(void **param) +{ + if (!param) { + return false; + } + + *param = NULL; + openssl3_state *state = (openssl3_state *) calloc(1, sizeof(openssl3_state)); + if (!state) { + RNP_LOG("Allocation failure."); + return false; + } + /* Load default crypto provider */ + state->def = OSSL_PROVIDER_load(NULL, "default"); + if (!state->def) { + RNP_LOG("Failed to load default crypto provider: %s", ossl_latest_err()); + free(state); + return false; + } + /* Load legacy crypto provider if needed */ +#if defined(OPENSSL_LOAD_LEGACY) + state->legacy = OSSL_PROVIDER_load(NULL, "legacy"); + if (!state->legacy) { + RNP_LOG("Failed to load legacy crypto provider: %s", ossl_latest_err()); + OSSL_PROVIDER_unload(state->def); + free(state); + return false; + } +#endif + *param = state; + return true; +} + +void +backend_finish(void *param) +{ + if (!param) { + return; + } + openssl3_state *state = (openssl3_state *) param; + OSSL_PROVIDER_unload(state->def); +#if defined(OPENSSL_LOAD_LEGACY) + OSSL_PROVIDER_unload(state->legacy); +#endif + free(state); +} +#else +bool +backend_init(void **param) +{ + return true; +} + +void +backend_finish(void *param) +{ + // Do nothing +} +#endif + +} // namespace rnp diff --git a/comm/third_party/rnp/src/lib/crypto/backend_version.h b/comm/third_party/rnp/src/lib/crypto/backend_version.h new file mode 100644 index 0000000000..13c52692e6 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/backend_version.h @@ -0,0 +1,44 @@ +/*- + * Copyright (c) 2021 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CRYPTO_BACKEND_VERSION_H_ +#define CRYPTO_BACKEND_VERSION_H_ + +#include "config.h" + +namespace rnp { + +const char *backend_string(); + +const char *backend_version(); + +bool backend_init(void **param); + +void backend_finish(void *param); + +} // namespace rnp + +#endif // CRYPTO_BACKEND_VERSION_H_ diff --git a/comm/third_party/rnp/src/lib/crypto/bn.cpp b/comm/third_party/rnp/src/lib/crypto/bn.cpp new file mode 100644 index 0000000000..d5ae6b4a94 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/bn.cpp @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2017-2021 Ribose Inc. + * Copyright (c) 2012 Alistair Crooks <agc@NetBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "bn.h" +#include <botan/ffi.h> +#include <stdlib.h> +#include <assert.h> +#include "utils.h" + +/* essentiually, these are just wrappers around the botan functions */ +/* usually the order of args changes */ +/* the bignum_t API tends to have more const poisoning */ +/* these wrappers also check the arguments passed for sanity */ + +/* store in unsigned [big endian] format */ +int +bn_bn2bin(const bignum_t *a, unsigned char *b) +{ + if (!a || !b) { + return -1; + } + return botan_mp_to_bin(a->mp, b); +} + +bignum_t * +mpi2bn(const pgp_mpi_t *val) +{ + assert(val); + if (!val) { + RNP_LOG("NULL val."); + return NULL; + } + bignum_t *res = bn_new(); + if (!res) { + return NULL; + } + if (botan_mp_from_bin(res->mp, val->mpi, val->len)) { + bn_free(res); + res = NULL; + } + return res; +} + +bool +bn2mpi(const bignum_t *bn, pgp_mpi_t *val) +{ + val->len = bn_num_bytes(*bn); + if (val->len > PGP_MPINT_SIZE) { + RNP_LOG("Too large MPI."); + val->len = 0; + return false; + } + return bn_bn2bin(bn, val->mpi) == 0; +} + +bignum_t * +bn_new(void) +{ + bignum_t *a = (bignum_t *) calloc(1, sizeof(*a)); + if (!a) { + return NULL; + } + botan_mp_init(&a->mp); + return a; +} + +void +bn_free(bignum_t *a) +{ + if (a) { + botan_mp_destroy(a->mp); + free(a); + } +} + +size_t +bn_num_bytes(const bignum_t &a) +{ + size_t bytes = 0; + if (botan_mp_num_bits(a.mp, &bytes)) { + RNP_LOG("botan_mp_num_bits failed."); + } + return BITS_TO_BYTES(bytes); +} diff --git a/comm/third_party/rnp/src/lib/crypto/bn.h b/comm/third_party/rnp/src/lib/crypto/bn.h new file mode 100644 index 0000000000..26cc547690 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/bn.h @@ -0,0 +1,64 @@ +/*- + * Copyright (c) 2017-2021 Ribose Inc. + * Copyright (c) 2012 Alistair Crooks <agc@NetBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RNP_BN_H_ +#define RNP_BN_H_ + +#include <stdio.h> +#include <stdint.h> +#include "config.h" +#include "mpi.h" + +#if defined(CRYPTO_BACKEND_OPENSSL) +#include <openssl/bn.h> + +#define bignum_t BIGNUM +#elif defined(CRYPTO_BACKEND_BOTAN) +typedef struct botan_mp_struct *botan_mp_t; +typedef struct bignum_t_st { + botan_mp_t mp; +} bignum_t; + +#define BN_HANDLE(x) ((x).mp) +#define BN_HANDLE_PTR(x) ((x)->mp) +#else +#error "Unknown crypto backend." +#endif + +/*********************************/ + +bignum_t *bn_new(void); +void bn_free(bignum_t * /*a*/); + +int bn_bn2bin(const bignum_t * /*a*/, unsigned char * /*b*/); + +bignum_t *mpi2bn(const pgp_mpi_t *val); + +bool bn2mpi(const bignum_t *bn, pgp_mpi_t *val); + +size_t bn_num_bytes(const bignum_t &a); + +#endif diff --git a/comm/third_party/rnp/src/lib/crypto/bn_ossl.cpp b/comm/third_party/rnp/src/lib/crypto/bn_ossl.cpp new file mode 100644 index 0000000000..34e1a3e205 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/bn_ossl.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdlib.h> +#include <assert.h> +#include "bn.h" +#include "logging.h" + +/* store in unsigned [big endian] format */ +int +bn_bn2bin(const bignum_t *a, unsigned char *b) +{ + if (!a || !b) { + return -1; + } + return BN_bn2bin(a, b) >= 0 ? 0 : -1; +} + +bignum_t * +mpi2bn(const pgp_mpi_t *val) +{ + assert(val); + if (!val) { + RNP_LOG("NULL val."); + return NULL; + } + bignum_t *res = bn_new(); + if (!res) { + return NULL; + } + if (!BN_bin2bn(val->mpi, val->len, res)) { + bn_free(res); + res = NULL; + } + return res; +} + +bool +bn2mpi(const bignum_t *bn, pgp_mpi_t *val) +{ + val->len = bn_num_bytes(*bn); + return bn_bn2bin(bn, val->mpi) == 0; +} + +bignum_t * +bn_new(void) +{ + return BN_new(); +} + +void +bn_free(bignum_t *a) +{ + BN_clear_free(a); +} + +size_t +bn_num_bytes(const bignum_t &a) +{ + return (BN_num_bits(&a) + 7) / 8; +} diff --git a/comm/third_party/rnp/src/lib/crypto/cipher.cpp b/comm/third_party/rnp/src/lib/crypto/cipher.cpp new file mode 100644 index 0000000000..a6c6fcee43 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/cipher.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "config.h" +#include "symmetric.h" +#include "cipher.hpp" + +#if defined(CRYPTO_BACKEND_OPENSSL) +#include "cipher_ossl.hpp" +#elif defined(CRYPTO_BACKEND_BOTAN) +#include "cipher_botan.hpp" +#endif + +std::unique_ptr<Cipher> +Cipher::encryption(pgp_symm_alg_t cipher, + pgp_cipher_mode_t mode, + size_t tag_size, + bool disable_padding) +{ +#if defined(CRYPTO_BACKEND_OPENSSL) + return Cipher_OpenSSL::encryption(cipher, mode, tag_size, disable_padding); +#elif defined(CRYPTO_BACKEND_BOTAN) + return Cipher_Botan::encryption(cipher, mode, tag_size, disable_padding); +#else +#error "Crypto backend not specified" +#endif +} + +std::unique_ptr<Cipher> +Cipher::decryption(pgp_symm_alg_t cipher, + pgp_cipher_mode_t mode, + size_t tag_size, + bool disable_padding) +{ +#if defined(CRYPTO_BACKEND_OPENSSL) + return Cipher_OpenSSL::decryption(cipher, mode, tag_size, disable_padding); +#elif defined(CRYPTO_BACKEND_BOTAN) + return Cipher_Botan::decryption(cipher, mode, tag_size, disable_padding); +#else +#error "Crypto backend not specified" +#endif +} + +Cipher::Cipher(pgp_symm_alg_t alg) : m_alg(alg) +{ +} + +Cipher::~Cipher() +{ +} + +size_t +Cipher::block_size() const +{ + return pgp_block_size(m_alg); +} diff --git a/comm/third_party/rnp/src/lib/crypto/cipher.hpp b/comm/third_party/rnp/src/lib/crypto/cipher.hpp new file mode 100644 index 0000000000..c9edf15789 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/cipher.hpp @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef RNP_CIPHER_HPP +#define RNP_CIPHER_HPP + +#include <memory> +#include <string> +#include <repgp/repgp_def.h> + +// Note: for AEAD modes we append the authentication tag to the ciphertext as in RFC 5116 +class Cipher { + public: + // the tag size should be 0 for non-AEAD and must be non-zero for AEAD modes (no default) + static std::unique_ptr<Cipher> encryption(pgp_symm_alg_t cipher, + pgp_cipher_mode_t mode, + size_t tag_size = 0, + bool disable_padding = false); + static std::unique_ptr<Cipher> decryption(pgp_symm_alg_t cipher, + pgp_cipher_mode_t mode, + size_t tag_size = 0, + bool disable_padding = false); + + virtual bool set_key(const uint8_t *key, size_t key_length) = 0; + virtual bool set_iv(const uint8_t *iv, size_t iv_length) = 0; + // only valid for AEAD modes + virtual bool set_ad(const uint8_t *ad, size_t ad_length) = 0; + + virtual size_t block_size() const; + virtual size_t update_granularity() const = 0; + + // input_length must be a multiple of update_granularity + virtual bool update(uint8_t * output, + size_t output_length, + size_t * output_written, + const uint8_t *input, + size_t input_length, + size_t * input_consumed) = 0; + // process final block and perform any padding + virtual bool finish(uint8_t * output, + size_t output_length, + size_t * output_written, + const uint8_t *input, + size_t input_length, + size_t * input_consumed) = 0; + + virtual ~Cipher(); + + protected: + Cipher(pgp_symm_alg_t alg); + + pgp_symm_alg_t m_alg; +}; + +#endif diff --git a/comm/third_party/rnp/src/lib/crypto/cipher_botan.cpp b/comm/third_party/rnp/src/lib/crypto/cipher_botan.cpp new file mode 100644 index 0000000000..c2c4ab3939 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/cipher_botan.cpp @@ -0,0 +1,245 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include <sstream> +#include <cassert> +#include <botan/aead.h> +#include "cipher_botan.hpp" +#include "utils.h" +#include "types.h" + +static const id_str_pair cipher_mode_map[] = { + {PGP_CIPHER_MODE_CBC, "CBC"}, + {PGP_CIPHER_MODE_OCB, "OCB"}, + {0, NULL}, +}; + +static const id_str_pair cipher_map[] = { + {PGP_SA_AES_128, "AES-128"}, + {PGP_SA_AES_256, "AES-256"}, + {PGP_SA_IDEA, "IDEA"}, + {0, NULL}, +}; + +Cipher_Botan * +Cipher_Botan::create(pgp_symm_alg_t alg, const std::string &name, bool encrypt) +{ +#if !defined(ENABLE_IDEA) + if (alg == PGP_SA_IDEA) { + RNP_LOG("IDEA support has been disabled"); + return nullptr; + } +#endif +#if !defined(ENABLE_BLOWFISH) + if (alg == PGP_SA_BLOWFISH) { + RNP_LOG("Blowfish support has been disabled"); + return nullptr; + } +#endif +#if !defined(ENABLE_CAST5) + if (alg == PGP_SA_CAST5) { + RNP_LOG("CAST5 support has been disabled"); + return nullptr; + } +#endif + auto cipher = Botan::Cipher_Mode::create( + name, encrypt ? Botan::Cipher_Dir::ENCRYPTION : Botan::Cipher_Dir::DECRYPTION); + if (!cipher) { + RNP_LOG("Failed to create cipher '%s'", name.c_str()); + return nullptr; + } + return new (std::nothrow) Cipher_Botan(alg, std::move(cipher)); +} + +static std::string +make_name(pgp_symm_alg_t cipher, pgp_cipher_mode_t mode, size_t tag_size, bool disable_padding) +{ + const char *cipher_string = id_str_pair::lookup(cipher_map, cipher, NULL); + const char *mode_string = id_str_pair::lookup(cipher_mode_map, mode, NULL); + if (!cipher_string || !mode_string) { + return ""; + } + try { + std::stringstream ss; + ss << cipher_string << "/" << mode_string; + if (tag_size) { + ss << "(" << tag_size << ")"; + } + if (mode == PGP_CIPHER_MODE_CBC && disable_padding) { + ss << "/NoPadding"; + } + return ss.str(); + } catch (const std::exception &e) { + return ""; + } +} + +std::unique_ptr<Cipher_Botan> +Cipher_Botan::encryption(pgp_symm_alg_t cipher, + pgp_cipher_mode_t mode, + size_t tag_size, + bool disable_padding) +{ + return std::unique_ptr<Cipher_Botan>( + create(cipher, make_name(cipher, mode, tag_size, disable_padding), true)); +} + +std::unique_ptr<Cipher_Botan> +Cipher_Botan::decryption(pgp_symm_alg_t cipher, + pgp_cipher_mode_t mode, + size_t tag_size, + bool disable_padding) +{ + return std::unique_ptr<Cipher_Botan>( + create(cipher, make_name(cipher, mode, tag_size, disable_padding), false)); +} + +size_t +Cipher_Botan::update_granularity() const +{ + return m_cipher->update_granularity(); +} + +bool +Cipher_Botan::set_key(const uint8_t *key, size_t key_length) +{ + try { + m_cipher->set_key(key, key_length); + } catch (const std::exception &e) { + RNP_LOG("Failed to set key: %s", e.what()); + return false; + } + return true; +} + +bool +Cipher_Botan::set_iv(const uint8_t *iv, size_t iv_length) +{ + try { + m_cipher->start(iv, iv_length); + m_buf.reserve(this->update_granularity()); + } catch (const std::exception &e) { + RNP_LOG("Failed to set IV: %s", e.what()); + return false; + } + return true; +} + +bool +Cipher_Botan::set_ad(const uint8_t *ad, size_t ad_length) +{ + assert(m_cipher->authenticated()); + try { + dynamic_cast<Botan::AEAD_Mode &>(*m_cipher).set_associated_data(ad, ad_length); + } catch (const std::exception &e) { + RNP_LOG("Failed to set AAD: %s", e.what()); + return false; + } + return true; +} + +bool +Cipher_Botan::update(uint8_t * output, + size_t output_length, + size_t * output_written, + const uint8_t *input, + size_t input_length, + size_t * input_consumed) +{ + try { + size_t ud = this->update_granularity(); + m_buf.resize(ud); + + *input_consumed = 0; + *output_written = 0; + while (input_length >= ud && output_length >= ud) { + m_buf.assign(input, input + ud); + size_t written = m_cipher->process(m_buf.data(), ud); + std::copy(m_buf.data(), m_buf.data() + written, output); + input += ud; + output += written; + input_length -= ud; + output_length -= written; + + *output_written += written; + *input_consumed += ud; + } + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } + return true; +} + +bool +Cipher_Botan::finish(uint8_t * output, + size_t output_length, + size_t * output_written, + const uint8_t *input, + size_t input_length, + size_t * input_consumed) +{ + try { + *input_consumed = 0; + *output_written = 0; + size_t ud = this->update_granularity(); + if (input_length > ud) { + if (!update(output, + output_length, + output_written, + input, + input_length - ud, + input_consumed)) { + return false; + } + input += *input_consumed; + input_length = input_length - *input_consumed; + output += *output_written; + output_length -= *output_written; + } + Botan::secure_vector<uint8_t> final_block(input, input + input_length); + m_cipher->finish(final_block); + if (final_block.size() > output_length) { + RNP_LOG("Insufficient buffer"); + return false; + } + std::copy(final_block.begin(), final_block.end(), output); + *output_written += final_block.size(); + *input_consumed += input_length; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } + return true; +} + +Cipher_Botan::Cipher_Botan(pgp_symm_alg_t alg, std::unique_ptr<Botan::Cipher_Mode> cipher) + : Cipher(alg), m_cipher(std::move(cipher)) +{ +} + +Cipher_Botan::~Cipher_Botan() +{ +} diff --git a/comm/third_party/rnp/src/lib/crypto/cipher_botan.hpp b/comm/third_party/rnp/src/lib/crypto/cipher_botan.hpp new file mode 100644 index 0000000000..517d38b6af --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/cipher_botan.hpp @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef RNP_CIPHER_BOTAN_HPP +#define RNP_CIPHER_BOTAN_HPP + +#include "cipher.hpp" +#include <botan/cipher_mode.h> +#include <repgp/repgp_def.h> + +class Cipher_Botan : public Cipher { + public: + static std::unique_ptr<Cipher_Botan> encryption(pgp_symm_alg_t cipher, + pgp_cipher_mode_t mode, + size_t tag_size, + bool disable_padding); + static std::unique_ptr<Cipher_Botan> decryption(pgp_symm_alg_t cipher, + pgp_cipher_mode_t mode, + size_t tag_size, + bool disable_padding); + + bool set_key(const uint8_t *key, size_t key_length) override; + bool set_iv(const uint8_t *iv, size_t iv_length) override; + bool set_ad(const uint8_t *ad, size_t ad_length) override; + + size_t update_granularity() const override; + + bool update(uint8_t * output, + size_t output_length, + size_t * output_written, + const uint8_t *input, + size_t input_length, + size_t * input_consumed) override; + bool finish(uint8_t * output, + size_t output_length, + size_t * output_written, + const uint8_t *input, + size_t input_length, + size_t * input_consumed) override; + virtual ~Cipher_Botan(); + + private: + Cipher_Botan(pgp_symm_alg_t alg, std::unique_ptr<Botan::Cipher_Mode> cipher); + + std::unique_ptr<Botan::Cipher_Mode> m_cipher; + std::vector<uint8_t> m_buf; + + static Cipher_Botan *create(pgp_symm_alg_t alg, const std::string &name, bool encrypt); +}; + +#endif diff --git a/comm/third_party/rnp/src/lib/crypto/cipher_ossl.cpp b/comm/third_party/rnp/src/lib/crypto/cipher_ossl.cpp new file mode 100644 index 0000000000..bb81d488a1 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/cipher_ossl.cpp @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include <cassert> +#include <algorithm> + +#include "cipher_ossl.hpp" +#include "utils.h" +#include "types.h" +#include <openssl/err.h> + +static const id_str_pair cipher_mode_map[] = { + {PGP_CIPHER_MODE_CBC, "CBC"}, + {PGP_CIPHER_MODE_OCB, "OCB"}, + {0, NULL}, +}; + +static const id_str_pair cipher_map[] = { + {PGP_SA_AES_128, "AES-128"}, + {PGP_SA_AES_256, "AES-256"}, + {PGP_SA_IDEA, "IDEA"}, + {0, NULL}, +}; + +EVP_CIPHER_CTX * +Cipher_OpenSSL::create(pgp_symm_alg_t alg, + const std::string &name, + bool encrypt, + size_t tag_size, + bool disable_padding) +{ +#if !defined(ENABLE_IDEA) + if (alg == PGP_SA_IDEA) { + RNP_LOG("IDEA support has been disabled"); + return nullptr; + } +#endif +#if !defined(ENABLE_BLOWFISH) + if (alg == PGP_SA_BLOWFISH) { + RNP_LOG("Blowfish support has been disabled"); + return nullptr; + } +#endif +#if !defined(ENABLE_CAST5) + if (alg == PGP_SA_CAST5) { + RNP_LOG("CAST5 support has been disabled"); + return nullptr; + } +#endif + const EVP_CIPHER *cipher = EVP_get_cipherbyname(name.c_str()); + if (!cipher) { + RNP_LOG("Unsupported cipher: %s", name.c_str()); + return nullptr; + } + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + if (!ctx) { + RNP_LOG("Failed to create cipher context: %lu", ERR_peek_last_error()); + return nullptr; + } + if (EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, encrypt ? 1 : 0) != 1) { + RNP_LOG("Failed to initialize cipher: %lu", ERR_peek_last_error()); + EVP_CIPHER_CTX_free(ctx); + return nullptr; + } + // set tag size + if (encrypt && tag_size) { + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, tag_size, NULL) != 1) { + RNP_LOG("Failed to set AEAD tag length: %lu", ERR_peek_last_error()); + EVP_CIPHER_CTX_free(ctx); + return nullptr; + } + } + if (disable_padding) { + EVP_CIPHER_CTX_set_padding(ctx, 0); + } + return ctx; +} + +static std::string +make_name(pgp_symm_alg_t cipher, pgp_cipher_mode_t mode) +{ + const char *cipher_string = id_str_pair::lookup(cipher_map, cipher, NULL); + const char *mode_string = id_str_pair::lookup(cipher_mode_map, mode, NULL); + if (!cipher_string || !mode_string) { + return ""; + } + return std::string(cipher_string) + "-" + mode_string; +} + +std::unique_ptr<Cipher_OpenSSL> +Cipher_OpenSSL::encryption(pgp_symm_alg_t cipher, + pgp_cipher_mode_t mode, + size_t tag_size, + bool disable_padding) +{ + EVP_CIPHER_CTX *ossl_ctx = + create(cipher, make_name(cipher, mode), true, tag_size, disable_padding); + if (!ossl_ctx) { + return NULL; + } + return std::unique_ptr<Cipher_OpenSSL>(new (std::nothrow) + Cipher_OpenSSL(cipher, ossl_ctx, tag_size, true)); +} + +std::unique_ptr<Cipher_OpenSSL> +Cipher_OpenSSL::decryption(pgp_symm_alg_t cipher, + pgp_cipher_mode_t mode, + size_t tag_size, + bool disable_padding) +{ + EVP_CIPHER_CTX *ossl_ctx = + create(cipher, make_name(cipher, mode), false, tag_size, disable_padding); + if (!ossl_ctx) { + return NULL; + } + return std::unique_ptr<Cipher_OpenSSL>( + new (std::nothrow) Cipher_OpenSSL(cipher, ossl_ctx, tag_size, false)); +} + +bool +Cipher_OpenSSL::set_key(const uint8_t *key, size_t key_length) +{ + assert(key_length <= INT_MAX); + return EVP_CIPHER_CTX_set_key_length(m_ctx, (int) key_length) == 1 && + EVP_CipherInit_ex(m_ctx, NULL, NULL, key, NULL, -1) == 1; +} + +bool +Cipher_OpenSSL::set_iv(const uint8_t *iv, size_t iv_length) +{ + assert(iv_length <= INT_MAX); + // set IV len for AEAD modes + if (m_tag_size && + EVP_CIPHER_CTX_ctrl(m_ctx, EVP_CTRL_AEAD_SET_IVLEN, (int) iv_length, NULL) != 1) { + RNP_LOG("Failed to set AEAD IV length: %lu", ERR_peek_last_error()); + return false; + } + if (EVP_CIPHER_CTX_iv_length(m_ctx) != (int) iv_length) { + RNP_LOG("IV length mismatch"); + return false; + } + if (EVP_CipherInit_ex(m_ctx, NULL, NULL, NULL, iv, -1) != 1) { + RNP_LOG("Failed to set IV: %lu", ERR_peek_last_error()); + } + return true; +} + +bool +Cipher_OpenSSL::set_ad(const uint8_t *ad, size_t ad_length) +{ + assert(m_tag_size); + int outlen = 0; + if (EVP_CipherUpdate(m_ctx, NULL, &outlen, ad, ad_length) != 1) { + RNP_LOG("Failed to set AD: %lu", ERR_peek_last_error()); + return false; + } + return true; +} + +size_t +Cipher_OpenSSL::update_granularity() const +{ + return (size_t) EVP_CIPHER_CTX_block_size(m_ctx); +} + +bool +Cipher_OpenSSL::update(uint8_t * output, + size_t output_length, + size_t * output_written, + const uint8_t *input, + size_t input_length, + size_t * input_consumed) +{ + if (input_length > INT_MAX) { + return false; + } + *input_consumed = 0; + *output_written = 0; + if (input_length == 0) { + return true; + } + int outl = 0; + if (EVP_CipherUpdate(m_ctx, output, &outl, input, (int) input_length) != 1) { + RNP_LOG("EVP_CipherUpdate failed: %lu", ERR_peek_last_error()); + return false; + } + assert((size_t) outl < output_length); + *input_consumed = input_length; + *output_written = (size_t) outl; + return true; +} + +bool +Cipher_OpenSSL::finish(uint8_t * output, + size_t output_length, + size_t * output_written, + const uint8_t *input, + size_t input_length, + size_t * input_consumed) +{ + if (input_length > INT_MAX) { + return false; + } + if (!m_encrypt && input_length < m_tag_size) { + RNP_LOG("Insufficient input for final block (missing tag)"); + return false; + } + *input_consumed = 0; + *output_written = 0; + if (!m_encrypt && m_tag_size) { + // set the tag from the end of the ciphertext + if (EVP_CIPHER_CTX_ctrl(m_ctx, + EVP_CTRL_AEAD_SET_TAG, + m_tag_size, + const_cast<uint8_t *>(input) + input_length - m_tag_size) != + 1) { + RNP_LOG("Failed to set expected AEAD tag: %lu", ERR_peek_last_error()); + return false; + } + size_t ats = std::min(m_tag_size, input_length); + input_length -= ats; // m_tag_size; + *input_consumed += ats; // m_tag_size; + } + int outl = 0; + if (EVP_CipherUpdate(m_ctx, output, &outl, input, (int) input_length) != 1) { + RNP_LOG("EVP_CipherUpdate failed: %lu", ERR_peek_last_error()); + return false; + } + input += input_length; + *input_consumed += input_length; + output += outl; + *output_written += (size_t) outl; + if (EVP_CipherFinal_ex(m_ctx, output, &outl) != 1) { + RNP_LOG("EVP_CipherFinal_ex failed: %lu", ERR_peek_last_error()); + return false; + } + *output_written += (size_t) outl; + output += (size_t) outl; + if (m_encrypt && m_tag_size) { + // append the tag + if (EVP_CIPHER_CTX_ctrl(m_ctx, EVP_CTRL_AEAD_GET_TAG, m_tag_size, output) != 1) { + RNP_LOG("Failed to append AEAD tag: %lu", ERR_peek_last_error()); + return false; + } + *output_written += m_tag_size; + } + return true; +} + +Cipher_OpenSSL::Cipher_OpenSSL(pgp_symm_alg_t alg, + EVP_CIPHER_CTX *ctx, + size_t tag_size, + bool encrypt) + : Cipher(alg), m_ctx(ctx), m_tag_size(tag_size), m_encrypt(encrypt) +{ + m_block_size = EVP_CIPHER_CTX_block_size(m_ctx); +} + +Cipher_OpenSSL::~Cipher_OpenSSL() +{ + EVP_CIPHER_CTX_free(m_ctx); +} diff --git a/comm/third_party/rnp/src/lib/crypto/cipher_ossl.hpp b/comm/third_party/rnp/src/lib/crypto/cipher_ossl.hpp new file mode 100644 index 0000000000..da2bb4efe9 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/cipher_ossl.hpp @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef RNP_CIPHER_OSSL_HPP +#define RNP_CIPHER_OSSL_HPP + +#include "cipher.hpp" +#include <openssl/evp.h> +#include <repgp/repgp_def.h> + +class Cipher_OpenSSL : public Cipher { + public: + static std::unique_ptr<Cipher_OpenSSL> encryption(pgp_symm_alg_t cipher, + pgp_cipher_mode_t mode, + size_t tag_size, + bool disable_padding); + static std::unique_ptr<Cipher_OpenSSL> decryption(pgp_symm_alg_t cipher, + pgp_cipher_mode_t mode, + size_t tag_size, + bool disable_padding); + + bool set_key(const uint8_t *key, size_t key_length) override; + bool set_iv(const uint8_t *iv, size_t iv_length) override; + bool set_ad(const uint8_t *ad, size_t ad_length) override; + + size_t update_granularity() const override; + + // input_length should not exceed INT_MAX + bool update(uint8_t * output, + size_t output_length, + size_t * output_written, + const uint8_t *input, + size_t input_length, + size_t * input_consumed) override; + bool finish(uint8_t * output, + size_t output_length, + size_t * output_written, + const uint8_t *input, + size_t input_length, + size_t * input_consumed) override; + virtual ~Cipher_OpenSSL(); + + private: + Cipher_OpenSSL(pgp_symm_alg_t alg, EVP_CIPHER_CTX *ctx, size_t tag_size, bool encrypt); + + EVP_CIPHER_CTX *m_ctx; + size_t m_block_size; + size_t m_tag_size; + bool m_encrypt; + + static EVP_CIPHER_CTX *create(pgp_symm_alg_t alg, + const std::string &name, + bool encrypt, + size_t tag_size, + bool disable_padding); +}; + +#endif diff --git a/comm/third_party/rnp/src/lib/crypto/common.h b/comm/third_party/rnp/src/lib/crypto/common.h new file mode 100644 index 0000000000..3d9b8378e7 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/common.h @@ -0,0 +1,51 @@ +/*- + * Copyright (c) 2018 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RNP_CRYPTO_COMMON_H_ +#define RNP_CRYPTO_COMMON_H_ + +/* base */ +#include "mpi.h" +#include "rng.h" +/* asymmetric crypto */ +#include "rsa.h" +#include "dsa.h" +#include "elgamal.h" +#include "ec.h" +#include "ecdh.h" +#include "ecdsa.h" +#include "sm2.h" +#include "eddsa.h" +/* symmetric crypto */ +#include "symmetric.h" +/* hash */ +#include "hash.hpp" +/* s2k */ +#include "s2k.h" +/* backend name and version */ +#include "backend_version.h" + +#endif // RNP_CRYPTO_COMMON_H_ diff --git a/comm/third_party/rnp/src/lib/crypto/dl_ossl.cpp b/comm/third_party/rnp/src/lib/crypto/dl_ossl.cpp new file mode 100644 index 0000000000..1e96218af9 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/dl_ossl.cpp @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <cstdlib> +#include <string> +#include <cassert> +#include "bn.h" +#include "dl_ossl.h" +#include "utils.h" +#include <openssl/dh.h> +#include <openssl/err.h> +#include <openssl/evp.h> + +EVP_PKEY * +dl_load_key(const pgp_mpi_t &mp, + const pgp_mpi_t *mq, + const pgp_mpi_t &mg, + const pgp_mpi_t &my, + const pgp_mpi_t *mx) +{ + DH * dh = NULL; + EVP_PKEY *evpkey = NULL; + bignum_t *p = mpi2bn(&mp); + bignum_t *q = mq ? mpi2bn(mq) : NULL; + bignum_t *g = mpi2bn(&mg); + bignum_t *y = mpi2bn(&my); + bignum_t *x = mx ? mpi2bn(mx) : NULL; + + if (!p || (mq && !q) || !g || !y || (mx && !x)) { + RNP_LOG("out of memory"); + goto done; + } + + dh = DH_new(); + if (!dh) { + RNP_LOG("out of memory"); + goto done; + } + int res; + /* line below must not fail */ + res = DH_set0_pqg(dh, p, q, g); + assert(res == 1); + if (res < 1) { + goto done; + } + p = NULL; + q = NULL; + g = NULL; + /* line below must not fail */ + res = DH_set0_key(dh, y, x); + assert(res == 1); + if (res < 1) { + goto done; + } + y = NULL; + x = NULL; + + evpkey = EVP_PKEY_new(); + if (!evpkey) { + RNP_LOG("allocation failed"); + goto done; + } + if (EVP_PKEY_set1_DH(evpkey, dh) <= 0) { + RNP_LOG("Failed to set key: %lu", ERR_peek_last_error()); + EVP_PKEY_free(evpkey); + evpkey = NULL; + } +done: + DH_free(dh); + bn_free(p); + bn_free(q); + bn_free(g); + bn_free(y); + bn_free(x); + return evpkey; +} + +static rnp_result_t +dl_validate_secret_key(EVP_PKEY *dlkey, const pgp_mpi_t &mx) +{ + const DH *dh = EVP_PKEY_get0_DH(dlkey); + assert(dh); + const bignum_t *p = DH_get0_p(dh); + const bignum_t *q = DH_get0_q(dh); + const bignum_t *g = DH_get0_g(dh); + const bignum_t *y = DH_get0_pub_key(dh); + assert(p && g && y); + bignum_t *p1 = NULL; + + rnp_result_t ret = RNP_ERROR_GENERIC; + + BN_CTX * ctx = BN_CTX_new(); + bignum_t *x = mpi2bn(&mx); + bignum_t *cy = bn_new(); + + if (!x || !cy || !ctx) { + RNP_LOG("Allocation failed"); + goto done; + } + if (!q) { + /* if q is NULL then group order is (p - 1) / 2 */ + p1 = BN_dup(p); + if (!p1) { + RNP_LOG("Allocation failed"); + goto done; + } + int res; + res = BN_rshift(p1, p1, 1); + assert(res == 1); + if (res < 1) { + RNP_LOG("BN_rshift failed."); + goto done; + } + q = p1; + } + if (BN_cmp(x, q) != -1) { + RNP_LOG("x is too large."); + goto done; + } + if (BN_mod_exp_mont_consttime(cy, g, x, p, ctx, NULL) < 1) { + RNP_LOG("Exponentiation failed"); + goto done; + } + if (BN_cmp(cy, y) == 0) { + ret = RNP_SUCCESS; + } +done: + BN_CTX_free(ctx); + bn_free(x); + bn_free(cy); + bn_free(p1); + return ret; +} + +rnp_result_t +dl_validate_key(EVP_PKEY *pkey, const pgp_mpi_t *x) +{ + rnp_result_t ret = RNP_ERROR_GENERIC; + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey, NULL); + if (!ctx) { + RNP_LOG("Context allocation failed: %lu", ERR_peek_last_error()); + goto done; + } + int res; + res = EVP_PKEY_param_check(ctx); + if (res < 0) { + RNP_LOG("Param validation error: %lu (%s)", + ERR_peek_last_error(), + ERR_reason_error_string(ERR_peek_last_error())); + } + if (res < 1) { + /* ElGamal specification doesn't seem to restrict P to the safe prime */ + auto err = ERR_peek_last_error(); + DHerr(DH_F_DH_CHECK_EX, DH_R_CHECK_P_NOT_SAFE_PRIME); + if ((ERR_GET_REASON(err) == DH_R_CHECK_P_NOT_SAFE_PRIME)) { + RNP_LOG("Warning! P is not a safe prime."); + } else { + goto done; + } + } + res = EVP_PKEY_public_check(ctx); + if (res < 0) { + RNP_LOG("Key validation error: %lu", ERR_peek_last_error()); + } + if (res < 1) { + goto done; + } + /* There is no private key check in OpenSSL yet, so need to check x vs y manually */ + if (!x) { + ret = RNP_SUCCESS; + goto done; + } + ret = dl_validate_secret_key(pkey, *x); +done: + EVP_PKEY_CTX_free(ctx); + return ret; +} diff --git a/comm/third_party/rnp/src/lib/crypto/dl_ossl.h b/comm/third_party/rnp/src/lib/crypto/dl_ossl.h new file mode 100644 index 0000000000..fcafc0ac9a --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/dl_ossl.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DL_OSSL_H_ +#define DL_OSSL_H_ + +#include "types.h" +#include "config.h" +#include <rnp/rnp_def.h> +#include "mpi.h" +#include <openssl/evp.h> + +EVP_PKEY *dl_load_key(const pgp_mpi_t &mp, + const pgp_mpi_t *mq, + const pgp_mpi_t &mg, + const pgp_mpi_t &my, + const pgp_mpi_t *mx); + +rnp_result_t dl_validate_key(EVP_PKEY *pkey, const pgp_mpi_t *mx); + +#endif diff --git a/comm/third_party/rnp/src/lib/crypto/dsa.cpp b/comm/third_party/rnp/src/lib/crypto/dsa.cpp new file mode 100644 index 0000000000..8763f00400 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/dsa.cpp @@ -0,0 +1,382 @@ +/*- + * Copyright (c) 2017-2018 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/*- + * Copyright (c) 2009 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Alistair Crooks (agc@NetBSD.org) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/* + * Copyright (c) 2005-2008 Nominet UK (www.nic.uk) + * All rights reserved. + * Contributors: Ben Laurie, Rachel Willmer. The Contributors have asserted + * their moral rights under the UK Copyright Design and Patents Act 1988 to + * be recorded as the authors of this copyright work. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. + * + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** \file + */ +#include <stdlib.h> +#include <string.h> +#include <botan/ffi.h> +#include <rnp/rnp_def.h> +#include "dsa.h" +#include "bn.h" +#include "utils.h" + +#define DSA_MAX_Q_BITLEN 256 + +rnp_result_t +dsa_validate_key(rnp::RNG *rng, const pgp_dsa_key_t *key, bool secret) +{ + bignum_t * p = NULL; + bignum_t * q = NULL; + bignum_t * g = NULL; + bignum_t * y = NULL; + bignum_t * x = NULL; + botan_pubkey_t bpkey = NULL; + botan_privkey_t bskey = NULL; + rnp_result_t ret = RNP_ERROR_GENERIC; + + /* load and check public key part */ + p = mpi2bn(&key->p); + q = mpi2bn(&key->q); + g = mpi2bn(&key->g); + y = mpi2bn(&key->y); + + if (!p || !q || !g || !y) { + RNP_LOG("out of memory"); + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + + if (botan_pubkey_load_dsa( + &bpkey, BN_HANDLE_PTR(p), BN_HANDLE_PTR(q), BN_HANDLE_PTR(g), BN_HANDLE_PTR(y))) { + goto done; + } + + if (botan_pubkey_check_key(bpkey, rng->handle(), 0)) { + goto done; + } + + if (!secret) { + ret = RNP_SUCCESS; + goto done; + } + + /* load and check secret key part */ + if (!(x = mpi2bn(&key->x))) { + RNP_LOG("out of memory"); + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + + if (botan_privkey_load_dsa( + &bskey, BN_HANDLE_PTR(p), BN_HANDLE_PTR(q), BN_HANDLE_PTR(g), BN_HANDLE_PTR(x))) { + goto done; + } + + if (botan_privkey_check_key(bskey, rng->handle(), 0)) { + goto done; + } + + ret = RNP_SUCCESS; +done: + bn_free(p); + bn_free(q); + bn_free(g); + bn_free(y); + bn_free(x); + botan_privkey_destroy(bskey); + botan_pubkey_destroy(bpkey); + return ret; +} + +rnp_result_t +dsa_sign(rnp::RNG * rng, + pgp_dsa_signature_t *sig, + const uint8_t * hash, + size_t hash_len, + const pgp_dsa_key_t *key) +{ + botan_privkey_t dsa_key = NULL; + botan_pk_op_sign_t sign_op = NULL; + size_t q_order = 0; + uint8_t sign_buf[2 * BITS_TO_BYTES(DSA_MAX_Q_BITLEN)] = {0}; + bignum_t * p = NULL, *q = NULL, *g = NULL, *x = NULL; + rnp_result_t ret = RNP_ERROR_SIGNING_FAILED; + size_t sigbuf_size = sizeof(sign_buf); + + size_t z_len = 0; + + memset(sig, 0, sizeof(*sig)); + q_order = mpi_bytes(&key->q); + if ((2 * q_order) > sizeof(sign_buf)) { + RNP_LOG("wrong q order"); + return RNP_ERROR_BAD_PARAMETERS; + } + + // As 'Raw' is used we need to reduce hash size (as per FIPS-186-4, 4.6) + z_len = hash_len < q_order ? hash_len : q_order; + + p = mpi2bn(&key->p); + q = mpi2bn(&key->q); + g = mpi2bn(&key->g); + x = mpi2bn(&key->x); + + if (!p || !q || !g || !x) { + RNP_LOG("out of memory"); + ret = RNP_ERROR_OUT_OF_MEMORY; + goto end; + } + + if (botan_privkey_load_dsa( + &dsa_key, BN_HANDLE_PTR(p), BN_HANDLE_PTR(q), BN_HANDLE_PTR(g), BN_HANDLE_PTR(x))) { + RNP_LOG("Can't load key"); + goto end; + } + + if (botan_pk_op_sign_create(&sign_op, dsa_key, "Raw", 0)) { + goto end; + } + + if (botan_pk_op_sign_update(sign_op, hash, z_len)) { + goto end; + } + + if (botan_pk_op_sign_finish(sign_op, rng->handle(), sign_buf, &sigbuf_size)) { + RNP_LOG("Signing has failed"); + goto end; + } + + // Now load the DSA (r,s) values from the signature. + if (!mem2mpi(&sig->r, sign_buf, q_order) || + !mem2mpi(&sig->s, sign_buf + q_order, q_order)) { + goto end; + } + ret = RNP_SUCCESS; + +end: + bn_free(p); + bn_free(q); + bn_free(g); + bn_free(x); + botan_pk_op_sign_destroy(sign_op); + botan_privkey_destroy(dsa_key); + return ret; +} + +rnp_result_t +dsa_verify(const pgp_dsa_signature_t *sig, + const uint8_t * hash, + size_t hash_len, + const pgp_dsa_key_t * key) +{ + botan_pubkey_t dsa_key = NULL; + botan_pk_op_verify_t verify_op = NULL; + uint8_t sign_buf[2 * BITS_TO_BYTES(DSA_MAX_Q_BITLEN)] = {0}; + size_t q_order = 0; + size_t r_blen, s_blen; + bignum_t * p = NULL, *q = NULL, *g = NULL, *y = NULL; + rnp_result_t ret = RNP_ERROR_GENERIC; + size_t z_len = 0; + + q_order = mpi_bytes(&key->q); + if ((2 * q_order) > sizeof(sign_buf)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + z_len = hash_len < q_order ? hash_len : q_order; + + r_blen = mpi_bytes(&sig->r); + s_blen = mpi_bytes(&sig->s); + if ((r_blen > q_order) || (s_blen > q_order)) { + RNP_LOG("Wrong signature"); + return RNP_ERROR_BAD_PARAMETERS; + } + + p = mpi2bn(&key->p); + q = mpi2bn(&key->q); + g = mpi2bn(&key->g); + y = mpi2bn(&key->y); + + if (!p || !q || !g || !y) { + RNP_LOG("out of memory"); + ret = RNP_ERROR_OUT_OF_MEMORY; + goto end; + } + + if (botan_pubkey_load_dsa( + &dsa_key, BN_HANDLE_PTR(p), BN_HANDLE_PTR(q), BN_HANDLE_PTR(g), BN_HANDLE_PTR(y))) { + RNP_LOG("Wrong key"); + goto end; + } + + mpi2mem(&sig->r, sign_buf + q_order - r_blen); + mpi2mem(&sig->s, sign_buf + 2 * q_order - s_blen); + + if (botan_pk_op_verify_create(&verify_op, dsa_key, "Raw", 0)) { + RNP_LOG("Can't create verifier"); + goto end; + } + + if (botan_pk_op_verify_update(verify_op, hash, z_len)) { + goto end; + } + + ret = (botan_pk_op_verify_finish(verify_op, sign_buf, 2 * q_order) == BOTAN_FFI_SUCCESS) ? + RNP_SUCCESS : + RNP_ERROR_SIGNATURE_INVALID; + +end: + bn_free(p); + bn_free(q); + bn_free(g); + bn_free(y); + botan_pk_op_verify_destroy(verify_op); + botan_pubkey_destroy(dsa_key); + return ret; +} + +rnp_result_t +dsa_generate(rnp::RNG *rng, pgp_dsa_key_t *key, size_t keylen, size_t qbits) +{ + if ((keylen < 1024) || (keylen > 3072) || (qbits < 160) || (qbits > 256)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + botan_privkey_t key_priv = NULL; + botan_pubkey_t key_pub = NULL; + rnp_result_t ret = RNP_ERROR_GENERIC; + bignum_t * p = bn_new(); + bignum_t * q = bn_new(); + bignum_t * g = bn_new(); + bignum_t * y = bn_new(); + bignum_t * x = bn_new(); + + if (!p || !q || !g || !y || !x) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto end; + } + + if (botan_privkey_create_dsa(&key_priv, rng->handle(), keylen, qbits) || + botan_privkey_check_key(key_priv, rng->handle(), 1) || + botan_privkey_export_pubkey(&key_pub, key_priv)) { + RNP_LOG("Wrong parameters"); + ret = RNP_ERROR_BAD_PARAMETERS; + goto end; + } + + if (botan_pubkey_get_field(BN_HANDLE_PTR(p), key_pub, "p") || + botan_pubkey_get_field(BN_HANDLE_PTR(q), key_pub, "q") || + botan_pubkey_get_field(BN_HANDLE_PTR(g), key_pub, "g") || + botan_pubkey_get_field(BN_HANDLE_PTR(y), key_pub, "y") || + botan_privkey_get_field(BN_HANDLE_PTR(x), key_priv, "x")) { + RNP_LOG("Botan FFI call failed"); + ret = RNP_ERROR_GENERIC; + goto end; + } + + if (!bn2mpi(p, &key->p) || !bn2mpi(q, &key->q) || !bn2mpi(g, &key->g) || + !bn2mpi(y, &key->y) || !bn2mpi(x, &key->x)) { + RNP_LOG("failed to copy mpi"); + goto end; + } + ret = RNP_SUCCESS; +end: + bn_free(p); + bn_free(q); + bn_free(g); + bn_free(y); + bn_free(x); + botan_privkey_destroy(key_priv); + botan_pubkey_destroy(key_pub); + return ret; +} + +pgp_hash_alg_t +dsa_get_min_hash(size_t qsize) +{ + /* + * I'm using _broken_ SHA1 here only because + * some old implementations may not understand keys created + * with other hashes. If you're sure we don't have to support + * such implementations, please be my guest and remove it. + */ + return (qsize < 160) ? PGP_HASH_UNKNOWN : + (qsize == 160) ? PGP_HASH_SHA1 : + (qsize <= 224) ? PGP_HASH_SHA224 : + (qsize <= 256) ? PGP_HASH_SHA256 : + (qsize <= 384) ? PGP_HASH_SHA384 : + (qsize <= 512) ? PGP_HASH_SHA512 + /*(qsize>512)*/ : + PGP_HASH_UNKNOWN; +} + +size_t +dsa_choose_qsize_by_psize(size_t psize) +{ + return (psize == 1024) ? 160 : + (psize <= 2047) ? 224 : + (psize <= 3072) ? DSA_MAX_Q_BITLEN : + 0; +} diff --git a/comm/third_party/rnp/src/lib/crypto/dsa.h b/comm/third_party/rnp/src/lib/crypto/dsa.h new file mode 100644 index 0000000000..52a186ac2b --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/dsa.h @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2017-2018, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * This code is originally derived from software contributed to + * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and + * carried further by Ribose Inc (https://www.ribose.com). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RNP_DSA_H_ +#define RNP_DSA_H_ + +#include <rnp/rnp_def.h> +#include <repgp/repgp_def.h> +#include "crypto/rng.h" +#include "crypto/mpi.h" + +typedef struct pgp_dsa_key_t { + pgp_mpi_t p; + pgp_mpi_t q; + pgp_mpi_t g; + pgp_mpi_t y; + /* secret mpi */ + pgp_mpi_t x; +} pgp_dsa_key_t; + +typedef struct pgp_dsa_signature_t { + pgp_mpi_t r; + pgp_mpi_t s; +} pgp_dsa_signature_t; + +/** + * @brief Checks DSA key fields for validity + * + * @param rng initialized PRNG + * @param key initialized DSA key structure + * @param secret flag which tells whether key has populated secret fields + * + * @return RNP_SUCCESS if key is valid or error code otherwise + */ +rnp_result_t dsa_validate_key(rnp::RNG *rng, const pgp_dsa_key_t *key, bool secret); + +/* + * @brief Performs DSA signing + * + * @param rng initialized PRNG + * @param sig[out] created signature + * @param hash hash to sign + * @param hash_len length of `hash` + * @param key DSA key (must include secret mpi) + * + * @returns RNP_SUCCESS + * RNP_ERROR_BAD_PARAMETERS wrong input provided + * RNP_ERROR_SIGNING_FAILED internal error + */ +rnp_result_t dsa_sign(rnp::RNG * rng, + pgp_dsa_signature_t *sig, + const uint8_t * hash, + size_t hash_len, + const pgp_dsa_key_t *key); + +/* + * @brief Performs DSA verification + * + * @param hash hash to verify + * @param hash_len length of `hash` + * @param sig signature to be verified + * @param key DSA key (secret mpi is not needed) + * + * @returns RNP_SUCCESS + * RNP_ERROR_BAD_PARAMETERS wrong input provided + * RNP_ERROR_GENERIC internal error + * RNP_ERROR_SIGNATURE_INVALID signature is invalid + */ +rnp_result_t dsa_verify(const pgp_dsa_signature_t *sig, + const uint8_t * hash, + size_t hash_len, + const pgp_dsa_key_t * key); + +/* + * @brief Performs DSA key generation + * + * @param rng initialized PRNG + * @param key[out] generated key data will be stored here + * @param keylen length of the key, in bits + * @param qbits subgroup size in bits + * + * @returns RNP_SUCCESS + * RNP_ERROR_BAD_PARAMETERS wrong input provided + * RNP_ERROR_OUT_OF_MEMORY memory allocation failed + * RNP_ERROR_GENERIC internal error + * RNP_ERROR_SIGNATURE_INVALID signature is invalid + */ +rnp_result_t dsa_generate(rnp::RNG *rng, pgp_dsa_key_t *key, size_t keylen, size_t qbits); + +/* + * @brief Returns minimally sized hash which will work + * with the DSA subgroup. + * + * @param qsize subgroup order + * + * @returns Either ID of the hash algorithm, or PGP_HASH_UNKNOWN + * if not found + */ +pgp_hash_alg_t dsa_get_min_hash(size_t qsize); + +/* + * @brief Helps to determine subgroup size by size of p + * In order not to confuse users, we use less complicated + * approach than suggested by FIPS-186, which is: + * p=1024 => q=160 + * p<2048 => q=224 + * p<=3072 => q=256 + * So we don't generate (2048, 224) pair + * + * @return Size of `q' or 0 in case `psize' is not in <1024,3072> range + */ +size_t dsa_choose_qsize_by_psize(size_t psize); + +#endif diff --git a/comm/third_party/rnp/src/lib/crypto/dsa_ossl.cpp b/comm/third_party/rnp/src/lib/crypto/dsa_ossl.cpp new file mode 100644 index 0000000000..1fb75b5fce --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/dsa_ossl.cpp @@ -0,0 +1,355 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdlib.h> +#include <string.h> +#include <rnp/rnp_def.h> +#include "bn.h" +#include "dsa.h" +#include "dl_ossl.h" +#include "utils.h" +#include <openssl/dsa.h> +#include <openssl/dh.h> +#include <openssl/err.h> +#include <openssl/evp.h> + +#define DSA_MAX_Q_BITLEN 256 + +static bool +dsa_decode_sig(const uint8_t *data, size_t len, pgp_dsa_signature_t &sig) +{ + DSA_SIG *dsig = d2i_DSA_SIG(NULL, &data, len); + if (!dsig) { + RNP_LOG("Failed to parse DSA sig: %lu", ERR_peek_last_error()); + return false; + } + const BIGNUM *r, *s; + DSA_SIG_get0(dsig, &r, &s); + bn2mpi(r, &sig.r); + bn2mpi(s, &sig.s); + DSA_SIG_free(dsig); + return true; +} + +static bool +dsa_encode_sig(uint8_t *data, size_t *len, const pgp_dsa_signature_t &sig) +{ + bool res = false; + DSA_SIG *dsig = DSA_SIG_new(); + BIGNUM * r = mpi2bn(&sig.r); + BIGNUM * s = mpi2bn(&sig.s); + if (!dsig || !r || !s) { + RNP_LOG("Allocation failed."); + goto done; + } + DSA_SIG_set0(dsig, r, s); + r = NULL; + s = NULL; + int outlen; + outlen = i2d_DSA_SIG(dsig, &data); + if (outlen < 0) { + RNP_LOG("Failed to encode signature."); + goto done; + } + *len = outlen; + res = true; +done: + DSA_SIG_free(dsig); + BN_free(r); + BN_free(s); + return res; +} + +static EVP_PKEY * +dsa_load_key(const pgp_dsa_key_t *key, bool secret = false) +{ + DSA * dsa = NULL; + EVP_PKEY *evpkey = NULL; + bignum_t *p = mpi2bn(&key->p); + bignum_t *q = mpi2bn(&key->q); + bignum_t *g = mpi2bn(&key->g); + bignum_t *y = mpi2bn(&key->y); + bignum_t *x = secret ? mpi2bn(&key->x) : NULL; + + if (!p || !q || !g || !y || (secret && !x)) { + RNP_LOG("out of memory"); + goto done; + } + + dsa = DSA_new(); + if (!dsa) { + RNP_LOG("Out of memory"); + goto done; + } + if (DSA_set0_pqg(dsa, p, q, g) != 1) { + RNP_LOG("Failed to set pqg. Error: %lu", ERR_peek_last_error()); + goto done; + } + p = NULL; + q = NULL; + g = NULL; + if (DSA_set0_key(dsa, y, x) != 1) { + RNP_LOG("Secret key load error: %lu", ERR_peek_last_error()); + goto done; + } + y = NULL; + x = NULL; + + evpkey = EVP_PKEY_new(); + if (!evpkey) { + RNP_LOG("allocation failed"); + goto done; + } + if (EVP_PKEY_set1_DSA(evpkey, dsa) <= 0) { + RNP_LOG("Failed to set key: %lu", ERR_peek_last_error()); + EVP_PKEY_free(evpkey); + evpkey = NULL; + } +done: + DSA_free(dsa); + bn_free(p); + bn_free(q); + bn_free(g); + bn_free(y); + bn_free(x); + return evpkey; +} + +rnp_result_t +dsa_validate_key(rnp::RNG *rng, const pgp_dsa_key_t *key, bool secret) +{ + /* OpenSSL doesn't implement key checks for the DSA, however we may use DL via DH */ + EVP_PKEY *pkey = dl_load_key(key->p, &key->q, key->g, key->y, NULL); + if (!pkey) { + RNP_LOG("Failed to load key"); + return RNP_ERROR_BAD_PARAMETERS; + } + rnp_result_t ret = dl_validate_key(pkey, secret ? &key->x : NULL); + EVP_PKEY_free(pkey); + return ret; +} + +rnp_result_t +dsa_sign(rnp::RNG * rng, + pgp_dsa_signature_t *sig, + const uint8_t * hash, + size_t hash_len, + const pgp_dsa_key_t *key) +{ + if (mpi_bytes(&key->x) == 0) { + RNP_LOG("private key not set"); + return RNP_ERROR_BAD_PARAMETERS; + } + + /* Load secret key to DSA structure*/ + EVP_PKEY *evpkey = dsa_load_key(key, true); + if (!evpkey) { + RNP_LOG("Failed to load key"); + return RNP_ERROR_BAD_PARAMETERS; + } + + rnp_result_t ret = RNP_ERROR_GENERIC; + /* init context and sign */ + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(evpkey, NULL); + if (!ctx) { + RNP_LOG("Context allocation failed: %lu", ERR_peek_last_error()); + goto done; + } + if (EVP_PKEY_sign_init(ctx) <= 0) { + RNP_LOG("Failed to initialize signing: %lu", ERR_peek_last_error()); + goto done; + } + sig->s.len = PGP_MPINT_SIZE; + if (EVP_PKEY_sign(ctx, sig->s.mpi, &sig->s.len, hash, hash_len) <= 0) { + RNP_LOG("Signing failed: %lu", ERR_peek_last_error()); + sig->s.len = 0; + goto done; + } + if (!dsa_decode_sig(&sig->s.mpi[0], sig->s.len, *sig)) { + RNP_LOG("Failed to parse DSA sig: %lu", ERR_peek_last_error()); + goto done; + } + ret = RNP_SUCCESS; +done: + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(evpkey); + return ret; +} + +rnp_result_t +dsa_verify(const pgp_dsa_signature_t *sig, + const uint8_t * hash, + size_t hash_len, + const pgp_dsa_key_t * key) +{ + /* Load secret key to DSA structure*/ + EVP_PKEY *evpkey = dsa_load_key(key, false); + if (!evpkey) { + RNP_LOG("Failed to load key"); + return RNP_ERROR_BAD_PARAMETERS; + } + + rnp_result_t ret = RNP_ERROR_GENERIC; + /* init context and sign */ + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(evpkey, NULL); + if (!ctx) { + RNP_LOG("Context allocation failed: %lu", ERR_peek_last_error()); + goto done; + } + if (EVP_PKEY_verify_init(ctx) <= 0) { + RNP_LOG("Failed to initialize verify: %lu", ERR_peek_last_error()); + goto done; + } + pgp_mpi_t sigbuf; + if (!dsa_encode_sig(sigbuf.mpi, &sigbuf.len, *sig)) { + goto done; + } + if (EVP_PKEY_verify(ctx, sigbuf.mpi, sigbuf.len, hash, hash_len) <= 0) { + ret = RNP_ERROR_SIGNATURE_INVALID; + } else { + ret = RNP_SUCCESS; + } +done: + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(evpkey); + return ret; +} + +rnp_result_t +dsa_generate(rnp::RNG *rng, pgp_dsa_key_t *key, size_t keylen, size_t qbits) +{ + if ((keylen < 1024) || (keylen > 3072) || (qbits < 160) || (qbits > 256)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + rnp_result_t ret = RNP_ERROR_GENERIC; + const DSA * dsa = NULL; + EVP_PKEY * pkey = NULL; + EVP_PKEY * parmkey = NULL; + EVP_PKEY_CTX *ctx = NULL; + + /* Generate DSA params */ + ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_DSA, NULL); + if (!ctx) { + RNP_LOG("Failed to create ctx: %lu", ERR_peek_last_error()); + return ret; + } + if (EVP_PKEY_paramgen_init(ctx) <= 0) { + RNP_LOG("Failed to init keygen: %lu", ERR_peek_last_error()); + goto done; + } + if (EVP_PKEY_CTX_set_dsa_paramgen_bits(ctx, keylen) <= 0) { + RNP_LOG("Failed to set key bits: %lu", ERR_peek_last_error()); + goto done; + } +#if OPENSSL_VERSION_NUMBER < 0x1010105fL + EVP_PKEY_CTX_ctrl( + ctx, EVP_PKEY_DSA, EVP_PKEY_OP_PARAMGEN, EVP_PKEY_CTRL_DSA_PARAMGEN_Q_BITS, qbits, NULL); +#else + if (EVP_PKEY_CTX_set_dsa_paramgen_q_bits(ctx, qbits) <= 0) { + RNP_LOG("Failed to set key qbits: %lu", ERR_peek_last_error()); + goto done; + } +#endif + if (EVP_PKEY_paramgen(ctx, &parmkey) <= 0) { + RNP_LOG("Failed to generate parameters: %lu", ERR_peek_last_error()); + goto done; + } + EVP_PKEY_CTX_free(ctx); + /* Generate DSA key */ + ctx = EVP_PKEY_CTX_new(parmkey, NULL); + if (!ctx) { + RNP_LOG("Failed to create ctx: %lu", ERR_peek_last_error()); + goto done; + } + if (EVP_PKEY_keygen_init(ctx) <= 0) { + RNP_LOG("Failed to init keygen: %lu", ERR_peek_last_error()); + goto done; + } + if (EVP_PKEY_keygen(ctx, &pkey) <= 0) { + RNP_LOG("DSA keygen failed: %lu", ERR_peek_last_error()); + goto done; + } + dsa = EVP_PKEY_get0_DSA(pkey); + if (!dsa) { + RNP_LOG("Failed to retrieve DSA key: %lu", ERR_peek_last_error()); + goto done; + } + + const bignum_t *p; + const bignum_t *q; + const bignum_t *g; + const bignum_t *y; + const bignum_t *x; + p = DSA_get0_p(dsa); + q = DSA_get0_q(dsa); + g = DSA_get0_g(dsa); + y = DSA_get0_pub_key(dsa); + x = DSA_get0_priv_key(dsa); + if (!p || !q || !g || !y || !x) { + ret = RNP_ERROR_BAD_STATE; + goto done; + } + bn2mpi(p, &key->p); + bn2mpi(q, &key->q); + bn2mpi(g, &key->g); + bn2mpi(y, &key->y); + bn2mpi(x, &key->x); + ret = RNP_SUCCESS; +done: + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(parmkey); + EVP_PKEY_free(pkey); + return ret; +} + +pgp_hash_alg_t +dsa_get_min_hash(size_t qsize) +{ + /* + * I'm using _broken_ SHA1 here only because + * some old implementations may not understand keys created + * with other hashes. If you're sure we don't have to support + * such implementations, please be my guest and remove it. + */ + return (qsize < 160) ? PGP_HASH_UNKNOWN : + (qsize == 160) ? PGP_HASH_SHA1 : + (qsize <= 224) ? PGP_HASH_SHA224 : + (qsize <= 256) ? PGP_HASH_SHA256 : + (qsize <= 384) ? PGP_HASH_SHA384 : + (qsize <= 512) ? PGP_HASH_SHA512 + /*(qsize>512)*/ : + PGP_HASH_UNKNOWN; +} + +size_t +dsa_choose_qsize_by_psize(size_t psize) +{ + return (psize == 1024) ? 160 : + (psize <= 2047) ? 224 : + (psize <= 3072) ? DSA_MAX_Q_BITLEN : + 0; +} diff --git a/comm/third_party/rnp/src/lib/crypto/ec.cpp b/comm/third_party/rnp/src/lib/crypto/ec.cpp new file mode 100644 index 0000000000..144c362ea5 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/ec.cpp @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2017, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <botan/ffi.h> +#include <string.h> +#include <cassert> +#include "ec.h" +#include "types.h" +#include "utils.h" +#include "mem.h" +#include "bn.h" + +static id_str_pair ec_algo_to_botan[] = { + {PGP_PKA_ECDH, "ECDH"}, + {PGP_PKA_ECDSA, "ECDSA"}, + {PGP_PKA_SM2, "SM2_Sig"}, + {0, NULL}, +}; + +rnp_result_t +x25519_generate(rnp::RNG *rng, pgp_ec_key_t *key) +{ + botan_privkey_t pr_key = NULL; + botan_pubkey_t pu_key = NULL; + rnp_result_t ret = RNP_ERROR_KEY_GENERATION; + + rnp::secure_array<uint8_t, 32> keyle; + + if (botan_privkey_create(&pr_key, "Curve25519", "", rng->handle())) { + goto end; + } + + if (botan_privkey_export_pubkey(&pu_key, pr_key)) { + goto end; + } + + /* botan returns key in little-endian, while mpi is big-endian */ + if (botan_privkey_x25519_get_privkey(pr_key, keyle.data())) { + goto end; + } + for (int i = 0; i < 32; i++) { + key->x.mpi[31 - i] = keyle[i]; + } + key->x.len = 32; + /* botan doesn't tweak secret key bits, so we should do that here */ + if (!x25519_tweak_bits(*key)) { + goto end; + } + + if (botan_pubkey_x25519_get_pubkey(pu_key, &key->p.mpi[1])) { + goto end; + } + key->p.len = 33; + key->p.mpi[0] = 0x40; + + ret = RNP_SUCCESS; +end: + botan_privkey_destroy(pr_key); + botan_pubkey_destroy(pu_key); + return ret; +} + +rnp_result_t +ec_generate(rnp::RNG * rng, + pgp_ec_key_t * key, + const pgp_pubkey_alg_t alg_id, + const pgp_curve_t curve) +{ + /** + * Keeps "0x04 || x || y" + * \see 13.2. ECDSA, ECDH, SM2 Conversion Primitives + * + * P-521 is biggest supported curve + */ + botan_privkey_t pr_key = NULL; + botan_pubkey_t pu_key = NULL; + bignum_t * px = NULL; + bignum_t * py = NULL; + bignum_t * x = NULL; + rnp_result_t ret = RNP_ERROR_KEY_GENERATION; + size_t filed_byte_size = 0; + + if (!alg_allows_curve(alg_id, curve)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + const char *ec_algo = id_str_pair::lookup(ec_algo_to_botan, alg_id, NULL); + assert(ec_algo); + const ec_curve_desc_t *ec_desc = get_curve_desc(curve); + if (!ec_desc) { + ret = RNP_ERROR_BAD_PARAMETERS; + goto end; + } + filed_byte_size = BITS_TO_BYTES(ec_desc->bitlen); + + // at this point it must succeed + if (botan_privkey_create(&pr_key, ec_algo, ec_desc->botan_name, rng->handle())) { + goto end; + } + + if (botan_privkey_export_pubkey(&pu_key, pr_key)) { + goto end; + } + + // Crash if seckey is null. It's clean and easy to debug design + px = bn_new(); + py = bn_new(); + x = bn_new(); + + if (!px || !py || !x) { + RNP_LOG("Allocation failed"); + ret = RNP_ERROR_OUT_OF_MEMORY; + goto end; + } + + if (botan_pubkey_get_field(BN_HANDLE_PTR(px), pu_key, "public_x")) { + goto end; + } + + if (botan_pubkey_get_field(BN_HANDLE_PTR(py), pu_key, "public_y")) { + goto end; + } + + if (botan_privkey_get_field(BN_HANDLE_PTR(x), pr_key, "x")) { + goto end; + } + + size_t x_bytes; + size_t y_bytes; + x_bytes = bn_num_bytes(*px); + y_bytes = bn_num_bytes(*py); + + // Safety check + if ((x_bytes > filed_byte_size) || (y_bytes > filed_byte_size)) { + RNP_LOG("Key generation failed"); + ret = RNP_ERROR_BAD_PARAMETERS; + goto end; + } + + /* + * Convert coordinates to MPI stored as + * "0x04 || x || y" + * + * \see 13.2. ECDSA and ECDH Conversion Primitives + * + * Note: Generated pk/sk may not always have exact number of bytes + * which is important when converting to octet-string + */ + memset(key->p.mpi, 0, sizeof(key->p.mpi)); + key->p.mpi[0] = 0x04; + bn_bn2bin(px, &key->p.mpi[1 + filed_byte_size - x_bytes]); + bn_bn2bin(py, &key->p.mpi[1 + filed_byte_size + (filed_byte_size - y_bytes)]); + key->p.len = 2 * filed_byte_size + 1; + /* secret key value */ + bn2mpi(x, &key->x); + ret = RNP_SUCCESS; +end: + botan_privkey_destroy(pr_key); + botan_pubkey_destroy(pu_key); + bn_free(px); + bn_free(py); + bn_free(x); + return ret; +} diff --git a/comm/third_party/rnp/src/lib/crypto/ec.h b/comm/third_party/rnp/src/lib/crypto/ec.h new file mode 100644 index 0000000000..07cb8e8101 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/ec.h @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2017-2020 [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * This code is originally derived from software contributed to + * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and + * carried further by Ribose Inc (https://www.ribose.com). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef EC_H_ +#define EC_H_ + +#include "config.h" +#include <rnp/rnp_def.h> +#include <repgp/repgp_def.h> +#include "crypto/rng.h" +#include "crypto/mpi.h" + +#define MAX_CURVE_BIT_SIZE 521 // secp521r1 +/* Maximal byte size of elliptic curve order (NIST P-521) */ +#define MAX_CURVE_BYTELEN ((MAX_CURVE_BIT_SIZE + 7) / 8) + +/** + * Maximal length of the OID in hex representation. + * + * \see RFC4880 bis01 - 9.2 ECC Curve OID + */ +#define MAX_CURVE_OID_HEX_LEN 10U + +/** + * Structure holds description of elliptic curve + */ +typedef struct ec_curve_desc_t { + const pgp_curve_t rnp_curve_id; + const size_t bitlen; + const uint8_t OIDhex[MAX_CURVE_OID_HEX_LEN]; + const size_t OIDhex_len; +#if defined(CRYPTO_BACKEND_BOTAN) + const char *botan_name; +#endif +#if defined(CRYPTO_BACKEND_OPENSSL) + const char *openssl_name; +#endif + const char *pgp_name; + /* Curve is supported for keygen/sign/encrypt operations */ + bool supported; + /* Curve parameters below. Needed for grip calculation */ + const char *p; + const char *a; + const char *b; + const char *n; + const char *gx; + const char *gy; + const char *h; +} ec_curve_desc_t; + +typedef struct pgp_ec_key_t { + pgp_curve_t curve; + pgp_mpi_t p; + /* secret mpi */ + pgp_mpi_t x; + /* ecdh params */ + pgp_hash_alg_t kdf_hash_alg; /* Hash used by kdf */ + pgp_symm_alg_t key_wrap_alg; /* Symmetric algorithm used to wrap KEK*/ +} pgp_ec_key_t; + +typedef struct pgp_ec_signature_t { + pgp_mpi_t r; + pgp_mpi_t s; +} pgp_ec_signature_t; + +/* + * @brief Finds curve ID by hex representation of OID + * + * @param oid buffer with OID in hex + * @param oid_len length of oid buffer + * + * @returns success curve ID + * failure PGP_CURVE_MAX is returned + * + * @remarks see RFC 4880 bis 01 - 9.2 ECC Curve OID + */ +pgp_curve_t find_curve_by_OID(const uint8_t *oid, size_t oid_len); + +pgp_curve_t find_curve_by_name(const char *name); + +/* + * @brief Returns pointer to the curve descriptor + * + * @param Valid curve ID + * + * @returns NULL if wrong ID provided, otherwise descriptor + * + */ +const ec_curve_desc_t *get_curve_desc(const pgp_curve_t curve_id); + +bool alg_allows_curve(pgp_pubkey_alg_t alg, pgp_curve_t curve); + +/** + * @brief Check whether curve is supported for operations. + * All available curves are supported for reading/parsing key data, however some of them + * may be disabled for use, i.e. for key generation/signing/encryption. + */ +bool curve_supported(pgp_curve_t curve); + +/* + * @brief Generates EC key in uncompressed format + * + * @param rng initialized rnp::RNG context* + * @param key key data to be generated + * @param alg_id ID of EC algorithm + * @param curve underlying ECC curve ID + * + * @pre alg_id MUST be supported algorithm + * + * @returns RNP_ERROR_BAD_PARAMETERS unknown curve_id + * @returns RNP_ERROR_OUT_OF_MEMORY memory allocation failed + * @returns RNP_ERROR_KEY_GENERATION implementation error + */ +rnp_result_t ec_generate(rnp::RNG * rng, + pgp_ec_key_t * key, + const pgp_pubkey_alg_t alg_id, + const pgp_curve_t curve); + +/* + * @brief Generates x25519 ECDH key in x25519-specific format + * + * @param rng initialized rnp::RNG context* + * @param key key data to be generated + * + * @returns RNP_ERROR_KEY_GENERATION implementation error + */ +rnp_result_t x25519_generate(rnp::RNG *rng, pgp_ec_key_t *key); + +/** + * @brief Set least significant/most significant bits of the 25519 secret key as per + * specification. + * + * @param key secret key. + * @return true on success or false otherwise. + */ +bool x25519_tweak_bits(pgp_ec_key_t &key); + +/** + * @brief Check whether least significant/most significant bits of 25519 secret key are + * correctly tweaked. + * + * @param key secret key. + * @return true if bits are set correctly, and false otherwise. + */ +bool x25519_bits_tweaked(const pgp_ec_key_t &key); + +#endif diff --git a/comm/third_party/rnp/src/lib/crypto/ec_curves.cpp b/comm/third_party/rnp/src/lib/crypto/ec_curves.cpp new file mode 100644 index 0000000000..db5cf090fe --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/ec_curves.cpp @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <string.h> +#include "ec.h" +#include "types.h" +#include "utils.h" +#include "str-utils.h" + +/** + * EC Curves definition used by implementation + * + * \see RFC4880 bis01 - 9.2. ECC Curve OID + * + * Order of the elements in this array corresponds to + * values in pgp_curve_t enum. + */ +static const ec_curve_desc_t ec_curves[] = { + {PGP_CURVE_UNKNOWN, 0, {0}, 0, NULL, NULL}, + + {PGP_CURVE_NIST_P_256, + 256, + {0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07}, + 8, +#if defined(CRYPTO_BACKEND_BOTAN) + "secp256r1", +#elif defined(CRYPTO_BACKEND_OPENSSL) + "prime256v1", +#endif + "NIST P-256", + true, + "0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff", + "0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc", + "0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", + "0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", + "0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", + "0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5", + "0x01"}, + {PGP_CURVE_NIST_P_384, + 384, + {0x2B, 0x81, 0x04, 0x00, 0x22}, + 5, +#if defined(CRYPTO_BACKEND_BOTAN) + "secp384r1", +#elif defined(CRYPTO_BACKEND_OPENSSL) + "secp384r1", +#endif + "NIST P-384", + true, + "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000" + "ffffffff", + "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000" + "fffffffc", + "0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8ed" + "d3ec2aef", + "0xffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196a" + "ccc52973", + "0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e38" + "72760ab7", + "0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c" + "90ea0e5f", + "0x01"}, + {PGP_CURVE_NIST_P_521, + 521, + {0x2B, 0x81, 0x04, 0x00, 0x23}, + 5, +#if defined(CRYPTO_BACKEND_BOTAN) + "secp521r1", +#elif defined(CRYPTO_BACKEND_OPENSSL) + "secp521r1", +#endif + "NIST P-521", + true, + "0x01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "ffffffffffffffffffffffffffffffffffffffffffff", + "0x01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "fffffffffffffffffffffffffffffffffffffffffffc", + "0x0051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652" + "c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00", + "0x01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc" + "0148f709a5d03bb5c9b8899c47aebb6fb71e91386409", + "0x00c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1d" + "c127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66", + "0x011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550" + "b9013fad0761353c7086a272c24088be94769fd16650", + "0x01"}, + {PGP_CURVE_ED25519, + 255, + {0x2b, 0x06, 0x01, 0x04, 0x01, 0xda, 0x47, 0x0f, 0x01}, + 9, +#if defined(CRYPTO_BACKEND_BOTAN) + "Ed25519", +#elif defined(CRYPTO_BACKEND_OPENSSL) + "ED25519", +#endif + "Ed25519", + true, + "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed", + /* two below are actually negative */ + "0x01", + "0x2dfc9311d490018c7338bf8688861767ff8ff5b2bebe27548a14b235eca6874a", + "0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed", + "0x216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51a", + "0x6666666666666666666666666666666666666666666666666666666666666658", + "0x08"}, + {PGP_CURVE_25519, + 255, + {0x2b, 0x06, 0x01, 0x04, 0x01, 0x97, 0x55, 0x01, 0x05, 0x01}, + 10, +#if defined(CRYPTO_BACKEND_BOTAN) + "curve25519", +#elif defined(CRYPTO_BACKEND_OPENSSL) + "X25519", +#endif + "Curve25519", + true, + "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed", + "0x01db41", + "0x01", + "0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed", + "0x0000000000000000000000000000000000000000000000000000000000000009", + "0x20ae19a1b8a086b4e01edd2c7748d14c923d4d7e6d7c61b229e9c5a27eced3d9", + "0x08"}, + {PGP_CURVE_BP256, + 256, + {0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x07}, + 9, +#if defined(CRYPTO_BACKEND_BOTAN) + "brainpool256r1", +#elif defined(CRYPTO_BACKEND_OPENSSL) + "brainpoolP256r1", +#endif + "brainpoolP256r1", +#if defined(ENABLE_BRAINPOOL) + true, +#else + false, +#endif + "0xa9fb57dba1eea9bc3e660a909d838d726e3bf623d52620282013481d1f6e5377", + "0x7d5a0975fc2c3057eef67530417affe7fb8055c126dc5c6ce94a4b44f330b5d9", + "0x26dc5c6ce94a4b44f330b5d9bbd77cbf958416295cf7e1ce6bccdc18ff8c07b6", + "0xa9fb57dba1eea9bc3e660a909d838d718c397aa3b561a6f7901e0e82974856a7", + "0x8bd2aeb9cb7e57cb2c4b482ffc81b7afb9de27e1e3bd23c23a4453bd9ace3262", + "0x547ef835c3dac4fd97f8461a14611dc9c27745132ded8e545c1d54c72f046997", + "0x01"}, + {PGP_CURVE_BP384, + 384, + {0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0B}, + 9, +#if defined(CRYPTO_BACKEND_BOTAN) + "brainpool384r1", +#elif defined(CRYPTO_BACKEND_OPENSSL) + "brainpoolP384r1", +#endif + "brainpoolP384r1", +#if defined(ENABLE_BRAINPOOL) + true, +#else + false, +#endif + "0x8cb91e82a3386d280f5d6f7e50e641df152f7109ed5456b412b1da197fb71123acd3a729901d1a7187470013" + "3107ec53", + "0x7bc382c63d8c150c3c72080ace05afa0c2bea28e4fb22787139165efba91f90f8aa5814a503ad4eb04a8c7dd" + "22ce2826", + "0x04a8c7dd22ce28268b39b55416f0447c2fb77de107dcd2a62e880ea53eeb62d57cb4390295dbc9943ab78696" + "fa504c11", + "0x8cb91e82a3386d280f5d6f7e50e641df152f7109ed5456b31f166e6cac0425a7cf3ab6af6b7fc3103b883202" + "e9046565", + "0x1d1c64f068cf45ffa2a63a81b7c13f6b8847a3e77ef14fe3db7fcafe0cbd10e8e826e03436d646aaef87b2e2" + "47d4af1e", + "0x8abe1d7520f9c2a45cb1eb8e95cfd55262b70b29feec5864e19c054ff99129280e4646217791811142820341" + "263c5315", + "0x01"}, + {PGP_CURVE_BP512, + 512, + {0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0D}, + 9, +#if defined(CRYPTO_BACKEND_BOTAN) + "brainpool512r1", +#elif defined(CRYPTO_BACKEND_OPENSSL) + "brainpoolP512r1", +#endif + "brainpoolP512r1", +#if defined(ENABLE_BRAINPOOL) + true, +#else + false, +#endif + "0xaadd9db8dbe9c48b3fd4e6ae33c9fc07cb308db3b3c9d20ed6639cca703308717d4d9b009bc66842aecda12a" + "e6a380e62881ff2f2d82c68528aa6056583a48f3", + "0x7830a3318b603b89e2327145ac234cc594cbdd8d3df91610a83441caea9863bc2ded5d5aa8253aa10a2ef1c9" + "8b9ac8b57f1117a72bf2c7b9e7c1ac4d77fc94ca", + "0x3df91610a83441caea9863bc2ded5d5aa8253aa10a2ef1c98b9ac8b57f1117a72bf2c7b9e7c1ac4d77fc94ca" + "dc083e67984050b75ebae5dd2809bd638016f723", + "0xaadd9db8dbe9c48b3fd4e6ae33c9fc07cb308db3b3c9d20ed6639cca70330870553e5c414ca9261941866119" + "7fac10471db1d381085ddaddb58796829ca90069", + "0x81aee4bdd82ed9645a21322e9c4c6a9385ed9f70b5d916c1b43b62eef4d0098eff3b1f78e2d0d48d50d1687b" + "93b97d5f7c6d5047406a5e688b352209bcb9f822", + "0x7dde385d566332ecc0eabfa9cf7822fdf209f70024a57b1aa000c55b881f8111b2dcde494a5f485e5bca4bd8" + "8a2763aed1ca2b2fa8f0540678cd1e0f3ad80892", + "0x01"}, + {PGP_CURVE_P256K1, + 256, + {0x2B, 0x81, 0x04, 0x00, 0x0A}, + 5, +#if defined(CRYPTO_BACKEND_BOTAN) + "secp256k1", +#elif defined(CRYPTO_BACKEND_OPENSSL) + "secp256k1", +#endif + "secp256k1", + true, + "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000007", + "0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", + "0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8", + "0x01"}, + { + PGP_CURVE_SM2_P_256, + 256, + {0x2A, 0x81, 0x1C, 0xCF, 0x55, 0x01, 0x82, 0x2D}, + 8, +#if defined(CRYPTO_BACKEND_BOTAN) + "sm2p256v1", +#elif defined(CRYPTO_BACKEND_OPENSSL) + "sm2", +#endif + "SM2 P-256", +#if defined(ENABLE_SM2) + true, +#else + false, +#endif + "0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF", + "0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC", + "0x28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93", + "0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", + "0x32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", + "0xBC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", + }, +}; + +pgp_curve_t +find_curve_by_OID(const uint8_t *oid, size_t oid_len) +{ + for (size_t i = 0; i < PGP_CURVE_MAX; i++) { + if ((oid_len == ec_curves[i].OIDhex_len) && + (!memcmp(oid, ec_curves[i].OIDhex, oid_len))) { + return static_cast<pgp_curve_t>(i); + } + } + + return PGP_CURVE_MAX; +} + +pgp_curve_t +find_curve_by_name(const char *name) +{ + for (size_t i = 1; i < PGP_CURVE_MAX; i++) { + if (rnp::str_case_eq(ec_curves[i].pgp_name, name)) { + return ec_curves[i].rnp_curve_id; + } + } + + return PGP_CURVE_MAX; +} + +const ec_curve_desc_t * +get_curve_desc(const pgp_curve_t curve_id) +{ + return (curve_id < PGP_CURVE_MAX && curve_id > 0) ? &ec_curves[curve_id] : NULL; +} + +bool +alg_allows_curve(pgp_pubkey_alg_t alg, pgp_curve_t curve) +{ + /* SM2 curve is only for SM2 algo */ + if ((alg == PGP_PKA_SM2) || (curve == PGP_CURVE_SM2_P_256)) { + return (alg == PGP_PKA_SM2) && (curve == PGP_CURVE_SM2_P_256); + } + /* EDDSA and PGP_CURVE_ED25519 */ + if ((alg == PGP_PKA_EDDSA) || (curve == PGP_CURVE_ED25519)) { + return (alg == PGP_PKA_EDDSA) && (curve == PGP_CURVE_ED25519); + } + /* Curve x25519 is only for ECDH */ + if (curve == PGP_CURVE_25519) { + return alg == PGP_PKA_ECDH; + } + /* Other curves are good for both ECDH and ECDSA */ + return true; +} + +bool +curve_supported(pgp_curve_t curve) +{ + const ec_curve_desc_t *info = get_curve_desc(curve); + return info && info->supported; +} diff --git a/comm/third_party/rnp/src/lib/crypto/ec_ossl.cpp b/comm/third_party/rnp/src/lib/crypto/ec_ossl.cpp new file mode 100644 index 0000000000..6974b4c210 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/ec_ossl.cpp @@ -0,0 +1,349 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <string> +#include <cassert> +#include "ec.h" +#include "ec_ossl.h" +#include "bn.h" +#include "types.h" +#include "mem.h" +#include "utils.h" +#include <openssl/evp.h> +#include <openssl/objects.h> +#include <openssl/err.h> +#include <openssl/ec.h> + +static bool +ec_is_raw_key(const pgp_curve_t curve) +{ + return (curve == PGP_CURVE_ED25519) || (curve == PGP_CURVE_25519); +} + +rnp_result_t +x25519_generate(rnp::RNG *rng, pgp_ec_key_t *key) +{ + return ec_generate(rng, key, PGP_PKA_ECDH, PGP_CURVE_25519); +} + +EVP_PKEY * +ec_generate_pkey(const pgp_pubkey_alg_t alg_id, const pgp_curve_t curve) +{ + if (!alg_allows_curve(alg_id, curve)) { + return NULL; + } + const ec_curve_desc_t *ec_desc = get_curve_desc(curve); + if (!ec_desc) { + return NULL; + } + int nid = OBJ_sn2nid(ec_desc->openssl_name); + if (nid == NID_undef) { + RNP_LOG("Unknown SN: %s", ec_desc->openssl_name); + return NULL; + } + bool raw = ec_is_raw_key(curve); + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(raw ? nid : EVP_PKEY_EC, NULL); + if (!ctx) { + RNP_LOG("Failed to create ctx: %lu", ERR_peek_last_error()); + return NULL; + } + EVP_PKEY *pkey = NULL; + if (EVP_PKEY_keygen_init(ctx) <= 0) { + RNP_LOG("Failed to init keygen: %lu", ERR_peek_last_error()); + goto done; + } + if (!raw && (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, nid) <= 0)) { + RNP_LOG("Failed to set curve nid: %lu", ERR_peek_last_error()); + goto done; + } + if (EVP_PKEY_keygen(ctx, &pkey) <= 0) { + RNP_LOG("EC keygen failed: %lu", ERR_peek_last_error()); + } +done: + EVP_PKEY_CTX_free(ctx); + return pkey; +} + +static bool +ec_write_raw_seckey(EVP_PKEY *pkey, pgp_ec_key_t *key) +{ + /* EdDSA and X25519 keys are saved in a different way */ + static_assert(sizeof(key->x.mpi) > 32, "mpi is too small."); + key->x.len = sizeof(key->x.mpi); + if (EVP_PKEY_get_raw_private_key(pkey, key->x.mpi, &key->x.len) <= 0) { + RNP_LOG("Failed get raw private key: %lu", ERR_peek_last_error()); + return false; + } + assert(key->x.len == 32); + if (EVP_PKEY_id(pkey) == EVP_PKEY_X25519) { + /* in OpenSSL private key is exported as little-endian, while MPI is big-endian */ + for (size_t i = 0; i < 16; i++) { + std::swap(key->x.mpi[i], key->x.mpi[31 - i]); + } + } + return true; +} + +rnp_result_t +ec_generate(rnp::RNG * rng, + pgp_ec_key_t * key, + const pgp_pubkey_alg_t alg_id, + const pgp_curve_t curve) +{ + EVP_PKEY *pkey = ec_generate_pkey(alg_id, curve); + if (!pkey) { + return RNP_ERROR_BAD_PARAMETERS; + } + rnp_result_t ret = RNP_ERROR_GENERIC; + if (ec_is_raw_key(curve)) { + if (ec_write_pubkey(pkey, key->p, curve) && ec_write_raw_seckey(pkey, key)) { + ret = RNP_SUCCESS; + } + EVP_PKEY_free(pkey); + return ret; + } + const EC_KEY *ec = EVP_PKEY_get0_EC_KEY(pkey); + if (!ec) { + RNP_LOG("Failed to retrieve EC key: %lu", ERR_peek_last_error()); + goto done; + } + if (!ec_write_pubkey(pkey, key->p, curve)) { + RNP_LOG("Failed to write pubkey."); + goto done; + } + const bignum_t *x; + x = EC_KEY_get0_private_key(ec); + if (!x) { + ret = RNP_ERROR_BAD_STATE; + goto done; + } + if (bn2mpi(x, &key->x)) { + ret = RNP_SUCCESS; + } +done: + EVP_PKEY_free(pkey); + return ret; +} + +static EVP_PKEY * +ec_load_raw_key(const pgp_mpi_t &keyp, const pgp_mpi_t *keyx, int nid) +{ + if (!keyx) { + /* as per RFC, EdDSA & 25519 keys must use 0x40 byte for encoding */ + if ((mpi_bytes(&keyp) != 33) || (keyp.mpi[0] != 0x40)) { + RNP_LOG("Invalid 25519 public key."); + return NULL; + } + + EVP_PKEY *evpkey = + EVP_PKEY_new_raw_public_key(nid, NULL, &keyp.mpi[1], mpi_bytes(&keyp) - 1); + if (!evpkey) { + RNP_LOG("Failed to load public key: %lu", ERR_peek_last_error()); + } + return evpkey; + } + + EVP_PKEY *evpkey = NULL; + if (nid == EVP_PKEY_X25519) { + if (keyx->len != 32) { + RNP_LOG("Invalid 25519 secret key"); + return NULL; + } + /* need to reverse byte order since in mpi we have big-endian */ + rnp::secure_array<uint8_t, 32> prkey; + for (int i = 0; i < 32; i++) { + prkey[i] = keyx->mpi[31 - i]; + } + evpkey = EVP_PKEY_new_raw_private_key(nid, NULL, prkey.data(), keyx->len); + } else { + if (keyx->len > 32) { + RNP_LOG("Invalid Ed25519 secret key"); + return NULL; + } + /* keyx->len may be smaller then 32 as high byte is random and could become 0 */ + rnp::secure_array<uint8_t, 32> prkey{}; + memcpy(prkey.data() + 32 - keyx->len, keyx->mpi, keyx->len); + evpkey = EVP_PKEY_new_raw_private_key(nid, NULL, prkey.data(), 32); + } + if (!evpkey) { + RNP_LOG("Failed to load private key: %lu", ERR_peek_last_error()); + } + return evpkey; +} + +EVP_PKEY * +ec_load_key(const pgp_mpi_t &keyp, const pgp_mpi_t *keyx, pgp_curve_t curve) +{ + const ec_curve_desc_t *curv_desc = get_curve_desc(curve); + if (!curv_desc) { + RNP_LOG("unknown curve"); + return NULL; + } + if (!curve_supported(curve)) { + RNP_LOG("Curve %s is not supported.", curv_desc->pgp_name); + return NULL; + } + int nid = OBJ_sn2nid(curv_desc->openssl_name); + if (nid == NID_undef) { + RNP_LOG("Unknown SN: %s", curv_desc->openssl_name); + return NULL; + } + /* EdDSA and X25519 keys are loaded in a different way */ + if (ec_is_raw_key(curve)) { + return ec_load_raw_key(keyp, keyx, nid); + } + EC_KEY *ec = EC_KEY_new_by_curve_name(nid); + if (!ec) { + RNP_LOG("Failed to create EC key with group %d (%s): %s", + nid, + curv_desc->openssl_name, + ERR_reason_error_string(ERR_peek_last_error())); + return NULL; + } + + bool res = false; + bignum_t *x = NULL; + EVP_PKEY *pkey = NULL; + EC_POINT *p = EC_POINT_new(EC_KEY_get0_group(ec)); + if (!p) { + RNP_LOG("Failed to allocate point: %lu", ERR_peek_last_error()); + goto done; + } + if (EC_POINT_oct2point(EC_KEY_get0_group(ec), p, keyp.mpi, keyp.len, NULL) <= 0) { + RNP_LOG("Failed to decode point: %lu", ERR_peek_last_error()); + goto done; + } + if (EC_KEY_set_public_key(ec, p) <= 0) { + RNP_LOG("Failed to set public key: %lu", ERR_peek_last_error()); + goto done; + } + + pkey = EVP_PKEY_new(); + if (!pkey) { + RNP_LOG("EVP_PKEY allocation failed: %lu", ERR_peek_last_error()); + goto done; + } + if (!keyx) { + res = true; + goto done; + } + + x = mpi2bn(keyx); + if (!x) { + RNP_LOG("allocation failed"); + goto done; + } + if (EC_KEY_set_private_key(ec, x) <= 0) { + RNP_LOG("Failed to set secret key: %lu", ERR_peek_last_error()); + goto done; + } + res = true; +done: + if (res) { + res = EVP_PKEY_set1_EC_KEY(pkey, ec) > 0; + } + EC_POINT_free(p); + BN_free(x); + EC_KEY_free(ec); + if (!res) { + EVP_PKEY_free(pkey); + pkey = NULL; + } + return pkey; +} + +rnp_result_t +ec_validate_key(const pgp_ec_key_t &key, bool secret) +{ + if (key.curve == PGP_CURVE_25519) { + /* No key check implementation for x25519 in the OpenSSL yet, so just basic size checks + */ + if ((mpi_bytes(&key.p) != 33) || (key.p.mpi[0] != 0x40)) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (secret && mpi_bytes(&key.x) != 32) { + return RNP_ERROR_BAD_PARAMETERS; + } + return RNP_SUCCESS; + } + EVP_PKEY *evpkey = ec_load_key(key.p, secret ? &key.x : NULL, key.curve); + if (!evpkey) { + return RNP_ERROR_BAD_PARAMETERS; + } + rnp_result_t ret = RNP_ERROR_GENERIC; + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(evpkey, NULL); + if (!ctx) { + RNP_LOG("Context allocation failed: %lu", ERR_peek_last_error()); + goto done; + } + int res; + res = secret ? EVP_PKEY_check(ctx) : EVP_PKEY_public_check(ctx); + if (res < 0) { + auto err = ERR_peek_last_error(); + RNP_LOG("EC key check failed: %lu (%s)", err, ERR_reason_error_string(err)); + } + if (res > 0) { + ret = RNP_SUCCESS; + } +done: + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(evpkey); + return ret; +} + +bool +ec_write_pubkey(EVP_PKEY *pkey, pgp_mpi_t &mpi, pgp_curve_t curve) +{ + if (ec_is_raw_key(curve)) { + /* EdDSA and X25519 keys are saved in a different way */ + mpi.len = sizeof(mpi.mpi) - 1; + if (EVP_PKEY_get_raw_public_key(pkey, &mpi.mpi[1], &mpi.len) <= 0) { + RNP_LOG("Failed get raw public key: %lu", ERR_peek_last_error()); + return false; + } + assert(mpi.len == 32); + mpi.mpi[0] = 0x40; + mpi.len++; + return true; + } + const EC_KEY *ec = EVP_PKEY_get0_EC_KEY(pkey); + if (!ec) { + RNP_LOG("Failed to retrieve EC key: %lu", ERR_peek_last_error()); + return false; + } + const EC_POINT *p = EC_KEY_get0_public_key(ec); + if (!p) { + RNP_LOG("Null point: %lu", ERR_peek_last_error()); + return false; + } + /* call below adds leading zeroes if needed */ + mpi.len = EC_POINT_point2oct( + EC_KEY_get0_group(ec), p, POINT_CONVERSION_UNCOMPRESSED, mpi.mpi, sizeof(mpi.mpi), NULL); + if (!mpi.len) { + RNP_LOG("Failed to encode public key: %lu", ERR_peek_last_error()); + } + return mpi.len; +} diff --git a/comm/third_party/rnp/src/lib/crypto/ec_ossl.h b/comm/third_party/rnp/src/lib/crypto/ec_ossl.h new file mode 100644 index 0000000000..f16afe8bd5 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/ec_ossl.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef EC_OSSL_H_ +#define EC_OSSL_H_ + +#include "types.h" +#include "ec.h" +#include <openssl/evp.h> + +EVP_PKEY *ec_load_key(const pgp_mpi_t &keyp, const pgp_mpi_t *keyx, pgp_curve_t curve); + +rnp_result_t ec_validate_key(const pgp_ec_key_t &key, bool secret); + +EVP_PKEY *ec_generate_pkey(const pgp_pubkey_alg_t alg_id, const pgp_curve_t curve); + +bool ec_write_pubkey(EVP_PKEY *key, pgp_mpi_t &mpi, pgp_curve_t curve); + +#endif diff --git a/comm/third_party/rnp/src/lib/crypto/ecdh.cpp b/comm/third_party/rnp/src/lib/crypto/ecdh.cpp new file mode 100644 index 0000000000..d4411c39f0 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/ecdh.cpp @@ -0,0 +1,377 @@ +/*- + * Copyright (c) 2017-2022 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <string.h> +#include <botan/ffi.h> +#include "hash_botan.hpp" +#include "ecdh.h" +#include "ecdh_utils.h" +#include "symmetric.h" +#include "types.h" +#include "utils.h" +#include "mem.h" +#include "bn.h" + +// Produces kek of size kek_len which corresponds to length of wrapping key +static bool +compute_kek(uint8_t * kek, + size_t kek_len, + const uint8_t * other_info, + size_t other_info_size, + const ec_curve_desc_t *curve_desc, + const pgp_mpi_t * ec_pubkey, + const botan_privkey_t ec_prvkey, + const pgp_hash_alg_t hash_alg) +{ + const uint8_t *p = ec_pubkey->mpi; + uint8_t p_len = ec_pubkey->len; + + if (curve_desc->rnp_curve_id == PGP_CURVE_25519) { + if ((p_len != 33) || (p[0] != 0x40)) { + return false; + } + p++; + p_len--; + } + + rnp::secure_array<uint8_t, MAX_CURVE_BYTELEN * 2 + 1> s; + + botan_pk_op_ka_t op_key_agreement = NULL; + bool ret = false; + char kdf_name[32] = {0}; + size_t s_len = s.size(); + + if (botan_pk_op_key_agreement_create(&op_key_agreement, ec_prvkey, "Raw", 0) || + botan_pk_op_key_agreement(op_key_agreement, s.data(), &s_len, p, p_len, NULL, 0)) { + goto end; + } + + snprintf( + kdf_name, sizeof(kdf_name), "SP800-56A(%s)", rnp::Hash_Botan::name_backend(hash_alg)); + ret = !botan_kdf( + kdf_name, kek, kek_len, s.data(), s_len, NULL, 0, other_info, other_info_size); +end: + return ret && !botan_pk_op_key_agreement_destroy(op_key_agreement); +} + +static bool +ecdh_load_public_key(botan_pubkey_t *pubkey, const pgp_ec_key_t *key) +{ + bool res = false; + + const ec_curve_desc_t *curve = get_curve_desc(key->curve); + if (!curve) { + RNP_LOG("unknown curve"); + return false; + } + + if (curve->rnp_curve_id == PGP_CURVE_25519) { + if ((key->p.len != 33) || (key->p.mpi[0] != 0x40)) { + return false; + } + rnp::secure_array<uint8_t, 32> pkey; + memcpy(pkey.data(), key->p.mpi + 1, 32); + return !botan_pubkey_load_x25519(pubkey, pkey.data()); + } + + if (!mpi_bytes(&key->p) || (key->p.mpi[0] != 0x04)) { + RNP_LOG("Failed to load public key"); + return false; + } + + botan_mp_t px = NULL; + botan_mp_t py = NULL; + const size_t curve_order = BITS_TO_BYTES(curve->bitlen); + + if (botan_mp_init(&px) || botan_mp_init(&py) || + botan_mp_from_bin(px, &key->p.mpi[1], curve_order) || + botan_mp_from_bin(py, &key->p.mpi[1 + curve_order], curve_order)) { + goto end; + } + + if (!(res = !botan_pubkey_load_ecdh(pubkey, px, py, curve->botan_name))) { + RNP_LOG("failed to load ecdh public key"); + } +end: + botan_mp_destroy(px); + botan_mp_destroy(py); + return res; +} + +static bool +ecdh_load_secret_key(botan_privkey_t *seckey, const pgp_ec_key_t *key) +{ + const ec_curve_desc_t *curve = get_curve_desc(key->curve); + + if (!curve) { + return false; + } + + if (curve->rnp_curve_id == PGP_CURVE_25519) { + if (key->x.len != 32) { + RNP_LOG("wrong x25519 key"); + return false; + } + /* need to reverse byte order since in mpi we have big-endian */ + rnp::secure_array<uint8_t, 32> prkey; + for (int i = 0; i < 32; i++) { + prkey[i] = key->x.mpi[31 - i]; + } + return !botan_privkey_load_x25519(seckey, prkey.data()); + } + + bignum_t *x = NULL; + if (!(x = mpi2bn(&key->x))) { + return false; + } + bool res = !botan_privkey_load_ecdh(seckey, BN_HANDLE_PTR(x), curve->botan_name); + bn_free(x); + return res; +} + +rnp_result_t +ecdh_validate_key(rnp::RNG *rng, const pgp_ec_key_t *key, bool secret) +{ + botan_pubkey_t bpkey = NULL; + botan_privkey_t bskey = NULL; + rnp_result_t ret = RNP_ERROR_BAD_PARAMETERS; + + const ec_curve_desc_t *curve_desc = get_curve_desc(key->curve); + if (!curve_desc) { + return RNP_ERROR_NOT_SUPPORTED; + } + + if (!ecdh_load_public_key(&bpkey, key) || + botan_pubkey_check_key(bpkey, rng->handle(), 0)) { + goto done; + } + if (!secret) { + ret = RNP_SUCCESS; + goto done; + } + + if (!ecdh_load_secret_key(&bskey, key) || + botan_privkey_check_key(bskey, rng->handle(), 0)) { + goto done; + } + ret = RNP_SUCCESS; +done: + botan_privkey_destroy(bskey); + botan_pubkey_destroy(bpkey); + return ret; +} + +rnp_result_t +ecdh_encrypt_pkcs5(rnp::RNG * rng, + pgp_ecdh_encrypted_t * out, + const uint8_t *const in, + size_t in_len, + const pgp_ec_key_t * key, + const pgp_fingerprint_t &fingerprint) +{ + botan_privkey_t eph_prv_key = NULL; + rnp_result_t ret = RNP_ERROR_GENERIC; + uint8_t other_info[MAX_SP800_56A_OTHER_INFO]; + uint8_t kek[32] = {0}; // Size of SHA-256 or smaller + // 'm' is padded to the 8-byte granularity + uint8_t m[MAX_SESSION_KEY_SIZE]; + const size_t m_padded_len = ((in_len / 8) + 1) * 8; + + if (!key || !out || !in || (in_len > sizeof(m))) { + return RNP_ERROR_BAD_PARAMETERS; + } +#if !defined(ENABLE_SM2) + if (key->curve == PGP_CURVE_SM2_P_256) { + RNP_LOG("SM2 curve support is disabled."); + return RNP_ERROR_NOT_IMPLEMENTED; + } +#endif + const ec_curve_desc_t *curve_desc = get_curve_desc(key->curve); + if (!curve_desc) { + RNP_LOG("unsupported curve"); + return RNP_ERROR_NOT_SUPPORTED; + } + + // +8 because of AES-wrap adds 8 bytes + if (ECDH_WRAPPED_KEY_SIZE < (m_padded_len + 8)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + // See 13.5 of RFC 4880 for definition of other_info_size + const size_t other_info_size = curve_desc->OIDhex_len + 46; + const size_t kek_len = pgp_key_size(key->key_wrap_alg); + size_t tmp_len = kdf_other_info_serialize( + other_info, curve_desc, fingerprint, key->kdf_hash_alg, key->key_wrap_alg); + + if (tmp_len != other_info_size) { + RNP_LOG("Serialization of other info failed"); + return RNP_ERROR_GENERIC; + } + + if (!strcmp(curve_desc->botan_name, "curve25519")) { + if (botan_privkey_create(&eph_prv_key, "Curve25519", "", rng->handle())) { + goto end; + } + } else { + if (botan_privkey_create( + &eph_prv_key, "ECDH", curve_desc->botan_name, rng->handle())) { + goto end; + } + } + + if (!compute_kek(kek, + kek_len, + other_info, + other_info_size, + curve_desc, + &key->p, + eph_prv_key, + key->kdf_hash_alg)) { + RNP_LOG("KEK computation failed"); + goto end; + } + + memcpy(m, in, in_len); + if (!pad_pkcs7(m, m_padded_len, in_len)) { + // Should never happen + goto end; + } + + out->mlen = sizeof(out->m); + if (botan_key_wrap3394(m, m_padded_len, kek, kek_len, out->m, &out->mlen)) { + goto end; + } + + /* we need to prepend 0x40 for the x25519 */ + if (key->curve == PGP_CURVE_25519) { + out->p.len = sizeof(out->p.mpi) - 1; + if (botan_pk_op_key_agreement_export_public( + eph_prv_key, out->p.mpi + 1, &out->p.len)) { + goto end; + } + out->p.mpi[0] = 0x40; + out->p.len++; + } else { + out->p.len = sizeof(out->p.mpi); + if (botan_pk_op_key_agreement_export_public(eph_prv_key, out->p.mpi, &out->p.len)) { + goto end; + } + } + + // All OK + ret = RNP_SUCCESS; +end: + botan_privkey_destroy(eph_prv_key); + return ret; +} + +rnp_result_t +ecdh_decrypt_pkcs5(uint8_t * out, + size_t * out_len, + const pgp_ecdh_encrypted_t *in, + const pgp_ec_key_t * key, + const pgp_fingerprint_t & fingerprint) +{ + if (!out_len || !in || !key || !mpi_bytes(&key->x)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + const ec_curve_desc_t *curve_desc = get_curve_desc(key->curve); + if (!curve_desc) { + RNP_LOG("unknown curve"); + return RNP_ERROR_NOT_SUPPORTED; + } + + const pgp_symm_alg_t wrap_alg = key->key_wrap_alg; + const pgp_hash_alg_t kdf_hash = key->kdf_hash_alg; + /* Ensure that AES is used for wrapping */ + if ((wrap_alg != PGP_SA_AES_128) && (wrap_alg != PGP_SA_AES_192) && + (wrap_alg != PGP_SA_AES_256)) { + RNP_LOG("non-aes wrap algorithm"); + return RNP_ERROR_NOT_SUPPORTED; + } + + // See 13.5 of RFC 4880 for definition of other_info_size + uint8_t other_info[MAX_SP800_56A_OTHER_INFO]; + const size_t other_info_size = curve_desc->OIDhex_len + 46; + const size_t tmp_len = + kdf_other_info_serialize(other_info, curve_desc, fingerprint, kdf_hash, wrap_alg); + + if (other_info_size != tmp_len) { + RNP_LOG("Serialization of other info failed"); + return RNP_ERROR_GENERIC; + } + + botan_privkey_t prv_key = NULL; + if (!ecdh_load_secret_key(&prv_key, key)) { + RNP_LOG("failed to load ecdh secret key"); + return RNP_ERROR_GENERIC; + } + + // Size of SHA-256 or smaller + rnp::secure_array<uint8_t, MAX_SYMM_KEY_SIZE> kek; + rnp::secure_array<uint8_t, MAX_SESSION_KEY_SIZE> deckey; + + size_t deckey_len = deckey.size(); + size_t offset = 0; + rnp_result_t ret = RNP_ERROR_GENERIC; + + /* Security: Always return same error code in case compute_kek, + * botan_key_unwrap3394 or unpad_pkcs7 fails + */ + size_t kek_len = pgp_key_size(wrap_alg); + if (!compute_kek(kek.data(), + kek_len, + other_info, + other_info_size, + curve_desc, + &in->p, + prv_key, + kdf_hash)) { + goto end; + } + + if (botan_key_unwrap3394( + in->m, in->mlen, kek.data(), kek_len, deckey.data(), &deckey_len)) { + goto end; + } + + if (!unpad_pkcs7(deckey.data(), deckey_len, &offset)) { + goto end; + } + + if (*out_len < offset) { + ret = RNP_ERROR_SHORT_BUFFER; + goto end; + } + + *out_len = offset; + memcpy(out, deckey.data(), *out_len); + ret = RNP_SUCCESS; +end: + botan_privkey_destroy(prv_key); + return ret; +} diff --git a/comm/third_party/rnp/src/lib/crypto/ecdh.h b/comm/third_party/rnp/src/lib/crypto/ecdh.h new file mode 100644 index 0000000000..017e1e69e5 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/ecdh.h @@ -0,0 +1,117 @@ +/*- + * Copyright (c) 2017 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ECDH_H_ +#define ECDH_H_ + +#include "crypto/ec.h" + +/* Max size of wrapped and obfuscated key size + * + * RNP pads a key with PKCS-5 always to 8 byte granularity, + * then 8 bytes is added by AES-wrap (RFC3394). + */ +#define ECDH_WRAPPED_KEY_SIZE 48 + +/* Forward declarations */ +typedef struct pgp_fingerprint_t pgp_fingerprint_t; + +typedef struct pgp_ecdh_encrypted_t { + pgp_mpi_t p; + uint8_t m[ECDH_WRAPPED_KEY_SIZE]; + size_t mlen; +} pgp_ecdh_encrypted_t; + +rnp_result_t ecdh_validate_key(rnp::RNG *rng, const pgp_ec_key_t *key, bool secret); + +/* + * @brief Sets hash algorithm and key wrapping algo + * based on curve_id + * + * @param key ec key to set parameters for + * @param curve underlying ECC curve ID + * + * @returns false if curve is not supported, otherwise true + */ +bool ecdh_set_params(pgp_ec_key_t *key, pgp_curve_t curve_id); + +/* + * Encrypts session key with a KEK agreed during ECDH as specified in + * RFC 4880 bis 01, 13.5 + * + * @param rng initialized rnp::RNG object + * @param session_key key to be encrypted + * @param session_key_len length of the key buffer + * @param wrapped_key [out] resulting key wrapped in by some AES + * as specified in RFC 3394 + * @param wrapped_key_len [out] length of the `wrapped_key' buffer + * Current implementation always produces 48 bytes as key + * is padded with PKCS-5/7 + * @param ephemeral_key [out] public ephemeral ECDH key used for key + * agreement (private part). Must be initialized + * @param pubkey public key to be used for encryption + * @param fingerprint fingerprint of the pubkey + * + * @return RNP_SUCCESS on success and output parameters are populated + * @return RNP_ERROR_NOT_SUPPORTED unknown curve + * @return RNP_ERROR_BAD_PARAMETERS unexpected input provided + * @return RNP_ERROR_SHORT_BUFFER `wrapped_key_len' to small to store result + * @return RNP_ERROR_GENERIC implementation error + */ +rnp_result_t ecdh_encrypt_pkcs5(rnp::RNG * rng, + pgp_ecdh_encrypted_t * out, + const uint8_t *const in, + size_t in_len, + const pgp_ec_key_t * key, + const pgp_fingerprint_t &fingerprint); + +/* + * Decrypts session key with a KEK agreed during ECDH as specified in + * RFC 4880 bis 01, 13.5 + * + * @param session_key [out] resulting session key + * @param session_key_len [out] length of the resulting session key + * @param wrapped_key session key wrapped with some AES as specified + * in RFC 3394 + * @param wrapped_key_len length of the `wrapped_key' buffer + * @param ephemeral_key public ephemeral ECDH key coming from + * encrypted packet. + * @param seckey secret key to be used for decryption + * @param fingerprint fingerprint of the key + * + * @return RNP_SUCCESS on success and output parameters are populated + * @return RNP_ERROR_NOT_SUPPORTED unknown curve + * @return RNP_ERROR_BAD_PARAMETERS unexpected input provided + * @return RNP_ERROR_SHORT_BUFFER `session_key_len' to small to store result + * @return RNP_ERROR_GENERIC decryption failed or implementation error + */ +rnp_result_t ecdh_decrypt_pkcs5(uint8_t * out, + size_t * out_len, + const pgp_ecdh_encrypted_t *in, + const pgp_ec_key_t * key, + const pgp_fingerprint_t & fingerprint); + +#endif // ECDH_H_ diff --git a/comm/third_party/rnp/src/lib/crypto/ecdh_ossl.cpp b/comm/third_party/rnp/src/lib/crypto/ecdh_ossl.cpp new file mode 100644 index 0000000000..60b7260456 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/ecdh_ossl.cpp @@ -0,0 +1,389 @@ +/* + * Copyright (c) 2021-2022, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <string> +#include <cassert> +#include "ecdh.h" +#include "ecdh_utils.h" +#include "ec_ossl.h" +#include "hash.hpp" +#include "symmetric.h" +#include "types.h" +#include "utils.h" +#include "logging.h" +#include "mem.h" +#include <openssl/evp.h> +#include <openssl/err.h> + +static const struct ecdh_wrap_alg_map_t { + pgp_symm_alg_t alg; + const char * name; +} ecdh_wrap_alg_map[] = {{PGP_SA_AES_128, "aes128-wrap"}, + {PGP_SA_AES_192, "aes192-wrap"}, + {PGP_SA_AES_256, "aes256-wrap"}}; + +rnp_result_t +ecdh_validate_key(rnp::RNG *rng, const pgp_ec_key_t *key, bool secret) +{ + return ec_validate_key(*key, secret); +} + +static rnp_result_t +ecdh_derive_kek(uint8_t * x, + size_t xlen, + const pgp_ec_key_t & key, + const pgp_fingerprint_t &fingerprint, + uint8_t * kek, + const size_t kek_len) +{ + const ec_curve_desc_t *curve_desc = get_curve_desc(key.curve); + if (!curve_desc) { + RNP_LOG("unsupported curve"); + return RNP_ERROR_NOT_SUPPORTED; + } + + // Serialize other info, see 13.5 of RFC 4880 bis + uint8_t other_info[MAX_SP800_56A_OTHER_INFO]; + const size_t hash_len = rnp::Hash::size(key.kdf_hash_alg); + if (!hash_len) { + // must not assert here as kdf/hash algs are not checked during key parsing + RNP_LOG("Unsupported key wrap hash algorithm."); + return RNP_ERROR_NOT_SUPPORTED; + } + size_t other_len = kdf_other_info_serialize( + other_info, curve_desc, fingerprint, key.kdf_hash_alg, key.key_wrap_alg); + // Self-check + assert(other_len == curve_desc->OIDhex_len + 46); + // Derive KEK, using the KDF from SP800-56A + rnp::secure_array<uint8_t, PGP_MAX_HASH_SIZE> dgst; + assert(hash_len <= PGP_MAX_HASH_SIZE); + size_t reps = (kek_len + hash_len - 1) / hash_len; + // As we use AES & SHA2 we should not get more then 2 iterations + if (reps > 2) { + RNP_LOG("Invalid key wrap/hash alg combination."); + return RNP_ERROR_NOT_SUPPORTED; + } + size_t have = 0; + try { + for (size_t i = 1; i <= reps; i++) { + auto hash = rnp::Hash::create(key.kdf_hash_alg); + hash->add(i); + hash->add(x, xlen); + hash->add(other_info, other_len); + hash->finish(dgst.data()); + size_t bytes = std::min(hash_len, kek_len - have); + memcpy(kek + have, dgst.data(), bytes); + have += bytes; + } + return RNP_SUCCESS; + } catch (const std::exception &e) { + RNP_LOG("Failed to derive kek: %s", e.what()); + return RNP_ERROR_GENERIC; + } +} + +static rnp_result_t +ecdh_rfc3394_wrap_ctx(EVP_CIPHER_CTX **ctx, + pgp_symm_alg_t wrap_alg, + const uint8_t * key, + bool decrypt) +{ + /* get OpenSSL EVP cipher for key wrap */ + const char *cipher_name = NULL; + ARRAY_LOOKUP_BY_ID(ecdh_wrap_alg_map, alg, name, wrap_alg, cipher_name); + if (!cipher_name) { + RNP_LOG("Unsupported key wrap algorithm: %d", (int) wrap_alg); + return RNP_ERROR_NOT_SUPPORTED; + } + const EVP_CIPHER *cipher = EVP_get_cipherbyname(cipher_name); + if (!cipher) { + RNP_LOG("Cipher %s is not supported by OpenSSL.", cipher_name); + return RNP_ERROR_NOT_SUPPORTED; + } + *ctx = EVP_CIPHER_CTX_new(); + if (!ctx) { + RNP_LOG("Context allocation failed : %lu", ERR_peek_last_error()); + return RNP_ERROR_OUT_OF_MEMORY; + } + EVP_CIPHER_CTX_set_flags(*ctx, EVP_CIPHER_CTX_FLAG_WRAP_ALLOW); + int res = decrypt ? EVP_DecryptInit_ex(*ctx, cipher, NULL, key, NULL) : + EVP_EncryptInit_ex(*ctx, cipher, NULL, key, NULL); + if (res <= 0) { + RNP_LOG("Failed to initialize cipher : %lu", ERR_peek_last_error()); + EVP_CIPHER_CTX_free(*ctx); + *ctx = NULL; + return RNP_ERROR_GENERIC; + } + return RNP_SUCCESS; +} + +static rnp_result_t +ecdh_rfc3394_wrap(uint8_t * out, + size_t * out_len, + const uint8_t *const in, + size_t in_len, + const uint8_t * key, + pgp_symm_alg_t wrap_alg) +{ + EVP_CIPHER_CTX *ctx = NULL; + rnp_result_t ret = ecdh_rfc3394_wrap_ctx(&ctx, wrap_alg, key, false); + if (ret) { + RNP_LOG("Wrap context initialization failed."); + return ret; + } + int intlen = *out_len; + /* encrypts in one pass, no final is needed */ + int res = EVP_EncryptUpdate(ctx, out, &intlen, in, in_len); + if (res <= 0) { + RNP_LOG("Failed to encrypt data : %lu", ERR_peek_last_error()); + } else { + *out_len = intlen; + } + EVP_CIPHER_CTX_free(ctx); + return res > 0 ? RNP_SUCCESS : RNP_ERROR_GENERIC; +} + +static rnp_result_t +ecdh_rfc3394_unwrap(uint8_t * out, + size_t * out_len, + const uint8_t *const in, + size_t in_len, + const uint8_t * key, + pgp_symm_alg_t wrap_alg) +{ + if ((in_len < 16) || (in_len % 8)) { + RNP_LOG("Invalid wrapped key size."); + return RNP_ERROR_GENERIC; + } + EVP_CIPHER_CTX *ctx = NULL; + rnp_result_t ret = ecdh_rfc3394_wrap_ctx(&ctx, wrap_alg, key, true); + if (ret) { + RNP_LOG("Unwrap context initialization failed."); + return ret; + } + int intlen = *out_len; + /* decrypts in one pass, no final is needed */ + int res = EVP_DecryptUpdate(ctx, out, &intlen, in, in_len); + if (res <= 0) { + RNP_LOG("Failed to decrypt data : %lu", ERR_peek_last_error()); + } else { + *out_len = intlen; + } + EVP_CIPHER_CTX_free(ctx); + return res > 0 ? RNP_SUCCESS : RNP_ERROR_GENERIC; +} + +static bool +ecdh_derive_secret(EVP_PKEY *sec, EVP_PKEY *peer, uint8_t *x, size_t *xlen) +{ + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(sec, NULL); + if (!ctx) { + RNP_LOG("Context allocation failed: %lu", ERR_peek_last_error()); + return false; + } + bool res = false; + if (EVP_PKEY_derive_init(ctx) <= 0) { + RNP_LOG("Key derivation init failed: %lu", ERR_peek_last_error()); + goto done; + } + if (EVP_PKEY_derive_set_peer(ctx, peer) <= 0) { + RNP_LOG("Peer setting failed: %lu", ERR_peek_last_error()); + goto done; + } + if (EVP_PKEY_derive(ctx, x, xlen) <= 0) { + RNP_LOG("Failed to obtain shared secret size: %lu", ERR_peek_last_error()); + goto done; + } + res = true; +done: + EVP_PKEY_CTX_free(ctx); + return res; +} + +static size_t +ecdh_kek_len(pgp_symm_alg_t wrap_alg) +{ + switch (wrap_alg) { + case PGP_SA_AES_128: + case PGP_SA_AES_192: + case PGP_SA_AES_256: + return pgp_key_size(wrap_alg); + default: + return 0; + } +} + +rnp_result_t +ecdh_encrypt_pkcs5(rnp::RNG * rng, + pgp_ecdh_encrypted_t * out, + const uint8_t *const in, + size_t in_len, + const pgp_ec_key_t * key, + const pgp_fingerprint_t &fingerprint) +{ + if (!key || !out || !in || (in_len > MAX_SESSION_KEY_SIZE)) { + return RNP_ERROR_BAD_PARAMETERS; + } +#if !defined(ENABLE_SM2) + if (key->curve == PGP_CURVE_SM2_P_256) { + RNP_LOG("SM2 curve support is disabled."); + return RNP_ERROR_NOT_IMPLEMENTED; + } +#endif + /* check whether we have valid wrap_alg before doing heavy operations */ + size_t keklen = ecdh_kek_len(key->key_wrap_alg); + if (!keklen) { + RNP_LOG("Unsupported key wrap algorithm: %d", (int) key->key_wrap_alg); + return RNP_ERROR_NOT_SUPPORTED; + } + /* load our public key */ + EVP_PKEY *pkey = ec_load_key(key->p, NULL, key->curve); + if (!pkey) { + RNP_LOG("Failed to load public key."); + return RNP_ERROR_BAD_PARAMETERS; + } + rnp::secure_array<uint8_t, MAX_CURVE_BYTELEN + 1> sec; + rnp::secure_array<uint8_t, MAX_AES_KEY_SIZE> kek; + rnp::secure_array<uint8_t, MAX_SESSION_KEY_SIZE> mpad; + + size_t seclen = sec.size(); + rnp_result_t ret = RNP_ERROR_GENERIC; + /* generate ephemeral key */ + EVP_PKEY *ephkey = ec_generate_pkey(PGP_PKA_ECDH, key->curve); + if (!ephkey) { + RNP_LOG("Failed to generate ephemeral key."); + ret = RNP_ERROR_KEY_GENERATION; + goto done; + } + /* do ECDH derivation */ + if (!ecdh_derive_secret(ephkey, pkey, sec.data(), &seclen)) { + RNP_LOG("ECDH derivation failed."); + goto done; + } + /* here we got x value in sec, deriving kek */ + ret = ecdh_derive_kek(sec.data(), seclen, *key, fingerprint, kek.data(), keklen); + if (ret) { + RNP_LOG("Failed to derive KEK."); + goto done; + } + /* add PKCS#7 padding */ + size_t m_padded_len; + m_padded_len = ((in_len / 8) + 1) * 8; + memcpy(mpad.data(), in, in_len); + if (!pad_pkcs7(mpad.data(), m_padded_len, in_len)) { + RNP_LOG("Failed to add PKCS #7 padding."); + goto done; + } + /* do RFC 3394 AES key wrap */ + static_assert(sizeof(out->m) == ECDH_WRAPPED_KEY_SIZE, "Wrong ECDH wrapped key size."); + out->mlen = ECDH_WRAPPED_KEY_SIZE; + ret = ecdh_rfc3394_wrap( + out->m, &out->mlen, mpad.data(), m_padded_len, kek.data(), key->key_wrap_alg); + if (ret) { + RNP_LOG("Failed to wrap key."); + goto done; + } + /* write ephemeral public key */ + if (!ec_write_pubkey(ephkey, out->p, key->curve)) { + RNP_LOG("Failed to write ec key."); + goto done; + } + ret = RNP_SUCCESS; +done: + EVP_PKEY_free(ephkey); + EVP_PKEY_free(pkey); + return ret; +} + +rnp_result_t +ecdh_decrypt_pkcs5(uint8_t * out, + size_t * out_len, + const pgp_ecdh_encrypted_t *in, + const pgp_ec_key_t * key, + const pgp_fingerprint_t & fingerprint) +{ + if (!out || !out_len || !in || !key || !mpi_bytes(&key->x)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + /* check whether we have valid wrap_alg before doing heavy operations */ + size_t keklen = ecdh_kek_len(key->key_wrap_alg); + if (!keklen) { + RNP_LOG("Unsupported key wrap algorithm: %d", (int) key->key_wrap_alg); + return RNP_ERROR_NOT_SUPPORTED; + } + /* load ephemeral public key */ + EVP_PKEY *ephkey = ec_load_key(in->p, NULL, key->curve); + if (!ephkey) { + RNP_LOG("Failed to load ephemeral public key."); + return RNP_ERROR_BAD_PARAMETERS; + } + /* load our secret key */ + rnp::secure_array<uint8_t, MAX_CURVE_BYTELEN + 1> sec; + rnp::secure_array<uint8_t, MAX_AES_KEY_SIZE> kek; + rnp::secure_array<uint8_t, MAX_SESSION_KEY_SIZE> mpad; + + size_t seclen = sec.size(); + size_t mpadlen = mpad.size(); + rnp_result_t ret = RNP_ERROR_GENERIC; + EVP_PKEY * pkey = ec_load_key(key->p, &key->x, key->curve); + if (!pkey) { + RNP_LOG("Failed to load secret key."); + ret = RNP_ERROR_BAD_PARAMETERS; + goto done; + } + /* do ECDH derivation */ + if (!ecdh_derive_secret(pkey, ephkey, sec.data(), &seclen)) { + RNP_LOG("ECDH derivation failed."); + goto done; + } + /* here we got x value in sec, deriving kek */ + ret = ecdh_derive_kek(sec.data(), seclen, *key, fingerprint, kek.data(), keklen); + if (ret) { + RNP_LOG("Failed to derive KEK."); + goto done; + } + /* do RFC 3394 AES key unwrap */ + ret = ecdh_rfc3394_unwrap( + mpad.data(), &mpadlen, in->m, in->mlen, kek.data(), key->key_wrap_alg); + if (ret) { + RNP_LOG("Failed to unwrap key."); + goto done; + } + /* remove PKCS#7 padding */ + if (!unpad_pkcs7(mpad.data(), mpadlen, &mpadlen)) { + RNP_LOG("Failed to unpad key."); + goto done; + } + assert(mpadlen <= *out_len); + *out_len = mpadlen; + memcpy(out, mpad.data(), mpadlen); + ret = RNP_SUCCESS; +done: + EVP_PKEY_free(ephkey); + EVP_PKEY_free(pkey); + return ret; +} diff --git a/comm/third_party/rnp/src/lib/crypto/ecdh_utils.cpp b/comm/third_party/rnp/src/lib/crypto/ecdh_utils.cpp new file mode 100644 index 0000000000..3ceb153f2e --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/ecdh_utils.cpp @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ecdh_utils.h" +#include "types.h" +#include "utils.h" +#include <cassert> + +/* Used by ECDH keys. Specifies which hash and wrapping algorithm + * to be used (see point 15. of RFC 4880). + * + * Note: sync with ec_curves. + */ +static const struct ecdh_params_t { + pgp_curve_t curve; /* Curve ID */ + pgp_hash_alg_t hash; /* Hash used by kdf */ + pgp_symm_alg_t wrap_alg; /* Symmetric algorithm used to wrap KEK*/ +} ecdh_params[] = { + {PGP_CURVE_NIST_P_256, PGP_HASH_SHA256, PGP_SA_AES_128}, + {PGP_CURVE_NIST_P_384, PGP_HASH_SHA384, PGP_SA_AES_192}, + {PGP_CURVE_NIST_P_521, PGP_HASH_SHA512, PGP_SA_AES_256}, + {PGP_CURVE_BP256, PGP_HASH_SHA256, PGP_SA_AES_128}, + {PGP_CURVE_BP384, PGP_HASH_SHA384, PGP_SA_AES_192}, + {PGP_CURVE_BP512, PGP_HASH_SHA512, PGP_SA_AES_256}, + {PGP_CURVE_25519, PGP_HASH_SHA256, PGP_SA_AES_128}, + {PGP_CURVE_P256K1, PGP_HASH_SHA256, PGP_SA_AES_128}, +}; + +// "Anonymous Sender " in hex +static const unsigned char ANONYMOUS_SENDER[] = {0x41, 0x6E, 0x6F, 0x6E, 0x79, 0x6D, 0x6F, + 0x75, 0x73, 0x20, 0x53, 0x65, 0x6E, 0x64, + 0x65, 0x72, 0x20, 0x20, 0x20, 0x20}; + +// returns size of data written to other_info +size_t +kdf_other_info_serialize(uint8_t other_info[MAX_SP800_56A_OTHER_INFO], + const ec_curve_desc_t * ec_curve, + const pgp_fingerprint_t &fingerprint, + const pgp_hash_alg_t kdf_hash, + const pgp_symm_alg_t wrap_alg) +{ + assert(fingerprint.length >= 20); + uint8_t *buf_ptr = &other_info[0]; + + /* KDF-OtherInfo: AlgorithmID + * Current implementation will always use SHA-512 and AES-256 for KEK wrapping + */ + *(buf_ptr++) = ec_curve->OIDhex_len; + memcpy(buf_ptr, ec_curve->OIDhex, ec_curve->OIDhex_len); + buf_ptr += ec_curve->OIDhex_len; + *(buf_ptr++) = PGP_PKA_ECDH; + // size of following 3 params (each 1 byte) + *(buf_ptr++) = 0x03; + // Value reserved for future use + *(buf_ptr++) = 0x01; + // Hash used with KDF + *(buf_ptr++) = kdf_hash; + // Algorithm ID used for key wrapping + *(buf_ptr++) = wrap_alg; + + /* KDF-OtherInfo: PartyUInfo + * 20 bytes representing "Anonymous Sender " + */ + memcpy(buf_ptr, ANONYMOUS_SENDER, sizeof(ANONYMOUS_SENDER)); + buf_ptr += sizeof(ANONYMOUS_SENDER); + + // keep 20, as per spec + memcpy(buf_ptr, fingerprint.fingerprint, 20); + return (buf_ptr - other_info) + 20 /*anonymous_sender*/; +} + +bool +pad_pkcs7(uint8_t *buf, size_t buf_len, size_t offset) +{ + if (buf_len <= offset) { + // Must have at least 1 byte of padding + return false; + } + + const uint8_t pad_byte = buf_len - offset; + memset(buf + offset, pad_byte, pad_byte); + return true; +} + +bool +unpad_pkcs7(uint8_t *buf, size_t buf_len, size_t *offset) +{ + if (!buf || !offset || !buf_len) { + return false; + } + + uint8_t err = 0; + const uint8_t pad_byte = buf[buf_len - 1]; + const uint32_t pad_begin = buf_len - pad_byte; + + // TODO: Still >, <, and <=,== are not constant time (maybe?) + err |= (pad_byte > buf_len); + err |= (pad_byte == 0); + + /* Check if padding is OK */ + for (size_t c = 0; c < buf_len; c++) { + err |= (buf[c] ^ pad_byte) * (pad_begin <= c); + } + + *offset = pad_begin; + return (err == 0); +} + +bool +ecdh_set_params(pgp_ec_key_t *key, pgp_curve_t curve_id) +{ + for (size_t i = 0; i < ARRAY_SIZE(ecdh_params); i++) { + if (ecdh_params[i].curve == curve_id) { + key->kdf_hash_alg = ecdh_params[i].hash; + key->key_wrap_alg = ecdh_params[i].wrap_alg; + return true; + } + } + + return false; +} + +bool +x25519_tweak_bits(pgp_ec_key_t &key) +{ + if (key.x.len != 32) { + return false; + } + /* MPI is big-endian, while raw x25519 key is little-endian */ + key.x.mpi[31] &= 248; // zero 3 low bits + key.x.mpi[0] &= 127; // zero high bit + key.x.mpi[0] |= 64; // set high - 1 bit + return true; +} + +bool +x25519_bits_tweaked(const pgp_ec_key_t &key) +{ + if (key.x.len != 32) { + return false; + } + return !(key.x.mpi[31] & 7) && (key.x.mpi[0] < 128) && (key.x.mpi[0] >= 64); +} diff --git a/comm/third_party/rnp/src/lib/crypto/ecdh_utils.h b/comm/third_party/rnp/src/lib/crypto/ecdh_utils.h new file mode 100644 index 0000000000..2d37a713b7 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/ecdh_utils.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ECDH_UTILS_H_ +#define ECDH_UTILS_H_ + +#include "ecdh.h" + +#define MAX_SP800_56A_OTHER_INFO 56 +// Keys up to 312 bits (+1 bytes of PKCS5 padding) +#define MAX_SESSION_KEY_SIZE 40 +#define MAX_AES_KEY_SIZE 32 + +size_t kdf_other_info_serialize(uint8_t other_info[MAX_SP800_56A_OTHER_INFO], + const ec_curve_desc_t * ec_curve, + const pgp_fingerprint_t &fingerprint, + const pgp_hash_alg_t kdf_hash, + const pgp_symm_alg_t wrap_alg); + +bool pad_pkcs7(uint8_t *buf, size_t buf_len, size_t offset); + +bool unpad_pkcs7(uint8_t *buf, size_t buf_len, size_t *offset); + +#endif // ECDH_UTILS_H_ diff --git a/comm/third_party/rnp/src/lib/crypto/ecdsa.cpp b/comm/third_party/rnp/src/lib/crypto/ecdsa.cpp new file mode 100644 index 0000000000..cffce12ba7 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/ecdsa.cpp @@ -0,0 +1,267 @@ +/* + * Copyright (c) 2017, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ecdsa.h" +#include "utils.h" +#include <botan/ffi.h> +#include <string.h> +#include "bn.h" + +static bool +ecdsa_load_public_key(botan_pubkey_t *pubkey, const pgp_ec_key_t *keydata) +{ + botan_mp_t px = NULL; + botan_mp_t py = NULL; + bool res = false; + + const ec_curve_desc_t *curve = get_curve_desc(keydata->curve); + if (!curve) { + RNP_LOG("unknown curve"); + return false; + } + const size_t curve_order = BITS_TO_BYTES(curve->bitlen); + + if (!mpi_bytes(&keydata->p) || (keydata->p.mpi[0] != 0x04)) { + RNP_LOG( + "Failed to load public key: %zu, %02x", mpi_bytes(&keydata->p), keydata->p.mpi[0]); + return false; + } + + if (botan_mp_init(&px) || botan_mp_init(&py) || + botan_mp_from_bin(px, &keydata->p.mpi[1], curve_order) || + botan_mp_from_bin(py, &keydata->p.mpi[1 + curve_order], curve_order)) { + goto end; + } + + if (!(res = !botan_pubkey_load_ecdsa(pubkey, px, py, curve->botan_name))) { + RNP_LOG("failed to load ecdsa public key"); + } +end: + botan_mp_destroy(px); + botan_mp_destroy(py); + return res; +} + +static bool +ecdsa_load_secret_key(botan_privkey_t *seckey, const pgp_ec_key_t *keydata) +{ + const ec_curve_desc_t *curve; + bignum_t * x = NULL; + bool res = false; + + if (!(curve = get_curve_desc(keydata->curve))) { + return false; + } + if (!(x = mpi2bn(&keydata->x))) { + return false; + } + if (!(res = !botan_privkey_load_ecdsa(seckey, BN_HANDLE_PTR(x), curve->botan_name))) { + RNP_LOG("Can't load private key"); + } + bn_free(x); + return res; +} + +rnp_result_t +ecdsa_validate_key(rnp::RNG *rng, const pgp_ec_key_t *key, bool secret) +{ + botan_pubkey_t bpkey = NULL; + botan_privkey_t bskey = NULL; + rnp_result_t ret = RNP_ERROR_BAD_PARAMETERS; + + if (!ecdsa_load_public_key(&bpkey, key) || + botan_pubkey_check_key(bpkey, rng->handle(), 0)) { + goto done; + } + if (!secret) { + ret = RNP_SUCCESS; + goto done; + } + + if (!ecdsa_load_secret_key(&bskey, key) || + botan_privkey_check_key(bskey, rng->handle(), 0)) { + goto done; + } + ret = RNP_SUCCESS; +done: + botan_privkey_destroy(bskey); + botan_pubkey_destroy(bpkey); + return ret; +} + +static const char * +ecdsa_padding_str_for(pgp_hash_alg_t hash_alg) +{ + switch (hash_alg) { + case PGP_HASH_MD5: + return "Raw(MD5)"; + case PGP_HASH_SHA1: + return "Raw(SHA-1)"; + case PGP_HASH_RIPEMD: + return "Raw(RIPEMD-160)"; + + case PGP_HASH_SHA256: + return "Raw(SHA-256)"; + case PGP_HASH_SHA384: + return "Raw(SHA-384)"; + case PGP_HASH_SHA512: + return "Raw(SHA-512)"; + case PGP_HASH_SHA224: + return "Raw(SHA-224)"; + case PGP_HASH_SHA3_256: + return "Raw(SHA3(256))"; + case PGP_HASH_SHA3_512: + return "Raw(SHA3(512))"; + + case PGP_HASH_SM3: + return "Raw(SM3)"; + default: + return "Raw"; + } +} + +rnp_result_t +ecdsa_sign(rnp::RNG * rng, + pgp_ec_signature_t *sig, + pgp_hash_alg_t hash_alg, + const uint8_t * hash, + size_t hash_len, + const pgp_ec_key_t *key) +{ + botan_pk_op_sign_t signer = NULL; + botan_privkey_t b_key = NULL; + rnp_result_t ret = RNP_ERROR_GENERIC; + uint8_t out_buf[2 * MAX_CURVE_BYTELEN] = {0}; + const ec_curve_desc_t *curve = get_curve_desc(key->curve); + const char * padding_str = ecdsa_padding_str_for(hash_alg); + + if (!curve) { + return RNP_ERROR_BAD_PARAMETERS; + } + const size_t curve_order = BITS_TO_BYTES(curve->bitlen); + size_t sig_len = 2 * curve_order; + + if (!ecdsa_load_secret_key(&b_key, key)) { + RNP_LOG("Can't load private key"); + goto end; + } + + if (botan_pk_op_sign_create(&signer, b_key, padding_str, 0)) { + goto end; + } + + if (botan_pk_op_sign_update(signer, hash, hash_len)) { + goto end; + } + + if (botan_pk_op_sign_finish(signer, rng->handle(), out_buf, &sig_len)) { + RNP_LOG("Signing failed"); + goto end; + } + + // Allocate memory and copy results + if (mem2mpi(&sig->r, out_buf, curve_order) && + mem2mpi(&sig->s, out_buf + curve_order, curve_order)) { + ret = RNP_SUCCESS; + } +end: + botan_privkey_destroy(b_key); + botan_pk_op_sign_destroy(signer); + return ret; +} + +rnp_result_t +ecdsa_verify(const pgp_ec_signature_t *sig, + pgp_hash_alg_t hash_alg, + const uint8_t * hash, + size_t hash_len, + const pgp_ec_key_t * key) +{ + botan_pubkey_t pub = NULL; + botan_pk_op_verify_t verifier = NULL; + rnp_result_t ret = RNP_ERROR_SIGNATURE_INVALID; + uint8_t sign_buf[2 * MAX_CURVE_BYTELEN] = {0}; + size_t r_blen, s_blen; + const char * padding_str = ecdsa_padding_str_for(hash_alg); + + const ec_curve_desc_t *curve = get_curve_desc(key->curve); + if (!curve) { + RNP_LOG("unknown curve"); + return RNP_ERROR_BAD_PARAMETERS; + } + const size_t curve_order = BITS_TO_BYTES(curve->bitlen); + + if (!ecdsa_load_public_key(&pub, key)) { + goto end; + } + + if (botan_pk_op_verify_create(&verifier, pub, padding_str, 0)) { + goto end; + } + + if (botan_pk_op_verify_update(verifier, hash, hash_len)) { + goto end; + } + + r_blen = mpi_bytes(&sig->r); + s_blen = mpi_bytes(&sig->s); + if ((r_blen > curve_order) || (s_blen > curve_order) || + (curve_order > MAX_CURVE_BYTELEN)) { + ret = RNP_ERROR_BAD_PARAMETERS; + goto end; + } + + // Both can't fail + mpi2mem(&sig->r, &sign_buf[curve_order - r_blen]); + mpi2mem(&sig->s, &sign_buf[curve_order + curve_order - s_blen]); + + if (!botan_pk_op_verify_finish(verifier, sign_buf, curve_order * 2)) { + ret = RNP_SUCCESS; + } +end: + botan_pubkey_destroy(pub); + botan_pk_op_verify_destroy(verifier); + return ret; +} + +pgp_hash_alg_t +ecdsa_get_min_hash(pgp_curve_t curve) +{ + switch (curve) { + case PGP_CURVE_NIST_P_256: + case PGP_CURVE_BP256: + case PGP_CURVE_P256K1: + return PGP_HASH_SHA256; + case PGP_CURVE_NIST_P_384: + case PGP_CURVE_BP384: + return PGP_HASH_SHA384; + case PGP_CURVE_NIST_P_521: + case PGP_CURVE_BP512: + return PGP_HASH_SHA512; + default: + return PGP_HASH_UNKNOWN; + } +} diff --git a/comm/third_party/rnp/src/lib/crypto/ecdsa.h b/comm/third_party/rnp/src/lib/crypto/ecdsa.h new file mode 100644 index 0000000000..aebfe6a68f --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/ecdsa.h @@ -0,0 +1,57 @@ +/*- + * Copyright (c) 2017 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ECDSA_H_ +#define ECDSA_H_ + +#include "crypto/ec.h" + +rnp_result_t ecdsa_validate_key(rnp::RNG *rng, const pgp_ec_key_t *key, bool secret); + +rnp_result_t ecdsa_sign(rnp::RNG * rng, + pgp_ec_signature_t *sig, + pgp_hash_alg_t hash_alg, + const uint8_t * hash, + size_t hash_len, + const pgp_ec_key_t *key); + +rnp_result_t ecdsa_verify(const pgp_ec_signature_t *sig, + pgp_hash_alg_t hash_alg, + const uint8_t * hash, + size_t hash_len, + const pgp_ec_key_t * key); + +/* + * @brief Returns hash which should be used with the curve + * + * @param curve Curve ID + * + * @returns Either ID of the hash algorithm, or PGP_HASH_UNKNOWN + * if not found + */ +pgp_hash_alg_t ecdsa_get_min_hash(pgp_curve_t curve); + +#endif // ECDSA_H_ diff --git a/comm/third_party/rnp/src/lib/crypto/ecdsa_ossl.cpp b/comm/third_party/rnp/src/lib/crypto/ecdsa_ossl.cpp new file mode 100644 index 0000000000..534811ad32 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/ecdsa_ossl.cpp @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ecdsa.h" +#include "utils.h" +#include <string.h> +#include "bn.h" +#include "ec_ossl.h" +#include <openssl/evp.h> +#include <openssl/err.h> +#include <openssl/ec.h> + +static bool +ecdsa_decode_sig(const uint8_t *data, size_t len, pgp_ec_signature_t &sig) +{ + ECDSA_SIG *esig = d2i_ECDSA_SIG(NULL, &data, len); + if (!esig) { + RNP_LOG("Failed to parse ECDSA sig: %lu", ERR_peek_last_error()); + return false; + } + const BIGNUM *r, *s; + ECDSA_SIG_get0(esig, &r, &s); + bn2mpi(r, &sig.r); + bn2mpi(s, &sig.s); + ECDSA_SIG_free(esig); + return true; +} + +static bool +ecdsa_encode_sig(uint8_t *data, size_t *len, const pgp_ec_signature_t &sig) +{ + bool res = false; + ECDSA_SIG *dsig = ECDSA_SIG_new(); + BIGNUM * r = mpi2bn(&sig.r); + BIGNUM * s = mpi2bn(&sig.s); + if (!dsig || !r || !s) { + RNP_LOG("Allocation failed."); + goto done; + } + ECDSA_SIG_set0(dsig, r, s); + r = NULL; + s = NULL; + int outlen; + outlen = i2d_ECDSA_SIG(dsig, &data); + if (outlen < 0) { + RNP_LOG("Failed to encode signature."); + goto done; + } + *len = outlen; + res = true; +done: + ECDSA_SIG_free(dsig); + BN_free(r); + BN_free(s); + return res; +} + +rnp_result_t +ecdsa_validate_key(rnp::RNG *rng, const pgp_ec_key_t *key, bool secret) +{ + return ec_validate_key(*key, secret); +} + +rnp_result_t +ecdsa_sign(rnp::RNG * rng, + pgp_ec_signature_t *sig, + pgp_hash_alg_t hash_alg, + const uint8_t * hash, + size_t hash_len, + const pgp_ec_key_t *key) +{ + if (mpi_bytes(&key->x) == 0) { + RNP_LOG("private key not set"); + return RNP_ERROR_BAD_PARAMETERS; + } + + /* Load secret key to DSA structure*/ + EVP_PKEY *evpkey = ec_load_key(key->p, &key->x, key->curve); + if (!evpkey) { + RNP_LOG("Failed to load key"); + return RNP_ERROR_BAD_PARAMETERS; + } + + rnp_result_t ret = RNP_ERROR_GENERIC; + /* init context and sign */ + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(evpkey, NULL); + if (!ctx) { + RNP_LOG("Context allocation failed: %lu", ERR_peek_last_error()); + goto done; + } + if (EVP_PKEY_sign_init(ctx) <= 0) { + RNP_LOG("Failed to initialize signing: %lu", ERR_peek_last_error()); + goto done; + } + sig->s.len = PGP_MPINT_SIZE; + if (EVP_PKEY_sign(ctx, sig->s.mpi, &sig->s.len, hash, hash_len) <= 0) { + RNP_LOG("Signing failed: %lu", ERR_peek_last_error()); + sig->s.len = 0; + goto done; + } + if (!ecdsa_decode_sig(&sig->s.mpi[0], sig->s.len, *sig)) { + RNP_LOG("Failed to parse ECDSA sig: %lu", ERR_peek_last_error()); + goto done; + } + ret = RNP_SUCCESS; +done: + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(evpkey); + return ret; +} + +rnp_result_t +ecdsa_verify(const pgp_ec_signature_t *sig, + pgp_hash_alg_t hash_alg, + const uint8_t * hash, + size_t hash_len, + const pgp_ec_key_t * key) +{ + /* Load secret key to DSA structure*/ + EVP_PKEY *evpkey = ec_load_key(key->p, NULL, key->curve); + if (!evpkey) { + RNP_LOG("Failed to load key"); + return RNP_ERROR_BAD_PARAMETERS; + } + + rnp_result_t ret = RNP_ERROR_SIGNATURE_INVALID; + /* init context and sign */ + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(evpkey, NULL); + if (!ctx) { + RNP_LOG("Context allocation failed: %lu", ERR_peek_last_error()); + goto done; + } + if (EVP_PKEY_verify_init(ctx) <= 0) { + RNP_LOG("Failed to initialize verify: %lu", ERR_peek_last_error()); + goto done; + } + pgp_mpi_t sigbuf; + if (!ecdsa_encode_sig(sigbuf.mpi, &sigbuf.len, *sig)) { + goto done; + } + if (EVP_PKEY_verify(ctx, sigbuf.mpi, sigbuf.len, hash, hash_len) > 0) { + ret = RNP_SUCCESS; + } +done: + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(evpkey); + return ret; +} + +pgp_hash_alg_t +ecdsa_get_min_hash(pgp_curve_t curve) +{ + switch (curve) { + case PGP_CURVE_NIST_P_256: + case PGP_CURVE_BP256: + case PGP_CURVE_P256K1: + return PGP_HASH_SHA256; + case PGP_CURVE_NIST_P_384: + case PGP_CURVE_BP384: + return PGP_HASH_SHA384; + case PGP_CURVE_NIST_P_521: + case PGP_CURVE_BP512: + return PGP_HASH_SHA512; + default: + return PGP_HASH_UNKNOWN; + } +} diff --git a/comm/third_party/rnp/src/lib/crypto/eddsa.cpp b/comm/third_party/rnp/src/lib/crypto/eddsa.cpp new file mode 100644 index 0000000000..8669180861 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/eddsa.cpp @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2017, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <string.h> +#include <botan/ffi.h> +#include "eddsa.h" +#include "utils.h" + +static bool +eddsa_load_public_key(botan_pubkey_t *pubkey, const pgp_ec_key_t *keydata) +{ + if (keydata->curve != PGP_CURVE_ED25519) { + return false; + } + /* + * See draft-ietf-openpgp-rfc4880bis-01 section 13.3 + */ + if ((mpi_bytes(&keydata->p) != 33) || (keydata->p.mpi[0] != 0x40)) { + return false; + } + if (botan_pubkey_load_ed25519(pubkey, keydata->p.mpi + 1)) { + return false; + } + + return true; +} + +static bool +eddsa_load_secret_key(botan_privkey_t *seckey, const pgp_ec_key_t *keydata) +{ + uint8_t keybuf[32] = {0}; + size_t sz; + + if (keydata->curve != PGP_CURVE_ED25519) { + return false; + } + sz = mpi_bytes(&keydata->x); + if (!sz || (sz > 32)) { + return false; + } + mpi2mem(&keydata->x, keybuf + 32 - sz); + if (botan_privkey_load_ed25519(seckey, keybuf)) { + return false; + } + + return true; +} + +rnp_result_t +eddsa_validate_key(rnp::RNG *rng, const pgp_ec_key_t *key, bool secret) +{ + botan_pubkey_t bpkey = NULL; + botan_privkey_t bskey = NULL; + rnp_result_t ret = RNP_ERROR_BAD_PARAMETERS; + + if (!eddsa_load_public_key(&bpkey, key) || + botan_pubkey_check_key(bpkey, rng->handle(), 0)) { + goto done; + } + + if (!secret) { + ret = RNP_SUCCESS; + goto done; + } + + if (!eddsa_load_secret_key(&bskey, key) || + botan_privkey_check_key(bskey, rng->handle(), 0)) { + goto done; + } + ret = RNP_SUCCESS; +done: + botan_privkey_destroy(bskey); + botan_pubkey_destroy(bpkey); + return ret; +} + +rnp_result_t +eddsa_generate(rnp::RNG *rng, pgp_ec_key_t *key) +{ + botan_privkey_t eddsa = NULL; + rnp_result_t ret = RNP_ERROR_GENERIC; + uint8_t key_bits[64]; + + if (botan_privkey_create(&eddsa, "Ed25519", NULL, rng->handle()) != 0) { + goto end; + } + + if (botan_privkey_ed25519_get_privkey(eddsa, key_bits)) { + goto end; + } + + // First 32 bytes of key_bits are the EdDSA seed (private key) + // Second 32 bytes are the EdDSA public key + + mem2mpi(&key->x, key_bits, 32); + // insert the required 0x40 prefix on the public key + key_bits[31] = 0x40; + mem2mpi(&key->p, key_bits + 31, 33); + key->curve = PGP_CURVE_ED25519; + + ret = RNP_SUCCESS; +end: + botan_privkey_destroy(eddsa); + return ret; +} + +rnp_result_t +eddsa_verify(const pgp_ec_signature_t *sig, + const uint8_t * hash, + size_t hash_len, + const pgp_ec_key_t * key) +{ + botan_pubkey_t eddsa = NULL; + botan_pk_op_verify_t verify_op = NULL; + rnp_result_t ret = RNP_ERROR_SIGNATURE_INVALID; + uint8_t bn_buf[64] = {0}; + + if (!eddsa_load_public_key(&eddsa, key)) { + ret = RNP_ERROR_BAD_PARAMETERS; + goto done; + } + + if (botan_pk_op_verify_create(&verify_op, eddsa, "Pure", 0) != 0) { + goto done; + } + + if (botan_pk_op_verify_update(verify_op, hash, hash_len) != 0) { + goto done; + } + + // Unexpected size for Ed25519 signature + if ((mpi_bytes(&sig->r) > 32) || (mpi_bytes(&sig->s) > 32)) { + goto done; + } + mpi2mem(&sig->r, &bn_buf[32 - mpi_bytes(&sig->r)]); + mpi2mem(&sig->s, &bn_buf[64 - mpi_bytes(&sig->s)]); + + if (botan_pk_op_verify_finish(verify_op, bn_buf, 64) == 0) { + ret = RNP_SUCCESS; + } +done: + botan_pk_op_verify_destroy(verify_op); + botan_pubkey_destroy(eddsa); + return ret; +} + +rnp_result_t +eddsa_sign(rnp::RNG * rng, + pgp_ec_signature_t *sig, + const uint8_t * hash, + size_t hash_len, + const pgp_ec_key_t *key) +{ + botan_privkey_t eddsa = NULL; + botan_pk_op_sign_t sign_op = NULL; + rnp_result_t ret = RNP_ERROR_SIGNING_FAILED; + uint8_t bn_buf[64] = {0}; + size_t sig_size = sizeof(bn_buf); + + if (!eddsa_load_secret_key(&eddsa, key)) { + ret = RNP_ERROR_BAD_PARAMETERS; + goto done; + } + + if (botan_pk_op_sign_create(&sign_op, eddsa, "Pure", 0) != 0) { + goto done; + } + + if (botan_pk_op_sign_update(sign_op, hash, hash_len) != 0) { + goto done; + } + + if (botan_pk_op_sign_finish(sign_op, rng->handle(), bn_buf, &sig_size) != 0) { + goto done; + } + + // Unexpected size... + if (sig_size != 64) { + goto done; + } + + mem2mpi(&sig->r, bn_buf, 32); + mem2mpi(&sig->s, bn_buf + 32, 32); + ret = RNP_SUCCESS; +done: + botan_pk_op_sign_destroy(sign_op); + botan_privkey_destroy(eddsa); + return ret; +} diff --git a/comm/third_party/rnp/src/lib/crypto/eddsa.h b/comm/third_party/rnp/src/lib/crypto/eddsa.h new file mode 100644 index 0000000000..7410a2806d --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/eddsa.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2017, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * This code is originally derived from software contributed to + * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and + * carried further by Ribose Inc (https://www.ribose.com). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RNP_ED25519_H_ +#define RNP_ED25519_H_ + +#include "ec.h" + +rnp_result_t eddsa_validate_key(rnp::RNG *rng, const pgp_ec_key_t *key, bool secret); +/* + * curve_len must be 255 currently (for Ed25519) + * If Ed448 was supported in the future curve_len=448 would also be allowed. + */ +rnp_result_t eddsa_generate(rnp::RNG *rng, pgp_ec_key_t *key); + +rnp_result_t eddsa_verify(const pgp_ec_signature_t *sig, + const uint8_t * hash, + size_t hash_len, + const pgp_ec_key_t * key); + +rnp_result_t eddsa_sign(rnp::RNG * rng, + pgp_ec_signature_t *sig, + const uint8_t * hash, + size_t hash_len, + const pgp_ec_key_t *key); + +#endif diff --git a/comm/third_party/rnp/src/lib/crypto/eddsa_ossl.cpp b/comm/third_party/rnp/src/lib/crypto/eddsa_ossl.cpp new file mode 100644 index 0000000000..16d8fad704 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/eddsa_ossl.cpp @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <string> +#include <cassert> +#include "eddsa.h" +#include "ec.h" +#include "ec_ossl.h" +#include "utils.h" +#include "bn.h" +#include <openssl/evp.h> +#include <openssl/objects.h> +#include <openssl/err.h> +#include <openssl/ec.h> + +rnp_result_t +eddsa_validate_key(rnp::RNG *rng, const pgp_ec_key_t *key, bool secret) +{ + /* Not implemented in the OpenSSL, so just do basic size checks. */ + if ((mpi_bytes(&key->p) != 33) || (key->p.mpi[0] != 0x40)) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (secret && mpi_bytes(&key->x) > 32) { + return RNP_ERROR_BAD_PARAMETERS; + } + return RNP_SUCCESS; +} + +rnp_result_t +eddsa_generate(rnp::RNG *rng, pgp_ec_key_t *key) +{ + rnp_result_t ret = ec_generate(rng, key, PGP_PKA_EDDSA, PGP_CURVE_ED25519); + if (!ret) { + key->curve = PGP_CURVE_ED25519; + } + return ret; +} + +rnp_result_t +eddsa_verify(const pgp_ec_signature_t *sig, + const uint8_t * hash, + size_t hash_len, + const pgp_ec_key_t * key) +{ + if ((mpi_bytes(&sig->r) > 32) || (mpi_bytes(&sig->s) > 32)) { + RNP_LOG("Invalid EdDSA signature."); + return RNP_ERROR_BAD_PARAMETERS; + } + if ((mpi_bytes(&key->p) != 33) || (key->p.mpi[0] != 0x40)) { + RNP_LOG("Invalid EdDSA public key."); + return RNP_ERROR_BAD_PARAMETERS; + } + + EVP_PKEY *evpkey = ec_load_key(key->p, NULL, PGP_CURVE_ED25519); + if (!evpkey) { + RNP_LOG("Failed to load key"); + return RNP_ERROR_BAD_PARAMETERS; + } + + rnp_result_t ret = RNP_ERROR_SIGNATURE_INVALID; + uint8_t sigbuf[64] = {0}; + /* init context and sign */ + EVP_PKEY_CTX *ctx = NULL; + EVP_MD_CTX * md = EVP_MD_CTX_new(); + if (!md) { + RNP_LOG("Failed to allocate MD ctx: %lu", ERR_peek_last_error()); + goto done; + } + if (EVP_DigestVerifyInit(md, &ctx, NULL, NULL, evpkey) <= 0) { + RNP_LOG("Failed to initialize signing: %lu", ERR_peek_last_error()); + goto done; + } + mpi2mem(&sig->r, &sigbuf[32 - mpi_bytes(&sig->r)]); + mpi2mem(&sig->s, &sigbuf[64 - mpi_bytes(&sig->s)]); + + if (EVP_DigestVerify(md, sigbuf, 64, hash, hash_len) > 0) { + ret = RNP_SUCCESS; + } +done: + /* line below will also free ctx */ + EVP_MD_CTX_free(md); + EVP_PKEY_free(evpkey); + return ret; +} + +rnp_result_t +eddsa_sign(rnp::RNG * rng, + pgp_ec_signature_t *sig, + const uint8_t * hash, + size_t hash_len, + const pgp_ec_key_t *key) +{ + if (!mpi_bytes(&key->x)) { + RNP_LOG("private key not set"); + return RNP_ERROR_BAD_PARAMETERS; + } + EVP_PKEY *evpkey = ec_load_key(key->p, &key->x, PGP_CURVE_ED25519); + if (!evpkey) { + RNP_LOG("Failed to load private key: %lu", ERR_peek_last_error()); + return RNP_ERROR_BAD_PARAMETERS; + } + + rnp_result_t ret = RNP_ERROR_GENERIC; + /* init context and sign */ + EVP_PKEY_CTX *ctx = NULL; + EVP_MD_CTX * md = EVP_MD_CTX_new(); + if (!md) { + RNP_LOG("Failed to allocate MD ctx: %lu", ERR_peek_last_error()); + goto done; + } + if (EVP_DigestSignInit(md, &ctx, NULL, NULL, evpkey) <= 0) { + RNP_LOG("Failed to initialize signing: %lu", ERR_peek_last_error()); + goto done; + } + static_assert((sizeof(sig->r.mpi) == PGP_MPINT_SIZE) && (PGP_MPINT_SIZE >= 64), + "invalid mpi type/size"); + sig->r.len = PGP_MPINT_SIZE; + if (EVP_DigestSign(md, sig->r.mpi, &sig->r.len, hash, hash_len) <= 0) { + RNP_LOG("Signing failed: %lu", ERR_peek_last_error()); + sig->r.len = 0; + goto done; + } + assert(sig->r.len == 64); + sig->r.len = 32; + sig->s.len = 32; + memcpy(sig->s.mpi, &sig->r.mpi[32], 32); + ret = RNP_SUCCESS; +done: + /* line below will also free ctx */ + EVP_MD_CTX_free(md); + EVP_PKEY_free(evpkey); + return ret; +} diff --git a/comm/third_party/rnp/src/lib/crypto/elgamal.cpp b/comm/third_party/rnp/src/lib/crypto/elgamal.cpp new file mode 100644 index 0000000000..acebf4d684 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/elgamal.cpp @@ -0,0 +1,302 @@ +/*- + * Copyright (c) 2017-2022 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdlib.h> +#include <string.h> +#include <botan/ffi.h> +#include <botan/bigint.h> +#include <botan/numthry.h> +#include <botan/reducer.h> +#include <rnp/rnp_def.h> +#include "elgamal.h" +#include "utils.h" +#include "bn.h" + +// Max supported key byte size +#define ELGAMAL_MAX_P_BYTELEN BITS_TO_BYTES(PGP_MPINT_BITS) + +static bool +elgamal_load_public_key(botan_pubkey_t *pubkey, const pgp_eg_key_t *keydata) +{ + bignum_t *p = NULL; + bignum_t *g = NULL; + bignum_t *y = NULL; + bool res = false; + + // Check if provided public key byte size is not greater than ELGAMAL_MAX_P_BYTELEN. + if (mpi_bytes(&keydata->p) > ELGAMAL_MAX_P_BYTELEN) { + goto done; + } + + if (!(p = mpi2bn(&keydata->p)) || !(g = mpi2bn(&keydata->g)) || + !(y = mpi2bn(&keydata->y))) { + goto done; + } + + res = + !botan_pubkey_load_elgamal(pubkey, BN_HANDLE_PTR(p), BN_HANDLE_PTR(g), BN_HANDLE_PTR(y)); +done: + bn_free(p); + bn_free(g); + bn_free(y); + return res; +} + +static bool +elgamal_load_secret_key(botan_privkey_t *seckey, const pgp_eg_key_t *keydata) +{ + bignum_t *p = NULL; + bignum_t *g = NULL; + bignum_t *x = NULL; + bool res = false; + + // Check if provided secret key byte size is not greater than ELGAMAL_MAX_P_BYTELEN. + if (mpi_bytes(&keydata->p) > ELGAMAL_MAX_P_BYTELEN) { + goto done; + } + + if (!(p = mpi2bn(&keydata->p)) || !(g = mpi2bn(&keydata->g)) || + !(x = mpi2bn(&keydata->x))) { + goto done; + } + + res = !botan_privkey_load_elgamal( + seckey, BN_HANDLE_PTR(p), BN_HANDLE_PTR(g), BN_HANDLE_PTR(x)); +done: + bn_free(p); + bn_free(g); + bn_free(x); + return res; +} + +bool +elgamal_validate_key(const pgp_eg_key_t *key, bool secret) +{ + // Check if provided public key byte size is not greater than ELGAMAL_MAX_P_BYTELEN. + if (mpi_bytes(&key->p) > ELGAMAL_MAX_P_BYTELEN) { + return false; + } + + /* Use custom validation since we added some custom validation, and Botan has slow test for + * prime for p */ + try { + Botan::BigInt p(key->p.mpi, key->p.len); + Botan::BigInt g(key->g.mpi, key->g.len); + + /* 1 < g < p */ + if ((g.cmp_word(1) != 1) || (g.cmp(p) != -1)) { + return false; + } + /* g ^ (p - 1) = 1 mod p */ + if (Botan::power_mod(g, p - 1, p).cmp_word(1)) { + return false; + } + /* check for small order subgroups */ + Botan::Modular_Reducer reducer(p); + Botan::BigInt v = g; + for (size_t i = 2; i < (1 << 17); i++) { + v = reducer.multiply(v, g); + if (!v.cmp_word(1)) { + RNP_LOG("Small subgroup detected. Order %zu", i); + return false; + } + } + if (!secret) { + return true; + } + /* check that g ^ x = y (mod p) */ + Botan::BigInt y(key->y.mpi, key->y.len); + Botan::BigInt x(key->x.mpi, key->x.len); + return Botan::power_mod(g, x, p) == y; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } +} + +rnp_result_t +elgamal_encrypt_pkcs1(rnp::RNG * rng, + pgp_eg_encrypted_t *out, + const uint8_t * in, + size_t in_len, + const pgp_eg_key_t *key) +{ + botan_pubkey_t b_key = NULL; + botan_pk_op_encrypt_t op_ctx = NULL; + rnp_result_t ret = RNP_ERROR_BAD_PARAMETERS; + /* Max size of an output len is twice an order of underlying group (p length) */ + uint8_t enc_buf[ELGAMAL_MAX_P_BYTELEN * 2] = {0}; + size_t p_len; + + if (!elgamal_load_public_key(&b_key, key)) { + RNP_LOG("Failed to load public key"); + goto end; + } + + /* Size of output buffer must be equal to twice the size of key byte len. + * as ElGamal encryption outputs concatenation of two components, both + * of size equal to size of public key byte len. + * Successful call to botan's ElGamal encryption will return output that's + * always 2*pubkey size. + */ + p_len = mpi_bytes(&key->p) * 2; + + if (botan_pk_op_encrypt_create(&op_ctx, b_key, "PKCS1v15", 0) || + botan_pk_op_encrypt(op_ctx, rng->handle(), enc_buf, &p_len, in, in_len)) { + RNP_LOG("Failed to create operation context"); + goto end; + } + + /* + * Botan's ElGamal formats the g^k and msg*(y^k) together into a single byte string. + * We have to parse out the two values after encryption, as rnp stores those values + * separatelly. + * + * We don't trim zeros from octet string as it is done before final marshalling + * (add_packet_body_mpi) + * + * We must assume that botan copies even number of bytes to output buffer (to avoid + * memory corruption) + */ + p_len /= 2; + if (mem2mpi(&out->g, enc_buf, p_len) && mem2mpi(&out->m, enc_buf + p_len, p_len)) { + ret = RNP_SUCCESS; + } +end: + botan_pk_op_encrypt_destroy(op_ctx); + botan_pubkey_destroy(b_key); + return ret; +} + +rnp_result_t +elgamal_decrypt_pkcs1(rnp::RNG * rng, + uint8_t * out, + size_t * out_len, + const pgp_eg_encrypted_t *in, + const pgp_eg_key_t * key) +{ + botan_privkey_t b_key = NULL; + botan_pk_op_decrypt_t op_ctx = NULL; + rnp_result_t ret = RNP_ERROR_BAD_PARAMETERS; + uint8_t enc_buf[ELGAMAL_MAX_P_BYTELEN * 2] = {0}; + size_t p_len; + size_t g_len; + size_t m_len; + + if (!mpi_bytes(&key->x)) { + RNP_LOG("empty secret key"); + goto end; + } + + // Check if provided public key byte size is not greater than ELGAMAL_MAX_P_BYTELEN. + p_len = mpi_bytes(&key->p); + g_len = mpi_bytes(&in->g); + m_len = mpi_bytes(&in->m); + + if ((2 * p_len > sizeof(enc_buf)) || (g_len > p_len) || (m_len > p_len)) { + RNP_LOG("Unsupported/wrong public key or encrypted data"); + goto end; + } + + if (!elgamal_load_secret_key(&b_key, key)) { + RNP_LOG("Failed to load private key"); + goto end; + } + + /* Botan expects ciphertext to be concatenated (g^k | encrypted m). Size must + * be equal to twice the byte size of public key, potentially prepended with zeros. + */ + memcpy(&enc_buf[p_len - g_len], in->g.mpi, g_len); + memcpy(&enc_buf[2 * p_len - m_len], in->m.mpi, m_len); + + *out_len = p_len; + if (botan_pk_op_decrypt_create(&op_ctx, b_key, "PKCS1v15", 0) || + botan_pk_op_decrypt(op_ctx, out, out_len, enc_buf, 2 * p_len)) { + RNP_LOG("Decryption failed"); + goto end; + } + ret = RNP_SUCCESS; +end: + botan_pk_op_decrypt_destroy(op_ctx); + botan_privkey_destroy(b_key); + return ret; +} + +rnp_result_t +elgamal_generate(rnp::RNG *rng, pgp_eg_key_t *key, size_t keybits) +{ + if ((keybits < 1024) || (keybits > PGP_MPINT_BITS)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + botan_privkey_t key_priv = NULL; + rnp_result_t ret = RNP_ERROR_GENERIC; + bignum_t * p = bn_new(); + bignum_t * g = bn_new(); + bignum_t * y = bn_new(); + bignum_t * x = bn_new(); + + if (!p || !g || !y || !x) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto end; + } + +start: + if (botan_privkey_create_elgamal(&key_priv, rng->handle(), keybits, keybits - 1)) { + RNP_LOG("Wrong parameters"); + ret = RNP_ERROR_BAD_PARAMETERS; + goto end; + } + + if (botan_privkey_get_field(BN_HANDLE_PTR(y), key_priv, "y")) { + RNP_LOG("Failed to obtain public key"); + goto end; + } + if (bn_num_bytes(*y) < BITS_TO_BYTES(keybits)) { + botan_privkey_destroy(key_priv); + goto start; + } + + if (botan_privkey_get_field(BN_HANDLE_PTR(p), key_priv, "p") || + botan_privkey_get_field(BN_HANDLE_PTR(g), key_priv, "g") || + botan_privkey_get_field(BN_HANDLE_PTR(y), key_priv, "y") || + botan_privkey_get_field(BN_HANDLE_PTR(x), key_priv, "x")) { + RNP_LOG("Botan FFI call failed"); + ret = RNP_ERROR_GENERIC; + goto end; + } + + if (bn2mpi(p, &key->p) && bn2mpi(g, &key->g) && bn2mpi(y, &key->y) && bn2mpi(x, &key->x)) { + ret = RNP_SUCCESS; + } +end: + bn_free(p); + bn_free(g); + bn_free(y); + bn_free(x); + botan_privkey_destroy(key_priv); + return ret; +} diff --git a/comm/third_party/rnp/src/lib/crypto/elgamal.h b/comm/third_party/rnp/src/lib/crypto/elgamal.h new file mode 100644 index 0000000000..42d05550fb --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/elgamal.h @@ -0,0 +1,116 @@ +/*- + * Copyright (c) 2017-2022 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RNP_ELG_H_ +#define RNP_ELG_H_ + +#include <stdint.h> +#include "crypto/rng.h" +#include "crypto/mpi.h" + +typedef struct pgp_eg_key_t { + pgp_mpi_t p; + pgp_mpi_t g; + pgp_mpi_t y; + /* secret mpi */ + pgp_mpi_t x; +} pgp_eg_key_t; + +typedef struct pgp_eg_signature_t { + /* This is kept only for packet reading. Implementation MUST + * not create elgamal signatures */ + pgp_mpi_t r; + pgp_mpi_t s; +} pgp_eg_signature_t; + +typedef struct pgp_eg_encrypted_t { + pgp_mpi_t g; + pgp_mpi_t m; +} pgp_eg_encrypted_t; + +bool elgamal_validate_key(const pgp_eg_key_t *key, bool secret); + +/* + * Performs ElGamal encryption + * Result of an encryption is composed of two parts - g2k and encm + * + * @param rng initialized rnp::RNG + * @param out encryption result + * @param in plaintext to be encrypted + * @param in_len length of the plaintext + * @param key public key to be used for encryption + * + * @pre out: must be valid pointer to corresponding structure + * @pre in_len: can't be bigger than byte size of `p' + * + * @return RNP_SUCCESS + * RNP_ERROR_OUT_OF_MEMORY allocation failure + * RNP_ERROR_BAD_PARAMETERS wrong input provided + */ +rnp_result_t elgamal_encrypt_pkcs1(rnp::RNG * rng, + pgp_eg_encrypted_t *out, + const uint8_t * in, + size_t in_len, + const pgp_eg_key_t *key); + +/* + * Performs ElGamal decryption + * + * @param rng initialized rnp::RNG + * @param out decrypted plaintext. Must be capable of storing at least as much bytes as p size + * @param out_len number of plaintext bytes written will be put here + * @param in encrypted data + * @param key private key + * + * @pre out, in: must be valid pointers + * @pre out: length must be long enough to store decrypted data. Max size of + * decrypted data is equal to bytes size of `p' + * + * @return RNP_SUCCESS + * RNP_ERROR_OUT_OF_MEMORY allocation failure + * RNP_ERROR_BAD_PARAMETERS wrong input provided + */ +rnp_result_t elgamal_decrypt_pkcs1(rnp::RNG * rng, + uint8_t * out, + size_t * out_len, + const pgp_eg_encrypted_t *in, + const pgp_eg_key_t * key); + +/* + * Generates ElGamal key + * + * @param rng pointer to PRNG + * @param key generated key + * @param keybits key bitlen + * + * @pre `keybits' > 1024 + * + * @returns RNP_ERROR_BAD_PARAMETERS wrong parameters provided + * RNP_ERROR_GENERIC internal error + * RNP_SUCCESS key generated and copied to `seckey' + */ +rnp_result_t elgamal_generate(rnp::RNG *rng, pgp_eg_key_t *key, size_t keybits); +#endif diff --git a/comm/third_party/rnp/src/lib/crypto/elgamal_ossl.cpp b/comm/third_party/rnp/src/lib/crypto/elgamal_ossl.cpp new file mode 100644 index 0000000000..f3fa381fd8 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/elgamal_ossl.cpp @@ -0,0 +1,418 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <cstdlib> +#include <string> +#include <cassert> +#include <rnp/rnp_def.h> +#include "elgamal.h" +#include "dl_ossl.h" +#include "utils.h" +#include "bn.h" +#include "mem.h" +#include <openssl/bn.h> +#include <openssl/dh.h> +#include <openssl/err.h> +#include <openssl/evp.h> +#include <openssl/rand.h> + +// Max supported key byte size +#define ELGAMAL_MAX_P_BYTELEN BITS_TO_BYTES(PGP_MPINT_BITS) + +bool +elgamal_validate_key(const pgp_eg_key_t *key, bool secret) +{ + BN_CTX *ctx = BN_CTX_new(); + if (!ctx) { + RNP_LOG("Allocation failed."); + return false; + } + BN_CTX_start(ctx); + bool res = false; + bignum_t * p = mpi2bn(&key->p); + bignum_t * g = mpi2bn(&key->g); + bignum_t * p1 = BN_CTX_get(ctx); + bignum_t * r = BN_CTX_get(ctx); + bignum_t * y = NULL; + bignum_t * x = NULL; + BN_RECP_CTX *rctx = NULL; + + if (!p || !g || !p1 || !r) { + goto done; + } + + /* 1 < g < p */ + if ((BN_cmp(g, BN_value_one()) != 1) || (BN_cmp(g, p) != -1)) { + RNP_LOG("Invalid g value."); + goto done; + } + /* g ^ (p - 1) = 1 mod p */ + if (!BN_copy(p1, p) || !BN_sub_word(p1, 1) || !BN_mod_exp(r, g, p1, p, ctx)) { + RNP_LOG("g exp failed."); + goto done; + } + if (BN_cmp(r, BN_value_one()) != 0) { + RNP_LOG("Wrong g exp value."); + goto done; + } + /* check for small order subgroups */ + rctx = BN_RECP_CTX_new(); + if (!rctx || !BN_RECP_CTX_set(rctx, p, ctx) || !BN_copy(r, g)) { + RNP_LOG("Failed to init RECP context."); + goto done; + } + for (size_t i = 2; i < (1 << 17); i++) { + if (!BN_mod_mul_reciprocal(r, r, g, rctx, ctx)) { + RNP_LOG("Multiplication failed."); + goto done; + } + if (BN_cmp(r, BN_value_one()) == 0) { + RNP_LOG("Small subgroup detected. Order %zu", i); + goto done; + } + } + if (!secret) { + res = true; + goto done; + } + /* check that g ^ x = y (mod p) */ + x = mpi2bn(&key->x); + y = mpi2bn(&key->y); + if (!x || !y) { + goto done; + } + res = BN_mod_exp(r, g, x, p, ctx) && !BN_cmp(r, y); +done: + BN_CTX_free(ctx); + BN_RECP_CTX_free(rctx); + bn_free(p); + bn_free(g); + bn_free(y); + bn_free(x); + return res; +} + +static bool +pkcs1v15_pad(uint8_t *out, size_t out_len, const uint8_t *in, size_t in_len) +{ + assert(out && in); + if (out_len < in_len + 11) { + return false; + } + out[0] = 0x00; + out[1] = 0x02; + size_t rnd = out_len - in_len - 3; + out[2 + rnd] = 0x00; + if (RAND_bytes(&out[2], rnd) != 1) { + return false; + } + for (size_t i = 2; i < 2 + rnd; i++) { + /* we need non-zero bytes */ + size_t cntr = 16; + while (!out[i] && (cntr--) && (RAND_bytes(&out[i], 1) == 1)) { + } + if (!out[i]) { + RNP_LOG("Something is wrong with RNG."); + return false; + } + } + memcpy(out + rnd + 3, in, in_len); + return true; +} + +static bool +pkcs1v15_unpad(size_t *padlen, const uint8_t *in, size_t in_len, bool skip0) +{ + if (in_len <= (size_t)(11 - skip0)) { + return false; + } + if (!skip0 && in[0]) { + return false; + } + if (in[1 - skip0] != 0x02) { + return false; + } + size_t pad = 2 - skip0; + while ((pad < in_len) && in[pad]) { + pad++; + } + if (pad >= in_len) { + return false; + } + *padlen = pad + 1; + return true; +} + +rnp_result_t +elgamal_encrypt_pkcs1(rnp::RNG * rng, + pgp_eg_encrypted_t *out, + const uint8_t * in, + size_t in_len, + const pgp_eg_key_t *key) +{ + pgp_mpi_t mm = {}; + mm.len = key->p.len; + if (!pkcs1v15_pad(mm.mpi, mm.len, in, in_len)) { + RNP_LOG("Failed to add PKCS1 v1.5 padding."); + return RNP_ERROR_BAD_PARAMETERS; + } + rnp_result_t ret = RNP_ERROR_GENERIC; + BN_CTX * ctx = BN_CTX_new(); + if (!ctx) { + RNP_LOG("Allocation failed."); + return RNP_ERROR_OUT_OF_MEMORY; + } + BN_CTX_start(ctx); + BN_MONT_CTX *mctx = BN_MONT_CTX_new(); + bignum_t * m = mpi2bn(&mm); + bignum_t * p = mpi2bn(&key->p); + bignum_t * g = mpi2bn(&key->g); + bignum_t * y = mpi2bn(&key->y); + bignum_t * c1 = BN_CTX_get(ctx); + bignum_t * c2 = BN_CTX_get(ctx); + bignum_t * k = BN_secure_new(); + if (!mctx || !m || !p || !g || !y || !c1 || !c2 || !k) { + RNP_LOG("Allocation failed."); + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + /* initialize Montgomery context */ + if (BN_MONT_CTX_set(mctx, p, ctx) < 1) { + RNP_LOG("Failed to setup Montgomery context."); + goto done; + } + int res; + /* must not fail */ + res = BN_rshift1(c1, p); + assert(res == 1); + if (res < 1) { + RNP_LOG("BN_rshift1 failed."); + goto done; + } + /* generate k */ + if (BN_rand_range(k, c1) < 1) { + RNP_LOG("Failed to generate k."); + goto done; + } + /* calculate c1 = g ^ k (mod p) */ + if (BN_mod_exp_mont_consttime(c1, g, k, p, ctx, mctx) < 1) { + RNP_LOG("Exponentiation 1 failed"); + goto done; + } + /* calculate c2 = m * y ^ k (mod p)*/ + if (BN_mod_exp_mont_consttime(c2, y, k, p, ctx, mctx) < 1) { + RNP_LOG("Exponentiation 2 failed"); + goto done; + } + if (BN_mod_mul(c2, c2, m, p, ctx) < 1) { + RNP_LOG("Multiplication failed"); + goto done; + } + res = bn2mpi(c1, &out->g) && bn2mpi(c2, &out->m); + assert(res == 1); + ret = RNP_SUCCESS; +done: + BN_MONT_CTX_free(mctx); + BN_CTX_free(ctx); + bn_free(m); + bn_free(p); + bn_free(g); + bn_free(y); + bn_free(k); + return ret; +} + +rnp_result_t +elgamal_decrypt_pkcs1(rnp::RNG * rng, + uint8_t * out, + size_t * out_len, + const pgp_eg_encrypted_t *in, + const pgp_eg_key_t * key) +{ + if (!mpi_bytes(&key->x)) { + RNP_LOG("Secret key not set."); + return RNP_ERROR_BAD_PARAMETERS; + } + BN_CTX *ctx = BN_CTX_new(); + if (!ctx) { + RNP_LOG("Allocation failed."); + return RNP_ERROR_OUT_OF_MEMORY; + } + pgp_mpi_t mm = {}; + size_t padlen = 0; + rnp_result_t ret = RNP_ERROR_GENERIC; + BN_CTX_start(ctx); + BN_MONT_CTX *mctx = BN_MONT_CTX_new(); + bignum_t * p = mpi2bn(&key->p); + bignum_t * g = mpi2bn(&key->g); + bignum_t * x = mpi2bn(&key->x); + bignum_t * c1 = mpi2bn(&in->g); + bignum_t * c2 = mpi2bn(&in->m); + bignum_t * s = BN_CTX_get(ctx); + bignum_t * m = BN_secure_new(); + if (!mctx || !p || !g || !x || !c1 || !c2 || !m) { + RNP_LOG("Allocation failed."); + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + /* initialize Montgomery context */ + if (BN_MONT_CTX_set(mctx, p, ctx) < 1) { + RNP_LOG("Failed to setup Montgomery context."); + goto done; + } + /* calculate s = c1 ^ x (mod p) */ + if (BN_mod_exp_mont_consttime(s, c1, x, p, ctx, mctx) < 1) { + RNP_LOG("Exponentiation 1 failed"); + goto done; + } + /* calculate s^-1 (mod p) */ + BN_set_flags(s, BN_FLG_CONSTTIME); + if (!BN_mod_inverse(s, s, p, ctx)) { + RNP_LOG("Failed to calculate inverse."); + goto done; + } + /* calculate m = c2 * s ^ -1 (mod p)*/ + if (BN_mod_mul(m, c2, s, p, ctx) < 1) { + RNP_LOG("Multiplication failed"); + goto done; + } + bool res; + res = bn2mpi(m, &mm); + assert(res); + if (!res) { + RNP_LOG("bn2mpi failed."); + goto done; + } + /* unpad, handling skipped leftmost 0 case */ + if (!pkcs1v15_unpad(&padlen, mm.mpi, mm.len, mm.len == key->p.len - 1)) { + RNP_LOG("Unpad failed."); + goto done; + } + *out_len = mm.len - padlen; + memcpy(out, &mm.mpi[padlen], *out_len); + ret = RNP_SUCCESS; +done: + secure_clear(mm.mpi, PGP_MPINT_SIZE); + BN_MONT_CTX_free(mctx); + BN_CTX_free(ctx); + bn_free(p); + bn_free(g); + bn_free(x); + bn_free(c1); + bn_free(c2); + bn_free(m); + return ret; +} + +rnp_result_t +elgamal_generate(rnp::RNG *rng, pgp_eg_key_t *key, size_t keybits) +{ + if ((keybits < 1024) || (keybits > PGP_MPINT_BITS)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + rnp_result_t ret = RNP_ERROR_GENERIC; + const DH * dh = NULL; + EVP_PKEY * pkey = NULL; + EVP_PKEY * parmkey = NULL; + EVP_PKEY_CTX *ctx = NULL; + + /* Generate DH params, which usable for ElGamal as well */ + ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_DH, NULL); + if (!ctx) { + RNP_LOG("Failed to create ctx: %lu", ERR_peek_last_error()); + return ret; + } + if (EVP_PKEY_paramgen_init(ctx) <= 0) { + RNP_LOG("Failed to init keygen: %lu", ERR_peek_last_error()); + goto done; + } + if (EVP_PKEY_CTX_set_dh_paramgen_prime_len(ctx, keybits) <= 0) { + RNP_LOG("Failed to set key bits: %lu", ERR_peek_last_error()); + goto done; + } + /* OpenSSL correctly handles case with g = 5, making sure that g is primitive root of + * q-group */ + if (EVP_PKEY_CTX_set_dh_paramgen_generator(ctx, DH_GENERATOR_5) <= 0) { + RNP_LOG("Failed to set key generator: %lu", ERR_peek_last_error()); + goto done; + } + if (EVP_PKEY_paramgen(ctx, &parmkey) <= 0) { + RNP_LOG("Failed to generate parameters: %lu", ERR_peek_last_error()); + goto done; + } + EVP_PKEY_CTX_free(ctx); + /* Generate DH (ElGamal) key */ +start: + ctx = EVP_PKEY_CTX_new(parmkey, NULL); + if (!ctx) { + RNP_LOG("Failed to create ctx: %lu", ERR_peek_last_error()); + goto done; + } + if (EVP_PKEY_keygen_init(ctx) <= 0) { + RNP_LOG("Failed to init keygen: %lu", ERR_peek_last_error()); + goto done; + } + if (EVP_PKEY_keygen(ctx, &pkey) <= 0) { + RNP_LOG("ElGamal keygen failed: %lu", ERR_peek_last_error()); + goto done; + } + dh = EVP_PKEY_get0_DH(pkey); + if (!dh) { + RNP_LOG("Failed to retrieve DH key: %lu", ERR_peek_last_error()); + goto done; + } + if (BITS_TO_BYTES(BN_num_bits(DH_get0_pub_key(dh))) != BITS_TO_BYTES(keybits)) { + EVP_PKEY_CTX_free(ctx); + ctx = NULL; + EVP_PKEY_free(pkey); + pkey = NULL; + goto start; + } + + const bignum_t *p; + const bignum_t *g; + const bignum_t *y; + const bignum_t *x; + p = DH_get0_p(dh); + g = DH_get0_g(dh); + y = DH_get0_pub_key(dh); + x = DH_get0_priv_key(dh); + if (!p || !g || !y || !x) { + ret = RNP_ERROR_BAD_STATE; + goto done; + } + bn2mpi(p, &key->p); + bn2mpi(g, &key->g); + bn2mpi(y, &key->y); + bn2mpi(x, &key->x); + ret = RNP_SUCCESS; +done: + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(parmkey); + EVP_PKEY_free(pkey); + return ret; +} diff --git a/comm/third_party/rnp/src/lib/crypto/hash.cpp b/comm/third_party/rnp/src/lib/crypto/hash.cpp new file mode 100644 index 0000000000..250deec8c2 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/hash.cpp @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2017-2022 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "hash_botan.hpp" +#include "logging.h" +#include <cassert> + +static const id_str_pair botan_alg_map[] = { + {PGP_HASH_MD5, "MD5"}, + {PGP_HASH_SHA1, "SHA-1"}, + {PGP_HASH_RIPEMD, "RIPEMD-160"}, + {PGP_HASH_SHA256, "SHA-256"}, + {PGP_HASH_SHA384, "SHA-384"}, + {PGP_HASH_SHA512, "SHA-512"}, + {PGP_HASH_SHA224, "SHA-224"}, +#if defined(ENABLE_SM2) + {PGP_HASH_SM3, "SM3"}, +#endif + {PGP_HASH_SHA3_256, "SHA-3(256)"}, + {PGP_HASH_SHA3_512, "SHA-3(512)"}, + {0, NULL}, +}; + +namespace rnp { + +Hash_Botan::Hash_Botan(pgp_hash_alg_t alg) : Hash(alg) +{ + auto name = Hash_Botan::name_backend(alg); + if (!name) { + throw rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + fn_ = Botan::HashFunction::create(name); + if (!fn_) { + RNP_LOG("Error creating hash object for '%s'", name); + throw rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + assert(size_ == fn_->output_length()); +} + +Hash_Botan::Hash_Botan(const Hash_Botan &src) : Hash(src.alg_) +{ + if (!src.fn_) { + throw rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + fn_ = src.fn_->copy_state(); +} + +Hash_Botan::~Hash_Botan() +{ +} + +std::unique_ptr<Hash_Botan> +Hash_Botan::create(pgp_hash_alg_t alg) +{ + return std::unique_ptr<Hash_Botan>(new Hash_Botan(alg)); +} + +std::unique_ptr<Hash> +Hash_Botan::clone() const +{ + return std::unique_ptr<Hash>(new Hash_Botan(*this)); +} + +void +Hash_Botan::add(const void *buf, size_t len) +{ + if (!fn_) { + throw rnp_exception(RNP_ERROR_NULL_POINTER); + } + fn_->update(static_cast<const uint8_t *>(buf), len); +} + +size_t +Hash_Botan::finish(uint8_t *digest) +{ + if (!fn_) { + return 0; + } + size_t outlen = size_; + if (digest) { + fn_->final(digest); + } + fn_ = nullptr; + size_ = 0; + return outlen; +} + +const char * +Hash_Botan::name_backend(pgp_hash_alg_t alg) +{ + return id_str_pair::lookup(botan_alg_map, alg); +} + +CRC24_Botan::CRC24_Botan() +{ + fn_ = Botan::HashFunction::create("CRC24"); + if (!fn_) { + RNP_LOG("Error creating CRC24 object"); + throw rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + assert(3 == fn_->output_length()); +} + +CRC24_Botan::~CRC24_Botan() +{ +} + +std::unique_ptr<CRC24_Botan> +CRC24_Botan::create() +{ + return std::unique_ptr<CRC24_Botan>(new CRC24_Botan()); +} + +void +CRC24_Botan::add(const void *buf, size_t len) +{ + if (!fn_) { + throw rnp_exception(RNP_ERROR_NULL_POINTER); + } + fn_->update(static_cast<const uint8_t *>(buf), len); +} + +std::array<uint8_t, 3> +CRC24_Botan::finish() +{ + if (!fn_) { + throw rnp_exception(RNP_ERROR_NULL_POINTER); + } + std::array<uint8_t, 3> crc{}; + fn_->final(crc.data()); + fn_ = nullptr; + return crc; +} + +} // namespace rnp diff --git a/comm/third_party/rnp/src/lib/crypto/hash.hpp b/comm/third_party/rnp/src/lib/crypto/hash.hpp new file mode 100644 index 0000000000..7fcb817700 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/hash.hpp @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2017-2022 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CRYPTO_HASH_H_ +#define CRYPTO_HASH_H_ + +#include <repgp/repgp_def.h> +#include "types.h" +#include "config.h" +#include <memory> +#include <vector> +#include <array> + +/** + * Output size (in bytes) of biggest supported hash algo + */ +#define PGP_MAX_HASH_SIZE (64) + +namespace rnp { +class Hash { + protected: + pgp_hash_alg_t alg_; + size_t size_; + Hash(pgp_hash_alg_t alg) : alg_(alg) + { + size_ = Hash::size(alg); + }; + + public: + pgp_hash_alg_t alg() const; + size_t size() const; + + static std::unique_ptr<Hash> create(pgp_hash_alg_t alg); + virtual std::unique_ptr<Hash> clone() const = 0; + + virtual void add(const void *buf, size_t len) = 0; + virtual void add(uint32_t val); + virtual void add(const pgp_mpi_t &mpi); + virtual size_t finish(uint8_t *digest = NULL) = 0; + + virtual ~Hash(); + + /* Hash algorithm by string representation from cleartext-signed text */ + static pgp_hash_alg_t alg(const char *name); + /* Hash algorithm representation for cleartext-signed text */ + static const char *name(pgp_hash_alg_t alg); + /* Size of the hash algorithm output or 0 if algorithm is unknown */ + static size_t size(pgp_hash_alg_t alg); +}; + +class CRC24 { + protected: + CRC24(){}; + + public: + static std::unique_ptr<CRC24> create(); + + virtual void add(const void *buf, size_t len) = 0; + virtual std::array<uint8_t, 3> finish() = 0; + + virtual ~CRC24(){}; +}; + +class HashList { + public: + std::vector<std::unique_ptr<Hash>> hashes; + + void add_alg(pgp_hash_alg_t alg); + const Hash *get(pgp_hash_alg_t alg) const; + void add(const void *buf, size_t len); +}; + +} // namespace rnp + +#endif diff --git a/comm/third_party/rnp/src/lib/crypto/hash_botan.hpp b/comm/third_party/rnp/src/lib/crypto/hash_botan.hpp new file mode 100644 index 0000000000..942e3a8051 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/hash_botan.hpp @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2022 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CRYPTO_HASH_BOTAN_HPP_ +#define CRYPTO_HASH_BOTAN_HPP_ + +#include "hash.hpp" +#include <botan/hash.h> + +namespace rnp { +class Hash_Botan : public Hash { + private: + std::unique_ptr<Botan::HashFunction> fn_; + + Hash_Botan(pgp_hash_alg_t alg); + Hash_Botan(const Hash_Botan &src); + + public: + virtual ~Hash_Botan(); + + static std::unique_ptr<Hash_Botan> create(pgp_hash_alg_t alg); + std::unique_ptr<Hash> clone() const override; + + void add(const void *buf, size_t len) override; + size_t finish(uint8_t *digest = NULL) override; + + static const char *name_backend(pgp_hash_alg_t alg); +}; + +class CRC24_Botan : public CRC24 { + std::unique_ptr<Botan::HashFunction> fn_; + CRC24_Botan(); + + public: + virtual ~CRC24_Botan(); + + static std::unique_ptr<CRC24_Botan> create(); + + void add(const void *buf, size_t len) override; + std::array<uint8_t, 3> finish() override; +}; + +} // namespace rnp + +#endif
\ No newline at end of file diff --git a/comm/third_party/rnp/src/lib/crypto/hash_common.cpp b/comm/third_party/rnp/src/lib/crypto/hash_common.cpp new file mode 100644 index 0000000000..69b400b0e9 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/hash_common.cpp @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2021-2022 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "hash.hpp" +#include "types.h" +#include "utils.h" +#include "str-utils.h" +#include "hash_sha1cd.hpp" +#if defined(CRYPTO_BACKEND_BOTAN) +#include "hash_botan.hpp" +#endif +#if defined(CRYPTO_BACKEND_OPENSSL) +#include "hash_ossl.hpp" +#include "hash_crc24.hpp" +#endif + +static const struct hash_alg_map_t { + pgp_hash_alg_t type; + const char * name; + size_t len; +} hash_alg_map[] = {{PGP_HASH_MD5, "MD5", 16}, + {PGP_HASH_SHA1, "SHA1", 20}, + {PGP_HASH_RIPEMD, "RIPEMD160", 20}, + {PGP_HASH_SHA256, "SHA256", 32}, + {PGP_HASH_SHA384, "SHA384", 48}, + {PGP_HASH_SHA512, "SHA512", 64}, + {PGP_HASH_SHA224, "SHA224", 28}, + {PGP_HASH_SM3, "SM3", 32}, + {PGP_HASH_SHA3_256, "SHA3-256", 32}, + {PGP_HASH_SHA3_512, "SHA3-512", 64}}; + +namespace rnp { + +pgp_hash_alg_t +Hash::alg() const +{ + return alg_; +} + +size_t +Hash::size() const +{ + return Hash::size(alg_); +} + +std::unique_ptr<Hash> +Hash::create(pgp_hash_alg_t alg) +{ + if (alg == PGP_HASH_SHA1) { + return Hash_SHA1CD::create(); + } +#if !defined(ENABLE_SM2) + if (alg == PGP_HASH_SM3) { + RNP_LOG("SM3 hash is not available."); + throw rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } +#endif +#if defined(CRYPTO_BACKEND_OPENSSL) + return Hash_OpenSSL::create(alg); +#elif defined(CRYPTO_BACKEND_BOTAN) + return Hash_Botan::create(alg); +#else +#error "Crypto backend not specified" +#endif +} + +std::unique_ptr<CRC24> +CRC24::create() +{ +#if defined(CRYPTO_BACKEND_OPENSSL) + return CRC24_RNP::create(); +#elif defined(CRYPTO_BACKEND_BOTAN) + return CRC24_Botan::create(); +#else +#error "Crypto backend not specified" +#endif +} + +void +Hash::add(uint32_t val) +{ + uint8_t ibuf[4]; + STORE32BE(ibuf, val); + add(ibuf, sizeof(ibuf)); +} + +void +Hash::add(const pgp_mpi_t &val) +{ + size_t len = mpi_bytes(&val); + size_t idx = 0; + while ((idx < len) && (!val.mpi[idx])) { + idx++; + } + + if (idx >= len) { + add(0); + return; + } + + add(len - idx); + if (val.mpi[idx] & 0x80) { + uint8_t padbyte = 0; + add(&padbyte, 1); + } + add(val.mpi + idx, len - idx); +} + +Hash::~Hash() +{ +} + +pgp_hash_alg_t +Hash::alg(const char *name) +{ + if (!name) { + return PGP_HASH_UNKNOWN; + } + for (size_t i = 0; i < ARRAY_SIZE(hash_alg_map); i++) { + if (rnp::str_case_eq(name, hash_alg_map[i].name)) { + return hash_alg_map[i].type; + } + } + return PGP_HASH_UNKNOWN; +} + +const char * +Hash::name(pgp_hash_alg_t alg) +{ + const char *ret = NULL; + ARRAY_LOOKUP_BY_ID(hash_alg_map, type, name, alg, ret); + return ret; +} + +size_t +Hash::size(pgp_hash_alg_t alg) +{ + size_t val = 0; + ARRAY_LOOKUP_BY_ID(hash_alg_map, type, len, alg, val); + return val; +} + +void +HashList::add_alg(pgp_hash_alg_t alg) +{ + if (!get(alg)) { + hashes.emplace_back(rnp::Hash::create(alg)); + } +} + +const Hash * +HashList::get(pgp_hash_alg_t alg) const +{ + for (auto &hash : hashes) { + if (hash->alg() == alg) { + return hash.get(); + } + } + return NULL; +} + +void +HashList::add(const void *buf, size_t len) +{ + for (auto &hash : hashes) { + hash->add(buf, len); + } +} + +} // namespace rnp diff --git a/comm/third_party/rnp/src/lib/crypto/hash_crc24.cpp b/comm/third_party/rnp/src/lib/crypto/hash_crc24.cpp new file mode 100644 index 0000000000..54f4d96ba5 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/hash_crc24.cpp @@ -0,0 +1,288 @@ +/* + * Copyright (c) 2017-2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdint.h> +#include <stddef.h> +#include "utils.h" +#include "hash_crc24.hpp" + +static const uint32_t T0[256] = { + 0x00000000, 0x00FB4C86, 0x000DD58A, 0x00F6990C, 0x00E1E693, 0x001AAA15, 0x00EC3319, + 0x00177F9F, 0x003981A1, 0x00C2CD27, 0x0034542B, 0x00CF18AD, 0x00D86732, 0x00232BB4, + 0x00D5B2B8, 0x002EFE3E, 0x00894EC5, 0x00720243, 0x00849B4F, 0x007FD7C9, 0x0068A856, + 0x0093E4D0, 0x00657DDC, 0x009E315A, 0x00B0CF64, 0x004B83E2, 0x00BD1AEE, 0x00465668, + 0x005129F7, 0x00AA6571, 0x005CFC7D, 0x00A7B0FB, 0x00E9D10C, 0x00129D8A, 0x00E40486, + 0x001F4800, 0x0008379F, 0x00F37B19, 0x0005E215, 0x00FEAE93, 0x00D050AD, 0x002B1C2B, + 0x00DD8527, 0x0026C9A1, 0x0031B63E, 0x00CAFAB8, 0x003C63B4, 0x00C72F32, 0x00609FC9, + 0x009BD34F, 0x006D4A43, 0x009606C5, 0x0081795A, 0x007A35DC, 0x008CACD0, 0x0077E056, + 0x00591E68, 0x00A252EE, 0x0054CBE2, 0x00AF8764, 0x00B8F8FB, 0x0043B47D, 0x00B52D71, + 0x004E61F7, 0x00D2A319, 0x0029EF9F, 0x00DF7693, 0x00243A15, 0x0033458A, 0x00C8090C, + 0x003E9000, 0x00C5DC86, 0x00EB22B8, 0x00106E3E, 0x00E6F732, 0x001DBBB4, 0x000AC42B, + 0x00F188AD, 0x000711A1, 0x00FC5D27, 0x005BEDDC, 0x00A0A15A, 0x00563856, 0x00AD74D0, + 0x00BA0B4F, 0x004147C9, 0x00B7DEC5, 0x004C9243, 0x00626C7D, 0x009920FB, 0x006FB9F7, + 0x0094F571, 0x00838AEE, 0x0078C668, 0x008E5F64, 0x007513E2, 0x003B7215, 0x00C03E93, + 0x0036A79F, 0x00CDEB19, 0x00DA9486, 0x0021D800, 0x00D7410C, 0x002C0D8A, 0x0002F3B4, + 0x00F9BF32, 0x000F263E, 0x00F46AB8, 0x00E31527, 0x001859A1, 0x00EEC0AD, 0x00158C2B, + 0x00B23CD0, 0x00497056, 0x00BFE95A, 0x0044A5DC, 0x0053DA43, 0x00A896C5, 0x005E0FC9, + 0x00A5434F, 0x008BBD71, 0x0070F1F7, 0x008668FB, 0x007D247D, 0x006A5BE2, 0x00911764, + 0x00678E68, 0x009CC2EE, 0x00A44733, 0x005F0BB5, 0x00A992B9, 0x0052DE3F, 0x0045A1A0, + 0x00BEED26, 0x0048742A, 0x00B338AC, 0x009DC692, 0x00668A14, 0x00901318, 0x006B5F9E, + 0x007C2001, 0x00876C87, 0x0071F58B, 0x008AB90D, 0x002D09F6, 0x00D64570, 0x0020DC7C, + 0x00DB90FA, 0x00CCEF65, 0x0037A3E3, 0x00C13AEF, 0x003A7669, 0x00148857, 0x00EFC4D1, + 0x00195DDD, 0x00E2115B, 0x00F56EC4, 0x000E2242, 0x00F8BB4E, 0x0003F7C8, 0x004D963F, + 0x00B6DAB9, 0x004043B5, 0x00BB0F33, 0x00AC70AC, 0x00573C2A, 0x00A1A526, 0x005AE9A0, + 0x0074179E, 0x008F5B18, 0x0079C214, 0x00828E92, 0x0095F10D, 0x006EBD8B, 0x00982487, + 0x00636801, 0x00C4D8FA, 0x003F947C, 0x00C90D70, 0x003241F6, 0x00253E69, 0x00DE72EF, + 0x0028EBE3, 0x00D3A765, 0x00FD595B, 0x000615DD, 0x00F08CD1, 0x000BC057, 0x001CBFC8, + 0x00E7F34E, 0x00116A42, 0x00EA26C4, 0x0076E42A, 0x008DA8AC, 0x007B31A0, 0x00807D26, + 0x009702B9, 0x006C4E3F, 0x009AD733, 0x00619BB5, 0x004F658B, 0x00B4290D, 0x0042B001, + 0x00B9FC87, 0x00AE8318, 0x0055CF9E, 0x00A35692, 0x00581A14, 0x00FFAAEF, 0x0004E669, + 0x00F27F65, 0x000933E3, 0x001E4C7C, 0x00E500FA, 0x001399F6, 0x00E8D570, 0x00C62B4E, + 0x003D67C8, 0x00CBFEC4, 0x0030B242, 0x0027CDDD, 0x00DC815B, 0x002A1857, 0x00D154D1, + 0x009F3526, 0x006479A0, 0x0092E0AC, 0x0069AC2A, 0x007ED3B5, 0x00859F33, 0x0073063F, + 0x00884AB9, 0x00A6B487, 0x005DF801, 0x00AB610D, 0x00502D8B, 0x00475214, 0x00BC1E92, + 0x004A879E, 0x00B1CB18, 0x00167BE3, 0x00ED3765, 0x001BAE69, 0x00E0E2EF, 0x00F79D70, + 0x000CD1F6, 0x00FA48FA, 0x0001047C, 0x002FFA42, 0x00D4B6C4, 0x00222FC8, 0x00D9634E, + 0x00CE1CD1, 0x00355057, 0x00C3C95B, 0x003885DD, +}; + +static const uint32_t T1[256] = { + 0x00000000, 0x00488F66, 0x00901ECD, 0x00D891AB, 0x00DB711C, 0x0093FE7A, 0x004B6FD1, + 0x0003E0B7, 0x00B6E338, 0x00FE6C5E, 0x0026FDF5, 0x006E7293, 0x006D9224, 0x00251D42, + 0x00FD8CE9, 0x00B5038F, 0x006CC771, 0x00244817, 0x00FCD9BC, 0x00B456DA, 0x00B7B66D, + 0x00FF390B, 0x0027A8A0, 0x006F27C6, 0x00DA2449, 0x0092AB2F, 0x004A3A84, 0x0002B5E2, + 0x00015555, 0x0049DA33, 0x00914B98, 0x00D9C4FE, 0x00D88EE3, 0x00900185, 0x0048902E, + 0x00001F48, 0x0003FFFF, 0x004B7099, 0x0093E132, 0x00DB6E54, 0x006E6DDB, 0x0026E2BD, + 0x00FE7316, 0x00B6FC70, 0x00B51CC7, 0x00FD93A1, 0x0025020A, 0x006D8D6C, 0x00B44992, + 0x00FCC6F4, 0x0024575F, 0x006CD839, 0x006F388E, 0x0027B7E8, 0x00FF2643, 0x00B7A925, + 0x0002AAAA, 0x004A25CC, 0x0092B467, 0x00DA3B01, 0x00D9DBB6, 0x009154D0, 0x0049C57B, + 0x00014A1D, 0x004B5141, 0x0003DE27, 0x00DB4F8C, 0x0093C0EA, 0x0090205D, 0x00D8AF3B, + 0x00003E90, 0x0048B1F6, 0x00FDB279, 0x00B53D1F, 0x006DACB4, 0x002523D2, 0x0026C365, + 0x006E4C03, 0x00B6DDA8, 0x00FE52CE, 0x00279630, 0x006F1956, 0x00B788FD, 0x00FF079B, + 0x00FCE72C, 0x00B4684A, 0x006CF9E1, 0x00247687, 0x00917508, 0x00D9FA6E, 0x00016BC5, + 0x0049E4A3, 0x004A0414, 0x00028B72, 0x00DA1AD9, 0x009295BF, 0x0093DFA2, 0x00DB50C4, + 0x0003C16F, 0x004B4E09, 0x0048AEBE, 0x000021D8, 0x00D8B073, 0x00903F15, 0x00253C9A, + 0x006DB3FC, 0x00B52257, 0x00FDAD31, 0x00FE4D86, 0x00B6C2E0, 0x006E534B, 0x0026DC2D, + 0x00FF18D3, 0x00B797B5, 0x006F061E, 0x00278978, 0x002469CF, 0x006CE6A9, 0x00B47702, + 0x00FCF864, 0x0049FBEB, 0x0001748D, 0x00D9E526, 0x00916A40, 0x00928AF7, 0x00DA0591, + 0x0002943A, 0x004A1B5C, 0x0096A282, 0x00DE2DE4, 0x0006BC4F, 0x004E3329, 0x004DD39E, + 0x00055CF8, 0x00DDCD53, 0x00954235, 0x002041BA, 0x0068CEDC, 0x00B05F77, 0x00F8D011, + 0x00FB30A6, 0x00B3BFC0, 0x006B2E6B, 0x0023A10D, 0x00FA65F3, 0x00B2EA95, 0x006A7B3E, + 0x0022F458, 0x002114EF, 0x00699B89, 0x00B10A22, 0x00F98544, 0x004C86CB, 0x000409AD, + 0x00DC9806, 0x00941760, 0x0097F7D7, 0x00DF78B1, 0x0007E91A, 0x004F667C, 0x004E2C61, + 0x0006A307, 0x00DE32AC, 0x0096BDCA, 0x00955D7D, 0x00DDD21B, 0x000543B0, 0x004DCCD6, + 0x00F8CF59, 0x00B0403F, 0x0068D194, 0x00205EF2, 0x0023BE45, 0x006B3123, 0x00B3A088, + 0x00FB2FEE, 0x0022EB10, 0x006A6476, 0x00B2F5DD, 0x00FA7ABB, 0x00F99A0C, 0x00B1156A, + 0x006984C1, 0x00210BA7, 0x00940828, 0x00DC874E, 0x000416E5, 0x004C9983, 0x004F7934, + 0x0007F652, 0x00DF67F9, 0x0097E89F, 0x00DDF3C3, 0x00957CA5, 0x004DED0E, 0x00056268, + 0x000682DF, 0x004E0DB9, 0x00969C12, 0x00DE1374, 0x006B10FB, 0x00239F9D, 0x00FB0E36, + 0x00B38150, 0x00B061E7, 0x00F8EE81, 0x00207F2A, 0x0068F04C, 0x00B134B2, 0x00F9BBD4, + 0x00212A7F, 0x0069A519, 0x006A45AE, 0x0022CAC8, 0x00FA5B63, 0x00B2D405, 0x0007D78A, + 0x004F58EC, 0x0097C947, 0x00DF4621, 0x00DCA696, 0x009429F0, 0x004CB85B, 0x0004373D, + 0x00057D20, 0x004DF246, 0x009563ED, 0x00DDEC8B, 0x00DE0C3C, 0x0096835A, 0x004E12F1, + 0x00069D97, 0x00B39E18, 0x00FB117E, 0x002380D5, 0x006B0FB3, 0x0068EF04, 0x00206062, + 0x00F8F1C9, 0x00B07EAF, 0x0069BA51, 0x00213537, 0x00F9A49C, 0x00B12BFA, 0x00B2CB4D, + 0x00FA442B, 0x0022D580, 0x006A5AE6, 0x00DF5969, 0x0097D60F, 0x004F47A4, 0x0007C8C2, + 0x00042875, 0x004CA713, 0x009436B8, 0x00DCB9DE, +}; + +static const uint32_t T2[256] = { + 0x00000000, 0x00D70983, 0x00555F80, 0x00825603, 0x0051F286, 0x0086FB05, 0x0004AD06, + 0x00D3A485, 0x0059A88B, 0x008EA108, 0x000CF70B, 0x00DBFE88, 0x00085A0D, 0x00DF538E, + 0x005D058D, 0x008A0C0E, 0x00491C91, 0x009E1512, 0x001C4311, 0x00CB4A92, 0x0018EE17, + 0x00CFE794, 0x004DB197, 0x009AB814, 0x0010B41A, 0x00C7BD99, 0x0045EB9A, 0x0092E219, + 0x0041469C, 0x00964F1F, 0x0014191C, 0x00C3109F, 0x006974A4, 0x00BE7D27, 0x003C2B24, + 0x00EB22A7, 0x00388622, 0x00EF8FA1, 0x006DD9A2, 0x00BAD021, 0x0030DC2F, 0x00E7D5AC, + 0x006583AF, 0x00B28A2C, 0x00612EA9, 0x00B6272A, 0x00347129, 0x00E378AA, 0x00206835, + 0x00F761B6, 0x007537B5, 0x00A23E36, 0x00719AB3, 0x00A69330, 0x0024C533, 0x00F3CCB0, + 0x0079C0BE, 0x00AEC93D, 0x002C9F3E, 0x00FB96BD, 0x00283238, 0x00FF3BBB, 0x007D6DB8, + 0x00AA643B, 0x0029A4CE, 0x00FEAD4D, 0x007CFB4E, 0x00ABF2CD, 0x00785648, 0x00AF5FCB, + 0x002D09C8, 0x00FA004B, 0x00700C45, 0x00A705C6, 0x002553C5, 0x00F25A46, 0x0021FEC3, + 0x00F6F740, 0x0074A143, 0x00A3A8C0, 0x0060B85F, 0x00B7B1DC, 0x0035E7DF, 0x00E2EE5C, + 0x00314AD9, 0x00E6435A, 0x00641559, 0x00B31CDA, 0x003910D4, 0x00EE1957, 0x006C4F54, + 0x00BB46D7, 0x0068E252, 0x00BFEBD1, 0x003DBDD2, 0x00EAB451, 0x0040D06A, 0x0097D9E9, + 0x00158FEA, 0x00C28669, 0x001122EC, 0x00C62B6F, 0x00447D6C, 0x009374EF, 0x001978E1, + 0x00CE7162, 0x004C2761, 0x009B2EE2, 0x00488A67, 0x009F83E4, 0x001DD5E7, 0x00CADC64, + 0x0009CCFB, 0x00DEC578, 0x005C937B, 0x008B9AF8, 0x00583E7D, 0x008F37FE, 0x000D61FD, + 0x00DA687E, 0x00506470, 0x00876DF3, 0x00053BF0, 0x00D23273, 0x000196F6, 0x00D69F75, + 0x0054C976, 0x0083C0F5, 0x00A9041B, 0x007E0D98, 0x00FC5B9B, 0x002B5218, 0x00F8F69D, + 0x002FFF1E, 0x00ADA91D, 0x007AA09E, 0x00F0AC90, 0x0027A513, 0x00A5F310, 0x0072FA93, + 0x00A15E16, 0x00765795, 0x00F40196, 0x00230815, 0x00E0188A, 0x00371109, 0x00B5470A, + 0x00624E89, 0x00B1EA0C, 0x0066E38F, 0x00E4B58C, 0x0033BC0F, 0x00B9B001, 0x006EB982, + 0x00ECEF81, 0x003BE602, 0x00E84287, 0x003F4B04, 0x00BD1D07, 0x006A1484, 0x00C070BF, + 0x0017793C, 0x00952F3F, 0x004226BC, 0x00918239, 0x00468BBA, 0x00C4DDB9, 0x0013D43A, + 0x0099D834, 0x004ED1B7, 0x00CC87B4, 0x001B8E37, 0x00C82AB2, 0x001F2331, 0x009D7532, + 0x004A7CB1, 0x00896C2E, 0x005E65AD, 0x00DC33AE, 0x000B3A2D, 0x00D89EA8, 0x000F972B, + 0x008DC128, 0x005AC8AB, 0x00D0C4A5, 0x0007CD26, 0x00859B25, 0x005292A6, 0x00813623, + 0x00563FA0, 0x00D469A3, 0x00036020, 0x0080A0D5, 0x0057A956, 0x00D5FF55, 0x0002F6D6, + 0x00D15253, 0x00065BD0, 0x00840DD3, 0x00530450, 0x00D9085E, 0x000E01DD, 0x008C57DE, + 0x005B5E5D, 0x0088FAD8, 0x005FF35B, 0x00DDA558, 0x000AACDB, 0x00C9BC44, 0x001EB5C7, + 0x009CE3C4, 0x004BEA47, 0x00984EC2, 0x004F4741, 0x00CD1142, 0x001A18C1, 0x009014CF, + 0x00471D4C, 0x00C54B4F, 0x001242CC, 0x00C1E649, 0x0016EFCA, 0x0094B9C9, 0x0043B04A, + 0x00E9D471, 0x003EDDF2, 0x00BC8BF1, 0x006B8272, 0x00B826F7, 0x006F2F74, 0x00ED7977, + 0x003A70F4, 0x00B07CFA, 0x00677579, 0x00E5237A, 0x00322AF9, 0x00E18E7C, 0x003687FF, + 0x00B4D1FC, 0x0063D87F, 0x00A0C8E0, 0x0077C163, 0x00F59760, 0x00229EE3, 0x00F13A66, + 0x002633E5, 0x00A465E6, 0x00736C65, 0x00F9606B, 0x002E69E8, 0x00AC3FEB, 0x007B3668, + 0x00A892ED, 0x007F9B6E, 0x00FDCD6D, 0x002AC4EE, +}; + +static const uint32_t T3[256] = { + 0x00000000, 0x00520936, 0x00A4126C, 0x00F61B5A, 0x004825D8, 0x001A2CEE, 0x00EC37B4, + 0x00BE3E82, 0x006B0636, 0x00390F00, 0x00CF145A, 0x009D1D6C, 0x002323EE, 0x00712AD8, + 0x00873182, 0x00D538B4, 0x00D60C6C, 0x0084055A, 0x00721E00, 0x00201736, 0x009E29B4, + 0x00CC2082, 0x003A3BD8, 0x006832EE, 0x00BD0A5A, 0x00EF036C, 0x00191836, 0x004B1100, + 0x00F52F82, 0x00A726B4, 0x00513DEE, 0x000334D8, 0x00AC19D8, 0x00FE10EE, 0x00080BB4, + 0x005A0282, 0x00E43C00, 0x00B63536, 0x00402E6C, 0x0012275A, 0x00C71FEE, 0x009516D8, + 0x00630D82, 0x003104B4, 0x008F3A36, 0x00DD3300, 0x002B285A, 0x0079216C, 0x007A15B4, + 0x00281C82, 0x00DE07D8, 0x008C0EEE, 0x0032306C, 0x0060395A, 0x00962200, 0x00C42B36, + 0x00111382, 0x00431AB4, 0x00B501EE, 0x00E708D8, 0x0059365A, 0x000B3F6C, 0x00FD2436, + 0x00AF2D00, 0x00A37F36, 0x00F17600, 0x00076D5A, 0x0055646C, 0x00EB5AEE, 0x00B953D8, + 0x004F4882, 0x001D41B4, 0x00C87900, 0x009A7036, 0x006C6B6C, 0x003E625A, 0x00805CD8, + 0x00D255EE, 0x00244EB4, 0x00764782, 0x0075735A, 0x00277A6C, 0x00D16136, 0x00836800, + 0x003D5682, 0x006F5FB4, 0x009944EE, 0x00CB4DD8, 0x001E756C, 0x004C7C5A, 0x00BA6700, + 0x00E86E36, 0x005650B4, 0x00045982, 0x00F242D8, 0x00A04BEE, 0x000F66EE, 0x005D6FD8, + 0x00AB7482, 0x00F97DB4, 0x00474336, 0x00154A00, 0x00E3515A, 0x00B1586C, 0x006460D8, + 0x003669EE, 0x00C072B4, 0x00927B82, 0x002C4500, 0x007E4C36, 0x0088576C, 0x00DA5E5A, + 0x00D96A82, 0x008B63B4, 0x007D78EE, 0x002F71D8, 0x00914F5A, 0x00C3466C, 0x00355D36, + 0x00675400, 0x00B26CB4, 0x00E06582, 0x00167ED8, 0x004477EE, 0x00FA496C, 0x00A8405A, + 0x005E5B00, 0x000C5236, 0x0046FF6C, 0x0014F65A, 0x00E2ED00, 0x00B0E436, 0x000EDAB4, + 0x005CD382, 0x00AAC8D8, 0x00F8C1EE, 0x002DF95A, 0x007FF06C, 0x0089EB36, 0x00DBE200, + 0x0065DC82, 0x0037D5B4, 0x00C1CEEE, 0x0093C7D8, 0x0090F300, 0x00C2FA36, 0x0034E16C, + 0x0066E85A, 0x00D8D6D8, 0x008ADFEE, 0x007CC4B4, 0x002ECD82, 0x00FBF536, 0x00A9FC00, + 0x005FE75A, 0x000DEE6C, 0x00B3D0EE, 0x00E1D9D8, 0x0017C282, 0x0045CBB4, 0x00EAE6B4, + 0x00B8EF82, 0x004EF4D8, 0x001CFDEE, 0x00A2C36C, 0x00F0CA5A, 0x0006D100, 0x0054D836, + 0x0081E082, 0x00D3E9B4, 0x0025F2EE, 0x0077FBD8, 0x00C9C55A, 0x009BCC6C, 0x006DD736, + 0x003FDE00, 0x003CEAD8, 0x006EE3EE, 0x0098F8B4, 0x00CAF182, 0x0074CF00, 0x0026C636, + 0x00D0DD6C, 0x0082D45A, 0x0057ECEE, 0x0005E5D8, 0x00F3FE82, 0x00A1F7B4, 0x001FC936, + 0x004DC000, 0x00BBDB5A, 0x00E9D26C, 0x00E5805A, 0x00B7896C, 0x00419236, 0x00139B00, + 0x00ADA582, 0x00FFACB4, 0x0009B7EE, 0x005BBED8, 0x008E866C, 0x00DC8F5A, 0x002A9400, + 0x00789D36, 0x00C6A3B4, 0x0094AA82, 0x0062B1D8, 0x0030B8EE, 0x00338C36, 0x00618500, + 0x00979E5A, 0x00C5976C, 0x007BA9EE, 0x0029A0D8, 0x00DFBB82, 0x008DB2B4, 0x00588A00, + 0x000A8336, 0x00FC986C, 0x00AE915A, 0x0010AFD8, 0x0042A6EE, 0x00B4BDB4, 0x00E6B482, + 0x00499982, 0x001B90B4, 0x00ED8BEE, 0x00BF82D8, 0x0001BC5A, 0x0053B56C, 0x00A5AE36, + 0x00F7A700, 0x00229FB4, 0x00709682, 0x00868DD8, 0x00D484EE, 0x006ABA6C, 0x0038B35A, + 0x00CEA800, 0x009CA136, 0x009F95EE, 0x00CD9CD8, 0x003B8782, 0x00698EB4, 0x00D7B036, + 0x0085B900, 0x0073A25A, 0x0021AB6C, 0x00F493D8, 0x00A69AEE, 0x005081B4, 0x00028882, + 0x00BCB600, 0x00EEBF36, 0x0018A46C, 0x004AAD5A}; + +#define CRC24_FAST_INIT 0xce04b7L + +static inline uint32_t +process8(uint32_t crc, uint8_t data) +{ + return (crc >> 8) ^ T0[(crc & 0xff) ^ data]; +} + +/* + * Process 4 bytes in one go + */ +static inline uint32_t +process32(uint32_t crc, uint32_t word) +{ + crc ^= word; + crc = T3[crc & 0xff] ^ T2[((crc >> 8) & 0xff)] ^ T1[((crc >> 16) & 0xff)] ^ + T0[(crc >> 24) & 0xff]; + return crc; +} + +static uint32_t +crc24_update(uint32_t crc, const uint8_t *in, size_t length) +{ + uint32_t d0, d1, d2, d3; + + while (length >= 16) { + LOAD32LE(d0, &in[0]); + LOAD32LE(d1, &in[4]); + LOAD32LE(d2, &in[8]); + LOAD32LE(d3, &in[12]); + + crc = process32(crc, d0); + crc = process32(crc, d1); + crc = process32(crc, d2); + crc = process32(crc, d3); + + in += 16; + length -= 16; + } + + while (length--) { + crc = process8(crc, *in++); + } + + return crc & 0xffffff; +} + +/* Swap endianness of 32-bit value */ +#if defined(__GNUC__) || defined(__clang__) +#define BSWAP32(x) __builtin_bswap32(x) +#else +#define BSWAP32(x) \ + ((x & 0x000000FF) << 24 | (x & 0x0000FF00) << 8 | (x & 0x00FF0000) >> 8 | \ + (x & 0xFF000000) >> 24) +#endif + +static uint32_t +crc24_final(uint32_t crc) +{ + return (BSWAP32(crc) >> 8); +} + +namespace rnp { + +CRC24_RNP::CRC24_RNP() +{ + state_ = CRC24_FAST_INIT; +} + +CRC24_RNP::~CRC24_RNP() +{ +} + +std::unique_ptr<CRC24_RNP> +CRC24_RNP::create() +{ + return std::unique_ptr<CRC24_RNP>(new CRC24_RNP()); +} + +void +CRC24_RNP::add(const void *buf, size_t len) +{ + state_ = crc24_update(state_, static_cast<const uint8_t *>(buf), len); +} + +std::array<uint8_t, 3> +CRC24_RNP::finish() +{ + uint32_t crc_fin = crc24_final(state_); + state_ = 0; + std::array<uint8_t, 3> res; + res[0] = (crc_fin >> 16) & 0xff; + res[1] = (crc_fin >> 8) & 0xff; + res[2] = crc_fin & 0xff; + return res; +} + +}; // namespace rnp diff --git a/comm/third_party/rnp/src/lib/crypto/hash_crc24.hpp b/comm/third_party/rnp/src/lib/crypto/hash_crc24.hpp new file mode 100644 index 0000000000..a73a46682e --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/hash_crc24.hpp @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2022 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CRYPTO_HASH_CRC24_HPP_ +#define CRYPTO_HASH_CRC24_HPP_ + +#include "hash.hpp" + +namespace rnp { +class CRC24_RNP : public CRC24 { + uint32_t state_; + CRC24_RNP(); + + public: + virtual ~CRC24_RNP(); + + static std::unique_ptr<CRC24_RNP> create(); + + void add(const void *buf, size_t len) override; + std::array<uint8_t, 3> finish() override; +}; +} // namespace rnp + +#endif
\ No newline at end of file diff --git a/comm/third_party/rnp/src/lib/crypto/hash_ossl.cpp b/comm/third_party/rnp/src/lib/crypto/hash_ossl.cpp new file mode 100644 index 0000000000..37b75464d4 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/hash_ossl.cpp @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2021-2022 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "hash_ossl.hpp" +#include <stdio.h> +#include <memory> +#include <cassert> +#include <openssl/err.h> +#include "config.h" +#include "types.h" +#include "utils.h" +#include "str-utils.h" +#include "defaults.h" + +static const id_str_pair openssl_alg_map[] = { + {PGP_HASH_MD5, "md5"}, + {PGP_HASH_SHA1, "sha1"}, + {PGP_HASH_RIPEMD, "ripemd160"}, + {PGP_HASH_SHA256, "sha256"}, + {PGP_HASH_SHA384, "sha384"}, + {PGP_HASH_SHA512, "sha512"}, + {PGP_HASH_SHA224, "sha224"}, + {PGP_HASH_SM3, "sm3"}, + {PGP_HASH_SHA3_256, "sha3-256"}, + {PGP_HASH_SHA3_512, "sha3-512"}, + {0, NULL}, +}; + +namespace rnp { +Hash_OpenSSL::Hash_OpenSSL(pgp_hash_alg_t alg) : Hash(alg) +{ + const char * hash_name = Hash_OpenSSL::name_backend(alg); + const EVP_MD *hash_tp = EVP_get_digestbyname(hash_name); + if (!hash_tp) { + RNP_LOG("Error creating hash object for '%s'", hash_name); + throw rnp_exception(RNP_ERROR_BAD_STATE); + } + fn_ = EVP_MD_CTX_new(); + if (!fn_) { + RNP_LOG("Allocation failure"); + throw rnp_exception(RNP_ERROR_OUT_OF_MEMORY); + } + int res = EVP_DigestInit_ex(fn_, hash_tp, NULL); + if (res != 1) { + RNP_LOG("Digest initializataion error %d : %lu", res, ERR_peek_last_error()); + EVP_MD_CTX_free(fn_); + throw rnp_exception(RNP_ERROR_BAD_STATE); + } + assert(size_ == (size_t) EVP_MD_size(hash_tp)); +} + +Hash_OpenSSL::Hash_OpenSSL(const Hash_OpenSSL &src) : Hash(src.alg_) +{ + if (!src.fn_) { + throw rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + fn_ = EVP_MD_CTX_new(); + if (!fn_) { + RNP_LOG("Allocation failure"); + throw rnp_exception(RNP_ERROR_OUT_OF_MEMORY); + } + + int res = EVP_MD_CTX_copy(fn_, src.fn_); + if (res != 1) { + RNP_LOG("Digest copying error %d: %lu", res, ERR_peek_last_error()); + EVP_MD_CTX_free(fn_); + throw rnp_exception(RNP_ERROR_BAD_STATE); + } +} + +std::unique_ptr<Hash_OpenSSL> +Hash_OpenSSL::create(pgp_hash_alg_t alg) +{ + return std::unique_ptr<Hash_OpenSSL>(new Hash_OpenSSL(alg)); +} + +std::unique_ptr<Hash> +Hash_OpenSSL::clone() const +{ + return std::unique_ptr<Hash>(new Hash_OpenSSL(*this)); +} + +void +Hash_OpenSSL::add(const void *buf, size_t len) +{ + if (!fn_) { + throw rnp_exception(RNP_ERROR_NULL_POINTER); + } + int res = EVP_DigestUpdate(fn_, buf, len); + if (res != 1) { + RNP_LOG("Digest updating error %d: %lu", res, ERR_peek_last_error()); + throw rnp_exception(RNP_ERROR_GENERIC); + } +} + +size_t +Hash_OpenSSL::finish(uint8_t *digest) +{ + if (!fn_) { + return 0; + } + int res = digest ? EVP_DigestFinal_ex(fn_, digest, NULL) : 1; + EVP_MD_CTX_free(fn_); + fn_ = NULL; + if (res != 1) { + RNP_LOG("Digest finalization error %d: %lu", res, ERR_peek_last_error()); + return 0; + } + + size_t outsz = size_; + size_ = 0; + return outsz; +} + +Hash_OpenSSL::~Hash_OpenSSL() +{ + if (!fn_) { + return; + } + EVP_MD_CTX_free(fn_); +} + +const char * +Hash_OpenSSL::name_backend(pgp_hash_alg_t alg) +{ + return id_str_pair::lookup(openssl_alg_map, alg); +} +} // namespace rnp diff --git a/comm/third_party/rnp/src/lib/crypto/hash_ossl.hpp b/comm/third_party/rnp/src/lib/crypto/hash_ossl.hpp new file mode 100644 index 0000000000..95b365b92b --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/hash_ossl.hpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2022 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CRYPTO_HASH_OSSL_HPP_ +#define CRYPTO_HASH_OSSL_HPP_ + +#include "hash.hpp" +#include <openssl/evp.h> + +namespace rnp { +class Hash_OpenSSL : public Hash { + private: + EVP_MD_CTX *fn_; + + Hash_OpenSSL(pgp_hash_alg_t alg); + Hash_OpenSSL(const Hash_OpenSSL &src); + + public: + virtual ~Hash_OpenSSL(); + + static std::unique_ptr<Hash_OpenSSL> create(pgp_hash_alg_t alg); + std::unique_ptr<Hash> clone() const override; + + void add(const void *buf, size_t len) override; + size_t finish(uint8_t *digest = NULL) override; + + static const char *name_backend(pgp_hash_alg_t alg); +}; + +} // namespace rnp + +#endif
\ No newline at end of file diff --git a/comm/third_party/rnp/src/lib/crypto/hash_sha1cd.cpp b/comm/third_party/rnp/src/lib/crypto/hash_sha1cd.cpp new file mode 100644 index 0000000000..863591e762 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/hash_sha1cd.cpp @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2021-2022 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <cassert> +#include "logging.h" +#include "hash_sha1cd.hpp" + +namespace rnp { +Hash_SHA1CD::Hash_SHA1CD() : Hash(PGP_HASH_SHA1) +{ + assert(size_ == 20); + SHA1DCInit(&ctx_); +} + +Hash_SHA1CD::Hash_SHA1CD(const Hash_SHA1CD &src) : Hash(PGP_HASH_SHA1) +{ + ctx_ = src.ctx_; +} + +Hash_SHA1CD::~Hash_SHA1CD() +{ +} + +std::unique_ptr<Hash_SHA1CD> +Hash_SHA1CD::create() +{ + return std::unique_ptr<Hash_SHA1CD>(new Hash_SHA1CD()); +} + +std::unique_ptr<Hash> +Hash_SHA1CD::clone() const +{ + return std::unique_ptr<Hash>(new Hash_SHA1CD(*this)); +} + +/* This produces runtime error: load of misaligned address 0x60d0000030a9 for type 'const + * uint32_t' (aka 'const unsigned int'), which requires 4 byte alignment */ +#if defined(__clang__) +__attribute__((no_sanitize("undefined"))) +#endif +void +Hash_SHA1CD::add(const void *buf, size_t len) +{ + SHA1DCUpdate(&ctx_, (const char *) buf, len); +} + +#if defined(__clang__) +__attribute__((no_sanitize("undefined"))) +#endif +size_t +Hash_SHA1CD::finish(uint8_t *digest) +{ + unsigned char fixed_digest[20]; + int res = SHA1DCFinal(fixed_digest, &ctx_); + if (res && digest) { + /* Show warning only if digest is non-null */ + RNP_LOG("Warning! SHA1 collision detected and mitigated."); + } + if (res) { + throw rnp_exception(RNP_ERROR_BAD_STATE); + } + if (digest) { + memcpy(digest, fixed_digest, 20); + } + return 20; +} + +} // namespace rnp diff --git a/comm/third_party/rnp/src/lib/crypto/hash_sha1cd.hpp b/comm/third_party/rnp/src/lib/crypto/hash_sha1cd.hpp new file mode 100644 index 0000000000..523bbe0e9c --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/hash_sha1cd.hpp @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2022 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CRYPTO_HASH_SHA1CD_HPP_ +#define CRYPTO_HASH_SHA1CD_HPP_ + +#include "hash.hpp" +#include "sha1cd/sha1.h" + +namespace rnp { +class Hash_SHA1CD : public Hash { + private: + SHA1_CTX ctx_; + + Hash_SHA1CD(); + Hash_SHA1CD(const Hash_SHA1CD &src); + + public: + virtual ~Hash_SHA1CD(); + + static std::unique_ptr<Hash_SHA1CD> create(); + std::unique_ptr<Hash> clone() const override; + + void add(const void *buf, size_t len) override; + size_t finish(uint8_t *digest = NULL) override; +}; + +} // namespace rnp +#endif diff --git a/comm/third_party/rnp/src/lib/crypto/mem.cpp b/comm/third_party/rnp/src/lib/crypto/mem.cpp new file mode 100644 index 0000000000..bf54aa6b81 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/mem.cpp @@ -0,0 +1,68 @@ +/*- + * Copyright (c) 2021 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <cstdio> +#include "mem.h" +#include "logging.h" +#include <botan/ffi.h> + +void +secure_clear(void *vp, size_t size) +{ + botan_scrub_mem(vp, size); +} + +namespace rnp { + +bool +hex_encode(const uint8_t *buf, size_t buf_len, char *hex, size_t hex_len, hex_format_t format) +{ + uint32_t flags = format == HEX_LOWERCASE ? BOTAN_FFI_HEX_LOWER_CASE : 0; + + if (hex_len < (buf_len * 2 + 1)) { + return false; + } + hex[buf_len * 2] = '\0'; + return botan_hex_encode(buf, buf_len, hex, flags) == 0; +} + +size_t +hex_decode(const char *hex, uint8_t *buf, size_t buf_len) +{ + size_t hexlen = strlen(hex); + + /* check for 0x prefix */ + if ((hexlen >= 2) && (hex[0] == '0') && ((hex[1] == 'x') || (hex[1] == 'X'))) { + hex += 2; + hexlen -= 2; + } + if (botan_hex_decode(hex, hexlen, buf, &buf_len) != 0) { + RNP_LOG("Hex decode failed on string: %s", hex); + return 0; + } + return buf_len; +} +} // namespace rnp
\ No newline at end of file diff --git a/comm/third_party/rnp/src/lib/crypto/mem.h b/comm/third_party/rnp/src/lib/crypto/mem.h new file mode 100644 index 0000000000..fe574da978 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/mem.h @@ -0,0 +1,158 @@ +/*- + * Copyright (c) 2021 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CRYPTO_MEM_H_ +#define CRYPTO_MEM_H_ + +#include "config.h" +#include <array> +#include <vector> +#if defined(CRYPTO_BACKEND_BOTAN) +#include <botan/secmem.h> +#include <botan/ffi.h> +#elif defined(CRYPTO_BACKEND_OPENSSL) +#include <openssl/crypto.h> +#endif + +namespace rnp { + +#if defined(CRYPTO_BACKEND_BOTAN) +template <typename T> using secure_vector = Botan::secure_vector<T>; +#elif defined(CRYPTO_BACKEND_OPENSSL) +template <typename T> class ossl_allocator { + public: + static_assert(std::is_integral<T>::value, "T must be integral type"); + + typedef T value_type; + typedef std::size_t size_type; + + ossl_allocator() noexcept = default; + ossl_allocator(const ossl_allocator &) noexcept = default; + ossl_allocator &operator=(const ossl_allocator &) noexcept = default; + ~ossl_allocator() noexcept = default; + + template <typename U> ossl_allocator(const ossl_allocator<U> &) noexcept + { + } + + T * + allocate(std::size_t n) + { + if (!n) { + return nullptr; + } + + /* attempt to use OpenSSL secure alloc */ + T *ptr = static_cast<T *>(OPENSSL_secure_zalloc(n * sizeof(T))); + if (ptr) { + return ptr; + } + /* fallback to std::alloc if failed */ + ptr = static_cast<T *>(std::calloc(n, sizeof(T))); + if (!ptr) + throw std::bad_alloc(); + return ptr; + } + + void + deallocate(T *p, std::size_t n) + { + if (!p) { + return; + } + if (CRYPTO_secure_allocated(p)) { + OPENSSL_secure_clear_free(p, n * sizeof(T)); + return; + } + OPENSSL_cleanse(p, n * sizeof(T)); + std::free(p); + } +}; + +template <typename T> using secure_vector = std::vector<T, ossl_allocator<T> >; +#else +#error Unsupported backend. +#endif + +template <typename T, std::size_t N> struct secure_array { + private: + static_assert(std::is_integral<T>::value, "T must be integer type"); + std::array<T, N> data_; + + public: + secure_array() : data_({0}) + { + } + + T * + data() + { + return &data_[0]; + } + + std::size_t + size() const + { + return data_.size(); + } + + T + operator[](size_t idx) const + { + return data_[idx]; + } + + T & + operator[](size_t idx) + { + return data_[idx]; + } + + ~secure_array() + { +#if defined(CRYPTO_BACKEND_BOTAN) + botan_scrub_mem(&data_[0], sizeof(data_)); +#elif defined(CRYPTO_BACKEND_OPENSSL) + OPENSSL_cleanse(&data_[0], sizeof(data_)); +#else +#error "Unsupported crypto backend." +#endif + } +}; + +typedef enum { HEX_LOWERCASE, HEX_UPPERCASE } hex_format_t; + +bool hex_encode(const uint8_t *buf, + size_t buf_len, + char * hex, + size_t hex_len, + hex_format_t format = HEX_UPPERCASE); +size_t hex_decode(const char *hex, uint8_t *buf, size_t buf_len); +} // namespace rnp + +void secure_clear(void *vp, size_t size); + +#endif // CRYPTO_MEM_H_ diff --git a/comm/third_party/rnp/src/lib/crypto/mem_ossl.cpp b/comm/third_party/rnp/src/lib/crypto/mem_ossl.cpp new file mode 100644 index 0000000000..e9d6a9373f --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/mem_ossl.cpp @@ -0,0 +1,113 @@ +/*- + * Copyright (c) 2021 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <cstdio> +#include <cstring> +#include "mem.h" +#include "logging.h" +#include <openssl/crypto.h> + +void +secure_clear(void *vp, size_t size) +{ + OPENSSL_cleanse(vp, size); +} + +namespace rnp { + +bool +hex_encode(const uint8_t *buf, size_t buf_len, char *hex, size_t hex_len, hex_format_t format) +{ + if (hex_len < (buf_len * 2 + 1)) { + return false; + } + static const char *hex_low = "0123456789abcdef"; + static const char *hex_up = "0123456789ABCDEF"; + const char * hex_ch = (format == HEX_LOWERCASE) ? hex_low : hex_up; + hex[buf_len * 2] = '\0'; + for (size_t i = 0; i < buf_len; i++) { + hex[i << 1] = hex_ch[buf[i] >> 4]; + hex[(i << 1) + 1] = hex_ch[buf[i] & 0xF]; + } + return true; +} + +static bool +hex_char_decode(const char hex, uint8_t &res) +{ + if ((hex >= '0') && (hex <= '9')) { + res = hex - '0'; + return true; + } + if (hex >= 'a' && hex <= 'f') { + res = hex + 10 - 'a'; + return true; + } + if (hex >= 'A' && hex <= 'F') { + res = hex + 10 - 'A'; + return true; + } + return false; +} + +size_t +hex_decode(const char *hex, uint8_t *buf, size_t buf_len) +{ + size_t hexlen = strlen(hex); + + /* check for 0x prefix */ + if ((hexlen >= 2) && (hex[0] == '0') && ((hex[1] == 'x') || (hex[1] == 'X'))) { + hex += 2; + hexlen -= 2; + } + const char *end = hex + hexlen; + uint8_t * buf_st = buf; + uint8_t * buf_en = buf + buf_len; + while (hex < end) { + /* skip whitespaces */ + if ((*hex < '0') && + ((*hex == ' ') || (*hex == '\t') || (*hex == '\r') || (*hex == '\n'))) { + hex++; + continue; + } + if (hexlen < 2) { + RNP_LOG("Invalid hex string length."); + return 0; + } + uint8_t lo, hi; + if (!hex_char_decode(*hex++, hi) || !hex_char_decode(*hex++, lo)) { + RNP_LOG("Hex decode failed on string: %s", hex); + return 0; + } + if (buf == buf_en) { + return 0; + } + *buf++ = (hi << 4) | lo; + } + return buf - buf_st; +} + +} // namespace rnp diff --git a/comm/third_party/rnp/src/lib/crypto/mpi.cpp b/comm/third_party/rnp/src/lib/crypto/mpi.cpp new file mode 100644 index 0000000000..4df7eea528 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/mpi.cpp @@ -0,0 +1,119 @@ +/*- + * Copyright (c) 2018 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <string.h> +#include <stdlib.h> +#include "mpi.h" +#include "mem.h" +#include "utils.h" + +size_t +mpi_bits(const pgp_mpi_t *val) +{ + size_t bits = 0; + size_t idx = 0; + uint8_t bt; + + for (idx = 0; (idx < val->len) && !val->mpi[idx]; idx++) + ; + + if (idx < val->len) { + for (bits = (val->len - idx - 1) << 3, bt = val->mpi[idx]; bt; bits++, bt = bt >> 1) + ; + } + + return bits; +} + +size_t +mpi_bytes(const pgp_mpi_t *val) +{ + return val->len; +} + +bool +mem2mpi(pgp_mpi_t *val, const void *mem, size_t len) +{ + if (len > sizeof(val->mpi)) { + return false; + } + + memcpy(val->mpi, mem, len); + val->len = len; + return true; +} + +void +mpi2mem(const pgp_mpi_t *val, void *mem) +{ + memcpy(mem, val->mpi, val->len); +} + +char * +mpi2hex(const pgp_mpi_t *val) +{ + static const char *hexes = "0123456789abcdef"; + char * out; + size_t len; + size_t idx = 0; + + len = mpi_bytes(val); + out = (char *) malloc(len * 2 + 1); + + if (!out) { + return out; + } + + for (size_t i = 0; i < len; i++) { + out[idx++] = hexes[val->mpi[i] >> 4]; + out[idx++] = hexes[val->mpi[i] & 0xf]; + } + out[idx] = '\0'; + return out; +} + +bool +mpi_equal(const pgp_mpi_t *val1, const pgp_mpi_t *val2) +{ + size_t idx1 = 0; + size_t idx2 = 0; + + for (idx1 = 0; (idx1 < val1->len) && !val1->mpi[idx1]; idx1++) + ; + + for (idx2 = 0; (idx2 < val2->len) && !val2->mpi[idx2]; idx2++) + ; + + return ((val1->len - idx1) == (val2->len - idx2) && + !memcmp(val1->mpi + idx1, val2->mpi + idx2, val1->len - idx1)); +} + +void +mpi_forget(pgp_mpi_t *val) +{ + secure_clear(val, sizeof(*val)); + val->len = 0; +} diff --git a/comm/third_party/rnp/src/lib/crypto/mpi.h b/comm/third_party/rnp/src/lib/crypto/mpi.h new file mode 100644 index 0000000000..f95aeea15f --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/mpi.h @@ -0,0 +1,58 @@ +/*- + * Copyright (c) 2018 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RNP_MPI_H_ +#define RNP_MPI_H_ + +#include <cstdint> +#include <cstdbool> +#include <cstddef> + +/* 16384 bits should be pretty enough for now */ +#define PGP_MPINT_BITS (16384) +#define PGP_MPINT_SIZE (PGP_MPINT_BITS >> 3) + +/** multi-precision integer, used in signatures and public/secret keys */ +typedef struct pgp_mpi_t { + uint8_t mpi[PGP_MPINT_SIZE]; + size_t len; +} pgp_mpi_t; + +bool mem2mpi(pgp_mpi_t *val, const void *mem, size_t len); + +void mpi2mem(const pgp_mpi_t *val, void *mem); + +char *mpi2hex(const pgp_mpi_t *val); + +size_t mpi_bits(const pgp_mpi_t *val); + +size_t mpi_bytes(const pgp_mpi_t *val); + +bool mpi_equal(const pgp_mpi_t *val1, const pgp_mpi_t *val2); + +void mpi_forget(pgp_mpi_t *val); + +#endif // MPI_H_ diff --git a/comm/third_party/rnp/src/lib/crypto/ossl_common.h b/comm/third_party/rnp/src/lib/crypto/ossl_common.h new file mode 100644 index 0000000000..b6b7067ce4 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/ossl_common.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2022, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RNP_OSSL_COMMON_H_ +#define RNP_OSSL_COMMON_H_ + +#include <string> +#include "config.h" +#include <openssl/err.h> + +inline const char * +ossl_latest_err() +{ + return ERR_error_string(ERR_peek_last_error(), NULL); +} + +#endif diff --git a/comm/third_party/rnp/src/lib/crypto/rng.cpp b/comm/third_party/rnp/src/lib/crypto/rng.cpp new file mode 100644 index 0000000000..bf5bfadc8a --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/rng.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2017-2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <assert.h> +#include <botan/ffi.h> +#include "rng.h" +#include "types.h" + +namespace rnp { +RNG::RNG(Type type) +{ + if (botan_rng_init(&botan_rng, type == Type::DRBG ? "user" : NULL)) { + throw rnp::rnp_exception(RNP_ERROR_RNG); + } +} + +RNG::~RNG() +{ + (void) botan_rng_destroy(botan_rng); +} + +void +RNG::get(uint8_t *data, size_t len) +{ + if (botan_rng_get(botan_rng, data, len)) { + // This should never happen + throw rnp::rnp_exception(RNP_ERROR_RNG); + } +} + +struct botan_rng_struct * +RNG::handle() +{ + return botan_rng; +} +} // namespace rnp diff --git a/comm/third_party/rnp/src/lib/crypto/rng.h b/comm/third_party/rnp/src/lib/crypto/rng.h new file mode 100644 index 0000000000..f452bd9edf --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/rng.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2017-2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RNP_RNG_H_ +#define RNP_RNG_H_ + +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> +#include "config.h" + +#ifdef CRYPTO_BACKEND_BOTAN +typedef struct botan_rng_struct *botan_rng_t; +#endif + +namespace rnp { +class RNG { + private: +#ifdef CRYPTO_BACKEND_BOTAN + struct botan_rng_struct *botan_rng; +#endif + public: + enum Type { DRBG, System }; + /** + * @brief Construct a new RNG object. + * Note: OpenSSL uses own global RNG, so this class is not needed there and left + * only for code-level compatibility. + * + * @param type indicates which random generator to initialize. + * Possible values for Botan backend: + * - DRBG will initialize HMAC_DRBG, this generator is initialized on-demand + * (when used for the first time) + * - SYSTEM will initialize /dev/(u)random + */ + RNG(Type type = Type::DRBG); + ~RNG(); + /** + * @brief Get randoom bytes. + * + * @param data buffer where data should be stored. Cannot be NULL. + * @param len number of bytes required. + */ + void get(uint8_t *data, size_t len); +#ifdef CRYPTO_BACKEND_BOTAN + /** + * @brief Returns internal handle to botan rng. Returned + * handle is always initialized. In case of + * internal error NULL is returned + */ + struct botan_rng_struct *handle(); +#endif +}; +} // namespace rnp + +#endif // RNP_RNG_H_ diff --git a/comm/third_party/rnp/src/lib/crypto/rng_ossl.cpp b/comm/third_party/rnp/src/lib/crypto/rng_ossl.cpp new file mode 100644 index 0000000000..4ebcc95dca --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/rng_ossl.cpp @@ -0,0 +1,48 @@ +/*- + * Copyright (c) 2021 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <assert.h> +#include <openssl/rand.h> +#include "rng.h" +#include "types.h" + +namespace rnp { +RNG::RNG(Type type) +{ +} + +RNG::~RNG() +{ +} + +void +RNG::get(uint8_t *data, size_t len) +{ + if (RAND_bytes(data, len) != 1) { + throw rnp::rnp_exception(RNP_ERROR_RNG); + } +} +} // namespace rnp diff --git a/comm/third_party/rnp/src/lib/crypto/rsa.cpp b/comm/third_party/rnp/src/lib/crypto/rsa.cpp new file mode 100644 index 0000000000..f7ddefe318 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/rsa.cpp @@ -0,0 +1,419 @@ +/*- + * Copyright (c) 2017-2022 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/*- + * Copyright (c) 2009 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Alistair Crooks (agc@NetBSD.org) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/* + * Copyright (c) 2005-2008 Nominet UK (www.nic.uk) + * All rights reserved. + * Contributors: Ben Laurie, Rachel Willmer. The Contributors have asserted + * their moral rights under the UK Copyright Design and Patents Act 1988 to + * be recorded as the authors of this copyright work. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. + * + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** \file + */ +#include <string> +#include <cstring> +#include <botan/ffi.h> +#include "hash_botan.hpp" +#include "crypto/rsa.h" +#include "config.h" +#include "utils.h" +#include "bn.h" + +rnp_result_t +rsa_validate_key(rnp::RNG *rng, const pgp_rsa_key_t *key, bool secret) +{ + bignum_t * n = NULL; + bignum_t * e = NULL; + bignum_t * p = NULL; + bignum_t * q = NULL; + botan_pubkey_t bpkey = NULL; + botan_privkey_t bskey = NULL; + rnp_result_t ret = RNP_ERROR_GENERIC; + + /* load and check public key part */ + if (!(n = mpi2bn(&key->n)) || !(e = mpi2bn(&key->e))) { + RNP_LOG("out of memory"); + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + + if (botan_pubkey_load_rsa(&bpkey, BN_HANDLE_PTR(n), BN_HANDLE_PTR(e)) != 0) { + goto done; + } + + if (botan_pubkey_check_key(bpkey, rng->handle(), 0)) { + goto done; + } + + if (!secret) { + ret = RNP_SUCCESS; + goto done; + } + + /* load and check secret key part */ + if (!(p = mpi2bn(&key->p)) || !(q = mpi2bn(&key->q))) { + RNP_LOG("out of memory"); + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + + /* p and q are reversed from normal usage in PGP */ + if (botan_privkey_load_rsa(&bskey, BN_HANDLE_PTR(q), BN_HANDLE_PTR(p), BN_HANDLE_PTR(e))) { + goto done; + } + + if (botan_privkey_check_key(bskey, rng->handle(), 0)) { + goto done; + } + ret = RNP_SUCCESS; +done: + botan_pubkey_destroy(bpkey); + botan_privkey_destroy(bskey); + bn_free(n); + bn_free(e); + bn_free(p); + bn_free(q); + return ret; +} + +static bool +rsa_load_public_key(botan_pubkey_t *bkey, const pgp_rsa_key_t *key) +{ + bignum_t *n = NULL; + bignum_t *e = NULL; + bool res = false; + + *bkey = NULL; + n = mpi2bn(&key->n); + e = mpi2bn(&key->e); + + if (!n || !e) { + RNP_LOG("out of memory"); + goto done; + } + + res = !botan_pubkey_load_rsa(bkey, BN_HANDLE_PTR(n), BN_HANDLE_PTR(e)); +done: + bn_free(n); + bn_free(e); + return res; +} + +static bool +rsa_load_secret_key(botan_privkey_t *bkey, const pgp_rsa_key_t *key) +{ + bignum_t *p = NULL; + bignum_t *q = NULL; + bignum_t *e = NULL; + bool res = false; + + *bkey = NULL; + p = mpi2bn(&key->p); + q = mpi2bn(&key->q); + e = mpi2bn(&key->e); + + if (!p || !q || !e) { + RNP_LOG("out of memory"); + goto done; + } + + /* p and q are reversed from normal usage in PGP */ + res = !botan_privkey_load_rsa(bkey, BN_HANDLE_PTR(q), BN_HANDLE_PTR(p), BN_HANDLE_PTR(e)); +done: + bn_free(p); + bn_free(q); + bn_free(e); + return res; +} + +rnp_result_t +rsa_encrypt_pkcs1(rnp::RNG * rng, + pgp_rsa_encrypted_t *out, + const uint8_t * in, + size_t in_len, + const pgp_rsa_key_t *key) +{ + rnp_result_t ret = RNP_ERROR_GENERIC; + botan_pubkey_t rsa_key = NULL; + botan_pk_op_encrypt_t enc_op = NULL; + + if (!rsa_load_public_key(&rsa_key, key)) { + RNP_LOG("failed to load key"); + return RNP_ERROR_OUT_OF_MEMORY; + } + + if (botan_pk_op_encrypt_create(&enc_op, rsa_key, "PKCS1v15", 0) != 0) { + goto done; + } + + out->m.len = sizeof(out->m.mpi); + if (botan_pk_op_encrypt(enc_op, rng->handle(), out->m.mpi, &out->m.len, in, in_len)) { + out->m.len = 0; + goto done; + } + ret = RNP_SUCCESS; +done: + botan_pk_op_encrypt_destroy(enc_op); + botan_pubkey_destroy(rsa_key); + return ret; +} + +rnp_result_t +rsa_verify_pkcs1(const pgp_rsa_signature_t *sig, + pgp_hash_alg_t hash_alg, + const uint8_t * hash, + size_t hash_len, + const pgp_rsa_key_t * key) +{ + char padding_name[64] = {0}; + botan_pubkey_t rsa_key = NULL; + botan_pk_op_verify_t verify_op = NULL; + rnp_result_t ret = RNP_ERROR_SIGNATURE_INVALID; + + if (!rsa_load_public_key(&rsa_key, key)) { + RNP_LOG("failed to load key"); + return RNP_ERROR_OUT_OF_MEMORY; + } + + snprintf(padding_name, + sizeof(padding_name), + "EMSA-PKCS1-v1_5(Raw,%s)", + rnp::Hash_Botan::name_backend(hash_alg)); + + if (botan_pk_op_verify_create(&verify_op, rsa_key, padding_name, 0) != 0) { + goto done; + } + + if (botan_pk_op_verify_update(verify_op, hash, hash_len) != 0) { + goto done; + } + + if (botan_pk_op_verify_finish(verify_op, sig->s.mpi, sig->s.len) != 0) { + goto done; + } + + ret = RNP_SUCCESS; +done: + botan_pk_op_verify_destroy(verify_op); + botan_pubkey_destroy(rsa_key); + return ret; +} + +rnp_result_t +rsa_sign_pkcs1(rnp::RNG * rng, + pgp_rsa_signature_t *sig, + pgp_hash_alg_t hash_alg, + const uint8_t * hash, + size_t hash_len, + const pgp_rsa_key_t *key) +{ + char padding_name[64] = {0}; + botan_privkey_t rsa_key; + botan_pk_op_sign_t sign_op; + rnp_result_t ret = RNP_ERROR_GENERIC; + + if (mpi_bytes(&key->q) == 0) { + RNP_LOG("private key not set"); + return ret; + } + + if (!rsa_load_secret_key(&rsa_key, key)) { + RNP_LOG("failed to load key"); + return RNP_ERROR_OUT_OF_MEMORY; + } + + snprintf(padding_name, + sizeof(padding_name), + "EMSA-PKCS1-v1_5(Raw,%s)", + rnp::Hash_Botan::name_backend(hash_alg)); + + if (botan_pk_op_sign_create(&sign_op, rsa_key, padding_name, 0) != 0) { + goto done; + } + + if (botan_pk_op_sign_update(sign_op, hash, hash_len)) { + goto done; + } + + sig->s.len = sizeof(sig->s.mpi); + if (botan_pk_op_sign_finish(sign_op, rng->handle(), sig->s.mpi, &sig->s.len)) { + goto done; + } + + ret = RNP_SUCCESS; +done: + botan_pk_op_sign_destroy(sign_op); + botan_privkey_destroy(rsa_key); + return ret; +} + +rnp_result_t +rsa_decrypt_pkcs1(rnp::RNG * rng, + uint8_t * out, + size_t * out_len, + const pgp_rsa_encrypted_t *in, + const pgp_rsa_key_t * key) +{ + botan_privkey_t rsa_key = NULL; + botan_pk_op_decrypt_t decrypt_op = NULL; + rnp_result_t ret = RNP_ERROR_GENERIC; + + if (mpi_bytes(&key->q) == 0) { + RNP_LOG("private key not set"); + return ret; + } + + if (!rsa_load_secret_key(&rsa_key, key)) { + RNP_LOG("failed to load key"); + return RNP_ERROR_OUT_OF_MEMORY; + } + + if (botan_pk_op_decrypt_create(&decrypt_op, rsa_key, "PKCS1v15", 0)) { + goto done; + } + + *out_len = PGP_MPINT_SIZE; + if (botan_pk_op_decrypt(decrypt_op, out, out_len, in->m.mpi, in->m.len)) { + goto done; + } + ret = RNP_SUCCESS; +done: + botan_privkey_destroy(rsa_key); + botan_pk_op_decrypt_destroy(decrypt_op); + return ret; +} + +rnp_result_t +rsa_generate(rnp::RNG *rng, pgp_rsa_key_t *key, size_t numbits) +{ + if ((numbits < 1024) || (numbits > PGP_MPINT_BITS)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + botan_privkey_t rsa_key = NULL; + rnp_result_t ret = RNP_ERROR_GENERIC; + int cmp; + bignum_t * n = bn_new(); + bignum_t * e = bn_new(); + bignum_t * p = bn_new(); + bignum_t * q = bn_new(); + bignum_t * d = bn_new(); + bignum_t * u = bn_new(); + + if (!n || !e || !p || !q || !d || !u) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto end; + } + + if (botan_privkey_create( + &rsa_key, "RSA", std::to_string(numbits).c_str(), rng->handle())) { + goto end; + } + + if (botan_privkey_check_key(rsa_key, rng->handle(), 1) != 0) { + goto end; + } + + if (botan_privkey_get_field(BN_HANDLE_PTR(n), rsa_key, "n") || + botan_privkey_get_field(BN_HANDLE_PTR(e), rsa_key, "e") || + botan_privkey_get_field(BN_HANDLE_PTR(d), rsa_key, "d") || + botan_privkey_get_field(BN_HANDLE_PTR(p), rsa_key, "p") || + botan_privkey_get_field(BN_HANDLE_PTR(q), rsa_key, "q")) { + goto end; + } + + /* RFC 4880, 5.5.3 tells that p < q. GnuPG relies on this. */ + (void) botan_mp_cmp(&cmp, BN_HANDLE_PTR(p), BN_HANDLE_PTR(q)); + if (cmp > 0) { + (void) botan_mp_swap(BN_HANDLE_PTR(p), BN_HANDLE_PTR(q)); + } + + if (botan_mp_mod_inverse(BN_HANDLE_PTR(u), BN_HANDLE_PTR(p), BN_HANDLE_PTR(q)) != 0) { + RNP_LOG("Error computing RSA u param"); + ret = RNP_ERROR_BAD_STATE; + goto end; + } + + bn2mpi(n, &key->n); + bn2mpi(e, &key->e); + bn2mpi(p, &key->p); + bn2mpi(q, &key->q); + bn2mpi(d, &key->d); + bn2mpi(u, &key->u); + + ret = RNP_SUCCESS; +end: + botan_privkey_destroy(rsa_key); + bn_free(n); + bn_free(e); + bn_free(p); + bn_free(q); + bn_free(d); + bn_free(u); + return ret; +} diff --git a/comm/third_party/rnp/src/lib/crypto/rsa.h b/comm/third_party/rnp/src/lib/crypto/rsa.h new file mode 100644 index 0000000000..6b1b615374 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/rsa.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2017, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * This code is originally derived from software contributed to + * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and + * carried further by Ribose Inc (https://www.ribose.com). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RNP_RSA_H_ +#define RNP_RSA_H_ + +#include <rnp/rnp_def.h> +#include <repgp/repgp_def.h> +#include "crypto/rng.h" +#include "crypto/mpi.h" + +typedef struct pgp_rsa_key_t { + pgp_mpi_t n; + pgp_mpi_t e; + /* secret mpis */ + pgp_mpi_t d; + pgp_mpi_t p; + pgp_mpi_t q; + pgp_mpi_t u; +} pgp_rsa_key_t; + +typedef struct pgp_rsa_signature_t { + pgp_mpi_t s; +} pgp_rsa_signature_t; + +typedef struct pgp_rsa_encrypted_t { + pgp_mpi_t m; +} pgp_rsa_encrypted_t; + +/* + * RSA encrypt/decrypt + */ + +rnp_result_t rsa_validate_key(rnp::RNG *rng, const pgp_rsa_key_t *key, bool secret); + +rnp_result_t rsa_generate(rnp::RNG *rng, pgp_rsa_key_t *key, size_t numbits); + +rnp_result_t rsa_encrypt_pkcs1(rnp::RNG * rng, + pgp_rsa_encrypted_t *out, + const uint8_t * in, + size_t in_len, + const pgp_rsa_key_t *key); + +rnp_result_t rsa_decrypt_pkcs1(rnp::RNG * rng, + uint8_t * out, + size_t * out_len, + const pgp_rsa_encrypted_t *in, + const pgp_rsa_key_t * key); + +rnp_result_t rsa_verify_pkcs1(const pgp_rsa_signature_t *sig, + pgp_hash_alg_t hash_alg, + const uint8_t * hash, + size_t hash_len, + const pgp_rsa_key_t * key); + +rnp_result_t rsa_sign_pkcs1(rnp::RNG * rng, + pgp_rsa_signature_t *sig, + pgp_hash_alg_t hash_alg, + const uint8_t * hash, + size_t hash_len, + const pgp_rsa_key_t *key); + +#endif diff --git a/comm/third_party/rnp/src/lib/crypto/rsa_ossl.cpp b/comm/third_party/rnp/src/lib/crypto/rsa_ossl.cpp new file mode 100644 index 0000000000..24cff296d9 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/rsa_ossl.cpp @@ -0,0 +1,629 @@ +/* + * Copyright (c) 2021-2022, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <string> +#include <cstring> +#include <cassert> +#include "crypto/rsa.h" +#include "config.h" +#include "utils.h" +#include "bn.h" +#include "ossl_common.h" +#include <openssl/rsa.h> +#include <openssl/err.h> +#include <openssl/evp.h> +#ifdef CRYPTO_BACKEND_OPENSSL3 +#include <openssl/param_build.h> +#include <openssl/core_names.h> +#endif +#include "hash_ossl.hpp" + +#ifndef CRYPTO_BACKEND_OPENSSL3 +static RSA * +rsa_load_public_key(const pgp_rsa_key_t *key) +{ + RSA * rsa = NULL; + bignum_t *n = mpi2bn(&key->n); + bignum_t *e = mpi2bn(&key->e); + + if (!n || !e) { + RNP_LOG("out of memory"); + goto done; + } + rsa = RSA_new(); + if (!rsa) { + RNP_LOG("Out of memory"); + goto done; + } + if (RSA_set0_key(rsa, n, e, NULL) != 1) { + RNP_LOG("Public key load error: %lu", ERR_peek_last_error()); + RSA_free(rsa); + rsa = NULL; + goto done; + } +done: + /* OpenSSL set0 function transfers ownership of bignums */ + if (!rsa) { + bn_free(n); + bn_free(e); + } + return rsa; +} + +static RSA * +rsa_load_secret_key(const pgp_rsa_key_t *key) +{ + RSA * rsa = NULL; + bignum_t *n = mpi2bn(&key->n); + bignum_t *e = mpi2bn(&key->e); + bignum_t *p = mpi2bn(&key->p); + bignum_t *q = mpi2bn(&key->q); + bignum_t *d = mpi2bn(&key->d); + + if (!n || !p || !q || !e || !d) { + RNP_LOG("out of memory"); + goto done; + } + + rsa = RSA_new(); + if (!rsa) { + RNP_LOG("Out of memory"); + goto done; + } + if (RSA_set0_key(rsa, n, e, d) != 1) { + RNP_LOG("Secret key load error: %lu", ERR_peek_last_error()); + RSA_free(rsa); + rsa = NULL; + goto done; + } + /* OpenSSL has p < q, as we do */ + if (RSA_set0_factors(rsa, p, q) != 1) { + RNP_LOG("Factors load error: %lu", ERR_peek_last_error()); + RSA_free(rsa); + rsa = NULL; + goto done; + } +done: + /* OpenSSL set0 function transfers ownership of bignums */ + if (!rsa) { + bn_free(n); + bn_free(p); + bn_free(q); + bn_free(e); + bn_free(d); + } + return rsa; +} + +static EVP_PKEY_CTX * +rsa_init_context(const pgp_rsa_key_t *key, bool secret) +{ + EVP_PKEY *evpkey = EVP_PKEY_new(); + if (!evpkey) { + RNP_LOG("allocation failed"); + return NULL; + } + EVP_PKEY_CTX *ctx = NULL; + RSA * rsakey = secret ? rsa_load_secret_key(key) : rsa_load_public_key(key); + if (!rsakey) { + goto done; + } + if (EVP_PKEY_set1_RSA(evpkey, rsakey) <= 0) { + RNP_LOG("Failed to set key: %lu", ERR_peek_last_error()); + goto done; + } + ctx = EVP_PKEY_CTX_new(evpkey, NULL); + if (!ctx) { + RNP_LOG("Context allocation failed: %lu", ERR_peek_last_error()); + } +done: + RSA_free(rsakey); + EVP_PKEY_free(evpkey); + return ctx; +} +#else +static OSSL_PARAM * +rsa_bld_params(const pgp_rsa_key_t *key, bool secret) +{ + OSSL_PARAM * params = NULL; + OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new(); + bignum_t * n = mpi2bn(&key->n); + bignum_t * e = mpi2bn(&key->e); + bignum_t * d = NULL; + bignum_t * p = NULL; + bignum_t * q = NULL; + bignum_t * u = NULL; + BN_CTX * bnctx = NULL; + + if (!n || !e || !bld) { + RNP_LOG("Out of memory"); + goto done; + } + + if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_N, n) || + !OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_E, e)) { + RNP_LOG("Failed to push RSA params."); + goto done; + } + if (secret) { + d = mpi2bn(&key->d); + /* As we have u = p^-1 mod q, and qInv = q^-1 mod p, we need to replace one with + * another */ + p = mpi2bn(&key->q); + q = mpi2bn(&key->p); + u = mpi2bn(&key->u); + if (!d || !p || !q || !u) { + goto done; + } + /* We need to calculate exponents manually */ + bnctx = BN_CTX_new(); + if (!bnctx) { + RNP_LOG("Failed to allocate BN_CTX."); + goto done; + } + bignum_t *p1 = BN_CTX_get(bnctx); + bignum_t *q1 = BN_CTX_get(bnctx); + bignum_t *dp = BN_CTX_get(bnctx); + bignum_t *dq = BN_CTX_get(bnctx); + if (!BN_copy(p1, p) || !BN_sub_word(p1, 1) || !BN_copy(q1, q) || !BN_sub_word(q1, 1) || + !BN_mod(dp, d, p1, bnctx) || !BN_mod(dq, d, q1, bnctx)) { + RNP_LOG("Failed to calculate dP or dQ."); + } + /* Push params */ + if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_D, d) || + !OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_FACTOR1, p) || + !OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_FACTOR2, q) || + !OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_EXPONENT1, dp) || + !OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_EXPONENT2, dq) || + !OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_COEFFICIENT1, u)) { + RNP_LOG("Failed to push RSA secret params."); + goto done; + } + } + params = OSSL_PARAM_BLD_to_param(bld); + if (!params) { + RNP_LOG("Failed to build RSA params: %s.", ossl_latest_err()); + } +done: + bn_free(n); + bn_free(e); + bn_free(d); + bn_free(p); + bn_free(q); + bn_free(u); + BN_CTX_free(bnctx); + OSSL_PARAM_BLD_free(bld); + return params; +} + +static EVP_PKEY * +rsa_load_key(const pgp_rsa_key_t *key, bool secret) +{ + /* Build params */ + OSSL_PARAM *params = rsa_bld_params(key, secret); + if (!params) { + return NULL; + } + /* Create context for key creation */ + EVP_PKEY * res = NULL; + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + if (!ctx) { + RNP_LOG("Context allocation failed: %s", ossl_latest_err()); + goto done; + } + /* Create key */ + if (EVP_PKEY_fromdata_init(ctx) <= 0) { + RNP_LOG("Failed to initialize key creation: %s", ossl_latest_err()); + goto done; + } + if (EVP_PKEY_fromdata( + ctx, &res, secret ? EVP_PKEY_KEYPAIR : EVP_PKEY_PUBLIC_KEY, params) <= 0) { + RNP_LOG("Failed to create RSA key: %s", ossl_latest_err()); + } +done: + EVP_PKEY_CTX_free(ctx); + OSSL_PARAM_free(params); + return res; +} + +static EVP_PKEY_CTX * +rsa_init_context(const pgp_rsa_key_t *key, bool secret) +{ + EVP_PKEY *pkey = rsa_load_key(key, secret); + if (!pkey) { + return NULL; + } + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey, NULL); + if (!ctx) { + RNP_LOG("Context allocation failed: %s", ossl_latest_err()); + } + EVP_PKEY_free(pkey); + return ctx; +} +#endif + +rnp_result_t +rsa_validate_key(rnp::RNG *rng, const pgp_rsa_key_t *key, bool secret) +{ +#ifdef CRYPTO_BACKEND_OPENSSL3 + EVP_PKEY_CTX *ctx = rsa_init_context(key, secret); + if (!ctx) { + RNP_LOG("Failed to init context: %s", ossl_latest_err()); + return RNP_ERROR_GENERIC; + } + int res = secret ? EVP_PKEY_pairwise_check(ctx) : EVP_PKEY_public_check(ctx); + if (res <= 0) { + RNP_LOG("Key validation error: %s", ossl_latest_err()); + } + EVP_PKEY_CTX_free(ctx); + return res > 0 ? RNP_SUCCESS : RNP_ERROR_GENERIC; +#else + if (secret) { + EVP_PKEY_CTX *ctx = rsa_init_context(key, secret); + if (!ctx) { + RNP_LOG("Failed to init context: %s", ossl_latest_err()); + return RNP_ERROR_GENERIC; + } + int res = EVP_PKEY_check(ctx); + if (res <= 0) { + RNP_LOG("Key validation error: %s", ossl_latest_err()); + } + EVP_PKEY_CTX_free(ctx); + return res > 0 ? RNP_SUCCESS : RNP_ERROR_GENERIC; + } + + /* OpenSSL 1.1.1 doesn't have RSA public key check function, so let's do some checks */ + rnp_result_t ret = RNP_ERROR_GENERIC; + bignum_t * n = mpi2bn(&key->n); + bignum_t * e = mpi2bn(&key->e); + if (!n || !e) { + RNP_LOG("out of memory"); + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + if ((BN_num_bits(n) < 512) || !BN_is_odd(n) || (BN_num_bits(e) < 2) || !BN_is_odd(e)) { + goto done; + } + ret = RNP_SUCCESS; +done: + bn_free(n); + bn_free(e); + return ret; +#endif +} + +static bool +rsa_setup_context(EVP_PKEY_CTX *ctx) +{ + if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0) { + RNP_LOG("Failed to set padding: %lu", ERR_peek_last_error()); + return false; + } + return true; +} + +static const uint8_t PKCS1_SHA1_ENCODING[15] = { + 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14}; + +static bool +rsa_setup_signature_hash(EVP_PKEY_CTX * ctx, + pgp_hash_alg_t hash_alg, + const uint8_t *&enc, + size_t & enc_size) +{ + const char *hash_name = rnp::Hash_OpenSSL::name(hash_alg); + if (!hash_name) { + RNP_LOG("Unknown hash: %d", (int) hash_alg); + return false; + } + const EVP_MD *hash_tp = EVP_get_digestbyname(hash_name); + if (!hash_tp) { + RNP_LOG("Error creating hash object for '%s'", hash_name); + return false; + } + if (EVP_PKEY_CTX_set_signature_md(ctx, hash_tp) <= 0) { + if ((hash_alg != PGP_HASH_SHA1)) { + RNP_LOG("Failed to set digest %s: %s", hash_name, ossl_latest_err()); + return false; + } + enc = &PKCS1_SHA1_ENCODING[0]; + enc_size = sizeof(PKCS1_SHA1_ENCODING); + } else { + enc = NULL; + enc_size = 0; + } + return true; +} + +rnp_result_t +rsa_encrypt_pkcs1(rnp::RNG * rng, + pgp_rsa_encrypted_t *out, + const uint8_t * in, + size_t in_len, + const pgp_rsa_key_t *key) +{ + rnp_result_t ret = RNP_ERROR_GENERIC; + EVP_PKEY_CTX *ctx = rsa_init_context(key, false); + if (!ctx) { + return ret; + } + if (EVP_PKEY_encrypt_init(ctx) <= 0) { + RNP_LOG("Failed to initialize encryption: %lu", ERR_peek_last_error()); + goto done; + } + if (!rsa_setup_context(ctx)) { + goto done; + } + out->m.len = sizeof(out->m.mpi); + if (EVP_PKEY_encrypt(ctx, out->m.mpi, &out->m.len, in, in_len) <= 0) { + RNP_LOG("Encryption failed: %lu", ERR_peek_last_error()); + out->m.len = 0; + goto done; + } + ret = RNP_SUCCESS; +done: + EVP_PKEY_CTX_free(ctx); + return ret; +} + +rnp_result_t +rsa_verify_pkcs1(const pgp_rsa_signature_t *sig, + pgp_hash_alg_t hash_alg, + const uint8_t * hash, + size_t hash_len, + const pgp_rsa_key_t * key) +{ + rnp_result_t ret = RNP_ERROR_SIGNATURE_INVALID; + EVP_PKEY_CTX *ctx = rsa_init_context(key, false); + if (!ctx) { + return ret; + } + const uint8_t *hash_enc = NULL; + size_t hash_enc_size = 0; + uint8_t hash_enc_buf[PGP_MAX_HASH_SIZE + 32] = {0}; + assert(hash_len + hash_enc_size <= sizeof(hash_enc_buf)); + + if (EVP_PKEY_verify_init(ctx) <= 0) { + RNP_LOG("Failed to initialize verification: %lu", ERR_peek_last_error()); + goto done; + } + if (!rsa_setup_context(ctx) || + !rsa_setup_signature_hash(ctx, hash_alg, hash_enc, hash_enc_size)) { + goto done; + } + /* Check whether we need to workaround on unsupported SHA1 for RSA signature verification + */ + if (hash_enc_size) { + memcpy(hash_enc_buf, hash_enc, hash_enc_size); + memcpy(&hash_enc_buf[hash_enc_size], hash, hash_len); + hash = hash_enc_buf; + hash_len += hash_enc_size; + } + int res; + if (sig->s.len < key->n.len) { + /* OpenSSL doesn't like signatures smaller then N */ + pgp_mpi_t sn; + sn.len = key->n.len; + size_t diff = key->n.len - sig->s.len; + memset(sn.mpi, 0, diff); + memcpy(&sn.mpi[diff], sig->s.mpi, sig->s.len); + res = EVP_PKEY_verify(ctx, sn.mpi, sn.len, hash, hash_len); + } else { + res = EVP_PKEY_verify(ctx, sig->s.mpi, sig->s.len, hash, hash_len); + } + if (res > 0) { + ret = RNP_SUCCESS; + } else { + RNP_LOG("RSA verification failure: %s", ossl_latest_err()); + } +done: + EVP_PKEY_CTX_free(ctx); + return ret; +} + +rnp_result_t +rsa_sign_pkcs1(rnp::RNG * rng, + pgp_rsa_signature_t *sig, + pgp_hash_alg_t hash_alg, + const uint8_t * hash, + size_t hash_len, + const pgp_rsa_key_t *key) +{ + rnp_result_t ret = RNP_ERROR_GENERIC; + if (mpi_bytes(&key->q) == 0) { + RNP_LOG("private key not set"); + return ret; + } + EVP_PKEY_CTX *ctx = rsa_init_context(key, true); + if (!ctx) { + return ret; + } + const uint8_t *hash_enc = NULL; + size_t hash_enc_size = 0; + uint8_t hash_enc_buf[PGP_MAX_HASH_SIZE + 32] = {0}; + assert(hash_len + hash_enc_size <= sizeof(hash_enc_buf)); + if (EVP_PKEY_sign_init(ctx) <= 0) { + RNP_LOG("Failed to initialize signing: %lu", ERR_peek_last_error()); + goto done; + } + if (!rsa_setup_context(ctx) || + !rsa_setup_signature_hash(ctx, hash_alg, hash_enc, hash_enc_size)) { + goto done; + } + /* Check whether we need to workaround on unsupported SHA1 for RSA signature verification + */ + if (hash_enc_size) { + memcpy(hash_enc_buf, hash_enc, hash_enc_size); + memcpy(&hash_enc_buf[hash_enc_size], hash, hash_len); + hash = hash_enc_buf; + hash_len += hash_enc_size; + } + sig->s.len = PGP_MPINT_SIZE; + if (EVP_PKEY_sign(ctx, sig->s.mpi, &sig->s.len, hash, hash_len) <= 0) { + RNP_LOG("Encryption failed: %lu", ERR_peek_last_error()); + sig->s.len = 0; + goto done; + } + ret = RNP_SUCCESS; +done: + EVP_PKEY_CTX_free(ctx); + return ret; +} + +rnp_result_t +rsa_decrypt_pkcs1(rnp::RNG * rng, + uint8_t * out, + size_t * out_len, + const pgp_rsa_encrypted_t *in, + const pgp_rsa_key_t * key) +{ + rnp_result_t ret = RNP_ERROR_GENERIC; + if (mpi_bytes(&key->q) == 0) { + RNP_LOG("private key not set"); + return ret; + } + EVP_PKEY_CTX *ctx = rsa_init_context(key, true); + if (!ctx) { + return ret; + } + if (EVP_PKEY_decrypt_init(ctx) <= 0) { + RNP_LOG("Failed to initialize encryption: %lu", ERR_peek_last_error()); + goto done; + } + if (!rsa_setup_context(ctx)) { + goto done; + } + *out_len = PGP_MPINT_SIZE; + if (EVP_PKEY_decrypt(ctx, out, out_len, in->m.mpi, in->m.len) <= 0) { + RNP_LOG("Encryption failed: %lu", ERR_peek_last_error()); + *out_len = 0; + goto done; + } + ret = RNP_SUCCESS; +done: + EVP_PKEY_CTX_free(ctx); + return ret; +} + +rnp_result_t +rsa_generate(rnp::RNG *rng, pgp_rsa_key_t *key, size_t numbits) +{ + if ((numbits < 1024) || (numbits > PGP_MPINT_BITS)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + rnp_result_t ret = RNP_ERROR_GENERIC; + const RSA * rsa = NULL; + EVP_PKEY * pkey = NULL; + EVP_PKEY_CTX * ctx = NULL; + const bignum_t *u = NULL; + BN_CTX * bnctx = NULL; + + ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + if (!ctx) { + RNP_LOG("Failed to create ctx: %lu", ERR_peek_last_error()); + return ret; + } + if (EVP_PKEY_keygen_init(ctx) <= 0) { + RNP_LOG("Failed to init keygen: %lu", ERR_peek_last_error()); + goto done; + } + if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, numbits) <= 0) { + RNP_LOG("Failed to set rsa bits: %lu", ERR_peek_last_error()); + goto done; + } + if (EVP_PKEY_keygen(ctx, &pkey) <= 0) { + RNP_LOG("RSA keygen failed: %lu", ERR_peek_last_error()); + goto done; + } + rsa = EVP_PKEY_get0_RSA(pkey); + if (!rsa) { + RNP_LOG("Failed to retrieve RSA key: %lu", ERR_peek_last_error()); + goto done; + } + if (RSA_check_key(rsa) != 1) { + RNP_LOG("Key validation error: %lu", ERR_peek_last_error()); + goto done; + } + + const bignum_t *n; + const bignum_t *e; + const bignum_t *p; + const bignum_t *q; + const bignum_t *d; + n = RSA_get0_n(rsa); + e = RSA_get0_e(rsa); + d = RSA_get0_d(rsa); + p = RSA_get0_p(rsa); + q = RSA_get0_q(rsa); + if (!n || !e || !d || !p || !q) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + /* OpenSSL doesn't care whether p < q */ + if (BN_cmp(p, q) > 0) { + /* In this case we have u, as iqmp is inverse of q mod p, and we exchange them */ + const bignum_t *tmp = p; + p = q; + q = tmp; + u = RSA_get0_iqmp(rsa); + } else { + /* we need to calculate u, since we need inverse of p mod q, while OpenSSL has inverse + * of q mod p, and doesn't care of p < q */ + bnctx = BN_CTX_new(); + if (!bnctx) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + BN_CTX_start(bnctx); + bignum_t *nu = BN_CTX_get(bnctx); + bignum_t *nq = BN_CTX_get(bnctx); + if (!nu || !nq) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + BN_with_flags(nq, q, BN_FLG_CONSTTIME); + /* calculate inverse of p mod q */ + if (!BN_mod_inverse(nu, p, nq, bnctx)) { + RNP_LOG("Failed to calculate u"); + ret = RNP_ERROR_BAD_STATE; + goto done; + } + u = nu; + } + bn2mpi(n, &key->n); + bn2mpi(e, &key->e); + bn2mpi(p, &key->p); + bn2mpi(q, &key->q); + bn2mpi(d, &key->d); + bn2mpi(u, &key->u); + ret = RNP_SUCCESS; +done: + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(pkey); + BN_CTX_free(bnctx); + return ret; +} diff --git a/comm/third_party/rnp/src/lib/crypto/s2k.cpp b/comm/third_party/rnp/src/lib/crypto/s2k.cpp new file mode 100644 index 0000000000..ede7965dda --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/s2k.cpp @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2017-2022 [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * This code is originally derived from software contributed to + * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and + * carried further by Ribose Inc (https://www.ribose.com). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdio.h> +#include "config.h" +#ifndef _MSC_VER +#include <sys/time.h> +#else +#include "uniwin.h" +#endif + +#include "crypto/s2k.h" +#include "defaults.h" +#include "rnp.h" +#include "types.h" +#include "utils.h" +#ifdef CRYPTO_BACKEND_BOTAN +#include <botan/ffi.h> +#include "hash_botan.hpp" +#endif + +bool +pgp_s2k_derive_key(pgp_s2k_t *s2k, const char *password, uint8_t *key, int keysize) +{ + uint8_t *saltptr = NULL; + unsigned iterations = 1; + + switch (s2k->specifier) { + case PGP_S2KS_SIMPLE: + break; + case PGP_S2KS_SALTED: + saltptr = s2k->salt; + break; + case PGP_S2KS_ITERATED_AND_SALTED: + saltptr = s2k->salt; + if (s2k->iterations < 256) { + iterations = pgp_s2k_decode_iterations(s2k->iterations); + } else { + iterations = s2k->iterations; + } + break; + default: + return false; + } + + if (pgp_s2k_iterated(s2k->hash_alg, key, keysize, password, saltptr, iterations)) { + RNP_LOG("s2k failed"); + return false; + } + + return true; +} + +#ifdef CRYPTO_BACKEND_BOTAN +int +pgp_s2k_iterated(pgp_hash_alg_t alg, + uint8_t * out, + size_t output_len, + const char * password, + const uint8_t *salt, + size_t iterations) +{ + char s2k_algo_str[128]; + snprintf(s2k_algo_str, + sizeof(s2k_algo_str), + "OpenPGP-S2K(%s)", + rnp::Hash_Botan::name_backend(alg)); + + return botan_pwdhash(s2k_algo_str, + iterations, + 0, + 0, + out, + output_len, + password, + 0, + salt, + salt ? PGP_SALT_SIZE : 0); +} +#endif + +size_t +pgp_s2k_decode_iterations(uint8_t c) +{ + // See RFC 4880 section 3.7.1.3 + return (16 + (c & 0x0F)) << ((c >> 4) + 6); +} + +size_t +pgp_s2k_round_iterations(size_t iterations) +{ + return pgp_s2k_decode_iterations(pgp_s2k_encode_iterations(iterations)); +} + +uint8_t +pgp_s2k_encode_iterations(size_t iterations) +{ + /* For compatibility, when an S2K specifier is used, the special value + * 254 or 255 is stored in the position where the hash algorithm octet + * would have been in the old data structure. This is then followed + * immediately by a one-octet algorithm identifier, and then by the S2K + * specifier as encoded above. + * 0: secret data is unencrypted (no password) + * 255 or 254: followed by algorithm octet and S2K specifier + * Cipher alg: use Simple S2K algorithm using MD5 hash + * For more info refer to rfc 4880 section 3.7.2.1. + */ + for (uint16_t c = 0; c < 256; ++c) { + // This could be a binary search + if (pgp_s2k_decode_iterations(c) >= iterations) { + return c; + } + } + return 255; +} + +/// Should this function be elsewhere? +static uint64_t +get_timestamp_usec() +{ +#ifndef _MSC_VER + // TODO: Consider clock_gettime + struct timeval tv; + ::gettimeofday(&tv, NULL); + return (static_cast<uint64_t>(tv.tv_sec) * 1000000) + static_cast<uint64_t>(tv.tv_usec); +#else + return GetTickCount64() * 1000; +#endif +} + +size_t +pgp_s2k_compute_iters(pgp_hash_alg_t alg, size_t desired_msec, size_t trial_msec) +{ + if (desired_msec == 0) { + desired_msec = DEFAULT_S2K_MSEC; + } + if (trial_msec == 0) { + trial_msec = DEFAULT_S2K_TUNE_MSEC; + } + + // number of iterations to estimate the number of iterations + // (sorry, cannot tell it better) + const uint8_t NUM_ITERATIONS = 16; + uint64_t duration = 0; + size_t bytes = 0; + try { + for (uint8_t i = 0; i < NUM_ITERATIONS; i++) { + uint64_t start = get_timestamp_usec(); + uint64_t end = start; + auto hash = rnp::Hash::create(alg); + uint8_t buf[8192] = {0}; + while (end - start < trial_msec * 1000ull) { + hash->add(buf, sizeof(buf)); + bytes += sizeof(buf); + end = get_timestamp_usec(); + } + hash->finish(buf); + duration += (end - start); + } + } catch (const std::exception &e) { + RNP_LOG("Failed to hash data: %s", e.what()); + return 0; + } + + const uint8_t MIN_ITERS = 96; + if (duration == 0) { + return pgp_s2k_decode_iterations(MIN_ITERS); + } + + const double bytes_per_usec = static_cast<double>(bytes) / duration; + const double desired_usec = desired_msec * 1000.0; + const double bytes_for_target = bytes_per_usec * desired_usec; + const uint8_t iters = pgp_s2k_encode_iterations(bytes_for_target); + + return pgp_s2k_decode_iterations((iters > MIN_ITERS) ? iters : MIN_ITERS); +} diff --git a/comm/third_party/rnp/src/lib/crypto/s2k.h b/comm/third_party/rnp/src/lib/crypto/s2k.h new file mode 100644 index 0000000000..c67a77321c --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/s2k.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2017, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * This code is originally derived from software contributed to + * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and + * carried further by Ribose Inc (https://www.ribose.com). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RNP_S2K_H_ +#define RNP_S2K_H_ + +#include <cstdint> +#include "repgp/repgp_def.h" + +typedef struct pgp_s2k_t pgp_s2k_t; + +int pgp_s2k_iterated(pgp_hash_alg_t alg, + uint8_t * out, + size_t output_len, + const char * password, + const uint8_t *salt, + size_t iterations); + +size_t pgp_s2k_decode_iterations(uint8_t encoded_iter); + +uint8_t pgp_s2k_encode_iterations(size_t iterations); + +// Round iterations to nearest representable value +size_t pgp_s2k_round_iterations(size_t iterations); + +size_t pgp_s2k_compute_iters(pgp_hash_alg_t alg, size_t desired_msec, size_t trial_msec); + +/** @brief Derive key from password using the information stored in s2k structure + * @param s2k pointer to s2k structure, filled according to RFC 4880. + * Iterations field may contain encoded ( < 256) or decoded ( > 256) value. + * @param password NULL-terminated password + * @param key buffer to store the derived key, must have at least keysize bytes + * @param keysize number of bytes in the key. + * @return true on success or false otherwise + */ +bool pgp_s2k_derive_key(pgp_s2k_t *s2k, const char *password, uint8_t *key, int keysize); + +#endif diff --git a/comm/third_party/rnp/src/lib/crypto/s2k_ossl.cpp b/comm/third_party/rnp/src/lib/crypto/s2k_ossl.cpp new file mode 100644 index 0000000000..acf1ca9d8c --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/s2k_ossl.cpp @@ -0,0 +1,97 @@ +/*- + * Copyright (c) 2021 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <cstdint> +#include <vector> +#include <algorithm> +#include <openssl/evp.h> +#include "hash.hpp" +#include "s2k.h" +#include "mem.h" +#include "logging.h" + +int +pgp_s2k_iterated(pgp_hash_alg_t alg, + uint8_t * out, + size_t output_len, + const char * password, + const uint8_t *salt, + size_t iterations) +{ + if ((iterations > 1) && !salt) { + RNP_LOG("Iterated S2K mus be salted as well."); + return 1; + } + size_t hash_len = rnp::Hash::size(alg); + if (!hash_len) { + RNP_LOG("Unknown digest: %d", (int) alg); + return 1; + } + try { + size_t pswd_len = strlen(password); + size_t salt_len = salt ? PGP_SALT_SIZE : 0; + + rnp::secure_vector<uint8_t> data(salt_len + pswd_len); + if (salt_len) { + memcpy(data.data(), salt, PGP_SALT_SIZE); + } + memcpy(data.data() + salt_len, password, pswd_len); + size_t zeroes = 0; + + while (output_len) { + /* create hash context */ + auto hash = rnp::Hash::create(alg); + /* add leading zeroes */ + for (size_t z = 0; z < zeroes; z++) { + uint8_t zero = 0; + hash->add(&zero, 1); + } + if (!data.empty()) { + /* if iteration is 1 then still hash the whole data chunk */ + size_t left = std::max(data.size(), iterations); + while (left) { + size_t to_hash = std::min(left, data.size()); + hash->add(data.data(), to_hash); + left -= to_hash; + } + } + rnp::secure_vector<uint8_t> dgst(hash_len); + size_t out_cpy = std::min(dgst.size(), output_len); + if (hash->finish(dgst.data()) != dgst.size()) { + RNP_LOG("Unexpected digest size."); + return 1; + } + memcpy(out, dgst.data(), out_cpy); + output_len -= out_cpy; + out += out_cpy; + zeroes++; + } + return 0; + } catch (const std::exception &e) { + RNP_LOG("s2k failed: %s", e.what()); + return 1; + } +} diff --git a/comm/third_party/rnp/src/lib/crypto/sha1cd/sha1.c b/comm/third_party/rnp/src/lib/crypto/sha1cd/sha1.c new file mode 100644 index 0000000000..d90bc418f8 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/sha1cd/sha1.c @@ -0,0 +1,2162 @@ +/*** + * Copyright 2017 Marc Stevens <marc@marc-stevens.nl>, Dan Shumow (danshu@microsoft.com) + * Distributed under the MIT Software License. + * See accompanying file LICENSE.txt or copy at + * https://opensource.org/licenses/MIT + ***/ + +#ifndef SHA1DC_NO_STANDARD_INCLUDES +#include <string.h> +#include <memory.h> +#include <stdio.h> +#include <stdlib.h> +#ifdef __unix__ +#include <sys/types.h> /* make sure macros like _BIG_ENDIAN visible */ +#endif +#endif + +#ifdef SHA1DC_CUSTOM_INCLUDE_SHA1_C +#include SHA1DC_CUSTOM_INCLUDE_SHA1_C +#endif + +#ifndef SHA1DC_INIT_SAFE_HASH_DEFAULT +#define SHA1DC_INIT_SAFE_HASH_DEFAULT 1 +#endif + +#include "sha1.h" +#include "ubc_check.h" + +#if (defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || \ + defined(i386) || defined(__i386) || defined(__i386__) || defined(__i486__) || \ + defined(__i586__) || defined(__i686__) || defined(_M_IX86) || defined(__X86__) || \ + defined(_X86_) || defined(__THW_INTEL__) || defined(__I86__) || defined(__INTEL__) || \ + defined(__386) || defined(_M_X64) || defined(_M_AMD64)) +#define SHA1DC_ON_INTEL_LIKE_PROCESSOR +#endif + +/* + Because Little-Endian architectures are most common, + we only set SHA1DC_BIGENDIAN if one of these conditions is met. + Note that all MSFT platforms are little endian, + so none of these will be defined under the MSC compiler. + If you are compiling on a big endian platform and your compiler does not define one of + these, you will have to add whatever macros your tool chain defines to indicate + Big-Endianness. + */ + +#if defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) +/* + * Should detect Big Endian under GCC since at least 4.6.0 (gcc svn + * rev #165881). See + * https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html + * + * This also works under clang since 3.2, it copied the GCC-ism. See + * clang.git's 3b198a97d2 ("Preprocessor: add __BYTE_ORDER__ + * predefined macro", 2012-07-27) + */ +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#define SHA1DC_BIGENDIAN +#endif + +/* Not under GCC-alike */ +#elif defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) +/* + * Should detect Big Endian under glibc.git since 14245eb70e ("entered + * into RCS", 1992-11-25). Defined in <endian.h> which will have been + * brought in by standard headers. See glibc.git and + * https://sourceforge.net/p/predef/wiki/Endianness/ + */ +#if __BYTE_ORDER == __BIG_ENDIAN +#define SHA1DC_BIGENDIAN +#endif + +/* Not under GCC-alike or glibc */ +#elif defined(_BYTE_ORDER) && defined(_BIG_ENDIAN) && defined(_LITTLE_ENDIAN) +/* + * *BSD and newlib (embedded linux, cygwin, etc). + * the defined(_BIG_ENDIAN) && defined(_LITTLE_ENDIAN) part prevents + * this condition from matching with Solaris/sparc. + * (Solaris defines only one endian macro) + */ +#if _BYTE_ORDER == _BIG_ENDIAN +#define SHA1DC_BIGENDIAN +#endif + +/* Not under GCC-alike or glibc or *BSD or newlib */ +#elif (defined(__ARMEB__) || defined(__THUMBEB__) || defined(__AARCH64EB__) || \ + defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(__sparc)) +/* + * Should define Big Endian for a whitelist of known processors. See + * https://sourceforge.net/p/predef/wiki/Endianness/ and + * http://www.oracle.com/technetwork/server-storage/solaris/portingtosolaris-138514.html + */ +#define SHA1DC_BIGENDIAN + +/* Not under GCC-alike or glibc or *BSD or newlib or <processor whitelist> */ +#elif (defined(_AIX) || defined(__hpux)) + +/* + * Defines Big Endian on a whitelist of OSs that are known to be Big + * Endian-only. See + * https://public-inbox.org/git/93056823-2740-d072-1ebd-46b440b33d7e@felt.demon.nl/ + */ +#define SHA1DC_BIGENDIAN + +/* Not under GCC-alike or glibc or *BSD or newlib or <processor whitelist> or <os whitelist> */ +#elif defined(SHA1DC_ON_INTEL_LIKE_PROCESSOR) +/* + * As a last resort before we do anything else we're not 100% sure + * about below, we blacklist specific processors here. We could add + * more, see e.g. https://wiki.debian.org/ArchitectureSpecificsMemo + */ +#else /* Not under GCC-alike or glibc or *BSD or newlib or <processor whitelist> or <os \ + whitelist> or <processor blacklist> */ + +/* We do nothing more here for now */ +/*#error "Uncomment this to see if you fall through all the detection"*/ + +#endif /* Big Endian detection */ + +#if (defined(SHA1DC_FORCE_LITTLEENDIAN) && defined(SHA1DC_BIGENDIAN)) +#undef SHA1DC_BIGENDIAN +#endif +#if (defined(SHA1DC_FORCE_BIGENDIAN) && !defined(SHA1DC_BIGENDIAN)) +#define SHA1DC_BIGENDIAN +#endif +/*ENDIANNESS SELECTION*/ + +#ifndef SHA1DC_FORCE_ALIGNED_ACCESS +#if defined(SHA1DC_FORCE_UNALIGNED_ACCESS) || defined(SHA1DC_ON_INTEL_LIKE_PROCESSOR) +#define SHA1DC_ALLOW_UNALIGNED_ACCESS +#endif /*UNALIGNED ACCESS DETECTION*/ +#endif /*FORCE ALIGNED ACCESS*/ + +#define rotate_right(x, n) (((x) >> (n)) | ((x) << (32 - (n)))) +#define rotate_left(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) + +#define sha1_bswap32(x) \ + { \ + x = ((x << 8) & 0xFF00FF00) | ((x >> 8) & 0xFF00FF); \ + x = (x << 16) | (x >> 16); \ + } + +#define sha1_mix(W, t) (rotate_left(W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16], 1)) + +#ifdef SHA1DC_BIGENDIAN +#define sha1_load(m, t, temp) \ + { \ + temp = m[t]; \ + } +#else +#define sha1_load(m, t, temp) \ + { \ + temp = m[t]; \ + sha1_bswap32(temp); \ + } +#endif + +#define sha1_store(W, t, x) *(volatile uint32_t *) &W[t] = x + +#define sha1_f1(b, c, d) ((d) ^ ((b) & ((c) ^ (d)))) +#define sha1_f2(b, c, d) ((b) ^ (c) ^ (d)) +#define sha1_f3(b, c, d) (((b) & (c)) + ((d) & ((b) ^ (c)))) +#define sha1_f4(b, c, d) ((b) ^ (c) ^ (d)) + +#define HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, m, t) \ + { \ + e += rotate_left(a, 5) + sha1_f1(b, c, d) + 0x5A827999 + m[t]; \ + b = rotate_left(b, 30); \ + } +#define HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, m, t) \ + { \ + e += rotate_left(a, 5) + sha1_f2(b, c, d) + 0x6ED9EBA1 + m[t]; \ + b = rotate_left(b, 30); \ + } +#define HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, m, t) \ + { \ + e += rotate_left(a, 5) + sha1_f3(b, c, d) + 0x8F1BBCDC + m[t]; \ + b = rotate_left(b, 30); \ + } +#define HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, m, t) \ + { \ + e += rotate_left(a, 5) + sha1_f4(b, c, d) + 0xCA62C1D6 + m[t]; \ + b = rotate_left(b, 30); \ + } + +#define HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, m, t) \ + { \ + b = rotate_right(b, 30); \ + e -= rotate_left(a, 5) + sha1_f1(b, c, d) + 0x5A827999 + m[t]; \ + } +#define HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, m, t) \ + { \ + b = rotate_right(b, 30); \ + e -= rotate_left(a, 5) + sha1_f2(b, c, d) + 0x6ED9EBA1 + m[t]; \ + } +#define HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, m, t) \ + { \ + b = rotate_right(b, 30); \ + e -= rotate_left(a, 5) + sha1_f3(b, c, d) + 0x8F1BBCDC + m[t]; \ + } +#define HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, m, t) \ + { \ + b = rotate_right(b, 30); \ + e -= rotate_left(a, 5) + sha1_f4(b, c, d) + 0xCA62C1D6 + m[t]; \ + } + +#define SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, t, temp) \ + { \ + sha1_load(m, t, temp); \ + sha1_store(W, t, temp); \ + e += temp + rotate_left(a, 5) + sha1_f1(b, c, d) + 0x5A827999; \ + b = rotate_left(b, 30); \ + } + +#define SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(a, b, c, d, e, W, t, temp) \ + { \ + temp = sha1_mix(W, t); \ + sha1_store(W, t, temp); \ + e += temp + rotate_left(a, 5) + sha1_f1(b, c, d) + 0x5A827999; \ + b = rotate_left(b, 30); \ + } + +#define SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, t, temp) \ + { \ + temp = sha1_mix(W, t); \ + sha1_store(W, t, temp); \ + e += temp + rotate_left(a, 5) + sha1_f2(b, c, d) + 0x6ED9EBA1; \ + b = rotate_left(b, 30); \ + } + +#define SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, t, temp) \ + { \ + temp = sha1_mix(W, t); \ + sha1_store(W, t, temp); \ + e += temp + rotate_left(a, 5) + sha1_f3(b, c, d) + 0x8F1BBCDC; \ + b = rotate_left(b, 30); \ + } + +#define SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, t, temp) \ + { \ + temp = sha1_mix(W, t); \ + sha1_store(W, t, temp); \ + e += temp + rotate_left(a, 5) + sha1_f4(b, c, d) + 0xCA62C1D6; \ + b = rotate_left(b, 30); \ + } + +#define SHA1_STORE_STATE(i) \ + states[i][0] = a; \ + states[i][1] = b; \ + states[i][2] = c; \ + states[i][3] = d; \ + states[i][4] = e; + +#ifdef BUILDNOCOLLDETECTSHA1COMPRESSION +void +sha1_compression(uint32_t ihv[5], const uint32_t m[16]) +{ + uint32_t W[80]; + uint32_t a, b, c, d, e; + unsigned i; + + memcpy(W, m, 16 * 4); + for (i = 16; i < 80; ++i) + W[i] = sha1_mix(W, i); + + a = ihv[0]; + b = ihv[1]; + c = ihv[2]; + d = ihv[3]; + e = ihv[4]; + + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 0); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 1); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 2); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 3); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 4); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 5); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 6); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 7); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 8); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 9); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 10); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 11); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 12); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 13); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 14); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 15); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 16); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 17); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 18); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 19); + + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 20); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 21); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 22); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 23); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 24); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 25); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 26); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 27); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 28); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 29); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 30); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 31); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 32); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 33); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 34); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 35); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 36); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 37); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 38); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 39); + + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 40); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 41); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 42); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 43); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 44); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 45); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 46); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 47); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 48); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 49); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 50); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 51); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 52); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 53); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 54); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 55); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 56); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 57); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 58); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 59); + + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 60); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 61); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 62); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 63); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 64); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 65); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 66); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 67); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 68); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 69); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 70); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 71); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 72); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 73); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 74); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 75); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 76); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 77); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 78); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 79); + + ihv[0] += a; + ihv[1] += b; + ihv[2] += c; + ihv[3] += d; + ihv[4] += e; +} +#endif /*BUILDNOCOLLDETECTSHA1COMPRESSION*/ + +static void +sha1_compression_W(uint32_t ihv[5], const uint32_t W[80]) +{ + uint32_t a = ihv[0], b = ihv[1], c = ihv[2], d = ihv[3], e = ihv[4]; + + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 0); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 1); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 2); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 3); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 4); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 5); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 6); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 7); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 8); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 9); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 10); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 11); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 12); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 13); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 14); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 15); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 16); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 17); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 18); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 19); + + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 20); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 21); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 22); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 23); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 24); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 25); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 26); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 27); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 28); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 29); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 30); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 31); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 32); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 33); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 34); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 35); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 36); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 37); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 38); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 39); + + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 40); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 41); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 42); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 43); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 44); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 45); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 46); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 47); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 48); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 49); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 50); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 51); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 52); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 53); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 54); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 55); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 56); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 57); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 58); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 59); + + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 60); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 61); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 62); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 63); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 64); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 65); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 66); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 67); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 68); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 69); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 70); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 71); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 72); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 73); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 74); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 75); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 76); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 77); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 78); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 79); + + ihv[0] += a; + ihv[1] += b; + ihv[2] += c; + ihv[3] += d; + ihv[4] += e; +} + +void +sha1_compression_states(uint32_t ihv[5], + const uint32_t m[16], + uint32_t W[80], + uint32_t states[80][5]) +{ + uint32_t a = ihv[0], b = ihv[1], c = ihv[2], d = ihv[3], e = ihv[4]; + uint32_t temp; + +#ifdef DOSTORESTATE00 + SHA1_STORE_STATE(0) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 0, temp); + +#ifdef DOSTORESTATE01 + SHA1_STORE_STATE(1) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(e, a, b, c, d, m, W, 1, temp); + +#ifdef DOSTORESTATE02 + SHA1_STORE_STATE(2) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(d, e, a, b, c, m, W, 2, temp); + +#ifdef DOSTORESTATE03 + SHA1_STORE_STATE(3) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(c, d, e, a, b, m, W, 3, temp); + +#ifdef DOSTORESTATE04 + SHA1_STORE_STATE(4) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(b, c, d, e, a, m, W, 4, temp); + +#ifdef DOSTORESTATE05 + SHA1_STORE_STATE(5) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 5, temp); + +#ifdef DOSTORESTATE06 + SHA1_STORE_STATE(6) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(e, a, b, c, d, m, W, 6, temp); + +#ifdef DOSTORESTATE07 + SHA1_STORE_STATE(7) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(d, e, a, b, c, m, W, 7, temp); + +#ifdef DOSTORESTATE08 + SHA1_STORE_STATE(8) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(c, d, e, a, b, m, W, 8, temp); + +#ifdef DOSTORESTATE09 + SHA1_STORE_STATE(9) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(b, c, d, e, a, m, W, 9, temp); + +#ifdef DOSTORESTATE10 + SHA1_STORE_STATE(10) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 10, temp); + +#ifdef DOSTORESTATE11 + SHA1_STORE_STATE(11) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(e, a, b, c, d, m, W, 11, temp); + +#ifdef DOSTORESTATE12 + SHA1_STORE_STATE(12) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(d, e, a, b, c, m, W, 12, temp); + +#ifdef DOSTORESTATE13 + SHA1_STORE_STATE(13) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(c, d, e, a, b, m, W, 13, temp); + +#ifdef DOSTORESTATE14 + SHA1_STORE_STATE(14) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(b, c, d, e, a, m, W, 14, temp); + +#ifdef DOSTORESTATE15 + SHA1_STORE_STATE(15) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 15, temp); + +#ifdef DOSTORESTATE16 + SHA1_STORE_STATE(16) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(e, a, b, c, d, W, 16, temp); + +#ifdef DOSTORESTATE17 + SHA1_STORE_STATE(17) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(d, e, a, b, c, W, 17, temp); + +#ifdef DOSTORESTATE18 + SHA1_STORE_STATE(18) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(c, d, e, a, b, W, 18, temp); + +#ifdef DOSTORESTATE19 + SHA1_STORE_STATE(19) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(b, c, d, e, a, W, 19, temp); + +#ifdef DOSTORESTATE20 + SHA1_STORE_STATE(20) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 20, temp); + +#ifdef DOSTORESTATE21 + SHA1_STORE_STATE(21) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 21, temp); + +#ifdef DOSTORESTATE22 + SHA1_STORE_STATE(22) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 22, temp); + +#ifdef DOSTORESTATE23 + SHA1_STORE_STATE(23) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 23, temp); + +#ifdef DOSTORESTATE24 + SHA1_STORE_STATE(24) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 24, temp); + +#ifdef DOSTORESTATE25 + SHA1_STORE_STATE(25) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 25, temp); + +#ifdef DOSTORESTATE26 + SHA1_STORE_STATE(26) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 26, temp); + +#ifdef DOSTORESTATE27 + SHA1_STORE_STATE(27) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 27, temp); + +#ifdef DOSTORESTATE28 + SHA1_STORE_STATE(28) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 28, temp); + +#ifdef DOSTORESTATE29 + SHA1_STORE_STATE(29) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 29, temp); + +#ifdef DOSTORESTATE30 + SHA1_STORE_STATE(30) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 30, temp); + +#ifdef DOSTORESTATE31 + SHA1_STORE_STATE(31) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 31, temp); + +#ifdef DOSTORESTATE32 + SHA1_STORE_STATE(32) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 32, temp); + +#ifdef DOSTORESTATE33 + SHA1_STORE_STATE(33) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 33, temp); + +#ifdef DOSTORESTATE34 + SHA1_STORE_STATE(34) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 34, temp); + +#ifdef DOSTORESTATE35 + SHA1_STORE_STATE(35) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 35, temp); + +#ifdef DOSTORESTATE36 + SHA1_STORE_STATE(36) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 36, temp); + +#ifdef DOSTORESTATE37 + SHA1_STORE_STATE(37) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 37, temp); + +#ifdef DOSTORESTATE38 + SHA1_STORE_STATE(38) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 38, temp); + +#ifdef DOSTORESTATE39 + SHA1_STORE_STATE(39) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 39, temp); + +#ifdef DOSTORESTATE40 + SHA1_STORE_STATE(40) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 40, temp); + +#ifdef DOSTORESTATE41 + SHA1_STORE_STATE(41) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 41, temp); + +#ifdef DOSTORESTATE42 + SHA1_STORE_STATE(42) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 42, temp); + +#ifdef DOSTORESTATE43 + SHA1_STORE_STATE(43) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 43, temp); + +#ifdef DOSTORESTATE44 + SHA1_STORE_STATE(44) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 44, temp); + +#ifdef DOSTORESTATE45 + SHA1_STORE_STATE(45) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 45, temp); + +#ifdef DOSTORESTATE46 + SHA1_STORE_STATE(46) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 46, temp); + +#ifdef DOSTORESTATE47 + SHA1_STORE_STATE(47) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 47, temp); + +#ifdef DOSTORESTATE48 + SHA1_STORE_STATE(48) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 48, temp); + +#ifdef DOSTORESTATE49 + SHA1_STORE_STATE(49) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 49, temp); + +#ifdef DOSTORESTATE50 + SHA1_STORE_STATE(50) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 50, temp); + +#ifdef DOSTORESTATE51 + SHA1_STORE_STATE(51) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 51, temp); + +#ifdef DOSTORESTATE52 + SHA1_STORE_STATE(52) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 52, temp); + +#ifdef DOSTORESTATE53 + SHA1_STORE_STATE(53) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 53, temp); + +#ifdef DOSTORESTATE54 + SHA1_STORE_STATE(54) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 54, temp); + +#ifdef DOSTORESTATE55 + SHA1_STORE_STATE(55) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 55, temp); + +#ifdef DOSTORESTATE56 + SHA1_STORE_STATE(56) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 56, temp); + +#ifdef DOSTORESTATE57 + SHA1_STORE_STATE(57) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 57, temp); + +#ifdef DOSTORESTATE58 + SHA1_STORE_STATE(58) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 58, temp); + +#ifdef DOSTORESTATE59 + SHA1_STORE_STATE(59) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 59, temp); + +#ifdef DOSTORESTATE60 + SHA1_STORE_STATE(60) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 60, temp); + +#ifdef DOSTORESTATE61 + SHA1_STORE_STATE(61) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 61, temp); + +#ifdef DOSTORESTATE62 + SHA1_STORE_STATE(62) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 62, temp); + +#ifdef DOSTORESTATE63 + SHA1_STORE_STATE(63) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 63, temp); + +#ifdef DOSTORESTATE64 + SHA1_STORE_STATE(64) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 64, temp); + +#ifdef DOSTORESTATE65 + SHA1_STORE_STATE(65) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 65, temp); + +#ifdef DOSTORESTATE66 + SHA1_STORE_STATE(66) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 66, temp); + +#ifdef DOSTORESTATE67 + SHA1_STORE_STATE(67) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 67, temp); + +#ifdef DOSTORESTATE68 + SHA1_STORE_STATE(68) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 68, temp); + +#ifdef DOSTORESTATE69 + SHA1_STORE_STATE(69) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 69, temp); + +#ifdef DOSTORESTATE70 + SHA1_STORE_STATE(70) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 70, temp); + +#ifdef DOSTORESTATE71 + SHA1_STORE_STATE(71) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 71, temp); + +#ifdef DOSTORESTATE72 + SHA1_STORE_STATE(72) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 72, temp); + +#ifdef DOSTORESTATE73 + SHA1_STORE_STATE(73) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 73, temp); + +#ifdef DOSTORESTATE74 + SHA1_STORE_STATE(74) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 74, temp); + +#ifdef DOSTORESTATE75 + SHA1_STORE_STATE(75) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 75, temp); + +#ifdef DOSTORESTATE76 + SHA1_STORE_STATE(76) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 76, temp); + +#ifdef DOSTORESTATE77 + SHA1_STORE_STATE(77) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 77, temp); + +#ifdef DOSTORESTATE78 + SHA1_STORE_STATE(78) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 78, temp); + +#ifdef DOSTORESTATE79 + SHA1_STORE_STATE(79) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 79, temp); + + ihv[0] += a; + ihv[1] += b; + ihv[2] += c; + ihv[3] += d; + ihv[4] += e; +} + +#define SHA1_RECOMPRESS(t) \ + static void sha1recompress_fast_##t( \ + uint32_t ihvin[5], uint32_t ihvout[5], const uint32_t me2[80], const uint32_t state[5]) \ + { \ + uint32_t a = state[0], b = state[1], c = state[2], d = state[3], e = state[4]; \ + if (t > 79) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 79); \ + if (t > 78) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 78); \ + if (t > 77) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 77); \ + if (t > 76) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 76); \ + if (t > 75) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 75); \ + if (t > 74) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 74); \ + if (t > 73) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 73); \ + if (t > 72) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 72); \ + if (t > 71) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 71); \ + if (t > 70) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 70); \ + if (t > 69) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 69); \ + if (t > 68) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 68); \ + if (t > 67) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 67); \ + if (t > 66) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 66); \ + if (t > 65) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 65); \ + if (t > 64) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 64); \ + if (t > 63) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 63); \ + if (t > 62) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 62); \ + if (t > 61) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 61); \ + if (t > 60) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 60); \ + if (t > 59) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 59); \ + if (t > 58) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 58); \ + if (t > 57) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 57); \ + if (t > 56) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 56); \ + if (t > 55) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 55); \ + if (t > 54) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 54); \ + if (t > 53) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 53); \ + if (t > 52) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 52); \ + if (t > 51) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 51); \ + if (t > 50) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 50); \ + if (t > 49) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 49); \ + if (t > 48) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 48); \ + if (t > 47) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 47); \ + if (t > 46) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 46); \ + if (t > 45) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 45); \ + if (t > 44) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 44); \ + if (t > 43) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 43); \ + if (t > 42) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 42); \ + if (t > 41) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 41); \ + if (t > 40) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 40); \ + if (t > 39) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 39); \ + if (t > 38) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 38); \ + if (t > 37) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 37); \ + if (t > 36) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 36); \ + if (t > 35) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 35); \ + if (t > 34) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 34); \ + if (t > 33) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 33); \ + if (t > 32) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 32); \ + if (t > 31) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 31); \ + if (t > 30) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 30); \ + if (t > 29) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 29); \ + if (t > 28) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 28); \ + if (t > 27) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 27); \ + if (t > 26) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 26); \ + if (t > 25) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 25); \ + if (t > 24) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 24); \ + if (t > 23) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 23); \ + if (t > 22) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 22); \ + if (t > 21) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 21); \ + if (t > 20) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 20); \ + if (t > 19) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 19); \ + if (t > 18) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 18); \ + if (t > 17) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 17); \ + if (t > 16) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 16); \ + if (t > 15) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 15); \ + if (t > 14) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 14); \ + if (t > 13) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 13); \ + if (t > 12) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 12); \ + if (t > 11) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 11); \ + if (t > 10) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 10); \ + if (t > 9) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 9); \ + if (t > 8) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 8); \ + if (t > 7) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 7); \ + if (t > 6) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 6); \ + if (t > 5) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 5); \ + if (t > 4) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 4); \ + if (t > 3) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 3); \ + if (t > 2) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 2); \ + if (t > 1) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 1); \ + if (t > 0) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 0); \ + ihvin[0] = a; \ + ihvin[1] = b; \ + ihvin[2] = c; \ + ihvin[3] = d; \ + ihvin[4] = e; \ + a = state[0]; \ + b = state[1]; \ + c = state[2]; \ + d = state[3]; \ + e = state[4]; \ + if (t <= 0) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 0); \ + if (t <= 1) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 1); \ + if (t <= 2) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 2); \ + if (t <= 3) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 3); \ + if (t <= 4) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 4); \ + if (t <= 5) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 5); \ + if (t <= 6) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 6); \ + if (t <= 7) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 7); \ + if (t <= 8) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 8); \ + if (t <= 9) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 9); \ + if (t <= 10) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 10); \ + if (t <= 11) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 11); \ + if (t <= 12) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 12); \ + if (t <= 13) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 13); \ + if (t <= 14) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 14); \ + if (t <= 15) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 15); \ + if (t <= 16) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 16); \ + if (t <= 17) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 17); \ + if (t <= 18) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 18); \ + if (t <= 19) \ + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 19); \ + if (t <= 20) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 20); \ + if (t <= 21) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 21); \ + if (t <= 22) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 22); \ + if (t <= 23) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 23); \ + if (t <= 24) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 24); \ + if (t <= 25) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 25); \ + if (t <= 26) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 26); \ + if (t <= 27) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 27); \ + if (t <= 28) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 28); \ + if (t <= 29) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 29); \ + if (t <= 30) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 30); \ + if (t <= 31) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 31); \ + if (t <= 32) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 32); \ + if (t <= 33) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 33); \ + if (t <= 34) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 34); \ + if (t <= 35) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 35); \ + if (t <= 36) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 36); \ + if (t <= 37) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 37); \ + if (t <= 38) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 38); \ + if (t <= 39) \ + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 39); \ + if (t <= 40) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 40); \ + if (t <= 41) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 41); \ + if (t <= 42) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 42); \ + if (t <= 43) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 43); \ + if (t <= 44) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 44); \ + if (t <= 45) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 45); \ + if (t <= 46) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 46); \ + if (t <= 47) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 47); \ + if (t <= 48) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 48); \ + if (t <= 49) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 49); \ + if (t <= 50) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 50); \ + if (t <= 51) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 51); \ + if (t <= 52) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 52); \ + if (t <= 53) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 53); \ + if (t <= 54) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 54); \ + if (t <= 55) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 55); \ + if (t <= 56) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 56); \ + if (t <= 57) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 57); \ + if (t <= 58) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 58); \ + if (t <= 59) \ + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 59); \ + if (t <= 60) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 60); \ + if (t <= 61) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 61); \ + if (t <= 62) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 62); \ + if (t <= 63) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 63); \ + if (t <= 64) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 64); \ + if (t <= 65) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 65); \ + if (t <= 66) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 66); \ + if (t <= 67) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 67); \ + if (t <= 68) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 68); \ + if (t <= 69) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 69); \ + if (t <= 70) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 70); \ + if (t <= 71) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 71); \ + if (t <= 72) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 72); \ + if (t <= 73) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 73); \ + if (t <= 74) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 74); \ + if (t <= 75) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 75); \ + if (t <= 76) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 76); \ + if (t <= 77) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 77); \ + if (t <= 78) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 78); \ + if (t <= 79) \ + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 79); \ + ihvout[0] = ihvin[0] + a; \ + ihvout[1] = ihvin[1] + b; \ + ihvout[2] = ihvin[2] + c; \ + ihvout[3] = ihvin[3] + d; \ + ihvout[4] = ihvin[4] + e; \ + } + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4127) /* Compiler complains about the checks in the above macro \ + being constant. */ +#endif + +#ifdef DOSTORESTATE0 +SHA1_RECOMPRESS(0) +#endif + +#ifdef DOSTORESTATE1 +SHA1_RECOMPRESS(1) +#endif + +#ifdef DOSTORESTATE2 +SHA1_RECOMPRESS(2) +#endif + +#ifdef DOSTORESTATE3 +SHA1_RECOMPRESS(3) +#endif + +#ifdef DOSTORESTATE4 +SHA1_RECOMPRESS(4) +#endif + +#ifdef DOSTORESTATE5 +SHA1_RECOMPRESS(5) +#endif + +#ifdef DOSTORESTATE6 +SHA1_RECOMPRESS(6) +#endif + +#ifdef DOSTORESTATE7 +SHA1_RECOMPRESS(7) +#endif + +#ifdef DOSTORESTATE8 +SHA1_RECOMPRESS(8) +#endif + +#ifdef DOSTORESTATE9 +SHA1_RECOMPRESS(9) +#endif + +#ifdef DOSTORESTATE10 +SHA1_RECOMPRESS(10) +#endif + +#ifdef DOSTORESTATE11 +SHA1_RECOMPRESS(11) +#endif + +#ifdef DOSTORESTATE12 +SHA1_RECOMPRESS(12) +#endif + +#ifdef DOSTORESTATE13 +SHA1_RECOMPRESS(13) +#endif + +#ifdef DOSTORESTATE14 +SHA1_RECOMPRESS(14) +#endif + +#ifdef DOSTORESTATE15 +SHA1_RECOMPRESS(15) +#endif + +#ifdef DOSTORESTATE16 +SHA1_RECOMPRESS(16) +#endif + +#ifdef DOSTORESTATE17 +SHA1_RECOMPRESS(17) +#endif + +#ifdef DOSTORESTATE18 +SHA1_RECOMPRESS(18) +#endif + +#ifdef DOSTORESTATE19 +SHA1_RECOMPRESS(19) +#endif + +#ifdef DOSTORESTATE20 +SHA1_RECOMPRESS(20) +#endif + +#ifdef DOSTORESTATE21 +SHA1_RECOMPRESS(21) +#endif + +#ifdef DOSTORESTATE22 +SHA1_RECOMPRESS(22) +#endif + +#ifdef DOSTORESTATE23 +SHA1_RECOMPRESS(23) +#endif + +#ifdef DOSTORESTATE24 +SHA1_RECOMPRESS(24) +#endif + +#ifdef DOSTORESTATE25 +SHA1_RECOMPRESS(25) +#endif + +#ifdef DOSTORESTATE26 +SHA1_RECOMPRESS(26) +#endif + +#ifdef DOSTORESTATE27 +SHA1_RECOMPRESS(27) +#endif + +#ifdef DOSTORESTATE28 +SHA1_RECOMPRESS(28) +#endif + +#ifdef DOSTORESTATE29 +SHA1_RECOMPRESS(29) +#endif + +#ifdef DOSTORESTATE30 +SHA1_RECOMPRESS(30) +#endif + +#ifdef DOSTORESTATE31 +SHA1_RECOMPRESS(31) +#endif + +#ifdef DOSTORESTATE32 +SHA1_RECOMPRESS(32) +#endif + +#ifdef DOSTORESTATE33 +SHA1_RECOMPRESS(33) +#endif + +#ifdef DOSTORESTATE34 +SHA1_RECOMPRESS(34) +#endif + +#ifdef DOSTORESTATE35 +SHA1_RECOMPRESS(35) +#endif + +#ifdef DOSTORESTATE36 +SHA1_RECOMPRESS(36) +#endif + +#ifdef DOSTORESTATE37 +SHA1_RECOMPRESS(37) +#endif + +#ifdef DOSTORESTATE38 +SHA1_RECOMPRESS(38) +#endif + +#ifdef DOSTORESTATE39 +SHA1_RECOMPRESS(39) +#endif + +#ifdef DOSTORESTATE40 +SHA1_RECOMPRESS(40) +#endif + +#ifdef DOSTORESTATE41 +SHA1_RECOMPRESS(41) +#endif + +#ifdef DOSTORESTATE42 +SHA1_RECOMPRESS(42) +#endif + +#ifdef DOSTORESTATE43 +SHA1_RECOMPRESS(43) +#endif + +#ifdef DOSTORESTATE44 +SHA1_RECOMPRESS(44) +#endif + +#ifdef DOSTORESTATE45 +SHA1_RECOMPRESS(45) +#endif + +#ifdef DOSTORESTATE46 +SHA1_RECOMPRESS(46) +#endif + +#ifdef DOSTORESTATE47 +SHA1_RECOMPRESS(47) +#endif + +#ifdef DOSTORESTATE48 +SHA1_RECOMPRESS(48) +#endif + +#ifdef DOSTORESTATE49 +SHA1_RECOMPRESS(49) +#endif + +#ifdef DOSTORESTATE50 +SHA1_RECOMPRESS(50) +#endif + +#ifdef DOSTORESTATE51 +SHA1_RECOMPRESS(51) +#endif + +#ifdef DOSTORESTATE52 +SHA1_RECOMPRESS(52) +#endif + +#ifdef DOSTORESTATE53 +SHA1_RECOMPRESS(53) +#endif + +#ifdef DOSTORESTATE54 +SHA1_RECOMPRESS(54) +#endif + +#ifdef DOSTORESTATE55 +SHA1_RECOMPRESS(55) +#endif + +#ifdef DOSTORESTATE56 +SHA1_RECOMPRESS(56) +#endif + +#ifdef DOSTORESTATE57 +SHA1_RECOMPRESS(57) +#endif + +#ifdef DOSTORESTATE58 +SHA1_RECOMPRESS(58) +#endif + +#ifdef DOSTORESTATE59 +SHA1_RECOMPRESS(59) +#endif + +#ifdef DOSTORESTATE60 +SHA1_RECOMPRESS(60) +#endif + +#ifdef DOSTORESTATE61 +SHA1_RECOMPRESS(61) +#endif + +#ifdef DOSTORESTATE62 +SHA1_RECOMPRESS(62) +#endif + +#ifdef DOSTORESTATE63 +SHA1_RECOMPRESS(63) +#endif + +#ifdef DOSTORESTATE64 +SHA1_RECOMPRESS(64) +#endif + +#ifdef DOSTORESTATE65 +SHA1_RECOMPRESS(65) +#endif + +#ifdef DOSTORESTATE66 +SHA1_RECOMPRESS(66) +#endif + +#ifdef DOSTORESTATE67 +SHA1_RECOMPRESS(67) +#endif + +#ifdef DOSTORESTATE68 +SHA1_RECOMPRESS(68) +#endif + +#ifdef DOSTORESTATE69 +SHA1_RECOMPRESS(69) +#endif + +#ifdef DOSTORESTATE70 +SHA1_RECOMPRESS(70) +#endif + +#ifdef DOSTORESTATE71 +SHA1_RECOMPRESS(71) +#endif + +#ifdef DOSTORESTATE72 +SHA1_RECOMPRESS(72) +#endif + +#ifdef DOSTORESTATE73 +SHA1_RECOMPRESS(73) +#endif + +#ifdef DOSTORESTATE74 +SHA1_RECOMPRESS(74) +#endif + +#ifdef DOSTORESTATE75 +SHA1_RECOMPRESS(75) +#endif + +#ifdef DOSTORESTATE76 +SHA1_RECOMPRESS(76) +#endif + +#ifdef DOSTORESTATE77 +SHA1_RECOMPRESS(77) +#endif + +#ifdef DOSTORESTATE78 +SHA1_RECOMPRESS(78) +#endif + +#ifdef DOSTORESTATE79 +SHA1_RECOMPRESS(79) +#endif + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +static void +sha1_recompression_step(uint32_t step, + uint32_t ihvin[5], + uint32_t ihvout[5], + const uint32_t me2[80], + const uint32_t state[5]) +{ + switch (step) { +#ifdef DOSTORESTATE0 + case 0: + sha1recompress_fast_0(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE1 + case 1: + sha1recompress_fast_1(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE2 + case 2: + sha1recompress_fast_2(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE3 + case 3: + sha1recompress_fast_3(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE4 + case 4: + sha1recompress_fast_4(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE5 + case 5: + sha1recompress_fast_5(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE6 + case 6: + sha1recompress_fast_6(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE7 + case 7: + sha1recompress_fast_7(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE8 + case 8: + sha1recompress_fast_8(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE9 + case 9: + sha1recompress_fast_9(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE10 + case 10: + sha1recompress_fast_10(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE11 + case 11: + sha1recompress_fast_11(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE12 + case 12: + sha1recompress_fast_12(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE13 + case 13: + sha1recompress_fast_13(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE14 + case 14: + sha1recompress_fast_14(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE15 + case 15: + sha1recompress_fast_15(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE16 + case 16: + sha1recompress_fast_16(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE17 + case 17: + sha1recompress_fast_17(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE18 + case 18: + sha1recompress_fast_18(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE19 + case 19: + sha1recompress_fast_19(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE20 + case 20: + sha1recompress_fast_20(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE21 + case 21: + sha1recompress_fast_21(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE22 + case 22: + sha1recompress_fast_22(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE23 + case 23: + sha1recompress_fast_23(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE24 + case 24: + sha1recompress_fast_24(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE25 + case 25: + sha1recompress_fast_25(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE26 + case 26: + sha1recompress_fast_26(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE27 + case 27: + sha1recompress_fast_27(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE28 + case 28: + sha1recompress_fast_28(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE29 + case 29: + sha1recompress_fast_29(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE30 + case 30: + sha1recompress_fast_30(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE31 + case 31: + sha1recompress_fast_31(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE32 + case 32: + sha1recompress_fast_32(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE33 + case 33: + sha1recompress_fast_33(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE34 + case 34: + sha1recompress_fast_34(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE35 + case 35: + sha1recompress_fast_35(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE36 + case 36: + sha1recompress_fast_36(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE37 + case 37: + sha1recompress_fast_37(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE38 + case 38: + sha1recompress_fast_38(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE39 + case 39: + sha1recompress_fast_39(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE40 + case 40: + sha1recompress_fast_40(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE41 + case 41: + sha1recompress_fast_41(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE42 + case 42: + sha1recompress_fast_42(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE43 + case 43: + sha1recompress_fast_43(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE44 + case 44: + sha1recompress_fast_44(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE45 + case 45: + sha1recompress_fast_45(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE46 + case 46: + sha1recompress_fast_46(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE47 + case 47: + sha1recompress_fast_47(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE48 + case 48: + sha1recompress_fast_48(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE49 + case 49: + sha1recompress_fast_49(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE50 + case 50: + sha1recompress_fast_50(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE51 + case 51: + sha1recompress_fast_51(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE52 + case 52: + sha1recompress_fast_52(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE53 + case 53: + sha1recompress_fast_53(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE54 + case 54: + sha1recompress_fast_54(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE55 + case 55: + sha1recompress_fast_55(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE56 + case 56: + sha1recompress_fast_56(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE57 + case 57: + sha1recompress_fast_57(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE58 + case 58: + sha1recompress_fast_58(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE59 + case 59: + sha1recompress_fast_59(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE60 + case 60: + sha1recompress_fast_60(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE61 + case 61: + sha1recompress_fast_61(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE62 + case 62: + sha1recompress_fast_62(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE63 + case 63: + sha1recompress_fast_63(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE64 + case 64: + sha1recompress_fast_64(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE65 + case 65: + sha1recompress_fast_65(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE66 + case 66: + sha1recompress_fast_66(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE67 + case 67: + sha1recompress_fast_67(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE68 + case 68: + sha1recompress_fast_68(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE69 + case 69: + sha1recompress_fast_69(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE70 + case 70: + sha1recompress_fast_70(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE71 + case 71: + sha1recompress_fast_71(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE72 + case 72: + sha1recompress_fast_72(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE73 + case 73: + sha1recompress_fast_73(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE74 + case 74: + sha1recompress_fast_74(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE75 + case 75: + sha1recompress_fast_75(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE76 + case 76: + sha1recompress_fast_76(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE77 + case 77: + sha1recompress_fast_77(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE78 + case 78: + sha1recompress_fast_78(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE79 + case 79: + sha1recompress_fast_79(ihvin, ihvout, me2, state); + break; +#endif + default: + abort(); + } +} + +static void +sha1_process(SHA1_CTX *ctx, const uint32_t block[16]) +{ + unsigned i, j; + uint32_t ubc_dv_mask[DVMASKSIZE] = {0xFFFFFFFF}; + uint32_t ihvtmp[5]; + + ctx->ihv1[0] = ctx->ihv[0]; + ctx->ihv1[1] = ctx->ihv[1]; + ctx->ihv1[2] = ctx->ihv[2]; + ctx->ihv1[3] = ctx->ihv[3]; + ctx->ihv1[4] = ctx->ihv[4]; + + sha1_compression_states(ctx->ihv, block, ctx->m1, ctx->states); + + if (ctx->detect_coll) { + if (ctx->ubc_check) { + ubc_check(ctx->m1, ubc_dv_mask); + } + + if (ubc_dv_mask[0] != 0) { + for (i = 0; sha1_dvs[i].dvType != 0; ++i) { + if (ubc_dv_mask[0] & ((uint32_t)(1) << sha1_dvs[i].maskb)) { + for (j = 0; j < 80; ++j) + ctx->m2[j] = ctx->m1[j] ^ sha1_dvs[i].dm[j]; + + sha1_recompression_step(sha1_dvs[i].testt, + ctx->ihv2, + ihvtmp, + ctx->m2, + ctx->states[sha1_dvs[i].testt]); + + /* to verify SHA-1 collision detection code with collisions for + * reduced-step SHA-1 */ + if ((0 == ((ihvtmp[0] ^ ctx->ihv[0]) | (ihvtmp[1] ^ ctx->ihv[1]) | + (ihvtmp[2] ^ ctx->ihv[2]) | (ihvtmp[3] ^ ctx->ihv[3]) | + (ihvtmp[4] ^ ctx->ihv[4]))) || + (ctx->reduced_round_coll && + 0 == ((ctx->ihv1[0] ^ ctx->ihv2[0]) | (ctx->ihv1[1] ^ ctx->ihv2[1]) | + (ctx->ihv1[2] ^ ctx->ihv2[2]) | (ctx->ihv1[3] ^ ctx->ihv2[3]) | + (ctx->ihv1[4] ^ ctx->ihv2[4])))) { + ctx->found_collision = 1; + + if (ctx->safe_hash) { + sha1_compression_W(ctx->ihv, ctx->m1); + sha1_compression_W(ctx->ihv, ctx->m1); + } + + break; + } + } + } + } + } +} + +void +SHA1DCInit(SHA1_CTX *ctx) +{ + ctx->total = 0; + ctx->ihv[0] = 0x67452301; + ctx->ihv[1] = 0xEFCDAB89; + ctx->ihv[2] = 0x98BADCFE; + ctx->ihv[3] = 0x10325476; + ctx->ihv[4] = 0xC3D2E1F0; + ctx->found_collision = 0; + ctx->safe_hash = SHA1DC_INIT_SAFE_HASH_DEFAULT; + ctx->ubc_check = 1; + ctx->detect_coll = 1; + ctx->reduced_round_coll = 0; + ctx->callback = NULL; +} + +void +SHA1DCSetSafeHash(SHA1_CTX *ctx, int safehash) +{ + if (safehash) + ctx->safe_hash = 1; + else + ctx->safe_hash = 0; +} + +void +SHA1DCSetUseUBC(SHA1_CTX *ctx, int ubc_check) +{ + if (ubc_check) + ctx->ubc_check = 1; + else + ctx->ubc_check = 0; +} + +void +SHA1DCSetUseDetectColl(SHA1_CTX *ctx, int detect_coll) +{ + if (detect_coll) + ctx->detect_coll = 1; + else + ctx->detect_coll = 0; +} + +void +SHA1DCSetDetectReducedRoundCollision(SHA1_CTX *ctx, int reduced_round_coll) +{ + if (reduced_round_coll) + ctx->reduced_round_coll = 1; + else + ctx->reduced_round_coll = 0; +} + +void +SHA1DCSetCallback(SHA1_CTX *ctx, collision_block_callback callback) +{ + ctx->callback = callback; +} + +void +SHA1DCUpdate(SHA1_CTX *ctx, const char *buf, size_t len) +{ + unsigned left, fill; + + if (len == 0) + return; + + left = ctx->total & 63; + fill = 64 - left; + + if (left && len >= fill) { + ctx->total += fill; + memcpy(ctx->buffer + left, buf, fill); + sha1_process(ctx, (uint32_t *) (ctx->buffer)); + buf += fill; + len -= fill; + left = 0; + } + while (len >= 64) { + ctx->total += 64; + +#if defined(SHA1DC_ALLOW_UNALIGNED_ACCESS) + sha1_process(ctx, (uint32_t *) (buf)); +#else + memcpy(ctx->buffer, buf, 64); + sha1_process(ctx, (uint32_t *) (ctx->buffer)); +#endif /* defined(SHA1DC_ALLOW_UNALIGNED_ACCESS) */ + buf += 64; + len -= 64; + } + if (len > 0) { + ctx->total += len; + memcpy(ctx->buffer + left, buf, len); + } +} + +static const unsigned char sha1_padding[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + +int +SHA1DCFinal(unsigned char output[20], SHA1_CTX *ctx) +{ + uint32_t last = ctx->total & 63; + uint32_t padn = (last < 56) ? (56 - last) : (120 - last); + uint64_t total; + SHA1DCUpdate(ctx, (const char *) (sha1_padding), padn); + + total = ctx->total - padn; + total <<= 3; + ctx->buffer[56] = (unsigned char) (total >> 56); + ctx->buffer[57] = (unsigned char) (total >> 48); + ctx->buffer[58] = (unsigned char) (total >> 40); + ctx->buffer[59] = (unsigned char) (total >> 32); + ctx->buffer[60] = (unsigned char) (total >> 24); + ctx->buffer[61] = (unsigned char) (total >> 16); + ctx->buffer[62] = (unsigned char) (total >> 8); + ctx->buffer[63] = (unsigned char) (total); + sha1_process(ctx, (uint32_t *) (ctx->buffer)); + output[0] = (unsigned char) (ctx->ihv[0] >> 24); + output[1] = (unsigned char) (ctx->ihv[0] >> 16); + output[2] = (unsigned char) (ctx->ihv[0] >> 8); + output[3] = (unsigned char) (ctx->ihv[0]); + output[4] = (unsigned char) (ctx->ihv[1] >> 24); + output[5] = (unsigned char) (ctx->ihv[1] >> 16); + output[6] = (unsigned char) (ctx->ihv[1] >> 8); + output[7] = (unsigned char) (ctx->ihv[1]); + output[8] = (unsigned char) (ctx->ihv[2] >> 24); + output[9] = (unsigned char) (ctx->ihv[2] >> 16); + output[10] = (unsigned char) (ctx->ihv[2] >> 8); + output[11] = (unsigned char) (ctx->ihv[2]); + output[12] = (unsigned char) (ctx->ihv[3] >> 24); + output[13] = (unsigned char) (ctx->ihv[3] >> 16); + output[14] = (unsigned char) (ctx->ihv[3] >> 8); + output[15] = (unsigned char) (ctx->ihv[3]); + output[16] = (unsigned char) (ctx->ihv[4] >> 24); + output[17] = (unsigned char) (ctx->ihv[4] >> 16); + output[18] = (unsigned char) (ctx->ihv[4] >> 8); + output[19] = (unsigned char) (ctx->ihv[4]); + return ctx->found_collision; +} + +#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_C +#include SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_C +#endif diff --git a/comm/third_party/rnp/src/lib/crypto/sha1cd/sha1.h b/comm/third_party/rnp/src/lib/crypto/sha1cd/sha1.h new file mode 100644 index 0000000000..5bc0925798 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/sha1cd/sha1.h @@ -0,0 +1,122 @@ +/*** + * Copyright 2017 Marc Stevens <marc@marc-stevens.nl>, Dan Shumow <danshu@microsoft.com> + * Distributed under the MIT Software License. + * See accompanying file LICENSE.txt or copy at + * https://opensource.org/licenses/MIT + ***/ + +#ifndef SHA1DC_SHA1_H +#define SHA1DC_SHA1_H + +#if defined(__cplusplus) +extern "C" { +#endif + +#ifndef SHA1DC_NO_STANDARD_INCLUDES +#include <stdint.h> +#endif + +/* sha-1 compression function that takes an already expanded message, and additionally store + * intermediate states */ +/* only stores states ii (the state between step ii-1 and step ii) when DOSTORESTATEii is + * defined in ubc_check.h */ +void sha1_compression_states(uint32_t[5], const uint32_t[16], uint32_t[80], uint32_t[80][5]); + +/* +// Function type for sha1_recompression_step_T (uint32_t ihvin[5], uint32_t ihvout[5], const +uint32_t me2[80], const uint32_t state[5]). +// Where 0 <= T < 80 +// me2 is an expanded message (the expansion of an original message block XOR'ed with a +disturbance vector's message block difference.) +// state is the internal state (a,b,c,d,e) before step T of the SHA-1 compression +function while processing the original message block. +// The function will return: +// ihvin: The reconstructed input chaining value. +// ihvout: The reconstructed output chaining value. +*/ +typedef void (*sha1_recompression_type)(uint32_t *, + uint32_t *, + const uint32_t *, + const uint32_t *); + +/* A callback function type that can be set to be called when a collision block has been found: + */ +/* void collision_block_callback(uint64_t byteoffset, const uint32_t ihvin1[5], const uint32_t + * ihvin2[5], const uint32_t m1[80], const uint32_t m2[80]) */ +typedef void (*collision_block_callback)( + uint64_t, const uint32_t *, const uint32_t *, const uint32_t *, const uint32_t *); + +/* The SHA-1 context. */ +typedef struct { + uint64_t total; + uint32_t ihv[5]; + unsigned char buffer[64]; + int found_collision; + int safe_hash; + int detect_coll; + int ubc_check; + int reduced_round_coll; + collision_block_callback callback; + + uint32_t ihv1[5]; + uint32_t ihv2[5]; + uint32_t m1[80]; + uint32_t m2[80]; + uint32_t states[80][5]; +} SHA1_CTX; + +/* Initialize SHA-1 context. */ +void SHA1DCInit(SHA1_CTX *); + +/* + Function to enable safe SHA-1 hashing: + Collision attacks are thwarted by hashing a detected near-collision block 3 times. + Think of it as extending SHA-1 from 80-steps to 240-steps for such blocks: + The best collision attacks against SHA-1 have complexity about 2^60, + thus for 240-steps an immediate lower-bound for the best cryptanalytic attacks would be + 2^180. An attacker would be better off using a generic birthday search of complexity 2^80. + + Enabling safe SHA-1 hashing will result in the correct SHA-1 hash for messages where no + collision attack was detected, but it will result in a different SHA-1 hash for messages + where a collision attack was detected. This will automatically invalidate SHA-1 based + digital signature forgeries. Enabled by default. +*/ +void SHA1DCSetSafeHash(SHA1_CTX *, int); + +/* + Function to disable or enable the use of Unavoidable Bitconditions (provides a significant + speed up). Enabled by default + */ +void SHA1DCSetUseUBC(SHA1_CTX *, int); + +/* + Function to disable or enable the use of Collision Detection. + Enabled by default. + */ +void SHA1DCSetUseDetectColl(SHA1_CTX *, int); + +/* function to disable or enable the detection of reduced-round SHA-1 collisions */ +/* disabled by default */ +void SHA1DCSetDetectReducedRoundCollision(SHA1_CTX *, int); + +/* function to set a callback function, pass NULL to disable */ +/* by default no callback set */ +void SHA1DCSetCallback(SHA1_CTX *, collision_block_callback); + +/* update SHA-1 context with buffer contents */ +void SHA1DCUpdate(SHA1_CTX *, const char *, size_t); + +/* obtain SHA-1 hash from SHA-1 context */ +/* returns: 0 = no collision detected, otherwise = collision found => warn user for active + * attack */ +int SHA1DCFinal(unsigned char[20], SHA1_CTX *); + +#if defined(__cplusplus) +} +#endif + +#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_H +#include SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_H +#endif + +#endif diff --git a/comm/third_party/rnp/src/lib/crypto/sha1cd/ubc_check.c b/comm/third_party/rnp/src/lib/crypto/sha1cd/ubc_check.c new file mode 100644 index 0000000000..c44c53d9d0 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/sha1cd/ubc_check.c @@ -0,0 +1,908 @@ +/*** + * Copyright 2017 Marc Stevens <marc@marc-stevens.nl>, Dan Shumow <danshu@microsoft.com> + * Distributed under the MIT Software License. + * See accompanying file LICENSE.txt or copy at + * https://opensource.org/licenses/MIT + ***/ + +/* +// this file was generated by the 'parse_bitrel' program in the tools section +// using the data files from directory 'tools/data/3565' +// +// sha1_dvs contains a list of SHA-1 Disturbance Vectors (DV) to check +// dvType, dvK and dvB define the DV: I(K,B) or II(K,B) (see the paper) +// dm[80] is the expanded message block XOR-difference defined by the DV +// testt is the step to do the recompression from for collision detection +// maski and maskb define the bit to check for each DV in the dvmask returned by ubc_check +// +// ubc_check takes as input an expanded message block and verifies the unavoidable +bitconditions for all listed DVs +// it returns a dvmask where each bit belonging to a DV is set if all unavoidable bitconditions +for that DV have been met +// thus one needs to do the recompression check for each DV that has its bit set +// +// ubc_check is programmatically generated and the unavoidable bitconditions have been +hardcoded +// a directly verifiable version named ubc_check_verify can be found in ubc_check_verify.c +// ubc_check has been verified against ubc_check_verify using the 'ubc_check_test' program in +the tools section +*/ + +#ifndef SHA1DC_NO_STANDARD_INCLUDES +#include <stdint.h> +#endif +#ifdef SHA1DC_CUSTOM_INCLUDE_UBC_CHECK_C +#include SHA1DC_CUSTOM_INCLUDE_UBC_CHECK_C +#endif +#include "ubc_check.h" + +static const uint32_t DV_I_43_0_bit = (uint32_t)(1) << 0; +static const uint32_t DV_I_44_0_bit = (uint32_t)(1) << 1; +static const uint32_t DV_I_45_0_bit = (uint32_t)(1) << 2; +static const uint32_t DV_I_46_0_bit = (uint32_t)(1) << 3; +static const uint32_t DV_I_46_2_bit = (uint32_t)(1) << 4; +static const uint32_t DV_I_47_0_bit = (uint32_t)(1) << 5; +static const uint32_t DV_I_47_2_bit = (uint32_t)(1) << 6; +static const uint32_t DV_I_48_0_bit = (uint32_t)(1) << 7; +static const uint32_t DV_I_48_2_bit = (uint32_t)(1) << 8; +static const uint32_t DV_I_49_0_bit = (uint32_t)(1) << 9; +static const uint32_t DV_I_49_2_bit = (uint32_t)(1) << 10; +static const uint32_t DV_I_50_0_bit = (uint32_t)(1) << 11; +static const uint32_t DV_I_50_2_bit = (uint32_t)(1) << 12; +static const uint32_t DV_I_51_0_bit = (uint32_t)(1) << 13; +static const uint32_t DV_I_51_2_bit = (uint32_t)(1) << 14; +static const uint32_t DV_I_52_0_bit = (uint32_t)(1) << 15; +static const uint32_t DV_II_45_0_bit = (uint32_t)(1) << 16; +static const uint32_t DV_II_46_0_bit = (uint32_t)(1) << 17; +static const uint32_t DV_II_46_2_bit = (uint32_t)(1) << 18; +static const uint32_t DV_II_47_0_bit = (uint32_t)(1) << 19; +static const uint32_t DV_II_48_0_bit = (uint32_t)(1) << 20; +static const uint32_t DV_II_49_0_bit = (uint32_t)(1) << 21; +static const uint32_t DV_II_49_2_bit = (uint32_t)(1) << 22; +static const uint32_t DV_II_50_0_bit = (uint32_t)(1) << 23; +static const uint32_t DV_II_50_2_bit = (uint32_t)(1) << 24; +static const uint32_t DV_II_51_0_bit = (uint32_t)(1) << 25; +static const uint32_t DV_II_51_2_bit = (uint32_t)(1) << 26; +static const uint32_t DV_II_52_0_bit = (uint32_t)(1) << 27; +static const uint32_t DV_II_53_0_bit = (uint32_t)(1) << 28; +static const uint32_t DV_II_54_0_bit = (uint32_t)(1) << 29; +static const uint32_t DV_II_55_0_bit = (uint32_t)(1) << 30; +static const uint32_t DV_II_56_0_bit = (uint32_t)(1) << 31; + +dv_info_t sha1_dvs[] = { + {1, 43, 0, 58, 0, 0, {0x08000000, 0x9800000c, 0xd8000010, 0x08000010, 0xb8000010, 0x98000000, + 0x60000000, 0x00000008, 0xc0000000, 0x90000014, 0x10000010, 0xb8000014, + 0x28000000, 0x20000010, 0x48000000, 0x08000018, 0x60000000, 0x90000010, + 0xf0000010, 0x90000008, 0xc0000000, 0x90000010, 0xf0000010, 0xb0000008, + 0x40000000, 0x90000000, 0xf0000010, 0x90000018, 0x60000000, 0x90000010, + 0x90000010, 0x90000000, 0x80000000, 0x00000010, 0xa0000000, 0x20000000, + 0xa0000000, 0x20000010, 0x00000000, 0x20000010, 0x20000000, 0x00000010, + 0x20000000, 0x00000010, 0xa0000000, 0x00000000, 0x20000000, 0x20000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000020, + 0x00000001, 0x40000002, 0x40000040, 0x40000002, 0x80000004, 0x80000080, + 0x80000006, 0x00000049, 0x00000103, 0x80000009, 0x80000012, 0x80000202, + 0x00000018, 0x00000164, 0x00000408, 0x800000e6, 0x8000004c, 0x00000803, + 0x80000161, 0x80000599}}, + {1, 44, 0, 58, 0, 1, {0xb4000008, 0x08000000, 0x9800000c, 0xd8000010, 0x08000010, 0xb8000010, + 0x98000000, 0x60000000, 0x00000008, 0xc0000000, 0x90000014, 0x10000010, + 0xb8000014, 0x28000000, 0x20000010, 0x48000000, 0x08000018, 0x60000000, + 0x90000010, 0xf0000010, 0x90000008, 0xc0000000, 0x90000010, 0xf0000010, + 0xb0000008, 0x40000000, 0x90000000, 0xf0000010, 0x90000018, 0x60000000, + 0x90000010, 0x90000010, 0x90000000, 0x80000000, 0x00000010, 0xa0000000, + 0x20000000, 0xa0000000, 0x20000010, 0x00000000, 0x20000010, 0x20000000, + 0x00000010, 0x20000000, 0x00000010, 0xa0000000, 0x00000000, 0x20000000, + 0x20000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000001, + 0x00000020, 0x00000001, 0x40000002, 0x40000040, 0x40000002, 0x80000004, + 0x80000080, 0x80000006, 0x00000049, 0x00000103, 0x80000009, 0x80000012, + 0x80000202, 0x00000018, 0x00000164, 0x00000408, 0x800000e6, 0x8000004c, + 0x00000803, 0x80000161}}, + {1, 45, 0, 58, 0, 2, {0xf4000014, 0xb4000008, 0x08000000, 0x9800000c, 0xd8000010, 0x08000010, + 0xb8000010, 0x98000000, 0x60000000, 0x00000008, 0xc0000000, 0x90000014, + 0x10000010, 0xb8000014, 0x28000000, 0x20000010, 0x48000000, 0x08000018, + 0x60000000, 0x90000010, 0xf0000010, 0x90000008, 0xc0000000, 0x90000010, + 0xf0000010, 0xb0000008, 0x40000000, 0x90000000, 0xf0000010, 0x90000018, + 0x60000000, 0x90000010, 0x90000010, 0x90000000, 0x80000000, 0x00000010, + 0xa0000000, 0x20000000, 0xa0000000, 0x20000010, 0x00000000, 0x20000010, + 0x20000000, 0x00000010, 0x20000000, 0x00000010, 0xa0000000, 0x00000000, + 0x20000000, 0x20000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000001, 0x00000020, 0x00000001, 0x40000002, 0x40000040, 0x40000002, + 0x80000004, 0x80000080, 0x80000006, 0x00000049, 0x00000103, 0x80000009, + 0x80000012, 0x80000202, 0x00000018, 0x00000164, 0x00000408, 0x800000e6, + 0x8000004c, 0x00000803}}, + {1, 46, 0, 58, 0, 3, {0x2c000010, 0xf4000014, 0xb4000008, 0x08000000, 0x9800000c, 0xd8000010, + 0x08000010, 0xb8000010, 0x98000000, 0x60000000, 0x00000008, 0xc0000000, + 0x90000014, 0x10000010, 0xb8000014, 0x28000000, 0x20000010, 0x48000000, + 0x08000018, 0x60000000, 0x90000010, 0xf0000010, 0x90000008, 0xc0000000, + 0x90000010, 0xf0000010, 0xb0000008, 0x40000000, 0x90000000, 0xf0000010, + 0x90000018, 0x60000000, 0x90000010, 0x90000010, 0x90000000, 0x80000000, + 0x00000010, 0xa0000000, 0x20000000, 0xa0000000, 0x20000010, 0x00000000, + 0x20000010, 0x20000000, 0x00000010, 0x20000000, 0x00000010, 0xa0000000, + 0x00000000, 0x20000000, 0x20000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000001, 0x00000020, 0x00000001, 0x40000002, 0x40000040, + 0x40000002, 0x80000004, 0x80000080, 0x80000006, 0x00000049, 0x00000103, + 0x80000009, 0x80000012, 0x80000202, 0x00000018, 0x00000164, 0x00000408, + 0x800000e6, 0x8000004c}}, + {1, 46, 2, 58, 0, 4, {0xb0000040, 0xd0000053, 0xd0000022, 0x20000000, 0x60000032, 0x60000043, + 0x20000040, 0xe0000042, 0x60000002, 0x80000001, 0x00000020, 0x00000003, + 0x40000052, 0x40000040, 0xe0000052, 0xa0000000, 0x80000040, 0x20000001, + 0x20000060, 0x80000001, 0x40000042, 0xc0000043, 0x40000022, 0x00000003, + 0x40000042, 0xc0000043, 0xc0000022, 0x00000001, 0x40000002, 0xc0000043, + 0x40000062, 0x80000001, 0x40000042, 0x40000042, 0x40000002, 0x00000002, + 0x00000040, 0x80000002, 0x80000000, 0x80000002, 0x80000040, 0x00000000, + 0x80000040, 0x80000000, 0x00000040, 0x80000000, 0x00000040, 0x80000002, + 0x00000000, 0x80000000, 0x80000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000004, 0x00000080, 0x00000004, 0x00000009, 0x00000101, + 0x00000009, 0x00000012, 0x00000202, 0x0000001a, 0x00000124, 0x0000040c, + 0x00000026, 0x0000004a, 0x0000080a, 0x00000060, 0x00000590, 0x00001020, + 0x0000039a, 0x00000132}}, + {1, 47, 0, 58, 0, 5, {0xc8000010, 0x2c000010, 0xf4000014, 0xb4000008, 0x08000000, 0x9800000c, + 0xd8000010, 0x08000010, 0xb8000010, 0x98000000, 0x60000000, 0x00000008, + 0xc0000000, 0x90000014, 0x10000010, 0xb8000014, 0x28000000, 0x20000010, + 0x48000000, 0x08000018, 0x60000000, 0x90000010, 0xf0000010, 0x90000008, + 0xc0000000, 0x90000010, 0xf0000010, 0xb0000008, 0x40000000, 0x90000000, + 0xf0000010, 0x90000018, 0x60000000, 0x90000010, 0x90000010, 0x90000000, + 0x80000000, 0x00000010, 0xa0000000, 0x20000000, 0xa0000000, 0x20000010, + 0x00000000, 0x20000010, 0x20000000, 0x00000010, 0x20000000, 0x00000010, + 0xa0000000, 0x00000000, 0x20000000, 0x20000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000001, 0x00000020, 0x00000001, 0x40000002, + 0x40000040, 0x40000002, 0x80000004, 0x80000080, 0x80000006, 0x00000049, + 0x00000103, 0x80000009, 0x80000012, 0x80000202, 0x00000018, 0x00000164, + 0x00000408, 0x800000e6}}, + {1, 47, 2, 58, 0, 6, {0x20000043, 0xb0000040, 0xd0000053, 0xd0000022, 0x20000000, 0x60000032, + 0x60000043, 0x20000040, 0xe0000042, 0x60000002, 0x80000001, 0x00000020, + 0x00000003, 0x40000052, 0x40000040, 0xe0000052, 0xa0000000, 0x80000040, + 0x20000001, 0x20000060, 0x80000001, 0x40000042, 0xc0000043, 0x40000022, + 0x00000003, 0x40000042, 0xc0000043, 0xc0000022, 0x00000001, 0x40000002, + 0xc0000043, 0x40000062, 0x80000001, 0x40000042, 0x40000042, 0x40000002, + 0x00000002, 0x00000040, 0x80000002, 0x80000000, 0x80000002, 0x80000040, + 0x00000000, 0x80000040, 0x80000000, 0x00000040, 0x80000000, 0x00000040, + 0x80000002, 0x00000000, 0x80000000, 0x80000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000004, 0x00000080, 0x00000004, 0x00000009, + 0x00000101, 0x00000009, 0x00000012, 0x00000202, 0x0000001a, 0x00000124, + 0x0000040c, 0x00000026, 0x0000004a, 0x0000080a, 0x00000060, 0x00000590, + 0x00001020, 0x0000039a}}, + {1, 48, 0, 58, 0, 7, {0xb800000a, 0xc8000010, 0x2c000010, 0xf4000014, 0xb4000008, 0x08000000, + 0x9800000c, 0xd8000010, 0x08000010, 0xb8000010, 0x98000000, 0x60000000, + 0x00000008, 0xc0000000, 0x90000014, 0x10000010, 0xb8000014, 0x28000000, + 0x20000010, 0x48000000, 0x08000018, 0x60000000, 0x90000010, 0xf0000010, + 0x90000008, 0xc0000000, 0x90000010, 0xf0000010, 0xb0000008, 0x40000000, + 0x90000000, 0xf0000010, 0x90000018, 0x60000000, 0x90000010, 0x90000010, + 0x90000000, 0x80000000, 0x00000010, 0xa0000000, 0x20000000, 0xa0000000, + 0x20000010, 0x00000000, 0x20000010, 0x20000000, 0x00000010, 0x20000000, + 0x00000010, 0xa0000000, 0x00000000, 0x20000000, 0x20000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000020, 0x00000001, + 0x40000002, 0x40000040, 0x40000002, 0x80000004, 0x80000080, 0x80000006, + 0x00000049, 0x00000103, 0x80000009, 0x80000012, 0x80000202, 0x00000018, + 0x00000164, 0x00000408}}, + {1, 48, 2, 58, 0, 8, {0xe000002a, 0x20000043, 0xb0000040, 0xd0000053, 0xd0000022, 0x20000000, + 0x60000032, 0x60000043, 0x20000040, 0xe0000042, 0x60000002, 0x80000001, + 0x00000020, 0x00000003, 0x40000052, 0x40000040, 0xe0000052, 0xa0000000, + 0x80000040, 0x20000001, 0x20000060, 0x80000001, 0x40000042, 0xc0000043, + 0x40000022, 0x00000003, 0x40000042, 0xc0000043, 0xc0000022, 0x00000001, + 0x40000002, 0xc0000043, 0x40000062, 0x80000001, 0x40000042, 0x40000042, + 0x40000002, 0x00000002, 0x00000040, 0x80000002, 0x80000000, 0x80000002, + 0x80000040, 0x00000000, 0x80000040, 0x80000000, 0x00000040, 0x80000000, + 0x00000040, 0x80000002, 0x00000000, 0x80000000, 0x80000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000004, 0x00000080, 0x00000004, + 0x00000009, 0x00000101, 0x00000009, 0x00000012, 0x00000202, 0x0000001a, + 0x00000124, 0x0000040c, 0x00000026, 0x0000004a, 0x0000080a, 0x00000060, + 0x00000590, 0x00001020}}, + {1, 49, 0, 58, 0, 9, {0x18000000, 0xb800000a, 0xc8000010, 0x2c000010, 0xf4000014, 0xb4000008, + 0x08000000, 0x9800000c, 0xd8000010, 0x08000010, 0xb8000010, 0x98000000, + 0x60000000, 0x00000008, 0xc0000000, 0x90000014, 0x10000010, 0xb8000014, + 0x28000000, 0x20000010, 0x48000000, 0x08000018, 0x60000000, 0x90000010, + 0xf0000010, 0x90000008, 0xc0000000, 0x90000010, 0xf0000010, 0xb0000008, + 0x40000000, 0x90000000, 0xf0000010, 0x90000018, 0x60000000, 0x90000010, + 0x90000010, 0x90000000, 0x80000000, 0x00000010, 0xa0000000, 0x20000000, + 0xa0000000, 0x20000010, 0x00000000, 0x20000010, 0x20000000, 0x00000010, + 0x20000000, 0x00000010, 0xa0000000, 0x00000000, 0x20000000, 0x20000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000020, + 0x00000001, 0x40000002, 0x40000040, 0x40000002, 0x80000004, 0x80000080, + 0x80000006, 0x00000049, 0x00000103, 0x80000009, 0x80000012, 0x80000202, + 0x00000018, 0x00000164}}, + {1, 49, 2, 58, 0, 10, {0x60000000, 0xe000002a, 0x20000043, 0xb0000040, 0xd0000053, + 0xd0000022, 0x20000000, 0x60000032, 0x60000043, 0x20000040, + 0xe0000042, 0x60000002, 0x80000001, 0x00000020, 0x00000003, + 0x40000052, 0x40000040, 0xe0000052, 0xa0000000, 0x80000040, + 0x20000001, 0x20000060, 0x80000001, 0x40000042, 0xc0000043, + 0x40000022, 0x00000003, 0x40000042, 0xc0000043, 0xc0000022, + 0x00000001, 0x40000002, 0xc0000043, 0x40000062, 0x80000001, + 0x40000042, 0x40000042, 0x40000002, 0x00000002, 0x00000040, + 0x80000002, 0x80000000, 0x80000002, 0x80000040, 0x00000000, + 0x80000040, 0x80000000, 0x00000040, 0x80000000, 0x00000040, + 0x80000002, 0x00000000, 0x80000000, 0x80000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000004, + 0x00000080, 0x00000004, 0x00000009, 0x00000101, 0x00000009, + 0x00000012, 0x00000202, 0x0000001a, 0x00000124, 0x0000040c, + 0x00000026, 0x0000004a, 0x0000080a, 0x00000060, 0x00000590}}, + {1, 50, 0, 65, 0, 11, {0x0800000c, 0x18000000, 0xb800000a, 0xc8000010, 0x2c000010, + 0xf4000014, 0xb4000008, 0x08000000, 0x9800000c, 0xd8000010, + 0x08000010, 0xb8000010, 0x98000000, 0x60000000, 0x00000008, + 0xc0000000, 0x90000014, 0x10000010, 0xb8000014, 0x28000000, + 0x20000010, 0x48000000, 0x08000018, 0x60000000, 0x90000010, + 0xf0000010, 0x90000008, 0xc0000000, 0x90000010, 0xf0000010, + 0xb0000008, 0x40000000, 0x90000000, 0xf0000010, 0x90000018, + 0x60000000, 0x90000010, 0x90000010, 0x90000000, 0x80000000, + 0x00000010, 0xa0000000, 0x20000000, 0xa0000000, 0x20000010, + 0x00000000, 0x20000010, 0x20000000, 0x00000010, 0x20000000, + 0x00000010, 0xa0000000, 0x00000000, 0x20000000, 0x20000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000001, 0x00000020, 0x00000001, 0x40000002, 0x40000040, + 0x40000002, 0x80000004, 0x80000080, 0x80000006, 0x00000049, + 0x00000103, 0x80000009, 0x80000012, 0x80000202, 0x00000018}}, + {1, 50, 2, 65, 0, 12, {0x20000030, 0x60000000, 0xe000002a, 0x20000043, 0xb0000040, + 0xd0000053, 0xd0000022, 0x20000000, 0x60000032, 0x60000043, + 0x20000040, 0xe0000042, 0x60000002, 0x80000001, 0x00000020, + 0x00000003, 0x40000052, 0x40000040, 0xe0000052, 0xa0000000, + 0x80000040, 0x20000001, 0x20000060, 0x80000001, 0x40000042, + 0xc0000043, 0x40000022, 0x00000003, 0x40000042, 0xc0000043, + 0xc0000022, 0x00000001, 0x40000002, 0xc0000043, 0x40000062, + 0x80000001, 0x40000042, 0x40000042, 0x40000002, 0x00000002, + 0x00000040, 0x80000002, 0x80000000, 0x80000002, 0x80000040, + 0x00000000, 0x80000040, 0x80000000, 0x00000040, 0x80000000, + 0x00000040, 0x80000002, 0x00000000, 0x80000000, 0x80000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000004, 0x00000080, 0x00000004, 0x00000009, 0x00000101, + 0x00000009, 0x00000012, 0x00000202, 0x0000001a, 0x00000124, + 0x0000040c, 0x00000026, 0x0000004a, 0x0000080a, 0x00000060}}, + {1, 51, 0, 65, 0, 13, {0xe8000000, 0x0800000c, 0x18000000, 0xb800000a, 0xc8000010, + 0x2c000010, 0xf4000014, 0xb4000008, 0x08000000, 0x9800000c, + 0xd8000010, 0x08000010, 0xb8000010, 0x98000000, 0x60000000, + 0x00000008, 0xc0000000, 0x90000014, 0x10000010, 0xb8000014, + 0x28000000, 0x20000010, 0x48000000, 0x08000018, 0x60000000, + 0x90000010, 0xf0000010, 0x90000008, 0xc0000000, 0x90000010, + 0xf0000010, 0xb0000008, 0x40000000, 0x90000000, 0xf0000010, + 0x90000018, 0x60000000, 0x90000010, 0x90000010, 0x90000000, + 0x80000000, 0x00000010, 0xa0000000, 0x20000000, 0xa0000000, + 0x20000010, 0x00000000, 0x20000010, 0x20000000, 0x00000010, + 0x20000000, 0x00000010, 0xa0000000, 0x00000000, 0x20000000, + 0x20000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000001, 0x00000020, 0x00000001, 0x40000002, + 0x40000040, 0x40000002, 0x80000004, 0x80000080, 0x80000006, + 0x00000049, 0x00000103, 0x80000009, 0x80000012, 0x80000202}}, + {1, 51, 2, 65, 0, 14, {0xa0000003, 0x20000030, 0x60000000, 0xe000002a, 0x20000043, + 0xb0000040, 0xd0000053, 0xd0000022, 0x20000000, 0x60000032, + 0x60000043, 0x20000040, 0xe0000042, 0x60000002, 0x80000001, + 0x00000020, 0x00000003, 0x40000052, 0x40000040, 0xe0000052, + 0xa0000000, 0x80000040, 0x20000001, 0x20000060, 0x80000001, + 0x40000042, 0xc0000043, 0x40000022, 0x00000003, 0x40000042, + 0xc0000043, 0xc0000022, 0x00000001, 0x40000002, 0xc0000043, + 0x40000062, 0x80000001, 0x40000042, 0x40000042, 0x40000002, + 0x00000002, 0x00000040, 0x80000002, 0x80000000, 0x80000002, + 0x80000040, 0x00000000, 0x80000040, 0x80000000, 0x00000040, + 0x80000000, 0x00000040, 0x80000002, 0x00000000, 0x80000000, + 0x80000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000004, 0x00000080, 0x00000004, 0x00000009, + 0x00000101, 0x00000009, 0x00000012, 0x00000202, 0x0000001a, + 0x00000124, 0x0000040c, 0x00000026, 0x0000004a, 0x0000080a}}, + {1, 52, 0, 65, 0, 15, {0x04000010, 0xe8000000, 0x0800000c, 0x18000000, 0xb800000a, + 0xc8000010, 0x2c000010, 0xf4000014, 0xb4000008, 0x08000000, + 0x9800000c, 0xd8000010, 0x08000010, 0xb8000010, 0x98000000, + 0x60000000, 0x00000008, 0xc0000000, 0x90000014, 0x10000010, + 0xb8000014, 0x28000000, 0x20000010, 0x48000000, 0x08000018, + 0x60000000, 0x90000010, 0xf0000010, 0x90000008, 0xc0000000, + 0x90000010, 0xf0000010, 0xb0000008, 0x40000000, 0x90000000, + 0xf0000010, 0x90000018, 0x60000000, 0x90000010, 0x90000010, + 0x90000000, 0x80000000, 0x00000010, 0xa0000000, 0x20000000, + 0xa0000000, 0x20000010, 0x00000000, 0x20000010, 0x20000000, + 0x00000010, 0x20000000, 0x00000010, 0xa0000000, 0x00000000, + 0x20000000, 0x20000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000001, 0x00000020, 0x00000001, + 0x40000002, 0x40000040, 0x40000002, 0x80000004, 0x80000080, + 0x80000006, 0x00000049, 0x00000103, 0x80000009, 0x80000012}}, + {2, 45, 0, 58, 0, 16, {0xec000014, 0x0c000002, 0xc0000010, 0xb400001c, 0x2c000004, + 0xbc000018, 0xb0000010, 0x0000000c, 0xb8000010, 0x08000018, + 0x78000010, 0x08000014, 0x70000010, 0xb800001c, 0xe8000000, + 0xb0000004, 0x58000010, 0xb000000c, 0x48000000, 0xb0000000, + 0xb8000010, 0x98000010, 0xa0000000, 0x00000000, 0x00000000, + 0x20000000, 0x80000000, 0x00000010, 0x00000000, 0x20000010, + 0x20000000, 0x00000010, 0x60000000, 0x00000018, 0xe0000000, + 0x90000000, 0x30000010, 0xb0000000, 0x20000000, 0x20000000, + 0xa0000000, 0x00000010, 0x80000000, 0x20000000, 0x20000000, + 0x20000000, 0x80000000, 0x00000010, 0x00000000, 0x20000010, + 0xa0000000, 0x00000000, 0x20000000, 0x20000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000001, 0x00000020, 0x00000001, 0x40000002, 0x40000041, + 0x40000022, 0x80000005, 0xc0000082, 0xc0000046, 0x4000004b, + 0x80000107, 0x00000089, 0x00000014, 0x8000024b, 0x0000011b, + 0x8000016d, 0x8000041a, 0x000002e4, 0x80000054, 0x00000967}}, + {2, 46, 0, 58, 0, 17, {0x2400001c, 0xec000014, 0x0c000002, 0xc0000010, 0xb400001c, + 0x2c000004, 0xbc000018, 0xb0000010, 0x0000000c, 0xb8000010, + 0x08000018, 0x78000010, 0x08000014, 0x70000010, 0xb800001c, + 0xe8000000, 0xb0000004, 0x58000010, 0xb000000c, 0x48000000, + 0xb0000000, 0xb8000010, 0x98000010, 0xa0000000, 0x00000000, + 0x00000000, 0x20000000, 0x80000000, 0x00000010, 0x00000000, + 0x20000010, 0x20000000, 0x00000010, 0x60000000, 0x00000018, + 0xe0000000, 0x90000000, 0x30000010, 0xb0000000, 0x20000000, + 0x20000000, 0xa0000000, 0x00000010, 0x80000000, 0x20000000, + 0x20000000, 0x20000000, 0x80000000, 0x00000010, 0x00000000, + 0x20000010, 0xa0000000, 0x00000000, 0x20000000, 0x20000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000001, 0x00000020, 0x00000001, 0x40000002, + 0x40000041, 0x40000022, 0x80000005, 0xc0000082, 0xc0000046, + 0x4000004b, 0x80000107, 0x00000089, 0x00000014, 0x8000024b, + 0x0000011b, 0x8000016d, 0x8000041a, 0x000002e4, 0x80000054}}, + {2, 46, 2, 58, 0, 18, {0x90000070, 0xb0000053, 0x30000008, 0x00000043, 0xd0000072, + 0xb0000010, 0xf0000062, 0xc0000042, 0x00000030, 0xe0000042, + 0x20000060, 0xe0000041, 0x20000050, 0xc0000041, 0xe0000072, + 0xa0000003, 0xc0000012, 0x60000041, 0xc0000032, 0x20000001, + 0xc0000002, 0xe0000042, 0x60000042, 0x80000002, 0x00000000, + 0x00000000, 0x80000000, 0x00000002, 0x00000040, 0x00000000, + 0x80000040, 0x80000000, 0x00000040, 0x80000001, 0x00000060, + 0x80000003, 0x40000002, 0xc0000040, 0xc0000002, 0x80000000, + 0x80000000, 0x80000002, 0x00000040, 0x00000002, 0x80000000, + 0x80000000, 0x80000000, 0x00000002, 0x00000040, 0x00000000, + 0x80000040, 0x80000002, 0x00000000, 0x80000000, 0x80000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000004, 0x00000080, 0x00000004, 0x00000009, + 0x00000105, 0x00000089, 0x00000016, 0x0000020b, 0x0000011b, + 0x0000012d, 0x0000041e, 0x00000224, 0x00000050, 0x0000092e, + 0x0000046c, 0x000005b6, 0x0000106a, 0x00000b90, 0x00000152}}, + {2, 47, 0, 58, 0, 19, {0x20000010, 0x2400001c, 0xec000014, 0x0c000002, 0xc0000010, + 0xb400001c, 0x2c000004, 0xbc000018, 0xb0000010, 0x0000000c, + 0xb8000010, 0x08000018, 0x78000010, 0x08000014, 0x70000010, + 0xb800001c, 0xe8000000, 0xb0000004, 0x58000010, 0xb000000c, + 0x48000000, 0xb0000000, 0xb8000010, 0x98000010, 0xa0000000, + 0x00000000, 0x00000000, 0x20000000, 0x80000000, 0x00000010, + 0x00000000, 0x20000010, 0x20000000, 0x00000010, 0x60000000, + 0x00000018, 0xe0000000, 0x90000000, 0x30000010, 0xb0000000, + 0x20000000, 0x20000000, 0xa0000000, 0x00000010, 0x80000000, + 0x20000000, 0x20000000, 0x20000000, 0x80000000, 0x00000010, + 0x00000000, 0x20000010, 0xa0000000, 0x00000000, 0x20000000, + 0x20000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000001, 0x00000020, 0x00000001, + 0x40000002, 0x40000041, 0x40000022, 0x80000005, 0xc0000082, + 0xc0000046, 0x4000004b, 0x80000107, 0x00000089, 0x00000014, + 0x8000024b, 0x0000011b, 0x8000016d, 0x8000041a, 0x000002e4}}, + {2, 48, 0, 58, 0, 20, {0xbc00001a, 0x20000010, 0x2400001c, 0xec000014, 0x0c000002, + 0xc0000010, 0xb400001c, 0x2c000004, 0xbc000018, 0xb0000010, + 0x0000000c, 0xb8000010, 0x08000018, 0x78000010, 0x08000014, + 0x70000010, 0xb800001c, 0xe8000000, 0xb0000004, 0x58000010, + 0xb000000c, 0x48000000, 0xb0000000, 0xb8000010, 0x98000010, + 0xa0000000, 0x00000000, 0x00000000, 0x20000000, 0x80000000, + 0x00000010, 0x00000000, 0x20000010, 0x20000000, 0x00000010, + 0x60000000, 0x00000018, 0xe0000000, 0x90000000, 0x30000010, + 0xb0000000, 0x20000000, 0x20000000, 0xa0000000, 0x00000010, + 0x80000000, 0x20000000, 0x20000000, 0x20000000, 0x80000000, + 0x00000010, 0x00000000, 0x20000010, 0xa0000000, 0x00000000, + 0x20000000, 0x20000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000020, + 0x00000001, 0x40000002, 0x40000041, 0x40000022, 0x80000005, + 0xc0000082, 0xc0000046, 0x4000004b, 0x80000107, 0x00000089, + 0x00000014, 0x8000024b, 0x0000011b, 0x8000016d, 0x8000041a}}, + {2, 49, 0, 58, 0, 21, {0x3c000004, 0xbc00001a, 0x20000010, 0x2400001c, 0xec000014, + 0x0c000002, 0xc0000010, 0xb400001c, 0x2c000004, 0xbc000018, + 0xb0000010, 0x0000000c, 0xb8000010, 0x08000018, 0x78000010, + 0x08000014, 0x70000010, 0xb800001c, 0xe8000000, 0xb0000004, + 0x58000010, 0xb000000c, 0x48000000, 0xb0000000, 0xb8000010, + 0x98000010, 0xa0000000, 0x00000000, 0x00000000, 0x20000000, + 0x80000000, 0x00000010, 0x00000000, 0x20000010, 0x20000000, + 0x00000010, 0x60000000, 0x00000018, 0xe0000000, 0x90000000, + 0x30000010, 0xb0000000, 0x20000000, 0x20000000, 0xa0000000, + 0x00000010, 0x80000000, 0x20000000, 0x20000000, 0x20000000, + 0x80000000, 0x00000010, 0x00000000, 0x20000010, 0xa0000000, + 0x00000000, 0x20000000, 0x20000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000001, + 0x00000020, 0x00000001, 0x40000002, 0x40000041, 0x40000022, + 0x80000005, 0xc0000082, 0xc0000046, 0x4000004b, 0x80000107, + 0x00000089, 0x00000014, 0x8000024b, 0x0000011b, 0x8000016d}}, + {2, 49, 2, 58, 0, 22, {0xf0000010, 0xf000006a, 0x80000040, 0x90000070, 0xb0000053, + 0x30000008, 0x00000043, 0xd0000072, 0xb0000010, 0xf0000062, + 0xc0000042, 0x00000030, 0xe0000042, 0x20000060, 0xe0000041, + 0x20000050, 0xc0000041, 0xe0000072, 0xa0000003, 0xc0000012, + 0x60000041, 0xc0000032, 0x20000001, 0xc0000002, 0xe0000042, + 0x60000042, 0x80000002, 0x00000000, 0x00000000, 0x80000000, + 0x00000002, 0x00000040, 0x00000000, 0x80000040, 0x80000000, + 0x00000040, 0x80000001, 0x00000060, 0x80000003, 0x40000002, + 0xc0000040, 0xc0000002, 0x80000000, 0x80000000, 0x80000002, + 0x00000040, 0x00000002, 0x80000000, 0x80000000, 0x80000000, + 0x00000002, 0x00000040, 0x00000000, 0x80000040, 0x80000002, + 0x00000000, 0x80000000, 0x80000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000004, + 0x00000080, 0x00000004, 0x00000009, 0x00000105, 0x00000089, + 0x00000016, 0x0000020b, 0x0000011b, 0x0000012d, 0x0000041e, + 0x00000224, 0x00000050, 0x0000092e, 0x0000046c, 0x000005b6}}, + {2, 50, 0, 65, 0, 23, {0xb400001c, 0x3c000004, 0xbc00001a, 0x20000010, 0x2400001c, + 0xec000014, 0x0c000002, 0xc0000010, 0xb400001c, 0x2c000004, + 0xbc000018, 0xb0000010, 0x0000000c, 0xb8000010, 0x08000018, + 0x78000010, 0x08000014, 0x70000010, 0xb800001c, 0xe8000000, + 0xb0000004, 0x58000010, 0xb000000c, 0x48000000, 0xb0000000, + 0xb8000010, 0x98000010, 0xa0000000, 0x00000000, 0x00000000, + 0x20000000, 0x80000000, 0x00000010, 0x00000000, 0x20000010, + 0x20000000, 0x00000010, 0x60000000, 0x00000018, 0xe0000000, + 0x90000000, 0x30000010, 0xb0000000, 0x20000000, 0x20000000, + 0xa0000000, 0x00000010, 0x80000000, 0x20000000, 0x20000000, + 0x20000000, 0x80000000, 0x00000010, 0x00000000, 0x20000010, + 0xa0000000, 0x00000000, 0x20000000, 0x20000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000001, 0x00000020, 0x00000001, 0x40000002, 0x40000041, + 0x40000022, 0x80000005, 0xc0000082, 0xc0000046, 0x4000004b, + 0x80000107, 0x00000089, 0x00000014, 0x8000024b, 0x0000011b}}, + {2, 50, 2, 65, 0, 24, {0xd0000072, 0xf0000010, 0xf000006a, 0x80000040, 0x90000070, + 0xb0000053, 0x30000008, 0x00000043, 0xd0000072, 0xb0000010, + 0xf0000062, 0xc0000042, 0x00000030, 0xe0000042, 0x20000060, + 0xe0000041, 0x20000050, 0xc0000041, 0xe0000072, 0xa0000003, + 0xc0000012, 0x60000041, 0xc0000032, 0x20000001, 0xc0000002, + 0xe0000042, 0x60000042, 0x80000002, 0x00000000, 0x00000000, + 0x80000000, 0x00000002, 0x00000040, 0x00000000, 0x80000040, + 0x80000000, 0x00000040, 0x80000001, 0x00000060, 0x80000003, + 0x40000002, 0xc0000040, 0xc0000002, 0x80000000, 0x80000000, + 0x80000002, 0x00000040, 0x00000002, 0x80000000, 0x80000000, + 0x80000000, 0x00000002, 0x00000040, 0x00000000, 0x80000040, + 0x80000002, 0x00000000, 0x80000000, 0x80000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000004, 0x00000080, 0x00000004, 0x00000009, 0x00000105, + 0x00000089, 0x00000016, 0x0000020b, 0x0000011b, 0x0000012d, + 0x0000041e, 0x00000224, 0x00000050, 0x0000092e, 0x0000046c}}, + {2, 51, 0, 65, 0, 25, {0xc0000010, 0xb400001c, 0x3c000004, 0xbc00001a, 0x20000010, + 0x2400001c, 0xec000014, 0x0c000002, 0xc0000010, 0xb400001c, + 0x2c000004, 0xbc000018, 0xb0000010, 0x0000000c, 0xb8000010, + 0x08000018, 0x78000010, 0x08000014, 0x70000010, 0xb800001c, + 0xe8000000, 0xb0000004, 0x58000010, 0xb000000c, 0x48000000, + 0xb0000000, 0xb8000010, 0x98000010, 0xa0000000, 0x00000000, + 0x00000000, 0x20000000, 0x80000000, 0x00000010, 0x00000000, + 0x20000010, 0x20000000, 0x00000010, 0x60000000, 0x00000018, + 0xe0000000, 0x90000000, 0x30000010, 0xb0000000, 0x20000000, + 0x20000000, 0xa0000000, 0x00000010, 0x80000000, 0x20000000, + 0x20000000, 0x20000000, 0x80000000, 0x00000010, 0x00000000, + 0x20000010, 0xa0000000, 0x00000000, 0x20000000, 0x20000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000001, 0x00000020, 0x00000001, 0x40000002, + 0x40000041, 0x40000022, 0x80000005, 0xc0000082, 0xc0000046, + 0x4000004b, 0x80000107, 0x00000089, 0x00000014, 0x8000024b}}, + {2, 51, 2, 65, 0, 26, {0x00000043, 0xd0000072, 0xf0000010, 0xf000006a, 0x80000040, + 0x90000070, 0xb0000053, 0x30000008, 0x00000043, 0xd0000072, + 0xb0000010, 0xf0000062, 0xc0000042, 0x00000030, 0xe0000042, + 0x20000060, 0xe0000041, 0x20000050, 0xc0000041, 0xe0000072, + 0xa0000003, 0xc0000012, 0x60000041, 0xc0000032, 0x20000001, + 0xc0000002, 0xe0000042, 0x60000042, 0x80000002, 0x00000000, + 0x00000000, 0x80000000, 0x00000002, 0x00000040, 0x00000000, + 0x80000040, 0x80000000, 0x00000040, 0x80000001, 0x00000060, + 0x80000003, 0x40000002, 0xc0000040, 0xc0000002, 0x80000000, + 0x80000000, 0x80000002, 0x00000040, 0x00000002, 0x80000000, + 0x80000000, 0x80000000, 0x00000002, 0x00000040, 0x00000000, + 0x80000040, 0x80000002, 0x00000000, 0x80000000, 0x80000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000004, 0x00000080, 0x00000004, 0x00000009, + 0x00000105, 0x00000089, 0x00000016, 0x0000020b, 0x0000011b, + 0x0000012d, 0x0000041e, 0x00000224, 0x00000050, 0x0000092e}}, + {2, 52, 0, 65, 0, 27, {0x0c000002, 0xc0000010, 0xb400001c, 0x3c000004, 0xbc00001a, + 0x20000010, 0x2400001c, 0xec000014, 0x0c000002, 0xc0000010, + 0xb400001c, 0x2c000004, 0xbc000018, 0xb0000010, 0x0000000c, + 0xb8000010, 0x08000018, 0x78000010, 0x08000014, 0x70000010, + 0xb800001c, 0xe8000000, 0xb0000004, 0x58000010, 0xb000000c, + 0x48000000, 0xb0000000, 0xb8000010, 0x98000010, 0xa0000000, + 0x00000000, 0x00000000, 0x20000000, 0x80000000, 0x00000010, + 0x00000000, 0x20000010, 0x20000000, 0x00000010, 0x60000000, + 0x00000018, 0xe0000000, 0x90000000, 0x30000010, 0xb0000000, + 0x20000000, 0x20000000, 0xa0000000, 0x00000010, 0x80000000, + 0x20000000, 0x20000000, 0x20000000, 0x80000000, 0x00000010, + 0x00000000, 0x20000010, 0xa0000000, 0x00000000, 0x20000000, + 0x20000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000001, 0x00000020, 0x00000001, + 0x40000002, 0x40000041, 0x40000022, 0x80000005, 0xc0000082, + 0xc0000046, 0x4000004b, 0x80000107, 0x00000089, 0x00000014}}, + {2, 53, 0, 65, 0, 28, {0xcc000014, 0x0c000002, 0xc0000010, 0xb400001c, 0x3c000004, + 0xbc00001a, 0x20000010, 0x2400001c, 0xec000014, 0x0c000002, + 0xc0000010, 0xb400001c, 0x2c000004, 0xbc000018, 0xb0000010, + 0x0000000c, 0xb8000010, 0x08000018, 0x78000010, 0x08000014, + 0x70000010, 0xb800001c, 0xe8000000, 0xb0000004, 0x58000010, + 0xb000000c, 0x48000000, 0xb0000000, 0xb8000010, 0x98000010, + 0xa0000000, 0x00000000, 0x00000000, 0x20000000, 0x80000000, + 0x00000010, 0x00000000, 0x20000010, 0x20000000, 0x00000010, + 0x60000000, 0x00000018, 0xe0000000, 0x90000000, 0x30000010, + 0xb0000000, 0x20000000, 0x20000000, 0xa0000000, 0x00000010, + 0x80000000, 0x20000000, 0x20000000, 0x20000000, 0x80000000, + 0x00000010, 0x00000000, 0x20000010, 0xa0000000, 0x00000000, + 0x20000000, 0x20000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000020, + 0x00000001, 0x40000002, 0x40000041, 0x40000022, 0x80000005, + 0xc0000082, 0xc0000046, 0x4000004b, 0x80000107, 0x00000089}}, + {2, 54, 0, 65, 0, 29, {0x0400001c, 0xcc000014, 0x0c000002, 0xc0000010, 0xb400001c, + 0x3c000004, 0xbc00001a, 0x20000010, 0x2400001c, 0xec000014, + 0x0c000002, 0xc0000010, 0xb400001c, 0x2c000004, 0xbc000018, + 0xb0000010, 0x0000000c, 0xb8000010, 0x08000018, 0x78000010, + 0x08000014, 0x70000010, 0xb800001c, 0xe8000000, 0xb0000004, + 0x58000010, 0xb000000c, 0x48000000, 0xb0000000, 0xb8000010, + 0x98000010, 0xa0000000, 0x00000000, 0x00000000, 0x20000000, + 0x80000000, 0x00000010, 0x00000000, 0x20000010, 0x20000000, + 0x00000010, 0x60000000, 0x00000018, 0xe0000000, 0x90000000, + 0x30000010, 0xb0000000, 0x20000000, 0x20000000, 0xa0000000, + 0x00000010, 0x80000000, 0x20000000, 0x20000000, 0x20000000, + 0x80000000, 0x00000010, 0x00000000, 0x20000010, 0xa0000000, + 0x00000000, 0x20000000, 0x20000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000001, + 0x00000020, 0x00000001, 0x40000002, 0x40000041, 0x40000022, + 0x80000005, 0xc0000082, 0xc0000046, 0x4000004b, 0x80000107}}, + {2, 55, 0, 65, 0, 30, {0x00000010, 0x0400001c, 0xcc000014, 0x0c000002, 0xc0000010, + 0xb400001c, 0x3c000004, 0xbc00001a, 0x20000010, 0x2400001c, + 0xec000014, 0x0c000002, 0xc0000010, 0xb400001c, 0x2c000004, + 0xbc000018, 0xb0000010, 0x0000000c, 0xb8000010, 0x08000018, + 0x78000010, 0x08000014, 0x70000010, 0xb800001c, 0xe8000000, + 0xb0000004, 0x58000010, 0xb000000c, 0x48000000, 0xb0000000, + 0xb8000010, 0x98000010, 0xa0000000, 0x00000000, 0x00000000, + 0x20000000, 0x80000000, 0x00000010, 0x00000000, 0x20000010, + 0x20000000, 0x00000010, 0x60000000, 0x00000018, 0xe0000000, + 0x90000000, 0x30000010, 0xb0000000, 0x20000000, 0x20000000, + 0xa0000000, 0x00000010, 0x80000000, 0x20000000, 0x20000000, + 0x20000000, 0x80000000, 0x00000010, 0x00000000, 0x20000010, + 0xa0000000, 0x00000000, 0x20000000, 0x20000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000001, 0x00000020, 0x00000001, 0x40000002, 0x40000041, + 0x40000022, 0x80000005, 0xc0000082, 0xc0000046, 0x4000004b}}, + {2, 56, 0, 65, 0, 31, {0x2600001a, 0x00000010, 0x0400001c, 0xcc000014, 0x0c000002, + 0xc0000010, 0xb400001c, 0x3c000004, 0xbc00001a, 0x20000010, + 0x2400001c, 0xec000014, 0x0c000002, 0xc0000010, 0xb400001c, + 0x2c000004, 0xbc000018, 0xb0000010, 0x0000000c, 0xb8000010, + 0x08000018, 0x78000010, 0x08000014, 0x70000010, 0xb800001c, + 0xe8000000, 0xb0000004, 0x58000010, 0xb000000c, 0x48000000, + 0xb0000000, 0xb8000010, 0x98000010, 0xa0000000, 0x00000000, + 0x00000000, 0x20000000, 0x80000000, 0x00000010, 0x00000000, + 0x20000010, 0x20000000, 0x00000010, 0x60000000, 0x00000018, + 0xe0000000, 0x90000000, 0x30000010, 0xb0000000, 0x20000000, + 0x20000000, 0xa0000000, 0x00000010, 0x80000000, 0x20000000, + 0x20000000, 0x20000000, 0x80000000, 0x00000010, 0x00000000, + 0x20000010, 0xa0000000, 0x00000000, 0x20000000, 0x20000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000001, 0x00000020, 0x00000001, 0x40000002, + 0x40000041, 0x40000022, 0x80000005, 0xc0000082, 0xc0000046}}, + {0, 0, 0, 0, 0, 0, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}}; +void +ubc_check(const uint32_t W[80], uint32_t dvmask[1]) +{ + uint32_t mask = ~((uint32_t)(0)); + mask &= (((((W[44] ^ W[45]) >> 29) & 1) - 1) | + ~(DV_I_48_0_bit | DV_I_51_0_bit | DV_I_52_0_bit | DV_II_45_0_bit | + DV_II_46_0_bit | DV_II_50_0_bit | DV_II_51_0_bit)); + mask &= (((((W[49] ^ W[50]) >> 29) & 1) - 1) | + ~(DV_I_46_0_bit | DV_II_45_0_bit | DV_II_50_0_bit | DV_II_51_0_bit | + DV_II_55_0_bit | DV_II_56_0_bit)); + mask &= (((((W[48] ^ W[49]) >> 29) & 1) - 1) | + ~(DV_I_45_0_bit | DV_I_52_0_bit | DV_II_49_0_bit | DV_II_50_0_bit | + DV_II_54_0_bit | DV_II_55_0_bit)); + mask &= ((((W[47] ^ (W[50] >> 25)) & (1 << 4)) - (1 << 4)) | + ~(DV_I_47_0_bit | DV_I_49_0_bit | DV_I_51_0_bit | DV_II_45_0_bit | + DV_II_51_0_bit | DV_II_56_0_bit)); + mask &= (((((W[47] ^ W[48]) >> 29) & 1) - 1) | + ~(DV_I_44_0_bit | DV_I_51_0_bit | DV_II_48_0_bit | DV_II_49_0_bit | + DV_II_53_0_bit | DV_II_54_0_bit)); + mask &= (((((W[46] >> 4) ^ (W[49] >> 29)) & 1) - 1) | + ~(DV_I_46_0_bit | DV_I_48_0_bit | DV_I_50_0_bit | DV_I_52_0_bit | DV_II_50_0_bit | + DV_II_55_0_bit)); + mask &= (((((W[46] ^ W[47]) >> 29) & 1) - 1) | + ~(DV_I_43_0_bit | DV_I_50_0_bit | DV_II_47_0_bit | DV_II_48_0_bit | + DV_II_52_0_bit | DV_II_53_0_bit)); + mask &= (((((W[45] >> 4) ^ (W[48] >> 29)) & 1) - 1) | + ~(DV_I_45_0_bit | DV_I_47_0_bit | DV_I_49_0_bit | DV_I_51_0_bit | DV_II_49_0_bit | + DV_II_54_0_bit)); + mask &= (((((W[45] ^ W[46]) >> 29) & 1) - 1) | + ~(DV_I_49_0_bit | DV_I_52_0_bit | DV_II_46_0_bit | DV_II_47_0_bit | + DV_II_51_0_bit | DV_II_52_0_bit)); + mask &= (((((W[44] >> 4) ^ (W[47] >> 29)) & 1) - 1) | + ~(DV_I_44_0_bit | DV_I_46_0_bit | DV_I_48_0_bit | DV_I_50_0_bit | DV_II_48_0_bit | + DV_II_53_0_bit)); + mask &= (((((W[43] >> 4) ^ (W[46] >> 29)) & 1) - 1) | + ~(DV_I_43_0_bit | DV_I_45_0_bit | DV_I_47_0_bit | DV_I_49_0_bit | DV_II_47_0_bit | + DV_II_52_0_bit)); + mask &= (((((W[43] ^ W[44]) >> 29) & 1) - 1) | + ~(DV_I_47_0_bit | DV_I_50_0_bit | DV_I_51_0_bit | DV_II_45_0_bit | + DV_II_49_0_bit | DV_II_50_0_bit)); + mask &= (((((W[42] >> 4) ^ (W[45] >> 29)) & 1) - 1) | + ~(DV_I_44_0_bit | DV_I_46_0_bit | DV_I_48_0_bit | DV_I_52_0_bit | DV_II_46_0_bit | + DV_II_51_0_bit)); + mask &= (((((W[41] >> 4) ^ (W[44] >> 29)) & 1) - 1) | + ~(DV_I_43_0_bit | DV_I_45_0_bit | DV_I_47_0_bit | DV_I_51_0_bit | DV_II_45_0_bit | + DV_II_50_0_bit)); + mask &= (((((W[40] ^ W[41]) >> 29) & 1) - 1) | + ~(DV_I_44_0_bit | DV_I_47_0_bit | DV_I_48_0_bit | DV_II_46_0_bit | + DV_II_47_0_bit | DV_II_56_0_bit)); + mask &= + (((((W[54] ^ W[55]) >> 29) & 1) - 1) | + ~(DV_I_51_0_bit | DV_II_47_0_bit | DV_II_50_0_bit | DV_II_55_0_bit | DV_II_56_0_bit)); + mask &= + (((((W[53] ^ W[54]) >> 29) & 1) - 1) | + ~(DV_I_50_0_bit | DV_II_46_0_bit | DV_II_49_0_bit | DV_II_54_0_bit | DV_II_55_0_bit)); + mask &= + (((((W[52] ^ W[53]) >> 29) & 1) - 1) | + ~(DV_I_49_0_bit | DV_II_45_0_bit | DV_II_48_0_bit | DV_II_53_0_bit | DV_II_54_0_bit)); + mask &= + ((((W[50] ^ (W[53] >> 25)) & (1 << 4)) - (1 << 4)) | + ~(DV_I_50_0_bit | DV_I_52_0_bit | DV_II_46_0_bit | DV_II_48_0_bit | DV_II_54_0_bit)); + mask &= + (((((W[50] ^ W[51]) >> 29) & 1) - 1) | + ~(DV_I_47_0_bit | DV_II_46_0_bit | DV_II_51_0_bit | DV_II_52_0_bit | DV_II_56_0_bit)); + mask &= + ((((W[49] ^ (W[52] >> 25)) & (1 << 4)) - (1 << 4)) | + ~(DV_I_49_0_bit | DV_I_51_0_bit | DV_II_45_0_bit | DV_II_47_0_bit | DV_II_53_0_bit)); + mask &= + ((((W[48] ^ (W[51] >> 25)) & (1 << 4)) - (1 << 4)) | + ~(DV_I_48_0_bit | DV_I_50_0_bit | DV_I_52_0_bit | DV_II_46_0_bit | DV_II_52_0_bit)); + mask &= + (((((W[42] ^ W[43]) >> 29) & 1) - 1) | + ~(DV_I_46_0_bit | DV_I_49_0_bit | DV_I_50_0_bit | DV_II_48_0_bit | DV_II_49_0_bit)); + mask &= + (((((W[41] ^ W[42]) >> 29) & 1) - 1) | + ~(DV_I_45_0_bit | DV_I_48_0_bit | DV_I_49_0_bit | DV_II_47_0_bit | DV_II_48_0_bit)); + mask &= + (((((W[40] >> 4) ^ (W[43] >> 29)) & 1) - 1) | + ~(DV_I_44_0_bit | DV_I_46_0_bit | DV_I_50_0_bit | DV_II_49_0_bit | DV_II_56_0_bit)); + mask &= + (((((W[39] >> 4) ^ (W[42] >> 29)) & 1) - 1) | + ~(DV_I_43_0_bit | DV_I_45_0_bit | DV_I_49_0_bit | DV_II_48_0_bit | DV_II_55_0_bit)); + if (mask & + (DV_I_44_0_bit | DV_I_48_0_bit | DV_II_47_0_bit | DV_II_54_0_bit | DV_II_56_0_bit)) + mask &= (((((W[38] >> 4) ^ (W[41] >> 29)) & 1) - 1) | + ~(DV_I_44_0_bit | DV_I_48_0_bit | DV_II_47_0_bit | DV_II_54_0_bit | + DV_II_56_0_bit)); + mask &= + (((((W[37] >> 4) ^ (W[40] >> 29)) & 1) - 1) | + ~(DV_I_43_0_bit | DV_I_47_0_bit | DV_II_46_0_bit | DV_II_53_0_bit | DV_II_55_0_bit)); + if (mask & (DV_I_52_0_bit | DV_II_48_0_bit | DV_II_51_0_bit | DV_II_56_0_bit)) + mask &= (((((W[55] ^ W[56]) >> 29) & 1) - 1) | + ~(DV_I_52_0_bit | DV_II_48_0_bit | DV_II_51_0_bit | DV_II_56_0_bit)); + if (mask & (DV_I_52_0_bit | DV_II_48_0_bit | DV_II_50_0_bit | DV_II_56_0_bit)) + mask &= ((((W[52] ^ (W[55] >> 25)) & (1 << 4)) - (1 << 4)) | + ~(DV_I_52_0_bit | DV_II_48_0_bit | DV_II_50_0_bit | DV_II_56_0_bit)); + if (mask & (DV_I_51_0_bit | DV_II_47_0_bit | DV_II_49_0_bit | DV_II_55_0_bit)) + mask &= ((((W[51] ^ (W[54] >> 25)) & (1 << 4)) - (1 << 4)) | + ~(DV_I_51_0_bit | DV_II_47_0_bit | DV_II_49_0_bit | DV_II_55_0_bit)); + if (mask & (DV_I_48_0_bit | DV_II_47_0_bit | DV_II_52_0_bit | DV_II_53_0_bit)) + mask &= (((((W[51] ^ W[52]) >> 29) & 1) - 1) | + ~(DV_I_48_0_bit | DV_II_47_0_bit | DV_II_52_0_bit | DV_II_53_0_bit)); + if (mask & (DV_I_46_0_bit | DV_I_49_0_bit | DV_II_45_0_bit | DV_II_48_0_bit)) + mask &= (((((W[36] >> 4) ^ (W[40] >> 29)) & 1) - 1) | + ~(DV_I_46_0_bit | DV_I_49_0_bit | DV_II_45_0_bit | DV_II_48_0_bit)); + if (mask & (DV_I_52_0_bit | DV_II_48_0_bit | DV_II_49_0_bit)) + mask &= ((0 - (((W[53] ^ W[56]) >> 29) & 1)) | + ~(DV_I_52_0_bit | DV_II_48_0_bit | DV_II_49_0_bit)); + if (mask & (DV_I_50_0_bit | DV_II_46_0_bit | DV_II_47_0_bit)) + mask &= ((0 - (((W[51] ^ W[54]) >> 29) & 1)) | + ~(DV_I_50_0_bit | DV_II_46_0_bit | DV_II_47_0_bit)); + if (mask & (DV_I_49_0_bit | DV_I_51_0_bit | DV_II_45_0_bit)) + mask &= ((0 - (((W[50] ^ W[52]) >> 29) & 1)) | + ~(DV_I_49_0_bit | DV_I_51_0_bit | DV_II_45_0_bit)); + if (mask & (DV_I_48_0_bit | DV_I_50_0_bit | DV_I_52_0_bit)) + mask &= ((0 - (((W[49] ^ W[51]) >> 29) & 1)) | + ~(DV_I_48_0_bit | DV_I_50_0_bit | DV_I_52_0_bit)); + if (mask & (DV_I_47_0_bit | DV_I_49_0_bit | DV_I_51_0_bit)) + mask &= ((0 - (((W[48] ^ W[50]) >> 29) & 1)) | + ~(DV_I_47_0_bit | DV_I_49_0_bit | DV_I_51_0_bit)); + if (mask & (DV_I_46_0_bit | DV_I_48_0_bit | DV_I_50_0_bit)) + mask &= ((0 - (((W[47] ^ W[49]) >> 29) & 1)) | + ~(DV_I_46_0_bit | DV_I_48_0_bit | DV_I_50_0_bit)); + if (mask & (DV_I_45_0_bit | DV_I_47_0_bit | DV_I_49_0_bit)) + mask &= ((0 - (((W[46] ^ W[48]) >> 29) & 1)) | + ~(DV_I_45_0_bit | DV_I_47_0_bit | DV_I_49_0_bit)); + mask &= ((((W[45] ^ W[47]) & (1 << 6)) - (1 << 6)) | + ~(DV_I_47_2_bit | DV_I_49_2_bit | DV_I_51_2_bit)); + if (mask & (DV_I_44_0_bit | DV_I_46_0_bit | DV_I_48_0_bit)) + mask &= ((0 - (((W[45] ^ W[47]) >> 29) & 1)) | + ~(DV_I_44_0_bit | DV_I_46_0_bit | DV_I_48_0_bit)); + mask &= + (((((W[44] ^ W[46]) >> 6) & 1) - 1) | ~(DV_I_46_2_bit | DV_I_48_2_bit | DV_I_50_2_bit)); + if (mask & (DV_I_43_0_bit | DV_I_45_0_bit | DV_I_47_0_bit)) + mask &= ((0 - (((W[44] ^ W[46]) >> 29) & 1)) | + ~(DV_I_43_0_bit | DV_I_45_0_bit | DV_I_47_0_bit)); + mask &= ((0 - ((W[41] ^ (W[42] >> 5)) & (1 << 1))) | + ~(DV_I_48_2_bit | DV_II_46_2_bit | DV_II_51_2_bit)); + mask &= ((0 - ((W[40] ^ (W[41] >> 5)) & (1 << 1))) | + ~(DV_I_47_2_bit | DV_I_51_2_bit | DV_II_50_2_bit)); + if (mask & (DV_I_44_0_bit | DV_I_46_0_bit | DV_II_56_0_bit)) + mask &= ((0 - (((W[40] ^ W[42]) >> 4) & 1)) | + ~(DV_I_44_0_bit | DV_I_46_0_bit | DV_II_56_0_bit)); + mask &= ((0 - ((W[39] ^ (W[40] >> 5)) & (1 << 1))) | + ~(DV_I_46_2_bit | DV_I_50_2_bit | DV_II_49_2_bit)); + if (mask & (DV_I_43_0_bit | DV_I_45_0_bit | DV_II_55_0_bit)) + mask &= ((0 - (((W[39] ^ W[41]) >> 4) & 1)) | + ~(DV_I_43_0_bit | DV_I_45_0_bit | DV_II_55_0_bit)); + if (mask & (DV_I_44_0_bit | DV_II_54_0_bit | DV_II_56_0_bit)) + mask &= ((0 - (((W[38] ^ W[40]) >> 4) & 1)) | + ~(DV_I_44_0_bit | DV_II_54_0_bit | DV_II_56_0_bit)); + if (mask & (DV_I_43_0_bit | DV_II_53_0_bit | DV_II_55_0_bit)) + mask &= ((0 - (((W[37] ^ W[39]) >> 4) & 1)) | + ~(DV_I_43_0_bit | DV_II_53_0_bit | DV_II_55_0_bit)); + mask &= ((0 - ((W[36] ^ (W[37] >> 5)) & (1 << 1))) | + ~(DV_I_47_2_bit | DV_I_50_2_bit | DV_II_46_2_bit)); + if (mask & (DV_I_45_0_bit | DV_I_48_0_bit | DV_II_47_0_bit)) + mask &= (((((W[35] >> 4) ^ (W[39] >> 29)) & 1) - 1) | + ~(DV_I_45_0_bit | DV_I_48_0_bit | DV_II_47_0_bit)); + if (mask & (DV_I_48_0_bit | DV_II_48_0_bit)) + mask &= + ((0 - ((W[63] ^ (W[64] >> 5)) & (1 << 0))) | ~(DV_I_48_0_bit | DV_II_48_0_bit)); + if (mask & (DV_I_45_0_bit | DV_II_45_0_bit)) + mask &= + ((0 - ((W[63] ^ (W[64] >> 5)) & (1 << 1))) | ~(DV_I_45_0_bit | DV_II_45_0_bit)); + if (mask & (DV_I_47_0_bit | DV_II_47_0_bit)) + mask &= + ((0 - ((W[62] ^ (W[63] >> 5)) & (1 << 0))) | ~(DV_I_47_0_bit | DV_II_47_0_bit)); + if (mask & (DV_I_46_0_bit | DV_II_46_0_bit)) + mask &= + ((0 - ((W[61] ^ (W[62] >> 5)) & (1 << 0))) | ~(DV_I_46_0_bit | DV_II_46_0_bit)); + mask &= ((0 - ((W[61] ^ (W[62] >> 5)) & (1 << 2))) | ~(DV_I_46_2_bit | DV_II_46_2_bit)); + if (mask & (DV_I_45_0_bit | DV_II_45_0_bit)) + mask &= + ((0 - ((W[60] ^ (W[61] >> 5)) & (1 << 0))) | ~(DV_I_45_0_bit | DV_II_45_0_bit)); + if (mask & (DV_II_51_0_bit | DV_II_54_0_bit)) + mask &= (((((W[58] ^ W[59]) >> 29) & 1) - 1) | ~(DV_II_51_0_bit | DV_II_54_0_bit)); + if (mask & (DV_II_50_0_bit | DV_II_53_0_bit)) + mask &= (((((W[57] ^ W[58]) >> 29) & 1) - 1) | ~(DV_II_50_0_bit | DV_II_53_0_bit)); + if (mask & (DV_II_52_0_bit | DV_II_54_0_bit)) + mask &= ((((W[56] ^ (W[59] >> 25)) & (1 << 4)) - (1 << 4)) | + ~(DV_II_52_0_bit | DV_II_54_0_bit)); + if (mask & (DV_II_51_0_bit | DV_II_52_0_bit)) + mask &= ((0 - (((W[56] ^ W[59]) >> 29) & 1)) | ~(DV_II_51_0_bit | DV_II_52_0_bit)); + if (mask & (DV_II_49_0_bit | DV_II_52_0_bit)) + mask &= (((((W[56] ^ W[57]) >> 29) & 1) - 1) | ~(DV_II_49_0_bit | DV_II_52_0_bit)); + if (mask & (DV_II_51_0_bit | DV_II_53_0_bit)) + mask &= ((((W[55] ^ (W[58] >> 25)) & (1 << 4)) - (1 << 4)) | + ~(DV_II_51_0_bit | DV_II_53_0_bit)); + if (mask & (DV_II_50_0_bit | DV_II_52_0_bit)) + mask &= ((((W[54] ^ (W[57] >> 25)) & (1 << 4)) - (1 << 4)) | + ~(DV_II_50_0_bit | DV_II_52_0_bit)); + if (mask & (DV_II_49_0_bit | DV_II_51_0_bit)) + mask &= ((((W[53] ^ (W[56] >> 25)) & (1 << 4)) - (1 << 4)) | + ~(DV_II_49_0_bit | DV_II_51_0_bit)); + mask &= + ((((W[51] ^ (W[50] >> 5)) & (1 << 1)) - (1 << 1)) | ~(DV_I_50_2_bit | DV_II_46_2_bit)); + mask &= ((((W[48] ^ W[50]) & (1 << 6)) - (1 << 6)) | ~(DV_I_50_2_bit | DV_II_46_2_bit)); + if (mask & (DV_I_51_0_bit | DV_I_52_0_bit)) + mask &= ((0 - (((W[48] ^ W[55]) >> 29) & 1)) | ~(DV_I_51_0_bit | DV_I_52_0_bit)); + mask &= ((((W[47] ^ W[49]) & (1 << 6)) - (1 << 6)) | ~(DV_I_49_2_bit | DV_I_51_2_bit)); + mask &= + ((((W[48] ^ (W[47] >> 5)) & (1 << 1)) - (1 << 1)) | ~(DV_I_47_2_bit | DV_II_51_2_bit)); + mask &= ((((W[46] ^ W[48]) & (1 << 6)) - (1 << 6)) | ~(DV_I_48_2_bit | DV_I_50_2_bit)); + mask &= + ((((W[47] ^ (W[46] >> 5)) & (1 << 1)) - (1 << 1)) | ~(DV_I_46_2_bit | DV_II_50_2_bit)); + mask &= ((0 - ((W[44] ^ (W[45] >> 5)) & (1 << 1))) | ~(DV_I_51_2_bit | DV_II_49_2_bit)); + mask &= ((((W[43] ^ W[45]) & (1 << 6)) - (1 << 6)) | ~(DV_I_47_2_bit | DV_I_49_2_bit)); + mask &= (((((W[42] ^ W[44]) >> 6) & 1) - 1) | ~(DV_I_46_2_bit | DV_I_48_2_bit)); + mask &= + ((((W[43] ^ (W[42] >> 5)) & (1 << 1)) - (1 << 1)) | ~(DV_II_46_2_bit | DV_II_51_2_bit)); + mask &= + ((((W[42] ^ (W[41] >> 5)) & (1 << 1)) - (1 << 1)) | ~(DV_I_51_2_bit | DV_II_50_2_bit)); + mask &= + ((((W[41] ^ (W[40] >> 5)) & (1 << 1)) - (1 << 1)) | ~(DV_I_50_2_bit | DV_II_49_2_bit)); + if (mask & (DV_I_52_0_bit | DV_II_51_0_bit)) + mask &= ((((W[39] ^ (W[43] >> 25)) & (1 << 4)) - (1 << 4)) | + ~(DV_I_52_0_bit | DV_II_51_0_bit)); + if (mask & (DV_I_51_0_bit | DV_II_50_0_bit)) + mask &= ((((W[38] ^ (W[42] >> 25)) & (1 << 4)) - (1 << 4)) | + ~(DV_I_51_0_bit | DV_II_50_0_bit)); + if (mask & (DV_I_48_2_bit | DV_I_51_2_bit)) + mask &= ((0 - ((W[37] ^ (W[38] >> 5)) & (1 << 1))) | ~(DV_I_48_2_bit | DV_I_51_2_bit)); + if (mask & (DV_I_50_0_bit | DV_II_49_0_bit)) + mask &= ((((W[37] ^ (W[41] >> 25)) & (1 << 4)) - (1 << 4)) | + ~(DV_I_50_0_bit | DV_II_49_0_bit)); + if (mask & (DV_II_52_0_bit | DV_II_54_0_bit)) + mask &= ((0 - ((W[36] ^ W[38]) & (1 << 4))) | ~(DV_II_52_0_bit | DV_II_54_0_bit)); + mask &= ((0 - ((W[35] ^ (W[36] >> 5)) & (1 << 1))) | ~(DV_I_46_2_bit | DV_I_49_2_bit)); + if (mask & (DV_I_51_0_bit | DV_II_47_0_bit)) + mask &= ((((W[35] ^ (W[39] >> 25)) & (1 << 3)) - (1 << 3)) | + ~(DV_I_51_0_bit | DV_II_47_0_bit)); + if (mask) { + if (mask & DV_I_43_0_bit) + if (!((W[61] ^ (W[62] >> 5)) & (1 << 1)) || + !(!((W[59] ^ (W[63] >> 25)) & (1 << 5))) || + !((W[58] ^ (W[63] >> 30)) & (1 << 0))) + mask &= ~DV_I_43_0_bit; + if (mask & DV_I_44_0_bit) + if (!((W[62] ^ (W[63] >> 5)) & (1 << 1)) || + !(!((W[60] ^ (W[64] >> 25)) & (1 << 5))) || + !((W[59] ^ (W[64] >> 30)) & (1 << 0))) + mask &= ~DV_I_44_0_bit; + if (mask & DV_I_46_2_bit) + mask &= ((~((W[40] ^ W[42]) >> 2)) | ~DV_I_46_2_bit); + if (mask & DV_I_47_2_bit) + if (!((W[62] ^ (W[63] >> 5)) & (1 << 2)) || !(!((W[41] ^ W[43]) & (1 << 6)))) + mask &= ~DV_I_47_2_bit; + if (mask & DV_I_48_2_bit) + if (!((W[63] ^ (W[64] >> 5)) & (1 << 2)) || + !(!((W[48] ^ (W[49] << 5)) & (1 << 6)))) + mask &= ~DV_I_48_2_bit; + if (mask & DV_I_49_2_bit) + if (!(!((W[49] ^ (W[50] << 5)) & (1 << 6))) || !((W[42] ^ W[50]) & (1 << 1)) || + !(!((W[39] ^ (W[40] << 5)) & (1 << 6))) || !((W[38] ^ W[40]) & (1 << 1))) + mask &= ~DV_I_49_2_bit; + if (mask & DV_I_50_0_bit) + mask &= ((((W[36] ^ W[37]) << 7)) | ~DV_I_50_0_bit); + if (mask & DV_I_50_2_bit) + mask &= ((((W[43] ^ W[51]) << 11)) | ~DV_I_50_2_bit); + if (mask & DV_I_51_0_bit) + mask &= ((((W[37] ^ W[38]) << 9)) | ~DV_I_51_0_bit); + if (mask & DV_I_51_2_bit) + if (!(!((W[51] ^ (W[52] << 5)) & (1 << 6))) || !(!((W[49] ^ W[51]) & (1 << 6))) || + !(!((W[37] ^ (W[37] >> 5)) & (1 << 1))) || + !(!((W[35] ^ (W[39] >> 25)) & (1 << 5)))) + mask &= ~DV_I_51_2_bit; + if (mask & DV_I_52_0_bit) + mask &= ((((W[38] ^ W[39]) << 11)) | ~DV_I_52_0_bit); + if (mask & DV_II_46_2_bit) + mask &= ((((W[47] ^ W[51]) << 17)) | ~DV_II_46_2_bit); + if (mask & DV_II_48_0_bit) + if (!(!((W[36] ^ (W[40] >> 25)) & (1 << 3))) || + !((W[35] ^ (W[40] << 2)) & (1 << 30))) + mask &= ~DV_II_48_0_bit; + if (mask & DV_II_49_0_bit) + if (!(!((W[37] ^ (W[41] >> 25)) & (1 << 3))) || + !((W[36] ^ (W[41] << 2)) & (1 << 30))) + mask &= ~DV_II_49_0_bit; + if (mask & DV_II_49_2_bit) + if (!(!((W[53] ^ (W[54] << 5)) & (1 << 6))) || !(!((W[51] ^ W[53]) & (1 << 6))) || + !((W[50] ^ W[54]) & (1 << 1)) || !(!((W[45] ^ (W[46] << 5)) & (1 << 6))) || + !(!((W[37] ^ (W[41] >> 25)) & (1 << 5))) || + !((W[36] ^ (W[41] >> 30)) & (1 << 0))) + mask &= ~DV_II_49_2_bit; + if (mask & DV_II_50_0_bit) + if (!((W[55] ^ W[58]) & (1 << 29)) || !(!((W[38] ^ (W[42] >> 25)) & (1 << 3))) || + !((W[37] ^ (W[42] << 2)) & (1 << 30))) + mask &= ~DV_II_50_0_bit; + if (mask & DV_II_50_2_bit) + if (!(!((W[54] ^ (W[55] << 5)) & (1 << 6))) || !(!((W[52] ^ W[54]) & (1 << 6))) || + !((W[51] ^ W[55]) & (1 << 1)) || !((W[45] ^ W[47]) & (1 << 1)) || + !(!((W[38] ^ (W[42] >> 25)) & (1 << 5))) || + !((W[37] ^ (W[42] >> 30)) & (1 << 0))) + mask &= ~DV_II_50_2_bit; + if (mask & DV_II_51_0_bit) + if (!(!((W[39] ^ (W[43] >> 25)) & (1 << 3))) || + !((W[38] ^ (W[43] << 2)) & (1 << 30))) + mask &= ~DV_II_51_0_bit; + if (mask & DV_II_51_2_bit) + if (!(!((W[55] ^ (W[56] << 5)) & (1 << 6))) || !(!((W[53] ^ W[55]) & (1 << 6))) || + !((W[52] ^ W[56]) & (1 << 1)) || !((W[46] ^ W[48]) & (1 << 1)) || + !(!((W[39] ^ (W[43] >> 25)) & (1 << 5))) || + !((W[38] ^ (W[43] >> 30)) & (1 << 0))) + mask &= ~DV_II_51_2_bit; + if (mask & DV_II_52_0_bit) + if (!(!((W[59] ^ W[60]) & (1 << 29))) || + !(!((W[40] ^ (W[44] >> 25)) & (1 << 3))) || + !(!((W[40] ^ (W[44] >> 25)) & (1 << 4))) || + !((W[39] ^ (W[44] << 2)) & (1 << 30))) + mask &= ~DV_II_52_0_bit; + if (mask & DV_II_53_0_bit) + if (!((W[58] ^ W[61]) & (1 << 29)) || !(!((W[57] ^ (W[61] >> 25)) & (1 << 4))) || + !(!((W[41] ^ (W[45] >> 25)) & (1 << 3))) || + !(!((W[41] ^ (W[45] >> 25)) & (1 << 4)))) + mask &= ~DV_II_53_0_bit; + if (mask & DV_II_54_0_bit) + if (!(!((W[58] ^ (W[62] >> 25)) & (1 << 4))) || + !(!((W[42] ^ (W[46] >> 25)) & (1 << 3))) || + !(!((W[42] ^ (W[46] >> 25)) & (1 << 4)))) + mask &= ~DV_II_54_0_bit; + if (mask & DV_II_55_0_bit) + if (!(!((W[59] ^ (W[63] >> 25)) & (1 << 4))) || + !(!((W[57] ^ (W[59] >> 25)) & (1 << 4))) || + !(!((W[43] ^ (W[47] >> 25)) & (1 << 3))) || + !(!((W[43] ^ (W[47] >> 25)) & (1 << 4)))) + mask &= ~DV_II_55_0_bit; + if (mask & DV_II_56_0_bit) + if (!(!((W[60] ^ (W[64] >> 25)) & (1 << 4))) || + !(!((W[44] ^ (W[48] >> 25)) & (1 << 3))) || + !(!((W[44] ^ (W[48] >> 25)) & (1 << 4)))) + mask &= ~DV_II_56_0_bit; + } + + dvmask[0] = mask; +} + +#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_C +#include SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_C +#endif diff --git a/comm/third_party/rnp/src/lib/crypto/sha1cd/ubc_check.h b/comm/third_party/rnp/src/lib/crypto/sha1cd/ubc_check.h new file mode 100644 index 0000000000..a43c7b685a --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/sha1cd/ubc_check.h @@ -0,0 +1,62 @@ +/*** + * Copyright 2017 Marc Stevens <marc@marc-stevens.nl>, Dan Shumow <danshu@microsoft.com> + * Distributed under the MIT Software License. + * See accompanying file LICENSE.txt or copy at + * https://opensource.org/licenses/MIT + ***/ + +/* +// this file was generated by the 'parse_bitrel' program in the tools section +// using the data files from directory 'tools/data/3565' +// +// sha1_dvs contains a list of SHA-1 Disturbance Vectors (DV) to check +// dvType, dvK and dvB define the DV: I(K,B) or II(K,B) (see the paper) +// dm[80] is the expanded message block XOR-difference defined by the DV +// testt is the step to do the recompression from for collision detection +// maski and maskb define the bit to check for each DV in the dvmask returned by ubc_check +// +// ubc_check takes as input an expanded message block and verifies the unavoidable +bitconditions for all listed DVs +// it returns a dvmask where each bit belonging to a DV is set if all unavoidable bitconditions +for that DV have been met +// thus one needs to do the recompression check for each DV that has its bit set +*/ + +#ifndef SHA1DC_UBC_CHECK_H +#define SHA1DC_UBC_CHECK_H + +#if defined(__cplusplus) +extern "C" { +#endif + +#ifndef SHA1DC_NO_STANDARD_INCLUDES +#include <stdint.h> +#endif + +#define DVMASKSIZE 1 +typedef struct { + int dvType; + int dvK; + int dvB; + int testt; + int maski; + int maskb; + uint32_t dm[80]; +} dv_info_t; +extern dv_info_t sha1_dvs[]; +void ubc_check(const uint32_t W[80], uint32_t dvmask[DVMASKSIZE]); + +#define DOSTORESTATE58 +#define DOSTORESTATE65 + +#define CHECK_DVMASK(_DVMASK) (0 != _DVMASK[0]) + +#if defined(__cplusplus) +} +#endif + +#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_H +#include SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_H +#endif + +#endif diff --git a/comm/third_party/rnp/src/lib/crypto/signatures.cpp b/comm/third_party/rnp/src/lib/crypto/signatures.cpp new file mode 100644 index 0000000000..ea39935d7a --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/signatures.cpp @@ -0,0 +1,281 @@ +/* + * Copyright (c) 2018-2022, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <string.h> +#include "crypto/signatures.h" +#include "librepgp/stream-packet.h" +#include "librepgp/stream-sig.h" +#include "utils.h" +#include "sec_profile.hpp" + +/** + * @brief Add signature fields to the hash context and finish it. + * @param hash initialized hash context fed with signed data (document, key, etc). + * It is finalized in this function. + * @param sig populated or loaded signature + * @param hbuf buffer to store the resulting hash. Must be large enough for hash output. + * @param hlen on success will be filled with the hash size, otherwise zeroed + * @return RNP_SUCCESS on success or some error otherwise + */ +static void +signature_hash_finish(const pgp_signature_t &sig, rnp::Hash &hash, uint8_t *hbuf, size_t &hlen) +{ + hash.add(sig.hashed_data, sig.hashed_len); + if (sig.version > PGP_V3) { + uint8_t trailer[6] = {0x04, 0xff, 0x00, 0x00, 0x00, 0x00}; + STORE32BE(&trailer[2], sig.hashed_len); + hash.add(trailer, 6); + } + hlen = hash.finish(hbuf); +} + +std::unique_ptr<rnp::Hash> +signature_init(const pgp_key_material_t &key, pgp_hash_alg_t hash_alg) +{ + auto hash = rnp::Hash::create(hash_alg); + if (key.alg == PGP_PKA_SM2) { +#if defined(ENABLE_SM2) + rnp_result_t r = sm2_compute_za(key.ec, *hash); + if (r != RNP_SUCCESS) { + RNP_LOG("failed to compute SM2 ZA field"); + throw rnp::rnp_exception(r); + } +#else + RNP_LOG("SM2 ZA computation not available"); + throw rnp::rnp_exception(RNP_ERROR_NOT_IMPLEMENTED); +#endif + } + return hash; +} + +void +signature_calculate(pgp_signature_t & sig, + pgp_key_material_t & seckey, + rnp::Hash & hash, + rnp::SecurityContext &ctx) +{ + uint8_t hval[PGP_MAX_HASH_SIZE]; + size_t hlen = 0; + rnp_result_t ret = RNP_ERROR_GENERIC; + const pgp_hash_alg_t hash_alg = hash.alg(); + + /* Finalize hash first, since function is required to do this */ + try { + signature_hash_finish(sig, hash, hval, hlen); + } catch (const std::exception &e) { + RNP_LOG("Failed to finalize hash: %s", e.what()); + throw; + } + + if (!seckey.secret) { + RNP_LOG("Secret key is required."); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + if (sig.palg != seckey.alg) { + RNP_LOG("Signature and secret key do not agree on algorithm type."); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + /* Validate key material if didn't before */ + seckey.validate(ctx, false); + if (!seckey.valid()) { + RNP_LOG("Attempt to sign with invalid key material."); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + /* copy left 16 bits to signature */ + memcpy(sig.lbits, hval, 2); + + /* sign */ + pgp_signature_material_t material = {}; + switch (sig.palg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + ret = rsa_sign_pkcs1(&ctx.rng, &material.rsa, sig.halg, hval, hlen, &seckey.rsa); + if (ret) { + RNP_LOG("rsa signing failed"); + } + break; + case PGP_PKA_EDDSA: + ret = eddsa_sign(&ctx.rng, &material.ecc, hval, hlen, &seckey.ec); + if (ret) { + RNP_LOG("eddsa signing failed"); + } + break; + case PGP_PKA_DSA: + ret = dsa_sign(&ctx.rng, &material.dsa, hval, hlen, &seckey.dsa); + if (ret != RNP_SUCCESS) { + RNP_LOG("DSA signing failed"); + } + break; + /* + * ECDH is signed with ECDSA. This must be changed when ECDH will support + * X25519, but I need to check how it should be done exactly. + */ + case PGP_PKA_ECDH: + case PGP_PKA_ECDSA: + case PGP_PKA_SM2: { + const ec_curve_desc_t *curve = get_curve_desc(seckey.ec.curve); + if (!curve) { + RNP_LOG("Unknown curve"); + ret = RNP_ERROR_BAD_PARAMETERS; + break; + } + if (!curve_supported(seckey.ec.curve)) { + RNP_LOG("EC sign: curve %s is not supported.", curve->pgp_name); + ret = RNP_ERROR_NOT_SUPPORTED; + break; + } + /* "-2" because ECDSA on P-521 must work with SHA-512 digest */ + if (BITS_TO_BYTES(curve->bitlen) - 2 > hlen) { + RNP_LOG("Message hash too small"); + ret = RNP_ERROR_BAD_PARAMETERS; + break; + } + + if (sig.palg == PGP_PKA_SM2) { +#if defined(ENABLE_SM2) + ret = sm2_sign(&ctx.rng, &material.ecc, hash_alg, hval, hlen, &seckey.ec); + if (ret) { + RNP_LOG("SM2 signing failed"); + } +#else + RNP_LOG("SM2 signing is not available."); + ret = RNP_ERROR_NOT_IMPLEMENTED; +#endif + break; + } + + ret = ecdsa_sign(&ctx.rng, &material.ecc, hash_alg, hval, hlen, &seckey.ec); + if (ret) { + RNP_LOG("ECDSA signing failed"); + } + break; + } + default: + RNP_LOG("Unsupported algorithm %d", sig.palg); + break; + } + if (ret) { + throw rnp::rnp_exception(ret); + } + try { + sig.write_material(material); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + throw; + } +} + +rnp_result_t +signature_validate(const pgp_signature_t & sig, + const pgp_key_material_t & key, + rnp::Hash & hash, + const rnp::SecurityContext &ctx) +{ + if (sig.palg != key.alg) { + RNP_LOG("Signature and key do not agree on algorithm type: %d vs %d", + (int) sig.palg, + (int) key.alg); + return RNP_ERROR_BAD_PARAMETERS; + } + + /* Check signature security */ + auto action = + sig.is_document() ? rnp::SecurityAction::VerifyData : rnp::SecurityAction::VerifyKey; + if (ctx.profile.hash_level(sig.halg, sig.creation(), action) < + rnp::SecurityLevel::Default) { + RNP_LOG("Insecure hash algorithm %d, marking signature as invalid.", sig.halg); + return RNP_ERROR_SIGNATURE_INVALID; + } + + /* Finalize hash */ + uint8_t hval[PGP_MAX_HASH_SIZE]; + size_t hlen = 0; + try { + signature_hash_finish(sig, hash, hval, hlen); + } catch (const std::exception &e) { + RNP_LOG("Failed to finalize signature hash."); + return RNP_ERROR_GENERIC; + } + + /* compare lbits */ + if (memcmp(hval, sig.lbits, 2)) { + RNP_LOG("wrong lbits"); + return RNP_ERROR_SIGNATURE_INVALID; + } + + /* validate signature */ + pgp_signature_material_t material = {}; + try { + sig.parse_material(material); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + rnp_result_t ret = RNP_ERROR_GENERIC; + switch (sig.palg) { + case PGP_PKA_DSA: + ret = dsa_verify(&material.dsa, hval, hlen, &key.dsa); + break; + case PGP_PKA_EDDSA: + ret = eddsa_verify(&material.ecc, hval, hlen, &key.ec); + break; + case PGP_PKA_SM2: +#if defined(ENABLE_SM2) + ret = sm2_verify(&material.ecc, hash.alg(), hval, hlen, &key.ec); +#else + RNP_LOG("SM2 verification is not available."); + ret = RNP_ERROR_NOT_IMPLEMENTED; +#endif + break; + case PGP_PKA_RSA: + case PGP_PKA_RSA_SIGN_ONLY: + ret = rsa_verify_pkcs1(&material.rsa, sig.halg, hval, hlen, &key.rsa); + break; + case PGP_PKA_RSA_ENCRYPT_ONLY: + RNP_LOG("RSA encrypt-only signature considered as invalid."); + ret = RNP_ERROR_SIGNATURE_INVALID; + break; + case PGP_PKA_ECDSA: + if (!curve_supported(key.ec.curve)) { + RNP_LOG("ECDSA verify: curve %d is not supported.", (int) key.ec.curve); + ret = RNP_ERROR_NOT_SUPPORTED; + break; + } + ret = ecdsa_verify(&material.ecc, hash.alg(), hval, hlen, &key.ec); + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + RNP_LOG("ElGamal are considered as invalid."); + ret = RNP_ERROR_SIGNATURE_INVALID; + break; + default: + RNP_LOG("Unknown algorithm"); + ret = RNP_ERROR_BAD_PARAMETERS; + } + return ret; +} diff --git a/comm/third_party/rnp/src/lib/crypto/signatures.h b/comm/third_party/rnp/src/lib/crypto/signatures.h new file mode 100644 index 0000000000..6ba64ce1f6 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/signatures.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2018-2022, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RNP_SIGNATURES_H_ +#define RNP_SIGNATURES_H_ + +#include "crypto/hash.hpp" + +/** + * @brief Initialize a signature computation. + * @param key the key that will be used to sign or verify + * @param hash_alg the digest algo to be used + * @param hash digest object that will be initialized + */ +std::unique_ptr<rnp::Hash> signature_init(const pgp_key_material_t &key, + pgp_hash_alg_t hash_alg); + +/** + * @brief Calculate signature with pre-populated hash + * @param sig signature to calculate + * @param seckey signing secret key material + * @param hash pre-populated with signed data hash context. It is finalized and destroyed + * during the execution. Signature fields and trailer are hashed in this function. + * @param rng random number generator + */ +void signature_calculate(pgp_signature_t & sig, + pgp_key_material_t & seckey, + rnp::Hash & hash, + rnp::SecurityContext &ctx); + +/** + * @brief Validate a signature with pre-populated hash. This method just checks correspondence + * between the hash and signature material. Expiration time and other fields are not + * checked for validity. + * @param sig signature to validate + * @param key public key material of the verifying key + * @param hash pre-populated with signed data hash context. It is finalized + * during the execution. Signature fields and trailer are hashed in this function. + * @return RNP_SUCCESS if signature was successfully validated or error code otherwise. + */ +rnp_result_t signature_validate(const pgp_signature_t & sig, + const pgp_key_material_t & key, + rnp::Hash & hash, + const rnp::SecurityContext &ctx); + +#endif diff --git a/comm/third_party/rnp/src/lib/crypto/sm2.cpp b/comm/third_party/rnp/src/lib/crypto/sm2.cpp new file mode 100644 index 0000000000..2af537d5f2 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/sm2.cpp @@ -0,0 +1,383 @@ +/*- + * Copyright (c) 2017-2022 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <string.h> +#include <botan/ffi.h> +#include "hash_botan.hpp" +#include "sm2.h" +#include "utils.h" +#include "bn.h" + +static bool +sm2_load_public_key(botan_pubkey_t *pubkey, const pgp_ec_key_t *keydata) +{ + const ec_curve_desc_t *curve = NULL; + botan_mp_t px = NULL; + botan_mp_t py = NULL; + size_t sz; + bool res = false; + + if (!(curve = get_curve_desc(keydata->curve))) { + return false; + } + + const size_t sign_half_len = BITS_TO_BYTES(curve->bitlen); + sz = mpi_bytes(&keydata->p); + if (!sz || (sz != (2 * sign_half_len + 1)) || (keydata->p.mpi[0] != 0x04)) { + goto end; + } + + if (botan_mp_init(&px) || botan_mp_init(&py) || + botan_mp_from_bin(px, &keydata->p.mpi[1], sign_half_len) || + botan_mp_from_bin(py, &keydata->p.mpi[1 + sign_half_len], sign_half_len)) { + goto end; + } + res = !botan_pubkey_load_sm2(pubkey, px, py, curve->botan_name); +end: + botan_mp_destroy(px); + botan_mp_destroy(py); + return res; +} + +static bool +sm2_load_secret_key(botan_privkey_t *seckey, const pgp_ec_key_t *keydata) +{ + const ec_curve_desc_t *curve = NULL; + bignum_t * x = NULL; + bool res = false; + + if (!(curve = get_curve_desc(keydata->curve))) { + return false; + } + if (!(x = mpi2bn(&keydata->x))) { + return false; + } + res = !botan_privkey_load_sm2(seckey, BN_HANDLE_PTR(x), curve->botan_name); + bn_free(x); + return res; +} + +rnp_result_t +sm2_compute_za(const pgp_ec_key_t &key, rnp::Hash &hash, const char *ident_field) +{ + rnp_result_t result = RNP_ERROR_GENERIC; + botan_pubkey_t sm2_key = NULL; + int rc; + + const char *hash_algo = rnp::Hash_Botan::name_backend(hash.alg()); + size_t digest_len = hash.size(); + + uint8_t *digest_buf = (uint8_t *) malloc(digest_len); + if (!digest_buf) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + if (!sm2_load_public_key(&sm2_key, &key)) { + RNP_LOG("Failed to load SM2 key"); + goto done; + } + + if (ident_field == NULL) + ident_field = "1234567812345678"; + + rc = botan_pubkey_sm2_compute_za(digest_buf, &digest_len, ident_field, hash_algo, sm2_key); + + if (rc != 0) { + RNP_LOG("compute_za failed %d", rc); + goto done; + } + + try { + hash.add(digest_buf, digest_len); + } catch (const std::exception &e) { + RNP_LOG("Failed to update hash: %s", e.what()); + goto done; + } + + result = RNP_SUCCESS; +done: + free(digest_buf); + botan_pubkey_destroy(sm2_key); + return result; +} + +rnp_result_t +sm2_validate_key(rnp::RNG *rng, const pgp_ec_key_t *key, bool secret) +{ + botan_pubkey_t bpkey = NULL; + botan_privkey_t bskey = NULL; + rnp_result_t ret = RNP_ERROR_BAD_PARAMETERS; + + if (!sm2_load_public_key(&bpkey, key) || botan_pubkey_check_key(bpkey, rng->handle(), 0)) { + goto done; + } + + if (!secret) { + ret = RNP_SUCCESS; + goto done; + } + + if (!sm2_load_secret_key(&bskey, key) || + botan_privkey_check_key(bskey, rng->handle(), 0)) { + goto done; + } + ret = RNP_SUCCESS; +done: + botan_privkey_destroy(bskey); + botan_pubkey_destroy(bpkey); + return ret; +} + +rnp_result_t +sm2_sign(rnp::RNG * rng, + pgp_ec_signature_t *sig, + pgp_hash_alg_t hash_alg, + const uint8_t * hash, + size_t hash_len, + const pgp_ec_key_t *key) +{ + const ec_curve_desc_t *curve = NULL; + botan_pk_op_sign_t signer = NULL; + botan_privkey_t b_key = NULL; + uint8_t out_buf[2 * MAX_CURVE_BYTELEN] = {0}; + size_t sign_half_len = 0; + size_t sig_len = 0; + rnp_result_t ret = RNP_ERROR_SIGNING_FAILED; + + if (botan_ffi_supports_api(20180713) != 0) { + RNP_LOG("SM2 signatures requires Botan 2.8 or higher"); + return RNP_ERROR_NOT_SUPPORTED; + } + + if (hash_len != rnp::Hash::size(hash_alg)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + if (!(curve = get_curve_desc(key->curve))) { + return RNP_ERROR_BAD_PARAMETERS; + } + sign_half_len = BITS_TO_BYTES(curve->bitlen); + sig_len = 2 * sign_half_len; + + if (!sm2_load_secret_key(&b_key, key)) { + RNP_LOG("Can't load private key"); + ret = RNP_ERROR_BAD_PARAMETERS; + goto end; + } + + if (botan_pk_op_sign_create(&signer, b_key, ",Raw", 0)) { + goto end; + } + + if (botan_pk_op_sign_update(signer, hash, hash_len)) { + goto end; + } + + if (botan_pk_op_sign_finish(signer, rng->handle(), out_buf, &sig_len)) { + RNP_LOG("Signing failed"); + goto end; + } + + // Allocate memory and copy results + if (mem2mpi(&sig->r, out_buf, sign_half_len) && + mem2mpi(&sig->s, out_buf + sign_half_len, sign_half_len)) { + // All good now + ret = RNP_SUCCESS; + } +end: + botan_privkey_destroy(b_key); + botan_pk_op_sign_destroy(signer); + return ret; +} + +rnp_result_t +sm2_verify(const pgp_ec_signature_t *sig, + pgp_hash_alg_t hash_alg, + const uint8_t * hash, + size_t hash_len, + const pgp_ec_key_t * key) +{ + const ec_curve_desc_t *curve = NULL; + botan_pubkey_t pub = NULL; + botan_pk_op_verify_t verifier = NULL; + rnp_result_t ret = RNP_ERROR_SIGNATURE_INVALID; + uint8_t sign_buf[2 * MAX_CURVE_BYTELEN] = {0}; + size_t r_blen, s_blen, sign_half_len; + + if (botan_ffi_supports_api(20180713) != 0) { + RNP_LOG("SM2 signatures requires Botan 2.8 or higher"); + return RNP_ERROR_NOT_SUPPORTED; + } + + if (hash_len != rnp::Hash::size(hash_alg)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + curve = get_curve_desc(key->curve); + if (curve == NULL) { + return RNP_ERROR_BAD_PARAMETERS; + } + sign_half_len = BITS_TO_BYTES(curve->bitlen); + + if (!sm2_load_public_key(&pub, key)) { + RNP_LOG("Failed to load public key"); + goto end; + } + + if (botan_pk_op_verify_create(&verifier, pub, ",Raw", 0)) { + goto end; + } + + if (botan_pk_op_verify_update(verifier, hash, hash_len)) { + goto end; + } + + r_blen = sig->r.len; + s_blen = sig->s.len; + if (!r_blen || (r_blen > sign_half_len) || !s_blen || (s_blen > sign_half_len) || + (sign_half_len > MAX_CURVE_BYTELEN)) { + goto end; + } + + mpi2mem(&sig->r, sign_buf + sign_half_len - r_blen); + mpi2mem(&sig->s, sign_buf + 2 * sign_half_len - s_blen); + + if (!botan_pk_op_verify_finish(verifier, sign_buf, sign_half_len * 2)) { + ret = RNP_SUCCESS; + } +end: + botan_pubkey_destroy(pub); + botan_pk_op_verify_destroy(verifier); + return ret; +} + +rnp_result_t +sm2_encrypt(rnp::RNG * rng, + pgp_sm2_encrypted_t *out, + const uint8_t * in, + size_t in_len, + pgp_hash_alg_t hash_algo, + const pgp_ec_key_t * key) +{ + rnp_result_t ret = RNP_ERROR_GENERIC; + const ec_curve_desc_t *curve = NULL; + botan_pubkey_t sm2_key = NULL; + botan_pk_op_encrypt_t enc_op = NULL; + size_t point_len; + size_t hash_alg_len; + size_t ctext_len; + + curve = get_curve_desc(key->curve); + if (curve == NULL) { + return RNP_ERROR_GENERIC; + } + point_len = BITS_TO_BYTES(curve->bitlen); + hash_alg_len = rnp::Hash::size(hash_algo); + if (!hash_alg_len) { + RNP_LOG("Unknown hash algorithm for SM2 encryption"); + goto done; + } + + /* + * Format of SM2 ciphertext is a point (2*point_len+1) plus + * the masked ciphertext (out_len) plus a hash. + */ + ctext_len = (2 * point_len + 1) + in_len + hash_alg_len; + if (ctext_len > PGP_MPINT_SIZE) { + RNP_LOG("too large output for SM2 encryption"); + goto done; + } + + if (!sm2_load_public_key(&sm2_key, key)) { + RNP_LOG("Failed to load public key"); + goto done; + } + + /* + SM2 encryption doesn't have any kind of format specifier because + it's an all in one scheme, only the hash (used for the integrity + check) is specified. + */ + if (botan_pk_op_encrypt_create( + &enc_op, sm2_key, rnp::Hash_Botan::name_backend(hash_algo), 0)) { + goto done; + } + + out->m.len = sizeof(out->m.mpi); + if (botan_pk_op_encrypt(enc_op, rng->handle(), out->m.mpi, &out->m.len, in, in_len) == 0) { + out->m.mpi[out->m.len++] = hash_algo; + ret = RNP_SUCCESS; + } +done: + botan_pk_op_encrypt_destroy(enc_op); + botan_pubkey_destroy(sm2_key); + return ret; +} + +rnp_result_t +sm2_decrypt(uint8_t * out, + size_t * out_len, + const pgp_sm2_encrypted_t *in, + const pgp_ec_key_t * key) +{ + const ec_curve_desc_t *curve; + botan_pk_op_decrypt_t decrypt_op = NULL; + botan_privkey_t b_key = NULL; + size_t in_len; + rnp_result_t ret = RNP_ERROR_GENERIC; + uint8_t hash_id; + const char * hash_name = NULL; + + curve = get_curve_desc(key->curve); + in_len = mpi_bytes(&in->m); + if (curve == NULL || in_len < 64) { + goto done; + } + + if (!sm2_load_secret_key(&b_key, key)) { + RNP_LOG("Can't load private key"); + goto done; + } + + hash_id = in->m.mpi[in_len - 1]; + hash_name = rnp::Hash_Botan::name_backend((pgp_hash_alg_t) hash_id); + if (!hash_name) { + RNP_LOG("Unknown hash used in SM2 ciphertext"); + goto done; + } + + if (botan_pk_op_decrypt_create(&decrypt_op, b_key, hash_name, 0) != 0) { + goto done; + } + + if (botan_pk_op_decrypt(decrypt_op, out, out_len, in->m.mpi, in_len - 1) == 0) { + ret = RNP_SUCCESS; + } +done: + botan_privkey_destroy(b_key); + botan_pk_op_decrypt_destroy(decrypt_op); + return ret; +} diff --git a/comm/third_party/rnp/src/lib/crypto/sm2.h b/comm/third_party/rnp/src/lib/crypto/sm2.h new file mode 100644 index 0000000000..a16f7fad83 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/sm2.h @@ -0,0 +1,79 @@ +/*- + * Copyright (c) 2017-2022 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RNP_SM2_H_ +#define RNP_SM2_H_ + +#include "config.h" +#include "ec.h" + +typedef struct pgp_sm2_encrypted_t { + pgp_mpi_t m; +} pgp_sm2_encrypted_t; + +namespace rnp { +class Hash; +} // namespace rnp + +#if defined(ENABLE_SM2) +rnp_result_t sm2_validate_key(rnp::RNG *rng, const pgp_ec_key_t *key, bool secret); + +/** + * Compute the SM2 "ZA" field, and add it to the hash object + * + * If ident_field is null, uses the default value + */ +rnp_result_t sm2_compute_za(const pgp_ec_key_t &key, + rnp::Hash & hash, + const char * ident_field = NULL); + +rnp_result_t sm2_sign(rnp::RNG * rng, + pgp_ec_signature_t *sig, + pgp_hash_alg_t hash_alg, + const uint8_t * hash, + size_t hash_len, + const pgp_ec_key_t *key); + +rnp_result_t sm2_verify(const pgp_ec_signature_t *sig, + pgp_hash_alg_t hash_alg, + const uint8_t * hash, + size_t hash_len, + const pgp_ec_key_t * key); + +rnp_result_t sm2_encrypt(rnp::RNG * rng, + pgp_sm2_encrypted_t *out, + const uint8_t * in, + size_t in_len, + pgp_hash_alg_t hash_algo, + const pgp_ec_key_t * key); + +rnp_result_t sm2_decrypt(uint8_t * out, + size_t * out_len, + const pgp_sm2_encrypted_t *in, + const pgp_ec_key_t * key); +#endif // defined(ENABLE_SM2) + +#endif // SM2_H_ diff --git a/comm/third_party/rnp/src/lib/crypto/sm2_ossl.cpp b/comm/third_party/rnp/src/lib/crypto/sm2_ossl.cpp new file mode 100644 index 0000000000..4c1eaf17a8 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/sm2_ossl.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <string.h> +#include "sm2.h" +#include "utils.h" + +rnp_result_t +sm2_validate_key(rnp::RNG *rng, const pgp_ec_key_t *key, bool secret) +{ + return RNP_ERROR_NOT_IMPLEMENTED; +} + +rnp_result_t +sm2_sign(rnp::RNG * rng, + pgp_ec_signature_t *sig, + pgp_hash_alg_t hash_alg, + const uint8_t * hash, + size_t hash_len, + const pgp_ec_key_t *key) +{ + return RNP_ERROR_NOT_IMPLEMENTED; +} + +rnp_result_t +sm2_verify(const pgp_ec_signature_t *sig, + pgp_hash_alg_t hash_alg, + const uint8_t * hash, + size_t hash_len, + const pgp_ec_key_t * key) +{ + return RNP_ERROR_NOT_IMPLEMENTED; +} + +rnp_result_t +sm2_encrypt(rnp::RNG * rng, + pgp_sm2_encrypted_t *out, + const uint8_t * in, + size_t in_len, + pgp_hash_alg_t hash_algo, + const pgp_ec_key_t * key) +{ + return RNP_ERROR_NOT_IMPLEMENTED; +} + +rnp_result_t +sm2_decrypt(uint8_t * out, + size_t * out_len, + const pgp_sm2_encrypted_t *in, + const pgp_ec_key_t * key) +{ + return RNP_ERROR_NOT_IMPLEMENTED; +} diff --git a/comm/third_party/rnp/src/lib/crypto/symmetric.cpp b/comm/third_party/rnp/src/lib/crypto/symmetric.cpp new file mode 100644 index 0000000000..aeed78432d --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/symmetric.cpp @@ -0,0 +1,648 @@ +/* + * Copyright (c) 2017, [Ribose Inc](https://www.ribose.com). + * Copyright (c) 2009 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is originally derived from software contributed to + * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and + * carried further by Ribose Inc (https://www.ribose.com). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/* + * Copyright (c) 2005-2008 Nominet UK (www.nic.uk) + * All rights reserved. + * Contributors: Ben Laurie, Rachel Willmer. The Contributors have asserted + * their moral rights under the UK Copyright Design and Patents Act 1988 to + * be recorded as the authors of this copyright work. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. + * + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "crypto.h" +#include "config.h" +#include "defaults.h" + +#include <string.h> +#include <stdlib.h> +#include <assert.h> +#include <botan/ffi.h> +#include "utils.h" + +static const char * +pgp_sa_to_botan_string(int alg, bool silent = false) +{ + switch (alg) { +#if defined(BOTAN_HAS_IDEA) && defined(ENABLE_IDEA) + case PGP_SA_IDEA: + return "IDEA"; +#endif + +#if defined(BOTAN_HAS_DES) + case PGP_SA_TRIPLEDES: + return "TripleDES"; +#endif + +#if defined(BOTAN_HAS_CAST) && defined(ENABLE_CAST5) + case PGP_SA_CAST5: + return "CAST-128"; +#endif + +#if defined(BOTAN_HAS_BLOWFISH) && defined(ENABLE_BLOWFISH) + case PGP_SA_BLOWFISH: + return "Blowfish"; +#endif + +#if defined(BOTAN_HAS_AES) + case PGP_SA_AES_128: + return "AES-128"; + case PGP_SA_AES_192: + return "AES-192"; + case PGP_SA_AES_256: + return "AES-256"; +#endif + +#if defined(BOTAN_HAS_SM4) && defined(ENABLE_SM2) + case PGP_SA_SM4: + return "SM4"; +#endif + +#if defined(BOTAN_HAS_TWOFISH) && defined(ENABLE_TWOFISH) + case PGP_SA_TWOFISH: + return "Twofish"; +#endif + +#if defined(BOTAN_HAS_CAMELLIA) + case PGP_SA_CAMELLIA_128: + return "Camellia-128"; + case PGP_SA_CAMELLIA_192: + return "Camellia-192"; + case PGP_SA_CAMELLIA_256: + return "Camellia-256"; +#endif + + default: + if (!silent) { + RNP_LOG("Unsupported symmetric algorithm %d", alg); + } + return NULL; + } +} + +#if defined(ENABLE_AEAD) +static bool +pgp_aead_to_botan_string(pgp_symm_alg_t ealg, pgp_aead_alg_t aalg, char *buf, size_t len) +{ + const char *ealg_name = pgp_sa_to_botan_string(ealg); + size_t ealg_len; + + if (!ealg_name) { + return false; + } + + ealg_len = strlen(ealg_name); + + if (len < ealg_len + 5) { + RNP_LOG("buffer too small"); + return false; + } + + switch (aalg) { + case PGP_AEAD_EAX: + memcpy(buf, ealg_name, ealg_len); + strncpy(buf + ealg_len, "/EAX", len - ealg_len); + break; + case PGP_AEAD_OCB: + memcpy(buf, ealg_name, ealg_len); + strncpy(buf + ealg_len, "/OCB", len - ealg_len); + break; + default: + RNP_LOG("unsupported AEAD alg %d", (int) aalg); + return false; + } + + return true; +} +#endif + +bool +pgp_cipher_cfb_start(pgp_crypt_t * crypt, + pgp_symm_alg_t alg, + const uint8_t *key, + const uint8_t *iv) +{ + memset(crypt, 0x0, sizeof(*crypt)); + + const char *cipher_name = pgp_sa_to_botan_string(alg); + if (!cipher_name) { + return false; + } + + crypt->alg = alg; + crypt->blocksize = pgp_block_size(alg); + + // This shouldn't happen if pgp_sa_to_botan_string returned a ptr + if (botan_block_cipher_init(&(crypt->cfb.obj), cipher_name) != 0) { + RNP_LOG("Block cipher '%s' not available", cipher_name); + return false; + } + + const size_t keysize = pgp_key_size(alg); + + if (botan_block_cipher_set_key(crypt->cfb.obj, key, keysize) != 0) { + RNP_LOG("Failure setting key on block cipher object"); + return false; + } + + if (iv != NULL) { + // Otherwise left as all zeros via memset at start of function + memcpy(crypt->cfb.iv, iv, crypt->blocksize); + } + + crypt->cfb.remaining = 0; + + return true; +} + +void +pgp_cipher_cfb_resync(pgp_crypt_t *crypt, const uint8_t *buf) +{ + /* iv will be encrypted in the upcoming call to encrypt/decrypt */ + memcpy(crypt->cfb.iv, buf, crypt->blocksize); + crypt->cfb.remaining = 0; +} + +int +pgp_cipher_cfb_finish(pgp_crypt_t *crypt) +{ + if (!crypt) { + return 0; + } + if (crypt->cfb.obj) { + botan_block_cipher_destroy(crypt->cfb.obj); + crypt->cfb.obj = NULL; + } + botan_scrub_mem((uint8_t *) crypt, sizeof(*crypt)); + return 0; +} + +/* we rely on fact that in and out could be the same */ +int +pgp_cipher_cfb_encrypt(pgp_crypt_t *crypt, uint8_t *out, const uint8_t *in, size_t bytes) +{ + uint64_t *in64; + uint64_t buf64[512]; // 4KB - page size + uint64_t iv64[2]; + size_t blocks, blockb; + unsigned blsize = crypt->blocksize; + + /* encrypting till the block boundary */ + while (bytes && crypt->cfb.remaining) { + *out = *in++ ^ crypt->cfb.iv[blsize - crypt->cfb.remaining]; + crypt->cfb.iv[blsize - crypt->cfb.remaining] = *out++; + crypt->cfb.remaining--; + bytes--; + } + + if (!bytes) { + return 0; + } + + /* encrypting full blocks */ + if (bytes > blsize) { + memcpy(iv64, crypt->cfb.iv, blsize); + while ((blocks = bytes & ~(blsize - 1)) > 0) { + if (blocks > sizeof(buf64)) { + blocks = sizeof(buf64); + } + bytes -= blocks; + blockb = blocks; + memcpy(buf64, in, blockb); + in64 = buf64; + + if (blsize == 16) { + blocks >>= 4; + while (blocks--) { + botan_block_cipher_encrypt_blocks( + crypt->cfb.obj, (uint8_t *) iv64, (uint8_t *) iv64, 1); + *in64 ^= iv64[0]; + iv64[0] = *in64++; + *in64 ^= iv64[1]; + iv64[1] = *in64++; + } + } else { + blocks >>= 3; + while (blocks--) { + botan_block_cipher_encrypt_blocks( + crypt->cfb.obj, (uint8_t *) iv64, (uint8_t *) iv64, 1); + *in64 ^= iv64[0]; + iv64[0] = *in64++; + } + } + + memcpy(out, buf64, blockb); + out += blockb; + in += blockb; + } + + memcpy(crypt->cfb.iv, iv64, blsize); + } + + if (!bytes) { + return 0; + } + + botan_block_cipher_encrypt_blocks(crypt->cfb.obj, crypt->cfb.iv, crypt->cfb.iv, 1); + crypt->cfb.remaining = blsize; + + /* encrypting tail */ + while (bytes) { + *out = *in++ ^ crypt->cfb.iv[blsize - crypt->cfb.remaining]; + crypt->cfb.iv[blsize - crypt->cfb.remaining] = *out++; + crypt->cfb.remaining--; + bytes--; + } + + return 0; +} + +/* we rely on fact that in and out could be the same */ +int +pgp_cipher_cfb_decrypt(pgp_crypt_t *crypt, uint8_t *out, const uint8_t *in, size_t bytes) +{ + /* for better code readability */ + uint64_t *out64, *in64; + uint64_t inbuf64[512]; // 4KB - page size + uint64_t outbuf64[512]; + uint64_t iv64[2]; + size_t blocks, blockb; + unsigned blsize = crypt->blocksize; + + /* decrypting till the block boundary */ + while (bytes && crypt->cfb.remaining) { + uint8_t c = *in++; + *out++ = c ^ crypt->cfb.iv[blsize - crypt->cfb.remaining]; + crypt->cfb.iv[blsize - crypt->cfb.remaining] = c; + crypt->cfb.remaining--; + bytes--; + } + + if (!bytes) { + return 0; + } + + /* decrypting full blocks */ + if (bytes > blsize) { + memcpy(iv64, crypt->cfb.iv, blsize); + + while ((blocks = bytes & ~(blsize - 1)) > 0) { + if (blocks > sizeof(inbuf64)) { + blocks = sizeof(inbuf64); + } + bytes -= blocks; + blockb = blocks; + memcpy(inbuf64, in, blockb); + out64 = outbuf64; + in64 = inbuf64; + + if (blsize == 16) { + blocks >>= 4; + while (blocks--) { + botan_block_cipher_encrypt_blocks( + crypt->cfb.obj, (uint8_t *) iv64, (uint8_t *) iv64, 1); + *out64++ = *in64 ^ iv64[0]; + iv64[0] = *in64++; + *out64++ = *in64 ^ iv64[1]; + iv64[1] = *in64++; + } + } else { + blocks >>= 3; + while (blocks--) { + botan_block_cipher_encrypt_blocks( + crypt->cfb.obj, (uint8_t *) iv64, (uint8_t *) iv64, 1); + *out64++ = *in64 ^ iv64[0]; + iv64[0] = *in64++; + } + } + + memcpy(out, outbuf64, blockb); + out += blockb; + in += blockb; + } + + memcpy(crypt->cfb.iv, iv64, blsize); + } + + if (!bytes) { + return 0; + } + + botan_block_cipher_encrypt_blocks(crypt->cfb.obj, crypt->cfb.iv, crypt->cfb.iv, 1); + crypt->cfb.remaining = blsize; + + /* decrypting tail */ + while (bytes) { + uint8_t c = *in++; + *out++ = c ^ crypt->cfb.iv[blsize - crypt->cfb.remaining]; + crypt->cfb.iv[blsize - crypt->cfb.remaining] = c; + crypt->cfb.remaining--; + bytes--; + } + + return 0; +} + +size_t +pgp_cipher_block_size(pgp_crypt_t *crypt) +{ + return crypt->blocksize; +} + +unsigned +pgp_block_size(pgp_symm_alg_t alg) +{ + switch (alg) { + case PGP_SA_IDEA: + case PGP_SA_TRIPLEDES: + case PGP_SA_CAST5: + case PGP_SA_BLOWFISH: + return 8; + case PGP_SA_AES_128: + case PGP_SA_AES_192: + case PGP_SA_AES_256: + case PGP_SA_TWOFISH: + case PGP_SA_CAMELLIA_128: + case PGP_SA_CAMELLIA_192: + case PGP_SA_CAMELLIA_256: + case PGP_SA_SM4: + return 16; + default: + return 0; + } +} + +unsigned +pgp_key_size(pgp_symm_alg_t alg) +{ + /* Update MAX_SYMM_KEY_SIZE after adding algorithm + * with bigger key size. + */ + static_assert(32 == MAX_SYMM_KEY_SIZE, "MAX_SYMM_KEY_SIZE must be updated"); + + switch (alg) { + case PGP_SA_IDEA: + case PGP_SA_CAST5: + case PGP_SA_BLOWFISH: + case PGP_SA_AES_128: + case PGP_SA_CAMELLIA_128: + case PGP_SA_SM4: + return 16; + + case PGP_SA_TRIPLEDES: + case PGP_SA_AES_192: + case PGP_SA_CAMELLIA_192: + return 24; + + case PGP_SA_TWOFISH: + case PGP_SA_AES_256: + case PGP_SA_CAMELLIA_256: + return 32; + + default: + return 0; + } +} + +bool +pgp_is_sa_supported(int alg, bool silent) +{ + return pgp_sa_to_botan_string(alg, silent); +} + +#if defined(ENABLE_AEAD) +bool +pgp_cipher_aead_init(pgp_crypt_t * crypt, + pgp_symm_alg_t ealg, + pgp_aead_alg_t aalg, + const uint8_t *key, + bool decrypt) +{ + char cipher_name[32]; + uint32_t flags; + + memset(crypt, 0x0, sizeof(*crypt)); + + if (!pgp_aead_to_botan_string(ealg, aalg, cipher_name, sizeof(cipher_name))) { + return false; + } + + crypt->alg = ealg; + crypt->blocksize = pgp_block_size(ealg); + crypt->aead.alg = aalg; + crypt->aead.decrypt = decrypt; + crypt->aead.taglen = PGP_AEAD_EAX_OCB_TAG_LEN; /* it's the same for EAX and OCB */ + + flags = decrypt ? BOTAN_CIPHER_INIT_FLAG_DECRYPT : BOTAN_CIPHER_INIT_FLAG_ENCRYPT; + + if (botan_cipher_init(&(crypt->aead.obj), cipher_name, flags)) { + RNP_LOG("cipher %s is not available", cipher_name); + return false; + } + + if (botan_cipher_set_key(crypt->aead.obj, key, (size_t) pgp_key_size(ealg))) { + RNP_LOG("failed to set key"); + return false; + } + + if (botan_cipher_get_update_granularity(crypt->aead.obj, &crypt->aead.granularity)) { + RNP_LOG("failed to get update granularity"); + return false; + } + + return true; +} + +size_t +pgp_cipher_aead_granularity(pgp_crypt_t *crypt) +{ + return crypt->aead.granularity; +} +#endif + +size_t +pgp_cipher_aead_nonce_len(pgp_aead_alg_t aalg) +{ + switch (aalg) { + case PGP_AEAD_EAX: + return PGP_AEAD_EAX_NONCE_LEN; + case PGP_AEAD_OCB: + return PGP_AEAD_OCB_NONCE_LEN; + default: + return 0; + } +} + +size_t +pgp_cipher_aead_tag_len(pgp_aead_alg_t aalg) +{ + switch (aalg) { + case PGP_AEAD_EAX: + case PGP_AEAD_OCB: + return PGP_AEAD_EAX_OCB_TAG_LEN; + default: + return 0; + } +} + +#if defined(ENABLE_AEAD) +bool +pgp_cipher_aead_set_ad(pgp_crypt_t *crypt, const uint8_t *ad, size_t len) +{ + return botan_cipher_set_associated_data(crypt->aead.obj, ad, len) == 0; +} + +bool +pgp_cipher_aead_start(pgp_crypt_t *crypt, const uint8_t *nonce, size_t len) +{ + return botan_cipher_start(crypt->aead.obj, nonce, len) == 0; +} + +bool +pgp_cipher_aead_update(pgp_crypt_t *crypt, uint8_t *out, const uint8_t *in, size_t len) +{ + size_t outwr = 0; + size_t inread = 0; + + 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) { + RNP_LOG("aead update failed"); + return false; + } + + if ((outwr != len) || (inread != len)) { + RNP_LOG("wrong aead usage"); + return false; + } + + return true; +} + +void +pgp_cipher_aead_reset(pgp_crypt_t *crypt) +{ + botan_cipher_reset(crypt->aead.obj); +} + +bool +pgp_cipher_aead_finish(pgp_crypt_t *crypt, uint8_t *out, const uint8_t *in, size_t len) +{ + uint32_t flags = BOTAN_CIPHER_UPDATE_FLAG_FINAL; + size_t inread = 0; + size_t outwr = 0; + int res; + + if (crypt->aead.decrypt) { + size_t datalen = len - crypt->aead.taglen; + /* for decryption we should have tag for the final update call */ + res = + botan_cipher_update(crypt->aead.obj, flags, out, datalen, &outwr, in, len, &inread); + if (res != 0) { + if (res != BOTAN_FFI_ERROR_BAD_MAC) { + RNP_LOG("aead finish failed: %d", res); + } + return false; + } + + if ((outwr != datalen) || (inread != len)) { + RNP_LOG("wrong decrypt aead finish usage"); + return false; + } + } else { + /* for encryption tag will be generated */ + size_t outlen = len + crypt->aead.taglen; + if (botan_cipher_update( + crypt->aead.obj, flags, out, outlen, &outwr, in, len, &inread) != 0) { + RNP_LOG("aead finish failed"); + return false; + } + + if ((outwr != outlen) || (inread != len)) { + RNP_LOG("wrong encrypt aead finish usage"); + return false; + } + } + + pgp_cipher_aead_reset(crypt); + return true; +} + +void +pgp_cipher_aead_destroy(pgp_crypt_t *crypt) +{ + botan_cipher_destroy(crypt->aead.obj); +} + +size_t +pgp_cipher_aead_nonce(pgp_aead_alg_t aalg, const uint8_t *iv, uint8_t *nonce, size_t index) +{ + switch (aalg) { + case PGP_AEAD_EAX: + /* The nonce for EAX mode is computed by treating the starting + initialization vector as a 16-octet, big-endian value and + exclusive-oring the low eight octets of it with the chunk index. + */ + memcpy(nonce, iv, PGP_AEAD_EAX_NONCE_LEN); + for (int i = 15; (i > 7) && index; i--) { + nonce[i] ^= index & 0xff; + index = index >> 8; + } + return PGP_AEAD_EAX_NONCE_LEN; + case PGP_AEAD_OCB: + /* The nonce for a chunk of chunk index "i" in OCB processing is defined as: + OCB-Nonce_{i} = IV[1..120] xor i + */ + memcpy(nonce, iv, PGP_AEAD_OCB_NONCE_LEN); + for (int i = 14; (i >= 0) && index; i--) { + nonce[i] ^= index & 0xff; + index = index >> 8; + } + return PGP_AEAD_OCB_NONCE_LEN; + default: + return 0; + } +} +#endif // ENABLE_AEAD diff --git a/comm/third_party/rnp/src/lib/crypto/symmetric.h b/comm/third_party/rnp/src/lib/crypto/symmetric.h new file mode 100644 index 0000000000..a50fe9a184 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/symmetric.h @@ -0,0 +1,252 @@ +/* + * Copyright (c) 2017, 2021 [Ribose Inc](https://www.ribose.com). + * Copyright (c) 2009 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is originally derived from software contributed to + * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and + * carried further by Ribose Inc (https://www.ribose.com). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/* + * Copyright (c) 2005-2008 Nominet UK (www.nic.uk) + * All rights reserved. + * Contributors: Ben Laurie, Rachel Willmer. The Contributors have asserted + * their moral rights under the UK Copyright Design and Patents Act 1988 to + * be recorded as the authors of this copyright work. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. + * + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SYMMETRIC_CRYPTO_H_ +#define SYMMETRIC_CRYPTO_H_ + +#include <repgp/repgp_def.h> +#include "crypto/rng.h" +#include "config.h" +#ifdef CRYPTO_BACKEND_OPENSSL +#include <openssl/evp.h> +#include "mem.h" +#endif + +/* Nonce len for AEAD/EAX */ +#define PGP_AEAD_EAX_NONCE_LEN 16 + +/* Nonce len for AEAD/OCB */ +#define PGP_AEAD_OCB_NONCE_LEN 15 + +/* Maximum AEAD nonce length */ +#define PGP_AEAD_MAX_NONCE_LEN 16 + +/* Authentication tag len for AEAD/EAX and AEAD/OCB */ +#define PGP_AEAD_EAX_OCB_TAG_LEN 16 + +/* Maximal size of symmetric key */ +#define MAX_SYMM_KEY_SIZE 32 + +/* Maximum AEAD tag length */ +#define PGP_AEAD_MAX_TAG_LEN 16 + +/* Maximum authenticated data length for AEAD */ +#define PGP_AEAD_MAX_AD_LEN 32 + +struct pgp_crypt_cfb_param_t { +#ifdef CRYPTO_BACKEND_BOTAN + struct botan_block_cipher_struct *obj; +#endif +#ifdef CRYPTO_BACKEND_OPENSSL + EVP_CIPHER_CTX *obj; +#endif + size_t remaining; + uint8_t iv[PGP_MAX_BLOCK_SIZE]; +}; + +struct pgp_crypt_aead_param_t { +#ifdef CRYPTO_BACKEND_BOTAN + struct botan_cipher_struct *obj; +#endif +#ifdef CRYPTO_BACKEND_OPENSSL + EVP_CIPHER_CTX * obj; + const EVP_CIPHER * cipher; + rnp::secure_vector<uint8_t> *key; + uint8_t ad[PGP_AEAD_MAX_AD_LEN]; + size_t ad_len; + size_t n_len; +#endif + pgp_aead_alg_t alg; + bool decrypt; + size_t granularity; + size_t taglen; +}; + +/** pgp_crypt_t */ +typedef struct pgp_crypt_t { + union { + struct pgp_crypt_cfb_param_t cfb; +#if defined(ENABLE_AEAD) + struct pgp_crypt_aead_param_t aead; +#endif + }; + + pgp_symm_alg_t alg; + size_t blocksize; + rnp::RNG * rng; +} pgp_crypt_t; + +unsigned pgp_block_size(pgp_symm_alg_t); +unsigned pgp_key_size(pgp_symm_alg_t); +bool pgp_is_sa_supported(int alg, bool silent = false); +size_t pgp_cipher_block_size(pgp_crypt_t *crypt); + +/** + * Initialize a cipher object. + * @param iv if null an all-zero IV is assumed + */ +bool pgp_cipher_cfb_start(pgp_crypt_t * crypt, + pgp_symm_alg_t alg, + const uint8_t *key, + const uint8_t *iv); + +// Deallocate all storage +int pgp_cipher_cfb_finish(pgp_crypt_t *crypt); +// CFB encryption/decryption +int pgp_cipher_cfb_encrypt(pgp_crypt_t *crypt, uint8_t *out, const uint8_t *in, size_t len); +int pgp_cipher_cfb_decrypt(pgp_crypt_t *crypt, uint8_t *out, const uint8_t *in, size_t len); + +void pgp_cipher_cfb_resync(pgp_crypt_t *crypt, const uint8_t *buf); + +#if defined(ENABLE_AEAD) +/** @brief Initialize AEAD cipher instance + * @param crypt pgp crypto object + * @param ealg symmetric encryption algorithm to use together with AEAD cipher mode + * @param aalg AEAD cipher mode. Only EAX is supported now + * @param key key buffer. Number of key bytes is determined by ealg. + * @param decrypt true for decryption, or false for encryption + * @return true on success or false otherwise. + */ +bool pgp_cipher_aead_init(pgp_crypt_t * crypt, + pgp_symm_alg_t ealg, + pgp_aead_alg_t aalg, + const uint8_t *key, + bool decrypt); + +/** @brief Return the AEAD cipher update granularity. Botan FFI will consume chunks which are + * multiple of this value. See the description of pgp_cipher_aead_update() + * @param crypt initialized AEAD crypto + * @return Update granularity value in bytes + */ +size_t pgp_cipher_aead_granularity(pgp_crypt_t *crypt); +#endif + +/** @brief Return the AEAD cipher tag length + * @param aalg OpenPGP AEAD algorithm + * @return length of authentication tag in bytes, or 0 for unknown algorithm + */ +size_t pgp_cipher_aead_tag_len(pgp_aead_alg_t aalg); + +/** @brief Return the AEAD cipher nonce and IV length + * @param aalg OpenPGP AEAD algorithm + * @return length of nonce in bytes, or 0 for unknown algorithm + */ +size_t pgp_cipher_aead_nonce_len(pgp_aead_alg_t aalg); + +#if defined(ENABLE_AEAD) +/** @brief Set associated data + * @param crypt initialized AEAD crypto + * @param ad buffer with data. Cannot be NULL. + * @param len number of bytes in ad + * @return true on success or false otherwise. + */ +bool pgp_cipher_aead_set_ad(pgp_crypt_t *crypt, const uint8_t *ad, size_t len); + +/** @brief Start the cipher operation, using the given nonce + * @param crypt initialized AEAD crypto + * @param nonce buffer with nonce, cannot be NULL. + * @param len number of bytes in nonce. Must conform to the cipher properties. + * @return true on success or false otherwise. + */ +bool pgp_cipher_aead_start(pgp_crypt_t *crypt, const uint8_t *nonce, size_t len); + +/** @brief Update the cipher. This should be called for non-final data, respecting the + * update granularity of underlying botan cipher. Now it is 256 bytes. + * @param crypt initialized AEAD crypto + * @param out buffer to put processed data. Cannot be NULL, and should be large enough to put + * len bytes + * @param in buffer with input, cannot be NULL + * @param len number of bytes to process. Should be multiple of update granularity. + * @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); + +/** @brief Do final update on the cipher. For decryption final chunk should contain at least + * authentication tag, for encryption input could be zero-size. + * @param crypt initialized AEAD crypto + * @param out buffer to put processed data. For decryption it should be large enough to put + * len bytes minus authentication tag, for encryption it should be large enough to + * put len byts plus a tag. + * @param in buffer with input, if any. May be NULL for encryption, then len should be zero. + * For decryption it should contain at least authentication tag. + * @param len number of input bytes bytes + * @return true on success or false otherwise. On success for decryption len minus tag size + * bytes will be stored in out, for encryption out will contain len bytes plus + * tag size. + */ +bool pgp_cipher_aead_finish(pgp_crypt_t *crypt, uint8_t *out, const uint8_t *in, size_t len); + +/** @brief Reset the AEAD cipher's state, calling the finish() and ignoring the result + * @param crypt initialized AEAD crypto + */ +void pgp_cipher_aead_reset(pgp_crypt_t *crypt); + +/** @brief Destroy the cipher object, deallocating all the memory. + * @param crypt initialized AEAD crypto + */ +void pgp_cipher_aead_destroy(pgp_crypt_t *crypt); + +/** @brief Helper function to set AEAD nonce for the chunk by its index. + * iv and nonce should be large enough to hold max nonce bytes + * @param aalg AEAD algorithm used + * @param iv Initial vector for the message, must have 16 bytes of data + * @param nonce Nonce to fill up, should have space for 16 bytes of data + * @param index Chunk's index + * @return Length of the nonce, or 0 if algorithm is unknown + */ +size_t pgp_cipher_aead_nonce(pgp_aead_alg_t aalg, + const uint8_t *iv, + uint8_t * nonce, + size_t index); +#endif // ENABLE_AEAD + +#endif diff --git a/comm/third_party/rnp/src/lib/crypto/symmetric_ossl.cpp b/comm/third_party/rnp/src/lib/crypto/symmetric_ossl.cpp new file mode 100644 index 0000000000..98e90eded3 --- /dev/null +++ b/comm/third_party/rnp/src/lib/crypto/symmetric_ossl.cpp @@ -0,0 +1,644 @@ +/*- + * Copyright (c) 2021 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "crypto.h" +#include "config.h" +#include "defaults.h" + +#include <string.h> +#include <stdlib.h> +#include <assert.h> +#include <openssl/evp.h> +#include <openssl/err.h> +#include "mem.h" +#include "utils.h" + +static const char * +pgp_sa_to_openssl_string(int alg, bool silent = false) +{ + switch (alg) { +#if defined(ENABLE_IDEA) + case PGP_SA_IDEA: + return "idea-ecb"; +#endif + case PGP_SA_TRIPLEDES: + return "des-ede3"; +#if defined(ENABLE_CAST5) + case PGP_SA_CAST5: + return "cast5-ecb"; +#endif +#if defined(ENABLE_BLOWFISH) + case PGP_SA_BLOWFISH: + return "bf-ecb"; +#endif + case PGP_SA_AES_128: + return "aes-128-ecb"; + case PGP_SA_AES_192: + return "aes-192-ecb"; + case PGP_SA_AES_256: + return "aes-256-ecb"; +#if defined(ENABLE_SM2) + case PGP_SA_SM4: + return "sm4-ecb"; +#endif + case PGP_SA_CAMELLIA_128: + return "camellia-128-ecb"; + case PGP_SA_CAMELLIA_192: + return "camellia-192-ecb"; + case PGP_SA_CAMELLIA_256: + return "camellia-256-ecb"; + default: + if (!silent) { + RNP_LOG("Unsupported symmetric algorithm %d", alg); + } + return NULL; + } +} + +bool +pgp_cipher_cfb_start(pgp_crypt_t * crypt, + pgp_symm_alg_t alg, + const uint8_t *key, + const uint8_t *iv) +{ + memset(crypt, 0x0, sizeof(*crypt)); + + const char *cipher_name = pgp_sa_to_openssl_string(alg); + if (!cipher_name) { + RNP_LOG("Unsupported algorithm: %d", alg); + return false; + } + + const EVP_CIPHER *cipher = EVP_get_cipherbyname(cipher_name); + if (!cipher) { + RNP_LOG("Cipher %s is not supported by OpenSSL.", cipher_name); + return false; + } + + crypt->alg = alg; + crypt->blocksize = pgp_block_size(alg); + + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + int res = EVP_EncryptInit_ex(ctx, cipher, NULL, key, iv); + if (res != 1) { + RNP_LOG("Failed to initialize cipher."); + EVP_CIPHER_CTX_free(ctx); + return false; + } + crypt->cfb.obj = ctx; + + if (iv) { + // Otherwise left as all zeros via memset at start of function + memcpy(crypt->cfb.iv, iv, crypt->blocksize); + } + + crypt->cfb.remaining = 0; + return true; +} + +void +pgp_cipher_cfb_resync(pgp_crypt_t *crypt, const uint8_t *buf) +{ + /* iv will be encrypted in the upcoming call to encrypt/decrypt */ + memcpy(crypt->cfb.iv, buf, crypt->blocksize); + crypt->cfb.remaining = 0; +} + +int +pgp_cipher_cfb_finish(pgp_crypt_t *crypt) +{ + if (!crypt) { + return 0; + } + if (crypt->cfb.obj) { + EVP_CIPHER_CTX_free(crypt->cfb.obj); + crypt->cfb.obj = NULL; + } + OPENSSL_cleanse((uint8_t *) crypt, sizeof(*crypt)); + return 0; +} + +/* we rely on fact that in and out could be the same */ +int +pgp_cipher_cfb_encrypt(pgp_crypt_t *crypt, uint8_t *out, const uint8_t *in, size_t bytes) +{ + uint64_t *in64; + uint64_t buf64[512]; // 4KB - page size + uint64_t iv64[2]; + size_t blocks, blockb; + unsigned blsize = crypt->blocksize; + + /* encrypting till the block boundary */ + while (bytes && crypt->cfb.remaining) { + *out = *in++ ^ crypt->cfb.iv[blsize - crypt->cfb.remaining]; + crypt->cfb.iv[blsize - crypt->cfb.remaining] = *out++; + crypt->cfb.remaining--; + bytes--; + } + + if (!bytes) { + return 0; + } + + /* encrypting full blocks */ + if (bytes > blsize) { + memcpy(iv64, crypt->cfb.iv, blsize); + while ((blocks = bytes & ~(blsize - 1)) > 0) { + if (blocks > sizeof(buf64)) { + blocks = sizeof(buf64); + } + bytes -= blocks; + blockb = blocks; + memcpy(buf64, in, blockb); + in64 = buf64; + + if (blsize == 16) { + blocks >>= 4; + while (blocks--) { + int outlen = 16; + EVP_EncryptUpdate( + crypt->cfb.obj, (uint8_t *) iv64, &outlen, (uint8_t *) iv64, 16); + if (outlen != 16) { + RNP_LOG("Bad outlen: must be 16"); + } + *in64 ^= iv64[0]; + iv64[0] = *in64++; + *in64 ^= iv64[1]; + iv64[1] = *in64++; + } + } else { + blocks >>= 3; + while (blocks--) { + int outlen = 8; + EVP_EncryptUpdate( + crypt->cfb.obj, (uint8_t *) iv64, &outlen, (uint8_t *) iv64, 8); + if (outlen != 8) { + RNP_LOG("Bad outlen: must be 8"); + } + *in64 ^= iv64[0]; + iv64[0] = *in64++; + } + } + + memcpy(out, buf64, blockb); + out += blockb; + in += blockb; + } + + memcpy(crypt->cfb.iv, iv64, blsize); + } + + if (!bytes) { + return 0; + } + + int outlen = blsize; + EVP_EncryptUpdate(crypt->cfb.obj, crypt->cfb.iv, &outlen, crypt->cfb.iv, (int) blsize); + if (outlen != (int) blsize) { + RNP_LOG("Bad outlen: must be %u", blsize); + } + crypt->cfb.remaining = blsize; + + /* encrypting tail */ + while (bytes) { + *out = *in++ ^ crypt->cfb.iv[blsize - crypt->cfb.remaining]; + crypt->cfb.iv[blsize - crypt->cfb.remaining] = *out++; + crypt->cfb.remaining--; + bytes--; + } + + return 0; +} + +/* we rely on fact that in and out could be the same */ +int +pgp_cipher_cfb_decrypt(pgp_crypt_t *crypt, uint8_t *out, const uint8_t *in, size_t bytes) +{ + /* for better code readability */ + uint64_t *out64, *in64; + uint64_t inbuf64[512]; // 4KB - page size + uint64_t outbuf64[512]; + uint64_t iv64[2]; + size_t blocks, blockb; + unsigned blsize = crypt->blocksize; + + /* decrypting till the block boundary */ + while (bytes && crypt->cfb.remaining) { + uint8_t c = *in++; + *out++ = c ^ crypt->cfb.iv[blsize - crypt->cfb.remaining]; + crypt->cfb.iv[blsize - crypt->cfb.remaining] = c; + crypt->cfb.remaining--; + bytes--; + } + + if (!bytes) { + return 0; + } + + /* decrypting full blocks */ + if (bytes > blsize) { + memcpy(iv64, crypt->cfb.iv, blsize); + + while ((blocks = bytes & ~(blsize - 1)) > 0) { + if (blocks > sizeof(inbuf64)) { + blocks = sizeof(inbuf64); + } + bytes -= blocks; + blockb = blocks; + memcpy(inbuf64, in, blockb); + out64 = outbuf64; + in64 = inbuf64; + + if (blsize == 16) { + blocks >>= 4; + while (blocks--) { + int outlen = 16; + EVP_EncryptUpdate( + crypt->cfb.obj, (uint8_t *) iv64, &outlen, (uint8_t *) iv64, 16); + if (outlen != 16) { + RNP_LOG("Bad outlen: must be 16"); + } + *out64++ = *in64 ^ iv64[0]; + iv64[0] = *in64++; + *out64++ = *in64 ^ iv64[1]; + iv64[1] = *in64++; + } + } else { + blocks >>= 3; + while (blocks--) { + int outlen = 8; + EVP_EncryptUpdate( + crypt->cfb.obj, (uint8_t *) iv64, &outlen, (uint8_t *) iv64, 8); + if (outlen != 8) { + RNP_LOG("Bad outlen: must be 8"); + } + *out64++ = *in64 ^ iv64[0]; + iv64[0] = *in64++; + } + } + + memcpy(out, outbuf64, blockb); + out += blockb; + in += blockb; + } + + memcpy(crypt->cfb.iv, iv64, blsize); + } + + if (!bytes) { + return 0; + } + + int outlen = blsize; + EVP_EncryptUpdate(crypt->cfb.obj, crypt->cfb.iv, &outlen, crypt->cfb.iv, (int) blsize); + if (outlen != (int) blsize) { + RNP_LOG("Bad outlen: must be %u", blsize); + } + crypt->cfb.remaining = blsize; + + /* decrypting tail */ + while (bytes) { + uint8_t c = *in++; + *out++ = c ^ crypt->cfb.iv[blsize - crypt->cfb.remaining]; + crypt->cfb.iv[blsize - crypt->cfb.remaining] = c; + crypt->cfb.remaining--; + bytes--; + } + + return 0; +} + +size_t +pgp_cipher_block_size(pgp_crypt_t *crypt) +{ + return crypt->blocksize; +} + +unsigned +pgp_block_size(pgp_symm_alg_t alg) +{ + switch (alg) { + case PGP_SA_IDEA: + case PGP_SA_TRIPLEDES: + case PGP_SA_CAST5: + case PGP_SA_BLOWFISH: + return 8; + case PGP_SA_AES_128: + case PGP_SA_AES_192: + case PGP_SA_AES_256: + case PGP_SA_TWOFISH: + case PGP_SA_CAMELLIA_128: + case PGP_SA_CAMELLIA_192: + case PGP_SA_CAMELLIA_256: + case PGP_SA_SM4: + return 16; + default: + return 0; + } +} + +unsigned +pgp_key_size(pgp_symm_alg_t alg) +{ + /* Update MAX_SYMM_KEY_SIZE after adding algorithm + * with bigger key size. + */ + static_assert(32 == MAX_SYMM_KEY_SIZE, "MAX_SYMM_KEY_SIZE must be updated"); + + switch (alg) { + case PGP_SA_IDEA: + case PGP_SA_CAST5: + case PGP_SA_BLOWFISH: + case PGP_SA_AES_128: + case PGP_SA_CAMELLIA_128: + case PGP_SA_SM4: + return 16; + case PGP_SA_TRIPLEDES: + case PGP_SA_AES_192: + case PGP_SA_CAMELLIA_192: + return 24; + case PGP_SA_TWOFISH: + case PGP_SA_AES_256: + case PGP_SA_CAMELLIA_256: + return 32; + default: + return 0; + } +} + +bool +pgp_is_sa_supported(int alg, bool silent) +{ + return pgp_sa_to_openssl_string(alg, silent); +} + +#if defined(ENABLE_AEAD) + +static const char * +openssl_aead_name(pgp_symm_alg_t ealg, pgp_aead_alg_t aalg) +{ + switch (aalg) { + case PGP_AEAD_OCB: + break; + default: + RNP_LOG("Only OCB mode is supported by the OpenSSL backend."); + return NULL; + } + switch (ealg) { + case PGP_SA_AES_128: + return "AES-128-OCB"; + case PGP_SA_AES_192: + return "AES-192-OCB"; + case PGP_SA_AES_256: + return "AES-256-OCB"; + default: + RNP_LOG("Only AES-OCB is supported by the OpenSSL backend."); + return NULL; + } +} + +bool +pgp_cipher_aead_init(pgp_crypt_t * crypt, + pgp_symm_alg_t ealg, + pgp_aead_alg_t aalg, + const uint8_t *key, + bool decrypt) +{ + memset(crypt, 0x0, sizeof(*crypt)); + /* OpenSSL backend currently supports only AES-OCB */ + const char *algname = openssl_aead_name(ealg, aalg); + if (!algname) { + return false; + } + auto cipher = EVP_get_cipherbyname(algname); + if (!cipher) { + RNP_LOG("Cipher %s is not supported.", algname); + return false; + } + /* Create and setup context */ + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + if (!ctx) { + RNP_LOG("Failed to create cipher context: %lu", ERR_peek_last_error()); + return false; + } + + crypt->aead.key = new rnp::secure_vector<uint8_t>(key, key + pgp_key_size(ealg)); + crypt->alg = ealg; + crypt->blocksize = pgp_block_size(ealg); + crypt->aead.cipher = cipher; + crypt->aead.obj = ctx; + crypt->aead.alg = aalg; + crypt->aead.decrypt = decrypt; + crypt->aead.granularity = crypt->blocksize; + crypt->aead.taglen = PGP_AEAD_EAX_OCB_TAG_LEN; + crypt->aead.ad_len = 0; + crypt->aead.n_len = pgp_cipher_aead_nonce_len(aalg); + return true; +} + +size_t +pgp_cipher_aead_granularity(pgp_crypt_t *crypt) +{ + return crypt->aead.granularity; +} +#endif + +size_t +pgp_cipher_aead_nonce_len(pgp_aead_alg_t aalg) +{ + switch (aalg) { + case PGP_AEAD_EAX: + return PGP_AEAD_EAX_NONCE_LEN; + case PGP_AEAD_OCB: + return PGP_AEAD_OCB_NONCE_LEN; + default: + return 0; + } +} + +size_t +pgp_cipher_aead_tag_len(pgp_aead_alg_t aalg) +{ + switch (aalg) { + case PGP_AEAD_EAX: + case PGP_AEAD_OCB: + return PGP_AEAD_EAX_OCB_TAG_LEN; + default: + return 0; + } +} + +#if defined(ENABLE_AEAD) +bool +pgp_cipher_aead_set_ad(pgp_crypt_t *crypt, const uint8_t *ad, size_t len) +{ + assert(len <= sizeof(crypt->aead.ad)); + memcpy(crypt->aead.ad, ad, len); + crypt->aead.ad_len = len; + return true; +} + +bool +pgp_cipher_aead_start(pgp_crypt_t *crypt, const uint8_t *nonce, size_t len) +{ + auto &aead = crypt->aead; + auto ctx = aead.obj; + int enc = aead.decrypt ? 0 : 1; + assert(len == aead.n_len); + EVP_CIPHER_CTX_reset(ctx); + if (EVP_CipherInit_ex(ctx, aead.cipher, NULL, NULL, NULL, enc) != 1) { + RNP_LOG("Failed to initialize cipher: %lu", ERR_peek_last_error()); + return false; + } + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, aead.n_len, NULL) != 1) { + RNP_LOG("Failed to set nonce length: %lu", ERR_peek_last_error()); + return false; + } + if (EVP_CipherInit_ex(ctx, NULL, NULL, aead.key->data(), nonce, enc) != 1) { + RNP_LOG("Failed to start cipher: %lu", ERR_peek_last_error()); + return false; + } + int adlen = 0; + if (EVP_CipherUpdate(ctx, NULL, &adlen, aead.ad, aead.ad_len) != 1) { + RNP_LOG("Failed to set AD: %lu", ERR_peek_last_error()); + return false; + } + return true; +} + +bool +pgp_cipher_aead_update(pgp_crypt_t *crypt, uint8_t *out, const uint8_t *in, size_t len) +{ + if (!len) { + return true; + } + int out_len = 0; + 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()); + } + assert(out_len == (int) len); + return res; +} + +void +pgp_cipher_aead_reset(pgp_crypt_t *crypt) +{ + /* Do nothing as subsequent pgp_cipher_aead_start() call will reset context */ +} + +bool +pgp_cipher_aead_finish(pgp_crypt_t *crypt, uint8_t *out, const uint8_t *in, size_t len) +{ + auto &aead = crypt->aead; + auto ctx = aead.obj; + if (aead.decrypt) { + assert(len >= aead.taglen); + if (len < aead.taglen) { + RNP_LOG("Invalid state: too few input bytes."); + return false; + } + size_t data_len = len - aead.taglen; + int out_len = 0; + if (EVP_CipherUpdate(ctx, out, &out_len, in, data_len) != 1) { + RNP_LOG("Failed to update cipher: %lu", ERR_peek_last_error()); + return false; + } + uint8_t tag[PGP_AEAD_MAX_TAG_LEN] = {0}; + memcpy(tag, in + data_len, aead.taglen); + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, aead.taglen, tag) != 1) { + RNP_LOG("Failed to set tag: %lu", ERR_peek_last_error()); + return false; + } + int out_len2 = 0; + if (EVP_CipherFinal_ex(ctx, out + out_len, &out_len2) != 1) { + /* Zero value if auth tag is incorrect */ + if (ERR_peek_last_error()) { + RNP_LOG("Failed to finish AEAD decryption: %lu", ERR_peek_last_error()); + } + return false; + } + assert(out_len + out_len2 == (int) (len - aead.taglen)); + } else { + int out_len = 0; + if (EVP_CipherUpdate(ctx, out, &out_len, in, len) != 1) { + RNP_LOG("Failed to update cipher: %lu", ERR_peek_last_error()); + return false; + } + int out_len2 = 0; + if (EVP_CipherFinal_ex(ctx, out + out_len, &out_len2) != 1) { + RNP_LOG("Failed to finish AEAD encryption: %lu", ERR_peek_last_error()); + return false; + } + assert(out_len + out_len2 == (int) len); + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, aead.taglen, out + len) != 1) { + RNP_LOG("Failed to get tag: %lu", ERR_peek_last_error()); + return false; + } + } + return true; +} + +void +pgp_cipher_aead_destroy(pgp_crypt_t *crypt) +{ + if (crypt->aead.obj) { + EVP_CIPHER_CTX_free(crypt->aead.obj); + } + delete crypt->aead.key; + memset(crypt, 0x0, sizeof(*crypt)); +} + +size_t +pgp_cipher_aead_nonce(pgp_aead_alg_t aalg, const uint8_t *iv, uint8_t *nonce, size_t index) +{ + switch (aalg) { + case PGP_AEAD_EAX: + /* The nonce for EAX mode is computed by treating the starting + initialization vector as a 16-octet, big-endian value and + exclusive-oring the low eight octets of it with the chunk index. + */ + memcpy(nonce, iv, PGP_AEAD_EAX_NONCE_LEN); + for (int i = 15; (i > 7) && index; i--) { + nonce[i] ^= index & 0xff; + index = index >> 8; + } + return PGP_AEAD_EAX_NONCE_LEN; + case PGP_AEAD_OCB: + /* The nonce for a chunk of chunk index "i" in OCB processing is defined as: + OCB-Nonce_{i} = IV[1..120] xor i + */ + memcpy(nonce, iv, PGP_AEAD_OCB_NONCE_LEN); + for (int i = 14; (i >= 0) && index; i--) { + nonce[i] ^= index & 0xff; + index = index >> 8; + } + return PGP_AEAD_OCB_NONCE_LEN; + default: + return 0; + } +} +#endif diff --git a/comm/third_party/rnp/src/lib/defaults.h b/comm/third_party/rnp/src/lib/defaults.h new file mode 100644 index 0000000000..22a3c46377 --- /dev/null +++ b/comm/third_party/rnp/src/lib/defaults.h @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2018-2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DEFAULTS_H_ +#define DEFAULTS_H_ + +/* Default hash algorithm as PGP constant */ +#define DEFAULT_PGP_HASH_ALG PGP_HASH_SHA256 + +/* Default symmetric algorithm as PGP constant */ +#define DEFAULT_PGP_SYMM_ALG PGP_SA_AES_256 + +/* Default number of msec to run S2K derivation */ +#define DEFAULT_S2K_MSEC 150 + +/* Default number of msec to run S2K tuning */ +#define DEFAULT_S2K_TUNE_MSEC 10 + +/* Default compression algorithm and level */ +#define DEFAULT_Z_ALG "ZIP" +#define DEFAULT_Z_LEVEL 6 + +/* Default AEAD algorithm */ +#define DEFAULT_AEAD_ALG PGP_AEAD_OCB + +/* Default AEAD chunk bits, equals to 256K chunks */ +#define DEFAULT_AEAD_CHUNK_BITS 12 + +/* Default cipher mode for secret key encryption */ +#define DEFAULT_CIPHER_MODE "CFB" + +/* Default cipher mode for secret key encryption */ +#define DEFAULT_PGP_CIPHER_MODE PGP_CIPHER_MODE_CFB + +/* Default public key algorithm for new key generation */ +#define DEFAULT_PK_ALG PGP_PKA_RSA + +/* Default RSA key length */ +#define DEFAULT_RSA_NUMBITS 2048 + +/* Default ElGamal key length */ +#define DEFAULT_ELGAMAL_NUMBITS 2048 +#define ELGAMAL_MIN_P_BITLEN 1024 +#define ELGAMAL_MAX_P_BITLEN 4096 + +/* Default, min and max DSA key length */ +#define DSA_MIN_P_BITLEN 1024 +#define DSA_MAX_P_BITLEN 3072 +#define DSA_DEFAULT_P_BITLEN 2048 + +/* Default EC curve */ +#define DEFAULT_CURVE "NIST P-256" + +/* Default maximum password request attempts */ +#define MAX_PASSWORD_ATTEMPTS 3 + +/* Infinite password request attempts */ +#define INFINITE_ATTEMPTS -1 + +/* Default key expiration in seconds, 2 years */ +#define DEFAULT_KEY_EXPIRATION (2 * 365 * 24 * 60 * 60) + +#endif diff --git a/comm/third_party/rnp/src/lib/ffi-priv-types.h b/comm/third_party/rnp/src/lib/ffi-priv-types.h new file mode 100644 index 0000000000..beb624c9ba --- /dev/null +++ b/comm/third_party/rnp/src/lib/ffi-priv-types.h @@ -0,0 +1,240 @@ +/*- + * Copyright (c) 2019 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR + * CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <rnp/rnp.h> +#include <json.h> +#include "utils.h" +#include <list> +#include <crypto/mem.h> +#include "sec_profile.hpp" + +struct rnp_key_handle_st { + rnp_ffi_t ffi; + pgp_key_search_t locator; + pgp_key_t * pub; + pgp_key_t * sec; +}; + +struct rnp_uid_handle_st { + rnp_ffi_t ffi; + pgp_key_t *key; + size_t idx; +}; + +struct rnp_signature_handle_st { + rnp_ffi_t ffi; + const pgp_key_t *key; + pgp_subsig_t * sig; + bool own_sig; +}; + +struct rnp_recipient_handle_st { + rnp_ffi_t ffi; + uint8_t keyid[PGP_KEY_ID_SIZE]; + pgp_pubkey_alg_t palg; +}; + +struct rnp_symenc_handle_st { + rnp_ffi_t ffi; + pgp_symm_alg_t alg; + pgp_hash_alg_t halg; + pgp_s2k_specifier_t s2k_type; + uint32_t iterations; + pgp_aead_alg_t aalg; +}; + +struct rnp_ffi_st { + FILE * errs; + rnp_key_store_t * pubring; + rnp_key_store_t * secring; + rnp_get_key_cb getkeycb; + void * getkeycb_ctx; + rnp_password_cb getpasscb; + void * getpasscb_ctx; + pgp_key_provider_t key_provider; + pgp_password_provider_t pass_provider; + rnp::SecurityContext context; + + rnp_ffi_st(pgp_key_store_format_t pub_fmt, pgp_key_store_format_t sec_fmt); + ~rnp_ffi_st(); + + rnp::RNG & rng() noexcept; + rnp::SecurityProfile &profile() noexcept; +}; + +struct rnp_input_st { + /* either src or src_directory are valid, not both */ + pgp_source_t src; + std::string src_directory; + rnp_input_reader_t *reader; + rnp_input_closer_t *closer; + void * app_ctx; + + rnp_input_st(); + rnp_input_st(const rnp_input_st &) = delete; + rnp_input_st(rnp_input_st &&) = delete; + ~rnp_input_st(); + + rnp_input_st &operator=(const rnp_input_st &) = delete; + rnp_input_st &operator=(rnp_input_st &&src); +}; + +struct rnp_output_st { + /* either dst or dst_directory are valid, not both */ + pgp_dest_t dst; + char * dst_directory; + rnp_output_writer_t *writer; + rnp_output_closer_t *closer; + void * app_ctx; + bool keep; +}; + +struct rnp_op_generate_st { + rnp_ffi_t ffi{}; + bool primary{}; + pgp_key_t *primary_sec{}; + pgp_key_t *primary_pub{}; + pgp_key_t *gen_sec{}; + pgp_key_t *gen_pub{}; + /* password used to encrypt the key, if specified */ + rnp::secure_vector<char> password; + /* request password for key encryption via ffi's password provider */ + bool request_password{}; + /* we don't use top-level keygen action here for easier fields access */ + rnp_keygen_crypto_params_t crypto{}; + rnp_key_protection_params_t protection{}; + rnp_selfsig_cert_info_t cert{}; + rnp_selfsig_binding_info_t binding{}; +}; + +struct rnp_op_sign_signature_st { + rnp_ffi_t ffi{}; + rnp_signer_info_t signer{}; + bool expiry_set : 1; + bool create_set : 1; + bool hash_set : 1; +}; + +typedef std::list<rnp_op_sign_signature_st> rnp_op_sign_signatures_t; + +struct rnp_op_sign_st { + rnp_ffi_t ffi{}; + rnp_input_t input{}; + rnp_output_t output{}; + rnp_ctx_t rnpctx{}; + rnp_op_sign_signatures_t signatures{}; +}; + +struct rnp_op_verify_signature_st { + rnp_ffi_t ffi; + rnp_result_t verify_status; + pgp_signature_t sig_pkt; +}; + +struct rnp_op_verify_st { + rnp_ffi_t ffi{}; + rnp_input_t input{}; + rnp_input_t detached_input{}; /* for detached signature will be source file/data */ + rnp_output_t output{}; + rnp_ctx_t rnpctx{}; + /* these fields are filled after operation execution */ + rnp_op_verify_signature_t signatures{}; + size_t signature_count{}; + char * filename{}; + uint32_t file_mtime{}; + /* encryption information */ + bool encrypted{}; + bool mdc{}; + bool validated{}; + pgp_aead_alg_t aead{}; + pgp_symm_alg_t salg{}; + bool ignore_sigs{}; + bool require_all_sigs{}; + bool allow_hidden{}; + /* recipient/symenc information */ + rnp_recipient_handle_t recipients{}; + size_t recipient_count{}; + rnp_recipient_handle_t used_recipient{}; + rnp_symenc_handle_t symencs{}; + size_t symenc_count{}; + rnp_symenc_handle_t used_symenc{}; + size_t encrypted_layers{}; + + ~rnp_op_verify_st(); +}; + +struct rnp_op_encrypt_st { + rnp_ffi_t ffi{}; + rnp_input_t input{}; + rnp_output_t output{}; + rnp_ctx_t rnpctx{}; + rnp_op_sign_signatures_t signatures{}; +}; + +#define RNP_LOCATOR_MAX_SIZE (MAX_ID_LENGTH + 1) +static_assert(RNP_LOCATOR_MAX_SIZE > PGP_FINGERPRINT_SIZE * 2, "Locator size mismatch."); +static_assert(RNP_LOCATOR_MAX_SIZE > PGP_KEY_ID_SIZE * 2, "Locator size mismatch."); +static_assert(RNP_LOCATOR_MAX_SIZE > PGP_KEY_GRIP_SIZE * 2, "Locator size mismatch."); +static_assert(RNP_LOCATOR_MAX_SIZE > MAX_ID_LENGTH, "Locator size mismatch."); + +struct rnp_identifier_iterator_st { + rnp_ffi_t ffi; + pgp_key_search_type_t type; + rnp_key_store_t * store; + std::list<pgp_key_t>::iterator *keyp; + unsigned uididx; + json_object * tbl; + char buf[RNP_LOCATOR_MAX_SIZE]; +}; + +struct rnp_decryption_kp_param_t { + rnp_op_verify_t op; + bool has_hidden; /* key provider had hidden keyid request */ + pgp_key_t * last; /* last key, returned in hidden keyid request */ + + rnp_decryption_kp_param_t(rnp_op_verify_t opobj) + : op(opobj), has_hidden(false), last(NULL){}; +}; + +/* This is just for readability at the call site and will hopefully reduce mistakes. + * + * Instead of: + * void do_something(rnp_ffi_t ffi, bool with_secret_keys); + * do_something(ffi, true); + * do_something(ffi, false); + * + * You can have something a bit clearer: + * void do_something(rnp_ffi_t ffi, key_type_t key_type); + * do_something(ffi, KEY_TYPE_PUBLIC); + * do_something(ffi, KEY_TYPE_SECRET); + */ +typedef enum key_type_t { + KEY_TYPE_NONE, + KEY_TYPE_PUBLIC, + KEY_TYPE_SECRET, + KEY_TYPE_ANY +} key_type_t; diff --git a/comm/third_party/rnp/src/lib/fingerprint.cpp b/comm/third_party/rnp/src/lib/fingerprint.cpp new file mode 100644 index 0000000000..c937c74915 --- /dev/null +++ b/comm/third_party/rnp/src/lib/fingerprint.cpp @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2017-2022, [Ribose Inc](https://www.ribose.com). + * Copyright (c) 2009 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is originally derived from software contributed to + * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and + * carried further by Ribose Inc (https://www.ribose.com). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <string.h> +#include "fingerprint.h" +#include "crypto/hash.hpp" +#include <librepgp/stream-key.h> +#include <librepgp/stream-sig.h> +#include <librepgp/stream-packet.h> +#include "utils.h" + +rnp_result_t +pgp_fingerprint(pgp_fingerprint_t &fp, const pgp_key_pkt_t &key) +{ + if ((key.version == PGP_V2) || (key.version == PGP_V3)) { + if (!is_rsa_key_alg(key.alg)) { + RNP_LOG("bad algorithm"); + return RNP_ERROR_NOT_SUPPORTED; + } + try { + auto hash = rnp::Hash::create(PGP_HASH_MD5); + hash->add(key.material.rsa.n); + hash->add(key.material.rsa.e); + fp.length = hash->finish(fp.fingerprint); + return RNP_SUCCESS; + } catch (const std::exception &e) { + RNP_LOG("Failed to calculate v3 fingerprint: %s", e.what()); + return RNP_ERROR_BAD_STATE; + } + } + + if (key.version != PGP_V4) { + RNP_LOG("unsupported key version"); + return RNP_ERROR_NOT_SUPPORTED; + } + + try { + auto hash = rnp::Hash::create(PGP_HASH_SHA1); + signature_hash_key(key, *hash); + fp.length = hash->finish(fp.fingerprint); + return RNP_SUCCESS; + } catch (const std::exception &e) { + RNP_LOG("Failed to calculate v4 fingerprint: %s", e.what()); + return RNP_ERROR_BAD_STATE; + } +} + +/** + * \ingroup Core_Keys + * \brief Calculate the Key ID from the public key. + * \param keyid Space for the calculated ID to be stored + * \param key The key for which the ID is calculated + */ + +rnp_result_t +pgp_keyid(pgp_key_id_t &keyid, const pgp_key_pkt_t &key) +{ + pgp_fingerprint_t fp; + rnp_result_t ret; + size_t n; + + if ((key.version == PGP_V2) || (key.version == PGP_V3)) { + if (!is_rsa_key_alg(key.alg)) { + RNP_LOG("bad algorithm"); + return RNP_ERROR_NOT_SUPPORTED; + } + n = mpi_bytes(&key.material.rsa.n); + (void) memcpy(keyid.data(), key.material.rsa.n.mpi + n - keyid.size(), keyid.size()); + return RNP_SUCCESS; + } + + if ((ret = pgp_fingerprint(fp, key))) { + return ret; + } + (void) memcpy(keyid.data(), fp.fingerprint + fp.length - keyid.size(), keyid.size()); + return RNP_SUCCESS; +} + +bool +pgp_fingerprint_t::operator==(const pgp_fingerprint_t &src) const +{ + return (length == src.length) && !memcmp(fingerprint, src.fingerprint, length); +} + +bool +pgp_fingerprint_t::operator!=(const pgp_fingerprint_t &src) const +{ + return !(*this == src); +} diff --git a/comm/third_party/rnp/src/lib/fingerprint.h b/comm/third_party/rnp/src/lib/fingerprint.h new file mode 100644 index 0000000000..e8d47138fd --- /dev/null +++ b/comm/third_party/rnp/src/lib/fingerprint.h @@ -0,0 +1,39 @@ +/*- + * Copyright (c) 2017 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RNP_FINGERPRINT_H_ +#define RNP_FINGERPRINT_H_ + +#include <rnp/rnp_def.h> +#include <stdint.h> +#include <stdlib.h> +#include "types.h" + +rnp_result_t pgp_fingerprint(pgp_fingerprint_t &fp, const pgp_key_pkt_t &key); + +rnp_result_t pgp_keyid(pgp_key_id_t &keyid, const pgp_key_pkt_t &key); + +#endif diff --git a/comm/third_party/rnp/src/lib/generate-key.cpp b/comm/third_party/rnp/src/lib/generate-key.cpp new file mode 100644 index 0000000000..dfd5556dd6 --- /dev/null +++ b/comm/third_party/rnp/src/lib/generate-key.cpp @@ -0,0 +1,467 @@ +/* + * Copyright (c) 2017, [Ribose Inc](https://www.ribose.com). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include <stdbool.h> +#include <stdint.h> +#include <assert.h> +#include <string.h> + +#include <rekey/rnp_key_store.h> +#include <librekey/key_store_pgp.h> +#include <librekey/key_store_g10.h> +#include <librepgp/stream-packet.h> +#include "crypto.h" +#include "pgp-key.h" +#include "defaults.h" +#include "utils.h" + +static const uint8_t DEFAULT_SYMMETRIC_ALGS[] = { + PGP_SA_AES_256, PGP_SA_AES_192, PGP_SA_AES_128}; +static const uint8_t DEFAULT_HASH_ALGS[] = { + PGP_HASH_SHA256, PGP_HASH_SHA384, PGP_HASH_SHA512, PGP_HASH_SHA224}; +static const uint8_t DEFAULT_COMPRESS_ALGS[] = { + PGP_C_ZLIB, PGP_C_BZIP2, PGP_C_ZIP, PGP_C_NONE}; + +static const id_str_pair pubkey_alg_map[] = { + {PGP_PKA_RSA, "RSA (Encrypt or Sign)"}, + {PGP_PKA_RSA_ENCRYPT_ONLY, "RSA Encrypt-Only"}, + {PGP_PKA_RSA_SIGN_ONLY, "RSA Sign-Only"}, + {PGP_PKA_ELGAMAL, "Elgamal (Encrypt-Only)"}, + {PGP_PKA_DSA, "DSA"}, + {PGP_PKA_ECDH, "ECDH"}, + {PGP_PKA_ECDSA, "ECDSA"}, + {PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN, "Reserved (formerly Elgamal Encrypt or Sign"}, + {PGP_PKA_RESERVED_DH, "Reserved for Diffie-Hellman (X9.42)"}, + {PGP_PKA_EDDSA, "EdDSA"}, + {PGP_PKA_SM2, "SM2"}, + {PGP_PKA_PRIVATE00, "Private/Experimental"}, + {PGP_PKA_PRIVATE01, "Private/Experimental"}, + {PGP_PKA_PRIVATE02, "Private/Experimental"}, + {PGP_PKA_PRIVATE03, "Private/Experimental"}, + {PGP_PKA_PRIVATE04, "Private/Experimental"}, + {PGP_PKA_PRIVATE05, "Private/Experimental"}, + {PGP_PKA_PRIVATE06, "Private/Experimental"}, + {PGP_PKA_PRIVATE07, "Private/Experimental"}, + {PGP_PKA_PRIVATE08, "Private/Experimental"}, + {PGP_PKA_PRIVATE09, "Private/Experimental"}, + {PGP_PKA_PRIVATE10, "Private/Experimental"}, + {0, NULL}}; + +static bool +load_generated_g10_key(pgp_key_t * dst, + pgp_key_pkt_t * newkey, + pgp_key_t * primary_key, + pgp_key_t * pubkey, + rnp::SecurityContext &ctx) +{ + // this should generally be zeroed + assert(dst->type() == 0); + // if a primary is provided, make sure it's actually a primary key + assert(!primary_key || primary_key->is_primary()); + // if a pubkey is provided, make sure it's actually a public key + assert(!pubkey || pubkey->is_public()); + // G10 always needs pubkey here + assert(pubkey); + + // this would be better on the stack but the key store does not allow it + std::unique_ptr<rnp_key_store_t> key_store(new (std::nothrow) rnp_key_store_t(ctx)); + if (!key_store) { + return false; + } + /* Write g10 seckey */ + rnp::MemoryDest memdst(NULL, 0); + if (!g10_write_seckey(&memdst.dst(), newkey, NULL, ctx)) { + RNP_LOG("failed to write generated seckey"); + return false; + } + + std::vector<pgp_key_t *> key_ptrs; /* holds primary and pubkey, when used */ + // if this is a subkey, add the primary in first + if (primary_key) { + key_ptrs.push_back(primary_key); + } + // G10 needs the pubkey for copying some attributes (key version, creation time, etc) + key_ptrs.push_back(pubkey); + + rnp::MemorySource memsrc(memdst.memory(), memdst.writeb(), false); + pgp_key_provider_t prov(rnp_key_provider_key_ptr_list, &key_ptrs); + if (!rnp_key_store_g10_from_src(key_store.get(), &memsrc.src(), &prov)) { + return false; + } + if (rnp_key_store_get_key_count(key_store.get()) != 1) { + return false; + } + // if a primary key is provided, it should match the sub with regards to type + assert(!primary_key || (primary_key->is_secret() == key_store->keys.front().is_secret())); + *dst = pgp_key_t(key_store->keys.front()); + return true; +} + +static uint8_t +pk_alg_default_flags(pgp_pubkey_alg_t alg) +{ + // just use the full capabilities as the ultimate fallback + return pgp_pk_alg_capabilities(alg); +} + +// TODO: Similar as pgp_pick_hash_alg but different enough to +// keep another version. This will be changed when refactoring crypto +static void +adjust_hash_alg(rnp_keygen_crypto_params_t &crypto) +{ + if (!crypto.hash_alg) { + crypto.hash_alg = (pgp_hash_alg_t) DEFAULT_HASH_ALGS[0]; + } + + if ((crypto.key_alg != PGP_PKA_DSA) && (crypto.key_alg != PGP_PKA_ECDSA)) { + return; + } + + pgp_hash_alg_t min_hash = (crypto.key_alg == PGP_PKA_ECDSA) ? + ecdsa_get_min_hash(crypto.ecc.curve) : + dsa_get_min_hash(crypto.dsa.q_bitlen); + + if (rnp::Hash::size(crypto.hash_alg) < rnp::Hash::size(min_hash)) { + crypto.hash_alg = min_hash; + } +} + +static void +keygen_merge_crypto_defaults(rnp_keygen_crypto_params_t &crypto) +{ + // default to RSA + if (!crypto.key_alg) { + crypto.key_alg = PGP_PKA_RSA; + } + + switch (crypto.key_alg) { + case PGP_PKA_RSA: + if (!crypto.rsa.modulus_bit_len) { + crypto.rsa.modulus_bit_len = DEFAULT_RSA_NUMBITS; + } + break; + + case PGP_PKA_SM2: + if (!crypto.hash_alg) { + crypto.hash_alg = PGP_HASH_SM3; + } + if (!crypto.ecc.curve) { + crypto.ecc.curve = PGP_CURVE_SM2_P_256; + } + break; + + case PGP_PKA_ECDH: + case PGP_PKA_ECDSA: { + if (!crypto.hash_alg) { + crypto.hash_alg = (pgp_hash_alg_t) DEFAULT_HASH_ALGS[0]; + } + break; + } + + case PGP_PKA_EDDSA: + if (!crypto.ecc.curve) { + crypto.ecc.curve = PGP_CURVE_ED25519; + } + break; + + case PGP_PKA_DSA: { + if (!crypto.dsa.p_bitlen) { + crypto.dsa.p_bitlen = DSA_DEFAULT_P_BITLEN; + } + if (!crypto.dsa.q_bitlen) { + crypto.dsa.q_bitlen = dsa_choose_qsize_by_psize(crypto.dsa.p_bitlen); + } + break; + } + default: + break; + } + + adjust_hash_alg(crypto); +} + +static bool +validate_keygen_primary(const rnp_keygen_primary_desc_t &desc) +{ + /* Confirm that the specified pk alg can certify. + * gpg requires this, though the RFC only says that a V4 primary + * key SHOULD be a key capable of certification. + */ + if (!(pgp_pk_alg_capabilities(desc.crypto.key_alg) & PGP_KF_CERTIFY)) { + RNP_LOG("primary key alg (%d) must be able to sign", desc.crypto.key_alg); + return false; + } + + // check key flags + if (!desc.cert.key_flags) { + // these are probably not *technically* required + RNP_LOG("key flags are required"); + return false; + } else if (desc.cert.key_flags & ~pgp_pk_alg_capabilities(desc.crypto.key_alg)) { + // check the flags against the alg capabilities + RNP_LOG("usage not permitted for pk algorithm"); + return false; + } + // require a userid + if (!desc.cert.userid[0]) { + RNP_LOG("userid is required for primary key"); + return false; + } + return true; +} + +static uint32_t +get_numbits(const rnp_keygen_crypto_params_t *crypto) +{ + switch (crypto->key_alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + return crypto->rsa.modulus_bit_len; + case PGP_PKA_ECDSA: + case PGP_PKA_ECDH: + case PGP_PKA_EDDSA: + case PGP_PKA_SM2: { + if (const ec_curve_desc_t *curve = get_curve_desc(crypto->ecc.curve)) { + return curve->bitlen; + } else { + return 0; + } + } + case PGP_PKA_DSA: + return crypto->dsa.p_bitlen; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + return crypto->elgamal.key_bitlen; + default: + return 0; + } +} + +static void +set_default_user_prefs(pgp_user_prefs_t &prefs) +{ + if (prefs.symm_algs.empty()) { + prefs.set_symm_algs( + std::vector<uint8_t>(DEFAULT_SYMMETRIC_ALGS, + DEFAULT_SYMMETRIC_ALGS + ARRAY_SIZE(DEFAULT_SYMMETRIC_ALGS))); + } + if (prefs.hash_algs.empty()) { + prefs.set_hash_algs(std::vector<uint8_t>( + DEFAULT_HASH_ALGS, DEFAULT_HASH_ALGS + ARRAY_SIZE(DEFAULT_HASH_ALGS))); + } + if (prefs.z_algs.empty()) { + prefs.set_z_algs(std::vector<uint8_t>( + DEFAULT_COMPRESS_ALGS, DEFAULT_COMPRESS_ALGS + ARRAY_SIZE(DEFAULT_COMPRESS_ALGS))); + } +} + +static void +keygen_primary_merge_defaults(rnp_keygen_primary_desc_t &desc) +{ + keygen_merge_crypto_defaults(desc.crypto); + set_default_user_prefs(desc.cert.prefs); + + if (!desc.cert.key_flags) { + // set some default key flags if none are provided + desc.cert.key_flags = pk_alg_default_flags(desc.crypto.key_alg); + } + if (desc.cert.userid.empty()) { + char uid[MAX_ID_LENGTH] = {0}; + snprintf(uid, + sizeof(uid), + "%s %d-bit key <%s@localhost>", + id_str_pair::lookup(pubkey_alg_map, desc.crypto.key_alg), + get_numbits(&desc.crypto), + getenv_logname()); + desc.cert.userid = uid; + } +} + +bool +pgp_generate_primary_key(rnp_keygen_primary_desc_t &desc, + bool merge_defaults, + pgp_key_t & primary_sec, + pgp_key_t & primary_pub, + pgp_key_store_format_t secformat) +{ + // validate args + if (primary_sec.type() || primary_pub.type()) { + RNP_LOG("invalid parameters (should be zeroed)"); + return false; + } + + try { + // merge some defaults in, if requested + if (merge_defaults) { + keygen_primary_merge_defaults(desc); + } + // now validate the keygen fields + if (!validate_keygen_primary(desc)) { + return false; + } + + // generate the raw key and fill tag/secret fields + pgp_key_pkt_t secpkt; + if (!pgp_generate_seckey(desc.crypto, secpkt, true)) { + return false; + } + + pgp_key_t sec(secpkt); + pgp_key_t pub(secpkt, true); + sec.add_uid_cert(desc.cert, desc.crypto.hash_alg, *desc.crypto.ctx, &pub); + + switch (secformat) { + case PGP_KEY_STORE_GPG: + case PGP_KEY_STORE_KBX: + primary_sec = std::move(sec); + primary_pub = std::move(pub); + break; + case PGP_KEY_STORE_G10: + primary_pub = std::move(pub); + if (!load_generated_g10_key( + &primary_sec, &secpkt, NULL, &primary_pub, *desc.crypto.ctx)) { + RNP_LOG("failed to load generated key"); + return false; + } + break; + default: + RNP_LOG("invalid format"); + return false; + } + } catch (const std::exception &e) { + RNP_LOG("Failure: %s", e.what()); + return false; + } + + /* mark it as valid */ + primary_pub.mark_valid(); + primary_sec.mark_valid(); + /* refresh key's data */ + return primary_pub.refresh_data(*desc.crypto.ctx) && + primary_sec.refresh_data(*desc.crypto.ctx); +} + +static bool +validate_keygen_subkey(rnp_keygen_subkey_desc_t &desc) +{ + if (!desc.binding.key_flags) { + RNP_LOG("key flags are required"); + return false; + } else if (desc.binding.key_flags & ~pgp_pk_alg_capabilities(desc.crypto.key_alg)) { + // check the flags against the alg capabilities + RNP_LOG("usage not permitted for pk algorithm"); + return false; + } + return true; +} + +static void +keygen_subkey_merge_defaults(rnp_keygen_subkey_desc_t &desc) +{ + keygen_merge_crypto_defaults(desc.crypto); + if (!desc.binding.key_flags) { + // set some default key flags if none are provided + desc.binding.key_flags = pk_alg_default_flags(desc.crypto.key_alg); + } +} + +bool +pgp_generate_subkey(rnp_keygen_subkey_desc_t & desc, + bool merge_defaults, + pgp_key_t & primary_sec, + pgp_key_t & primary_pub, + pgp_key_t & subkey_sec, + pgp_key_t & subkey_pub, + const pgp_password_provider_t &password_provider, + pgp_key_store_format_t secformat) +{ + // validate args + if (!primary_sec.is_primary() || !primary_pub.is_primary() || !primary_sec.is_secret() || + !primary_pub.is_public()) { + RNP_LOG("invalid parameters"); + return false; + } + if (subkey_sec.type() || subkey_pub.type()) { + RNP_LOG("invalid parameters (should be zeroed)"); + return false; + } + + // merge some defaults in, if requested + if (merge_defaults) { + keygen_subkey_merge_defaults(desc); + } + + // now validate the keygen fields + if (!validate_keygen_subkey(desc)) { + return false; + } + + try { + /* decrypt the primary seckey if needed (for signatures) */ + rnp::KeyLocker primlock(primary_sec); + if (primary_sec.encrypted() && + !primary_sec.unlock(password_provider, PGP_OP_ADD_SUBKEY)) { + RNP_LOG("Failed to unlock primary key."); + return false; + } + /* generate the raw subkey */ + pgp_key_pkt_t secpkt; + if (!pgp_generate_seckey(desc.crypto, secpkt, false)) { + return false; + } + pgp_key_pkt_t pubpkt = pgp_key_pkt_t(secpkt, true); + pgp_key_t sec(secpkt, primary_sec); + pgp_key_t pub(pubpkt, primary_pub); + /* add binding */ + primary_sec.add_sub_binding( + sec, pub, desc.binding, desc.crypto.hash_alg, *desc.crypto.ctx); + /* copy to the result */ + subkey_pub = std::move(pub); + switch (secformat) { + case PGP_KEY_STORE_GPG: + case PGP_KEY_STORE_KBX: + subkey_sec = std::move(sec); + break; + case PGP_KEY_STORE_G10: + if (!load_generated_g10_key( + &subkey_sec, &secpkt, &primary_sec, &subkey_pub, *desc.crypto.ctx)) { + RNP_LOG("failed to load generated key"); + return false; + } + break; + default: + RNP_LOG("invalid format"); + return false; + } + + subkey_pub.mark_valid(); + subkey_sec.mark_valid(); + return subkey_pub.refresh_data(&primary_pub, *desc.crypto.ctx) && + subkey_sec.refresh_data(&primary_sec, *desc.crypto.ctx); + } catch (const std::exception &e) { + RNP_LOG("Subkey generation failed: %s", e.what()); + return false; + } +} diff --git a/comm/third_party/rnp/src/lib/json-utils.cpp b/comm/third_party/rnp/src/lib/json-utils.cpp new file mode 100644 index 0000000000..742fbc1eeb --- /dev/null +++ b/comm/third_party/rnp/src/lib/json-utils.cpp @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "json-utils.h" +#include "logging.h" +#include "crypto/mem.h" + +/* Shortcut function to add field checking it for null to avoid allocation failure. + Please note that it deallocates val on failure. */ +bool +obj_add_field_json(json_object *obj, const char *name, json_object *val) +{ + if (!val) { + return false; + } + // TODO: in JSON-C 0.13 json_object_object_add returns bool instead of void + json_object_object_add(obj, name, val); + if (!json_object_object_get_ex(obj, name, NULL)) { + json_object_put(val); + return false; + } + + return true; +} + +bool +json_add(json_object *obj, const char *name, const char *value) +{ + return obj_add_field_json(obj, name, json_object_new_string(value)); +} + +bool +json_add(json_object *obj, const char *name, bool value) +{ + return obj_add_field_json(obj, name, json_object_new_boolean(value)); +} + +bool +json_add(json_object *obj, const char *name, const char *value, size_t len) +{ + return obj_add_field_json(obj, name, json_object_new_string_len(value, len)); +} + +bool +obj_add_hex_json(json_object *obj, const char *name, const uint8_t *val, size_t val_len) +{ + if (val_len > 1024 * 1024) { + RNP_LOG("too large json hex field: %zu", val_len); + val_len = 1024 * 1024; + } + + char smallbuf[64] = {0}; + size_t hexlen = val_len * 2 + 1; + + char *hexbuf = hexlen < sizeof(smallbuf) ? smallbuf : (char *) malloc(hexlen); + if (!hexbuf) { + return false; + } + + bool res = rnp::hex_encode(val, val_len, hexbuf, hexlen, rnp::HEX_LOWERCASE) && + obj_add_field_json(obj, name, json_object_new_string(hexbuf)); + + if (hexbuf != smallbuf) { + free(hexbuf); + } + return res; +} + +bool +array_add_element_json(json_object *obj, json_object *val) +{ + if (!val) { + return false; + } + if (json_object_array_add(obj, val)) { + json_object_put(val); + return false; + } + return true; +} diff --git a/comm/third_party/rnp/src/lib/json-utils.h b/comm/third_party/rnp/src/lib/json-utils.h new file mode 100644 index 0000000000..c60615b147 --- /dev/null +++ b/comm/third_party/rnp/src/lib/json-utils.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2019, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef RNP_JSON_UTILS_H_ +#define RNP_JSON_UTILS_H_ + +#include <stdio.h> +#include "types.h" +#include <limits.h> +#include "json_object.h" +#include "json.h" + +/** + * @brief Add field to the json object. + * Note: this function is for convenience, it will check val for NULL and destroy val + * on failure. + * @param obj allocated json_object of object type. + * @param name name of the field + * @param val json object of any type. Will be checked for NULL. + * @return true if val is not NULL and field was added successfully, false otherwise. + */ +bool obj_add_field_json(json_object *obj, const char *name, json_object *val); + +/** + * @brief Shortcut to add string via obj_add_field_json(). + */ +bool json_add(json_object *obj, const char *name, const char *value); + +/** + * @brief Shortcut to add string with length via obj_add_field_json(). + */ +bool json_add(json_object *obj, const char *name, const char *value, size_t len); + +/** + * @brief Shortcut to add bool via obj_add_field_json(). + */ +bool json_add(json_object *obj, const char *name, bool value); + +/** + * @brief Add hex representation of binary data as string field to JSON object. + * Note: this function follows conventions of obj_add_field_json(). + */ +bool obj_add_hex_json(json_object *obj, const char *name, const uint8_t *val, size_t val_len); + +/** + * @brief Add element to JSON array. + * Note: this function follows convention of the obj_add_field_json. + */ +bool array_add_element_json(json_object *obj, json_object *val); + +namespace rnp { +class JSONObject { + json_object *obj_; + + public: + JSONObject(json_object *obj) : obj_(obj) + { + } + + ~JSONObject() + { + if (obj_) { + json_object_put(obj_); + } + } +}; +} // namespace rnp + +#endif diff --git a/comm/third_party/rnp/src/lib/key-provider.cpp b/comm/third_party/rnp/src/lib/key-provider.cpp new file mode 100644 index 0000000000..2a64b63329 --- /dev/null +++ b/comm/third_party/rnp/src/lib/key-provider.cpp @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2017, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <assert.h> +#include <string.h> +#include "key-provider.h" +#include "pgp-key.h" +#include "fingerprint.h" +#include "types.h" +#include "utils.h" +#include <rekey/rnp_key_store.h> + +bool +rnp_key_matches_search(const pgp_key_t *key, const pgp_key_search_t *search) +{ + if (!key) { + return false; + } + switch (search->type) { + case PGP_KEY_SEARCH_KEYID: + return (key->keyid() == search->by.keyid) || (search->by.keyid == pgp_key_id_t({})); + case PGP_KEY_SEARCH_FINGERPRINT: + return key->fp() == search->by.fingerprint; + case PGP_KEY_SEARCH_GRIP: + return key->grip() == search->by.grip; + case PGP_KEY_SEARCH_USERID: + if (key->has_uid(search->by.userid)) { + return true; + } + break; + default: + assert(false); + break; + } + return false; +} + +pgp_key_t * +pgp_request_key(const pgp_key_provider_t *provider, const pgp_key_request_ctx_t *ctx) +{ + pgp_key_t *key = NULL; + if (!provider || !provider->callback || !ctx) { + return NULL; + } + if (!(key = provider->callback(ctx, provider->userdata))) { + return NULL; + } + // confirm that the key actually matches the search criteria + if (!rnp_key_matches_search(key, &ctx->search) && key->is_secret() == ctx->secret) { + return NULL; + } + return key; +} + +pgp_key_t * +rnp_key_provider_key_ptr_list(const pgp_key_request_ctx_t *ctx, void *userdata) +{ + std::vector<pgp_key_t *> *key_list = (std::vector<pgp_key_t *> *) userdata; + for (auto key : *key_list) { + if (rnp_key_matches_search(key, &ctx->search) && (key->is_secret() == ctx->secret)) { + return key; + } + } + return NULL; +} + +pgp_key_t * +rnp_key_provider_chained(const pgp_key_request_ctx_t *ctx, void *userdata) +{ + for (pgp_key_provider_t **pprovider = (pgp_key_provider_t **) userdata; + pprovider && *pprovider; + pprovider++) { + pgp_key_provider_t *provider = *pprovider; + pgp_key_t * key = NULL; + if ((key = provider->callback(ctx, provider->userdata))) { + return key; + } + } + return NULL; +} + +pgp_key_t * +rnp_key_provider_store(const pgp_key_request_ctx_t *ctx, void *userdata) +{ + rnp_key_store_t *ks = (rnp_key_store_t *) userdata; + + for (pgp_key_t *key = rnp_key_store_search(ks, &ctx->search, NULL); key; + key = rnp_key_store_search(ks, &ctx->search, key)) { + if (key->is_secret() == ctx->secret) { + return key; + } + } + return NULL; +} diff --git a/comm/third_party/rnp/src/lib/key-provider.h b/comm/third_party/rnp/src/lib/key-provider.h new file mode 100644 index 0000000000..4d09e2f825 --- /dev/null +++ b/comm/third_party/rnp/src/lib/key-provider.h @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2017, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef RNP_KEY_PROVIDER_H +#define RNP_KEY_PROVIDER_H + +#include "types.h" +#include "fingerprint.h" + +typedef struct pgp_key_t pgp_key_t; + +typedef enum { + PGP_KEY_SEARCH_UNKNOWN, + PGP_KEY_SEARCH_KEYID, + PGP_KEY_SEARCH_FINGERPRINT, + PGP_KEY_SEARCH_GRIP, + PGP_KEY_SEARCH_USERID +} pgp_key_search_type_t; + +typedef struct pgp_key_search_t { + pgp_key_search_type_t type; + union { + pgp_key_id_t keyid; + pgp_key_grip_t grip; + pgp_fingerprint_t fingerprint; + char userid[MAX_ID_LENGTH + 1]; + } by; + + pgp_key_search_t(pgp_key_search_type_t atype = PGP_KEY_SEARCH_UNKNOWN) : type(atype){}; +} pgp_key_search_t; + +typedef struct pgp_key_request_ctx_t { + pgp_op_t op; + bool secret; + pgp_key_search_t search; + + pgp_key_request_ctx_t(pgp_op_t anop = PGP_OP_UNKNOWN, + bool sec = false, + pgp_key_search_type_t tp = PGP_KEY_SEARCH_UNKNOWN) + : op(anop), secret(sec) + { + search.type = tp; + } +} pgp_key_request_ctx_t; + +typedef pgp_key_t *pgp_key_callback_t(const pgp_key_request_ctx_t *ctx, void *userdata); + +typedef struct pgp_key_provider_t { + pgp_key_callback_t *callback; + void * userdata; + + pgp_key_provider_t(pgp_key_callback_t *cb = NULL, void *ud = NULL) + : callback(cb), userdata(ud){}; +} pgp_key_provider_t; + +/** checks if a key matches search criteria + * + * Note that this does not do any check on the type of key (public/secret), + * that is left up to the caller. + * + * @param key the key to check + * @param search the search criteria to check against + * @return true if the key satisfies the search criteria, false otherwise + **/ +bool rnp_key_matches_search(const pgp_key_t *key, const pgp_key_search_t *search); + +/** @brief request public or secret pgp key, according to information stored in ctx + * @param ctx information about the request - which operation requested the key, which search + * criteria should be used and whether secret or public key is needed + * @param key pointer to the key structure will be stored here on success + * @return a key pointer on success, or NULL if key was not found otherwise + **/ +pgp_key_t *pgp_request_key(const pgp_key_provider_t * provider, + const pgp_key_request_ctx_t *ctx); + +/** key provider callback that searches a list of pgp_key_t pointers + * + * @param ctx + * @param userdata must be a list of key pgp_key_t** + */ +pgp_key_t *rnp_key_provider_key_ptr_list(const pgp_key_request_ctx_t *ctx, void *userdata); + +/** key provider callback that searches a given store + * + * @param ctx + * @param userdata must be a pointer to rnp_key_store_t + */ +pgp_key_t *rnp_key_provider_store(const pgp_key_request_ctx_t *ctx, void *userdata); + +/** key provider that calls other key providers + * + * @param ctx + * @param userdata must be an array pgp_key_provider_t pointers, + * ending with a NULL. + */ +pgp_key_t *rnp_key_provider_chained(const pgp_key_request_ctx_t *ctx, void *userdata); + +#endif diff --git a/comm/third_party/rnp/src/lib/librnp.3.adoc b/comm/third_party/rnp/src/lib/librnp.3.adoc new file mode 100644 index 0000000000..9af84ab9f9 --- /dev/null +++ b/comm/third_party/rnp/src/lib/librnp.3.adoc @@ -0,0 +1,89 @@ += librnp(3) +RNP +:doctype: manpage +:release-version: {component-version} +:man manual: RNP Manual +:man source: RNP {release-version} + +== NAME + +librnp - OpenPGP implementation, available via FFI interface. + +== SYNOPSIS + +*#include <rnp/rnp.h>* + +*#include <rnp/rnp_err.h>* + + +== DESCRIPTION + +*librnp* is part of the *RNP* suite and forms the basis for the _rnp(1)_ and _rnpkeys(1)_ command-line utilities. + +It provides an FFI interface to functions required for operations needed by the OpenPGP protocol. + +Interface to the library is exposed via _<rnp/rnp.h>_ and _<rnp/rnp_err.h>_ headers. +You will also need to link to _librnp_. + +Please see its headers for the full function list and detailed documentation. + +== EXAMPLES + +A number of examples are provided in *src/examples* folder of the *RNP* suite source tree. + +*generate.c*:: +Demonstrates generation of an OpenPGP keypair using the JSON key description mechanism. +May be used to generate any custom key types that are supported by the *RNP* suite. + +*encrypt.c*:: +Demonstrates how to build OpenPGP-encrypted messages. +A message is encrypted with keys, generated via *./generate*, with a hardcoded password. + +*decrypt.c*:: +Demonstrates how to decrypt OpenPGP messages. +Running this example requires the *./encrypt* example to be first run +in order to produce the sample encrypted message for decryption. + +*sign.c*:: +Demonstrates how to sign OpenPGP messages. +Running this example requires the *./generate* example to be first run +in order to generate and write out secret keys. + +*verify.c*:: +Demonstrates verify OpenPGP signed messages. +Again, running this example requires the *./sign* example to be first run +in order to generate a signed OpenPGP message. + +== BUGS + +Please report _issues_ via the RNP public issue tracker at: +https://github.com/rnpgp/rnp/issues. + +_Security reports_ or _security-sensitive feedback_ should be reported +according to the instructions at: +https://www.rnpgp.org/feedback. + + +== AUTHORS + +*RNP* is an open source project led by Ribose and has +received contributions from numerous individuals and +organizations. + + +== RESOURCES + +*Web site*: https://www.rnpgp.org + +*Source repository*: https://github.com/rnpgp/rnp + + +== COPYING + +Copyright \(C) 2017-2021 Ribose. +The RNP software suite is _freely licensed_: +please refer to the *LICENSE* file for details. + + +== SEE ALSO + +*rnp(1)*, *rnpkeys(1)* diff --git a/comm/third_party/rnp/src/lib/librnp.symbols b/comm/third_party/rnp/src/lib/librnp.symbols new file mode 100644 index 0000000000..d8667cea57 --- /dev/null +++ b/comm/third_party/rnp/src/lib/librnp.symbols @@ -0,0 +1 @@ +_rnp_* diff --git a/comm/third_party/rnp/src/lib/librnp.vsc b/comm/third_party/rnp/src/lib/librnp.vsc new file mode 100644 index 0000000000..460db98583 --- /dev/null +++ b/comm/third_party/rnp/src/lib/librnp.vsc @@ -0,0 +1,4 @@ +{ + global: rnp_*; + local: *; +}; diff --git a/comm/third_party/rnp/src/lib/logging.cpp b/comm/third_party/rnp/src/lib/logging.cpp new file mode 100644 index 0000000000..74c67e3a41 --- /dev/null +++ b/comm/third_party/rnp/src/lib/logging.cpp @@ -0,0 +1,75 @@ +/*- + * Copyright (c) 2017-2021 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "string.h" +#include "logging.h" + +/* -1 -- not initialized + 0 -- logging is off + 1 -- logging is on +*/ +static int8_t _rnp_log_switch = +#ifdef NDEBUG + -1 // lazy-initialize later +#else + 1 // always on in debug build +#endif + ; + +/* Temporary disable logging */ +static size_t _rnp_log_disable = 0; + +void +set_rnp_log_switch(int8_t value) +{ + _rnp_log_switch = value; +} + +bool +rnp_log_switch() +{ + if (_rnp_log_switch < 0) { + const char *var = getenv(RNP_LOG_CONSOLE); + _rnp_log_switch = (var && strcmp(var, "0")) ? 1 : 0; + } + return !_rnp_log_disable && !!_rnp_log_switch; +} + +void +rnp_log_stop() +{ + if (_rnp_log_disable < SIZE_MAX) { + _rnp_log_disable++; + } +} + +void +rnp_log_continue() +{ + if (_rnp_log_disable) { + _rnp_log_disable--; + } +} diff --git a/comm/third_party/rnp/src/lib/logging.h b/comm/third_party/rnp/src/lib/logging.h new file mode 100644 index 0000000000..7335e57483 --- /dev/null +++ b/comm/third_party/rnp/src/lib/logging.h @@ -0,0 +1,97 @@ +/*- + * Copyright (c) 2017-2021 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RNP_LOGGING_H_ +#define RNP_LOGGING_H_ + +#include <stdlib.h> +#include <stdint.h> + +/* environment variable name */ +static const char RNP_LOG_CONSOLE[] = "RNP_LOG_CONSOLE"; + +bool rnp_log_switch(); +void set_rnp_log_switch(int8_t); +void rnp_log_stop(); +void rnp_log_continue(); + +namespace rnp { +class LogStop { + bool stop_; + + public: + LogStop(bool stop = true) : stop_(stop) + { + if (stop_) { + rnp_log_stop(); + } + } + ~LogStop() + { + if (stop_) { + rnp_log_continue(); + } + } +}; +} // namespace rnp + +#define RNP_LOG_FD(fd, ...) \ + do { \ + if (!rnp_log_switch()) \ + break; \ + (void) fprintf((fd), "[%s() %s:%d] ", __func__, __FILE__, __LINE__); \ + (void) fprintf((fd), __VA_ARGS__); \ + (void) fprintf((fd), "\n"); \ + } while (0) + +#define RNP_LOG(...) RNP_LOG_FD(stderr, __VA_ARGS__) + +#define RNP_LOG_KEY(msg, key) \ + do { \ + if (!(key)) { \ + RNP_LOG(msg, "(null)"); \ + break; \ + } \ + char keyid[PGP_KEY_ID_SIZE * 2 + 1] = {0}; \ + const pgp_key_id_t &id = key->keyid(); \ + rnp::hex_encode(id.data(), id.size(), keyid, sizeof(keyid), rnp::HEX_LOWERCASE); \ + RNP_LOG(msg, keyid); \ + } while (0) + +#define RNP_LOG_KEY_PKT(msg, key) \ + do { \ + pgp_key_id_t keyid = {}; \ + if (pgp_keyid(keyid, (key))) { \ + RNP_LOG(msg, "unknown"); \ + break; \ + }; \ + char keyidhex[PGP_KEY_ID_SIZE * 2 + 1] = {0}; \ + rnp::hex_encode( \ + keyid.data(), keyid.size(), keyidhex, sizeof(keyidhex), rnp::HEX_LOWERCASE); \ + RNP_LOG(msg, keyidhex); \ + } while (0) + +#endif
\ No newline at end of file diff --git a/comm/third_party/rnp/src/lib/pass-provider.cpp b/comm/third_party/rnp/src/lib/pass-provider.cpp new file mode 100644 index 0000000000..788fc23eac --- /dev/null +++ b/comm/third_party/rnp/src/lib/pass-provider.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2017 - 2019, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "pass-provider.h" +#include <stdio.h> +#include <string.h> + +bool +rnp_password_provider_string(const pgp_password_ctx_t *ctx, + char * password, + size_t password_size, + void * userdata) +{ + char *passc = (char *) userdata; + + if (!passc || strlen(passc) >= (password_size - 1)) { + return false; + } + + strncpy(password, passc, password_size - 1); + return true; +} + +bool +pgp_request_password(const pgp_password_provider_t *provider, + const pgp_password_ctx_t * ctx, + char * password, + size_t password_size) +{ + if (!provider || !provider->callback || !ctx || !password || !password_size) { + return false; + } + return provider->callback(ctx, password, password_size, provider->userdata); +} diff --git a/comm/third_party/rnp/src/lib/pass-provider.h b/comm/third_party/rnp/src/lib/pass-provider.h new file mode 100644 index 0000000000..fd79fc5719 --- /dev/null +++ b/comm/third_party/rnp/src/lib/pass-provider.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2017 - 2019, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef RNP_PASS_PROVIDER_H +#define RNP_PASS_PROVIDER_H + +#include <cstddef> +#include <cstdint> + +typedef struct pgp_key_t pgp_key_t; + +typedef struct pgp_password_ctx_t { + uint8_t op; + const pgp_key_t *key; + + pgp_password_ctx_t(uint8_t anop, const pgp_key_t *akey = NULL) : op(anop), key(akey){}; +} pgp_password_ctx_t; + +typedef bool pgp_password_callback_t(const pgp_password_ctx_t *ctx, + char * password, + size_t password_size, + void * userdata); + +typedef struct pgp_password_provider_t { + pgp_password_callback_t *callback; + void * userdata; + pgp_password_provider_t(pgp_password_callback_t *cb = NULL, void *ud = NULL) + : callback(cb), userdata(ud){}; +} pgp_password_provider_t; + +bool pgp_request_password(const pgp_password_provider_t *provider, + const pgp_password_ctx_t * ctx, + char * password, + size_t password_size); +bool rnp_password_provider_string(const pgp_password_ctx_t *ctx, + char * password, + size_t password_size, + void * userdata); +#endif diff --git a/comm/third_party/rnp/src/lib/pgp-key.cpp b/comm/third_party/rnp/src/lib/pgp-key.cpp new file mode 100644 index 0000000000..430033158b --- /dev/null +++ b/comm/third_party/rnp/src/lib/pgp-key.cpp @@ -0,0 +1,2776 @@ +/* + * Copyright (c) 2017-2022 [Ribose Inc](https://www.ribose.com). + * Copyright (c) 2009 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is originally derived from software contributed to + * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and + * carried further by Ribose Inc (https://www.ribose.com). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/* + * Copyright (c) 2005-2008 Nominet UK (www.nic.uk) + * All rights reserved. + * Contributors: Ben Laurie, Rachel Willmer. The Contributors have asserted + * their moral rights under the UK Copyright Design and Patents Act 1988 to + * be recorded as the authors of this copyright work. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. + * + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "pgp-key.h" +#include "utils.h" +#include <librekey/key_store_pgp.h> +#include <librekey/key_store_g10.h> +#include "crypto.h" +#include "crypto/s2k.h" +#include "crypto/mem.h" +#include "crypto/signatures.h" +#include "fingerprint.h" + +#include <librepgp/stream-packet.h> +#include <librepgp/stream-key.h> +#include <librepgp/stream-sig.h> +#include <librepgp/stream-armor.h> + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <assert.h> +#include <time.h> +#include <algorithm> +#include <stdexcept> +#include "defaults.h" + +pgp_key_pkt_t * +pgp_decrypt_seckey_pgp(const pgp_rawpacket_t &raw, + const pgp_key_pkt_t & pubkey, + const char * password) +{ + try { + rnp::MemorySource src(raw.raw.data(), raw.raw.size(), false); + auto res = std::unique_ptr<pgp_key_pkt_t>(new pgp_key_pkt_t()); + if (res->parse(src.src()) || decrypt_secret_key(res.get(), password)) { + return NULL; + } + return res.release(); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return NULL; + } +} + +/* Note that this function essentially serves two purposes. + * - In the case of a protected key, it requests a password and + * uses it to decrypt the key and fill in key->key.seckey. + * - In the case of an unprotected key, it simply re-loads + * key->key.seckey by parsing the key data in packets[0]. + */ +pgp_key_pkt_t * +pgp_decrypt_seckey(const pgp_key_t & key, + const pgp_password_provider_t &provider, + const pgp_password_ctx_t & ctx) +{ + // sanity checks + if (!key.is_secret()) { + RNP_LOG("invalid args"); + return NULL; + } + // ask the provider for a password + rnp::secure_array<char, MAX_PASSWORD_LENGTH> password; + if (key.is_protected() && + !pgp_request_password(&provider, &ctx, password.data(), password.size())) { + return NULL; + } + // attempt to decrypt with the provided password + switch (key.format) { + case PGP_KEY_STORE_GPG: + case PGP_KEY_STORE_KBX: + return pgp_decrypt_seckey_pgp(key.rawpkt(), key.pkt(), password.data()); + case PGP_KEY_STORE_G10: + return g10_decrypt_seckey(key.rawpkt(), key.pkt(), password.data()); + default: + RNP_LOG("unexpected format: %d", key.format); + return NULL; + } +} + +pgp_key_t * +pgp_sig_get_signer(const pgp_subsig_t &sig, rnp_key_store_t *keyring, pgp_key_provider_t *prov) +{ + pgp_key_request_ctx_t ctx(PGP_OP_VERIFY, false, PGP_KEY_SEARCH_UNKNOWN); + /* if we have fingerprint let's check it */ + if (sig.sig.has_keyfp()) { + ctx.search.by.fingerprint = sig.sig.keyfp(); + ctx.search.type = PGP_KEY_SEARCH_FINGERPRINT; + } else if (sig.sig.has_keyid()) { + ctx.search.by.keyid = sig.sig.keyid(); + ctx.search.type = PGP_KEY_SEARCH_KEYID; + } else { + RNP_LOG("No way to search for the signer."); + return NULL; + } + + pgp_key_t *key = rnp_key_store_search(keyring, &ctx.search, NULL); + if (key || !prov) { + return key; + } + return pgp_request_key(prov, &ctx); +} + +static const id_str_pair ss_rr_code_map[] = { + {PGP_REVOCATION_NO_REASON, "No reason specified"}, + {PGP_REVOCATION_SUPERSEDED, "Key is superseded"}, + {PGP_REVOCATION_COMPROMISED, "Key material has been compromised"}, + {PGP_REVOCATION_RETIRED, "Key is retired and no longer used"}, + {PGP_REVOCATION_NO_LONGER_VALID, "User ID information is no longer valid"}, + {0x00, NULL}, +}; + +pgp_key_t * +pgp_key_get_subkey(const pgp_key_t *key, rnp_key_store_t *store, size_t idx) +{ + try { + return rnp_key_store_get_key_by_fpr(store, key->get_subkey_fp(idx)); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return NULL; + } +} + +pgp_key_flags_t +pgp_pk_alg_capabilities(pgp_pubkey_alg_t alg) +{ + switch (alg) { + case PGP_PKA_RSA: + return pgp_key_flags_t(PGP_KF_SIGN | PGP_KF_CERTIFY | PGP_KF_AUTH | PGP_KF_ENCRYPT); + + case PGP_PKA_RSA_SIGN_ONLY: + // deprecated, but still usable + return PGP_KF_SIGN; + + case PGP_PKA_RSA_ENCRYPT_ONLY: + // deprecated, but still usable + return PGP_KF_ENCRYPT; + + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: /* deprecated */ + // These are no longer permitted per the RFC + return PGP_KF_NONE; + + case PGP_PKA_DSA: + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: + return pgp_key_flags_t(PGP_KF_SIGN | PGP_KF_CERTIFY | PGP_KF_AUTH); + + case PGP_PKA_SM2: + return pgp_key_flags_t(PGP_KF_SIGN | PGP_KF_CERTIFY | PGP_KF_AUTH | PGP_KF_ENCRYPT); + + case PGP_PKA_ECDH: + case PGP_PKA_ELGAMAL: + return PGP_KF_ENCRYPT; + + default: + RNP_LOG("unknown pk alg: %d\n", alg); + return PGP_KF_NONE; + } +} + +bool +pgp_key_t::write_sec_pgp(pgp_dest_t & dst, + pgp_key_pkt_t & seckey, + const std::string &password, + rnp::RNG & rng) +{ + bool res = false; + pgp_pkt_type_t oldtag = seckey.tag; + + seckey.tag = type(); + if (encrypt_secret_key(&seckey, password.c_str(), rng)) { + goto done; + } + try { + seckey.write(dst); + res = !dst.werr; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + } +done: + seckey.tag = oldtag; + return res; +} + +bool +pgp_key_t::write_sec_rawpkt(pgp_key_pkt_t & seckey, + const std::string & password, + rnp::SecurityContext &ctx) +{ + // encrypt+write the key in the appropriate format + try { + rnp::MemoryDest memdst; + switch (format) { + case PGP_KEY_STORE_GPG: + case PGP_KEY_STORE_KBX: + if (!write_sec_pgp(memdst.dst(), seckey, password, ctx.rng)) { + RNP_LOG("failed to write secret key"); + return false; + } + break; + case PGP_KEY_STORE_G10: + if (!g10_write_seckey(&memdst.dst(), &seckey, password.c_str(), ctx)) { + RNP_LOG("failed to write g10 secret key"); + return false; + } + break; + default: + RNP_LOG("invalid format"); + return false; + } + + rawpkt_ = pgp_rawpacket_t((uint8_t *) memdst.memory(), memdst.writeb(), type()); + return true; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } +} + +static bool +update_sig_expiration(pgp_signature_t * dst, + const pgp_signature_t *src, + uint64_t create, + uint32_t expiry) +{ + try { + *dst = *src; + if (!expiry) { + dst->remove_subpkt(dst->get_subpkt(PGP_SIG_SUBPKT_KEY_EXPIRY)); + } else { + dst->set_key_expiration(expiry); + } + dst->set_creation(create); + return true; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } +} + +bool +pgp_key_set_expiration(pgp_key_t * key, + pgp_key_t * seckey, + uint32_t expiry, + const pgp_password_provider_t &prov, + rnp::SecurityContext & ctx) +{ + if (!key->is_primary()) { + RNP_LOG("Not a primary key"); + return false; + } + + std::vector<pgp_sig_id_t> sigs; + /* update expiration for the latest direct-key signature and self-signature for each userid + */ + pgp_subsig_t *sig = key->latest_selfsig(PGP_UID_NONE); + if (sig) { + sigs.push_back(sig->sigid); + } + for (size_t uid = 0; uid < key->uid_count(); uid++) { + sig = key->latest_selfsig(uid); + if (sig) { + sigs.push_back(sig->sigid); + } + } + if (sigs.empty()) { + RNP_LOG("No valid self-signature(s)"); + return false; + } + + rnp::KeyLocker seclock(*seckey); + for (const auto &sigid : sigs) { + pgp_subsig_t &sig = key->get_sig(sigid); + /* update signature and re-sign it */ + if (!expiry && !sig.sig.has_subpkt(PGP_SIG_SUBPKT_KEY_EXPIRY)) { + continue; + } + + /* unlock secret key if needed */ + if (seckey->is_locked() && !seckey->unlock(prov)) { + RNP_LOG("Failed to unlock secret key"); + return false; + } + + pgp_signature_t newsig; + pgp_sig_id_t oldsigid = sigid; + if (!update_sig_expiration(&newsig, &sig.sig, ctx.time(), expiry)) { + return false; + } + try { + if (sig.is_cert()) { + if (sig.uid >= key->uid_count()) { + RNP_LOG("uid not found"); + return false; + } + seckey->sign_cert(key->pkt(), key->get_uid(sig.uid).pkt, newsig, ctx); + } else { + /* direct-key signature case */ + seckey->sign_direct(key->pkt(), newsig, ctx); + } + /* replace signature, first for secret key since it may be replaced in public */ + if (seckey->has_sig(oldsigid)) { + seckey->replace_sig(oldsigid, newsig); + } + if (key != seckey) { + key->replace_sig(oldsigid, newsig); + } + } catch (const std::exception &e) { + RNP_LOG("failed to calculate or add signature: %s", e.what()); + return false; + } + } + + if (!seckey->refresh_data(ctx)) { + RNP_LOG("Failed to refresh seckey data."); + return false; + } + if ((key != seckey) && !key->refresh_data(ctx)) { + RNP_LOG("Failed to refresh key data."); + return false; + } + return true; +} + +bool +pgp_subkey_set_expiration(pgp_key_t * sub, + pgp_key_t * primsec, + pgp_key_t * secsub, + uint32_t expiry, + const pgp_password_provider_t &prov, + rnp::SecurityContext & ctx) +{ + if (!sub->is_subkey()) { + RNP_LOG("Not a subkey"); + return false; + } + + /* find the latest valid subkey binding */ + pgp_subsig_t *subsig = sub->latest_binding(); + if (!subsig) { + RNP_LOG("No valid subkey binding"); + return false; + } + if (!expiry && !subsig->sig.has_subpkt(PGP_SIG_SUBPKT_KEY_EXPIRY)) { + return true; + } + + rnp::KeyLocker primlock(*primsec); + if (primsec->is_locked() && !primsec->unlock(prov)) { + RNP_LOG("Failed to unlock primary key"); + return false; + } + bool subsign = secsub->can_sign(); + rnp::KeyLocker sublock(*secsub); + if (subsign && secsub->is_locked() && !secsub->unlock(prov)) { + RNP_LOG("Failed to unlock subkey"); + return false; + } + + try { + /* update signature and re-sign */ + pgp_signature_t newsig; + pgp_sig_id_t oldsigid = subsig->sigid; + if (!update_sig_expiration(&newsig, &subsig->sig, ctx.time(), expiry)) { + return false; + } + primsec->sign_subkey_binding(*secsub, newsig, ctx); + /* replace signature, first for the secret key since it may be replaced in public */ + if (secsub->has_sig(oldsigid)) { + secsub->replace_sig(oldsigid, newsig); + if (!secsub->refresh_data(primsec, ctx)) { + return false; + } + } + if (sub == secsub) { + return true; + } + sub->replace_sig(oldsigid, newsig); + return sub->refresh_data(primsec, ctx); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } +} + +pgp_key_t * +find_suitable_key(pgp_op_t op, + pgp_key_t * key, + pgp_key_provider_t *key_provider, + bool no_primary) +{ + if (!key) { + return NULL; + } + bool secret = false; + switch (op) { + case PGP_OP_ENCRYPT: + break; + case PGP_OP_SIGN: + case PGP_OP_CERTIFY: + secret = true; + break; + default: + RNP_LOG("Unsupported operation: %d", (int) op); + return NULL; + } + /* Return if specified primary key fits our needs */ + if (!no_primary && key->usable_for(op)) { + return key; + } + /* Check for the case when we need to look up for a secret key */ + pgp_key_request_ctx_t ctx(op, secret, PGP_KEY_SEARCH_FINGERPRINT); + if (!no_primary && secret && key->is_public() && key->usable_for(op, true)) { + ctx.search.by.fingerprint = key->fp(); + pgp_key_t *sec = pgp_request_key(key_provider, &ctx); + if (sec && sec->usable_for(op)) { + return sec; + } + } + /* Now look up for subkeys */ + pgp_key_t *subkey = NULL; + for (auto &fp : key->subkey_fps()) { + ctx.search.by.fingerprint = fp; + pgp_key_t *cur = pgp_request_key(key_provider, &ctx); + if (!cur || !cur->usable_for(op)) { + continue; + } + if (!subkey || (cur->creation() > subkey->creation())) { + subkey = cur; + } + } + return subkey; +} + +pgp_hash_alg_t +pgp_hash_adjust_alg_to_key(pgp_hash_alg_t hash, const pgp_key_pkt_t *pubkey) +{ + if ((pubkey->alg != PGP_PKA_DSA) && (pubkey->alg != PGP_PKA_ECDSA)) { + return hash; + } + + pgp_hash_alg_t hash_min; + if (pubkey->alg == PGP_PKA_ECDSA) { + hash_min = ecdsa_get_min_hash(pubkey->material.ec.curve); + } else { + hash_min = dsa_get_min_hash(mpi_bits(&pubkey->material.dsa.q)); + } + + if (rnp::Hash::size(hash) < rnp::Hash::size(hash_min)) { + return hash_min; + } + return hash; +} + +static void +bytevec_append_uniq(std::vector<uint8_t> &vec, uint8_t val) +{ + if (std::find(vec.begin(), vec.end(), val) == vec.end()) { + vec.push_back(val); + } +} + +void +pgp_user_prefs_t::set_symm_algs(const std::vector<uint8_t> &algs) +{ + symm_algs = algs; +} + +void +pgp_user_prefs_t::add_symm_alg(pgp_symm_alg_t alg) +{ + bytevec_append_uniq(symm_algs, alg); +} + +void +pgp_user_prefs_t::set_hash_algs(const std::vector<uint8_t> &algs) +{ + hash_algs = algs; +} + +void +pgp_user_prefs_t::add_hash_alg(pgp_hash_alg_t alg) +{ + bytevec_append_uniq(hash_algs, alg); +} + +void +pgp_user_prefs_t::set_z_algs(const std::vector<uint8_t> &algs) +{ + z_algs = algs; +} + +void +pgp_user_prefs_t::add_z_alg(pgp_compression_type_t alg) +{ + bytevec_append_uniq(z_algs, alg); +} + +void +pgp_user_prefs_t::set_ks_prefs(const std::vector<uint8_t> &prefs) +{ + ks_prefs = prefs; +} + +void +pgp_user_prefs_t::add_ks_pref(pgp_key_server_prefs_t pref) +{ + bytevec_append_uniq(ks_prefs, pref); +} + +pgp_rawpacket_t::pgp_rawpacket_t(const pgp_signature_t &sig) +{ + rnp::MemoryDest dst; + sig.write(dst.dst()); + raw = dst.to_vector(); + tag = PGP_PKT_SIGNATURE; +} + +pgp_rawpacket_t::pgp_rawpacket_t(pgp_key_pkt_t &key) +{ + rnp::MemoryDest dst; + key.write(dst.dst()); + raw = dst.to_vector(); + tag = key.tag; +} + +pgp_rawpacket_t::pgp_rawpacket_t(const pgp_userid_pkt_t &uid) +{ + rnp::MemoryDest dst; + uid.write(dst.dst()); + raw = dst.to_vector(); + tag = uid.tag; +} + +void +pgp_rawpacket_t::write(pgp_dest_t &dst) const +{ + dst_write(&dst, raw.data(), raw.size()); +} + +void +pgp_validity_t::mark_valid() +{ + validated = true; + valid = true; + expired = false; +} + +void +pgp_validity_t::reset() +{ + validated = false; + valid = false; + expired = false; +} + +pgp_subsig_t::pgp_subsig_t(const pgp_signature_t &pkt) +{ + sig = pkt; + sigid = sig.get_id(); + if (sig.has_subpkt(PGP_SIG_SUBPKT_TRUST)) { + trustlevel = sig.trust_level(); + trustamount = sig.trust_amount(); + } + prefs.set_symm_algs(sig.preferred_symm_algs()); + prefs.set_hash_algs(sig.preferred_hash_algs()); + prefs.set_z_algs(sig.preferred_z_algs()); + + if (sig.has_subpkt(PGP_SIG_SUBPKT_KEY_FLAGS)) { + key_flags = sig.key_flags(); + } + if (sig.has_subpkt(PGP_SIG_SUBPKT_KEYSERV_PREFS)) { + prefs.set_ks_prefs({sig.key_server_prefs()}); + } + if (sig.has_subpkt(PGP_SIG_SUBPKT_PREF_KEYSERV)) { + prefs.key_server = sig.key_server(); + } + /* add signature rawpacket */ + rawpkt = pgp_rawpacket_t(sig); +} + +bool +pgp_subsig_t::valid() const +{ + return validity.validated && validity.valid && !validity.expired; +} + +bool +pgp_subsig_t::validated() const +{ + return validity.validated; +} + +bool +pgp_subsig_t::is_cert() const +{ + pgp_sig_type_t type = sig.type(); + return (type == PGP_CERT_CASUAL) || (type == PGP_CERT_GENERIC) || + (type == PGP_CERT_PERSONA) || (type == PGP_CERT_POSITIVE); +} + +bool +pgp_subsig_t::expired(uint64_t at) const +{ + /* sig expiration: absence of subpkt or 0 means it never expires */ + uint64_t expiration = sig.expiration(); + if (!expiration) { + return false; + } + return expiration + sig.creation() < at; +} + +pgp_userid_t::pgp_userid_t(const pgp_userid_pkt_t &uidpkt) +{ + /* copy packet data */ + pkt = uidpkt; + rawpkt = pgp_rawpacket_t(uidpkt); + /* populate uid string */ + if (uidpkt.tag == PGP_PKT_USER_ID) { + str = std::string(uidpkt.uid, uidpkt.uid + uidpkt.uid_len); + } else { + str = "(photo)"; + } +} + +size_t +pgp_userid_t::sig_count() const +{ + return sigs_.size(); +} + +const pgp_sig_id_t & +pgp_userid_t::get_sig(size_t idx) const +{ + if (idx >= sigs_.size()) { + throw std::out_of_range("idx"); + } + return sigs_[idx]; +} + +bool +pgp_userid_t::has_sig(const pgp_sig_id_t &id) const +{ + return std::find(sigs_.begin(), sigs_.end(), id) != sigs_.end(); +} + +void +pgp_userid_t::add_sig(const pgp_sig_id_t &sig) +{ + sigs_.push_back(sig); +} + +void +pgp_userid_t::replace_sig(const pgp_sig_id_t &id, const pgp_sig_id_t &newsig) +{ + auto it = std::find(sigs_.begin(), sigs_.end(), id); + if (it == sigs_.end()) { + throw std::invalid_argument("id"); + } + *it = newsig; +} + +bool +pgp_userid_t::del_sig(const pgp_sig_id_t &id) +{ + auto it = std::find(sigs_.begin(), sigs_.end(), id); + if (it == sigs_.end()) { + return false; + } + sigs_.erase(it); + return true; +} + +void +pgp_userid_t::clear_sigs() +{ + sigs_.clear(); +} + +pgp_revoke_t::pgp_revoke_t(pgp_subsig_t &sig) +{ + uid = sig.uid; + sigid = sig.sigid; + if (!sig.sig.has_subpkt(PGP_SIG_SUBPKT_REVOCATION_REASON)) { + RNP_LOG("Warning: no revocation reason in the revocation"); + code = PGP_REVOCATION_NO_REASON; + } else { + code = sig.sig.revocation_code(); + reason = sig.sig.revocation_reason(); + } + if (reason.empty()) { + reason = id_str_pair::lookup(ss_rr_code_map, code); + } +} + +pgp_key_t::pgp_key_t(const pgp_key_pkt_t &keypkt) : pkt_(keypkt) +{ + if (!is_key_pkt(pkt_.tag) || !pkt_.material.alg) { + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + if (pgp_keyid(keyid_, pkt_) || pgp_fingerprint(fingerprint_, pkt_) || + !rnp_key_store_get_key_grip(&pkt_.material, grip_)) { + throw rnp::rnp_exception(RNP_ERROR_GENERIC); + } + + /* parse secret key if not encrypted */ + if (is_secret_key_pkt(pkt_.tag)) { + bool cleartext = pkt_.sec_protection.s2k.usage == PGP_S2KU_NONE; + if (cleartext && decrypt_secret_key(&pkt_, NULL)) { + RNP_LOG("failed to setup key fields"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + /* decryption resets validity */ + pkt_.material.validity = keypkt.material.validity; + } + /* add rawpacket */ + rawpkt_ = pgp_rawpacket_t(pkt_); + format = PGP_KEY_STORE_GPG; +} + +pgp_key_t::pgp_key_t(const pgp_key_pkt_t &pkt, pgp_key_t &primary) : pgp_key_t(pkt) +{ + primary.link_subkey_fp(*this); +} + +pgp_key_t::pgp_key_t(const pgp_key_t &src, bool pubonly) +{ + /* Do some checks for g10 keys */ + if (src.format == PGP_KEY_STORE_G10) { + if (pubonly) { + RNP_LOG("attempt to copy public part from g10 key"); + throw std::invalid_argument("pubonly"); + } + } + + if (pubonly) { + pkt_ = pgp_key_pkt_t(src.pkt_, true); + rawpkt_ = pgp_rawpacket_t(pkt_); + } else { + pkt_ = src.pkt_; + rawpkt_ = src.rawpkt_; + } + + uids_ = src.uids_; + sigs_ = src.sigs_; + sigs_map_ = src.sigs_map_; + keysigs_ = src.keysigs_; + subkey_fps_ = src.subkey_fps_; + primary_fp_set_ = src.primary_fp_set_; + primary_fp_ = src.primary_fp_; + expiration_ = src.expiration_; + flags_ = src.flags_; + keyid_ = src.keyid_; + fingerprint_ = src.fingerprint_; + grip_ = src.grip_; + uid0_ = src.uid0_; + uid0_set_ = src.uid0_set_; + revoked_ = src.revoked_; + revocation_ = src.revocation_; + format = src.format; + validity_ = src.validity_; + valid_till_ = src.valid_till_; +} + +pgp_key_t::pgp_key_t(const pgp_transferable_key_t &src) : pgp_key_t(src.key) +{ + /* add direct-key signatures */ + for (auto &sig : src.signatures) { + add_sig(sig); + } + + /* add userids and their signatures */ + for (auto &uid : src.userids) { + add_uid(uid); + } +} + +pgp_key_t::pgp_key_t(const pgp_transferable_subkey_t &src, pgp_key_t *primary) + : pgp_key_t(src.subkey) +{ + /* add subkey binding signatures */ + for (auto &sig : src.signatures) { + add_sig(sig); + } + + /* setup key grips if primary is available */ + if (primary) { + primary->link_subkey_fp(*this); + } +} + +size_t +pgp_key_t::sig_count() const +{ + return sigs_.size(); +} + +pgp_subsig_t & +pgp_key_t::get_sig(size_t idx) +{ + if (idx >= sigs_.size()) { + throw std::out_of_range("idx"); + } + return get_sig(sigs_[idx]); +} + +const pgp_subsig_t & +pgp_key_t::get_sig(size_t idx) const +{ + if (idx >= sigs_.size()) { + throw std::out_of_range("idx"); + } + return get_sig(sigs_[idx]); +} + +bool +pgp_key_t::has_sig(const pgp_sig_id_t &id) const +{ + return sigs_map_.count(id); +} + +pgp_subsig_t & +pgp_key_t::get_sig(const pgp_sig_id_t &id) +{ + if (!has_sig(id)) { + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + return sigs_map_.at(id); +} + +const pgp_subsig_t & +pgp_key_t::get_sig(const pgp_sig_id_t &id) const +{ + if (!has_sig(id)) { + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + return sigs_map_.at(id); +} + +pgp_subsig_t & +pgp_key_t::replace_sig(const pgp_sig_id_t &id, const pgp_signature_t &newsig) +{ + /* save oldsig's uid */ + size_t uid = get_sig(id).uid; + /* delete first old sig since we may have theoretically the same sigid */ + pgp_sig_id_t oldid = id; + sigs_map_.erase(oldid); + auto &res = sigs_map_.emplace(std::make_pair(newsig.get_id(), newsig)).first->second; + res.uid = uid; + auto it = std::find(sigs_.begin(), sigs_.end(), oldid); + if (it == sigs_.end()) { + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + *it = res.sigid; + if (uid == PGP_UID_NONE) { + auto it = std::find(keysigs_.begin(), keysigs_.end(), oldid); + if (it == keysigs_.end()) { + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + *it = res.sigid; + } else { + uids_[uid].replace_sig(oldid, res.sigid); + } + return res; +} + +pgp_subsig_t & +pgp_key_t::add_sig(const pgp_signature_t &sig, size_t uid) +{ + const pgp_sig_id_t sigid = sig.get_id(); + sigs_map_.erase(sigid); + pgp_subsig_t &res = sigs_map_.emplace(std::make_pair(sigid, sig)).first->second; + res.uid = uid; + sigs_.push_back(sigid); + if (uid == PGP_UID_NONE) { + keysigs_.push_back(sigid); + } else { + uids_[uid].add_sig(sigid); + } + return res; +} + +bool +pgp_key_t::del_sig(const pgp_sig_id_t &sigid) +{ + if (!has_sig(sigid)) { + return false; + } + uint32_t uid = get_sig(sigid).uid; + if (uid == PGP_UID_NONE) { + /* signature over the key itself */ + auto it = std::find(keysigs_.begin(), keysigs_.end(), sigid); + if (it != keysigs_.end()) { + keysigs_.erase(it); + } + } else if (uid < uids_.size()) { + /* userid-related signature */ + uids_[uid].del_sig(sigid); + } + auto it = std::find(sigs_.begin(), sigs_.end(), sigid); + if (it != sigs_.end()) { + sigs_.erase(it); + } + return sigs_map_.erase(sigid); +} + +size_t +pgp_key_t::del_sigs(const std::vector<pgp_sig_id_t> &sigs) +{ + /* delete actual signatures */ + size_t res = 0; + for (auto &sig : sigs) { + res += sigs_map_.erase(sig); + } + /* rebuild vectors with signatures order */ + keysigs_.clear(); + for (auto &uid : uids_) { + uid.clear_sigs(); + } + std::vector<pgp_sig_id_t> newsigs; + newsigs.reserve(sigs_map_.size()); + for (auto &sigid : sigs_) { + if (!sigs_map_.count(sigid)) { + continue; + } + newsigs.push_back(sigid); + uint32_t uid = get_sig(sigid).uid; + if (uid == PGP_UID_NONE) { + keysigs_.push_back(sigid); + } else { + uids_[uid].add_sig(sigid); + } + } + sigs_ = std::move(newsigs); + return res; +} + +size_t +pgp_key_t::keysig_count() const +{ + return keysigs_.size(); +} + +pgp_subsig_t & +pgp_key_t::get_keysig(size_t idx) +{ + if (idx >= keysigs_.size()) { + throw std::out_of_range("idx"); + } + return get_sig(keysigs_[idx]); +} + +size_t +pgp_key_t::uid_count() const +{ + return uids_.size(); +} + +pgp_userid_t & +pgp_key_t::get_uid(size_t idx) +{ + if (idx >= uids_.size()) { + throw std::out_of_range("idx"); + } + return uids_[idx]; +} + +const pgp_userid_t & +pgp_key_t::get_uid(size_t idx) const +{ + if (idx >= uids_.size()) { + throw std::out_of_range("idx"); + } + return uids_[idx]; +} + +bool +pgp_key_t::has_uid(const std::string &uidstr) const +{ + for (auto &userid : uids_) { + if (!userid.valid) { + continue; + } + if (userid.str == uidstr) { + return true; + } + } + return false; +} + +void +pgp_key_t::del_uid(size_t idx) +{ + if (idx >= uids_.size()) { + throw std::out_of_range("idx"); + } + + std::vector<pgp_sig_id_t> newsigs; + /* copy sigs which do not belong to uid */ + newsigs.reserve(sigs_.size()); + for (auto &id : sigs_) { + if (get_sig(id).uid == idx) { + sigs_map_.erase(id); + continue; + } + newsigs.push_back(id); + } + sigs_ = newsigs; + uids_.erase(uids_.begin() + idx); + /* update uids */ + if (idx == uids_.size()) { + return; + } + for (auto &sig : sigs_map_) { + if ((sig.second.uid == PGP_UID_NONE) || (sig.second.uid <= idx)) { + continue; + } + sig.second.uid--; + } +} + +bool +pgp_key_t::has_primary_uid() const +{ + return uid0_set_; +} + +uint32_t +pgp_key_t::get_primary_uid() const +{ + if (!uid0_set_) { + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + return uid0_; +} + +pgp_userid_t & +pgp_key_t::add_uid(const pgp_transferable_userid_t &uid) +{ + /* construct userid */ + uids_.emplace_back(uid.uid); + /* add certifications */ + for (auto &sig : uid.signatures) { + add_sig(sig, uid_count() - 1); + } + return uids_.back(); +} + +bool +pgp_key_t::revoked() const +{ + return revoked_; +} + +const pgp_revoke_t & +pgp_key_t::revocation() const +{ + if (!revoked_) { + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + return revocation_; +} + +void +pgp_key_t::clear_revokes() +{ + revoked_ = false; + revocation_ = {}; + for (auto &uid : uids_) { + uid.revoked = false; + uid.revocation = {}; + } +} + +const pgp_key_pkt_t & +pgp_key_t::pkt() const +{ + return pkt_; +} + +pgp_key_pkt_t & +pgp_key_t::pkt() +{ + return pkt_; +} + +void +pgp_key_t::set_pkt(const pgp_key_pkt_t &pkt) +{ + pkt_ = pkt; +} + +pgp_key_material_t & +pgp_key_t::material() +{ + return pkt_.material; +} + +pgp_pubkey_alg_t +pgp_key_t::alg() const +{ + return pkt_.alg; +} + +pgp_curve_t +pgp_key_t::curve() const +{ + switch (alg()) { + case PGP_PKA_ECDH: + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: + case PGP_PKA_SM2: + return pkt_.material.ec.curve; + default: + return PGP_CURVE_UNKNOWN; + } +} + +pgp_version_t +pgp_key_t::version() const +{ + return pkt().version; +} + +pgp_pkt_type_t +pgp_key_t::type() const +{ + return pkt().tag; +} + +bool +pgp_key_t::encrypted() const +{ + return is_secret() && !pkt().material.secret; +} + +uint8_t +pgp_key_t::flags() const +{ + return flags_; +} + +bool +pgp_key_t::can_sign() const +{ + return flags_ & PGP_KF_SIGN; +} + +bool +pgp_key_t::can_certify() const +{ + return flags_ & PGP_KF_CERTIFY; +} + +bool +pgp_key_t::can_encrypt() const +{ + return flags_ & PGP_KF_ENCRYPT; +} + +bool +pgp_key_t::has_secret() const +{ + if (!is_secret()) { + return false; + } + if ((format == PGP_KEY_STORE_GPG) && !pkt_.sec_len) { + return false; + } + if (pkt_.sec_protection.s2k.usage == PGP_S2KU_NONE) { + return true; + } + switch (pkt_.sec_protection.s2k.specifier) { + case PGP_S2KS_SIMPLE: + case PGP_S2KS_SALTED: + case PGP_S2KS_ITERATED_AND_SALTED: + return true; + default: + return false; + } +} + +bool +pgp_key_t::usable_for(pgp_op_t op, bool if_secret) const +{ + switch (op) { + case PGP_OP_ADD_SUBKEY: + return is_primary() && can_sign() && (if_secret || has_secret()); + case PGP_OP_SIGN: + return can_sign() && valid() && (if_secret || has_secret()); + case PGP_OP_CERTIFY: + return can_certify() && valid() && (if_secret || has_secret()); + case PGP_OP_DECRYPT: + return can_encrypt() && valid() && (if_secret || has_secret()); + case PGP_OP_UNLOCK: + case PGP_OP_PROTECT: + case PGP_OP_UNPROTECT: + return has_secret(); + case PGP_OP_VERIFY: + return can_sign() && valid(); + case PGP_OP_ADD_USERID: + return is_primary() && can_sign() && (if_secret || has_secret()); + case PGP_OP_ENCRYPT: + return can_encrypt() && valid(); + default: + return false; + } +} + +uint32_t +pgp_key_t::expiration() const +{ + if (pkt_.version >= 4) { + return expiration_; + } + /* too large value for pkt.v3_days may overflow uint32_t */ + if (pkt_.v3_days > (0xffffffffu / 86400)) { + return 0xffffffffu; + } + return (uint32_t) pkt_.v3_days * 86400; +} + +bool +pgp_key_t::expired() const +{ + return validity_.expired; +} + +uint32_t +pgp_key_t::creation() const +{ + return pkt_.creation_time; +} + +bool +pgp_key_t::is_public() const +{ + return is_public_key_pkt(pkt_.tag); +} + +bool +pgp_key_t::is_secret() const +{ + return is_secret_key_pkt(pkt_.tag); +} + +bool +pgp_key_t::is_primary() const +{ + return is_primary_key_pkt(pkt_.tag); +} + +bool +pgp_key_t::is_subkey() const +{ + return is_subkey_pkt(pkt_.tag); +} + +bool +pgp_key_t::is_locked() const +{ + if (!is_secret()) { + RNP_LOG("key is not a secret key"); + return false; + } + return encrypted(); +} + +bool +pgp_key_t::is_protected() const +{ + // sanity check + if (!is_secret()) { + RNP_LOG("Warning: this is not a secret key"); + } + return pkt_.sec_protection.s2k.usage != PGP_S2KU_NONE; +} + +bool +pgp_key_t::valid() const +{ + return validity_.validated && validity_.valid && !validity_.expired; +} + +bool +pgp_key_t::validated() const +{ + return validity_.validated; +} + +uint64_t +pgp_key_t::valid_till_common(bool expiry) const +{ + if (!validated()) { + return 0; + } + uint64_t till = expiration() ? (uint64_t) creation() + expiration() : UINT64_MAX; + if (valid()) { + return till; + } + if (revoked()) { + /* we should not believe to the compromised key at all */ + if (revocation_.code == PGP_REVOCATION_COMPROMISED) { + return 0; + } + const pgp_subsig_t &revsig = get_sig(revocation_.sigid); + if (revsig.sig.creation() > creation()) { + /* pick less time from revocation time and expiration time */ + return std::min((uint64_t) revsig.sig.creation(), till); + } + return 0; + } + /* if key is not marked as expired then it wasn't valid at all */ + return expiry ? till : 0; +} + +uint64_t +pgp_key_t::valid_till() const +{ + return valid_till_; +} + +bool +pgp_key_t::valid_at(uint64_t timestamp) const +{ + /* TODO: consider implementing more sophisticated checks, as key validity time could + * possibly be non-continuous */ + return (timestamp >= creation()) && timestamp && (timestamp <= valid_till()); +} + +const pgp_key_id_t & +pgp_key_t::keyid() const +{ + return keyid_; +} + +const pgp_fingerprint_t & +pgp_key_t::fp() const +{ + return fingerprint_; +} + +const pgp_key_grip_t & +pgp_key_t::grip() const +{ + return grip_; +} + +const pgp_fingerprint_t & +pgp_key_t::primary_fp() const +{ + if (!primary_fp_set_) { + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + return primary_fp_; +} + +bool +pgp_key_t::has_primary_fp() const +{ + return primary_fp_set_; +} + +void +pgp_key_t::unset_primary_fp() +{ + primary_fp_set_ = false; + primary_fp_ = {}; +} + +void +pgp_key_t::link_subkey_fp(pgp_key_t &subkey) +{ + if (!is_primary() || !subkey.is_subkey()) { + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + subkey.primary_fp_ = fp(); + subkey.primary_fp_set_ = true; + add_subkey_fp(subkey.fp()); +} + +void +pgp_key_t::add_subkey_fp(const pgp_fingerprint_t &fp) +{ + if (std::find(subkey_fps_.begin(), subkey_fps_.end(), fp) == subkey_fps_.end()) { + subkey_fps_.push_back(fp); + } +} + +size_t +pgp_key_t::subkey_count() const +{ + return subkey_fps_.size(); +} + +void +pgp_key_t::remove_subkey_fp(const pgp_fingerprint_t &fp) +{ + auto it = std::find(subkey_fps_.begin(), subkey_fps_.end(), fp); + if (it != subkey_fps_.end()) { + subkey_fps_.erase(it); + } +} + +const pgp_fingerprint_t & +pgp_key_t::get_subkey_fp(size_t idx) const +{ + return subkey_fps_[idx]; +} + +const std::vector<pgp_fingerprint_t> & +pgp_key_t::subkey_fps() const +{ + return subkey_fps_; +} + +size_t +pgp_key_t::rawpkt_count() const +{ + if (format == PGP_KEY_STORE_G10) { + return 1; + } + return 1 + uid_count() + sig_count(); +} + +pgp_rawpacket_t & +pgp_key_t::rawpkt() +{ + return rawpkt_; +} + +const pgp_rawpacket_t & +pgp_key_t::rawpkt() const +{ + return rawpkt_; +} + +void +pgp_key_t::set_rawpkt(const pgp_rawpacket_t &src) +{ + rawpkt_ = src; +} + +bool +pgp_key_t::unlock(const pgp_password_provider_t &provider, pgp_op_t op) +{ + // sanity checks + if (!usable_for(PGP_OP_UNLOCK)) { + return false; + } + // see if it's already unlocked + if (!is_locked()) { + return true; + } + + pgp_password_ctx_t ctx(op, this); + pgp_key_pkt_t * decrypted_seckey = pgp_decrypt_seckey(*this, provider, ctx); + if (!decrypted_seckey) { + return false; + } + + // this shouldn't really be necessary, but just in case + forget_secret_key_fields(&pkt_.material); + // copy the decrypted mpis into the pgp_key_t + pkt_.material = decrypted_seckey->material; + pkt_.material.secret = true; + delete decrypted_seckey; + return true; +} + +bool +pgp_key_t::lock() +{ + // sanity checks + if (!is_secret()) { + RNP_LOG("invalid args"); + return false; + } + + // see if it's already locked + if (is_locked()) { + return true; + } + + forget_secret_key_fields(&pkt_.material); + return true; +} + +bool +pgp_key_t::protect(const rnp_key_protection_params_t &protection, + const pgp_password_provider_t & password_provider, + rnp::SecurityContext & sctx) +{ + pgp_password_ctx_t ctx(PGP_OP_PROTECT, this); + + // ask the provider for a password + rnp::secure_array<char, MAX_PASSWORD_LENGTH> password; + if (!pgp_request_password(&password_provider, &ctx, password.data(), password.size())) { + return false; + } + return protect(pkt_, protection, password.data(), sctx); +} + +bool +pgp_key_t::protect(pgp_key_pkt_t & decrypted, + const rnp_key_protection_params_t &protection, + const std::string & new_password, + rnp::SecurityContext & ctx) +{ + if (!is_secret()) { + RNP_LOG("Warning: this is not a secret key"); + return false; + } + bool ownpkt = &decrypted == &pkt_; + if (!decrypted.material.secret) { + RNP_LOG("Decrypted secret key must be provided"); + return false; + } + + /* force encrypted-and-hashed and iterated-and-salted as it's the only method we support*/ + pkt_.sec_protection.s2k.usage = PGP_S2KU_ENCRYPTED_AND_HASHED; + pkt_.sec_protection.s2k.specifier = PGP_S2KS_ITERATED_AND_SALTED; + /* use default values where needed */ + pkt_.sec_protection.symm_alg = + protection.symm_alg ? protection.symm_alg : DEFAULT_PGP_SYMM_ALG; + pkt_.sec_protection.cipher_mode = + protection.cipher_mode ? protection.cipher_mode : DEFAULT_PGP_CIPHER_MODE; + pkt_.sec_protection.s2k.hash_alg = + protection.hash_alg ? protection.hash_alg : DEFAULT_PGP_HASH_ALG; + auto iter = protection.iterations; + if (!iter) { + iter = ctx.s2k_iterations(pkt_.sec_protection.s2k.hash_alg); + } + pkt_.sec_protection.s2k.iterations = pgp_s2k_round_iterations(iter); + if (!ownpkt) { + /* decrypted is assumed to be temporary variable so we may modify it */ + decrypted.sec_protection = pkt_.sec_protection; + } + + /* write the protected key to raw packet */ + return write_sec_rawpkt(decrypted, new_password, ctx); +} + +bool +pgp_key_t::unprotect(const pgp_password_provider_t &password_provider, + rnp::SecurityContext & secctx) +{ + /* sanity check */ + if (!is_secret()) { + RNP_LOG("Warning: this is not a secret key"); + return false; + } + /* already unprotected */ + if (!is_protected()) { + return true; + } + /* simple case */ + if (!encrypted()) { + pkt_.sec_protection.s2k.usage = PGP_S2KU_NONE; + return write_sec_rawpkt(pkt_, "", secctx); + } + + pgp_password_ctx_t ctx(PGP_OP_UNPROTECT, this); + + pgp_key_pkt_t *decrypted_seckey = pgp_decrypt_seckey(*this, password_provider, ctx); + if (!decrypted_seckey) { + return false; + } + decrypted_seckey->sec_protection.s2k.usage = PGP_S2KU_NONE; + if (!write_sec_rawpkt(*decrypted_seckey, "", secctx)) { + delete decrypted_seckey; + return false; + } + pkt_ = std::move(*decrypted_seckey); + /* current logic is that unprotected key should be additionally unlocked */ + forget_secret_key_fields(&pkt_.material); + delete decrypted_seckey; + return true; +} + +void +pgp_key_t::write(pgp_dest_t &dst) const +{ + /* write key rawpacket */ + rawpkt_.write(dst); + + if (format == PGP_KEY_STORE_G10) { + return; + } + + /* write signatures on key */ + for (auto &sigid : keysigs_) { + get_sig(sigid).rawpkt.write(dst); + } + + /* write uids and their signatures */ + for (const auto &uid : uids_) { + uid.rawpkt.write(dst); + for (size_t idx = 0; idx < uid.sig_count(); idx++) { + get_sig(uid.get_sig(idx)).rawpkt.write(dst); + } + } +} + +void +pgp_key_t::write_xfer(pgp_dest_t &dst, const rnp_key_store_t *keyring) const +{ + write(dst); + if (dst.werr) { + RNP_LOG("Failed to export primary key"); + return; + } + + if (!keyring) { + return; + } + + // Export subkeys + for (auto &fp : subkey_fps_) { + const pgp_key_t *subkey = rnp_key_store_get_key_by_fpr(keyring, fp); + if (!subkey) { + char fphex[PGP_FINGERPRINT_SIZE * 2 + 1] = {0}; + rnp::hex_encode( + fp.fingerprint, fp.length, fphex, sizeof(fphex), rnp::HEX_LOWERCASE); + RNP_LOG("Warning! Subkey %s not found.", fphex); + continue; + } + subkey->write(dst); + if (dst.werr) { + RNP_LOG("Error occurred when exporting a subkey"); + return; + } + } +} + +bool +pgp_key_t::write_autocrypt(pgp_dest_t &dst, pgp_key_t &sub, uint32_t uid) +{ + pgp_subsig_t *cert = latest_uid_selfcert(uid); + if (!cert) { + RNP_LOG("No valid uid certification"); + return false; + } + pgp_subsig_t *binding = sub.latest_binding(); + if (!binding) { + RNP_LOG("No valid binding for subkey"); + return false; + } + if (is_secret() || sub.is_secret()) { + RNP_LOG("Public key required"); + return false; + } + + try { + /* write all or nothing */ + rnp::MemoryDest memdst; + pkt().write(memdst.dst()); + get_uid(uid).pkt.write(memdst.dst()); + cert->sig.write(memdst.dst()); + sub.pkt().write(memdst.dst()); + binding->sig.write(memdst.dst()); + dst_write(&dst, memdst.memory(), memdst.writeb()); + return !dst.werr; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } +} + +/* look only for primary userids */ +#define PGP_UID_PRIMARY ((uint32_t) -2) +/* look for any uid, except PGP_UID_NONE) */ +#define PGP_UID_ANY ((uint32_t) -3) + +pgp_subsig_t * +pgp_key_t::latest_selfsig(uint32_t uid) +{ + uint32_t latest = 0; + pgp_subsig_t *res = nullptr; + + for (auto &sigid : sigs_) { + auto &sig = get_sig(sigid); + if (!sig.valid()) { + continue; + } + bool skip = false; + switch (uid) { + case PGP_UID_NONE: + skip = (sig.uid != PGP_UID_NONE) || !is_direct_self(sig); + break; + case PGP_UID_PRIMARY: { + pgp_sig_subpkt_t *subpkt = sig.sig.get_subpkt(PGP_SIG_SUBPKT_PRIMARY_USER_ID); + skip = !is_self_cert(sig) || !subpkt || !subpkt->fields.primary_uid || + (sig.uid == PGP_UID_NONE); + break; + } + case PGP_UID_ANY: + skip = !is_self_cert(sig) || (sig.uid == PGP_UID_NONE); + break; + default: + skip = (sig.uid != uid) || !is_self_cert(sig); + break; + } + if (skip) { + continue; + } + + uint32_t creation = sig.sig.creation(); + if (creation >= latest) { + latest = creation; + res = &sig; + } + } + + /* if there is later self-sig for the same uid without primary flag, then drop res */ + if ((uid == PGP_UID_PRIMARY) && res) { + pgp_subsig_t *overres = latest_selfsig(res->uid); + if (overres && (overres->sig.creation() > res->sig.creation())) { + res = nullptr; + } + } + return res; +} + +pgp_subsig_t * +pgp_key_t::latest_binding(bool validated) +{ + uint32_t latest = 0; + pgp_subsig_t *res = NULL; + + for (auto &sigid : sigs_) { + auto &sig = get_sig(sigid); + if (validated && !sig.valid()) { + continue; + } + if (!is_binding(sig)) { + continue; + } + + uint32_t creation = sig.sig.creation(); + if (creation >= latest) { + latest = creation; + res = &sig; + } + } + return res; +} + +pgp_subsig_t * +pgp_key_t::latest_uid_selfcert(uint32_t uid) +{ + uint32_t latest = 0; + pgp_subsig_t *res = NULL; + + if (uid >= uids_.size()) { + return NULL; + } + + for (size_t idx = 0; idx < uids_[uid].sig_count(); idx++) { + auto &sig = get_sig(uids_[uid].get_sig(idx)); + if (!sig.valid() || (sig.uid != uid)) { + continue; + } + if (!is_self_cert(sig)) { + continue; + } + + uint32_t creation = sig.sig.creation(); + if (creation >= latest) { + latest = creation; + res = &sig; + } + } + return res; +} + +bool +pgp_key_t::is_signer(const pgp_subsig_t &sig) const +{ + /* if we have fingerprint let's check it */ + if (sig.sig.has_keyfp()) { + return sig.sig.keyfp() == fp(); + } + if (!sig.sig.has_keyid()) { + return false; + } + return keyid() == sig.sig.keyid(); +} + +bool +pgp_key_t::expired_with(const pgp_subsig_t &sig, uint64_t at) const +{ + /* key expiration: absence of subpkt or 0 means it never expires */ + uint64_t expiration = sig.sig.key_expiration(); + if (!expiration) { + return false; + } + return expiration + creation() < at; +} + +bool +pgp_key_t::is_self_cert(const pgp_subsig_t &sig) const +{ + return is_primary() && sig.is_cert() && is_signer(sig); +} + +bool +pgp_key_t::is_direct_self(const pgp_subsig_t &sig) const +{ + return is_primary() && (sig.sig.type() == PGP_SIG_DIRECT) && is_signer(sig); +} + +bool +pgp_key_t::is_revocation(const pgp_subsig_t &sig) const +{ + return is_primary() ? (sig.sig.type() == PGP_SIG_REV_KEY) : + (sig.sig.type() == PGP_SIG_REV_SUBKEY); +} + +bool +pgp_key_t::is_uid_revocation(const pgp_subsig_t &sig) const +{ + return is_primary() && (sig.sig.type() == PGP_SIG_REV_CERT); +} + +bool +pgp_key_t::is_binding(const pgp_subsig_t &sig) const +{ + return is_subkey() && (sig.sig.type() == PGP_SIG_SUBKEY); +} + +void +pgp_key_t::validate_sig(const pgp_key_t & key, + pgp_subsig_t & sig, + const rnp::SecurityContext &ctx) const noexcept +{ + sig.validity.reset(); + + pgp_signature_info_t sinfo = {}; + sinfo.sig = &sig.sig; + sinfo.signer_valid = true; + if (key.is_self_cert(sig) || key.is_binding(sig)) { + sinfo.ignore_expiry = true; + } + + pgp_sig_type_t stype = sig.sig.type(); + try { + switch (stype) { + case PGP_SIG_BINARY: + case PGP_SIG_TEXT: + case PGP_SIG_STANDALONE: + case PGP_SIG_PRIMARY: + RNP_LOG("Invalid key signature type: %d", (int) stype); + return; + case PGP_CERT_GENERIC: + case PGP_CERT_PERSONA: + case PGP_CERT_CASUAL: + case PGP_CERT_POSITIVE: + case PGP_SIG_REV_CERT: { + if (sig.uid >= key.uid_count()) { + RNP_LOG("Userid not found"); + return; + } + validate_cert(sinfo, key.pkt(), key.get_uid(sig.uid).pkt, ctx); + break; + } + case PGP_SIG_SUBKEY: + if (!is_signer(sig)) { + RNP_LOG("Invalid subkey binding's signer."); + return; + } + validate_binding(sinfo, key, ctx); + break; + case PGP_SIG_DIRECT: + case PGP_SIG_REV_KEY: + validate_direct(sinfo, ctx); + break; + case PGP_SIG_REV_SUBKEY: + if (!is_signer(sig)) { + RNP_LOG("Invalid subkey revocation's signer."); + return; + } + validate_sub_rev(sinfo, key.pkt(), ctx); + break; + default: + RNP_LOG("Unsupported key signature type: %d", (int) stype); + return; + } + } catch (const std::exception &e) { + RNP_LOG("Key signature validation failed: %s", e.what()); + } + + sig.validity.validated = true; + sig.validity.valid = sinfo.valid; + /* revocation signature cannot expire */ + if ((stype != PGP_SIG_REV_KEY) && (stype != PGP_SIG_REV_SUBKEY) && + (stype != PGP_SIG_REV_CERT)) { + sig.validity.expired = sinfo.expired; + } +} + +void +pgp_key_t::validate_sig(pgp_signature_info_t & sinfo, + rnp::Hash & hash, + const rnp::SecurityContext &ctx) const noexcept +{ + sinfo.no_signer = false; + sinfo.valid = false; + sinfo.expired = false; + + /* Validate signature itself */ + if (sinfo.signer_valid || valid_at(sinfo.sig->creation())) { + sinfo.valid = !signature_validate(*sinfo.sig, pkt_.material, hash, ctx); + } else { + sinfo.valid = false; + RNP_LOG("invalid or untrusted key"); + } + + /* Check signature's expiration time */ + uint32_t now = ctx.time(); + uint32_t create = sinfo.sig->creation(); + uint32_t expiry = sinfo.sig->expiration(); + if (create > now) { + /* signature created later then now */ + RNP_LOG("signature created %d seconds in future", (int) (create - now)); + sinfo.expired = true; + } + if (create && expiry && (create + expiry < now)) { + /* signature expired */ + RNP_LOG("signature expired"); + sinfo.expired = true; + } + + /* check key creation time vs signature creation */ + if (creation() > create) { + RNP_LOG("key is newer than signature"); + sinfo.valid = false; + } + + /* check whether key was not expired when sig created */ + if (!sinfo.ignore_expiry && expiration() && (creation() + expiration() < create)) { + RNP_LOG("signature made after key expiration"); + sinfo.valid = false; + } + + /* Check signer's fingerprint */ + if (sinfo.sig->has_keyfp() && (sinfo.sig->keyfp() != fp())) { + RNP_LOG("issuer fingerprint doesn't match signer's one"); + sinfo.valid = false; + } + + /* Check for unknown critical notations */ + for (auto &subpkt : sinfo.sig->subpkts) { + if (!subpkt.critical || (subpkt.type != PGP_SIG_SUBPKT_NOTATION_DATA)) { + continue; + } + std::string name(subpkt.fields.notation.name, + subpkt.fields.notation.name + subpkt.fields.notation.nlen); + RNP_LOG("unknown critical notation: %s", name.c_str()); + sinfo.valid = false; + } +} + +void +pgp_key_t::validate_cert(pgp_signature_info_t & sinfo, + const pgp_key_pkt_t & key, + const pgp_userid_pkt_t & uid, + const rnp::SecurityContext &ctx) const +{ + auto hash = signature_hash_certification(*sinfo.sig, key, uid); + validate_sig(sinfo, *hash, ctx); +} + +void +pgp_key_t::validate_binding(pgp_signature_info_t & sinfo, + const pgp_key_t & subkey, + const rnp::SecurityContext &ctx) const +{ + if (!is_primary() || !subkey.is_subkey()) { + RNP_LOG("Invalid binding signature key type(s)"); + sinfo.valid = false; + return; + } + auto hash = signature_hash_binding(*sinfo.sig, pkt(), subkey.pkt()); + validate_sig(sinfo, *hash, ctx); + if (!sinfo.valid || !(sinfo.sig->key_flags() & PGP_KF_SIGN)) { + return; + } + + /* check primary key binding signature if any */ + sinfo.valid = false; + pgp_sig_subpkt_t *subpkt = sinfo.sig->get_subpkt(PGP_SIG_SUBPKT_EMBEDDED_SIGNATURE, false); + if (!subpkt) { + RNP_LOG("error! no primary key binding signature"); + return; + } + if (!subpkt->parsed) { + RNP_LOG("invalid embedded signature subpacket"); + return; + } + if (subpkt->fields.sig->type() != PGP_SIG_PRIMARY) { + RNP_LOG("invalid primary key binding signature"); + return; + } + if (subpkt->fields.sig->version < PGP_V4) { + RNP_LOG("invalid primary key binding signature version"); + return; + } + + hash = signature_hash_binding(*subpkt->fields.sig, pkt(), subkey.pkt()); + pgp_signature_info_t bindinfo = {}; + bindinfo.sig = subpkt->fields.sig; + bindinfo.signer_valid = true; + bindinfo.ignore_expiry = true; + subkey.validate_sig(bindinfo, *hash, ctx); + sinfo.valid = bindinfo.valid && !bindinfo.expired; +} + +void +pgp_key_t::validate_sub_rev(pgp_signature_info_t & sinfo, + const pgp_key_pkt_t & subkey, + const rnp::SecurityContext &ctx) const +{ + auto hash = signature_hash_binding(*sinfo.sig, pkt(), subkey); + validate_sig(sinfo, *hash, ctx); +} + +void +pgp_key_t::validate_direct(pgp_signature_info_t &sinfo, const rnp::SecurityContext &ctx) const +{ + auto hash = signature_hash_direct(*sinfo.sig, pkt()); + validate_sig(sinfo, *hash, ctx); +} + +void +pgp_key_t::validate_self_signatures(const rnp::SecurityContext &ctx) +{ + for (auto &sigid : sigs_) { + pgp_subsig_t &sig = get_sig(sigid); + if (sig.validity.validated) { + continue; + } + + if (is_direct_self(sig) || is_self_cert(sig) || is_uid_revocation(sig) || + is_revocation(sig)) { + validate_sig(*this, sig, ctx); + } + } +} + +void +pgp_key_t::validate_self_signatures(pgp_key_t &primary, const rnp::SecurityContext &ctx) +{ + for (auto &sigid : sigs_) { + pgp_subsig_t &sig = get_sig(sigid); + if (sig.validity.validated) { + continue; + } + + if (is_binding(sig) || is_revocation(sig)) { + primary.validate_sig(*this, sig, ctx); + } + } +} + +void +pgp_key_t::validate_primary(rnp_key_store_t &keyring) +{ + /* validate signatures if needed */ + validate_self_signatures(keyring.secctx); + + /* consider public key as valid on this level if it is not expired and has at least one + * valid self-signature, and is not revoked */ + validity_.reset(); + validity_.validated = true; + bool has_cert = false; + bool has_expired = false; + /* check whether key is revoked */ + for (auto &sigid : sigs_) { + pgp_subsig_t &sig = get_sig(sigid); + if (!sig.valid()) { + continue; + } + if (is_revocation(sig)) { + return; + } + } + /* if we have direct-key signature, then it has higher priority for expiration check */ + uint64_t now = keyring.secctx.time(); + pgp_subsig_t *dirsig = latest_selfsig(PGP_UID_NONE); + if (dirsig) { + has_expired = expired_with(*dirsig, now); + has_cert = !has_expired; + } + /* if we have primary uid and it is more restrictive, then use it as well */ + pgp_subsig_t *prisig = NULL; + if (!has_expired && (prisig = latest_selfsig(PGP_UID_PRIMARY))) { + has_expired = expired_with(*prisig, now); + has_cert = !has_expired; + } + /* if we don't have direct-key sig and primary uid, use the latest self-cert */ + pgp_subsig_t *latest = NULL; + if (!dirsig && !prisig && (latest = latest_selfsig(PGP_UID_ANY))) { + has_expired = expired_with(*latest, now); + has_cert = !has_expired; + } + + /* we have at least one non-expiring key self-signature */ + if (has_cert) { + validity_.valid = true; + return; + } + /* we have valid self-signature which expires key */ + if (has_expired) { + validity_.expired = true; + return; + } + + /* let's check whether key has at least one valid subkey binding */ + for (size_t i = 0; i < subkey_count(); i++) { + pgp_key_t *sub = pgp_key_get_subkey(this, &keyring, i); + if (!sub) { + continue; + } + sub->validate_self_signatures(*this, keyring.secctx); + pgp_subsig_t *sig = sub->latest_binding(); + if (!sig) { + continue; + } + /* check whether subkey is expired - then do not mark key as valid */ + if (sub->expired_with(*sig, now)) { + continue; + } + validity_.valid = true; + return; + } +} + +void +pgp_key_t::validate_subkey(pgp_key_t *primary, const rnp::SecurityContext &ctx) +{ + /* consider subkey as valid on this level if it has valid primary key, has at least one + * non-expired binding signature, and is not revoked. */ + validity_.reset(); + validity_.validated = true; + if (!primary || (!primary->valid() && !primary->expired())) { + return; + } + /* validate signatures if needed */ + validate_self_signatures(*primary, ctx); + + bool has_binding = false; + bool has_expired = false; + for (auto &sigid : sigs_) { + pgp_subsig_t &sig = get_sig(sigid); + if (!sig.valid()) { + continue; + } + + if (is_binding(sig) && !has_binding) { + /* check whether subkey is expired */ + if (expired_with(sig, ctx.time())) { + has_expired = true; + continue; + } + has_binding = true; + } else if (is_revocation(sig)) { + return; + } + } + validity_.valid = has_binding && primary->valid(); + if (!validity_.valid) { + validity_.expired = has_expired; + } +} + +void +pgp_key_t::validate(rnp_key_store_t &keyring) +{ + validity_.reset(); + if (!is_subkey()) { + validate_primary(keyring); + } else { + pgp_key_t *primary = NULL; + if (has_primary_fp()) { + primary = rnp_key_store_get_key_by_fpr(&keyring, primary_fp()); + } + validate_subkey(primary, keyring.secctx); + } +} + +void +pgp_key_t::revalidate(rnp_key_store_t &keyring) +{ + if (is_subkey()) { + pgp_key_t *primary = rnp_key_store_get_primary_key(&keyring, this); + if (primary) { + primary->revalidate(keyring); + } else { + validate_subkey(NULL, keyring.secctx); + } + return; + } + + validate(keyring); + if (!refresh_data(keyring.secctx)) { + RNP_LOG("Failed to refresh key data"); + } + /* validate/re-validate all subkeys as well */ + for (auto &fp : subkey_fps_) { + pgp_key_t *subkey = rnp_key_store_get_key_by_fpr(&keyring, fp); + if (subkey) { + subkey->validate_subkey(this, keyring.secctx); + if (!subkey->refresh_data(this, keyring.secctx)) { + RNP_LOG("Failed to refresh subkey data"); + } + } + } +} + +void +pgp_key_t::mark_valid() +{ + validity_.mark_valid(); + for (size_t i = 0; i < sig_count(); i++) { + get_sig(i).validity.mark_valid(); + } +} + +void +pgp_key_t::sign_init(pgp_signature_t &sig, pgp_hash_alg_t hash, uint64_t creation) const +{ + sig.version = PGP_V4; + sig.halg = pgp_hash_adjust_alg_to_key(hash, &pkt_); + sig.palg = alg(); + sig.set_keyfp(fp()); + sig.set_creation(creation); + sig.set_keyid(keyid()); +} + +void +pgp_key_t::sign_cert(const pgp_key_pkt_t & key, + const pgp_userid_pkt_t &uid, + pgp_signature_t & sig, + rnp::SecurityContext & ctx) +{ + sig.fill_hashed_data(); + auto hash = signature_hash_certification(sig, key, uid); + signature_calculate(sig, pkt_.material, *hash, ctx); +} + +void +pgp_key_t::sign_direct(const pgp_key_pkt_t & key, + pgp_signature_t & sig, + rnp::SecurityContext &ctx) +{ + sig.fill_hashed_data(); + auto hash = signature_hash_direct(sig, key); + signature_calculate(sig, pkt_.material, *hash, ctx); +} + +void +pgp_key_t::sign_binding(const pgp_key_pkt_t & key, + pgp_signature_t & sig, + rnp::SecurityContext &ctx) +{ + sig.fill_hashed_data(); + auto hash = is_primary() ? signature_hash_binding(sig, pkt(), key) : + signature_hash_binding(sig, key, pkt()); + signature_calculate(sig, pkt_.material, *hash, ctx); +} + +void +pgp_key_t::gen_revocation(const pgp_revoke_t & revoke, + pgp_hash_alg_t hash, + const pgp_key_pkt_t & key, + pgp_signature_t & sig, + rnp::SecurityContext &ctx) +{ + sign_init(sig, hash, ctx.time()); + sig.set_type(is_primary_key_pkt(key.tag) ? PGP_SIG_REV_KEY : PGP_SIG_REV_SUBKEY); + sig.set_revocation_reason(revoke.code, revoke.reason); + + if (is_primary_key_pkt(key.tag)) { + sign_direct(key, sig, ctx); + } else { + sign_binding(key, sig, ctx); + } +} + +void +pgp_key_t::sign_subkey_binding(pgp_key_t & sub, + pgp_signature_t & sig, + rnp::SecurityContext &ctx, + bool subsign) +{ + if (!is_primary()) { + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + sign_binding(sub.pkt(), sig, ctx); + /* add primary key binding subpacket if requested */ + if (subsign) { + pgp_signature_t embsig; + sub.sign_init(embsig, sig.halg, ctx.time()); + embsig.set_type(PGP_SIG_PRIMARY); + sub.sign_binding(pkt(), embsig, ctx); + sig.set_embedded_sig(embsig); + } +} + +void +pgp_key_t::add_uid_cert(rnp_selfsig_cert_info_t &cert, + pgp_hash_alg_t hash, + rnp::SecurityContext & ctx, + pgp_key_t * pubkey) +{ + if (cert.userid.empty()) { + /* todo: why not to allow empty uid? */ + RNP_LOG("wrong parameters"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + // userids are only valid for primary keys, not subkeys + if (!is_primary()) { + RNP_LOG("cannot add a userid to a subkey"); + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + // see if the key already has this userid + if (has_uid(cert.userid)) { + RNP_LOG("key already has this userid"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + // this isn't really valid for this format + if (format == PGP_KEY_STORE_G10) { + RNP_LOG("Unsupported key store type"); + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + // We only support modifying v4 and newer keys + if (pkt().version < PGP_V4) { + RNP_LOG("adding a userid to V2/V3 key is not supported"); + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + /* TODO: if key has at least one uid then has_primary_uid() will be always true! */ + if (has_primary_uid() && cert.primary) { + RNP_LOG("changing the primary userid is not supported"); + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + + /* Fill the transferable userid */ + pgp_userid_pkt_t uid; + pgp_signature_t sig; + sign_init(sig, hash, ctx.time()); + cert.populate(uid, sig); + try { + sign_cert(pkt_, uid, sig, ctx); + } catch (const std::exception &e) { + RNP_LOG("Failed to certify: %s", e.what()); + throw; + } + /* add uid and signature to the key and pubkey, if non-NULL */ + uids_.emplace_back(uid); + add_sig(sig, uid_count() - 1); + refresh_data(ctx); + if (!pubkey) { + return; + } + pubkey->uids_.emplace_back(uid); + pubkey->add_sig(sig, pubkey->uid_count() - 1); + pubkey->refresh_data(ctx); +} + +void +pgp_key_t::add_sub_binding(pgp_key_t & subsec, + pgp_key_t & subpub, + const rnp_selfsig_binding_info_t &binding, + pgp_hash_alg_t hash, + rnp::SecurityContext & ctx) +{ + if (!is_primary()) { + RNP_LOG("must be called on primary key"); + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + + /* populate signature */ + pgp_signature_t sig; + sign_init(sig, hash, ctx.time()); + sig.set_type(PGP_SIG_SUBKEY); + if (binding.key_expiration) { + sig.set_key_expiration(binding.key_expiration); + } + if (binding.key_flags) { + sig.set_key_flags(binding.key_flags); + } + /* calculate binding */ + pgp_key_flags_t realkf = (pgp_key_flags_t) binding.key_flags; + if (!realkf) { + realkf = pgp_pk_alg_capabilities(subsec.alg()); + } + sign_subkey_binding(subsec, sig, ctx, realkf & PGP_KF_SIGN); + /* add to the secret and public key */ + subsec.add_sig(sig); + subpub.add_sig(sig); +} + +bool +pgp_key_t::refresh_data(const rnp::SecurityContext &ctx) +{ + if (!is_primary()) { + RNP_LOG("key must be primary"); + return false; + } + /* validate self-signatures if not done yet */ + validate_self_signatures(ctx); + /* key expiration */ + expiration_ = 0; + /* if we have direct-key signature, then it has higher priority */ + pgp_subsig_t *dirsig = latest_selfsig(PGP_UID_NONE); + if (dirsig) { + expiration_ = dirsig->sig.key_expiration(); + } + /* if we have primary uid and it is more restrictive, then use it as well */ + pgp_subsig_t *prisig = latest_selfsig(PGP_UID_PRIMARY); + if (prisig && prisig->sig.key_expiration() && + (!expiration_ || (prisig->sig.key_expiration() < expiration_))) { + expiration_ = prisig->sig.key_expiration(); + } + /* if we don't have direct-key sig and primary uid, use the latest self-cert */ + pgp_subsig_t *latest = latest_selfsig(PGP_UID_ANY); + if (!dirsig && !prisig && latest) { + expiration_ = latest->sig.key_expiration(); + } + /* key flags: check in direct-key sig first, then primary uid, and then latest */ + if (dirsig && dirsig->sig.has_subpkt(PGP_SIG_SUBPKT_KEY_FLAGS)) { + flags_ = dirsig->key_flags; + } else if (prisig && prisig->sig.has_subpkt(PGP_SIG_SUBPKT_KEY_FLAGS)) { + flags_ = prisig->key_flags; + } else if (latest && latest->sig.has_subpkt(PGP_SIG_SUBPKT_KEY_FLAGS)) { + flags_ = latest->key_flags; + } else { + flags_ = pgp_pk_alg_capabilities(alg()); + } + /* revocation(s) */ + clear_revokes(); + for (size_t i = 0; i < sig_count(); i++) { + pgp_subsig_t &sig = get_sig(i); + if (!sig.valid()) { + continue; + } + try { + if (is_revocation(sig)) { + if (revoked_) { + continue; + } + revoked_ = true; + revocation_ = pgp_revoke_t(sig); + } else if (is_uid_revocation(sig)) { + if (sig.uid >= uid_count()) { + RNP_LOG("Invalid uid index"); + continue; + } + pgp_userid_t &uid = get_uid(sig.uid); + if (uid.revoked) { + continue; + } + uid.revoked = true; + uid.revocation = pgp_revoke_t(sig); + } + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } + } + /* valid till */ + valid_till_ = valid_till_common(expired()); + /* userid validities */ + for (size_t i = 0; i < uid_count(); i++) { + get_uid(i).valid = false; + } + for (size_t i = 0; i < sig_count(); i++) { + pgp_subsig_t &sig = get_sig(i); + /* consider userid as valid if it has at least one non-expired self-sig */ + if (!sig.valid() || !sig.is_cert() || !is_signer(sig) || sig.expired(ctx.time())) { + continue; + } + if (sig.uid >= uid_count()) { + continue; + } + get_uid(sig.uid).valid = true; + } + /* check whether uid is revoked */ + for (size_t i = 0; i < uid_count(); i++) { + pgp_userid_t &uid = get_uid(i); + if (uid.revoked) { + uid.valid = false; + } + } + /* primary userid: use latest one which is not overridden by later non-primary selfsig */ + uid0_set_ = false; + if (prisig && get_uid(prisig->uid).valid) { + uid0_ = prisig->uid; + uid0_set_ = true; + } + return true; +} + +bool +pgp_key_t::refresh_data(pgp_key_t *primary, const rnp::SecurityContext &ctx) +{ + /* validate self-signatures if not done yet */ + if (primary) { + validate_self_signatures(*primary, ctx); + } + pgp_subsig_t *sig = latest_binding(primary); + /* subkey expiration */ + expiration_ = sig ? sig->sig.key_expiration() : 0; + /* subkey flags */ + if (sig && sig->sig.has_subpkt(PGP_SIG_SUBPKT_KEY_FLAGS)) { + flags_ = sig->key_flags; + } else { + flags_ = pgp_pk_alg_capabilities(alg()); + } + /* revocation */ + clear_revokes(); + for (size_t i = 0; i < sig_count(); i++) { + pgp_subsig_t &sig = get_sig(i); + if (!sig.valid() || !is_revocation(sig)) { + continue; + } + revoked_ = true; + try { + revocation_ = pgp_revoke_t(sig); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } + break; + } + /* valid till */ + if (primary) { + valid_till_ = + std::min(primary->valid_till(), valid_till_common(expired() || primary->expired())); + } else { + valid_till_ = valid_till_common(expired()); + } + return true; +} + +void +pgp_key_t::merge_validity(const pgp_validity_t &src) +{ + validity_.valid = validity_.valid && src.valid; + /* We may safely leave validated status only if both merged keys are valid && validated. + * Otherwise we'll need to revalidate. For instance, one validated but invalid key may add + * revocation signature, or valid key may add certification to the invalid one. */ + validity_.validated = validity_.valid && validity_.validated && src.validated; + /* if expired is true at least in one case then valid and validated are false */ + validity_.expired = false; +} + +bool +pgp_key_t::merge(const pgp_key_t &src) +{ + if (is_subkey() || src.is_subkey()) { + RNP_LOG("wrong key merge call"); + return false; + } + + pgp_transferable_key_t dstkey; + if (transferable_key_from_key(dstkey, *this)) { + RNP_LOG("failed to get transferable key from dstkey"); + return false; + } + + pgp_transferable_key_t srckey; + if (transferable_key_from_key(srckey, src)) { + RNP_LOG("failed to get transferable key from srckey"); + return false; + } + + /* if src is secret key then merged key will become secret as well. */ + if (is_secret_key_pkt(srckey.key.tag) && !is_secret_key_pkt(dstkey.key.tag)) { + pgp_key_pkt_t tmp = dstkey.key; + dstkey.key = srckey.key; + srckey.key = tmp; + /* no subkey processing here - they are separated from the main key */ + } + + if (transferable_key_merge(dstkey, srckey)) { + RNP_LOG("failed to merge transferable keys"); + return false; + } + + pgp_key_t tmpkey; + try { + tmpkey = std::move(dstkey); + for (auto &fp : subkey_fps()) { + tmpkey.add_subkey_fp(fp); + } + for (auto &fp : src.subkey_fps()) { + tmpkey.add_subkey_fp(fp); + } + } catch (const std::exception &e) { + RNP_LOG("failed to process key/add subkey fps: %s", e.what()); + return false; + } + /* check whether key was unlocked and assign secret key data */ + if (is_secret() && !is_locked()) { + /* we may do thing below only because key material is opaque structure without + * pointers! */ + tmpkey.pkt().material = pkt().material; + } else if (src.is_secret() && !src.is_locked()) { + tmpkey.pkt().material = src.pkt().material; + } + /* copy validity status */ + tmpkey.validity_ = validity_; + tmpkey.merge_validity(src.validity_); + + *this = std::move(tmpkey); + return true; +} + +bool +pgp_key_t::merge(const pgp_key_t &src, pgp_key_t *primary) +{ + if (!is_subkey() || !src.is_subkey()) { + RNP_LOG("wrong subkey merge call"); + return false; + } + + pgp_transferable_subkey_t dstkey; + if (transferable_subkey_from_key(dstkey, *this)) { + RNP_LOG("failed to get transferable key from dstkey"); + return false; + } + + pgp_transferable_subkey_t srckey; + if (transferable_subkey_from_key(srckey, src)) { + RNP_LOG("failed to get transferable key from srckey"); + return false; + } + + /* if src is secret key then merged key will become secret as well. */ + if (is_secret_key_pkt(srckey.subkey.tag) && !is_secret_key_pkt(dstkey.subkey.tag)) { + pgp_key_pkt_t tmp = dstkey.subkey; + dstkey.subkey = srckey.subkey; + srckey.subkey = tmp; + } + + if (transferable_subkey_merge(dstkey, srckey)) { + RNP_LOG("failed to merge transferable subkeys"); + return false; + } + + pgp_key_t tmpkey; + try { + tmpkey = pgp_key_t(dstkey, primary); + } catch (const std::exception &e) { + RNP_LOG("failed to process subkey: %s", e.what()); + return false; + } + + /* check whether key was unlocked and assign secret key data */ + if (is_secret() && !is_locked()) { + /* we may do thing below only because key material is opaque structure without + * pointers! */ + tmpkey.pkt().material = pkt().material; + } else if (src.is_secret() && !src.is_locked()) { + tmpkey.pkt().material = src.pkt().material; + } + /* copy validity status */ + tmpkey.validity_ = validity_; + tmpkey.merge_validity(src.validity_); + + *this = std::move(tmpkey); + return true; +} + +size_t +pgp_key_material_t::bits() const +{ + switch (alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + return 8 * mpi_bytes(&rsa.n); + case PGP_PKA_DSA: + return 8 * mpi_bytes(&dsa.p); + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + return 8 * mpi_bytes(&eg.y); + case PGP_PKA_ECDH: + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: + case PGP_PKA_SM2: { + // bn_num_bytes returns value <= curve order + const ec_curve_desc_t *curve = get_curve_desc(ec.curve); + return curve ? curve->bitlen : 0; + } + default: + RNP_LOG("Unknown public key alg: %d", (int) alg); + return 0; + } +} + +size_t +pgp_key_material_t::qbits() const +{ + if (alg != PGP_PKA_DSA) { + return 0; + } + return 8 * mpi_bytes(&dsa.q); +} + +void +pgp_key_material_t::validate(rnp::SecurityContext &ctx, bool reset) +{ + if (!reset && validity.validated) { + return; + } + validity.reset(); + validity.valid = !validate_pgp_key_material(this, &ctx.rng); + validity.validated = true; +} + +bool +pgp_key_material_t::valid() const +{ + return validity.validated && validity.valid; +} diff --git a/comm/third_party/rnp/src/lib/pgp-key.h b/comm/third_party/rnp/src/lib/pgp-key.h new file mode 100644 index 0000000000..aa088bb47b --- /dev/null +++ b/comm/third_party/rnp/src/lib/pgp-key.h @@ -0,0 +1,671 @@ +/* + * Copyright (c) 2017-2021 [Ribose Inc](https://www.ribose.com). + * Copyright (c) 2009 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is originally derived from software contributed to + * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and + * carried further by Ribose Inc (https://www.ribose.com). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/* + * Copyright (c) 2005-2008 Nominet UK (www.nic.uk) + * All rights reserved. + * Contributors: Ben Laurie, Rachel Willmer. The Contributors have asserted + * their moral rights under the UK Copyright Design and Patents Act 1988 to + * be recorded as the authors of this copyright work. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. + * + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RNP_PACKET_KEY_H +#define RNP_PACKET_KEY_H + +#include <stdbool.h> +#include <stdio.h> +#include <vector> +#include <unordered_map> +#include "pass-provider.h" +#include "../librepgp/stream-key.h" +#include <rekey/rnp_key_store.h> +#include "../librepgp/stream-packet.h" +#include "crypto/symmetric.h" +#include "types.h" +#include "sec_profile.hpp" + +/** pgp_rawpacket_t */ +typedef struct pgp_rawpacket_t { + pgp_pkt_type_t tag; + std::vector<uint8_t> raw; + + pgp_rawpacket_t() = default; + pgp_rawpacket_t(const uint8_t *data, size_t len, pgp_pkt_type_t tag) + : tag(tag), + raw(data ? std::vector<uint8_t>(data, data + len) : std::vector<uint8_t>()){}; + pgp_rawpacket_t(const pgp_signature_t &sig); + pgp_rawpacket_t(pgp_key_pkt_t &key); + pgp_rawpacket_t(const pgp_userid_pkt_t &uid); + + void write(pgp_dest_t &dst) const; +} pgp_rawpacket_t; + +/** information about the signature */ +typedef struct pgp_subsig_t { + uint32_t uid{}; /* index in userid array in key for certification sig */ + pgp_signature_t sig{}; /* signature packet */ + pgp_sig_id_t sigid{}; /* signature identifier */ + pgp_rawpacket_t rawpkt{}; /* signature's rawpacket */ + uint8_t trustlevel{}; /* level of trust */ + uint8_t trustamount{}; /* amount of trust */ + uint8_t key_flags{}; /* key flags for certification/direct key sig */ + pgp_user_prefs_t prefs{}; /* user preferences for certification sig */ + pgp_validity_t validity{}; /* signature validity information */ + + pgp_subsig_t() = delete; + pgp_subsig_t(const pgp_signature_t &sig); + + bool validated() const; + bool valid() const; + /** @brief Returns true if signature is certification */ + bool is_cert() const; + /** @brief Returns true if signature is expired */ + bool expired(uint64_t at) const; +} pgp_subsig_t; + +typedef std::unordered_map<pgp_sig_id_t, pgp_subsig_t> pgp_sig_map_t; + +/* userid, built on top of userid packet structure */ +typedef struct pgp_userid_t { + private: + std::vector<pgp_sig_id_t> sigs_{}; /* all signatures related to this userid */ + public: + pgp_userid_pkt_t pkt{}; /* User ID or User Attribute packet as it was loaded */ + pgp_rawpacket_t rawpkt{}; /* Raw packet contents */ + std::string str{}; /* Human-readable representation of the userid */ + bool valid{}; /* User ID is valid, i.e. has valid, non-expired self-signature */ + bool revoked{}; + pgp_revoke_t revocation{}; + + pgp_userid_t(const pgp_userid_pkt_t &pkt); + + size_t sig_count() const; + const pgp_sig_id_t &get_sig(size_t idx) const; + bool has_sig(const pgp_sig_id_t &id) const; + void add_sig(const pgp_sig_id_t &sig); + void replace_sig(const pgp_sig_id_t &id, const pgp_sig_id_t &newsig); + bool del_sig(const pgp_sig_id_t &id); + void clear_sigs(); +} pgp_userid_t; + +#define PGP_UID_NONE ((uint32_t) -1) + +typedef struct rnp_key_store_t rnp_key_store_t; + +/* describes a user's key */ +struct pgp_key_t { + private: + pgp_sig_map_t sigs_map_{}; /* map with subsigs stored by their id */ + std::vector<pgp_sig_id_t> sigs_{}; /* subsig ids to lookup actual sig in map */ + std::vector<pgp_sig_id_t> keysigs_{}; /* direct-key signature ids in the original order */ + std::vector<pgp_userid_t> uids_{}; /* array of user ids */ + pgp_key_pkt_t pkt_{}; /* pubkey/seckey data packet */ + uint8_t flags_{}; /* key flags */ + uint32_t expiration_{}; /* key expiration time, if available */ + pgp_key_id_t keyid_{}; + pgp_fingerprint_t fingerprint_{}; + pgp_key_grip_t grip_{}; + pgp_fingerprint_t primary_fp_{}; /* fingerprint of the primary key (for subkeys) */ + bool primary_fp_set_{}; + std::vector<pgp_fingerprint_t> + subkey_fps_{}; /* array of subkey fingerprints (for primary keys) */ + pgp_rawpacket_t rawpkt_{}; /* key raw packet */ + uint32_t uid0_{}; /* primary uid index in uids array */ + bool uid0_set_{}; /* flag for the above */ + bool revoked_{}; /* key has been revoked */ + pgp_revoke_t revocation_{}; /* revocation reason */ + pgp_validity_t validity_{}; /* key's validity */ + uint64_t valid_till_{}; /* date till which key is/was valid */ + + pgp_subsig_t *latest_uid_selfcert(uint32_t uid); + void validate_primary(rnp_key_store_t &keyring); + void merge_validity(const pgp_validity_t &src); + uint64_t valid_till_common(bool expiry) const; + bool write_sec_pgp(pgp_dest_t & dst, + pgp_key_pkt_t & seckey, + const std::string &password, + rnp::RNG & rng); + + public: + pgp_key_store_format_t format{}; /* the format of the key in packets[0] */ + + pgp_key_t() = default; + pgp_key_t(const pgp_key_pkt_t &pkt); + pgp_key_t(const pgp_key_pkt_t &pkt, pgp_key_t &primary); + pgp_key_t(const pgp_key_t &src, bool pubonly = false); + pgp_key_t(const pgp_transferable_key_t &src); + pgp_key_t(const pgp_transferable_subkey_t &src, pgp_key_t *primary); + pgp_key_t &operator=(const pgp_key_t &) = default; + pgp_key_t &operator=(pgp_key_t &&) = default; + + size_t sig_count() const; + pgp_subsig_t & get_sig(size_t idx); + const pgp_subsig_t &get_sig(size_t idx) const; + bool has_sig(const pgp_sig_id_t &id) const; + pgp_subsig_t & replace_sig(const pgp_sig_id_t &id, const pgp_signature_t &newsig); + pgp_subsig_t & get_sig(const pgp_sig_id_t &id); + const pgp_subsig_t &get_sig(const pgp_sig_id_t &id) const; + pgp_subsig_t & add_sig(const pgp_signature_t &sig, size_t uid = PGP_UID_NONE); + bool del_sig(const pgp_sig_id_t &sigid); + size_t del_sigs(const std::vector<pgp_sig_id_t> &sigs); + size_t keysig_count() const; + pgp_subsig_t & get_keysig(size_t idx); + size_t uid_count() const; + pgp_userid_t & get_uid(size_t idx); + const pgp_userid_t &get_uid(size_t idx) const; + pgp_userid_t & add_uid(const pgp_transferable_userid_t &uid); + bool has_uid(const std::string &uid) const; + void del_uid(size_t idx); + bool has_primary_uid() const; + uint32_t get_primary_uid() const; + bool revoked() const; + const pgp_revoke_t &revocation() const; + void clear_revokes(); + + const pgp_key_pkt_t &pkt() const; + pgp_key_pkt_t & pkt(); + void set_pkt(const pgp_key_pkt_t &pkt); + + pgp_key_material_t &material(); + + pgp_pubkey_alg_t alg() const; + pgp_curve_t curve() const; + pgp_version_t version() const; + pgp_pkt_type_t type() const; + bool encrypted() const; + uint8_t flags() const; + bool can_sign() const; + bool can_certify() const; + bool can_encrypt() const; + bool has_secret() const; + /** + * @brief Check whether key is usable for the specified operation. + * + * @param op operation to check. + * @param if_secret check whether secret part of this key could be usable for op. + * @return true if key (or corresponding secret key) is usable or false otherwise. + */ + bool usable_for(pgp_op_t op, bool if_secret = false) const; + /** @brief Get key's expiration time in seconds. If 0 then it doesn't expire. */ + uint32_t expiration() const; + /** @brief Check whether key is expired. Must be validated before that. */ + bool expired() const; + /** @brief Get key's creation time in seconds since Jan, 1 1970. */ + uint32_t creation() const; + bool is_public() const; + bool is_secret() const; + bool is_primary() const; + bool is_subkey() const; + /** @brief check if a key is currently locked, i.e. secret fields are not decrypted. + * Note: Key locking does not apply to unprotected keys. + */ + bool is_locked() const; + /** @brief check if a key is currently protected, i.e. its secret data is encrypted */ + bool is_protected() const; + + bool valid() const; + bool validated() const; + /** @brief return time till which key is considered to be valid */ + uint64_t valid_till() const; + /** @brief check whether key was/will be valid at the specified time */ + bool valid_at(uint64_t timestamp) const; + + /** @brief Get key's id */ + const pgp_key_id_t &keyid() const; + /** @brief Get key's fingerprint */ + const pgp_fingerprint_t &fp() const; + /** @brief Get key's grip */ + const pgp_key_grip_t &grip() const; + /** @brief Get primary key's fingerprint for the subkey, if it is available. + * Note: will throw if it is not available, use has_primary_fp() to check. + */ + const pgp_fingerprint_t &primary_fp() const; + /** @brief Check whether key has primary key's fingerprint */ + bool has_primary_fp() const; + /** @brief Clean primary_fp */ + void unset_primary_fp(); + /** @brief Link key with subkey via primary_fp and subkey_fps list */ + void link_subkey_fp(pgp_key_t &subkey); + /** + * @brief Add subkey fp to key's list. + * Note: this function will check for duplicates. + */ + void add_subkey_fp(const pgp_fingerprint_t &fp); + /** @brief Get the number of pgp key's subkeys. */ + size_t subkey_count() const; + /** @brief Remove subkey fingerprint from key's list. */ + void remove_subkey_fp(const pgp_fingerprint_t &fp); + /** + * @brief Get the pgp key's subkey fingerprint + * @return fingerprint or throws std::out_of_range exception + */ + const pgp_fingerprint_t & get_subkey_fp(size_t idx) const; + const std::vector<pgp_fingerprint_t> &subkey_fps() const; + + size_t rawpkt_count() const; + pgp_rawpacket_t & rawpkt(); + const pgp_rawpacket_t &rawpkt() const; + void set_rawpkt(const pgp_rawpacket_t &src); + /** @brief write secret key data to the rawpkt, optionally encrypting with password */ + bool write_sec_rawpkt(pgp_key_pkt_t & seckey, + const std::string & password, + rnp::SecurityContext &ctx); + + /** @brief Unlock a key, i.e. decrypt its secret data so it can be used for + * signing/decryption. + * Note: Key locking does not apply to unprotected keys. + * + * @param pass_provider the password provider that may be used to unlock the key + * @param op operation for which secret key should be unloacked + * @return true if the key was unlocked, false otherwise + **/ + bool unlock(const pgp_password_provider_t &provider, pgp_op_t op = PGP_OP_UNLOCK); + /** @brief Lock a key, i.e. cleanup decrypted secret data. + * Note: Key locking does not apply to unprotected keys. + * + * @param key the key + * @return true if the key was locked, false otherwise + **/ + bool lock(); + /** @brief Add protection to an unlocked key, i.e. encrypt its secret data with specified + * parameters. */ + bool protect(const rnp_key_protection_params_t &protection, + const pgp_password_provider_t & password_provider, + rnp::SecurityContext & ctx); + /** @brief Add/change protection of a key */ + bool protect(pgp_key_pkt_t & decrypted, + const rnp_key_protection_params_t &protection, + const std::string & new_password, + rnp::SecurityContext & ctx); + /** @brief Remove protection from a key, i.e. leave secret fields unencrypted */ + bool unprotect(const pgp_password_provider_t &password_provider, + rnp::SecurityContext & ctx); + + /** @brief Write key's packets to the output. */ + void write(pgp_dest_t &dst) const; + /** + * @brief Write OpenPGP key packets (including subkeys) to the specified stream + * + * @param dst stream to write packets + * @param keyring keyring, which will be searched for subkeys. Pass NULL to skip subkeys. + * @return void, but error may be checked via dst.werr + */ + void write_xfer(pgp_dest_t &dst, const rnp_key_store_t *keyring = NULL) const; + /** + * @brief Export key with subkey as it is required by Autocrypt (5-packet sequence: key, + * uid, sig, subkey, sig). + * + * @param dst stream to write packets + * @param sub subkey + * @param uid index of uid to export + * @return true on success or false otherwise + */ + bool write_autocrypt(pgp_dest_t &dst, pgp_key_t &sub, uint32_t uid); + + /** + * @brief Get the latest valid self-signature with information about the primary key for + * the specified uid (including the special cases). It could be userid certification + * or direct-key signature. + * + * @param uid uid for which latest self-signature should be returned, + * PGP_UID_NONE for direct-key signature, + * PGP_UID_PRIMARY for any primary key, + * PGP_UID_ANY for any uid. + * @return pointer to signature object or NULL if failed/not found. + */ + pgp_subsig_t *latest_selfsig(uint32_t uid); + + /** + * @brief Get the latest valid subkey binding. Should be called on subkey. + * + * @param validated set to true whether binding signature must be validated + * @return pointer to signature object or NULL if failed/not found. + */ + pgp_subsig_t *latest_binding(bool validated = true); + + /** @brief Returns true if signature is produced by the key itself. */ + bool is_signer(const pgp_subsig_t &sig) const; + + /** @brief Returns true if key is expired according to sig. */ + bool expired_with(const pgp_subsig_t &sig, uint64_t at) const; + + /** @brief Check whether signature is key's self certification. */ + bool is_self_cert(const pgp_subsig_t &sig) const; + + /** @brief Check whether signature is key's direct-key self-signature */ + bool is_direct_self(const pgp_subsig_t &sig) const; + + /** @brief Check whether signature is key's/subkey's revocation */ + bool is_revocation(const pgp_subsig_t &sig) const; + + /** @brief Check whether signature is userid revocation */ + bool is_uid_revocation(const pgp_subsig_t &sig) const; + + /** @brief Check whether signature is subkey binding */ + bool is_binding(const pgp_subsig_t &sig) const; + + /** + * @brief Validate key's signature, assuming that 'this' is a signing key. + * + * @param key key or subkey to which signature belongs. + * @param sig signature to validate. + * @param ctx Populated security context. + */ + void validate_sig(const pgp_key_t & key, + pgp_subsig_t & sig, + const rnp::SecurityContext &ctx) const noexcept; + + /** + * @brief Validate signature, assuming that 'this' is a signing key. + * + * @param sinfo populated signature info. Validation results will be stored here. + * @param hash hash, feed with all signed data except signature trailer. + * @param ctx Populated security context. + */ + void validate_sig(pgp_signature_info_t & sinfo, + rnp::Hash & hash, + const rnp::SecurityContext &ctx) const noexcept; + + /** + * @brief Validate certification. + * + * @param sinfo populated signature info. Validation results will be stored here. + * @param key key packet to which certification belongs. + * @param uid userid which is bound by certification to the key packet. + */ + void validate_cert(pgp_signature_info_t & sinfo, + const pgp_key_pkt_t & key, + const pgp_userid_pkt_t & uid, + const rnp::SecurityContext &ctx) const; + + /** + * @brief Validate subkey binding. + * + * @param sinfo populated signature info. Validation results will be stored here. + * @param subkey subkey packet. + */ + void validate_binding(pgp_signature_info_t & sinfo, + const pgp_key_t & subkey, + const rnp::SecurityContext &ctx) const; + + /** + * @brief Validate subkey revocation. + * + * @param sinfo populated signature info. Validation results will be stored here. + * @param subkey subkey packet. + */ + void validate_sub_rev(pgp_signature_info_t & sinfo, + const pgp_key_pkt_t & subkey, + const rnp::SecurityContext &ctx) const; + + /** + * @brief Validate direct-key signature. + * + * @param sinfo populated signature info. Validation results will be stored here. + */ + void validate_direct(pgp_signature_info_t &sinfo, const rnp::SecurityContext &ctx) const; + + void validate_self_signatures(const rnp::SecurityContext &ctx); + void validate_self_signatures(pgp_key_t &primary, const rnp::SecurityContext &ctx); + void validate(rnp_key_store_t &keyring); + void validate_subkey(pgp_key_t *primary, const rnp::SecurityContext &ctx); + void revalidate(rnp_key_store_t &keyring); + void mark_valid(); + /** + * @brief Fill common signature parameters, assuming that current key is a signing one. + * @param sig signature to init. + * @param hash hash algorithm to use (may be changed if it is not suitable for public key + * algorithm). + * @param creation signature's creation time. + */ + void sign_init(pgp_signature_t &sig, pgp_hash_alg_t hash, uint64_t creation) const; + /** + * @brief Calculate a certification and fill signature material. + * Note: secret key must be unlocked before calling this function. + * + * @param key key packet to sign. May be both public and secret. Could be signing key's + * packet for self-signature, or any other one for cross-key certification. + * @param uid uid to certify. + * @param sig signature, pre-populated with all of the required data, except the + * signature material. + */ + void sign_cert(const pgp_key_pkt_t & key, + const pgp_userid_pkt_t &uid, + pgp_signature_t & sig, + rnp::SecurityContext & ctx); + + /** + * @brief Calculate direct-key signature. + * Note: secret key must be unlocked before calling this function. + * + * @param key key packet to sign. May be both public and secret. + * @param sig signature, pre-populated with all of the required data, except the + * signature material. + */ + void sign_direct(const pgp_key_pkt_t & key, + pgp_signature_t & sig, + rnp::SecurityContext &ctx); + + /** + * @brief Calculate subkey or primary key binding. + * Note: this will not embed primary key binding for the signing subkey, it should + * be added by the caller. + * + * @param key subkey or primary key packet, may be both public or secret. + * @param sig signature, pre-populated with all of the required data, except the + * signature material. + */ + void sign_binding(const pgp_key_pkt_t & key, + pgp_signature_t & sig, + rnp::SecurityContext &ctx); + + /** + * @brief Calculate subkey binding. + * Note: secret key must be unlocked before calling this function. If subsign is + * true then subkey must be secret and unlocked as well so function can calculate + * primary key binding. + * + * @param sub subkey to bind to the primary key. If subsign is true then must be unlocked + * secret key. + * @param sig signature, pre-populated with all of the required data, except the + * signature material. + */ + void sign_subkey_binding(pgp_key_t & sub, + pgp_signature_t & sig, + rnp::SecurityContext &ctx, + bool subsign = false); + + /** + * @brief Generate key or subkey revocation signature. + * + * @param revoke revocation information. + * @param key key or subkey packet to revoke. + * @param sig object to store revocation signature. Will be populated in method call. + */ + void gen_revocation(const pgp_revoke_t & revoke, + pgp_hash_alg_t hash, + const pgp_key_pkt_t & key, + pgp_signature_t & sig, + rnp::SecurityContext &ctx); + + /** + * @brief Add and certify userid. + * Note: secret key must be unlocked before calling this function. + * + * @param cert certification and userid parameters. + * @param hash hash algorithm to use during signing. See sign_init() for more details. + * @param ctx security context. + * @param pubkey if non-NULL then userid and certification will be added to this key as + * well. + */ + void add_uid_cert(rnp_selfsig_cert_info_t &cert, + pgp_hash_alg_t hash, + rnp::SecurityContext & ctx, + pgp_key_t * pubkey = nullptr); + + /** + * @brief Calculate and add subkey binding signature. + * Note: must be called on the unlocked secret primary key. Calculated signature is + * added to the subkey. + * + * @param subsec secret subkey. + * @param subpub subkey's public part (so signature is added to both). + * @param binding information about subkey to put to the signature. + * @param hash hash algorithm to use (may be adjusted according to key and subkey + * algorithms) + */ + void add_sub_binding(pgp_key_t & subsec, + pgp_key_t & subpub, + const rnp_selfsig_binding_info_t &binding, + pgp_hash_alg_t hash, + rnp::SecurityContext & ctx); + + /** @brief Refresh internal fields after primary key is updated */ + bool refresh_data(const rnp::SecurityContext &ctx); + /** @brief Refresh internal fields after subkey is updated */ + bool refresh_data(pgp_key_t *primary, const rnp::SecurityContext &ctx); + /** @brief Merge primary key with the src, i.e. add all new userids/signatures/subkeys */ + bool merge(const pgp_key_t &src); + /** @brief Merge subkey with the source, i.e. add all new signatures */ + bool merge(const pgp_key_t &src, pgp_key_t *primary); +}; + +namespace rnp { +class KeyLocker { + bool lock_; + pgp_key_t &key_; + + public: + KeyLocker(pgp_key_t &key) : lock_(key.is_locked()), key_(key) + { + } + + ~KeyLocker() + { + if (lock_ && !key_.is_locked()) { + key_.lock(); + } + } +}; +}; // namespace rnp + +pgp_key_pkt_t *pgp_decrypt_seckey_pgp(const pgp_rawpacket_t &raw, + const pgp_key_pkt_t & key, + const char * password); + +pgp_key_pkt_t *pgp_decrypt_seckey(const pgp_key_t &, + const pgp_password_provider_t &, + const pgp_password_ctx_t &); + +/** + * @brief Get the signer's key for signature + * + * @param sig signature + * @param keyring keyring to search for the key. May be NULL. + * @param prov key provider to request needed key, may be NULL. + * @return pointer to the key or NULL if key is not found. + */ +pgp_key_t *pgp_sig_get_signer(const pgp_subsig_t &sig, + rnp_key_store_t * keyring, + pgp_key_provider_t *prov); + +/** + * @brief Get the key's subkey by its index + * + * @param key primary key + * @param store key store which will be searched for subkeys + * @param idx index of the subkey + * @return pointer to the subkey or NULL if subkey not found + */ +pgp_key_t *pgp_key_get_subkey(const pgp_key_t *key, rnp_key_store_t *store, size_t idx); + +pgp_key_flags_t pgp_pk_alg_capabilities(pgp_pubkey_alg_t alg); + +bool pgp_key_set_expiration(pgp_key_t * key, + pgp_key_t * signer, + uint32_t expiry, + const pgp_password_provider_t &prov, + rnp::SecurityContext & ctx); + +bool pgp_subkey_set_expiration(pgp_key_t * sub, + pgp_key_t * primsec, + pgp_key_t * secsub, + uint32_t expiry, + const pgp_password_provider_t &prov, + rnp::SecurityContext & ctx); + +/** Find a key or it's subkey, suitable for a particular operation + * + * If the key passed is suitable, it will be returned. + * Otherwise, its subkeys (if it is a primary w/subs) + * will be checked. NULL will be returned if no suitable + * key is found. + * + * @param op the operation for which the key should be suitable + * @param key the key + * @param key_provider the key provider. This will be used + * if/when subkeys are checked. + * @param no_primary set true if only subkeys must be returned + * + * @returns key or last created subkey with desired usage flag + * set or NULL if not found + */ +pgp_key_t *find_suitable_key(pgp_op_t op, + pgp_key_t * key, + pgp_key_provider_t *key_provider, + bool no_primary = false); + +/* + * Picks up hash algorithm according to domain parameters set + * in `pubkey' and user provided hash. That's mostly because DSA + * and ECDSA needs special treatment. + * + * @param hash set by the caller + * @param pubkey initialized public key + * + * @returns hash algorithm that must be use for operation (mostly + signing with secure key which corresponds to 'pubkey') + */ +pgp_hash_alg_t pgp_hash_adjust_alg_to_key(pgp_hash_alg_t hash, const pgp_key_pkt_t *pubkey); + +#endif // RNP_PACKET_KEY_H diff --git a/comm/third_party/rnp/src/lib/rnp.cpp b/comm/third_party/rnp/src/lib/rnp.cpp new file mode 100644 index 0000000000..24c46f986d --- /dev/null +++ b/comm/third_party/rnp/src/lib/rnp.cpp @@ -0,0 +1,8403 @@ +/*- + * Copyright (c) 2017-2021, Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR + * CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "crypto.h" +#include "crypto/common.h" +#include "pgp-key.h" +#include "defaults.h" +#include <assert.h> +#include <json_object.h> +#include <json.h> +#include <librekey/key_store_pgp.h> +#include <librepgp/stream-ctx.h> +#include <librepgp/stream-common.h> +#include <librepgp/stream-armor.h> +#include <librepgp/stream-parse.h> +#include <librepgp/stream-write.h> +#include <librepgp/stream-sig.h> +#include <librepgp/stream-packet.h> +#include <librepgp/stream-key.h> +#include <librepgp/stream-dump.h> +#include <rnp/rnp.h> +#include <stdarg.h> +#include <stdlib.h> +#ifdef _MSC_VER +#include "uniwin.h" +#include <inttypes.h> +#else +#include <unistd.h> +#endif +#include <string.h> +#include <sys/stat.h> +#include <stdexcept> +#include "utils.h" +#include "str-utils.h" +#include "json-utils.h" +#include "version.h" +#include "ffi-priv-types.h" +#include "file-utils.h" + +#define FFI_LOG(ffi, ...) \ + do { \ + FILE *fp = stderr; \ + if (ffi && ffi->errs) { \ + fp = ffi->errs; \ + } \ + RNP_LOG_FD(fp, __VA_ARGS__); \ + } while (0) + +static pgp_key_t *get_key_require_public(rnp_key_handle_t handle); +static pgp_key_t *get_key_prefer_public(rnp_key_handle_t handle); +static pgp_key_t *get_key_require_secret(rnp_key_handle_t handle); + +static bool locator_to_str(const pgp_key_search_t &locator, + const char ** identifier_type, + char * identifier, + size_t identifier_size); + +static bool rnp_password_cb_bounce(const pgp_password_ctx_t *ctx, + char * password, + size_t password_size, + void * userdata_void); + +static rnp_result_t rnp_dump_src_to_json(pgp_source_t *src, uint32_t flags, char **result); + +static bool +call_key_callback(rnp_ffi_t ffi, const pgp_key_search_t &search, bool secret) +{ + if (!ffi->getkeycb) { + return false; + } + char identifier[RNP_LOCATOR_MAX_SIZE]; + const char *identifier_type = NULL; + if (!locator_to_str(search, &identifier_type, identifier, sizeof(identifier))) { + return false; + } + + ffi->getkeycb(ffi, ffi->getkeycb_ctx, identifier_type, identifier, secret); + return true; +} + +static pgp_key_t * +find_key(rnp_ffi_t ffi, + const pgp_key_search_t &search, + bool secret, + bool try_key_provider, + pgp_key_t * after = NULL) +{ + pgp_key_t *key = + rnp_key_store_search(secret ? ffi->secring : ffi->pubring, &search, after); + if (!key && try_key_provider && call_key_callback(ffi, search, secret)) { + // recurse and try the store search above once more + return find_key(ffi, search, secret, false, after); + } + return key; +} + +static pgp_key_t * +ffi_key_provider(const pgp_key_request_ctx_t *ctx, void *userdata) +{ + rnp_ffi_t ffi = (rnp_ffi_t) userdata; + return find_key(ffi, ctx->search, ctx->secret, true); +} + +static void +rnp_ctx_init_ffi(rnp_ctx_t &ctx, rnp_ffi_t ffi) +{ + ctx.ctx = &ffi->context; + ctx.ealg = DEFAULT_PGP_SYMM_ALG; + ctx.aalg = PGP_AEAD_NONE; + ctx.abits = DEFAULT_AEAD_CHUNK_BITS; +} + +static const id_str_pair sig_type_map[] = {{PGP_SIG_BINARY, "binary"}, + {PGP_SIG_TEXT, "text"}, + {PGP_SIG_STANDALONE, "standalone"}, + {PGP_CERT_GENERIC, "certification (generic)"}, + {PGP_CERT_PERSONA, "certification (persona)"}, + {PGP_CERT_CASUAL, "certification (casual)"}, + {PGP_CERT_POSITIVE, "certification (positive)"}, + {PGP_SIG_SUBKEY, "subkey binding"}, + {PGP_SIG_PRIMARY, "primary key binding"}, + {PGP_SIG_DIRECT, "direct"}, + {PGP_SIG_REV_KEY, "key revocation"}, + {PGP_SIG_REV_SUBKEY, "subkey revocation"}, + {PGP_SIG_REV_CERT, "certification revocation"}, + {PGP_SIG_TIMESTAMP, "timestamp"}, + {PGP_SIG_3RD_PARTY, "third-party"}, + {0, NULL}}; + +static const id_str_pair pubkey_alg_map[] = { + {PGP_PKA_RSA, RNP_ALGNAME_RSA}, + {PGP_PKA_RSA_ENCRYPT_ONLY, RNP_ALGNAME_RSA}, + {PGP_PKA_RSA_SIGN_ONLY, RNP_ALGNAME_RSA}, + {PGP_PKA_ELGAMAL, RNP_ALGNAME_ELGAMAL}, + {PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN, RNP_ALGNAME_ELGAMAL}, + {PGP_PKA_DSA, RNP_ALGNAME_DSA}, + {PGP_PKA_ECDH, RNP_ALGNAME_ECDH}, + {PGP_PKA_ECDSA, RNP_ALGNAME_ECDSA}, + {PGP_PKA_EDDSA, RNP_ALGNAME_EDDSA}, + {PGP_PKA_SM2, RNP_ALGNAME_SM2}, + {0, NULL}}; + +static const id_str_pair symm_alg_map[] = {{PGP_SA_IDEA, RNP_ALGNAME_IDEA}, + {PGP_SA_TRIPLEDES, RNP_ALGNAME_TRIPLEDES}, + {PGP_SA_CAST5, RNP_ALGNAME_CAST5}, + {PGP_SA_BLOWFISH, RNP_ALGNAME_BLOWFISH}, + {PGP_SA_AES_128, RNP_ALGNAME_AES_128}, + {PGP_SA_AES_192, RNP_ALGNAME_AES_192}, + {PGP_SA_AES_256, RNP_ALGNAME_AES_256}, + {PGP_SA_TWOFISH, RNP_ALGNAME_TWOFISH}, + {PGP_SA_CAMELLIA_128, RNP_ALGNAME_CAMELLIA_128}, + {PGP_SA_CAMELLIA_192, RNP_ALGNAME_CAMELLIA_192}, + {PGP_SA_CAMELLIA_256, RNP_ALGNAME_CAMELLIA_256}, + {PGP_SA_SM4, RNP_ALGNAME_SM4}, + {0, NULL}}; + +static const id_str_pair aead_alg_map[] = { + {PGP_AEAD_NONE, "None"}, {PGP_AEAD_EAX, "EAX"}, {PGP_AEAD_OCB, "OCB"}, {0, NULL}}; + +static const id_str_pair cipher_mode_map[] = {{PGP_CIPHER_MODE_CFB, "CFB"}, + {PGP_CIPHER_MODE_CBC, "CBC"}, + {PGP_CIPHER_MODE_OCB, "OCB"}, + {0, NULL}}; + +static const id_str_pair compress_alg_map[] = {{PGP_C_NONE, "Uncompressed"}, + {PGP_C_ZIP, "ZIP"}, + {PGP_C_ZLIB, "ZLIB"}, + {PGP_C_BZIP2, "BZip2"}, + {0, NULL}}; + +static const id_str_pair hash_alg_map[] = {{PGP_HASH_MD5, RNP_ALGNAME_MD5}, + {PGP_HASH_SHA1, RNP_ALGNAME_SHA1}, + {PGP_HASH_RIPEMD, RNP_ALGNAME_RIPEMD160}, + {PGP_HASH_SHA256, RNP_ALGNAME_SHA256}, + {PGP_HASH_SHA384, RNP_ALGNAME_SHA384}, + {PGP_HASH_SHA512, RNP_ALGNAME_SHA512}, + {PGP_HASH_SHA224, RNP_ALGNAME_SHA224}, + {PGP_HASH_SHA3_256, RNP_ALGNAME_SHA3_256}, + {PGP_HASH_SHA3_512, RNP_ALGNAME_SHA3_512}, + {PGP_HASH_SM3, RNP_ALGNAME_SM3}, + {0, NULL}}; + +static const id_str_pair s2k_type_map[] = { + {PGP_S2KS_SIMPLE, "Simple"}, + {PGP_S2KS_SALTED, "Salted"}, + {PGP_S2KS_ITERATED_AND_SALTED, "Iterated and salted"}, + {0, NULL}}; + +static const id_str_pair key_usage_map[] = { + {PGP_KF_SIGN, "sign"}, + {PGP_KF_CERTIFY, "certify"}, + {PGP_KF_ENCRYPT, "encrypt"}, + {PGP_KF_AUTH, "authenticate"}, + {0, NULL}, +}; + +static const id_str_pair key_flags_map[] = { + {PGP_KF_SPLIT, "split"}, + {PGP_KF_SHARED, "shared"}, + {0, NULL}, +}; + +static const id_str_pair identifier_type_map[] = {{PGP_KEY_SEARCH_USERID, "userid"}, + {PGP_KEY_SEARCH_KEYID, "keyid"}, + {PGP_KEY_SEARCH_FINGERPRINT, "fingerprint"}, + {PGP_KEY_SEARCH_GRIP, "grip"}, + {0, NULL}}; + +static const id_str_pair key_server_prefs_map[] = {{PGP_KEY_SERVER_NO_MODIFY, "no-modify"}, + {0, NULL}}; + +static const id_str_pair armor_type_map[] = {{PGP_ARMORED_MESSAGE, "message"}, + {PGP_ARMORED_PUBLIC_KEY, "public key"}, + {PGP_ARMORED_SECRET_KEY, "secret key"}, + {PGP_ARMORED_SIGNATURE, "signature"}, + {PGP_ARMORED_CLEARTEXT, "cleartext"}, + {0, NULL}}; + +static const id_str_pair key_import_status_map[] = { + {PGP_KEY_IMPORT_STATUS_UNKNOWN, "unknown"}, + {PGP_KEY_IMPORT_STATUS_UNCHANGED, "unchanged"}, + {PGP_KEY_IMPORT_STATUS_UPDATED, "updated"}, + {PGP_KEY_IMPORT_STATUS_NEW, "new"}, + {0, NULL}}; + +static const id_str_pair sig_import_status_map[] = { + {PGP_SIG_IMPORT_STATUS_UNKNOWN, "unknown"}, + {PGP_SIG_IMPORT_STATUS_UNKNOWN_KEY, "unknown key"}, + {PGP_SIG_IMPORT_STATUS_UNCHANGED, "unchanged"}, + {PGP_SIG_IMPORT_STATUS_NEW, "new"}, + {0, NULL}}; + +static const id_str_pair revocation_code_map[] = { + {PGP_REVOCATION_NO_REASON, "no"}, + {PGP_REVOCATION_SUPERSEDED, "superseded"}, + {PGP_REVOCATION_COMPROMISED, "compromised"}, + {PGP_REVOCATION_RETIRED, "retired"}, + {PGP_REVOCATION_NO_LONGER_VALID, "no longer valid"}, + {0, NULL}}; + +static bool +symm_alg_supported(int alg) +{ + return pgp_is_sa_supported(alg, true); +} + +static bool +hash_alg_supported(int alg) +{ + switch (alg) { + case PGP_HASH_MD5: + case PGP_HASH_SHA1: +#if defined(ENABLE_RIPEMD160) + case PGP_HASH_RIPEMD: +#endif + case PGP_HASH_SHA256: + case PGP_HASH_SHA384: + case PGP_HASH_SHA512: + case PGP_HASH_SHA224: + case PGP_HASH_SHA3_256: + case PGP_HASH_SHA3_512: +#if defined(ENABLE_SM2) + case PGP_HASH_SM3: +#endif + return true; + default: + return false; + } +} + +static bool +aead_alg_supported(int alg) +{ + switch (alg) { + case PGP_AEAD_NONE: +#if defined(ENABLE_AEAD) +#if !defined(CRYPTO_BACKEND_OPENSSL) + case PGP_AEAD_EAX: +#endif + case PGP_AEAD_OCB: +#endif + return true; + default: + return false; + } +} + +static bool +pub_alg_supported(int alg) +{ + switch (alg) { + case PGP_PKA_RSA: + case PGP_PKA_ELGAMAL: + case PGP_PKA_DSA: + case PGP_PKA_ECDH: + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: +#if defined(ENABLE_SM2) + case PGP_PKA_SM2: +#endif + return true; + default: + return false; + } +} + +static bool +z_alg_supported(int alg) +{ + switch (alg) { + case PGP_C_NONE: + case PGP_C_ZIP: + case PGP_C_ZLIB: + case PGP_C_BZIP2: + return true; + default: + return false; + } +} + +static bool +curve_str_to_type(const char *str, pgp_curve_t *value) +{ + *value = find_curve_by_name(str); + return curve_supported(*value); +} + +static bool +curve_type_to_str(pgp_curve_t type, const char **str) +{ + const ec_curve_desc_t *desc = get_curve_desc(type); + if (!desc) { + return false; + } + *str = desc->pgp_name; + return true; +} + +static bool +str_to_cipher(const char *str, pgp_symm_alg_t *cipher) +{ + auto alg = id_str_pair::lookup(symm_alg_map, str, PGP_SA_UNKNOWN); + if (!symm_alg_supported(alg)) { + return false; + } + *cipher = static_cast<pgp_symm_alg_t>(alg); + return true; +} + +static bool +str_to_hash_alg(const char *str, pgp_hash_alg_t *hash_alg) +{ + auto alg = id_str_pair::lookup(hash_alg_map, str, PGP_HASH_UNKNOWN); + if (!hash_alg_supported(alg)) { + return false; + } + *hash_alg = static_cast<pgp_hash_alg_t>(alg); + return true; +} + +static bool +str_to_aead_alg(const char *str, pgp_aead_alg_t *aead_alg) +{ + auto alg = id_str_pair::lookup(aead_alg_map, str, PGP_AEAD_UNKNOWN); + if (!aead_alg_supported(alg)) { + return false; + } + *aead_alg = static_cast<pgp_aead_alg_t>(alg); + return true; +} + +static bool +str_to_compression_alg(const char *str, pgp_compression_type_t *zalg) +{ + auto alg = id_str_pair::lookup(compress_alg_map, str, PGP_C_UNKNOWN); + if (!z_alg_supported(alg)) { + return false; + } + *zalg = static_cast<pgp_compression_type_t>(alg); + return true; +} + +static bool +str_to_revocation_type(const char *str, pgp_revocation_type_t *code) +{ + pgp_revocation_type_t rev = static_cast<pgp_revocation_type_t>( + id_str_pair::lookup(revocation_code_map, str, PGP_REVOCATION_NO_REASON)); + if ((rev == PGP_REVOCATION_NO_REASON) && !rnp::str_case_eq(str, "no")) { + return false; + } + *code = rev; + return true; +} + +static bool +str_to_cipher_mode(const char *str, pgp_cipher_mode_t *mode) +{ + pgp_cipher_mode_t c_mode = static_cast<pgp_cipher_mode_t>( + id_str_pair::lookup(cipher_mode_map, str, PGP_CIPHER_MODE_NONE)); + if (c_mode == PGP_CIPHER_MODE_NONE) { + return false; + } + + *mode = c_mode; + return true; +} + +static bool +str_to_pubkey_alg(const char *str, pgp_pubkey_alg_t *pub_alg) +{ + auto alg = id_str_pair::lookup(pubkey_alg_map, str, PGP_PKA_NOTHING); + if (!pub_alg_supported(alg)) { + return false; + } + *pub_alg = static_cast<pgp_pubkey_alg_t>(alg); + return true; +} + +static bool +str_to_key_flag(const char *str, uint8_t *flag) +{ + uint8_t _flag = id_str_pair::lookup(key_usage_map, str); + if (!_flag) { + return false; + } + *flag = _flag; + return true; +} + +static bool +parse_ks_format(pgp_key_store_format_t *key_store_format, const char *format) +{ + if (!strcmp(format, RNP_KEYSTORE_GPG)) { + *key_store_format = PGP_KEY_STORE_GPG; + } else if (!strcmp(format, RNP_KEYSTORE_KBX)) { + *key_store_format = PGP_KEY_STORE_KBX; + } else if (!strcmp(format, RNP_KEYSTORE_G10)) { + *key_store_format = PGP_KEY_STORE_G10; + } else { + return false; + } + return true; +} + +static rnp_result_t +hex_encode_value(const uint8_t * value, + size_t len, + char ** res, + rnp::hex_format_t format = rnp::HEX_UPPERCASE) +{ + size_t hex_len = len * 2 + 1; + *res = (char *) malloc(hex_len); + if (!*res) { + return RNP_ERROR_OUT_OF_MEMORY; + } + if (!rnp::hex_encode(value, len, *res, hex_len, format)) { + free(*res); + *res = NULL; + return RNP_ERROR_GENERIC; + } + return RNP_SUCCESS; +} + +static rnp_result_t +get_map_value(const id_str_pair *map, int val, char **res) +{ + const char *str = id_str_pair::lookup(map, val, NULL); + if (!str) { + return RNP_ERROR_BAD_PARAMETERS; + } + char *strcp = strdup(str); + if (!strcp) { + return RNP_ERROR_OUT_OF_MEMORY; + } + *res = strcp; + return RNP_SUCCESS; +} + +static rnp_result_t +ret_str_value(const char *str, char **res) +{ + if (!str) { + return RNP_ERROR_BAD_PARAMETERS; + } + char *strcp = strdup(str); + if (!strcp) { + return RNP_ERROR_OUT_OF_MEMORY; + } + *res = strcp; + return RNP_SUCCESS; +} + +static uint32_t +ffi_exception(FILE *fp, const char *func, const char *msg, uint32_t ret = RNP_ERROR_GENERIC) +{ + if (rnp_log_switch()) { + fprintf( + fp, "[%s()] Error 0x%08X (%s): %s\n", func, ret, rnp_result_to_string(ret), msg); + } + return ret; +} + +#define FFI_GUARD_FP(fp) \ + catch (rnp::rnp_exception & e) \ + { \ + return ffi_exception((fp), __func__, e.what(), e.code()); \ + } \ + catch (std::bad_alloc &) \ + { \ + return ffi_exception((fp), __func__, "bad_alloc", RNP_ERROR_OUT_OF_MEMORY); \ + } \ + catch (std::exception & e) \ + { \ + return ffi_exception((fp), __func__, e.what()); \ + } \ + catch (...) \ + { \ + return ffi_exception((fp), __func__, "unknown exception"); \ + } + +#define FFI_GUARD FFI_GUARD_FP((stderr)) + +rnp_ffi_st::rnp_ffi_st(pgp_key_store_format_t pub_fmt, pgp_key_store_format_t sec_fmt) +{ + errs = stderr; + pubring = new rnp_key_store_t(pub_fmt, "", context); + secring = new rnp_key_store_t(sec_fmt, "", context); + getkeycb = NULL; + getkeycb_ctx = NULL; + getpasscb = NULL; + getpasscb_ctx = NULL; + key_provider.callback = ffi_key_provider; + key_provider.userdata = this; + pass_provider.callback = rnp_password_cb_bounce; + pass_provider.userdata = this; +} + +rnp::RNG & +rnp_ffi_st::rng() noexcept +{ + return context.rng; +} + +rnp::SecurityProfile & +rnp_ffi_st::profile() noexcept +{ + return context.profile; +} + +rnp_result_t +rnp_ffi_create(rnp_ffi_t *ffi, const char *pub_format, const char *sec_format) +try { + // checks + if (!ffi || !pub_format || !sec_format) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_key_store_format_t pub_ks_format = PGP_KEY_STORE_UNKNOWN; + pgp_key_store_format_t sec_ks_format = PGP_KEY_STORE_UNKNOWN; + if (!parse_ks_format(&pub_ks_format, pub_format) || + !parse_ks_format(&sec_ks_format, sec_format)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + struct rnp_ffi_st *ob = new rnp_ffi_st(pub_ks_format, sec_ks_format); + *ffi = ob; + return RNP_SUCCESS; +} +FFI_GUARD + +static bool +is_std_file(FILE *fp) +{ + return fp == stdout || fp == stderr; +} + +static void +close_io_file(FILE **fp) +{ + if (*fp && !is_std_file(*fp)) { + fclose(*fp); + } + *fp = NULL; +} + +rnp_ffi_st::~rnp_ffi_st() +{ + close_io_file(&errs); + delete pubring; + delete secring; +} + +rnp_result_t +rnp_ffi_destroy(rnp_ffi_t ffi) +try { + if (ffi) { + delete ffi; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_ffi_set_log_fd(rnp_ffi_t ffi, int fd) +try { + // checks + if (!ffi) { + return RNP_ERROR_NULL_POINTER; + } + + // open + FILE *errs = rnp_fdopen(fd, "a"); + if (!errs) { + return RNP_ERROR_ACCESS; + } + // close previous streams and replace them + close_io_file(&ffi->errs); + ffi->errs = errs; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_ffi_set_key_provider(rnp_ffi_t ffi, rnp_get_key_cb getkeycb, void *getkeycb_ctx) +try { + if (!ffi) { + return RNP_ERROR_NULL_POINTER; + } + ffi->getkeycb = getkeycb; + ffi->getkeycb_ctx = getkeycb_ctx; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_ffi_set_pass_provider(rnp_ffi_t ffi, rnp_password_cb getpasscb, void *getpasscb_ctx) +try { + if (!ffi) { + return RNP_ERROR_NULL_POINTER; + } + ffi->getpasscb = getpasscb; + ffi->getpasscb_ctx = getpasscb_ctx; + return RNP_SUCCESS; +} +FFI_GUARD + +static const char * +operation_description(uint8_t op) +{ + switch (op) { + case PGP_OP_ADD_SUBKEY: + return "add subkey"; + case PGP_OP_ADD_USERID: + return "add userid"; + case PGP_OP_SIGN: + return "sign"; + case PGP_OP_DECRYPT: + return "decrypt"; + case PGP_OP_UNLOCK: + return "unlock"; + case PGP_OP_PROTECT: + return "protect"; + case PGP_OP_UNPROTECT: + return "unprotect"; + case PGP_OP_DECRYPT_SYM: + return "decrypt (symmetric)"; + case PGP_OP_ENCRYPT_SYM: + return "encrypt (symmetric)"; + default: + return "unknown"; + } +} + +static bool +rnp_password_cb_bounce(const pgp_password_ctx_t *ctx, + char * password, + size_t password_size, + void * userdata_void) +{ + rnp_ffi_t ffi = (rnp_ffi_t) userdata_void; + + if (!ffi || !ffi->getpasscb) { + return false; + } + + struct rnp_key_handle_st key = {}; + key.ffi = ffi; + key.sec = (pgp_key_t *) ctx->key; + return ffi->getpasscb(ffi, + ffi->getpasscb_ctx, + ctx->key ? &key : NULL, + operation_description(ctx->op), + password, + password_size); +} + +const char * +rnp_result_to_string(rnp_result_t result) +{ + switch (result) { + case RNP_SUCCESS: + return "Success"; + + case RNP_ERROR_GENERIC: + return "Unknown error"; + case RNP_ERROR_BAD_FORMAT: + return "Bad format"; + case RNP_ERROR_BAD_PARAMETERS: + return "Bad parameters"; + case RNP_ERROR_NOT_IMPLEMENTED: + return "Not implemented"; + case RNP_ERROR_NOT_SUPPORTED: + return "Not supported"; + case RNP_ERROR_OUT_OF_MEMORY: + return "Out of memory"; + case RNP_ERROR_SHORT_BUFFER: + return "Buffer too short"; + case RNP_ERROR_NULL_POINTER: + return "Null pointer"; + + case RNP_ERROR_ACCESS: + return "Error accessing file"; + case RNP_ERROR_READ: + return "Error reading file"; + case RNP_ERROR_WRITE: + return "Error writing file"; + + case RNP_ERROR_BAD_STATE: + return "Bad state"; + case RNP_ERROR_MAC_INVALID: + return "Invalid MAC"; + case RNP_ERROR_SIGNATURE_INVALID: + return "Invalid signature"; + case RNP_ERROR_KEY_GENERATION: + return "Error during key generation"; + case RNP_ERROR_BAD_PASSWORD: + return "Bad password"; + case RNP_ERROR_KEY_NOT_FOUND: + return "Key not found"; + case RNP_ERROR_NO_SUITABLE_KEY: + return "No suitable key"; + case RNP_ERROR_DECRYPT_FAILED: + return "Decryption failed"; + case RNP_ERROR_RNG: + return "Failure of random number generator"; + case RNP_ERROR_SIGNING_FAILED: + return "Signing failed"; + case RNP_ERROR_NO_SIGNATURES_FOUND: + return "No signatures found cannot verify"; + + case RNP_ERROR_SIGNATURE_EXPIRED: + return "Expired signature"; + case RNP_ERROR_VERIFICATION_FAILED: + return "Signature verification failed cannot verify"; + case RNP_ERROR_SIGNATURE_UNKNOWN: + return "Unknown signature"; + + case RNP_ERROR_NOT_ENOUGH_DATA: + return "Not enough data"; + case RNP_ERROR_UNKNOWN_TAG: + return "Unknown tag"; + case RNP_ERROR_PACKET_NOT_CONSUMED: + return "Packet not consumed"; + case RNP_ERROR_NO_USERID: + return "No userid"; + case RNP_ERROR_EOF: + return "EOF detected"; + } + + return "Unsupported error code"; +} + +const char * +rnp_version_string() +{ + return RNP_VERSION_STRING; +} + +const char * +rnp_version_string_full() +{ + return RNP_VERSION_STRING_FULL; +} + +uint32_t +rnp_version() +{ + return RNP_VERSION_CODE; +} + +uint32_t +rnp_version_for(uint32_t major, uint32_t minor, uint32_t patch) +{ + if (major > RNP_VERSION_COMPONENT_MASK || minor > RNP_VERSION_COMPONENT_MASK || + patch > RNP_VERSION_COMPONENT_MASK) { + RNP_LOG("invalid version, out of range: %d.%d.%d", major, minor, patch); + return 0; + } + return RNP_VERSION_CODE_FOR(major, minor, patch); +} + +uint32_t +rnp_version_major(uint32_t version) +{ + return (version >> RNP_VERSION_MAJOR_SHIFT) & RNP_VERSION_COMPONENT_MASK; +} + +uint32_t +rnp_version_minor(uint32_t version) +{ + return (version >> RNP_VERSION_MINOR_SHIFT) & RNP_VERSION_COMPONENT_MASK; +} + +uint32_t +rnp_version_patch(uint32_t version) +{ + return (version >> RNP_VERSION_PATCH_SHIFT) & RNP_VERSION_COMPONENT_MASK; +} + +uint64_t +rnp_version_commit_timestamp() +{ + return RNP_VERSION_COMMIT_TIMESTAMP; +} + +#ifndef RNP_NO_DEPRECATED +rnp_result_t +rnp_enable_debug(const char *file) +try { + return RNP_SUCCESS; +} +FFI_GUARD +#endif + +#ifndef RNP_NO_DEPRECATED +rnp_result_t +rnp_disable_debug() +try { + return RNP_SUCCESS; +} +FFI_GUARD +#endif + +rnp_result_t +rnp_get_default_homedir(char **homedir) +try { + // checks + if (!homedir) { + return RNP_ERROR_NULL_POINTER; + } + + // get the users home dir + auto home = rnp::path::HOME(".rnp"); + if (home.empty()) { + return RNP_ERROR_NOT_SUPPORTED; + } + *homedir = strdup(home.c_str()); + if (!*homedir) { + return RNP_ERROR_OUT_OF_MEMORY; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_detect_homedir_info( + const char *homedir, char **pub_format, char **pub_path, char **sec_format, char **sec_path) +try { + // checks + if (!homedir || !pub_format || !pub_path || !sec_format || !sec_path) { + return RNP_ERROR_NULL_POINTER; + } + + // we only support the common cases of GPG+GPG or GPG+G10, we don't + // support unused combinations like KBX+KBX + + *pub_format = NULL; + *pub_path = NULL; + *sec_format = NULL; + *sec_path = NULL; + + // check for pubring.kbx file and for private-keys-v1.d dir + std::string pub = rnp::path::append(homedir, "pubring.kbx"); + std::string sec = rnp::path::append(homedir, "private-keys-v1.d"); + if (rnp::path::exists(pub) && rnp::path::exists(sec, true)) { + *pub_format = strdup("KBX"); + *sec_format = strdup("G10"); + } else { + // check for pubring.gpg and secring.gpg + pub = rnp::path::append(homedir, "pubring.gpg"); + sec = rnp::path::append(homedir, "secring.gpg"); + if (rnp::path::exists(pub) && rnp::path::exists(sec)) { + *pub_format = strdup("GPG"); + *sec_format = strdup("GPG"); + } else { + // we leave the *formats as NULL if we were not able to determine the format + // (but no error occurred) + return RNP_SUCCESS; + } + } + + // set pathes + *pub_path = strdup(pub.c_str()); + *sec_path = strdup(sec.c_str()); + + // check for allocation failures + if (*pub_format && *pub_path && *sec_format && *sec_path) { + return RNP_SUCCESS; + } + + free(*pub_format); + *pub_format = NULL; + free(*pub_path); + *pub_path = NULL; + free(*sec_format); + *sec_format = NULL; + free(*sec_path); + *sec_path = NULL; + return RNP_ERROR_OUT_OF_MEMORY; +} +FFI_GUARD + +rnp_result_t +rnp_detect_key_format(const uint8_t buf[], size_t buf_len, char **format) +try { + rnp_result_t ret = RNP_ERROR_GENERIC; + + // checks + if (!buf || !format) { + return RNP_ERROR_NULL_POINTER; + } + if (!buf_len) { + return RNP_ERROR_SHORT_BUFFER; + } + + *format = NULL; + // ordered from most reliable detection to least + const char *guess = NULL; + if (buf_len >= 12 && memcmp(buf + 8, "KBXf", 4) == 0) { + // KBX has a magic KBXf marker + guess = "KBX"; + } else if (buf_len >= 5 && memcmp(buf, "-----", 5) == 0) { + // likely armored GPG + guess = "GPG"; + } else if (buf[0] == '(') { + // G10 is s-exprs and should start end end with parentheses + guess = "G10"; + } else if (buf[0] & PGP_PTAG_ALWAYS_SET) { + // this is harder to reliably determine, but could likely be improved + guess = "GPG"; + } + if (guess) { + *format = strdup(guess); + if (!*format) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + } + + // success + ret = RNP_SUCCESS; +done: + return ret; +} +FFI_GUARD + +rnp_result_t +rnp_calculate_iterations(const char *hash, size_t msec, size_t *iterations) +try { + if (!hash || !iterations) { + return RNP_ERROR_NULL_POINTER; + } + pgp_hash_alg_t halg = PGP_HASH_UNKNOWN; + if (!str_to_hash_alg(hash, &halg)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + *iterations = pgp_s2k_compute_iters(halg, msec, 0); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_supports_feature(const char *type, const char *name, bool *supported) +try { + if (!type || !name || !supported) { + return RNP_ERROR_NULL_POINTER; + } + if (rnp::str_case_eq(type, RNP_FEATURE_SYMM_ALG)) { + pgp_symm_alg_t alg = PGP_SA_UNKNOWN; + *supported = str_to_cipher(name, &alg); + } else if (rnp::str_case_eq(type, RNP_FEATURE_AEAD_ALG)) { + pgp_aead_alg_t alg = PGP_AEAD_UNKNOWN; + *supported = str_to_aead_alg(name, &alg); + } else if (rnp::str_case_eq(type, RNP_FEATURE_PROT_MODE)) { + // for now we support only CFB for key encryption + *supported = rnp::str_case_eq(name, "CFB"); + } else if (rnp::str_case_eq(type, RNP_FEATURE_PK_ALG)) { + pgp_pubkey_alg_t alg = PGP_PKA_NOTHING; + *supported = str_to_pubkey_alg(name, &alg); + } else if (rnp::str_case_eq(type, RNP_FEATURE_HASH_ALG)) { + pgp_hash_alg_t alg = PGP_HASH_UNKNOWN; + *supported = str_to_hash_alg(name, &alg); + } else if (rnp::str_case_eq(type, RNP_FEATURE_COMP_ALG)) { + pgp_compression_type_t alg = PGP_C_UNKNOWN; + *supported = str_to_compression_alg(name, &alg); + } else if (rnp::str_case_eq(type, RNP_FEATURE_CURVE)) { + pgp_curve_t curve = PGP_CURVE_UNKNOWN; + *supported = curve_str_to_type(name, &curve); + } else { + return RNP_ERROR_BAD_PARAMETERS; + } + return RNP_SUCCESS; +} +FFI_GUARD + +static rnp_result_t +json_array_add_id_str(json_object *arr, const id_str_pair *map, bool (*check)(int)) +{ + while (map->str) { + if (check(map->id) && !array_add_element_json(arr, json_object_new_string(map->str))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + map++; + } + return RNP_SUCCESS; +} + +rnp_result_t +rnp_supported_features(const char *type, char **result) +try { + if (!type || !result) { + return RNP_ERROR_NULL_POINTER; + } + + json_object *features = json_object_new_array(); + if (!features) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + rnp_result_t ret = RNP_ERROR_BAD_PARAMETERS; + + if (rnp::str_case_eq(type, RNP_FEATURE_SYMM_ALG)) { + ret = json_array_add_id_str(features, symm_alg_map, symm_alg_supported); + } else if (rnp::str_case_eq(type, RNP_FEATURE_AEAD_ALG)) { + ret = json_array_add_id_str(features, aead_alg_map, aead_alg_supported); + } else if (rnp::str_case_eq(type, RNP_FEATURE_PROT_MODE)) { + ret = json_array_add_id_str( + features, cipher_mode_map, [](int alg) { return alg == PGP_CIPHER_MODE_CFB; }); + } else if (rnp::str_case_eq(type, RNP_FEATURE_PK_ALG)) { + ret = json_array_add_id_str(features, pubkey_alg_map, pub_alg_supported); + } else if (rnp::str_case_eq(type, RNP_FEATURE_HASH_ALG)) { + ret = json_array_add_id_str(features, hash_alg_map, hash_alg_supported); + } else if (rnp::str_case_eq(type, RNP_FEATURE_COMP_ALG)) { + ret = json_array_add_id_str(features, compress_alg_map, z_alg_supported); + } else if (rnp::str_case_eq(type, RNP_FEATURE_CURVE)) { + for (pgp_curve_t curve = PGP_CURVE_NIST_P_256; curve < PGP_CURVE_MAX; + curve = (pgp_curve_t)(curve + 1)) { + const ec_curve_desc_t *desc = get_curve_desc(curve); + if (!desc) { + ret = RNP_ERROR_BAD_STATE; + goto done; + } + if (!desc->supported) { + continue; + } + if (!array_add_element_json(features, json_object_new_string(desc->pgp_name))) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + } + ret = RNP_SUCCESS; + } + + if (ret) { + goto done; + } + + *result = (char *) json_object_to_json_string_ext(features, JSON_C_TO_STRING_PRETTY); + if (!*result) { + ret = RNP_ERROR_BAD_STATE; + goto done; + } + *result = strdup(*result); + if (!*result) { + ret = RNP_ERROR_OUT_OF_MEMORY; + } +done: + json_object_put(features); + return ret; +} +FFI_GUARD + +static bool +get_feature_sec_value( + rnp_ffi_t ffi, const char *stype, const char *sname, rnp::FeatureType &type, int &value) +{ + /* check type */ + if (!rnp::str_case_eq(stype, RNP_FEATURE_HASH_ALG)) { + FFI_LOG(ffi, "Unsupported feature type: %s", stype); + return false; + } + type = rnp::FeatureType::Hash; + /* check feature name */ + pgp_hash_alg_t alg = PGP_HASH_UNKNOWN; + if (sname && !str_to_hash_alg(sname, &alg)) { + FFI_LOG(ffi, "Unknown hash algorithm: %s", sname); + return false; + } + value = alg; + return true; +} + +static bool +get_feature_sec_level(rnp_ffi_t ffi, uint32_t flevel, rnp::SecurityLevel &level) +{ + switch (flevel) { + case RNP_SECURITY_PROHIBITED: + level = rnp::SecurityLevel::Disabled; + break; + case RNP_SECURITY_INSECURE: + level = rnp::SecurityLevel::Insecure; + break; + case RNP_SECURITY_DEFAULT: + level = rnp::SecurityLevel::Default; + break; + default: + FFI_LOG(ffi, "Invalid security level : %" PRIu32, flevel); + return false; + } + return true; +} + +static bool +extract_flag(uint32_t &flags, uint32_t flag) +{ + bool res = flags & flag; + flags &= ~flag; + return res; +} + +rnp_result_t +rnp_add_security_rule(rnp_ffi_t ffi, + const char *type, + const char *name, + uint32_t flags, + uint64_t from, + uint32_t level) +try { + if (!ffi || !type || !name) { + return RNP_ERROR_NULL_POINTER; + } + /* convert values */ + rnp::FeatureType ftype; + int fvalue; + rnp::SecurityLevel sec_level; + if (!get_feature_sec_value(ffi, type, name, ftype, fvalue) || + !get_feature_sec_level(ffi, level, sec_level)) { + return RNP_ERROR_BAD_PARAMETERS; + } + /* check flags */ + bool rule_override = extract_flag(flags, RNP_SECURITY_OVERRIDE); + bool verify_key = extract_flag(flags, RNP_SECURITY_VERIFY_KEY); + bool verify_data = extract_flag(flags, RNP_SECURITY_VERIFY_DATA); + if (flags) { + FFI_LOG(ffi, "Unknown flags: %" PRIu32, flags); + return RNP_ERROR_BAD_PARAMETERS; + } + /* add rule */ + rnp::SecurityRule newrule(ftype, fvalue, sec_level, from); + newrule.override = rule_override; + /* Add rule for any action */ + if (!verify_key && !verify_data) { + ffi->profile().add_rule(newrule); + return RNP_SUCCESS; + } + /* Add rule for each specified key usage */ + if (verify_key) { + newrule.action = rnp::SecurityAction::VerifyKey; + ffi->profile().add_rule(newrule); + } + if (verify_data) { + newrule.action = rnp::SecurityAction::VerifyData; + ffi->profile().add_rule(newrule); + } + return RNP_SUCCESS; +} +FFI_GUARD + +static rnp::SecurityAction +get_security_action(uint32_t flags) +{ + if (flags & RNP_SECURITY_VERIFY_KEY) { + return rnp::SecurityAction::VerifyKey; + } + if (flags & RNP_SECURITY_VERIFY_DATA) { + return rnp::SecurityAction::VerifyData; + } + return rnp::SecurityAction::Any; +} + +rnp_result_t +rnp_get_security_rule(rnp_ffi_t ffi, + const char *type, + const char *name, + uint64_t time, + uint32_t * flags, + uint64_t * from, + uint32_t * level) +try { + if (!ffi || !type || !name || !level) { + return RNP_ERROR_NULL_POINTER; + } + /* convert values */ + rnp::FeatureType ftype; + int fvalue; + if (!get_feature_sec_value(ffi, type, name, ftype, fvalue)) { + return RNP_ERROR_BAD_PARAMETERS; + } + /* init default rule */ + rnp::SecurityRule rule(ftype, fvalue, ffi->profile().def_level()); + /* Check whether limited usage is requested */ + auto action = get_security_action(flags ? *flags : 0); + /* check whether rule exists */ + if (ffi->profile().has_rule(ftype, fvalue, time, action)) { + rule = ffi->profile().get_rule(ftype, fvalue, time, action); + } + /* fill the results */ + if (flags) { + *flags = rule.override ? RNP_SECURITY_OVERRIDE : 0; + switch (rule.action) { + case rnp::SecurityAction::VerifyKey: + *flags |= RNP_SECURITY_VERIFY_KEY; + break; + case rnp::SecurityAction::VerifyData: + *flags |= RNP_SECURITY_VERIFY_DATA; + break; + default: + break; + } + } + if (from) { + *from = rule.from; + } + switch (rule.level) { + case rnp::SecurityLevel::Disabled: + *level = RNP_SECURITY_PROHIBITED; + break; + case rnp::SecurityLevel::Insecure: + *level = RNP_SECURITY_INSECURE; + break; + case rnp::SecurityLevel::Default: + *level = RNP_SECURITY_DEFAULT; + break; + default: + FFI_LOG(ffi, "Invalid security level."); + return RNP_ERROR_BAD_STATE; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_remove_security_rule(rnp_ffi_t ffi, + const char *type, + const char *name, + uint32_t level, + uint32_t flags, + uint64_t from, + size_t * removed) +try { + if (!ffi) { + return RNP_ERROR_NULL_POINTER; + } + /* check flags */ + bool remove_all = extract_flag(flags, RNP_SECURITY_REMOVE_ALL); + bool rule_override = extract_flag(flags, RNP_SECURITY_OVERRIDE); + rnp::SecurityAction action = get_security_action(flags); + extract_flag(flags, RNP_SECURITY_VERIFY_DATA | RNP_SECURITY_VERIFY_KEY); + if (flags) { + FFI_LOG(ffi, "Unknown flags: %" PRIu32, flags); + return RNP_ERROR_BAD_PARAMETERS; + } + /* remove all rules */ + size_t rules = ffi->profile().size(); + if (!type) { + ffi->profile().clear_rules(); + goto success; + } + rnp::FeatureType ftype; + int fvalue; + rnp::SecurityLevel flevel; + if (!get_feature_sec_value(ffi, type, name, ftype, fvalue) || + !get_feature_sec_level(ffi, level, flevel)) { + return RNP_ERROR_BAD_PARAMETERS; + } + /* remove all rules for the specified type */ + if (!name) { + ffi->profile().clear_rules(ftype); + goto success; + } + if (remove_all) { + /* remove all rules for the specified type and name */ + ffi->profile().clear_rules(ftype, fvalue); + } else { + /* remove specific rule */ + rnp::SecurityRule rule(ftype, fvalue, flevel, from, action); + rule.override = rule_override; + ffi->profile().del_rule(rule); + } +success: + if (removed) { + *removed = rules - ffi->profile().size(); + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_request_password(rnp_ffi_t ffi, rnp_key_handle_t key, const char *context, char **password) +try { + if (!ffi || !password || !ffi->getpasscb) { + return RNP_ERROR_NULL_POINTER; + } + + rnp::secure_vector<char> pass(MAX_PASSWORD_LENGTH, '\0'); + bool req_res = + ffi->getpasscb(ffi, ffi->getpasscb_ctx, key, context, pass.data(), pass.size()); + if (!req_res) { + return RNP_ERROR_GENERIC; + } + size_t pass_len = strlen(pass.data()) + 1; + *password = (char *) malloc(pass_len); + if (!*password) { + return RNP_ERROR_OUT_OF_MEMORY; + } + memcpy(*password, pass.data(), pass_len); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_set_timestamp(rnp_ffi_t ffi, uint64_t time) +try { + if (!ffi) { + return RNP_ERROR_NULL_POINTER; + } + ffi->context.set_time(time); + return RNP_SUCCESS; +} +FFI_GUARD + +static rnp_result_t +load_keys_from_input(rnp_ffi_t ffi, rnp_input_t input, rnp_key_store_t *store) +{ + pgp_key_provider_t chained(rnp_key_provider_store, store); + const pgp_key_provider_t *key_providers[] = {&chained, &ffi->key_provider, NULL}; + const pgp_key_provider_t key_provider(rnp_key_provider_chained, key_providers); + + if (!input->src_directory.empty()) { + // load the keys + store->path = input->src_directory; + if (!rnp_key_store_load_from_path(store, &key_provider)) { + return RNP_ERROR_BAD_FORMAT; + } + return RNP_SUCCESS; + } + + // load the keys + if (!rnp_key_store_load_from_src(store, &input->src, &key_provider)) { + return RNP_ERROR_BAD_FORMAT; + } + return RNP_SUCCESS; +} + +static bool +key_needs_conversion(const pgp_key_t *key, const rnp_key_store_t *store) +{ + pgp_key_store_format_t key_format = key->format; + pgp_key_store_format_t store_format = store->format; + /* pgp_key_t->format is only ever GPG or G10. + * + * The key store, however, could have a format of KBX, GPG, or G10. + * A KBX (and GPG) key store can only handle a pgp_key_t with a format of GPG. + * A G10 key store can only handle a pgp_key_t with a format of G10. + */ + // should never be the case + assert(key_format != PGP_KEY_STORE_KBX); + // normalize the store format + if (store_format == PGP_KEY_STORE_KBX) { + store_format = PGP_KEY_STORE_GPG; + } + // from here, both the key and store formats can only be GPG or G10 + return key_format != store_format; +} + +static rnp_result_t +do_load_keys(rnp_ffi_t ffi, + rnp_input_t input, + pgp_key_store_format_t format, + key_type_t key_type) +{ + // create a temporary key store to hold the keys + std::unique_ptr<rnp_key_store_t> tmp_store; + try { + tmp_store = + std::unique_ptr<rnp_key_store_t>(new rnp_key_store_t(format, "", ffi->context)); + } catch (const std::invalid_argument &e) { + FFI_LOG(ffi, "Failed to create key store of format: %d", (int) format); + return RNP_ERROR_BAD_PARAMETERS; + } + + // load keys into our temporary store + rnp_result_t tmpret = load_keys_from_input(ffi, input, tmp_store.get()); + if (tmpret) { + return tmpret; + } + // go through all the loaded keys + for (auto &key : tmp_store->keys) { + // check that the key is the correct type and has not already been loaded + // add secret key part if it is and we need it + if (key.is_secret() && ((key_type == KEY_TYPE_SECRET) || (key_type == KEY_TYPE_ANY))) { + if (key_needs_conversion(&key, ffi->secring)) { + FFI_LOG(ffi, "This key format conversion is not yet supported"); + return RNP_ERROR_NOT_IMPLEMENTED; + } + + if (!rnp_key_store_add_key(ffi->secring, &key)) { + FFI_LOG(ffi, "Failed to add secret key"); + return RNP_ERROR_GENERIC; + } + } + + // add public key part if needed + if ((key.format == PGP_KEY_STORE_G10) || + ((key_type != KEY_TYPE_ANY) && (key_type != KEY_TYPE_PUBLIC))) { + continue; + } + + pgp_key_t keycp; + try { + keycp = pgp_key_t(key, true); + } catch (const std::exception &e) { + RNP_LOG("Failed to copy public key part: %s", e.what()); + return RNP_ERROR_GENERIC; + } + + /* TODO: We could do this a few different ways. There isn't an obvious reason + * to restrict what formats we load, so we don't necessarily need to require a + * conversion just to load and use a G10 key when using GPG keyrings, for + * example. We could just convert when saving. + */ + + if (key_needs_conversion(&key, ffi->pubring)) { + FFI_LOG(ffi, "This key format conversion is not yet supported"); + return RNP_ERROR_NOT_IMPLEMENTED; + } + + if (!rnp_key_store_add_key(ffi->pubring, &keycp)) { + FFI_LOG(ffi, "Failed to add public key"); + return RNP_ERROR_GENERIC; + } + } + // success, even if we didn't actually load any + return RNP_SUCCESS; +} + +static key_type_t +flags_to_key_type(uint32_t *flags) +{ + key_type_t type = KEY_TYPE_NONE; + // figure out what type of keys to operate on, based on flags + if ((*flags & RNP_LOAD_SAVE_PUBLIC_KEYS) && (*flags & RNP_LOAD_SAVE_SECRET_KEYS)) { + type = KEY_TYPE_ANY; + extract_flag(*flags, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS); + } else if (*flags & RNP_LOAD_SAVE_PUBLIC_KEYS) { + type = KEY_TYPE_PUBLIC; + extract_flag(*flags, RNP_LOAD_SAVE_PUBLIC_KEYS); + } else if (*flags & RNP_LOAD_SAVE_SECRET_KEYS) { + type = KEY_TYPE_SECRET; + extract_flag(*flags, RNP_LOAD_SAVE_SECRET_KEYS); + } + return type; +} + +rnp_result_t +rnp_load_keys(rnp_ffi_t ffi, const char *format, rnp_input_t input, uint32_t flags) +try { + // checks + if (!ffi || !format || !input) { + return RNP_ERROR_NULL_POINTER; + } + key_type_t type = flags_to_key_type(&flags); + if (!type) { + FFI_LOG(ffi, "invalid flags - must have public and/or secret keys"); + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_key_store_format_t ks_format = PGP_KEY_STORE_UNKNOWN; + if (!parse_ks_format(&ks_format, format)) { + FFI_LOG(ffi, "invalid key store format: %s", format); + return RNP_ERROR_BAD_PARAMETERS; + } + + // check for any unrecognized flags (not forward-compat, but maybe still a good idea) + if (flags) { + FFI_LOG(ffi, "unexpected flags remaining: 0x%X", flags); + return RNP_ERROR_BAD_PARAMETERS; + } + return do_load_keys(ffi, input, ks_format, type); +} +FFI_GUARD + +rnp_result_t +rnp_unload_keys(rnp_ffi_t ffi, uint32_t flags) +try { + if (!ffi) { + return RNP_ERROR_NULL_POINTER; + } + + if (flags & ~(RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + if (flags & RNP_KEY_UNLOAD_PUBLIC) { + rnp_key_store_clear(ffi->pubring); + } + if (flags & RNP_KEY_UNLOAD_SECRET) { + rnp_key_store_clear(ffi->secring); + } + + return RNP_SUCCESS; +} +FFI_GUARD + +static rnp_result_t +rnp_input_dearmor_if_needed(rnp_input_t input, bool noheaders = false) +{ + if (!input) { + return RNP_ERROR_NULL_POINTER; + } + if (!input->src_directory.empty()) { + return RNP_ERROR_BAD_PARAMETERS; + } + bool require_armor = false; + /* check whether we already have armored stream */ + if (input->src.type == PGP_STREAM_ARMORED) { + if (!src_eof(&input->src)) { + /* be ready for the case of damaged armoring */ + return src_error(&input->src) ? RNP_ERROR_READ : RNP_SUCCESS; + } + /* eof - probably next we have another armored message */ + src_close(&input->src); + rnp_input_st *base = (rnp_input_st *) input->app_ctx; + *input = std::move(*base); + delete base; + /* we should not mix armored data with binary */ + require_armor = true; + } + if (src_eof(&input->src)) { + return RNP_ERROR_EOF; + } + /* check whether input is armored only if base64 is not forced */ + if (!noheaders && !is_armored_source(&input->src)) { + return require_armor ? RNP_ERROR_BAD_FORMAT : RNP_SUCCESS; + } + + /* Store original input in app_ctx and replace src/app_ctx with armored data */ + rnp_input_t app_ctx = new rnp_input_st(); + *app_ctx = std::move(*input); + + rnp_result_t ret = init_armored_src(&input->src, &app_ctx->src, noheaders); + if (ret) { + /* original src may be changed during init_armored_src call, so copy it back */ + *input = std::move(*app_ctx); + delete app_ctx; + return ret; + } + input->app_ctx = app_ctx; + return RNP_SUCCESS; +} + +static const char * +key_status_to_str(pgp_key_import_status_t status) +{ + if (status == PGP_KEY_IMPORT_STATUS_UNKNOWN) { + return "none"; + } + return id_str_pair::lookup(key_import_status_map, status, "none"); +} + +static rnp_result_t +add_key_status(json_object * keys, + const pgp_key_t * key, + pgp_key_import_status_t pub, + pgp_key_import_status_t sec) +{ + json_object *jsokey = json_object_new_object(); + if (!jsokey) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + if (!obj_add_field_json( + jsokey, "public", json_object_new_string(key_status_to_str(pub))) || + !obj_add_field_json( + jsokey, "secret", json_object_new_string(key_status_to_str(sec))) || + !obj_add_hex_json(jsokey, "fingerprint", key->fp().fingerprint, key->fp().length) || + !array_add_element_json(keys, jsokey)) { + json_object_put(jsokey); + return RNP_ERROR_OUT_OF_MEMORY; + } + + return RNP_SUCCESS; +} + +rnp_result_t +rnp_import_keys(rnp_ffi_t ffi, rnp_input_t input, uint32_t flags, char **results) +try { + if (!ffi || !input) { + return RNP_ERROR_NULL_POINTER; + } + bool sec = extract_flag(flags, RNP_LOAD_SAVE_SECRET_KEYS); + bool pub = extract_flag(flags, RNP_LOAD_SAVE_PUBLIC_KEYS); + if (!pub && !sec) { + FFI_LOG(ffi, "bad flags: need to specify public and/or secret keys"); + return RNP_ERROR_BAD_PARAMETERS; + } + bool skipbad = extract_flag(flags, RNP_LOAD_SAVE_PERMISSIVE); + bool single = extract_flag(flags, RNP_LOAD_SAVE_SINGLE); + bool base64 = extract_flag(flags, RNP_LOAD_SAVE_BASE64); + if (flags) { + FFI_LOG(ffi, "unexpected flags remaining: 0x%X", flags); + return RNP_ERROR_BAD_PARAMETERS; + } + + rnp_result_t ret = RNP_ERROR_GENERIC; + rnp_key_store_t tmp_store(PGP_KEY_STORE_GPG, "", ffi->context); + + /* check whether input is base64 */ + if (base64 && is_base64_source(input->src)) { + ret = rnp_input_dearmor_if_needed(input, true); + if (ret) { + return ret; + } + } + + // load keys to temporary keystore. + if (single) { + /* we need to init and handle dearmor on this layer since it may be used for the next + * keys import */ + ret = rnp_input_dearmor_if_needed(input); + if (ret == RNP_ERROR_EOF) { + return ret; + } + if (ret) { + FFI_LOG(ffi, "Failed to init/check dearmor."); + return ret; + } + ret = rnp_key_store_pgp_read_key_from_src(tmp_store, input->src, skipbad); + if (ret) { + return ret; + } + } else { + ret = rnp_key_store_pgp_read_from_src(&tmp_store, &input->src, skipbad); + if (ret) { + return ret; + } + } + + json_object *jsores = json_object_new_object(); + if (!jsores) { + return RNP_ERROR_OUT_OF_MEMORY; + } + rnp::JSONObject jsowrap(jsores); + json_object * jsokeys = json_object_new_array(); + if (!obj_add_field_json(jsores, "keys", jsokeys)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + // import keys to the main keystore. + for (auto &key : tmp_store.keys) { + pgp_key_import_status_t pub_status = PGP_KEY_IMPORT_STATUS_UNKNOWN; + pgp_key_import_status_t sec_status = PGP_KEY_IMPORT_STATUS_UNKNOWN; + if (!pub && key.is_public()) { + continue; + } + // if we got here then we add public key itself or public part of the secret key + if (!rnp_key_store_import_key(ffi->pubring, &key, true, &pub_status)) { + return RNP_ERROR_BAD_PARAMETERS; + } + // import secret key part if available and requested + if (sec && key.is_secret()) { + if (!rnp_key_store_import_key(ffi->secring, &key, false, &sec_status)) { + return RNP_ERROR_BAD_PARAMETERS; + } + // add uids, certifications and other stuff from the public key if any + pgp_key_t *expub = rnp_key_store_get_key_by_fpr(ffi->pubring, key.fp()); + if (expub && !rnp_key_store_import_key(ffi->secring, expub, true, NULL)) { + return RNP_ERROR_BAD_PARAMETERS; + } + } + // now add key fingerprint to json based on statuses + rnp_result_t tmpret = add_key_status(jsokeys, &key, pub_status, sec_status); + if (tmpret) { + return tmpret; + } + } + + if (results) { + *results = (char *) json_object_to_json_string_ext(jsores, JSON_C_TO_STRING_PRETTY); + if (!*results) { + return RNP_ERROR_GENERIC; + } + *results = strdup(*results); + if (!*results) { + return RNP_ERROR_OUT_OF_MEMORY; + } + } + return RNP_SUCCESS; +} +FFI_GUARD + +static const char * +sig_status_to_str(pgp_sig_import_status_t status) +{ + if (status == PGP_SIG_IMPORT_STATUS_UNKNOWN) { + return "none"; + } + return id_str_pair::lookup(sig_import_status_map, status, "none"); +} + +static rnp_result_t +add_sig_status(json_object * sigs, + const pgp_key_t * signer, + pgp_sig_import_status_t pub, + pgp_sig_import_status_t sec) +{ + json_object *jsosig = json_object_new_object(); + if (!jsosig) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + if (!obj_add_field_json( + jsosig, "public", json_object_new_string(sig_status_to_str(pub))) || + !obj_add_field_json( + jsosig, "secret", json_object_new_string(sig_status_to_str(sec)))) { + json_object_put(jsosig); + return RNP_ERROR_OUT_OF_MEMORY; + } + + if (signer) { + const pgp_fingerprint_t &fp = signer->fp(); + if (!obj_add_hex_json(jsosig, "signer fingerprint", fp.fingerprint, fp.length)) { + json_object_put(jsosig); + return RNP_ERROR_OUT_OF_MEMORY; + } + } + + if (!array_add_element_json(sigs, jsosig)) { + json_object_put(jsosig); + return RNP_ERROR_OUT_OF_MEMORY; + } + + return RNP_SUCCESS; +} + +rnp_result_t +rnp_import_signatures(rnp_ffi_t ffi, rnp_input_t input, uint32_t flags, char **results) +try { + if (!ffi || !input) { + return RNP_ERROR_NULL_POINTER; + } + if (flags) { + FFI_LOG(ffi, "wrong flags: %d", (int) flags); + return RNP_ERROR_BAD_PARAMETERS; + } + + pgp_signature_list_t sigs; + rnp_result_t sigret = process_pgp_signatures(input->src, sigs); + if (sigret) { + FFI_LOG(ffi, "failed to parse signature(s)"); + return sigret; + } + + json_object *jsores = json_object_new_object(); + if (!jsores) { + return RNP_ERROR_OUT_OF_MEMORY; + } + rnp::JSONObject jsowrap(jsores); + json_object * jsosigs = json_object_new_array(); + if (!obj_add_field_json(jsores, "sigs", jsosigs)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + for (auto &sig : sigs) { + pgp_sig_import_status_t pub_status = PGP_SIG_IMPORT_STATUS_UNKNOWN; + pgp_sig_import_status_t sec_status = PGP_SIG_IMPORT_STATUS_UNKNOWN; + pgp_key_t *pkey = rnp_key_store_import_signature(ffi->pubring, &sig, &pub_status); + pgp_key_t *skey = rnp_key_store_import_signature(ffi->secring, &sig, &sec_status); + sigret = add_sig_status(jsosigs, pkey ? pkey : skey, pub_status, sec_status); + if (sigret) { + return sigret; + } + } + + if (results) { + *results = (char *) json_object_to_json_string_ext(jsores, JSON_C_TO_STRING_PRETTY); + if (!*results) { + return RNP_ERROR_OUT_OF_MEMORY; + } + *results = strdup(*results); + if (!*results) { + return RNP_ERROR_OUT_OF_MEMORY; + } + } + return RNP_SUCCESS; +} +FFI_GUARD + +static bool +copy_store_keys(rnp_ffi_t ffi, rnp_key_store_t *dest, rnp_key_store_t *src) +{ + for (auto &key : src->keys) { + if (!rnp_key_store_add_key(dest, &key)) { + FFI_LOG(ffi, "failed to add key to the store"); + return false; + } + } + return true; +} + +static rnp_result_t +do_save_keys(rnp_ffi_t ffi, + rnp_output_t output, + pgp_key_store_format_t format, + key_type_t key_type) +{ + rnp_result_t ret = RNP_ERROR_GENERIC; + + // create a temporary key store to hold the keys + rnp_key_store_t *tmp_store = NULL; + try { + tmp_store = new rnp_key_store_t(format, "", ffi->context); + } catch (const std::invalid_argument &e) { + FFI_LOG(ffi, "Failed to create key store of format: %d", (int) format); + return RNP_ERROR_BAD_PARAMETERS; + } catch (const std::exception &e) { + FFI_LOG(ffi, "%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + // include the public keys, if desired + if (key_type == KEY_TYPE_PUBLIC || key_type == KEY_TYPE_ANY) { + if (!copy_store_keys(ffi, tmp_store, ffi->pubring)) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + } + // include the secret keys, if desired + if (key_type == KEY_TYPE_SECRET || key_type == KEY_TYPE_ANY) { + if (!copy_store_keys(ffi, tmp_store, ffi->secring)) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + } + // preliminary check on the format + for (auto &key : tmp_store->keys) { + if (key_needs_conversion(&key, tmp_store)) { + FFI_LOG(ffi, "This key format conversion is not yet supported"); + ret = RNP_ERROR_NOT_IMPLEMENTED; + goto done; + } + } + // write + if (output->dst_directory) { + try { + tmp_store->path = output->dst_directory; + } catch (const std::exception &e) { + FFI_LOG(ffi, "%s", e.what()); + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + if (!rnp_key_store_write_to_path(tmp_store)) { + ret = RNP_ERROR_WRITE; + goto done; + } + ret = RNP_SUCCESS; + } else { + if (!rnp_key_store_write_to_dst(tmp_store, &output->dst)) { + ret = RNP_ERROR_WRITE; + goto done; + } + dst_flush(&output->dst); + output->keep = (output->dst.werr == RNP_SUCCESS); + ret = output->dst.werr; + } + +done: + delete tmp_store; + return ret; +} + +rnp_result_t +rnp_save_keys(rnp_ffi_t ffi, const char *format, rnp_output_t output, uint32_t flags) +try { + // checks + if (!ffi || !format || !output) { + return RNP_ERROR_NULL_POINTER; + } + key_type_t type = flags_to_key_type(&flags); + if (!type) { + FFI_LOG(ffi, "invalid flags - must have public and/or secret keys"); + return RNP_ERROR_BAD_PARAMETERS; + } + // check for any unrecognized flags (not forward-compat, but maybe still a good idea) + if (flags) { + FFI_LOG(ffi, "unexpected flags remaining: 0x%X", flags); + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_key_store_format_t ks_format = PGP_KEY_STORE_UNKNOWN; + if (!parse_ks_format(&ks_format, format)) { + FFI_LOG(ffi, "unknown key store format: %s", format); + return RNP_ERROR_BAD_PARAMETERS; + } + return do_save_keys(ffi, output, ks_format, type); +} +FFI_GUARD + +rnp_result_t +rnp_get_public_key_count(rnp_ffi_t ffi, size_t *count) +try { + if (!ffi || !count) { + return RNP_ERROR_NULL_POINTER; + } + *count = rnp_key_store_get_key_count(ffi->pubring); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_get_secret_key_count(rnp_ffi_t ffi, size_t *count) +try { + if (!ffi || !count) { + return RNP_ERROR_NULL_POINTER; + } + *count = rnp_key_store_get_key_count(ffi->secring); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_input_st::rnp_input_st() : reader(NULL), closer(NULL), app_ctx(NULL) +{ + memset(&src, 0, sizeof(src)); +} + +rnp_input_st & +rnp_input_st::operator=(rnp_input_st &&input) +{ + src_close(&src); + src = std::move(input.src); + memset(&input.src, 0, sizeof(input.src)); + reader = input.reader; + input.reader = NULL; + closer = input.closer; + input.closer = NULL; + app_ctx = input.app_ctx; + input.app_ctx = NULL; + src_directory = std::move(input.src_directory); + return *this; +} + +rnp_input_st::~rnp_input_st() +{ + bool armored = src.type == PGP_STREAM_ARMORED; + src_close(&src); + if (armored) { + rnp_input_t armored = (rnp_input_t) app_ctx; + delete armored; + app_ctx = NULL; + } +} + +rnp_result_t +rnp_input_from_path(rnp_input_t *input, const char *path) +try { + if (!input || !path) { + return RNP_ERROR_NULL_POINTER; + } + rnp_input_st *ob = new rnp_input_st(); + struct stat st = {0}; + if (rnp_stat(path, &st) == 0 && S_ISDIR(st.st_mode)) { + // a bit hacky, just save the directory path + ob->src_directory = path; + // return error on attempt to read from this source + (void) init_null_src(&ob->src); + } else { + // simple input from a file + rnp_result_t ret = init_file_src(&ob->src, path); + if (ret) { + delete ob; + return ret; + } + } + *input = ob; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_input_from_stdin(rnp_input_t *input) +try { + if (!input) { + return RNP_ERROR_NULL_POINTER; + } + *input = new rnp_input_st(); + rnp_result_t ret = init_stdin_src(&(*input)->src); + if (ret) { + delete *input; + *input = NULL; + return ret; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_input_from_memory(rnp_input_t *input, const uint8_t buf[], size_t buf_len, bool do_copy) +try { + if (!input || !buf) { + return RNP_ERROR_NULL_POINTER; + } + if (!buf_len) { + return RNP_ERROR_SHORT_BUFFER; + } + *input = new rnp_input_st(); + uint8_t *data = (uint8_t *) buf; + if (do_copy) { + data = (uint8_t *) malloc(buf_len); + if (!data) { + delete *input; + *input = NULL; + return RNP_ERROR_OUT_OF_MEMORY; + } + memcpy(data, buf, buf_len); + } + rnp_result_t ret = init_mem_src(&(*input)->src, data, buf_len, do_copy); + if (ret) { + if (do_copy) { + free(data); + } + delete *input; + *input = NULL; + return ret; + } + return RNP_SUCCESS; +} +FFI_GUARD + +static bool +input_reader_bounce(pgp_source_t *src, void *buf, size_t len, size_t *read) +{ + rnp_input_t input = (rnp_input_t) src->param; + if (!input->reader) { + return false; + } + return input->reader(input->app_ctx, buf, len, read); +} + +static void +input_closer_bounce(pgp_source_t *src) +{ + rnp_input_t input = (rnp_input_t) src->param; + if (input->closer) { + input->closer(input->app_ctx); + } +} + +rnp_result_t +rnp_input_from_callback(rnp_input_t * input, + rnp_input_reader_t *reader, + rnp_input_closer_t *closer, + void * app_ctx) +try { + // checks + if (!input || !reader) { + return RNP_ERROR_NULL_POINTER; + } + rnp_input_st *obj = new rnp_input_st(); + pgp_source_t *src = &obj->src; + obj->reader = reader; + obj->closer = closer; + obj->app_ctx = app_ctx; + if (!init_src_common(src, 0)) { + delete obj; + return RNP_ERROR_OUT_OF_MEMORY; + } + src->param = obj; + src->read = input_reader_bounce; + src->close = input_closer_bounce; + src->type = PGP_STREAM_MEMORY; + *input = obj; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_input_destroy(rnp_input_t input) +try { + delete input; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_output_to_path(rnp_output_t *output, const char *path) +try { + struct rnp_output_st *ob = NULL; + struct stat st = {0}; + + if (!output || !path) { + return RNP_ERROR_NULL_POINTER; + } + ob = (rnp_output_st *) calloc(1, sizeof(*ob)); + if (!ob) { + return RNP_ERROR_OUT_OF_MEMORY; + } + if (rnp_stat(path, &st) == 0 && S_ISDIR(st.st_mode)) { + // a bit hacky, just save the directory path + ob->dst_directory = strdup(path); + if (!ob->dst_directory) { + free(ob); + return RNP_ERROR_OUT_OF_MEMORY; + } + } else { + // simple output to a file + rnp_result_t ret = init_file_dest(&ob->dst, path, true); + if (ret) { + free(ob); + return ret; + } + } + *output = ob; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_output_to_file(rnp_output_t *output, const char *path, uint32_t flags) +try { + if (!output || !path) { + return RNP_ERROR_NULL_POINTER; + } + bool overwrite = extract_flag(flags, RNP_OUTPUT_FILE_OVERWRITE); + bool random = extract_flag(flags, RNP_OUTPUT_FILE_RANDOM); + if (flags) { + return RNP_ERROR_BAD_PARAMETERS; + } + rnp_output_t res = (rnp_output_t) calloc(1, sizeof(*res)); + if (!res) { + return RNP_ERROR_OUT_OF_MEMORY; + } + rnp_result_t ret = RNP_ERROR_GENERIC; + if (random) { + ret = init_tmpfile_dest(&res->dst, path, overwrite); + } else { + ret = init_file_dest(&res->dst, path, overwrite); + } + if (ret) { + free(res); + return ret; + } + *output = res; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_output_to_stdout(rnp_output_t *output) +try { + if (!output) { + return RNP_ERROR_NULL_POINTER; + } + rnp_output_t res = (rnp_output_t) calloc(1, sizeof(*res)); + if (!res) { + return RNP_ERROR_OUT_OF_MEMORY; + } + rnp_result_t ret = init_stdout_dest(&res->dst); + if (ret) { + free(res); + return ret; + } + *output = res; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_output_to_memory(rnp_output_t *output, size_t max_alloc) +try { + // checks + if (!output) { + return RNP_ERROR_NULL_POINTER; + } + + *output = (rnp_output_t) calloc(1, sizeof(**output)); + if (!*output) { + return RNP_ERROR_OUT_OF_MEMORY; + } + rnp_result_t ret = init_mem_dest(&(*output)->dst, NULL, max_alloc); + if (ret) { + free(*output); + *output = NULL; + return ret; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_output_to_armor(rnp_output_t base, rnp_output_t *output, const char *type) +try { + if (!base || !output) { + return RNP_ERROR_NULL_POINTER; + } + pgp_armored_msg_t msgtype = PGP_ARMORED_MESSAGE; + if (type) { + msgtype = static_cast<pgp_armored_msg_t>( + id_str_pair::lookup(armor_type_map, type, PGP_ARMORED_UNKNOWN)); + if (msgtype == PGP_ARMORED_UNKNOWN) { + RNP_LOG("Unsupported armor type: %s", type); + return RNP_ERROR_BAD_PARAMETERS; + } + } + *output = (rnp_output_t) calloc(1, sizeof(**output)); + if (!*output) { + return RNP_ERROR_OUT_OF_MEMORY; + } + rnp_result_t ret = init_armored_dst(&(*output)->dst, &base->dst, msgtype); + if (ret) { + free(*output); + *output = NULL; + return ret; + } + (*output)->app_ctx = base; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_output_memory_get_buf(rnp_output_t output, uint8_t **buf, size_t *len, bool do_copy) +try { + if (!output || !buf || !len) { + return RNP_ERROR_NULL_POINTER; + } + + *len = output->dst.writeb; + *buf = (uint8_t *) mem_dest_get_memory(&output->dst); + if (!*buf) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (do_copy) { + uint8_t *tmp_buf = *buf; + *buf = (uint8_t *) malloc(*len); + if (!*buf) { + return RNP_ERROR_OUT_OF_MEMORY; + } + memcpy(*buf, tmp_buf, *len); + } + return RNP_SUCCESS; +} +FFI_GUARD + +static rnp_result_t +output_writer_bounce(pgp_dest_t *dst, const void *buf, size_t len) +{ + rnp_output_t output = (rnp_output_t) dst->param; + if (!output->writer) { + return RNP_ERROR_NULL_POINTER; + } + if (!output->writer(output->app_ctx, buf, len)) { + return RNP_ERROR_WRITE; + } + return RNP_SUCCESS; +} + +static void +output_closer_bounce(pgp_dest_t *dst, bool discard) +{ + rnp_output_t output = (rnp_output_t) dst->param; + if (output->closer) { + output->closer(output->app_ctx, discard); + } +} + +rnp_result_t +rnp_output_to_null(rnp_output_t *output) +try { + // checks + if (!output) { + return RNP_ERROR_NULL_POINTER; + } + + *output = (rnp_output_t) calloc(1, sizeof(**output)); + if (!*output) { + return RNP_ERROR_OUT_OF_MEMORY; + } + rnp_result_t ret = init_null_dest(&(*output)->dst); + if (ret) { + free(*output); + *output = NULL; + return ret; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_output_write(rnp_output_t output, const void *data, size_t size, size_t *written) +try { + if (!output || (!data && size)) { + return RNP_ERROR_NULL_POINTER; + } + if (!data && !size) { + if (written) { + *written = 0; + } + return RNP_SUCCESS; + } + size_t old = output->dst.writeb + output->dst.clen; + dst_write(&output->dst, data, size); + if (!output->dst.werr && written) { + *written = output->dst.writeb + output->dst.clen - old; + } + output->keep = !output->dst.werr; + return output->dst.werr; +} +FFI_GUARD + +rnp_result_t +rnp_output_to_callback(rnp_output_t * output, + rnp_output_writer_t *writer, + rnp_output_closer_t *closer, + void * app_ctx) +try { + // checks + if (!output || !writer) { + return RNP_ERROR_NULL_POINTER; + } + + *output = (rnp_output_t) calloc(1, sizeof(**output)); + if (!*output) { + return RNP_ERROR_OUT_OF_MEMORY; + } + (*output)->writer = writer; + (*output)->closer = closer; + (*output)->app_ctx = app_ctx; + + pgp_dest_t *dst = &(*output)->dst; + dst->write = output_writer_bounce; + dst->close = output_closer_bounce; + dst->param = *output; + dst->type = PGP_STREAM_MEMORY; + dst->writeb = 0; + dst->werr = RNP_SUCCESS; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_output_finish(rnp_output_t output) +try { + if (!output) { + return RNP_ERROR_NULL_POINTER; + } + return dst_finish(&output->dst); +} +FFI_GUARD + +rnp_result_t +rnp_output_destroy(rnp_output_t output) +try { + if (output) { + if (output->dst.type == PGP_STREAM_ARMORED) { + ((rnp_output_t) output->app_ctx)->keep = output->keep; + } + dst_close(&output->dst, !output->keep); + free(output->dst_directory); + free(output); + } + return RNP_SUCCESS; +} +FFI_GUARD + +static rnp_result_t +rnp_op_add_signature(rnp_ffi_t ffi, + rnp_op_sign_signatures_t &signatures, + rnp_key_handle_t key, + rnp_ctx_t & ctx, + rnp_op_sign_signature_t * sig) +{ + if (!key) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_key_t *signkey = + find_suitable_key(PGP_OP_SIGN, get_key_require_secret(key), &key->ffi->key_provider); + if (!signkey) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + + try { + signatures.emplace_back(); + } catch (const std::exception &e) { + FFI_LOG(ffi, "%s", e.what()); + return RNP_ERROR_BAD_PARAMETERS; + } + rnp_op_sign_signature_t newsig = &signatures.back(); + newsig->signer.key = signkey; + /* set default create/expire times */ + newsig->signer.sigcreate = ctx.sigcreate; + newsig->signer.sigexpire = ctx.sigexpire; + newsig->ffi = ffi; + + if (sig) { + *sig = newsig; + } + return RNP_SUCCESS; +} + +static rnp_result_t +rnp_op_set_armor(rnp_ctx_t &ctx, bool armored) +{ + ctx.armor = armored; + return RNP_SUCCESS; +} + +static rnp_result_t +rnp_op_set_compression(rnp_ffi_t ffi, rnp_ctx_t &ctx, const char *compression, int level) +{ + if (!compression) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_compression_type_t zalg = PGP_C_UNKNOWN; + if (!str_to_compression_alg(compression, &zalg)) { + FFI_LOG(ffi, "Invalid compression: %s", compression); + return RNP_ERROR_BAD_PARAMETERS; + } + ctx.zalg = (int) zalg; + ctx.zlevel = level; + return RNP_SUCCESS; +} + +static rnp_result_t +rnp_op_set_hash(rnp_ffi_t ffi, rnp_ctx_t &ctx, const char *hash) +{ + if (!hash) { + return RNP_ERROR_NULL_POINTER; + } + + if (!str_to_hash_alg(hash, &ctx.halg)) { + FFI_LOG(ffi, "Invalid hash: %s", hash); + return RNP_ERROR_BAD_PARAMETERS; + } + return RNP_SUCCESS; +} + +static rnp_result_t +rnp_op_set_creation_time(rnp_ctx_t &ctx, uint32_t create) +{ + ctx.sigcreate = create; + return RNP_SUCCESS; +} + +static rnp_result_t +rnp_op_set_expiration_time(rnp_ctx_t &ctx, uint32_t expire) +{ + ctx.sigexpire = expire; + return RNP_SUCCESS; +} + +static rnp_result_t +rnp_op_set_flags(rnp_ffi_t ffi, rnp_ctx_t &ctx, uint32_t flags) +{ + ctx.no_wrap = extract_flag(flags, RNP_ENCRYPT_NOWRAP); + if (flags) { + FFI_LOG(ffi, "Unknown operation flags: %x", flags); + return RNP_ERROR_BAD_PARAMETERS; + } + return RNP_SUCCESS; +} + +static rnp_result_t +rnp_op_set_file_name(rnp_ctx_t &ctx, const char *filename) +{ + ctx.filename = filename ? filename : ""; + return RNP_SUCCESS; +} + +static rnp_result_t +rnp_op_set_file_mtime(rnp_ctx_t &ctx, uint32_t mtime) +{ + ctx.filemtime = mtime; + return RNP_SUCCESS; +} + +rnp_result_t +rnp_op_encrypt_create(rnp_op_encrypt_t *op, + rnp_ffi_t ffi, + rnp_input_t input, + rnp_output_t output) +try { + // checks + if (!op || !ffi || !input || !output) { + return RNP_ERROR_NULL_POINTER; + } + + *op = new rnp_op_encrypt_st(); + rnp_ctx_init_ffi((*op)->rnpctx, ffi); + (*op)->ffi = ffi; + (*op)->input = input; + (*op)->output = output; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_add_recipient(rnp_op_encrypt_t op, rnp_key_handle_t handle) +try { + // checks + if (!op || !handle) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_key_t *key = find_suitable_key( + PGP_OP_ENCRYPT, get_key_prefer_public(handle), &handle->ffi->key_provider); + if (!key) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + op->rnpctx.recipients.push_back(key); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_add_signature(rnp_op_encrypt_t op, + rnp_key_handle_t key, + rnp_op_sign_signature_t *sig) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_add_signature(op->ffi, op->signatures, key, op->rnpctx, sig); +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_set_hash(rnp_op_encrypt_t op, const char *hash) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_hash(op->ffi, op->rnpctx, hash); +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_set_creation_time(rnp_op_encrypt_t op, uint32_t create) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_creation_time(op->rnpctx, create); +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_set_expiration_time(rnp_op_encrypt_t op, uint32_t expire) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_expiration_time(op->rnpctx, expire); +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_add_password(rnp_op_encrypt_t op, + const char * password, + const char * s2k_hash, + size_t iterations, + const char * s2k_cipher) +try { + // checks + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + if (password && !*password) { + // no blank passwords + FFI_LOG(op->ffi, "Blank password"); + return RNP_ERROR_BAD_PARAMETERS; + } + + // set some defaults + if (!s2k_hash) { + s2k_hash = DEFAULT_HASH_ALG; + } + if (!s2k_cipher) { + s2k_cipher = DEFAULT_SYMM_ALG; + } + // parse + pgp_hash_alg_t hash_alg = PGP_HASH_UNKNOWN; + if (!str_to_hash_alg(s2k_hash, &hash_alg)) { + FFI_LOG(op->ffi, "Invalid hash: %s", s2k_hash); + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_symm_alg_t symm_alg = PGP_SA_UNKNOWN; + if (!str_to_cipher(s2k_cipher, &symm_alg)) { + FFI_LOG(op->ffi, "Invalid cipher: %s", s2k_cipher); + return RNP_ERROR_BAD_PARAMETERS; + } + rnp::secure_vector<char> ask_pass(MAX_PASSWORD_LENGTH, '\0'); + if (!password) { + pgp_password_ctx_t pswdctx(PGP_OP_ENCRYPT_SYM); + if (!pgp_request_password( + &op->ffi->pass_provider, &pswdctx, ask_pass.data(), ask_pass.size())) { + return RNP_ERROR_BAD_PASSWORD; + } + password = ask_pass.data(); + } + return op->rnpctx.add_encryption_password(password, hash_alg, symm_alg, iterations); +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_set_armor(rnp_op_encrypt_t op, bool armored) +try { + // checks + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_armor(op->rnpctx, armored); +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_set_cipher(rnp_op_encrypt_t op, const char *cipher) +try { + // checks + if (!op || !cipher) { + return RNP_ERROR_NULL_POINTER; + } + if (!str_to_cipher(cipher, &op->rnpctx.ealg)) { + FFI_LOG(op->ffi, "Invalid cipher: %s", cipher); + return RNP_ERROR_BAD_PARAMETERS; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_set_aead(rnp_op_encrypt_t op, const char *alg) +try { + // checks + if (!op || !alg) { + return RNP_ERROR_NULL_POINTER; + } + if (!str_to_aead_alg(alg, &op->rnpctx.aalg)) { + FFI_LOG(op->ffi, "Invalid AEAD algorithm: %s", alg); + return RNP_ERROR_BAD_PARAMETERS; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_set_aead_bits(rnp_op_encrypt_t op, int bits) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + if ((bits < 0) || (bits > 16)) { + return RNP_ERROR_BAD_PARAMETERS; + } + op->rnpctx.abits = bits; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_set_compression(rnp_op_encrypt_t op, const char *compression, int level) +try { + // checks + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_compression(op->ffi, op->rnpctx, compression, level); +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_set_flags(rnp_op_encrypt_t op, uint32_t flags) +try { + // checks + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_flags(op->ffi, op->rnpctx, flags); +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_set_file_name(rnp_op_encrypt_t op, const char *filename) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_file_name(op->rnpctx, filename); +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_set_file_mtime(rnp_op_encrypt_t op, uint32_t mtime) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_file_mtime(op->rnpctx, mtime); +} +FFI_GUARD + +static pgp_write_handler_t +pgp_write_handler(pgp_password_provider_t *pass_provider, + rnp_ctx_t * rnpctx, + void * param, + pgp_key_provider_t * key_provider) +{ + pgp_write_handler_t handler; + memset(&handler, 0, sizeof(handler)); + handler.password_provider = pass_provider; + handler.ctx = rnpctx; + handler.param = param; + handler.key_provider = key_provider; + return handler; +} + +static rnp_result_t +rnp_op_add_signatures(rnp_op_sign_signatures_t &opsigs, rnp_ctx_t &ctx) +{ + for (auto &sig : opsigs) { + if (!sig.signer.key) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + + rnp_signer_info_t sinfo = sig.signer; + if (!sig.hash_set) { + sinfo.halg = ctx.halg; + } + if (!sig.expiry_set) { + sinfo.sigexpire = ctx.sigexpire; + } + if (!sig.create_set) { + sinfo.sigcreate = ctx.sigcreate; + } + ctx.signers.push_back(sinfo); + } + return RNP_SUCCESS; +} + +rnp_result_t +rnp_op_encrypt_execute(rnp_op_encrypt_t op) +try { + // checks + if (!op || !op->input || !op->output) { + return RNP_ERROR_NULL_POINTER; + } + + // set the default hash alg if none was specified + if (!op->rnpctx.halg) { + op->rnpctx.halg = DEFAULT_PGP_HASH_ALG; + } + pgp_write_handler_t handler = + pgp_write_handler(&op->ffi->pass_provider, &op->rnpctx, NULL, &op->ffi->key_provider); + + rnp_result_t ret; + if (!op->signatures.empty() && (ret = rnp_op_add_signatures(op->signatures, op->rnpctx))) { + return ret; + } + ret = rnp_encrypt_sign_src(&handler, &op->input->src, &op->output->dst); + + dst_flush(&op->output->dst); + op->output->keep = ret == RNP_SUCCESS; + op->input = NULL; + op->output = NULL; + return ret; +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_destroy(rnp_op_encrypt_t op) +try { + delete op; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_create(rnp_op_sign_t *op, rnp_ffi_t ffi, rnp_input_t input, rnp_output_t output) +try { + // checks + if (!op || !ffi || !input || !output) { + return RNP_ERROR_NULL_POINTER; + } + + *op = new rnp_op_sign_st(); + rnp_ctx_init_ffi((*op)->rnpctx, ffi); + (*op)->ffi = ffi; + (*op)->input = input; + (*op)->output = output; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_cleartext_create(rnp_op_sign_t *op, + rnp_ffi_t ffi, + rnp_input_t input, + rnp_output_t output) +try { + rnp_result_t res = rnp_op_sign_create(op, ffi, input, output); + if (!res) { + (*op)->rnpctx.clearsign = true; + } + return res; +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_detached_create(rnp_op_sign_t *op, + rnp_ffi_t ffi, + rnp_input_t input, + rnp_output_t signature) +try { + rnp_result_t res = rnp_op_sign_create(op, ffi, input, signature); + if (!res) { + (*op)->rnpctx.detached = true; + } + return res; +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_add_signature(rnp_op_sign_t op, rnp_key_handle_t key, rnp_op_sign_signature_t *sig) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_add_signature(op->ffi, op->signatures, key, op->rnpctx, sig); +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_signature_set_hash(rnp_op_sign_signature_t sig, const char *hash) +try { + if (!sig || !hash) { + return RNP_ERROR_NULL_POINTER; + } + if (!str_to_hash_alg(hash, &sig->signer.halg)) { + FFI_LOG(sig->ffi, "Invalid hash: %s", hash); + return RNP_ERROR_BAD_PARAMETERS; + } + sig->hash_set = true; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_signature_set_creation_time(rnp_op_sign_signature_t sig, uint32_t create) +try { + if (!sig) { + return RNP_ERROR_NULL_POINTER; + } + sig->signer.sigcreate = create; + sig->create_set = true; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_signature_set_expiration_time(rnp_op_sign_signature_t sig, uint32_t expires) +try { + if (!sig) { + return RNP_ERROR_NULL_POINTER; + } + sig->signer.sigexpire = expires; + sig->expiry_set = true; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_set_armor(rnp_op_sign_t op, bool armored) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_armor(op->rnpctx, armored); +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_set_compression(rnp_op_sign_t op, const char *compression, int level) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_compression(op->ffi, op->rnpctx, compression, level); +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_set_hash(rnp_op_sign_t op, const char *hash) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_hash(op->ffi, op->rnpctx, hash); +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_set_creation_time(rnp_op_sign_t op, uint32_t create) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_creation_time(op->rnpctx, create); +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_set_expiration_time(rnp_op_sign_t op, uint32_t expire) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_expiration_time(op->rnpctx, expire); +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_set_file_name(rnp_op_sign_t op, const char *filename) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_file_name(op->rnpctx, filename); +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_set_file_mtime(rnp_op_sign_t op, uint32_t mtime) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_file_mtime(op->rnpctx, mtime); +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_execute(rnp_op_sign_t op) +try { + // checks + if (!op || !op->input || !op->output) { + return RNP_ERROR_NULL_POINTER; + } + + // set the default hash alg if none was specified + if (!op->rnpctx.halg) { + op->rnpctx.halg = DEFAULT_PGP_HASH_ALG; + } + pgp_write_handler_t handler = + pgp_write_handler(&op->ffi->pass_provider, &op->rnpctx, NULL, &op->ffi->key_provider); + + rnp_result_t ret; + if ((ret = rnp_op_add_signatures(op->signatures, op->rnpctx))) { + return ret; + } + ret = rnp_sign_src(&handler, &op->input->src, &op->output->dst); + + dst_flush(&op->output->dst); + op->output->keep = ret == RNP_SUCCESS; + op->input = NULL; + op->output = NULL; + return ret; +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_destroy(rnp_op_sign_t op) +try { + delete op; + return RNP_SUCCESS; +} +FFI_GUARD + +static void +rnp_op_verify_on_signatures(const std::vector<pgp_signature_info_t> &sigs, void *param) +{ + rnp_op_verify_t op = (rnp_op_verify_t) param; + + try { + /* in case we have multiple signed layers */ + delete[] op->signatures; + op->signatures = new rnp_op_verify_signature_st[sigs.size()]; + } catch (const std::exception &e) { + FFI_LOG(op->ffi, "%s", e.what()); + return; + } + op->signature_count = sigs.size(); + + size_t i = 0; + for (const auto &sinfo : sigs) { + rnp_op_verify_signature_t res = &op->signatures[i++]; + /* sinfo.sig may be NULL */ + if (sinfo.sig) { + try { + res->sig_pkt = *sinfo.sig; + } catch (const std::exception &e) { + FFI_LOG(op->ffi, "%s", e.what()); + } + } + + if (sinfo.unknown) { + res->verify_status = RNP_ERROR_SIGNATURE_UNKNOWN; + } else if (sinfo.valid) { + res->verify_status = sinfo.expired ? RNP_ERROR_SIGNATURE_EXPIRED : RNP_SUCCESS; + } else { + res->verify_status = + sinfo.no_signer ? RNP_ERROR_KEY_NOT_FOUND : RNP_ERROR_SIGNATURE_INVALID; + } + res->ffi = op->ffi; + } +} + +static bool +rnp_verify_src_provider(pgp_parse_handler_t *handler, pgp_source_t *src) +{ + /* this one is called only when input for detached signature is needed */ + rnp_op_verify_t op = (rnp_op_verify_t) handler->param; + if (!op->detached_input) { + return false; + } + *src = op->detached_input->src; + /* we should give ownership on src to caller */ + memset(&op->detached_input->src, 0, sizeof(op->detached_input->src)); + return true; +}; + +static bool +rnp_verify_dest_provider(pgp_parse_handler_t *handler, + pgp_dest_t ** dst, + bool * closedst, + const char * filename, + uint32_t mtime) +{ + rnp_op_verify_t op = (rnp_op_verify_t) handler->param; + if (!op->output) { + return false; + } + *dst = &(op->output->dst); + *closedst = false; + op->filename = filename ? strdup(filename) : NULL; + op->file_mtime = mtime; + return true; +} + +static void +recipient_handle_from_pk_sesskey(rnp_recipient_handle_t handle, + const pgp_pk_sesskey_t &sesskey) +{ + static_assert(sizeof(handle->keyid) == PGP_KEY_ID_SIZE, "Keyid size mismatch"); + memcpy(handle->keyid, sesskey.key_id.data(), PGP_KEY_ID_SIZE); + handle->palg = sesskey.alg; +} + +static void +symenc_handle_from_sk_sesskey(rnp_symenc_handle_t handle, const pgp_sk_sesskey_t &sesskey) +{ + handle->alg = sesskey.alg; + handle->halg = sesskey.s2k.hash_alg; + handle->s2k_type = sesskey.s2k.specifier; + if (sesskey.s2k.specifier == PGP_S2KS_ITERATED_AND_SALTED) { + handle->iterations = pgp_s2k_decode_iterations(sesskey.s2k.iterations); + } else { + handle->iterations = 1; + } + handle->aalg = sesskey.aalg; +} + +static void +rnp_verify_on_recipients(const std::vector<pgp_pk_sesskey_t> &recipients, + const std::vector<pgp_sk_sesskey_t> &passwords, + void * param) +{ + rnp_op_verify_t op = (rnp_op_verify_t) param; + /* store only top-level encrypted stream recipients info for now */ + if (op->encrypted_layers++) { + return; + } + if (!recipients.empty()) { + op->recipients = + (rnp_recipient_handle_t) calloc(recipients.size(), sizeof(*op->recipients)); + if (!op->recipients) { + FFI_LOG(op->ffi, "allocation failed"); + return; + } + for (size_t i = 0; i < recipients.size(); i++) { + recipient_handle_from_pk_sesskey(&op->recipients[i], recipients[i]); + } + } + op->recipient_count = recipients.size(); + if (!passwords.empty()) { + op->symencs = (rnp_symenc_handle_t) calloc(passwords.size(), sizeof(*op->symencs)); + if (!op->symencs) { + FFI_LOG(op->ffi, "allocation failed"); + return; + } + for (size_t i = 0; i < passwords.size(); i++) { + symenc_handle_from_sk_sesskey(&op->symencs[i], passwords[i]); + } + } + op->symenc_count = passwords.size(); +} + +static void +rnp_verify_on_decryption_start(pgp_pk_sesskey_t *pubenc, pgp_sk_sesskey_t *symenc, void *param) +{ + rnp_op_verify_t op = (rnp_op_verify_t) param; + /* store only top-level encrypted stream info */ + if (op->encrypted_layers > 1) { + return; + } + if (pubenc) { + op->used_recipient = (rnp_recipient_handle_t) calloc(1, sizeof(*op->used_recipient)); + if (!op->used_recipient) { + FFI_LOG(op->ffi, "allocation failed"); + return; + } + recipient_handle_from_pk_sesskey(op->used_recipient, *pubenc); + return; + } + if (symenc) { + op->used_symenc = (rnp_symenc_handle_t) calloc(1, sizeof(*op->used_symenc)); + if (!op->used_symenc) { + FFI_LOG(op->ffi, "allocation failed"); + return; + } + symenc_handle_from_sk_sesskey(op->used_symenc, *symenc); + return; + } + FFI_LOG(op->ffi, "Warning! Both pubenc and symenc are NULL."); +} + +static void +rnp_verify_on_decryption_info(bool mdc, pgp_aead_alg_t aead, pgp_symm_alg_t salg, void *param) +{ + rnp_op_verify_t op = (rnp_op_verify_t) param; + /* store only top-level encrypted stream info for now */ + if (op->encrypted_layers > 1) { + return; + } + op->mdc = mdc; + op->aead = aead; + op->salg = salg; + op->encrypted = true; +} + +static void +rnp_verify_on_decryption_done(bool validated, void *param) +{ + rnp_op_verify_t op = (rnp_op_verify_t) param; + if (op->encrypted_layers > 1) { + return; + } + op->validated = validated; +} + +rnp_result_t +rnp_op_verify_create(rnp_op_verify_t *op, + rnp_ffi_t ffi, + rnp_input_t input, + rnp_output_t output) +try { + if (!op || !ffi || !input || !output) { + return RNP_ERROR_NULL_POINTER; + } + + *op = new rnp_op_verify_st(); + rnp_ctx_init_ffi((*op)->rnpctx, ffi); + (*op)->ffi = ffi; + (*op)->input = input; + (*op)->output = output; + + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_detached_create(rnp_op_verify_t *op, + rnp_ffi_t ffi, + rnp_input_t input, + rnp_input_t signature) +try { + if (!op || !ffi || !input || !signature) { + return RNP_ERROR_NULL_POINTER; + } + + *op = new rnp_op_verify_st(); + rnp_ctx_init_ffi((*op)->rnpctx, ffi); + (*op)->rnpctx.detached = true; + (*op)->ffi = ffi; + (*op)->input = signature; + (*op)->detached_input = input; + + return RNP_SUCCESS; +} +FFI_GUARD + +static pgp_key_t * +ffi_decrypt_key_provider(const pgp_key_request_ctx_t *ctx, void *userdata) +{ + rnp_decryption_kp_param_t *kparam = (rnp_decryption_kp_param_t *) userdata; + + auto ffi = kparam->op->ffi; + bool hidden = ctx->secret && (ctx->search.type == PGP_KEY_SEARCH_KEYID) && + (ctx->search.by.keyid == pgp_key_id_t({})); + /* default to the FFI key provider if not hidden keyid request */ + if (!hidden) { + return ffi->key_provider.callback(ctx, ffi->key_provider.userdata); + } + /* if we had hidden request and last key is NULL then key search was exhausted */ + if (!kparam->op->allow_hidden || (kparam->has_hidden && !kparam->last)) { + return NULL; + } + /* inform user about the hidden recipient before searching through the loaded keys */ + if (!kparam->has_hidden) { + call_key_callback(ffi, ctx->search, ctx->secret); + } + kparam->has_hidden = true; + kparam->last = find_key(ffi, ctx->search, true, true, kparam->last); + return kparam->last; +} + +rnp_result_t +rnp_op_verify_set_flags(rnp_op_verify_t op, uint32_t flags) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + /* Allow to decrypt without valid signatures */ + op->ignore_sigs = extract_flag(flags, RNP_VERIFY_IGNORE_SIGS_ON_DECRYPT); + /* Strict mode: require all signatures to be valid */ + op->require_all_sigs = extract_flag(flags, RNP_VERIFY_REQUIRE_ALL_SIGS); + /* Allow hidden recipients if any */ + op->allow_hidden = extract_flag(flags, RNP_VERIFY_ALLOW_HIDDEN_RECIPIENT); + + if (flags) { + FFI_LOG(op->ffi, "Unknown operation flags: %x", flags); + return RNP_ERROR_BAD_PARAMETERS; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_execute(rnp_op_verify_t op) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_parse_handler_t handler; + + handler.password_provider = &op->ffi->pass_provider; + + rnp_decryption_kp_param_t kparam(op); + pgp_key_provider_t kprov = {ffi_decrypt_key_provider, &kparam}; + + handler.key_provider = &kprov; + handler.on_signatures = rnp_op_verify_on_signatures; + handler.src_provider = rnp_verify_src_provider; + handler.dest_provider = rnp_verify_dest_provider; + handler.on_recipients = rnp_verify_on_recipients; + handler.on_decryption_start = rnp_verify_on_decryption_start; + handler.on_decryption_info = rnp_verify_on_decryption_info; + handler.on_decryption_done = rnp_verify_on_decryption_done; + handler.param = op; + handler.ctx = &op->rnpctx; + + rnp_result_t ret = process_pgp_source(&handler, op->input->src); + /* Allow to decrypt data ignoring the signatures check if requested */ + if (op->ignore_sigs && op->validated && (ret == RNP_ERROR_SIGNATURE_INVALID)) { + ret = RNP_SUCCESS; + } + /* Allow to require all signatures be valid */ + if (op->require_all_sigs && !ret) { + for (size_t i = 0; i < op->signature_count; i++) { + if (op->signatures[i].verify_status) { + ret = RNP_ERROR_SIGNATURE_INVALID; + break; + } + } + } + if (op->output) { + dst_flush(&op->output->dst); + op->output->keep = ret == RNP_SUCCESS; + } + return ret; +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_get_signature_count(rnp_op_verify_t op, size_t *count) +try { + if (!op || !count) { + return RNP_ERROR_NULL_POINTER; + } + + *count = op->signature_count; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_get_signature_at(rnp_op_verify_t op, size_t idx, rnp_op_verify_signature_t *sig) +try { + if (!op || !sig) { + return RNP_ERROR_NULL_POINTER; + } + if (idx >= op->signature_count) { + FFI_LOG(op->ffi, "Invalid signature index: %zu", idx); + return RNP_ERROR_BAD_PARAMETERS; + } + *sig = &op->signatures[idx]; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_get_file_info(rnp_op_verify_t op, char **filename, uint32_t *mtime) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + if (mtime) { + *mtime = op->file_mtime; + } + if (filename) { + if (op->filename) { + *filename = strdup(op->filename); + } else { + *filename = NULL; + } + } + return RNP_SUCCESS; +} +FFI_GUARD + +static const char * +get_protection_mode(rnp_op_verify_t op) +{ + if (!op->encrypted) { + return "none"; + } + if (op->mdc) { + return "cfb-mdc"; + } + if (op->aead == PGP_AEAD_NONE) { + return "cfb"; + } + switch (op->aead) { + case PGP_AEAD_EAX: + return "aead-eax"; + case PGP_AEAD_OCB: + return "aead-ocb"; + default: + return "aead-unknown"; + } +} + +static const char * +get_protection_cipher(rnp_op_verify_t op) +{ + if (!op->encrypted) { + return "none"; + } + return id_str_pair::lookup(symm_alg_map, op->salg); +} + +rnp_result_t +rnp_op_verify_get_protection_info(rnp_op_verify_t op, char **mode, char **cipher, bool *valid) +try { + if (!op || (!mode && !cipher && !valid)) { + return RNP_ERROR_NULL_POINTER; + } + + if (mode) { + *mode = strdup(get_protection_mode(op)); + if (!*mode) { + return RNP_ERROR_OUT_OF_MEMORY; + } + } + if (cipher) { + *cipher = strdup(get_protection_cipher(op)); + if (!*cipher) { + return RNP_ERROR_OUT_OF_MEMORY; + } + } + if (valid) { + *valid = op->validated; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_get_recipient_count(rnp_op_verify_t op, size_t *count) +try { + if (!op || !count) { + return RNP_ERROR_NULL_POINTER; + } + *count = op->recipient_count; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_get_used_recipient(rnp_op_verify_t op, rnp_recipient_handle_t *recipient) +try { + if (!op || !recipient) { + return RNP_ERROR_NULL_POINTER; + } + *recipient = op->used_recipient; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_get_recipient_at(rnp_op_verify_t op, + size_t idx, + rnp_recipient_handle_t *recipient) +try { + if (!op || !recipient) { + return RNP_ERROR_NULL_POINTER; + } + if (idx >= op->recipient_count) { + return RNP_ERROR_BAD_PARAMETERS; + } + *recipient = &op->recipients[idx]; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_recipient_get_keyid(rnp_recipient_handle_t recipient, char **keyid) +try { + if (!recipient || !keyid) { + return RNP_ERROR_NULL_POINTER; + } + static_assert(sizeof(recipient->keyid) == PGP_KEY_ID_SIZE, + "rnp_recipient_handle_t.keyid size mismatch"); + return hex_encode_value(recipient->keyid, PGP_KEY_ID_SIZE, keyid); +} +FFI_GUARD + +rnp_result_t +rnp_recipient_get_alg(rnp_recipient_handle_t recipient, char **alg) +try { + if (!recipient || !alg) { + return RNP_ERROR_NULL_POINTER; + } + return get_map_value(pubkey_alg_map, recipient->palg, alg); +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_get_symenc_count(rnp_op_verify_t op, size_t *count) +try { + if (!op || !count) { + return RNP_ERROR_NULL_POINTER; + } + *count = op->symenc_count; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_get_used_symenc(rnp_op_verify_t op, rnp_symenc_handle_t *symenc) +try { + if (!op || !symenc) { + return RNP_ERROR_NULL_POINTER; + } + *symenc = op->used_symenc; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_get_symenc_at(rnp_op_verify_t op, size_t idx, rnp_symenc_handle_t *symenc) +try { + if (!op || !symenc) { + return RNP_ERROR_NULL_POINTER; + } + if (idx >= op->symenc_count) { + return RNP_ERROR_BAD_PARAMETERS; + } + *symenc = &op->symencs[idx]; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_symenc_get_cipher(rnp_symenc_handle_t symenc, char **cipher) +try { + if (!symenc || !cipher) { + return RNP_ERROR_NULL_POINTER; + } + return get_map_value(symm_alg_map, symenc->alg, cipher); +} +FFI_GUARD + +rnp_result_t +rnp_symenc_get_aead_alg(rnp_symenc_handle_t symenc, char **alg) +try { + if (!symenc || !alg) { + return RNP_ERROR_NULL_POINTER; + } + return get_map_value(aead_alg_map, symenc->aalg, alg); +} +FFI_GUARD + +rnp_result_t +rnp_symenc_get_hash_alg(rnp_symenc_handle_t symenc, char **alg) +try { + if (!symenc || !alg) { + return RNP_ERROR_NULL_POINTER; + } + return get_map_value(hash_alg_map, symenc->halg, alg); +} +FFI_GUARD + +rnp_result_t +rnp_symenc_get_s2k_type(rnp_symenc_handle_t symenc, char **type) +try { + if (!symenc || !type) { + return RNP_ERROR_NULL_POINTER; + } + return get_map_value(s2k_type_map, symenc->s2k_type, type); +} +FFI_GUARD + +rnp_result_t +rnp_symenc_get_s2k_iterations(rnp_symenc_handle_t symenc, uint32_t *iterations) +try { + if (!symenc || !iterations) { + return RNP_ERROR_NULL_POINTER; + } + *iterations = symenc->iterations; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_destroy(rnp_op_verify_t op) +try { + delete op; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_op_verify_st::~rnp_op_verify_st() +{ + delete[] signatures; + free(filename); + free(recipients); + free(used_recipient); + free(symencs); + free(used_symenc); +} + +rnp_result_t +rnp_op_verify_signature_get_status(rnp_op_verify_signature_t sig) +try { + if (!sig) { + return RNP_ERROR_NULL_POINTER; + } + return sig->verify_status; +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_signature_get_handle(rnp_op_verify_signature_t sig, + rnp_signature_handle_t * handle) +try { + if (!sig || !handle) { + return RNP_ERROR_NULL_POINTER; + } + + *handle = (rnp_signature_handle_t) calloc(1, sizeof(**handle)); + if (!*handle) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + try { + (*handle)->sig = new pgp_subsig_t(sig->sig_pkt); + } catch (const std::exception &e) { + FFI_LOG(sig->ffi, "%s", e.what()); + free(*handle); + return RNP_ERROR_OUT_OF_MEMORY; + } + (*handle)->ffi = sig->ffi; + (*handle)->key = NULL; + (*handle)->own_sig = true; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_signature_get_hash(rnp_op_verify_signature_t sig, char **hash) +try { + if (!sig || !hash) { + return RNP_ERROR_NULL_POINTER; + } + return get_map_value(hash_alg_map, sig->sig_pkt.halg, hash); +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_signature_get_key(rnp_op_verify_signature_t sig, rnp_key_handle_t *key) +try { + if (!sig->sig_pkt.has_keyid()) { + return RNP_ERROR_BAD_PARAMETERS; + } + rnp_ffi_t ffi = sig->ffi; + // create a search (since we'll use this later anyways) + pgp_key_search_t search(PGP_KEY_SEARCH_KEYID); + search.by.keyid = sig->sig_pkt.keyid(); + + // search the stores + pgp_key_t *pub = rnp_key_store_search(ffi->pubring, &search, NULL); + pgp_key_t *sec = rnp_key_store_search(ffi->secring, &search, NULL); + if (!pub && !sec) { + return RNP_ERROR_KEY_NOT_FOUND; + } + + struct rnp_key_handle_st *handle = (rnp_key_handle_st *) calloc(1, sizeof(*handle)); + if (!handle) { + return RNP_ERROR_OUT_OF_MEMORY; + } + handle->ffi = ffi; + handle->pub = pub; + handle->sec = sec; + handle->locator = search; + *key = handle; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_signature_get_times(rnp_op_verify_signature_t sig, + uint32_t * create, + uint32_t * expires) +try { + if (create) { + *create = sig->sig_pkt.creation(); + } + if (expires) { + *expires = sig->sig_pkt.expiration(); + } + + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_decrypt(rnp_ffi_t ffi, rnp_input_t input, rnp_output_t output) +try { + // checks + if (!ffi || !input || !output) { + return RNP_ERROR_NULL_POINTER; + } + + rnp_op_verify_t op = NULL; + rnp_result_t ret = rnp_op_verify_create(&op, ffi, input, output); + if (ret) { + return ret; + } + ret = rnp_op_verify_set_flags(op, RNP_VERIFY_IGNORE_SIGS_ON_DECRYPT); + if (!ret) { + ret = rnp_op_verify_execute(op); + } + rnp_op_verify_destroy(op); + return ret; +} +FFI_GUARD + +static rnp_result_t +str_to_locator(rnp_ffi_t ffi, + pgp_key_search_t *locator, + const char * identifier_type, + const char * identifier) +{ + // parse the identifier type + locator->type = static_cast<pgp_key_search_type_t>( + id_str_pair::lookup(identifier_type_map, identifier_type, PGP_KEY_SEARCH_UNKNOWN)); + if (locator->type == PGP_KEY_SEARCH_UNKNOWN) { + FFI_LOG(ffi, "Invalid identifier type: %s", identifier_type); + return RNP_ERROR_BAD_PARAMETERS; + } + // see what type we have + switch (locator->type) { + case PGP_KEY_SEARCH_USERID: + if (snprintf(locator->by.userid, sizeof(locator->by.userid), "%s", identifier) >= + (int) sizeof(locator->by.userid)) { + FFI_LOG(ffi, "UserID too long"); + return RNP_ERROR_BAD_PARAMETERS; + } + break; + case PGP_KEY_SEARCH_KEYID: { + if (strlen(identifier) != (PGP_KEY_ID_SIZE * 2) || + !rnp::hex_decode(identifier, locator->by.keyid.data(), locator->by.keyid.size())) { + FFI_LOG(ffi, "Invalid keyid: %s", identifier); + return RNP_ERROR_BAD_PARAMETERS; + } + } break; + case PGP_KEY_SEARCH_FINGERPRINT: { + // TODO: support v5 fingerprints + // Note: v2/v3 fingerprint are 16 bytes (32 chars) long. + if ((strlen(identifier) != (PGP_FINGERPRINT_SIZE * 2)) && (strlen(identifier) != 32)) { + FFI_LOG(ffi, "Invalid fingerprint: %s", identifier); + return RNP_ERROR_BAD_PARAMETERS; + } + locator->by.fingerprint.length = rnp::hex_decode( + identifier, locator->by.fingerprint.fingerprint, PGP_FINGERPRINT_SIZE); + if (!locator->by.fingerprint.length) { + FFI_LOG(ffi, "Invalid fingerprint: %s", identifier); + return RNP_ERROR_BAD_PARAMETERS; + } + } break; + case PGP_KEY_SEARCH_GRIP: { + if (strlen(identifier) != (PGP_KEY_GRIP_SIZE * 2) || + !rnp::hex_decode(identifier, locator->by.grip.data(), locator->by.grip.size())) { + FFI_LOG(ffi, "Invalid grip: %s", identifier); + return RNP_ERROR_BAD_PARAMETERS; + } + } break; + default: + // should never happen + assert(false); + return RNP_ERROR_BAD_STATE; + } + return RNP_SUCCESS; +} + +static bool +locator_to_str(const pgp_key_search_t &locator, + const char ** identifier_type, + char * identifier, + size_t identifier_size) +{ + // find the identifier type string with the map + *identifier_type = id_str_pair::lookup(identifier_type_map, locator.type, NULL); + if (!*identifier_type) { + return false; + } + // fill in the actual identifier + switch (locator.type) { + case PGP_KEY_SEARCH_USERID: + if (snprintf(identifier, identifier_size, "%s", locator.by.userid) >= + (int) identifier_size) { + return false; + } + break; + case PGP_KEY_SEARCH_KEYID: + if (!rnp::hex_encode( + locator.by.keyid.data(), locator.by.keyid.size(), identifier, identifier_size)) { + return false; + } + break; + case PGP_KEY_SEARCH_FINGERPRINT: + if (!rnp::hex_encode(locator.by.fingerprint.fingerprint, + locator.by.fingerprint.length, + identifier, + identifier_size)) { + return false; + } + break; + case PGP_KEY_SEARCH_GRIP: + if (!rnp::hex_encode( + locator.by.grip.data(), locator.by.grip.size(), identifier, identifier_size)) { + return false; + } + break; + default: + assert(false); + return false; + } + return true; +} + +static rnp_result_t +rnp_locate_key_int(rnp_ffi_t ffi, + const pgp_key_search_t &locator, + rnp_key_handle_t * handle, + bool require_secret = false) +{ + // search pubring + pgp_key_t *pub = rnp_key_store_search(ffi->pubring, &locator, NULL); + // search secring + pgp_key_t *sec = rnp_key_store_search(ffi->secring, &locator, NULL); + + if (require_secret && !sec) { + *handle = NULL; + return RNP_SUCCESS; + } + + if (pub || sec) { + *handle = (rnp_key_handle_t) malloc(sizeof(**handle)); + if (!*handle) { + return RNP_ERROR_OUT_OF_MEMORY; + } + (*handle)->ffi = ffi; + (*handle)->pub = pub; + (*handle)->sec = sec; + (*handle)->locator = locator; + } else { + *handle = NULL; + } + return RNP_SUCCESS; +} + +rnp_result_t +rnp_locate_key(rnp_ffi_t ffi, + const char * identifier_type, + const char * identifier, + rnp_key_handle_t *handle) +try { + // checks + if (!ffi || !identifier_type || !identifier || !handle) { + return RNP_ERROR_NULL_POINTER; + } + + // figure out the identifier type + pgp_key_search_t locator; + rnp_result_t ret = str_to_locator(ffi, &locator, identifier_type, identifier); + if (ret) { + return ret; + } + + return rnp_locate_key_int(ffi, locator, handle); +} +FFI_GUARD + +rnp_result_t +rnp_key_export(rnp_key_handle_t handle, rnp_output_t output, uint32_t flags) +try { + pgp_dest_t *dst = NULL; + pgp_dest_t armordst = {}; + + // checks + if (!handle || !output) { + return RNP_ERROR_NULL_POINTER; + } + dst = &output->dst; + if ((flags & RNP_KEY_EXPORT_PUBLIC) && (flags & RNP_KEY_EXPORT_SECRET)) { + FFI_LOG(handle->ffi, "Invalid export flags, select only public or secret, not both."); + return RNP_ERROR_BAD_PARAMETERS; + } + + // handle flags + bool armored = extract_flag(flags, RNP_KEY_EXPORT_ARMORED); + pgp_key_t * key = NULL; + rnp_key_store_t *store = NULL; + if (flags & RNP_KEY_EXPORT_PUBLIC) { + extract_flag(flags, RNP_KEY_EXPORT_PUBLIC); + key = get_key_require_public(handle); + store = handle->ffi->pubring; + } else if (flags & RNP_KEY_EXPORT_SECRET) { + extract_flag(flags, RNP_KEY_EXPORT_SECRET); + key = get_key_require_secret(handle); + store = handle->ffi->secring; + } else { + FFI_LOG(handle->ffi, "must specify public or secret key for export"); + return RNP_ERROR_BAD_PARAMETERS; + } + bool export_subs = extract_flag(flags, RNP_KEY_EXPORT_SUBKEYS); + // check for any unrecognized flags + if (flags) { + FFI_LOG(handle->ffi, "unrecognized flags remaining: 0x%X", flags); + return RNP_ERROR_BAD_PARAMETERS; + } + // make sure we found our key + if (!key) { + FFI_LOG(handle->ffi, "no suitable key found"); + return RNP_ERROR_NO_SUITABLE_KEY; + } + // only PGP packets supported for now + if (key->format != PGP_KEY_STORE_GPG && key->format != PGP_KEY_STORE_KBX) { + return RNP_ERROR_NOT_IMPLEMENTED; + } + if (armored) { + auto msgtype = key->is_secret() ? PGP_ARMORED_SECRET_KEY : PGP_ARMORED_PUBLIC_KEY; + rnp_result_t res = init_armored_dst(&armordst, &output->dst, msgtype); + if (res) { + return res; + } + dst = &armordst; + } + // write + if (key->is_primary()) { + // primary key, write just the primary or primary and all subkeys + key->write_xfer(*dst, export_subs ? store : NULL); + if (dst->werr) { + return RNP_ERROR_WRITE; + } + } else { + // subkeys flag is only valid for primary + if (export_subs) { + FFI_LOG(handle->ffi, "export with subkeys requested but key is not primary"); + return RNP_ERROR_BAD_PARAMETERS; + } + // subkey, write the primary + this subkey only + pgp_key_t *primary = rnp_key_store_get_primary_key(store, key); + if (!primary) { + // shouldn't happen + return RNP_ERROR_GENERIC; + } + primary->write_xfer(*dst); + if (dst->werr) { + return RNP_ERROR_WRITE; + } + key->write_xfer(*dst); + if (dst->werr) { + return RNP_ERROR_WRITE; + } + } + if (armored) { + dst_finish(&armordst); + dst_close(&armordst, false); + } + output->keep = true; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_export_autocrypt(rnp_key_handle_t key, + rnp_key_handle_t subkey, + const char * uid, + rnp_output_t output, + uint32_t flags) +try { + if (!key || !output) { + return RNP_ERROR_NULL_POINTER; + } + bool base64 = extract_flag(flags, RNP_KEY_EXPORT_BASE64); + if (flags) { + FFI_LOG(key->ffi, "Unknown flags remaining: 0x%X", flags); + return RNP_ERROR_BAD_PARAMETERS; + } + /* Get the primary key */ + pgp_key_t *primary = get_key_prefer_public(key); + if (!primary || !primary->is_primary() || !primary->usable_for(PGP_OP_VERIFY)) { + FFI_LOG(key->ffi, "No valid signing primary key"); + return RNP_ERROR_BAD_PARAMETERS; + } + /* Get encrypting subkey */ + pgp_key_t *sub = + subkey ? get_key_prefer_public(subkey) : + find_suitable_key(PGP_OP_ENCRYPT, primary, &key->ffi->key_provider, true); + if (!sub || sub->is_primary() || !sub->usable_for(PGP_OP_ENCRYPT)) { + FFI_LOG(key->ffi, "No encrypting subkey"); + return RNP_ERROR_KEY_NOT_FOUND; + } + /* Get userid */ + size_t uididx = primary->uid_count(); + if (uid) { + for (size_t idx = 0; idx < primary->uid_count(); idx++) { + if (primary->get_uid(idx).str == uid) { + uididx = idx; + break; + } + } + } else { + if (primary->uid_count() > 1) { + FFI_LOG(key->ffi, "Ambiguous userid"); + return RNP_ERROR_BAD_PARAMETERS; + } + uididx = 0; + } + if (uididx >= primary->uid_count()) { + FFI_LOG(key->ffi, "Userid not found"); + return RNP_ERROR_BAD_PARAMETERS; + } + + /* Check whether base64 is requested */ + bool res = false; + if (base64) { + rnp::ArmoredDest armor(output->dst, PGP_ARMORED_BASE64); + res = primary->write_autocrypt(armor.dst(), *sub, uididx); + } else { + res = primary->write_autocrypt(output->dst, *sub, uididx); + } + return res ? RNP_SUCCESS : RNP_ERROR_BAD_PARAMETERS; +} +FFI_GUARD + +static pgp_key_t * +rnp_key_get_revoker(rnp_key_handle_t key) +{ + pgp_key_t *exkey = get_key_prefer_public(key); + if (!exkey) { + return NULL; + } + if (exkey->is_subkey()) { + return rnp_key_store_get_primary_key(key->ffi->secring, exkey); + } + // TODO: search through revocation key subpackets as well + return get_key_require_secret(key); +} + +static rnp_result_t +rnp_key_get_revocation(rnp_ffi_t ffi, + pgp_key_t * key, + pgp_key_t * revoker, + const char * hash, + const char * code, + const char * reason, + pgp_signature_t &sig) +{ + if (!hash) { + hash = DEFAULT_HASH_ALG; + } + pgp_hash_alg_t halg = PGP_HASH_UNKNOWN; + if (!str_to_hash_alg(hash, &halg)) { + FFI_LOG(ffi, "Unknown hash algorithm: %s", hash); + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_revoke_t revinfo = {}; + if (code && !str_to_revocation_type(code, &revinfo.code)) { + FFI_LOG(ffi, "Wrong revocation code: %s", code); + return RNP_ERROR_BAD_PARAMETERS; + } + if (revinfo.code > PGP_REVOCATION_RETIRED) { + FFI_LOG(ffi, "Wrong key revocation code: %d", (int) revinfo.code); + return RNP_ERROR_BAD_PARAMETERS; + } + if (reason) { + try { + revinfo.reason = reason; + } catch (const std::exception &e) { + FFI_LOG(ffi, "%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + } + /* unlock the secret key if needed */ + rnp::KeyLocker revlock(*revoker); + if (revoker->is_locked() && !revoker->unlock(ffi->pass_provider)) { + FFI_LOG(ffi, "Failed to unlock secret key"); + return RNP_ERROR_BAD_PASSWORD; + } + try { + revoker->gen_revocation(revinfo, halg, key->pkt(), sig, ffi->context); + } catch (const std::exception &e) { + FFI_LOG(ffi, "Failed to generate revocation signature: %s", e.what()); + return RNP_ERROR_BAD_STATE; + } + return RNP_SUCCESS; +} + +rnp_result_t +rnp_key_export_revocation(rnp_key_handle_t key, + rnp_output_t output, + uint32_t flags, + const char * hash, + const char * code, + const char * reason) +try { + if (!key || !key->ffi || !output) { + return RNP_ERROR_NULL_POINTER; + } + bool need_armor = extract_flag(flags, RNP_KEY_EXPORT_ARMORED); + if (flags) { + return RNP_ERROR_BAD_PARAMETERS; + } + + pgp_key_t *exkey = get_key_prefer_public(key); + if (!exkey || !exkey->is_primary()) { + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_key_t *revoker = rnp_key_get_revoker(key); + if (!revoker) { + FFI_LOG(key->ffi, "Revoker secret key not found"); + return RNP_ERROR_BAD_PARAMETERS; + } + + pgp_signature_t sig; + rnp_result_t ret = + rnp_key_get_revocation(key->ffi, exkey, revoker, hash, code, reason, sig); + if (ret) { + return ret; + } + + if (need_armor) { + rnp::ArmoredDest armor(output->dst, PGP_ARMORED_PUBLIC_KEY); + sig.write(armor.dst()); + ret = armor.werr(); + dst_flush(&armor.dst()); + } else { + sig.write(output->dst); + ret = output->dst.werr; + dst_flush(&output->dst); + } + output->keep = !ret; + return ret; +} +FFI_GUARD + +rnp_result_t +rnp_key_revoke( + rnp_key_handle_t key, uint32_t flags, const char *hash, const char *code, const char *reason) +try { + if (!key || !key->ffi) { + return RNP_ERROR_NULL_POINTER; + } + if (flags) { + return RNP_ERROR_BAD_PARAMETERS; + } + + pgp_key_t *exkey = get_key_prefer_public(key); + if (!exkey) { + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_key_t *revoker = rnp_key_get_revoker(key); + if (!revoker) { + FFI_LOG(key->ffi, "Revoker secret key not found"); + return RNP_ERROR_BAD_PARAMETERS; + } + + pgp_signature_t sig; + rnp_result_t ret = + rnp_key_get_revocation(key->ffi, exkey, revoker, hash, code, reason, sig); + if (ret) { + return ret; + } + pgp_sig_import_status_t pub_status = PGP_SIG_IMPORT_STATUS_UNKNOWN_KEY; + pgp_sig_import_status_t sec_status = PGP_SIG_IMPORT_STATUS_UNKNOWN_KEY; + if (key->pub) { + pub_status = rnp_key_store_import_key_signature(key->ffi->pubring, key->pub, &sig); + } + if (key->sec) { + sec_status = rnp_key_store_import_key_signature(key->ffi->secring, key->sec, &sig); + } + + if ((pub_status == PGP_SIG_IMPORT_STATUS_UNKNOWN) || + (sec_status == PGP_SIG_IMPORT_STATUS_UNKNOWN)) { + return RNP_ERROR_GENERIC; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_25519_bits_tweaked(rnp_key_handle_t key, bool *result) +try { + if (!key || !result) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *seckey = get_key_require_secret(key); + if (!seckey || seckey->is_locked() || (seckey->alg() != PGP_PKA_ECDH) || + (seckey->curve() != PGP_CURVE_25519)) { + return RNP_ERROR_BAD_PARAMETERS; + } + *result = x25519_bits_tweaked(seckey->material().ec); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_25519_bits_tweak(rnp_key_handle_t key) +try { + if (!key) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *seckey = get_key_require_secret(key); + if (!seckey || seckey->is_protected() || (seckey->alg() != PGP_PKA_ECDH) || + (seckey->curve() != PGP_CURVE_25519)) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!x25519_tweak_bits(seckey->pkt().material.ec)) { + FFI_LOG(key->ffi, "Failed to tweak 25519 key bits."); + return RNP_ERROR_BAD_STATE; + } + if (!seckey->write_sec_rawpkt(seckey->pkt(), "", key->ffi->context)) { + FFI_LOG(key->ffi, "Failed to update rawpkt."); + return RNP_ERROR_BAD_STATE; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_remove(rnp_key_handle_t key, uint32_t flags) +try { + if (!key || !key->ffi) { + return RNP_ERROR_NULL_POINTER; + } + bool pub = extract_flag(flags, RNP_KEY_REMOVE_PUBLIC); + bool sec = extract_flag(flags, RNP_KEY_REMOVE_SECRET); + bool sub = extract_flag(flags, RNP_KEY_REMOVE_SUBKEYS); + if (flags) { + FFI_LOG(key->ffi, "Unknown flags: %" PRIu32, flags); + return RNP_ERROR_BAD_PARAMETERS; + } + if (!pub && !sec) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (sub && get_key_prefer_public(key)->is_subkey()) { + return RNP_ERROR_BAD_PARAMETERS; + } + + if (pub) { + if (!key->ffi->pubring || !key->pub) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!rnp_key_store_remove_key(key->ffi->pubring, key->pub, sub)) { + return RNP_ERROR_KEY_NOT_FOUND; + } + key->pub = NULL; + } + if (sec) { + if (!key->ffi->secring || !key->sec) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!rnp_key_store_remove_key(key->ffi->secring, key->sec, sub)) { + return RNP_ERROR_KEY_NOT_FOUND; + } + key->sec = NULL; + } + return RNP_SUCCESS; +} +FFI_GUARD + +static void +report_signature_removal(rnp_ffi_t ffi, + const pgp_key_t & key, + rnp_key_signatures_cb sigcb, + void * app_ctx, + pgp_subsig_t & keysig, + bool & remove) +{ + if (!sigcb) { + return; + } + rnp_signature_handle_t sig = (rnp_signature_handle_t) calloc(1, sizeof(*sig)); + if (!sig) { + FFI_LOG(ffi, "Signature handle allocation failed."); + return; + } + sig->ffi = ffi; + sig->key = &key; + sig->sig = &keysig; + sig->own_sig = false; + uint32_t action = remove ? RNP_KEY_SIGNATURE_REMOVE : RNP_KEY_SIGNATURE_KEEP; + sigcb(ffi, app_ctx, sig, &action); + switch (action) { + case RNP_KEY_SIGNATURE_REMOVE: + remove = true; + break; + case RNP_KEY_SIGNATURE_KEEP: + remove = false; + break; + default: + FFI_LOG(ffi, "Invalid signature removal action: %" PRIu32, action); + break; + } + rnp_signature_handle_destroy(sig); +} + +static bool +signature_needs_removal(rnp_ffi_t ffi, const pgp_key_t &key, pgp_subsig_t &sig, uint32_t flags) +{ + /* quick check for non-self signatures */ + bool nonself = flags & RNP_KEY_SIGNATURE_NON_SELF_SIG; + if (nonself && key.is_primary() && !key.is_signer(sig)) { + return true; + } + if (nonself && key.is_subkey()) { + pgp_key_t *primary = rnp_key_store_get_primary_key(ffi->pubring, &key); + if (primary && !primary->is_signer(sig)) { + return true; + } + } + /* unknown signer */ + pgp_key_t *signer = pgp_sig_get_signer(sig, ffi->pubring, &ffi->key_provider); + if (!signer && (flags & RNP_KEY_SIGNATURE_UNKNOWN_KEY)) { + return true; + } + /* validate signature if didn't */ + if (signer && !sig.validated()) { + signer->validate_sig(key, sig, ffi->context); + } + /* we cannot check for invalid/expired if sig was not validated */ + if (!sig.validated()) { + return false; + } + if ((flags & RNP_KEY_SIGNATURE_INVALID) && !sig.validity.valid) { + return true; + } + return false; +} + +static void +remove_key_signatures(rnp_ffi_t ffi, + pgp_key_t & pub, + pgp_key_t * sec, + uint32_t flags, + rnp_key_signatures_cb sigcb, + void * app_ctx) +{ + std::vector<pgp_sig_id_t> sigs; + + for (size_t idx = 0; idx < pub.sig_count(); idx++) { + pgp_subsig_t &sig = pub.get_sig(idx); + bool remove = signature_needs_removal(ffi, pub, sig, flags); + report_signature_removal(ffi, pub, sigcb, app_ctx, sig, remove); + if (remove) { + sigs.push_back(sig.sigid); + } + } + size_t deleted = pub.del_sigs(sigs); + if (deleted != sigs.size()) { + FFI_LOG(ffi, "Invalid deleted sigs count: %zu instead of %zu.", deleted, sigs.size()); + } + /* delete from the secret key if any */ + if (sec && (sec != &pub)) { + sec->del_sigs(sigs); + } +} + +rnp_result_t +rnp_key_remove_signatures(rnp_key_handle_t handle, + uint32_t flags, + rnp_key_signatures_cb sigcb, + void * app_ctx) +try { + if (!handle) { + return RNP_ERROR_NULL_POINTER; + } + if (!flags && !sigcb) { + return RNP_ERROR_BAD_PARAMETERS; + } + uint32_t origflags = flags; + extract_flag(flags, + RNP_KEY_SIGNATURE_INVALID | RNP_KEY_SIGNATURE_NON_SELF_SIG | + RNP_KEY_SIGNATURE_UNKNOWN_KEY); + if (flags) { + FFI_LOG(handle->ffi, "Invalid flags: %" PRIu32, flags); + return RNP_ERROR_BAD_PARAMETERS; + } + flags = origflags; + + pgp_key_t *key = get_key_prefer_public(handle); + if (!key) { + return RNP_ERROR_BAD_PARAMETERS; + } + + /* process key itself */ + pgp_key_t *sec = get_key_require_secret(handle); + remove_key_signatures(handle->ffi, *key, sec, flags, sigcb, app_ctx); + + /* process subkeys */ + for (size_t idx = 0; key->is_primary() && (idx < key->subkey_count()); idx++) { + pgp_key_t *sub = pgp_key_get_subkey(key, handle->ffi->pubring, idx); + if (!sub) { + FFI_LOG(handle->ffi, "Failed to get subkey at idx %zu.", idx); + continue; + } + pgp_key_t *subsec = rnp_key_store_get_key_by_fpr(handle->ffi->secring, sub->fp()); + remove_key_signatures(handle->ffi, *sub, subsec, flags, sigcb, app_ctx); + } + /* revalidate key/subkey */ + key->revalidate(*handle->ffi->pubring); + if (sec) { + sec->revalidate(*handle->ffi->secring); + } + return RNP_SUCCESS; +} +FFI_GUARD + +static bool +pk_alg_allows_custom_curve(pgp_pubkey_alg_t pkalg) +{ + switch (pkalg) { + case PGP_PKA_ECDH: + case PGP_PKA_ECDSA: + case PGP_PKA_SM2: + return true; + default: + return false; + } +} + +static bool +parse_preferences(json_object *jso, pgp_user_prefs_t &prefs) +{ + static const struct { + const char * key; + enum json_type type; + } properties[] = {{"hashes", json_type_array}, + {"ciphers", json_type_array}, + {"compression", json_type_array}, + {"key server", json_type_string}}; + + for (size_t iprop = 0; iprop < ARRAY_SIZE(properties); iprop++) { + json_object *value = NULL; + const char * key = properties[iprop].key; + + if (!json_object_object_get_ex(jso, key, &value)) { + continue; + } + + if (!json_object_is_type(value, properties[iprop].type)) { + return false; + } + try { + if (rnp::str_case_eq(key, "hashes")) { + int length = json_object_array_length(value); + for (int i = 0; i < length; i++) { + json_object *item = json_object_array_get_idx(value, i); + if (!json_object_is_type(item, json_type_string)) { + return false; + } + pgp_hash_alg_t hash_alg = PGP_HASH_UNKNOWN; + if (!str_to_hash_alg(json_object_get_string(item), &hash_alg)) { + return false; + } + prefs.add_hash_alg(hash_alg); + } + } else if (rnp::str_case_eq(key, "ciphers")) { + int length = json_object_array_length(value); + for (int i = 0; i < length; i++) { + json_object *item = json_object_array_get_idx(value, i); + if (!json_object_is_type(item, json_type_string)) { + return false; + } + pgp_symm_alg_t symm_alg = PGP_SA_UNKNOWN; + if (!str_to_cipher(json_object_get_string(item), &symm_alg)) { + return false; + } + prefs.add_symm_alg(symm_alg); + } + } else if (rnp::str_case_eq(key, "compression")) { + int length = json_object_array_length(value); + for (int i = 0; i < length; i++) { + json_object *item = json_object_array_get_idx(value, i); + if (!json_object_is_type(item, json_type_string)) { + return false; + } + pgp_compression_type_t z_alg = PGP_C_UNKNOWN; + if (!str_to_compression_alg(json_object_get_string(item), &z_alg)) { + return false; + } + prefs.add_z_alg(z_alg); + } + } else if (rnp::str_case_eq(key, "key server")) { + prefs.key_server = json_object_get_string(value); + } + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } + // delete this field since it has been handled + json_object_object_del(jso, key); + } + return true; +} + +static bool +parse_keygen_crypto(json_object *jso, rnp_keygen_crypto_params_t &crypto) +{ + static const struct { + const char * key; + enum json_type type; + } properties[] = {{"type", json_type_string}, + {"curve", json_type_string}, + {"length", json_type_int}, + {"hash", json_type_string}}; + + for (size_t i = 0; i < ARRAY_SIZE(properties); i++) { + json_object *value = NULL; + const char * key = properties[i].key; + + if (!json_object_object_get_ex(jso, key, &value)) { + continue; + } + + if (!json_object_is_type(value, properties[i].type)) { + return false; + } + // TODO: make sure there are no duplicate keys in the JSON + if (rnp::str_case_eq(key, "type")) { + if (!str_to_pubkey_alg(json_object_get_string(value), &crypto.key_alg)) { + return false; + } + } else if (rnp::str_case_eq(key, "length")) { + int length = json_object_get_int(value); + switch (crypto.key_alg) { + case PGP_PKA_RSA: + crypto.rsa.modulus_bit_len = length; + break; + case PGP_PKA_DSA: + crypto.dsa.p_bitlen = length; + break; + case PGP_PKA_ELGAMAL: + crypto.elgamal.key_bitlen = length; + break; + default: + return false; + } + } else if (rnp::str_case_eq(key, "curve")) { + if (!pk_alg_allows_custom_curve(crypto.key_alg)) { + return false; + } + if (!curve_str_to_type(json_object_get_string(value), &crypto.ecc.curve)) { + return false; + } + } else if (rnp::str_case_eq(key, "hash")) { + if (!str_to_hash_alg(json_object_get_string(value), &crypto.hash_alg)) { + return false; + } + } else { + // shouldn't happen + return false; + } + // delete this field since it has been handled + json_object_object_del(jso, key); + } + return true; +} + +static bool +parse_protection(json_object *jso, rnp_key_protection_params_t &protection) +{ + static const struct { + const char * key; + enum json_type type; + } properties[] = {{"cipher", json_type_string}, + {"mode", json_type_string}, + {"iterations", json_type_int}, + {"hash", json_type_string}}; + + for (size_t i = 0; i < ARRAY_SIZE(properties); i++) { + json_object *value = NULL; + const char * key = properties[i].key; + + if (!json_object_object_get_ex(jso, key, &value)) { + continue; + } + + if (!json_object_is_type(value, properties[i].type)) { + return false; + } + // TODO: make sure there are no duplicate keys in the JSON + if (rnp::str_case_eq(key, "cipher")) { + if (!str_to_cipher(json_object_get_string(value), &protection.symm_alg)) { + return false; + } + } else if (rnp::str_case_eq(key, "mode")) { + if (!str_to_cipher_mode(json_object_get_string(value), &protection.cipher_mode)) { + return false; + } + } else if (rnp::str_case_eq(key, "iterations")) { + protection.iterations = json_object_get_int(value); + } else if (rnp::str_case_eq(key, "hash")) { + if (!str_to_hash_alg(json_object_get_string(value), &protection.hash_alg)) { + return false; + } + } else { + // shouldn't happen + return false; + } + // delete this field since it has been handled + json_object_object_del(jso, key); + } + return true; +} + +static bool +parse_keygen_primary(json_object * jso, + rnp_keygen_primary_desc_t & desc, + rnp_key_protection_params_t &prot) +{ + static const char *properties[] = { + "userid", "usage", "expiration", "preferences", "protection"}; + auto &cert = desc.cert; + + if (!parse_keygen_crypto(jso, desc.crypto)) { + return false; + } + for (size_t i = 0; i < ARRAY_SIZE(properties); i++) { + json_object *value = NULL; + const char * key = properties[i]; + + if (!json_object_object_get_ex(jso, key, &value)) { + continue; + } + if (rnp::str_case_eq(key, "userid")) { + if (!json_object_is_type(value, json_type_string)) { + return false; + } + auto uid = json_object_get_string(value); + if (strlen(uid) > MAX_ID_LENGTH) { + return false; + } + cert.userid = json_object_get_string(value); + } else if (rnp::str_case_eq(key, "usage")) { + switch (json_object_get_type(value)) { + case json_type_array: { + int length = json_object_array_length(value); + for (int j = 0; j < length; j++) { + json_object *item = json_object_array_get_idx(value, j); + if (!json_object_is_type(item, json_type_string)) { + return false; + } + uint8_t flag = 0; + if (!str_to_key_flag(json_object_get_string(item), &flag)) { + return false; + } + // check for duplicate + if (cert.key_flags & flag) { + return false; + } + cert.key_flags |= flag; + } + } break; + case json_type_string: { + if (!str_to_key_flag(json_object_get_string(value), &cert.key_flags)) { + return false; + } + } break; + default: + return false; + } + } else if (rnp::str_case_eq(key, "expiration")) { + if (!json_object_is_type(value, json_type_int)) { + return false; + } + cert.key_expiration = json_object_get_int(value); + } else if (rnp::str_case_eq(key, "preferences")) { + if (!json_object_is_type(value, json_type_object)) { + return false; + } + if (!parse_preferences(value, cert.prefs)) { + return false; + } + if (json_object_object_length(value)) { + return false; + } + } else if (rnp::str_case_eq(key, "protection")) { + if (!json_object_is_type(value, json_type_object)) { + return false; + } + if (!parse_protection(value, prot)) { + return false; + } + if (json_object_object_length(value)) { + return false; + } + } + // delete this field since it has been handled + json_object_object_del(jso, key); + } + return !json_object_object_length(jso); +} + +static bool +parse_keygen_sub(json_object * jso, + rnp_keygen_subkey_desc_t & desc, + rnp_key_protection_params_t &prot) +{ + static const char *properties[] = {"usage", "expiration", "protection"}; + auto & binding = desc.binding; + + if (!parse_keygen_crypto(jso, desc.crypto)) { + return false; + } + for (size_t i = 0; i < ARRAY_SIZE(properties); i++) { + json_object *value = NULL; + const char * key = properties[i]; + + if (!json_object_object_get_ex(jso, key, &value)) { + continue; + } + if (rnp::str_case_eq(key, "usage")) { + switch (json_object_get_type(value)) { + case json_type_array: { + int length = json_object_array_length(value); + for (int j = 0; j < length; j++) { + json_object *item = json_object_array_get_idx(value, j); + if (!json_object_is_type(item, json_type_string)) { + return false; + } + uint8_t flag = 0; + if (!str_to_key_flag(json_object_get_string(item), &flag)) { + return false; + } + if (binding.key_flags & flag) { + return false; + } + binding.key_flags |= flag; + } + } break; + case json_type_string: { + if (!str_to_key_flag(json_object_get_string(value), &binding.key_flags)) { + return false; + } + } break; + default: + return false; + } + } else if (rnp::str_case_eq(key, "expiration")) { + if (!json_object_is_type(value, json_type_int)) { + return false; + } + binding.key_expiration = json_object_get_int(value); + } else if (rnp::str_case_eq(key, "protection")) { + if (!json_object_is_type(value, json_type_object)) { + return false; + } + if (!parse_protection(value, prot)) { + return false; + } + if (json_object_object_length(value)) { + return false; + } + } + // delete this field since it has been handled + json_object_object_del(jso, key); + } + return !json_object_object_length(jso); +} + +static bool +gen_json_grips(char **result, const pgp_key_t *primary, const pgp_key_t *sub) +{ + if (!result) { + return true; + } + + json_object *jso = json_object_new_object(); + if (!jso) { + return false; + } + rnp::JSONObject jsowrap(jso); + + char grip[PGP_KEY_GRIP_SIZE * 2 + 1]; + if (primary) { + json_object *jsoprimary = json_object_new_object(); + if (!jsoprimary) { + return false; + } + json_object_object_add(jso, "primary", jsoprimary); + if (!rnp::hex_encode( + primary->grip().data(), primary->grip().size(), grip, sizeof(grip))) { + return false; + } + json_object *jsogrip = json_object_new_string(grip); + if (!jsogrip) { + return false; + } + json_object_object_add(jsoprimary, "grip", jsogrip); + } + if (sub) { + json_object *jsosub = json_object_new_object(); + if (!jsosub) { + return false; + } + json_object_object_add(jso, "sub", jsosub); + if (!rnp::hex_encode(sub->grip().data(), sub->grip().size(), grip, sizeof(grip))) { + return false; + } + json_object *jsogrip = json_object_new_string(grip); + if (!jsogrip) { + return false; + } + json_object_object_add(jsosub, "grip", jsogrip); + } + *result = strdup(json_object_to_json_string_ext(jso, JSON_C_TO_STRING_PRETTY)); + return *result; +} + +static rnp_result_t +gen_json_primary_key(rnp_ffi_t ffi, + json_object * jsoparams, + rnp_key_protection_params_t &prot, + pgp_fingerprint_t & fp, + bool protect) +{ + rnp_keygen_primary_desc_t desc = {}; + // desc.crypto is a union + // so at least Clang 12 on Windows zero-initializes the first union member only + // keeping the "larger" member partially unintialized + desc.crypto.dsa.q_bitlen = 0; + + desc.cert.key_expiration = DEFAULT_KEY_EXPIRATION; + if (!parse_keygen_primary(jsoparams, desc, prot)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + pgp_key_t pub; + pgp_key_t sec; + desc.crypto.ctx = &ffi->context; + if (!pgp_generate_primary_key(desc, true, sec, pub, ffi->secring->format)) { + return RNP_ERROR_GENERIC; + } + if (!rnp_key_store_add_key(ffi->pubring, &pub)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + /* encrypt secret key if specified */ + if (protect && prot.symm_alg && !sec.protect(prot, ffi->pass_provider, ffi->context)) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!rnp_key_store_add_key(ffi->secring, &sec)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + fp = pub.fp(); + return RNP_SUCCESS; +} + +static rnp_result_t +gen_json_subkey(rnp_ffi_t ffi, + json_object * jsoparams, + pgp_key_t & prim_pub, + pgp_key_t & prim_sec, + pgp_fingerprint_t &fp) +{ + rnp_keygen_subkey_desc_t desc = {}; + rnp_key_protection_params_t prot = {}; + + desc.binding.key_expiration = DEFAULT_KEY_EXPIRATION; + if (!parse_keygen_sub(jsoparams, desc, prot)) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!desc.binding.key_flags) { + /* Generate encrypt-only subkeys by default */ + desc.binding.key_flags = PGP_KF_ENCRYPT; + } + pgp_key_t pub; + pgp_key_t sec; + desc.crypto.ctx = &ffi->context; + if (!pgp_generate_subkey(desc, + true, + prim_sec, + prim_pub, + sec, + pub, + ffi->pass_provider, + ffi->secring->format)) { + return RNP_ERROR_GENERIC; + } + if (!rnp_key_store_add_key(ffi->pubring, &pub)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + /* encrypt subkey if specified */ + if (prot.symm_alg && !sec.protect(prot, ffi->pass_provider, ffi->context)) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!rnp_key_store_add_key(ffi->secring, &sec)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + fp = pub.fp(); + return RNP_SUCCESS; +} + +rnp_result_t +rnp_generate_key_json(rnp_ffi_t ffi, const char *json, char **results) +try { + // checks + if (!ffi || !ffi->secring || !json) { + return RNP_ERROR_NULL_POINTER; + } + + // parse the JSON + json_tokener_error error; + json_object * jso = json_tokener_parse_verbose(json, &error); + if (!jso) { + // syntax error or some other issue + FFI_LOG(ffi, "Invalid JSON: %s", json_tokener_error_desc(error)); + return RNP_ERROR_BAD_FORMAT; + } + rnp::JSONObject jsowrap(jso); + + // locate the appropriate sections + rnp_result_t ret = RNP_ERROR_GENERIC; + json_object *jsoprimary = NULL; + json_object *jsosub = NULL; + { + json_object_object_foreach(jso, key, value) + { + json_object **dest = NULL; + + if (rnp::str_case_eq(key, "primary")) { + dest = &jsoprimary; + } else if (rnp::str_case_eq(key, "sub")) { + dest = &jsosub; + } else { + // unrecognized key in the object + FFI_LOG(ffi, "Unexpected key in JSON: %s", key); + return RNP_ERROR_BAD_PARAMETERS; + } + + // duplicate "primary"/"sub" + if (*dest) { + return RNP_ERROR_BAD_PARAMETERS; + } + *dest = value; + } + } + + if (!jsoprimary && !jsosub) { + return RNP_ERROR_BAD_PARAMETERS; + } + + // generate primary key + pgp_key_t * prim_pub = NULL; + pgp_key_t * prim_sec = NULL; + rnp_key_protection_params_t prim_prot = {}; + pgp_fingerprint_t fp; + if (jsoprimary) { + ret = gen_json_primary_key(ffi, jsoprimary, prim_prot, fp, !jsosub); + if (ret) { + return ret; + } + prim_pub = rnp_key_store_get_key_by_fpr(ffi->pubring, fp); + if (!jsosub) { + if (!gen_json_grips(results, prim_pub, NULL)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + return RNP_SUCCESS; + } + prim_sec = rnp_key_store_get_key_by_fpr(ffi->secring, fp); + } else { + /* generate subkey only - find primary key via JSON params */ + json_object *jsoparent = NULL; + if (!json_object_object_get_ex(jsosub, "primary", &jsoparent) || + json_object_object_length(jsoparent) != 1) { + return RNP_ERROR_BAD_PARAMETERS; + } + const char *identifier_type = NULL; + const char *identifier = NULL; + json_object_object_foreach(jsoparent, key, value) + { + if (!json_object_is_type(value, json_type_string)) { + return RNP_ERROR_BAD_PARAMETERS; + } + identifier_type = key; + identifier = json_object_get_string(value); + } + if (!identifier_type || !identifier) { + return RNP_ERROR_BAD_STATE; + } + + pgp_key_search_t locator; + rnp_result_t tmpret = str_to_locator(ffi, &locator, identifier_type, identifier); + if (tmpret) { + return tmpret; + } + + prim_pub = rnp_key_store_search(ffi->pubring, &locator, NULL); + prim_sec = rnp_key_store_search(ffi->secring, &locator, NULL); + if (!prim_sec || !prim_pub) { + return RNP_ERROR_KEY_NOT_FOUND; + } + json_object_object_del(jsosub, "primary"); + } + + /* Generate subkey */ + ret = gen_json_subkey(ffi, jsosub, *prim_pub, *prim_sec, fp); + if (ret) { + if (jsoprimary) { + /* do not leave generated primary key in keyring */ + rnp_key_store_remove_key(ffi->pubring, prim_pub, false); + rnp_key_store_remove_key(ffi->secring, prim_sec, false); + } + return ret; + } + /* Protect the primary key now */ + if (prim_prot.symm_alg && + !prim_sec->protect(prim_prot, ffi->pass_provider, ffi->context)) { + rnp_key_store_remove_key(ffi->pubring, prim_pub, true); + rnp_key_store_remove_key(ffi->secring, prim_sec, true); + return RNP_ERROR_BAD_PARAMETERS; + } + + pgp_key_t *sub_pub = rnp_key_store_get_key_by_fpr(ffi->pubring, fp); + bool res = gen_json_grips(results, jsoprimary ? prim_pub : NULL, sub_pub); + return res ? RNP_SUCCESS : RNP_ERROR_OUT_OF_MEMORY; +} +FFI_GUARD + +rnp_result_t +rnp_generate_key_ex(rnp_ffi_t ffi, + const char * key_alg, + const char * sub_alg, + uint32_t key_bits, + uint32_t sub_bits, + const char * key_curve, + const char * sub_curve, + const char * userid, + const char * password, + rnp_key_handle_t *key) +try { + rnp_op_generate_t op = NULL; + rnp_op_generate_t subop = NULL; + rnp_key_handle_t primary = NULL; + rnp_key_handle_t subkey = NULL; + rnp_result_t ret = RNP_ERROR_KEY_GENERATION; + + /* generate primary key */ + if ((ret = rnp_op_generate_create(&op, ffi, key_alg))) { + return ret; + } + if (key_bits && (ret = rnp_op_generate_set_bits(op, key_bits))) { + goto done; + } + if (key_curve && (ret = rnp_op_generate_set_curve(op, key_curve))) { + goto done; + } + if ((ret = rnp_op_generate_set_userid(op, userid))) { + goto done; + } + if ((ret = rnp_op_generate_add_usage(op, "sign"))) { + goto done; + } + if ((ret = rnp_op_generate_add_usage(op, "certify"))) { + goto done; + } + if ((ret = rnp_op_generate_execute(op))) { + goto done; + } + if ((ret = rnp_op_generate_get_key(op, &primary))) { + goto done; + } + /* generate subkey if requested */ + if (!sub_alg) { + goto done; + } + if ((ret = rnp_op_generate_subkey_create(&subop, ffi, primary, sub_alg))) { + goto done; + } + if (sub_bits && (ret = rnp_op_generate_set_bits(subop, sub_bits))) { + goto done; + } + if (sub_curve && (ret = rnp_op_generate_set_curve(subop, sub_curve))) { + goto done; + } + if (password && (ret = rnp_op_generate_set_protection_password(subop, password))) { + goto done; + } + if ((ret = rnp_op_generate_add_usage(subop, "encrypt"))) { + goto done; + } + if ((ret = rnp_op_generate_execute(subop))) { + goto done; + } + if ((ret = rnp_op_generate_get_key(subop, &subkey))) { + goto done; + } +done: + /* only now will protect the primary key - to not spend time on unlocking to sign + * subkey */ + if (!ret && password) { + ret = rnp_key_protect(primary, password, NULL, NULL, NULL, 0); + } + if (ret && primary) { + rnp_key_remove(primary, RNP_KEY_REMOVE_PUBLIC | RNP_KEY_REMOVE_SECRET); + } + if (ret && subkey) { + rnp_key_remove(subkey, RNP_KEY_REMOVE_PUBLIC | RNP_KEY_REMOVE_SECRET); + } + if (!ret && key) { + *key = primary; + } else { + rnp_key_handle_destroy(primary); + } + rnp_key_handle_destroy(subkey); + rnp_op_generate_destroy(op); + rnp_op_generate_destroy(subop); + return ret; +} +FFI_GUARD + +rnp_result_t +rnp_generate_key_rsa(rnp_ffi_t ffi, + uint32_t bits, + uint32_t subbits, + const char * userid, + const char * password, + rnp_key_handle_t *key) +try { + return rnp_generate_key_ex(ffi, + RNP_ALGNAME_RSA, + subbits ? RNP_ALGNAME_RSA : NULL, + bits, + subbits, + NULL, + NULL, + userid, + password, + key); +} +FFI_GUARD + +rnp_result_t +rnp_generate_key_dsa_eg(rnp_ffi_t ffi, + uint32_t bits, + uint32_t subbits, + const char * userid, + const char * password, + rnp_key_handle_t *key) +try { + return rnp_generate_key_ex(ffi, + RNP_ALGNAME_DSA, + subbits ? RNP_ALGNAME_ELGAMAL : NULL, + bits, + subbits, + NULL, + NULL, + userid, + password, + key); +} +FFI_GUARD + +rnp_result_t +rnp_generate_key_ec(rnp_ffi_t ffi, + const char * curve, + const char * userid, + const char * password, + rnp_key_handle_t *key) +try { + return rnp_generate_key_ex( + ffi, RNP_ALGNAME_ECDSA, RNP_ALGNAME_ECDH, 0, 0, curve, curve, userid, password, key); +} +FFI_GUARD + +rnp_result_t +rnp_generate_key_25519(rnp_ffi_t ffi, + const char * userid, + const char * password, + rnp_key_handle_t *key) +try { + return rnp_generate_key_ex(ffi, + RNP_ALGNAME_EDDSA, + RNP_ALGNAME_ECDH, + 0, + 0, + NULL, + "Curve25519", + userid, + password, + key); +} +FFI_GUARD + +rnp_result_t +rnp_generate_key_sm2(rnp_ffi_t ffi, + const char * userid, + const char * password, + rnp_key_handle_t *key) +try { + return rnp_generate_key_ex( + ffi, RNP_ALGNAME_SM2, RNP_ALGNAME_SM2, 0, 0, NULL, NULL, userid, password, key); +} +FFI_GUARD + +static pgp_key_flags_t +default_key_flags(pgp_pubkey_alg_t alg, bool subkey) +{ + switch (alg) { + case PGP_PKA_RSA: + return subkey ? PGP_KF_ENCRYPT : pgp_key_flags_t(PGP_KF_SIGN | PGP_KF_CERTIFY); + case PGP_PKA_DSA: + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: + return subkey ? PGP_KF_SIGN : pgp_key_flags_t(PGP_KF_SIGN | PGP_KF_CERTIFY); + case PGP_PKA_SM2: + return subkey ? PGP_KF_ENCRYPT : pgp_key_flags_t(PGP_KF_SIGN | PGP_KF_CERTIFY); + case PGP_PKA_ECDH: + case PGP_PKA_ELGAMAL: + return PGP_KF_ENCRYPT; + default: + return PGP_KF_NONE; + } +} + +rnp_result_t +rnp_op_generate_create(rnp_op_generate_t *op, rnp_ffi_t ffi, const char *alg) +try { + pgp_pubkey_alg_t key_alg = PGP_PKA_NOTHING; + + if (!op || !ffi || !alg) { + return RNP_ERROR_NULL_POINTER; + } + + if (!ffi->pubring || !ffi->secring) { + return RNP_ERROR_BAD_PARAMETERS; + } + + if (!str_to_pubkey_alg(alg, &key_alg)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + if (!(pgp_pk_alg_capabilities(key_alg) & PGP_KF_SIGN)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + *op = new rnp_op_generate_st(); + (*op)->ffi = ffi; + (*op)->primary = true; + (*op)->crypto.key_alg = key_alg; + (*op)->crypto.ctx = &ffi->context; + (*op)->cert.key_flags = default_key_flags(key_alg, false); + (*op)->cert.key_expiration = DEFAULT_KEY_EXPIRATION; + + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_subkey_create(rnp_op_generate_t *op, + rnp_ffi_t ffi, + rnp_key_handle_t primary, + const char * alg) +try { + if (!op || !ffi || !alg || !primary) { + return RNP_ERROR_NULL_POINTER; + } + + if (!ffi->pubring || !ffi->secring) { + return RNP_ERROR_BAD_PARAMETERS; + } + + /* TODO: should we do these checks here or may leave it up till generate call? */ + if (!primary->sec || !primary->sec->usable_for(PGP_OP_ADD_SUBKEY)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + pgp_pubkey_alg_t key_alg = PGP_PKA_NOTHING; + if (!str_to_pubkey_alg(alg, &key_alg)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + *op = new rnp_op_generate_st(); + (*op)->ffi = ffi; + (*op)->primary = false; + (*op)->crypto.key_alg = key_alg; + (*op)->crypto.ctx = &ffi->context; + (*op)->binding.key_flags = default_key_flags(key_alg, true); + (*op)->binding.key_expiration = DEFAULT_KEY_EXPIRATION; + (*op)->primary_sec = primary->sec; + (*op)->primary_pub = primary->pub; + + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_set_bits(rnp_op_generate_t op, uint32_t bits) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + + switch (op->crypto.key_alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + op->crypto.rsa.modulus_bit_len = bits; + break; + case PGP_PKA_ELGAMAL: + op->crypto.elgamal.key_bitlen = bits; + break; + case PGP_PKA_DSA: + op->crypto.dsa.p_bitlen = bits; + break; + default: + return RNP_ERROR_BAD_PARAMETERS; + } + + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_set_hash(rnp_op_generate_t op, const char *hash) +try { + if (!op || !hash) { + return RNP_ERROR_NULL_POINTER; + } + if (!str_to_hash_alg(hash, &op->crypto.hash_alg)) { + FFI_LOG(op->ffi, "Invalid hash: %s", hash); + return RNP_ERROR_BAD_PARAMETERS; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_set_dsa_qbits(rnp_op_generate_t op, uint32_t qbits) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + if (op->crypto.key_alg != PGP_PKA_DSA) { + return RNP_ERROR_BAD_PARAMETERS; + } + op->crypto.dsa.q_bitlen = qbits; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_set_curve(rnp_op_generate_t op, const char *curve) +try { + if (!op || !curve) { + return RNP_ERROR_NULL_POINTER; + } + if (!pk_alg_allows_custom_curve(op->crypto.key_alg)) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!curve_str_to_type(curve, &op->crypto.ecc.curve)) { + return RNP_ERROR_BAD_PARAMETERS; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_set_protection_password(rnp_op_generate_t op, const char *password) +try { + if (!op || !password) { + return RNP_ERROR_NULL_POINTER; + } + op->password.assign(password, password + strlen(password) + 1); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_set_request_password(rnp_op_generate_t op, bool request) +try { + if (!op || !request) { + return RNP_ERROR_NULL_POINTER; + } + op->request_password = request; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_set_protection_cipher(rnp_op_generate_t op, const char *cipher) +try { + if (!op || !cipher) { + return RNP_ERROR_NULL_POINTER; + } + if (!str_to_cipher(cipher, &op->protection.symm_alg)) { + return RNP_ERROR_BAD_PARAMETERS; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_set_protection_hash(rnp_op_generate_t op, const char *hash) +try { + if (!op || !hash) { + return RNP_ERROR_NULL_POINTER; + } + if (!str_to_hash_alg(hash, &op->protection.hash_alg)) { + return RNP_ERROR_BAD_PARAMETERS; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_set_protection_mode(rnp_op_generate_t op, const char *mode) +try { + if (!op || !mode) { + return RNP_ERROR_NULL_POINTER; + } + if (!str_to_cipher_mode(mode, &op->protection.cipher_mode)) { + return RNP_ERROR_BAD_PARAMETERS; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_set_protection_iterations(rnp_op_generate_t op, uint32_t iterations) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + op->protection.iterations = iterations; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_add_usage(rnp_op_generate_t op, const char *usage) +try { + if (!op || !usage) { + return RNP_ERROR_NULL_POINTER; + } + uint8_t flag = 0; + if (!str_to_key_flag(usage, &flag)) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!(pgp_pk_alg_capabilities(op->crypto.key_alg) & flag)) { + return RNP_ERROR_NOT_SUPPORTED; + } + if (op->primary) { + op->cert.key_flags |= flag; + } else { + op->binding.key_flags |= flag; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_clear_usage(rnp_op_generate_t op) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + if (op->primary) { + op->cert.key_flags = 0; + } else { + op->binding.key_flags = 0; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_set_userid(rnp_op_generate_t op, const char *userid) +try { + if (!op || !userid) { + return RNP_ERROR_NULL_POINTER; + } + if (!op->primary) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (strlen(userid) > MAX_ID_LENGTH) { + return RNP_ERROR_BAD_PARAMETERS; + } + op->cert.userid = userid; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_set_expiration(rnp_op_generate_t op, uint32_t expiration) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + if (op->primary) { + op->cert.key_expiration = expiration; + } else { + op->binding.key_expiration = expiration; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_clear_pref_hashes(rnp_op_generate_t op) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + if (!op->primary) { + return RNP_ERROR_BAD_PARAMETERS; + } + op->cert.prefs.set_hash_algs({}); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_add_pref_hash(rnp_op_generate_t op, const char *hash) +try { + if (!op || !hash) { + return RNP_ERROR_NULL_POINTER; + } + if (!op->primary) { + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_hash_alg_t hash_alg = PGP_HASH_UNKNOWN; + if (!str_to_hash_alg(hash, &hash_alg)) { + return RNP_ERROR_BAD_PARAMETERS; + } + op->cert.prefs.add_hash_alg(hash_alg); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_clear_pref_compression(rnp_op_generate_t op) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + if (!op->primary) { + return RNP_ERROR_BAD_PARAMETERS; + } + op->cert.prefs.set_z_algs({}); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_add_pref_compression(rnp_op_generate_t op, const char *compression) +try { + if (!op || !compression) { + return RNP_ERROR_NULL_POINTER; + } + if (!op->primary) { + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_compression_type_t z_alg = PGP_C_UNKNOWN; + if (!str_to_compression_alg(compression, &z_alg)) { + return RNP_ERROR_BAD_PARAMETERS; + } + op->cert.prefs.add_z_alg(z_alg); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_clear_pref_ciphers(rnp_op_generate_t op) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + if (!op->primary) { + return RNP_ERROR_BAD_PARAMETERS; + } + op->cert.prefs.set_symm_algs({}); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_add_pref_cipher(rnp_op_generate_t op, const char *cipher) +try { + if (!op || !cipher) { + return RNP_ERROR_NULL_POINTER; + } + if (!op->primary) { + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_symm_alg_t symm_alg = PGP_SA_UNKNOWN; + if (!str_to_cipher(cipher, &symm_alg)) { + return RNP_ERROR_BAD_PARAMETERS; + } + op->cert.prefs.add_symm_alg(symm_alg); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_set_pref_keyserver(rnp_op_generate_t op, const char *keyserver) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + if (!op->primary) { + return RNP_ERROR_BAD_PARAMETERS; + } + op->cert.prefs.key_server = keyserver ? keyserver : ""; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_execute(rnp_op_generate_t op) +try { + if (!op || !op->ffi) { + return RNP_ERROR_NULL_POINTER; + } + + rnp_result_t ret = RNP_ERROR_GENERIC; + pgp_key_t pub; + pgp_key_t sec; + pgp_password_provider_t prov; + + if (op->primary) { + rnp_keygen_primary_desc_t keygen = {}; + keygen.crypto = op->crypto; + keygen.cert = op->cert; + op->cert.prefs = {}; /* generate call will free prefs */ + + if (!pgp_generate_primary_key(keygen, true, sec, pub, op->ffi->secring->format)) { + return RNP_ERROR_KEY_GENERATION; + } + } else { + /* subkey generation */ + rnp_keygen_subkey_desc_t keygen = {}; + keygen.crypto = op->crypto; + keygen.binding = op->binding; + if (!pgp_generate_subkey(keygen, + true, + *op->primary_sec, + *op->primary_pub, + sec, + pub, + op->ffi->pass_provider, + op->ffi->secring->format)) { + return RNP_ERROR_KEY_GENERATION; + } + } + + /* add public key part to the keyring */ + if (!(op->gen_pub = rnp_key_store_add_key(op->ffi->pubring, &pub))) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + + /* encrypt secret key if requested */ + if (!op->password.empty()) { + prov = {rnp_password_provider_string, (void *) op->password.data()}; + } else if (op->request_password) { + prov = {rnp_password_cb_bounce, op->ffi}; + } + if (prov.callback && !sec.protect(op->protection, prov, op->ffi->context)) { + FFI_LOG(op->ffi, "failed to encrypt the key"); + ret = RNP_ERROR_BAD_PARAMETERS; + goto done; + } + + /* add secret key to the keyring */ + if (!(op->gen_sec = rnp_key_store_add_key(op->ffi->secring, &sec))) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + ret = RNP_SUCCESS; +done: + op->password.clear(); + if (ret && op->gen_pub) { + rnp_key_store_remove_key(op->ffi->pubring, op->gen_pub, false); + op->gen_pub = NULL; + } + if (ret && op->gen_sec) { + rnp_key_store_remove_key(op->ffi->secring, op->gen_sec, false); + op->gen_sec = NULL; + } + return ret; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_get_key(rnp_op_generate_t op, rnp_key_handle_t *handle) +try { + if (!op || !handle) { + return RNP_ERROR_NULL_POINTER; + } + if (!op->gen_sec || !op->gen_pub) { + return RNP_ERROR_BAD_PARAMETERS; + } + + *handle = (rnp_key_handle_t) malloc(sizeof(**handle)); + if (!*handle) { + return RNP_ERROR_OUT_OF_MEMORY; + } + (*handle)->ffi = op->ffi; + (*handle)->pub = op->gen_pub; + (*handle)->sec = op->gen_sec; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_destroy(rnp_op_generate_t op) +try { + delete op; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_handle_destroy(rnp_key_handle_t key) +try { + // This does not free key->key which is owned by the keyring + free(key); + return RNP_SUCCESS; +} +FFI_GUARD + +void +rnp_buffer_destroy(void *ptr) +{ + free(ptr); +} + +void +rnp_buffer_clear(void *ptr, size_t size) +{ + if (ptr) { + secure_clear(ptr, size); + } +} + +static pgp_key_t * +get_key_require_public(rnp_key_handle_t handle) +{ + if (!handle->pub && handle->sec) { + pgp_key_request_ctx_t request; + request.secret = false; + + // try fingerprint + request.search.type = PGP_KEY_SEARCH_FINGERPRINT; + request.search.by.fingerprint = handle->sec->fp(); + handle->pub = pgp_request_key(&handle->ffi->key_provider, &request); + if (handle->pub) { + return handle->pub; + } + + // try keyid + request.search.type = PGP_KEY_SEARCH_KEYID; + request.search.by.keyid = handle->sec->keyid(); + handle->pub = pgp_request_key(&handle->ffi->key_provider, &request); + } + return handle->pub; +} + +static pgp_key_t * +get_key_prefer_public(rnp_key_handle_t handle) +{ + pgp_key_t *pub = get_key_require_public(handle); + return pub ? pub : get_key_require_secret(handle); +} + +static pgp_key_t * +get_key_require_secret(rnp_key_handle_t handle) +{ + if (!handle->sec && handle->pub) { + pgp_key_request_ctx_t request; + request.secret = true; + + // try fingerprint + request.search.type = PGP_KEY_SEARCH_FINGERPRINT; + request.search.by.fingerprint = handle->pub->fp(); + handle->sec = pgp_request_key(&handle->ffi->key_provider, &request); + if (handle->sec) { + return handle->sec; + } + + // try keyid + request.search.type = PGP_KEY_SEARCH_KEYID; + request.search.by.keyid = handle->pub->keyid(); + handle->sec = pgp_request_key(&handle->ffi->key_provider, &request); + } + return handle->sec; +} + +static rnp_result_t +key_get_uid_at(pgp_key_t *key, size_t idx, char **uid) +{ + if (!key || !uid) { + return RNP_ERROR_NULL_POINTER; + } + if (idx >= key->uid_count()) { + return RNP_ERROR_BAD_PARAMETERS; + } + *uid = strdup(key->get_uid(idx).str.c_str()); + if (!*uid) { + return RNP_ERROR_OUT_OF_MEMORY; + } + return RNP_SUCCESS; +} + +rnp_result_t +rnp_key_add_uid(rnp_key_handle_t handle, + const char * uid, + const char * hash, + uint32_t expiration, + uint8_t key_flags, + bool primary) +try { + if (!handle || !uid) { + return RNP_ERROR_NULL_POINTER; + } + /* setup parameters */ + if (!hash) { + hash = DEFAULT_HASH_ALG; + } + pgp_hash_alg_t hash_alg = PGP_HASH_UNKNOWN; + if (!str_to_hash_alg(hash, &hash_alg)) { + FFI_LOG(handle->ffi, "Invalid hash: %s", hash); + return RNP_ERROR_BAD_PARAMETERS; + } + + if (strlen(uid) > MAX_ID_LENGTH) { + FFI_LOG(handle->ffi, "UserID too long"); + return RNP_ERROR_BAD_PARAMETERS; + } + rnp_selfsig_cert_info_t info; + info.userid = uid; + info.key_flags = key_flags; + info.key_expiration = expiration; + info.primary = primary; + + /* obtain and unlok secret key */ + pgp_key_t *secret_key = get_key_require_secret(handle); + if (!secret_key || !secret_key->usable_for(PGP_OP_ADD_USERID)) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + pgp_key_t *public_key = get_key_prefer_public(handle); + if (!public_key && secret_key->format == PGP_KEY_STORE_G10) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + rnp::KeyLocker seclock(*secret_key); + if (secret_key->is_locked() && + !secret_key->unlock(handle->ffi->pass_provider, PGP_OP_ADD_USERID)) { + return RNP_ERROR_BAD_PASSWORD; + } + /* add and certify userid */ + secret_key->add_uid_cert(info, hash_alg, handle->ffi->context, public_key); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_get_primary_uid(rnp_key_handle_t handle, char **uid) +try { + if (!handle || !uid) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_key_t *key = get_key_prefer_public(handle); + if (key->has_primary_uid()) { + return key_get_uid_at(key, key->get_primary_uid(), uid); + } + for (size_t i = 0; i < key->uid_count(); i++) { + if (!key->get_uid(i).valid) { + continue; + } + return key_get_uid_at(key, i, uid); + } + return RNP_ERROR_BAD_PARAMETERS; +} +FFI_GUARD + +rnp_result_t +rnp_key_get_uid_count(rnp_key_handle_t handle, size_t *count) +try { + if (!handle || !count) { + return RNP_ERROR_NULL_POINTER; + } + + *count = get_key_prefer_public(handle)->uid_count(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_get_uid_at(rnp_key_handle_t handle, size_t idx, char **uid) +try { + if (handle == NULL || uid == NULL) + return RNP_ERROR_NULL_POINTER; + + pgp_key_t *key = get_key_prefer_public(handle); + return key_get_uid_at(key, idx, uid); +} +FFI_GUARD + +rnp_result_t +rnp_key_get_uid_handle_at(rnp_key_handle_t key, size_t idx, rnp_uid_handle_t *uid) +try { + if (!key || !uid) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_key_t *akey = get_key_prefer_public(key); + if (!akey) { + return RNP_ERROR_BAD_PARAMETERS; + } + + if (idx >= akey->uid_count()) { + return RNP_ERROR_BAD_PARAMETERS; + } + + *uid = (rnp_uid_handle_t) malloc(sizeof(**uid)); + if (!*uid) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + (*uid)->ffi = key->ffi; + (*uid)->key = akey; + (*uid)->idx = idx; + return RNP_SUCCESS; +} +FFI_GUARD + +static pgp_userid_t * +rnp_uid_handle_get_uid(rnp_uid_handle_t uid) +{ + if (!uid || !uid->key) { + return NULL; + } + return &uid->key->get_uid(uid->idx); +} + +rnp_result_t +rnp_uid_get_type(rnp_uid_handle_t uid, uint32_t *type) +try { + if (!type) { + return RNP_ERROR_NULL_POINTER; + } + pgp_userid_t *id = rnp_uid_handle_get_uid(uid); + if (!id) { + return RNP_ERROR_NULL_POINTER; + } + switch (id->pkt.tag) { + case PGP_PKT_USER_ID: + *type = RNP_USER_ID; + return RNP_SUCCESS; + case PGP_PKT_USER_ATTR: + *type = RNP_USER_ATTR; + return RNP_SUCCESS; + default: + return RNP_ERROR_BAD_STATE; + } +} +FFI_GUARD + +rnp_result_t +rnp_uid_get_data(rnp_uid_handle_t uid, void **data, size_t *size) +try { + if (!data || !size) { + return RNP_ERROR_NULL_POINTER; + } + pgp_userid_t *id = rnp_uid_handle_get_uid(uid); + if (!id) { + return RNP_ERROR_NULL_POINTER; + } + *data = malloc(id->pkt.uid_len); + if (id->pkt.uid_len && !*data) { + return RNP_ERROR_OUT_OF_MEMORY; + } + memcpy(*data, id->pkt.uid, id->pkt.uid_len); + *size = id->pkt.uid_len; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_uid_is_primary(rnp_uid_handle_t uid, bool *primary) +try { + if (!primary) { + return RNP_ERROR_NULL_POINTER; + } + pgp_userid_t *id = rnp_uid_handle_get_uid(uid); + if (!id) { + return RNP_ERROR_NULL_POINTER; + } + *primary = uid->key->has_primary_uid() && (uid->key->get_primary_uid() == uid->idx); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_uid_is_valid(rnp_uid_handle_t uid, bool *valid) +try { + if (!valid) { + return RNP_ERROR_NULL_POINTER; + } + pgp_userid_t *id = rnp_uid_handle_get_uid(uid); + if (!id) { + return RNP_ERROR_NULL_POINTER; + } + *valid = id->valid; + return RNP_SUCCESS; +} +FFI_GUARD + +static rnp_result_t +rnp_key_return_signature(rnp_ffi_t ffi, + pgp_key_t * key, + pgp_subsig_t * subsig, + rnp_signature_handle_t *sig) +{ + *sig = (rnp_signature_handle_t) calloc(1, sizeof(**sig)); + if (!*sig) { + return RNP_ERROR_OUT_OF_MEMORY; + } + (*sig)->ffi = ffi; + (*sig)->key = key; + (*sig)->sig = subsig; + return RNP_SUCCESS; +} + +rnp_result_t +rnp_key_get_signature_count(rnp_key_handle_t handle, size_t *count) +try { + if (!handle || !count) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_prefer_public(handle); + if (!key) { + return RNP_ERROR_BAD_PARAMETERS; + } + *count = key->keysig_count(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_get_signature_at(rnp_key_handle_t handle, size_t idx, rnp_signature_handle_t *sig) +try { + if (!handle || !sig) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_key_t *key = get_key_prefer_public(handle); + if (!key || (idx >= key->keysig_count())) { + return RNP_ERROR_BAD_PARAMETERS; + } + return rnp_key_return_signature(handle->ffi, key, &key->get_keysig(idx), sig); +} +FFI_GUARD + +rnp_result_t +rnp_key_get_revocation_signature(rnp_key_handle_t handle, rnp_signature_handle_t *sig) +try { + if (!handle || !sig) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_prefer_public(handle); + if (!key) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!key->revoked()) { + *sig = NULL; + return RNP_SUCCESS; + } + if (!key->has_sig(key->revocation().sigid)) { + return RNP_ERROR_BAD_STATE; + } + return rnp_key_return_signature( + handle->ffi, key, &key->get_sig(key->revocation().sigid), sig); +} +FFI_GUARD + +rnp_result_t +rnp_uid_get_signature_count(rnp_uid_handle_t handle, size_t *count) +try { + if (!handle || !count) { + return RNP_ERROR_NULL_POINTER; + } + if (!handle->key) { + return RNP_ERROR_BAD_PARAMETERS; + } + *count = handle->key->get_uid(handle->idx).sig_count(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_uid_get_signature_at(rnp_uid_handle_t handle, size_t idx, rnp_signature_handle_t *sig) +try { + if (!handle || !sig) { + return RNP_ERROR_NULL_POINTER; + } + if (!handle->key) { + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_userid_t &uid = handle->key->get_uid(handle->idx); + if (idx >= uid.sig_count()) { + return RNP_ERROR_BAD_PARAMETERS; + } + const pgp_sig_id_t &sigid = uid.get_sig(idx); + if (!handle->key->has_sig(sigid)) { + return RNP_ERROR_BAD_STATE; + } + return rnp_key_return_signature( + handle->ffi, handle->key, &handle->key->get_sig(sigid), sig); +} +FFI_GUARD + +rnp_result_t +rnp_signature_get_type(rnp_signature_handle_t handle, char **type) +try { + if (!handle || !type) { + return RNP_ERROR_NULL_POINTER; + } + if (!handle->sig) { + return RNP_ERROR_BAD_PARAMETERS; + } + auto sigtype = id_str_pair::lookup(sig_type_map, handle->sig->sig.type()); + return ret_str_value(sigtype, type); +} +FFI_GUARD + +rnp_result_t +rnp_signature_get_alg(rnp_signature_handle_t handle, char **alg) +try { + if (!handle || !alg) { + return RNP_ERROR_NULL_POINTER; + } + if (!handle->sig) { + return RNP_ERROR_BAD_PARAMETERS; + } + return get_map_value(pubkey_alg_map, handle->sig->sig.palg, alg); +} +FFI_GUARD + +rnp_result_t +rnp_signature_get_hash_alg(rnp_signature_handle_t handle, char **alg) +try { + if (!handle || !alg) { + return RNP_ERROR_NULL_POINTER; + } + if (!handle->sig) { + return RNP_ERROR_BAD_PARAMETERS; + } + return get_map_value(hash_alg_map, handle->sig->sig.halg, alg); +} +FFI_GUARD + +rnp_result_t +rnp_signature_get_creation(rnp_signature_handle_t handle, uint32_t *create) +try { + if (!handle || !create) { + return RNP_ERROR_NULL_POINTER; + } + if (!handle->sig) { + return RNP_ERROR_BAD_PARAMETERS; + } + *create = handle->sig->sig.creation(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_signature_get_expiration(rnp_signature_handle_t handle, uint32_t *expires) +try { + if (!handle || !expires) { + return RNP_ERROR_NULL_POINTER; + } + if (!handle->sig) { + return RNP_ERROR_BAD_PARAMETERS; + } + *expires = handle->sig->sig.expiration(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_signature_get_keyid(rnp_signature_handle_t handle, char **result) +try { + if (!handle || !result) { + return RNP_ERROR_NULL_POINTER; + } + if (!handle->sig) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!handle->sig->sig.has_keyid()) { + *result = NULL; + return RNP_SUCCESS; + } + pgp_key_id_t keyid = handle->sig->sig.keyid(); + return hex_encode_value(keyid.data(), keyid.size(), result); +} +FFI_GUARD + +rnp_result_t +rnp_signature_get_key_fprint(rnp_signature_handle_t handle, char **result) +try { + if (!handle || !result) { + return RNP_ERROR_NULL_POINTER; + } + if (!handle->sig) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!handle->sig->sig.has_keyfp()) { + *result = NULL; + return RNP_SUCCESS; + } + pgp_fingerprint_t keyfp = handle->sig->sig.keyfp(); + return hex_encode_value(keyfp.fingerprint, keyfp.length, result); +} +FFI_GUARD + +rnp_result_t +rnp_signature_get_signer(rnp_signature_handle_t sig, rnp_key_handle_t *key) +try { + if (!sig || !sig->sig) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!sig->sig->sig.has_keyid()) { + *key = NULL; + return RNP_SUCCESS; + } + pgp_key_search_t locator(PGP_KEY_SEARCH_KEYID); + locator.by.keyid = sig->sig->sig.keyid(); + return rnp_locate_key_int(sig->ffi, locator, key); +} +FFI_GUARD + +rnp_result_t +rnp_signature_is_valid(rnp_signature_handle_t sig, uint32_t flags) +try { + if (!sig) { + return RNP_ERROR_NULL_POINTER; + } + if (!sig->sig || sig->own_sig || flags) { + return RNP_ERROR_BAD_PARAMETERS; + } + + if (!sig->sig->validity.validated) { + pgp_key_t *signer = + pgp_sig_get_signer(*sig->sig, sig->ffi->pubring, &sig->ffi->key_provider); + if (!signer) { + return RNP_ERROR_KEY_NOT_FOUND; + } + signer->validate_sig(*sig->key, *sig->sig, sig->ffi->context); + } + + if (!sig->sig->validity.validated) { + return RNP_ERROR_VERIFICATION_FAILED; + } + if (sig->sig->validity.expired) { + return RNP_ERROR_SIGNATURE_EXPIRED; + } + return sig->sig->valid() ? RNP_SUCCESS : RNP_ERROR_SIGNATURE_INVALID; +} +FFI_GUARD + +rnp_result_t +rnp_signature_packet_to_json(rnp_signature_handle_t sig, uint32_t flags, char **json) +try { + if (!sig || !json) { + return RNP_ERROR_NULL_POINTER; + } + + rnp::MemoryDest memdst; + sig->sig->sig.write(memdst.dst()); + auto vec = memdst.to_vector(); + rnp::MemorySource memsrc(vec); + return rnp_dump_src_to_json(&memsrc.src(), flags, json); +} +FFI_GUARD + +rnp_result_t +rnp_signature_remove(rnp_key_handle_t key, rnp_signature_handle_t sig) +try { + if (!key || !sig) { + return RNP_ERROR_NULL_POINTER; + } + if (sig->own_sig || !sig->sig) { + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_key_t *pkey = get_key_require_public(key); + pgp_key_t *skey = get_key_require_secret(key); + if (!pkey && !skey) { + return RNP_ERROR_BAD_PARAMETERS; + } + const pgp_sig_id_t sigid = sig->sig->sigid; + bool ok = false; + if (pkey) { + ok = pkey->del_sig(sigid); + pkey->revalidate(*key->ffi->pubring); + } + if (skey) { + /* secret key may not have signature, but we still need to delete it at least once to + * succeed */ + ok = skey->del_sig(sigid) || ok; + skey->revalidate(*key->ffi->secring); + } + return ok ? RNP_SUCCESS : RNP_ERROR_NO_SIGNATURES_FOUND; +} +FFI_GUARD + +static rnp_result_t +write_signature(rnp_signature_handle_t sig, pgp_dest_t &dst) +{ + sig->sig->rawpkt.write(dst); + dst_flush(&dst); + return dst.werr; +} + +rnp_result_t +rnp_signature_export(rnp_signature_handle_t sig, rnp_output_t output, uint32_t flags) +try { + if (!sig || !sig->sig || !output) { + return RNP_ERROR_NULL_POINTER; + } + bool need_armor = extract_flag(flags, RNP_KEY_EXPORT_ARMORED); + if (flags) { + FFI_LOG(sig->ffi, "Invalid flags: %" PRIu32, flags); + return RNP_ERROR_BAD_PARAMETERS; + } + rnp_result_t ret; + if (need_armor) { + rnp::ArmoredDest armor(output->dst, PGP_ARMORED_PUBLIC_KEY); + ret = write_signature(sig, armor.dst()); + } else { + ret = write_signature(sig, output->dst); + } + output->keep = !ret; + return ret; +} +FFI_GUARD + +rnp_result_t +rnp_signature_handle_destroy(rnp_signature_handle_t sig) +try { + if (sig && sig->own_sig) { + delete sig->sig; + } + free(sig); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_uid_is_revoked(rnp_uid_handle_t uid, bool *result) +try { + if (!uid || !result) { + return RNP_ERROR_NULL_POINTER; + } + + if (!uid->key) { + return RNP_ERROR_BAD_PARAMETERS; + } + + *result = uid->key->get_uid(uid->idx).revoked; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_uid_get_revocation_signature(rnp_uid_handle_t uid, rnp_signature_handle_t *sig) +try { + if (!uid || !sig) { + return RNP_ERROR_NULL_POINTER; + } + if (!uid->key) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (uid->idx >= uid->key->uid_count()) { + return RNP_ERROR_BAD_STATE; + } + const pgp_userid_t &userid = uid->key->get_uid(uid->idx); + if (!userid.revoked) { + *sig = NULL; + return RNP_SUCCESS; + } + if (!uid->key->has_sig(userid.revocation.sigid)) { + return RNP_ERROR_BAD_STATE; + } + return rnp_key_return_signature( + uid->ffi, uid->key, &uid->key->get_sig(userid.revocation.sigid), sig); +} +FFI_GUARD + +rnp_result_t +rnp_uid_remove(rnp_key_handle_t key, rnp_uid_handle_t uid) +try { + if (!key || !uid) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *pkey = get_key_require_public(key); + pgp_key_t *skey = get_key_require_secret(key); + if (!pkey && !skey) { + return RNP_ERROR_BAD_PARAMETERS; + } + if ((uid->key != pkey) && (uid->key != skey)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + bool ok = false; + if (pkey && (pkey->uid_count() > uid->idx)) { + pkey->del_uid(uid->idx); + pkey->revalidate(*key->ffi->pubring); + ok = true; + } + if (skey && (skey->uid_count() > uid->idx)) { + skey->del_uid(uid->idx); + skey->revalidate(*key->ffi->secring); + ok = true; + } + return ok ? RNP_SUCCESS : RNP_ERROR_BAD_PARAMETERS; +} +FFI_GUARD + +rnp_result_t +rnp_uid_handle_destroy(rnp_uid_handle_t uid) +try { + free(uid); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_get_subkey_count(rnp_key_handle_t handle, size_t *count) +try { + if (!handle || !count) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_prefer_public(handle); + *count = key->subkey_count(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_get_subkey_at(rnp_key_handle_t handle, size_t idx, rnp_key_handle_t *subkey) +try { + if (!handle || !subkey) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_prefer_public(handle); + if (idx >= key->subkey_count()) { + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_key_search_t locator(PGP_KEY_SEARCH_FINGERPRINT); + locator.by.fingerprint = key->get_subkey_fp(idx); + return rnp_locate_key_int(handle->ffi, locator, subkey); +} +FFI_GUARD + +rnp_result_t +rnp_key_get_default_key(rnp_key_handle_t primary_key, + const char * usage, + uint32_t flags, + rnp_key_handle_t *default_key) +try { + if (!primary_key || !usage || !default_key) { + return RNP_ERROR_NULL_POINTER; + } + uint8_t keyflag = 0; + if (!str_to_key_flag(usage, &keyflag)) { + return RNP_ERROR_BAD_PARAMETERS; + } + bool no_primary = extract_flag(flags, RNP_KEY_SUBKEYS_ONLY); + if (flags) { + FFI_LOG(primary_key->ffi, "Invalid flags: %" PRIu32, flags); + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_op_t op = PGP_OP_UNKNOWN; + bool secret = false; + switch (keyflag) { + case PGP_KF_SIGN: + op = PGP_OP_SIGN; + secret = true; + break; + case PGP_KF_CERTIFY: + op = PGP_OP_CERTIFY; + secret = true; + break; + case PGP_KF_ENCRYPT: + op = PGP_OP_ENCRYPT; + break; + default: + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_key_t *key = get_key_prefer_public(primary_key); + if (!key) { + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_key_t *defkey = + find_suitable_key(op, key, &primary_key->ffi->key_provider, no_primary); + if (!defkey) { + *default_key = NULL; + return RNP_ERROR_NO_SUITABLE_KEY; + } + + pgp_key_search_t search(PGP_KEY_SEARCH_FINGERPRINT); + search.by.fingerprint = defkey->fp(); + + rnp_result_t ret = rnp_locate_key_int(primary_key->ffi, search, default_key, secret); + + if (!*default_key && !ret) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + return ret; +} +FFI_GUARD + +rnp_result_t +rnp_key_get_alg(rnp_key_handle_t handle, char **alg) +try { + if (!handle || !alg) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_prefer_public(handle); + return get_map_value(pubkey_alg_map, key->alg(), alg); +} +FFI_GUARD + +rnp_result_t +rnp_key_get_bits(rnp_key_handle_t handle, uint32_t *bits) +try { + if (!handle || !bits) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_prefer_public(handle); + size_t _bits = key->material().bits(); + if (!_bits) { + return RNP_ERROR_BAD_PARAMETERS; + } + *bits = _bits; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_get_dsa_qbits(rnp_key_handle_t handle, uint32_t *qbits) +try { + if (!handle || !qbits) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_prefer_public(handle); + size_t _qbits = key->material().qbits(); + if (!_qbits) { + return RNP_ERROR_BAD_PARAMETERS; + } + *qbits = _qbits; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_get_curve(rnp_key_handle_t handle, char **curve) +try { + if (!handle || !curve) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t * key = get_key_prefer_public(handle); + pgp_curve_t _curve = key->curve(); + if (_curve == PGP_CURVE_UNKNOWN) { + return RNP_ERROR_BAD_PARAMETERS; + } + const char *curvename = NULL; + if (!curve_type_to_str(_curve, &curvename)) { + return RNP_ERROR_BAD_PARAMETERS; + } + char *curvenamecp = strdup(curvename); + if (!curvenamecp) { + return RNP_ERROR_OUT_OF_MEMORY; + } + *curve = curvenamecp; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_get_fprint(rnp_key_handle_t handle, char **fprint) +try { + if (!handle || !fprint) { + return RNP_ERROR_NULL_POINTER; + } + + const pgp_fingerprint_t &fp = get_key_prefer_public(handle)->fp(); + return hex_encode_value(fp.fingerprint, fp.length, fprint); +} +FFI_GUARD + +rnp_result_t +rnp_key_get_keyid(rnp_key_handle_t handle, char **keyid) +try { + if (!handle || !keyid) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_key_t *key = get_key_prefer_public(handle); + return hex_encode_value(key->keyid().data(), key->keyid().size(), keyid); +} +FFI_GUARD + +rnp_result_t +rnp_key_get_grip(rnp_key_handle_t handle, char **grip) +try { + if (!handle || !grip) { + return RNP_ERROR_NULL_POINTER; + } + + const pgp_key_grip_t &kgrip = get_key_prefer_public(handle)->grip(); + return hex_encode_value(kgrip.data(), kgrip.size(), grip); +} +FFI_GUARD + +static const pgp_key_grip_t * +rnp_get_grip_by_fp(rnp_ffi_t ffi, const pgp_fingerprint_t &fp) +{ + pgp_key_t *key = NULL; + if (ffi->pubring) { + key = rnp_key_store_get_key_by_fpr(ffi->pubring, fp); + } + if (!key && ffi->secring) { + key = rnp_key_store_get_key_by_fpr(ffi->secring, fp); + } + return key ? &key->grip() : NULL; +} + +rnp_result_t +rnp_key_get_primary_grip(rnp_key_handle_t handle, char **grip) +try { + if (!handle || !grip) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_key_t *key = get_key_prefer_public(handle); + if (!key->is_subkey()) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!key->has_primary_fp()) { + *grip = NULL; + return RNP_SUCCESS; + } + const pgp_key_grip_t *pgrip = rnp_get_grip_by_fp(handle->ffi, key->primary_fp()); + if (!pgrip) { + *grip = NULL; + return RNP_SUCCESS; + } + return hex_encode_value(pgrip->data(), pgrip->size(), grip); +} +FFI_GUARD + +rnp_result_t +rnp_key_get_primary_fprint(rnp_key_handle_t handle, char **fprint) +try { + if (!handle || !fprint) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_key_t *key = get_key_prefer_public(handle); + if (!key->is_subkey()) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!key->has_primary_fp()) { + *fprint = NULL; + return RNP_SUCCESS; + } + const pgp_fingerprint_t &fp = key->primary_fp(); + return hex_encode_value(fp.fingerprint, fp.length, fprint); +} +FFI_GUARD + +rnp_result_t +rnp_key_allows_usage(rnp_key_handle_t handle, const char *usage, bool *result) +try { + if (!handle || !usage || !result) { + return RNP_ERROR_NULL_POINTER; + } + uint8_t flag = 0; + if (!str_to_key_flag(usage, &flag)) { + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_key_t *key = get_key_prefer_public(handle); + if (!key) { + return RNP_ERROR_BAD_PARAMETERS; + } + *result = key->flags() & flag; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_get_creation(rnp_key_handle_t handle, uint32_t *result) +try { + if (!handle || !result) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_prefer_public(handle); + if (!key) { + return RNP_ERROR_BAD_PARAMETERS; + } + *result = key->creation(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_is_revoked(rnp_key_handle_t handle, bool *result) +try { + if (!handle || !result) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_prefer_public(handle); + if (!key) { + return RNP_ERROR_BAD_PARAMETERS; + } + *result = key->revoked(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_is_valid(rnp_key_handle_t handle, bool *result) +try { + if (!handle || !result) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_require_public(handle); + if (!key) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!key->validated()) { + key->validate(*handle->ffi->pubring); + } + if (!key->validated()) { + return RNP_ERROR_VERIFICATION_FAILED; + } + *result = key->valid(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_valid_till(rnp_key_handle_t handle, uint32_t *result) +try { + if (!result) { + return RNP_ERROR_NULL_POINTER; + } + uint64_t res = 0; + rnp_result_t ret = rnp_key_valid_till64(handle, &res); + if (ret) { + return ret; + } + if (res == UINT64_MAX) { + *result = UINT32_MAX; + } else if (res >= UINT32_MAX) { + *result = UINT32_MAX - 1; + } else { + *result = (uint32_t) res; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_valid_till64(rnp_key_handle_t handle, uint64_t *result) +try { + if (!handle || !result) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_require_public(handle); + if (!key) { + return RNP_ERROR_BAD_PARAMETERS; + } + + if (!key->validated()) { + key->validate(*handle->ffi->pubring); + } + if (!key->validated()) { + return RNP_ERROR_VERIFICATION_FAILED; + } + + if (key->is_subkey()) { + /* check validity time of the primary key as well */ + pgp_key_t *primary = rnp_key_store_get_primary_key(handle->ffi->pubring, key); + if (!primary) { + /* no primary key - subkey considered as never valid */ + *result = 0; + return RNP_SUCCESS; + } + if (!primary->validated()) { + primary->validate(*handle->ffi->pubring); + } + if (!primary->validated()) { + return RNP_ERROR_VERIFICATION_FAILED; + } + *result = key->valid_till(); + } else { + *result = key->valid_till(); + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_get_expiration(rnp_key_handle_t handle, uint32_t *result) +try { + if (!handle || !result) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_prefer_public(handle); + if (!key) { + return RNP_ERROR_BAD_PARAMETERS; + } + *result = key->expiration(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_set_expiration(rnp_key_handle_t key, uint32_t expiry) +try { + if (!key) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_key_t *pkey = get_key_prefer_public(key); + if (!pkey) { + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_key_t *skey = get_key_require_secret(key); + if (!skey) { + FFI_LOG(key->ffi, "Secret key required."); + return RNP_ERROR_BAD_PARAMETERS; + } + + if (pkey->is_primary()) { + if (!pgp_key_set_expiration( + pkey, skey, expiry, key->ffi->pass_provider, key->ffi->context)) { + return RNP_ERROR_GENERIC; + } + pkey->revalidate(*key->ffi->pubring); + if (pkey != skey) { + skey->revalidate(*key->ffi->secring); + } + return RNP_SUCCESS; + } + + /* for subkey we need primary key */ + if (!pkey->has_primary_fp()) { + FFI_LOG(key->ffi, "Primary key fp not available."); + return RNP_ERROR_BAD_PARAMETERS; + } + + pgp_key_search_t search(PGP_KEY_SEARCH_FINGERPRINT); + search.by.fingerprint = pkey->primary_fp(); + pgp_key_t *prim_sec = find_key(key->ffi, search, true, true); + if (!prim_sec) { + FFI_LOG(key->ffi, "Primary secret key not found."); + return RNP_ERROR_KEY_NOT_FOUND; + } + if (!pgp_subkey_set_expiration( + pkey, prim_sec, skey, expiry, key->ffi->pass_provider, key->ffi->context)) { + return RNP_ERROR_GENERIC; + } + prim_sec->revalidate(*key->ffi->secring); + pgp_key_t *prim_pub = find_key(key->ffi, search, false, true); + if (prim_pub) { + prim_pub->revalidate(*key->ffi->pubring); + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_get_revocation_reason(rnp_key_handle_t handle, char **result) +try { + if (!handle || !result) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_prefer_public(handle); + if (!key || !key->revoked()) { + return RNP_ERROR_BAD_PARAMETERS; + } + + *result = strdup(key->revocation().reason.c_str()); + if (!*result) { + return RNP_ERROR_OUT_OF_MEMORY; + } + return RNP_SUCCESS; +} +FFI_GUARD + +static rnp_result_t +rnp_key_is_revoked_with_code(rnp_key_handle_t handle, bool *result, int code) +{ + if (!handle || !result) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_prefer_public(handle); + if (!key || !key->revoked()) { + return RNP_ERROR_BAD_PARAMETERS; + } + + *result = key->revocation().code == code; + return RNP_SUCCESS; +} + +rnp_result_t +rnp_key_is_superseded(rnp_key_handle_t handle, bool *result) +try { + return rnp_key_is_revoked_with_code(handle, result, PGP_REVOCATION_SUPERSEDED); +} +FFI_GUARD + +rnp_result_t +rnp_key_is_compromised(rnp_key_handle_t handle, bool *result) +try { + return rnp_key_is_revoked_with_code(handle, result, PGP_REVOCATION_COMPROMISED); +} +FFI_GUARD + +rnp_result_t +rnp_key_is_retired(rnp_key_handle_t handle, bool *result) +try { + return rnp_key_is_revoked_with_code(handle, result, PGP_REVOCATION_RETIRED); +} +FFI_GUARD + +rnp_result_t +rnp_key_is_expired(rnp_key_handle_t handle, bool *result) +try { + if (!handle || !result) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_prefer_public(handle); + if (!key) { + return RNP_ERROR_BAD_PARAMETERS; + } + *result = key->expired(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_get_protection_type(rnp_key_handle_t key, char **type) +try { + if (!key || !type) { + return RNP_ERROR_NULL_POINTER; + } + if (!key->sec) { + return RNP_ERROR_BAD_PARAMETERS; + } + + const pgp_s2k_t &s2k = key->sec->pkt().sec_protection.s2k; + const char * res = "Unknown"; + if (s2k.usage == PGP_S2KU_NONE) { + res = "None"; + } + if ((s2k.usage == PGP_S2KU_ENCRYPTED) && (s2k.specifier != PGP_S2KS_EXPERIMENTAL)) { + res = "Encrypted"; + } + if ((s2k.usage == PGP_S2KU_ENCRYPTED_AND_HASHED) && + (s2k.specifier != PGP_S2KS_EXPERIMENTAL)) { + res = "Encrypted-Hashed"; + } + if ((s2k.specifier == PGP_S2KS_EXPERIMENTAL) && + (s2k.gpg_ext_num == PGP_S2K_GPG_NO_SECRET)) { + res = "GPG-None"; + } + if ((s2k.specifier == PGP_S2KS_EXPERIMENTAL) && + (s2k.gpg_ext_num == PGP_S2K_GPG_SMARTCARD)) { + res = "GPG-Smartcard"; + } + + return ret_str_value(res, type); +} +FFI_GUARD + +rnp_result_t +rnp_key_get_protection_mode(rnp_key_handle_t key, char **mode) +try { + if (!key || !mode) { + return RNP_ERROR_NULL_POINTER; + } + if (!key->sec) { + return RNP_ERROR_BAD_PARAMETERS; + } + + if (key->sec->pkt().sec_protection.s2k.usage == PGP_S2KU_NONE) { + return ret_str_value("None", mode); + } + if (key->sec->pkt().sec_protection.s2k.specifier == PGP_S2KS_EXPERIMENTAL) { + return ret_str_value("Unknown", mode); + } + + return get_map_value(cipher_mode_map, key->sec->pkt().sec_protection.cipher_mode, mode); +} +FFI_GUARD + +static bool +pgp_key_has_encryption_info(const pgp_key_t *key) +{ + return (key->pkt().sec_protection.s2k.usage != PGP_S2KU_NONE) && + (key->pkt().sec_protection.s2k.specifier != PGP_S2KS_EXPERIMENTAL); +} + +rnp_result_t +rnp_key_get_protection_cipher(rnp_key_handle_t key, char **cipher) +try { + if (!key || !cipher) { + return RNP_ERROR_NULL_POINTER; + } + if (!key->sec) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!pgp_key_has_encryption_info(key->sec)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + return get_map_value(symm_alg_map, key->sec->pkt().sec_protection.symm_alg, cipher); +} +FFI_GUARD + +rnp_result_t +rnp_key_get_protection_hash(rnp_key_handle_t key, char **hash) +try { + if (!key || !hash) { + return RNP_ERROR_NULL_POINTER; + } + if (!key->sec) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!pgp_key_has_encryption_info(key->sec)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + return get_map_value(hash_alg_map, key->sec->pkt().sec_protection.s2k.hash_alg, hash); +} +FFI_GUARD + +rnp_result_t +rnp_key_get_protection_iterations(rnp_key_handle_t key, size_t *iterations) +try { + if (!key || !iterations) { + return RNP_ERROR_NULL_POINTER; + } + if (!key->sec) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!pgp_key_has_encryption_info(key->sec)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + if (key->sec->pkt().sec_protection.s2k.specifier == PGP_S2KS_ITERATED_AND_SALTED) { + *iterations = pgp_s2k_decode_iterations(key->sec->pkt().sec_protection.s2k.iterations); + } else { + *iterations = 1; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_is_locked(rnp_key_handle_t handle, bool *result) +try { + if (handle == NULL || result == NULL) + return RNP_ERROR_NULL_POINTER; + + pgp_key_t *key = get_key_require_secret(handle); + if (!key) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + *result = key->is_locked(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_lock(rnp_key_handle_t handle) +try { + if (handle == NULL) + return RNP_ERROR_NULL_POINTER; + + pgp_key_t *key = get_key_require_secret(handle); + if (!key) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + if (!key->lock()) { + return RNP_ERROR_GENERIC; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_unlock(rnp_key_handle_t handle, const char *password) +try { + if (!handle) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_require_secret(handle); + if (!key) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + bool ok = false; + if (password) { + pgp_password_provider_t prov(rnp_password_provider_string, + reinterpret_cast<void *>(const_cast<char *>(password))); + ok = key->unlock(prov); + } else { + ok = key->unlock(handle->ffi->pass_provider); + } + if (!ok) { + // likely a bad password + return RNP_ERROR_BAD_PASSWORD; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_is_protected(rnp_key_handle_t handle, bool *result) +try { + if (handle == NULL || result == NULL) + return RNP_ERROR_NULL_POINTER; + + pgp_key_t *key = get_key_require_secret(handle); + if (!key) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + *result = key->is_protected(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_protect(rnp_key_handle_t handle, + const char * password, + const char * cipher, + const char * cipher_mode, + const char * hash, + size_t iterations) +try { + rnp_key_protection_params_t protection = {}; + + // checks + if (!handle || !password) { + return RNP_ERROR_NULL_POINTER; + } + + if (cipher && !str_to_cipher(cipher, &protection.symm_alg)) { + FFI_LOG(handle->ffi, "Invalid cipher: %s", cipher); + return RNP_ERROR_BAD_PARAMETERS; + } + if (cipher_mode && !str_to_cipher_mode(cipher_mode, &protection.cipher_mode)) { + FFI_LOG(handle->ffi, "Invalid cipher mode: %s", cipher_mode); + return RNP_ERROR_BAD_PARAMETERS; + } + if (hash && !str_to_hash_alg(hash, &protection.hash_alg)) { + FFI_LOG(handle->ffi, "Invalid hash: %s", hash); + return RNP_ERROR_BAD_PARAMETERS; + } + protection.iterations = iterations; + + // get the key + pgp_key_t *key = get_key_require_secret(handle); + if (!key) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + pgp_key_pkt_t * decrypted_key = NULL; + const std::string pass = password; + if (key->encrypted()) { + pgp_password_ctx_t ctx(PGP_OP_PROTECT, key); + decrypted_key = pgp_decrypt_seckey(*key, handle->ffi->pass_provider, ctx); + if (!decrypted_key) { + return RNP_ERROR_GENERIC; + } + } + bool res = key->protect( + decrypted_key ? *decrypted_key : key->pkt(), protection, pass, handle->ffi->context); + delete decrypted_key; + return res ? RNP_SUCCESS : RNP_ERROR_GENERIC; +} +FFI_GUARD + +rnp_result_t +rnp_key_unprotect(rnp_key_handle_t handle, const char *password) +try { + // checks + if (!handle) { + return RNP_ERROR_NULL_POINTER; + } + + // get the key + pgp_key_t *key = get_key_require_secret(handle); + if (!key) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + bool ok = false; + if (password) { + pgp_password_provider_t prov(rnp_password_provider_string, + reinterpret_cast<void *>(const_cast<char *>(password))); + ok = key->unprotect(prov, handle->ffi->context); + } else { + ok = key->unprotect(handle->ffi->pass_provider, handle->ffi->context); + } + if (!ok) { + // likely a bad password + return RNP_ERROR_BAD_PASSWORD; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_is_primary(rnp_key_handle_t handle, bool *result) +try { + if (handle == NULL || result == NULL) + return RNP_ERROR_NULL_POINTER; + + pgp_key_t *key = get_key_prefer_public(handle); + if (key->format == PGP_KEY_STORE_G10) { + // we can't currently determine this for a G10 secret key + return RNP_ERROR_NO_SUITABLE_KEY; + } + *result = key->is_primary(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_is_sub(rnp_key_handle_t handle, bool *result) +try { + if (handle == NULL || result == NULL) + return RNP_ERROR_NULL_POINTER; + + pgp_key_t *key = get_key_prefer_public(handle); + if (key->format == PGP_KEY_STORE_G10) { + // we can't currently determine this for a G10 secret key + return RNP_ERROR_NO_SUITABLE_KEY; + } + *result = key->is_subkey(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_have_secret(rnp_key_handle_t handle, bool *result) +try { + if (handle == NULL || result == NULL) + return RNP_ERROR_NULL_POINTER; + + *result = handle->sec != NULL; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_have_public(rnp_key_handle_t handle, bool *result) +try { + if (handle == NULL || result == NULL) + return RNP_ERROR_NULL_POINTER; + *result = handle->pub != NULL; + return RNP_SUCCESS; +} +FFI_GUARD + +static rnp_result_t +key_to_bytes(pgp_key_t *key, uint8_t **buf, size_t *buf_len) +{ + auto vec = rnp_key_to_vec(*key); + *buf = (uint8_t *) calloc(1, vec.size()); + if (!*buf) { + return RNP_ERROR_OUT_OF_MEMORY; + } + memcpy(*buf, vec.data(), vec.size()); + *buf_len = vec.size(); + return RNP_SUCCESS; +} + +rnp_result_t +rnp_get_public_key_data(rnp_key_handle_t handle, uint8_t **buf, size_t *buf_len) +try { + // checks + if (!handle || !buf || !buf_len) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_key_t *key = handle->pub; + if (!key) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + return key_to_bytes(key, buf, buf_len); +} +FFI_GUARD + +rnp_result_t +rnp_get_secret_key_data(rnp_key_handle_t handle, uint8_t **buf, size_t *buf_len) +try { + // checks + if (!handle || !buf || !buf_len) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_key_t *key = handle->sec; + if (!key) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + return key_to_bytes(key, buf, buf_len); +} +FFI_GUARD + +static bool +add_json_string_field(json_object *jso, const char *key, const char *value) +{ + json_object *jsostr = json_object_new_string(value); + if (!jsostr) { + return false; + } + json_object_object_add(jso, key, jsostr); + return true; +} + +static bool +add_json_int_field(json_object *jso, const char *key, int32_t value) +{ + json_object *jsoval = json_object_new_int(value); + if (!jsoval) { + return false; + } + json_object_object_add(jso, key, jsoval); + return true; +} + +static bool +add_json_key_usage(json_object *jso, uint8_t key_flags) +{ + json_object *jsoarr = json_object_new_array(); + if (!jsoarr) { + return false; + } + for (size_t i = 0; i < ARRAY_SIZE(key_usage_map); i++) { + if (key_usage_map[i].id & key_flags) { + json_object *jsostr = json_object_new_string(key_usage_map[i].str); + if (!jsostr || json_object_array_add(jsoarr, jsostr)) { + json_object_put(jsoarr); + return false; + } + } + } + if (json_object_array_length(jsoarr)) { + json_object_object_add(jso, "usage", jsoarr); + } else { + json_object_put(jsoarr); + } + return true; +} + +static bool +add_json_key_flags(json_object *jso, uint8_t key_flags) +{ + json_object *jsoarr = json_object_new_array(); + if (!jsoarr) { + return false; + } + for (size_t i = 0; i < ARRAY_SIZE(key_flags_map); i++) { + if (key_flags_map[i].id & key_flags) { + json_object *jsostr = json_object_new_string(key_flags_map[i].str); + if (!jsostr || json_object_array_add(jsoarr, jsostr)) { + json_object_put(jsoarr); + return false; + } + } + } + if (json_object_array_length(jsoarr)) { + json_object_object_add(jso, "flags", jsoarr); + } else { + json_object_put(jsoarr); + } + return true; +} + +static rnp_result_t +add_json_mpis(json_object *jso, ...) +{ + va_list ap; + const char * name; + rnp_result_t ret = RNP_ERROR_GENERIC; + + va_start(ap, jso); + while ((name = va_arg(ap, const char *))) { + pgp_mpi_t *val = va_arg(ap, pgp_mpi_t *); + if (!val) { + ret = RNP_ERROR_BAD_PARAMETERS; + goto done; + } + char *hex = mpi2hex(val); + if (!hex) { + // this could probably be other things + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + json_object *jsostr = json_object_new_string(hex); + free(hex); + if (!jsostr) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + json_object_object_add(jso, name, jsostr); + } + ret = RNP_SUCCESS; + +done: + va_end(ap); + return ret; +} + +static rnp_result_t +add_json_public_mpis(json_object *jso, pgp_key_t *key) +{ + const pgp_key_material_t &km = key->material(); + switch (km.alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + return add_json_mpis(jso, "n", &km.rsa.n, "e", &km.rsa.e, NULL); + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + return add_json_mpis(jso, "p", &km.eg.p, "g", &km.eg.g, "y", &km.eg.y, NULL); + case PGP_PKA_DSA: + return add_json_mpis( + jso, "p", &km.dsa.p, "q", &km.dsa.q, "g", &km.dsa.g, "y", &km.dsa.y, NULL); + case PGP_PKA_ECDH: + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: + case PGP_PKA_SM2: + return add_json_mpis(jso, "point", &km.ec.p, NULL); + default: + return RNP_ERROR_NOT_SUPPORTED; + } + return RNP_SUCCESS; +} + +static rnp_result_t +add_json_secret_mpis(json_object *jso, pgp_key_t *key) +{ + const pgp_key_material_t &km = key->material(); + switch (key->alg()) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + return add_json_mpis( + jso, "d", &km.rsa.d, "p", &km.rsa.p, "q", &km.rsa.q, "u", &km.rsa.u, NULL); + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + return add_json_mpis(jso, "x", &km.eg.x, NULL); + case PGP_PKA_DSA: + return add_json_mpis(jso, "x", &km.dsa.x, NULL); + case PGP_PKA_ECDH: + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: + case PGP_PKA_SM2: + return add_json_mpis(jso, "x", &km.ec.x, NULL); + default: + return RNP_ERROR_NOT_SUPPORTED; + } + return RNP_SUCCESS; +} + +static rnp_result_t +add_json_sig_mpis(json_object *jso, const pgp_signature_t *sig) +{ + pgp_signature_material_t material = {}; + try { + if (!sig->parse_material(material)) { + return RNP_ERROR_BAD_PARAMETERS; + } + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + switch (sig->palg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + return add_json_mpis(jso, "sig", &material.rsa.s, NULL); + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + return add_json_mpis(jso, "r", &material.eg.r, "s", &material.eg.s, NULL); + case PGP_PKA_DSA: + return add_json_mpis(jso, "r", &material.dsa.r, "s", &material.dsa.s, NULL); + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: + case PGP_PKA_SM2: + return add_json_mpis(jso, "r", &material.ecc.r, "s", &material.ecc.s, NULL); + default: + // TODO: we could use info->unknown and add a hex string of raw data here + return RNP_ERROR_NOT_SUPPORTED; + } + return RNP_SUCCESS; +} + +static bool +add_json_user_prefs(json_object *jso, const pgp_user_prefs_t &prefs) +{ + // TODO: instead of using a string "Unknown" as a fallback for these, + // we could add a string of hex/dec (or even an int) + if (!prefs.symm_algs.empty()) { + json_object *jsoarr = json_object_new_array(); + if (!jsoarr) { + return false; + } + json_object_object_add(jso, "ciphers", jsoarr); + for (auto alg : prefs.symm_algs) { + const char * name = id_str_pair::lookup(symm_alg_map, alg, "Unknown"); + json_object *jsoname = json_object_new_string(name); + if (!jsoname || json_object_array_add(jsoarr, jsoname)) { + return false; + } + } + } + if (!prefs.hash_algs.empty()) { + json_object *jsoarr = json_object_new_array(); + if (!jsoarr) { + return false; + } + json_object_object_add(jso, "hashes", jsoarr); + for (auto alg : prefs.hash_algs) { + const char * name = id_str_pair::lookup(hash_alg_map, alg, "Unknown"); + json_object *jsoname = json_object_new_string(name); + if (!jsoname || json_object_array_add(jsoarr, jsoname)) { + return false; + } + } + } + if (!prefs.z_algs.empty()) { + json_object *jsoarr = json_object_new_array(); + if (!jsoarr) { + return false; + } + json_object_object_add(jso, "compression", jsoarr); + for (auto alg : prefs.z_algs) { + const char * name = id_str_pair::lookup(compress_alg_map, alg, "Unknown"); + json_object *jsoname = json_object_new_string(name); + if (!jsoname || json_object_array_add(jsoarr, jsoname)) { + return false; + } + } + } + if (!prefs.ks_prefs.empty()) { + json_object *jsoarr = json_object_new_array(); + if (!jsoarr) { + return false; + } + json_object_object_add(jso, "key server preferences", jsoarr); + for (auto flag : prefs.ks_prefs) { + const char * name = id_str_pair::lookup(key_server_prefs_map, flag, "Unknown"); + json_object *jsoname = json_object_new_string(name); + if (!jsoname || json_object_array_add(jsoarr, jsoname)) { + return false; + } + } + } + if (!prefs.key_server.empty()) { + if (!add_json_string_field(jso, "key server", prefs.key_server.c_str())) { + return false; + } + } + return true; +} + +static rnp_result_t +add_json_subsig(json_object *jso, bool is_sub, uint32_t flags, const pgp_subsig_t *subsig) +{ + // userid (if applicable) + if (!is_sub) { + json_object *jsouid = json_object_new_int(subsig->uid); + if (!jsouid) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "userid", jsouid); + } + // trust + json_object *jsotrust = json_object_new_object(); + if (!jsotrust) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "trust", jsotrust); + // trust (level) + json_object *jsotrust_level = json_object_new_int(subsig->trustlevel); + if (!jsotrust_level) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jsotrust, "level", jsotrust_level); + // trust (amount) + json_object *jsotrust_amount = json_object_new_int(subsig->trustamount); + if (!jsotrust_amount) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jsotrust, "amount", jsotrust_amount); + // key flags (usage) + if (!add_json_key_usage(jso, subsig->key_flags)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + // key flags (other) + if (!add_json_key_flags(jso, subsig->key_flags)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + // preferences + const pgp_user_prefs_t &prefs = subsig->prefs; + if (!prefs.symm_algs.empty() || !prefs.hash_algs.empty() || !prefs.z_algs.empty() || + !prefs.ks_prefs.empty() || !prefs.key_server.empty()) { + json_object *jsoprefs = json_object_new_object(); + if (!jsoprefs) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "preferences", jsoprefs); + if (!add_json_user_prefs(jsoprefs, prefs)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + } + const pgp_signature_t *sig = &subsig->sig; + // version + json_object *jsoversion = json_object_new_int(sig->version); + if (!jsoversion) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "version", jsoversion); + // signature type + auto type = id_str_pair::lookup(sig_type_map, sig->type()); + if (!add_json_string_field(jso, "type", type)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + // signer key type + const char *key_type = id_str_pair::lookup(pubkey_alg_map, sig->palg); + if (!add_json_string_field(jso, "key type", key_type)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + // hash + const char *hash = id_str_pair::lookup(hash_alg_map, sig->halg); + if (!add_json_string_field(jso, "hash", hash)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + // creation time + json_object *jsocreation_time = json_object_new_int64(sig->creation()); + if (!jsocreation_time) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "creation time", jsocreation_time); + // expiration (seconds) + json_object *jsoexpiration = json_object_new_int64(sig->expiration()); + if (!jsoexpiration) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "expiration", jsoexpiration); + // signer + json_object *jsosigner = NULL; + // TODO: add signer fingerprint as well (no support internally yet) + if (sig->has_keyid()) { + jsosigner = json_object_new_object(); + if (!jsosigner) { + return RNP_ERROR_OUT_OF_MEMORY; + } + char keyid[PGP_KEY_ID_SIZE * 2 + 1]; + pgp_key_id_t signer = sig->keyid(); + if (!rnp::hex_encode(signer.data(), signer.size(), keyid, sizeof(keyid))) { + return RNP_ERROR_GENERIC; + } + if (!add_json_string_field(jsosigner, "keyid", keyid)) { + json_object_put(jsosigner); + return RNP_ERROR_OUT_OF_MEMORY; + } + } + json_object_object_add(jso, "signer", jsosigner); + // mpis + json_object *jsompis = NULL; + if (flags & RNP_JSON_SIGNATURE_MPIS) { + jsompis = json_object_new_object(); + if (!jsompis) { + return RNP_ERROR_OUT_OF_MEMORY; + } + rnp_result_t tmpret; + if ((tmpret = add_json_sig_mpis(jsompis, sig))) { + json_object_put(jsompis); + return tmpret; + } + } + json_object_object_add(jso, "mpis", jsompis); + return RNP_SUCCESS; +} + +static rnp_result_t +key_to_json(json_object *jso, rnp_key_handle_t handle, uint32_t flags) +{ + pgp_key_t *key = get_key_prefer_public(handle); + + // type + const char *str = id_str_pair::lookup(pubkey_alg_map, key->alg(), NULL); + if (!str) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!add_json_string_field(jso, "type", str)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + // length + if (!add_json_int_field(jso, "length", key->material().bits())) { + return RNP_ERROR_OUT_OF_MEMORY; + } + // curve / alg-specific items + switch (key->alg()) { + case PGP_PKA_ECDH: { + const char *hash_name = + id_str_pair::lookup(hash_alg_map, key->material().ec.kdf_hash_alg, NULL); + if (!hash_name) { + return RNP_ERROR_BAD_PARAMETERS; + } + const char *cipher_name = + id_str_pair::lookup(symm_alg_map, key->material().ec.key_wrap_alg, NULL); + if (!cipher_name) { + return RNP_ERROR_BAD_PARAMETERS; + } + json_object *jsohash = json_object_new_string(hash_name); + if (!jsohash) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "kdf hash", jsohash); + json_object *jsocipher = json_object_new_string(cipher_name); + if (!jsocipher) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "key wrap cipher", jsocipher); + } + [[fallthrough]]; + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: + case PGP_PKA_SM2: { + const char *curve_name = NULL; + if (!curve_type_to_str(key->material().ec.curve, &curve_name)) { + return RNP_ERROR_BAD_PARAMETERS; + } + json_object *jsocurve = json_object_new_string(curve_name); + if (!jsocurve) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "curve", jsocurve); + } break; + default: + break; + } + + // keyid + char keyid[PGP_KEY_ID_SIZE * 2 + 1]; + if (!rnp::hex_encode(key->keyid().data(), key->keyid().size(), keyid, sizeof(keyid))) { + return RNP_ERROR_GENERIC; + } + if (!add_json_string_field(jso, "keyid", keyid)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + // fingerprint + char fpr[PGP_FINGERPRINT_SIZE * 2 + 1]; + if (!rnp::hex_encode(key->fp().fingerprint, key->fp().length, fpr, sizeof(fpr))) { + return RNP_ERROR_GENERIC; + } + if (!add_json_string_field(jso, "fingerprint", fpr)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + // grip + char grip[PGP_KEY_GRIP_SIZE * 2 + 1]; + if (!rnp::hex_encode(key->grip().data(), key->grip().size(), grip, sizeof(grip))) { + return RNP_ERROR_GENERIC; + } + if (!add_json_string_field(jso, "grip", grip)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + // revoked + json_object *jsorevoked = json_object_new_boolean(key->revoked() ? true : false); + if (!jsorevoked) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "revoked", jsorevoked); + // creation time + json_object *jsocreation_time = json_object_new_int64(key->creation()); + if (!jsocreation_time) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "creation time", jsocreation_time); + // expiration + json_object *jsoexpiration = json_object_new_int64(key->expiration()); + if (!jsoexpiration) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "expiration", jsoexpiration); + // key flags (usage) + if (!add_json_key_usage(jso, key->flags())) { + return RNP_ERROR_OUT_OF_MEMORY; + } + // key flags (other) + if (!add_json_key_flags(jso, key->flags())) { + return RNP_ERROR_OUT_OF_MEMORY; + } + // parent / subkeys + if (key->is_primary()) { + json_object *jsosubkeys_arr = json_object_new_array(); + if (!jsosubkeys_arr) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "subkey grips", jsosubkeys_arr); + for (auto &subfp : key->subkey_fps()) { + const pgp_key_grip_t *subgrip = rnp_get_grip_by_fp(handle->ffi, subfp); + if (!subgrip) { + continue; + } + if (!rnp::hex_encode(subgrip->data(), subgrip->size(), grip, sizeof(grip))) { + return RNP_ERROR_GENERIC; + } + json_object *jsostr = json_object_new_string(grip); + if (!jsostr || json_object_array_add(jsosubkeys_arr, jsostr)) { + json_object_put(jsostr); + return RNP_ERROR_OUT_OF_MEMORY; + } + } + } else if (key->has_primary_fp()) { + auto pgrip = rnp_get_grip_by_fp(handle->ffi, key->primary_fp()); + if (pgrip) { + if (!rnp::hex_encode(pgrip->data(), pgrip->size(), grip, sizeof(grip))) { + return RNP_ERROR_GENERIC; + } + if (!add_json_string_field(jso, "primary key grip", grip)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + } + } + // public + json_object *jsopublic = json_object_new_object(); + if (!jsopublic) { + return RNP_ERROR_OUT_OF_MEMORY; + } + bool have_sec = handle->sec != NULL; + bool have_pub = handle->pub != NULL; + json_object_object_add(jso, "public key", jsopublic); + json_object_object_add( + jsopublic, "present", json_object_new_boolean(have_pub ? true : false)); + if (flags & RNP_JSON_PUBLIC_MPIS) { + json_object *jsompis = json_object_new_object(); + if (!jsompis) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jsopublic, "mpis", jsompis); + rnp_result_t tmpret; + if ((tmpret = add_json_public_mpis(jsompis, key))) { + return tmpret; + } + } + // secret + json_object *jsosecret = json_object_new_object(); + if (!jsosecret) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "secret key", jsosecret); + json_object_object_add( + jsosecret, "present", json_object_new_boolean(have_sec ? true : false)); + if (have_sec) { + bool locked = handle->sec->is_locked(); + if (flags & RNP_JSON_SECRET_MPIS) { + if (locked) { + json_object_object_add(jsosecret, "mpis", NULL); + } else { + json_object *jsompis = json_object_new_object(); + if (!jsompis) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jsosecret, "mpis", jsompis); + rnp_result_t tmpret; + if ((tmpret = add_json_secret_mpis(jsompis, handle->sec))) { + return tmpret; + } + } + } + json_object *jsolocked = json_object_new_boolean(locked ? true : false); + if (!jsolocked) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jsosecret, "locked", jsolocked); + json_object *jsoprotected = + json_object_new_boolean(handle->sec->is_protected() ? true : false); + if (!jsoprotected) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jsosecret, "protected", jsoprotected); + } + // userids + if (key->is_primary()) { + json_object *jsouids_arr = json_object_new_array(); + if (!jsouids_arr) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "userids", jsouids_arr); + for (size_t i = 0; i < key->uid_count(); i++) { + json_object *jsouid = json_object_new_string(key->get_uid(i).str.c_str()); + if (!jsouid || json_object_array_add(jsouids_arr, jsouid)) { + json_object_put(jsouid); + return RNP_ERROR_OUT_OF_MEMORY; + } + } + } + // signatures + if (flags & RNP_JSON_SIGNATURES) { + json_object *jsosigs_arr = json_object_new_array(); + if (!jsosigs_arr) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "signatures", jsosigs_arr); + for (size_t i = 0; i < key->sig_count(); i++) { + json_object *jsosig = json_object_new_object(); + if (!jsosig || json_object_array_add(jsosigs_arr, jsosig)) { + json_object_put(jsosig); + return RNP_ERROR_OUT_OF_MEMORY; + } + rnp_result_t tmpret; + if ((tmpret = + add_json_subsig(jsosig, key->is_subkey(), flags, &key->get_sig(i)))) { + return tmpret; + } + } + } + return RNP_SUCCESS; +} + +rnp_result_t +rnp_key_to_json(rnp_key_handle_t handle, uint32_t flags, char **result) +try { + rnp_result_t ret = RNP_ERROR_GENERIC; + json_object *jso = NULL; + + // checks + if (!handle || !result) { + return RNP_ERROR_NULL_POINTER; + } + jso = json_object_new_object(); + if (!jso) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + if ((ret = key_to_json(jso, handle, flags))) { + goto done; + } + *result = (char *) json_object_to_json_string_ext(jso, JSON_C_TO_STRING_PRETTY); + if (!*result) { + goto done; + } + *result = strdup(*result); + if (!*result) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + + ret = RNP_SUCCESS; +done: + json_object_put(jso); + return ret; +} +FFI_GUARD + +static rnp_result_t +rnp_dump_src_to_json(pgp_source_t *src, uint32_t flags, char **result) +{ + rnp_dump_ctx_t dumpctx = {}; + + dumpctx.dump_mpi = extract_flag(flags, RNP_JSON_DUMP_MPI); + dumpctx.dump_packets = extract_flag(flags, RNP_JSON_DUMP_RAW); + dumpctx.dump_grips = extract_flag(flags, RNP_JSON_DUMP_GRIP); + if (flags) { + return RNP_ERROR_BAD_PARAMETERS; + } + + json_object *jso = NULL; + rnp_result_t ret = stream_dump_packets_json(&dumpctx, src, &jso); + if (ret) { + goto done; + } + + *result = (char *) json_object_to_json_string_ext(jso, JSON_C_TO_STRING_PRETTY); + if (!*result) { + goto done; + } + *result = strdup(*result); + if (!*result) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + + ret = RNP_SUCCESS; +done: + json_object_put(jso); + return ret; +} + +rnp_result_t +rnp_key_packets_to_json(rnp_key_handle_t handle, bool secret, uint32_t flags, char **result) +try { + if (!handle || !result) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_key_t *key = secret ? handle->sec : handle->pub; + if (!key || (key->format == PGP_KEY_STORE_G10)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + auto vec = rnp_key_to_vec(*key); + rnp::MemorySource mem(vec); + return rnp_dump_src_to_json(&mem.src(), flags, result); +} +FFI_GUARD + +rnp_result_t +rnp_dump_packets_to_json(rnp_input_t input, uint32_t flags, char **result) +try { + if (!input || !result) { + return RNP_ERROR_NULL_POINTER; + } + + return rnp_dump_src_to_json(&input->src, flags, result); +} +FFI_GUARD + +rnp_result_t +rnp_dump_packets_to_output(rnp_input_t input, rnp_output_t output, uint32_t flags) +try { + if (!input || !output) { + return RNP_ERROR_NULL_POINTER; + } + + rnp_dump_ctx_t dumpctx = {}; + dumpctx.dump_mpi = extract_flag(flags, RNP_DUMP_MPI); + dumpctx.dump_packets = extract_flag(flags, RNP_DUMP_RAW); + dumpctx.dump_grips = extract_flag(flags, RNP_DUMP_GRIP); + if (flags) { + return RNP_ERROR_BAD_PARAMETERS; + } + + rnp_result_t ret = stream_dump_packets(&dumpctx, &input->src, &output->dst); + output->keep = true; + return ret; +} +FFI_GUARD + +// move to next key +static bool +key_iter_next_key(rnp_identifier_iterator_t it) +{ + // check if we not reached the end of the ring + *it->keyp = std::next(*it->keyp); + if (*it->keyp != it->store->keys.end()) { + it->uididx = 0; + return true; + } + // if we are currently on pubring, switch to secring (if not empty) + if (it->store == it->ffi->pubring && !it->ffi->secring->keys.empty()) { + it->store = it->ffi->secring; + *it->keyp = it->store->keys.begin(); + it->uididx = 0; + return true; + } + // we've gone through both rings + it->store = NULL; + return false; +} + +// move to next item (key or userid) +static bool +key_iter_next_item(rnp_identifier_iterator_t it) +{ + switch (it->type) { + case PGP_KEY_SEARCH_KEYID: + case PGP_KEY_SEARCH_FINGERPRINT: + case PGP_KEY_SEARCH_GRIP: + return key_iter_next_key(it); + case PGP_KEY_SEARCH_USERID: + it->uididx++; + while (it->uididx >= (*it->keyp)->uid_count()) { + if (!key_iter_next_key(it)) { + return false; + } + it->uididx = 0; + } + break; + default: + assert(false); + break; + } + return true; +} + +static bool +key_iter_first_key(rnp_identifier_iterator_t it) +{ + if (rnp_key_store_get_key_count(it->ffi->pubring)) { + it->store = it->ffi->pubring; + } else if (rnp_key_store_get_key_count(it->ffi->secring)) { + it->store = it->ffi->secring; + } else { + it->store = NULL; + return false; + } + *it->keyp = it->store->keys.begin(); + it->uididx = 0; + return true; +} + +static bool +key_iter_first_item(rnp_identifier_iterator_t it) +{ + switch (it->type) { + case PGP_KEY_SEARCH_KEYID: + case PGP_KEY_SEARCH_FINGERPRINT: + case PGP_KEY_SEARCH_GRIP: + return key_iter_first_key(it); + case PGP_KEY_SEARCH_USERID: + if (!key_iter_first_key(it)) { + return false; + } + it->uididx = 0; + while (it->uididx >= (*it->keyp)->uid_count()) { + if (!key_iter_next_key(it)) { + return false; + } + } + break; + default: + assert(false); + break; + } + return true; +} + +static bool +key_iter_get_item(const rnp_identifier_iterator_t it, char *buf, size_t buf_len) +{ + const pgp_key_t *key = &**it->keyp; + switch (it->type) { + case PGP_KEY_SEARCH_KEYID: { + if (!rnp::hex_encode(key->keyid().data(), key->keyid().size(), buf, buf_len)) { + return false; + } + break; + } + case PGP_KEY_SEARCH_FINGERPRINT: + if (!rnp::hex_encode(key->fp().fingerprint, key->fp().length, buf, buf_len)) { + return false; + } + break; + case PGP_KEY_SEARCH_GRIP: + if (!rnp::hex_encode(key->grip().data(), key->grip().size(), buf, buf_len)) { + return false; + } + break; + case PGP_KEY_SEARCH_USERID: { + if (it->uididx >= key->uid_count()) { + return false; + } + const pgp_userid_t &uid = key->get_uid(it->uididx); + if (uid.str.size() >= buf_len) { + return false; + } + memcpy(buf, uid.str.c_str(), uid.str.size() + 1); + } break; + default: + assert(false); + break; + } + return true; +} + +rnp_result_t +rnp_identifier_iterator_create(rnp_ffi_t ffi, + rnp_identifier_iterator_t *it, + const char * identifier_type) +try { + rnp_result_t ret = RNP_ERROR_GENERIC; + struct rnp_identifier_iterator_st *obj = NULL; + + // checks + if (!ffi || !it || !identifier_type) { + return RNP_ERROR_NULL_POINTER; + } + // create iterator + obj = (struct rnp_identifier_iterator_st *) calloc(1, sizeof(*obj)); + if (!obj) { + return RNP_ERROR_OUT_OF_MEMORY; + } + obj->ffi = ffi; + obj->keyp = new std::list<pgp_key_t>::iterator(); + obj->uididx = 0; + // parse identifier type + obj->type = static_cast<pgp_key_search_type_t>( + id_str_pair::lookup(identifier_type_map, identifier_type, PGP_KEY_SEARCH_UNKNOWN)); + if (obj->type == PGP_KEY_SEARCH_UNKNOWN) { + ret = RNP_ERROR_BAD_PARAMETERS; + goto done; + } + obj->tbl = json_object_new_object(); + if (!obj->tbl) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + // move to first item (if any) + key_iter_first_item(obj); + *it = obj; + + ret = RNP_SUCCESS; +done: + if (ret) { + rnp_identifier_iterator_destroy(obj); + } + return ret; +} +FFI_GUARD + +rnp_result_t +rnp_identifier_iterator_next(rnp_identifier_iterator_t it, const char **identifier) +try { + rnp_result_t ret = RNP_ERROR_GENERIC; + + // checks + if (!it || !identifier) { + return RNP_ERROR_NULL_POINTER; + } + // initialize the result to NULL + *identifier = NULL; + // this means we reached the end of the rings + if (!it->store) { + return RNP_SUCCESS; + } + // get the item + if (!key_iter_get_item(it, it->buf, sizeof(it->buf))) { + return RNP_ERROR_GENERIC; + } + bool exists; + bool iterator_valid = true; + while ((exists = json_object_object_get_ex(it->tbl, it->buf, NULL))) { + if (!((iterator_valid = key_iter_next_item(it)))) { + break; + } + if (!key_iter_get_item(it, it->buf, sizeof(it->buf))) { + return RNP_ERROR_GENERIC; + } + } + // see if we actually found a new entry + if (!exists) { + // TODO: Newer json-c has a useful return value for json_object_object_add, + // which doesn't require the json_object_object_get_ex check below. + json_object_object_add(it->tbl, it->buf, NULL); + if (!json_object_object_get_ex(it->tbl, it->buf, NULL)) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + *identifier = it->buf; + } + // prepare for the next one + if (iterator_valid) { + key_iter_next_item(it); + } + ret = RNP_SUCCESS; + +done: + if (ret) { + *identifier = NULL; + } + return ret; +} +FFI_GUARD + +rnp_result_t +rnp_identifier_iterator_destroy(rnp_identifier_iterator_t it) +try { + if (it) { + json_object_put(it->tbl); + if (it->keyp) { + delete it->keyp; + } + free(it); + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_guess_contents(rnp_input_t input, char **contents) +try { + if (!input || !contents) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_armored_msg_t msgtype = PGP_ARMORED_UNKNOWN; + if (is_armored_source(&input->src)) { + msgtype = rnp_armored_get_type(&input->src); + } else { + msgtype = rnp_armor_guess_type(&input->src); + } + const char *msg = id_str_pair::lookup(armor_type_map, msgtype); + size_t len = strlen(msg); + *contents = (char *) calloc(1, len + 1); + if (!*contents) { + return RNP_ERROR_OUT_OF_MEMORY; + } + memcpy(*contents, msg, len); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_enarmor(rnp_input_t input, rnp_output_t output, const char *type) +try { + pgp_armored_msg_t msgtype = PGP_ARMORED_UNKNOWN; + if (!input || !output) { + return RNP_ERROR_NULL_POINTER; + } + if (type) { + msgtype = static_cast<pgp_armored_msg_t>( + id_str_pair::lookup(armor_type_map, type, PGP_ARMORED_UNKNOWN)); + if (msgtype == PGP_ARMORED_UNKNOWN) { + RNP_LOG("Unsupported armor type: %s", type); + return RNP_ERROR_BAD_PARAMETERS; + } + } else { + msgtype = rnp_armor_guess_type(&input->src); + if (!msgtype) { + RNP_LOG("Unrecognized data to armor (try specifying a type)"); + return RNP_ERROR_BAD_PARAMETERS; + } + } + rnp_result_t ret = rnp_armor_source(&input->src, &output->dst, msgtype); + output->keep = !ret; + return ret; +} +FFI_GUARD + +rnp_result_t +rnp_dearmor(rnp_input_t input, rnp_output_t output) +try { + if (!input || !output) { + return RNP_ERROR_NULL_POINTER; + } + rnp_result_t ret = rnp_dearmor_source(&input->src, &output->dst); + output->keep = !ret; + return ret; +} +FFI_GUARD + +rnp_result_t +rnp_output_pipe(rnp_input_t input, rnp_output_t output) +try { + if (!input || !output) { + return RNP_ERROR_NULL_POINTER; + } + rnp_result_t ret = dst_write_src(&input->src, &output->dst); + output->keep = !ret; + return ret; +} +FFI_GUARD + +rnp_result_t +rnp_output_armor_set_line_length(rnp_output_t output, size_t llen) +try { + if (!output || !llen) { + return RNP_ERROR_BAD_PARAMETERS; + } + return armored_dst_set_line_length(&output->dst, llen); +} +FFI_GUARD + +const char * +rnp_backend_string() +{ + return rnp::backend_string(); +} + +const char * +rnp_backend_version() +{ + return rnp::backend_version(); +} diff --git a/comm/third_party/rnp/src/lib/rnp/rnp_export.h b/comm/third_party/rnp/src/lib/rnp/rnp_export.h new file mode 100644 index 0000000000..3fdce5edcf --- /dev/null +++ b/comm/third_party/rnp/src/lib/rnp/rnp_export.h @@ -0,0 +1,42 @@ + +#ifndef RNP_EXPORT +#define RNP_EXPORT + +#ifdef RNP_STATIC +# define RNP_API +# define RNP_NO_EXPORT +#else +# ifndef RNP_API +# ifdef librnp_EXPORTS + /* We are building this library */ +# define RNP_API +# else + /* We are using this library */ +# define RNP_API +# endif +# endif + +# ifndef RNP_NO_EXPORT +# define RNP_NO_EXPORT +# endif +#endif + +#ifndef RNP_DEPRECATED +# define RNP_DEPRECATED __attribute__ ((__deprecated__)) +#endif + +#ifndef RNP_DEPRECATED_EXPORT +# define RNP_DEPRECATED_EXPORT RNP_API RNP_DEPRECATED +#endif + +#ifndef RNP_DEPRECATED_NO_EXPORT +# define RNP_DEPRECATED_NO_EXPORT RNP_NO_EXPORT RNP_DEPRECATED +#endif + +#if 0 /* DEFINE_NO_DEPRECATED */ +# ifndef RNP_NO_DEPRECATED +# define RNP_NO_DEPRECATED +# endif +#endif + +#endif /* RNP_EXPORT */ diff --git a/comm/third_party/rnp/src/lib/sec_profile.cpp b/comm/third_party/rnp/src/lib/sec_profile.cpp new file mode 100644 index 0000000000..f9d0de8362 --- /dev/null +++ b/comm/third_party/rnp/src/lib/sec_profile.cpp @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2021 [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "sec_profile.hpp" +#include "types.h" +#include "defaults.h" +#include <ctime> +#include <algorithm> + +namespace rnp { +bool +SecurityRule::operator==(const SecurityRule &src) const +{ + return (type == src.type) && (feature == src.feature) && (from == src.from) && + (level == src.level) && (override == src.override) && (action == src.action); +} + +bool +SecurityRule::operator!=(const SecurityRule &src) const +{ + return !(*this == src); +} + +bool +SecurityRule::matches(FeatureType ftype, + int fval, + uint64_t ftime, + SecurityAction faction) const noexcept +{ + if ((type != ftype) || (feature != fval) || (from > ftime)) { + return false; + } + return (action == SecurityAction::Any) || (faction == SecurityAction::Any) || + (action == faction); +} + +size_t +SecurityProfile::size() const noexcept +{ + return rules_.size(); +} + +SecurityRule & +SecurityProfile::add_rule(const SecurityRule &rule) +{ + rules_.push_back(rule); + return rules_.back(); +} + +SecurityRule & +SecurityProfile::add_rule(SecurityRule &&rule) +{ + rules_.emplace_back(rule); + return rules_.back(); +} + +bool +SecurityProfile::del_rule(const SecurityRule &rule) +{ + size_t old_size = rules_.size(); + rules_.erase(std::remove_if(rules_.begin(), + rules_.end(), + [rule](const SecurityRule &item) { return item == rule; }), + rules_.end()); + return old_size != rules_.size(); +} + +void +SecurityProfile::clear_rules(FeatureType type, int feature) +{ + rules_.erase(std::remove_if(rules_.begin(), + rules_.end(), + [type, feature](const SecurityRule &item) { + return (item.type == type) && (item.feature == feature); + }), + rules_.end()); +} + +void +SecurityProfile::clear_rules(FeatureType type) +{ + rules_.erase( + std::remove_if(rules_.begin(), + rules_.end(), + [type](const SecurityRule &item) { return item.type == type; }), + rules_.end()); +} + +void +SecurityProfile::clear_rules() +{ + rules_.clear(); +} + +bool +SecurityProfile::has_rule(FeatureType type, + int value, + uint64_t time, + SecurityAction action) const noexcept +{ + for (auto &rule : rules_) { + if (rule.matches(type, value, time, action)) { + return true; + } + } + return false; +} + +const SecurityRule & +SecurityProfile::get_rule(FeatureType type, + int value, + uint64_t time, + SecurityAction action) const +{ + const SecurityRule *res = nullptr; + for (auto &rule : rules_) { + if (!rule.matches(type, value, time, action)) { + continue; + } + if (rule.override) { + return rule; + } + if (!res || (res->from < rule.from)) { + res = &rule; + } + } + if (!res) { + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + return *res; +} + +SecurityLevel +SecurityProfile::hash_level(pgp_hash_alg_t hash, + uint64_t time, + SecurityAction action) const noexcept +{ + if (!has_rule(FeatureType::Hash, hash, time, action)) { + return def_level(); + } + + try { + return get_rule(FeatureType::Hash, hash, time, action).level; + } catch (const std::exception &e) { + /* this should never happen however we need to satisfy noexcept specifier */ + return def_level(); + } +} + +SecurityLevel +SecurityProfile::def_level() const +{ + return SecurityLevel::Default; +}; + +SecurityContext::SecurityContext() : time_(0), prov_state_(NULL), rng(RNG::Type::DRBG) +{ + /* Initialize crypto provider if needed (currently only for OpenSSL 3.0) */ + if (!rnp::backend_init(&prov_state_)) { + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + /* Mark SHA-1 data signature insecure since 2019-01-19, as GnuPG does */ + profile.add_rule({FeatureType::Hash, + PGP_HASH_SHA1, + SecurityLevel::Insecure, + 1547856000, + SecurityAction::VerifyData}); + /* Mark SHA-1 key signature insecure since 2024-01-19 by default */ + profile.add_rule({FeatureType::Hash, + PGP_HASH_SHA1, + SecurityLevel::Insecure, + 1705629600, + SecurityAction::VerifyKey}); + /* Mark MD5 insecure since 2012-01-01 */ + profile.add_rule({FeatureType::Hash, PGP_HASH_MD5, SecurityLevel::Insecure, 1325376000}); +} + +SecurityContext::~SecurityContext() +{ + rnp::backend_finish(prov_state_); +} + +size_t +SecurityContext::s2k_iterations(pgp_hash_alg_t halg) +{ + if (!s2k_iterations_.count(halg)) { + s2k_iterations_[halg] = + pgp_s2k_compute_iters(halg, DEFAULT_S2K_MSEC, DEFAULT_S2K_TUNE_MSEC); + } + return s2k_iterations_[halg]; +} + +void +SecurityContext::set_time(uint64_t time) noexcept +{ + time_ = time; +} + +uint64_t +SecurityContext::time() const noexcept +{ + return time_ ? time_ : ::time(NULL); +} + +} // namespace rnp diff --git a/comm/third_party/rnp/src/lib/sec_profile.hpp b/comm/third_party/rnp/src/lib/sec_profile.hpp new file mode 100644 index 0000000000..a4d84563fc --- /dev/null +++ b/comm/third_party/rnp/src/lib/sec_profile.hpp @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2021 [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef RNP_SEC_PROFILE_H_ +#define RNP_SEC_PROFILE_H_ + +#include <cstdint> +#include <vector> +#include <unordered_map> +#include "repgp/repgp_def.h" +#include "crypto/rng.h" + +namespace rnp { + +enum class FeatureType { Hash, Cipher, PublicKey }; +enum class SecurityLevel { Disabled, Insecure, Default }; +enum class SecurityAction { Any, VerifyKey, VerifyData }; + +struct SecurityRule { + FeatureType type; + int feature; + SecurityLevel level; + uint64_t from; + bool override; + SecurityAction action; + + SecurityRule(FeatureType ftype, + int fval, + SecurityLevel flevel, + uint64_t ffrom = 0, + SecurityAction faction = SecurityAction::Any) + : type(ftype), feature(fval), level(flevel), from(ffrom), override(false), + action(faction){}; + + bool operator==(const SecurityRule &src) const; + bool operator!=(const SecurityRule &src) const; + + bool matches(FeatureType ftype, + int fval, + uint64_t ftime, + SecurityAction faction) const noexcept; +}; + +class SecurityProfile { + private: + std::vector<SecurityRule> rules_; + + public: + size_t size() const noexcept; + SecurityRule &add_rule(const SecurityRule &rule); + SecurityRule &add_rule(SecurityRule &&rule); + bool del_rule(const SecurityRule &rule); + void clear_rules(FeatureType type, int feature); + void clear_rules(FeatureType type); + void clear_rules(); + + bool has_rule(FeatureType type, + int value, + uint64_t time, + SecurityAction action = SecurityAction::Any) const noexcept; + const SecurityRule &get_rule(FeatureType type, + int value, + uint64_t time, + SecurityAction action = SecurityAction::Any) const; + SecurityLevel hash_level(pgp_hash_alg_t hash, + uint64_t time, + SecurityAction action = SecurityAction::Any) const noexcept; + SecurityLevel def_level() const; +}; + +class SecurityContext { + std::unordered_map<int, size_t> s2k_iterations_; + uint64_t time_; + void * prov_state_; + + public: + SecurityProfile profile; + RNG rng; + + SecurityContext(); + ~SecurityContext(); + + size_t s2k_iterations(pgp_hash_alg_t halg); + + void set_time(uint64_t time) noexcept; + uint64_t time() const noexcept; +}; +} // namespace rnp + +#endif diff --git a/comm/third_party/rnp/src/lib/types.h b/comm/third_party/rnp/src/lib/types.h new file mode 100644 index 0000000000..5a67d4225c --- /dev/null +++ b/comm/third_party/rnp/src/lib/types.h @@ -0,0 +1,482 @@ +/* + * Copyright (c) 2017-2021, [Ribose Inc](https://www.ribose.com). + * Copyright (c) 2009 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is originally derived from software contributed to + * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and + * carried further by Ribose Inc (https://www.ribose.com). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/* + * Copyright (c) 2005-2008 Nominet UK (www.nic.uk) + * All rights reserved. + * Contributors: Ben Laurie, Rachel Willmer. The Contributors have asserted + * their moral rights under the UK Copyright Design and Patents Act 1988 to + * be recorded as the authors of this copyright work. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. + * + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef TYPES_H_ +#define TYPES_H_ + +#include <stdint.h> +#include <string> +#include <vector> +#include <array> +#include <cstring> +#include <type_traits> + +#include <rnp/rnp_def.h> +#include "crypto/common.h" +#include "sec_profile.hpp" + +/* SHA1 Hash Size */ +#define PGP_SHA1_HASH_SIZE 20 + +/* Maximum length of the packet header */ +#define PGP_MAX_HEADER_SIZE 6 + +/* Maximum supported userid length */ +#define MAX_ID_LENGTH 128 + +/* Maximum supported password length */ +#define MAX_PASSWORD_LENGTH 256 + +class id_str_pair { + public: + int id; + const char *str; + + /** + * @brief Lookup constant pair array for the specified id or string value. + * Note: array must be finished with NULL string to stop the lookup. + * + * @param pair pointer to the const array with pairs. + * @param id identifier to search for + * @param notfound value to return if identifier is not found. + * @return string, representing the identifier. + */ + static const char *lookup(const id_str_pair pair[], + int id, + const char * notfound = "unknown"); + static int lookup(const id_str_pair pair[], const char *str, int notfound = 0); + static int lookup(const id_str_pair pair[], + const std::vector<uint8_t> &bytes, + int notfound = 0); + static int lookup(const id_str_pair pair[], + const std::basic_string<uint8_t> &bytes, + int notfound = 0); +}; + +/** pgp_fingerprint_t */ +typedef struct pgp_fingerprint_t { + uint8_t fingerprint[PGP_FINGERPRINT_SIZE]; + unsigned length; + bool operator==(const pgp_fingerprint_t &src) const; + bool operator!=(const pgp_fingerprint_t &src) const; +} pgp_fingerprint_t; + +typedef std::array<uint8_t, PGP_KEY_GRIP_SIZE> pgp_sig_id_t; + +namespace std { +template <> struct hash<pgp_fingerprint_t> { + std::size_t + operator()(pgp_fingerprint_t const &fp) const noexcept + { + /* since fingerprint value is hash itself, we may use its low bytes */ + size_t res = 0; + static_assert(sizeof(fp.fingerprint) == PGP_FINGERPRINT_SIZE, + "pgp_fingerprint_t size mismatch"); + static_assert(PGP_FINGERPRINT_SIZE >= sizeof(res), "pgp_fingerprint_t size mismatch"); + std::memcpy(&res, fp.fingerprint, sizeof(res)); + return res; + } +}; + +template <> struct hash<pgp_sig_id_t> { + std::size_t + operator()(pgp_sig_id_t const &sigid) const noexcept + { + /* since signature id value is hash itself, we may use its low bytes */ + size_t res = 0; + static_assert(std::tuple_size<pgp_sig_id_t>::value >= sizeof(res), + "pgp_sig_id_t size mismatch"); + std::memcpy(&res, sigid.data(), sizeof(res)); + return res; + } +}; +}; // namespace std + +typedef std::array<uint8_t, PGP_KEY_GRIP_SIZE> pgp_key_grip_t; + +typedef std::array<uint8_t, PGP_KEY_ID_SIZE> pgp_key_id_t; + +namespace rnp { +class rnp_exception : public std::exception { + rnp_result_t code_; + + public: + rnp_exception(rnp_result_t code = RNP_ERROR_GENERIC) : code_(code){}; + virtual const char * + what() const throw() + { + return "rnp_exception"; + }; + rnp_result_t + code() const + { + return code_; + }; +}; +} // namespace rnp + +/* validity information for the signature/key/userid */ +typedef struct pgp_validity_t { + bool validated{}; /* item was validated */ + bool valid{}; /* item is valid by signature/key checks and calculations. + Still may be revoked or expired. */ + bool expired{}; /* item is expired */ + + void mark_valid(); + void reset(); +} pgp_validity_t; + +/** + * Type to keep public/secret key mpis without any openpgp-dependent data. + */ +typedef struct pgp_key_material_t { + pgp_pubkey_alg_t alg; /* algorithm of the key */ + bool secret; /* secret part of the key material is populated */ + pgp_validity_t validity; /* key material validation status */ + + union { + pgp_rsa_key_t rsa; + pgp_dsa_key_t dsa; + pgp_eg_key_t eg; + pgp_ec_key_t ec; + }; + + size_t bits() const; + size_t qbits() const; + void validate(rnp::SecurityContext &ctx, bool reset = true); + bool valid() const; +} pgp_key_material_t; + +/** + * Type to keep signature without any openpgp-dependent data. + */ +typedef struct pgp_signature_material_t { + union { + pgp_rsa_signature_t rsa; + pgp_dsa_signature_t dsa; + pgp_ec_signature_t ecc; + pgp_eg_signature_t eg; + }; +} pgp_signature_material_t; + +/** + * Type to keep pk-encrypted data without any openpgp-dependent data. + */ +typedef struct pgp_encrypted_material_t { + union { + pgp_rsa_encrypted_t rsa; + pgp_eg_encrypted_t eg; + pgp_sm2_encrypted_t sm2; + pgp_ecdh_encrypted_t ecdh; + }; +} pgp_encrypted_material_t; + +typedef struct pgp_s2k_t { + pgp_s2k_usage_t usage{}; + + /* below fields may not all be valid, depending on the usage field above */ + pgp_s2k_specifier_t specifier{}; + pgp_hash_alg_t hash_alg{}; + uint8_t salt[PGP_SALT_SIZE]; + unsigned iterations{}; + /* GnuPG custom s2k data */ + pgp_s2k_gpg_extension_t gpg_ext_num{}; + uint8_t gpg_serial_len{}; + uint8_t gpg_serial[16]; + /* Experimental s2k data */ + std::vector<uint8_t> experimental{}; +} pgp_s2k_t; + +typedef struct pgp_key_protection_t { + pgp_s2k_t s2k{}; /* string-to-key kdf params */ + pgp_symm_alg_t symm_alg{}; /* symmetric alg */ + pgp_cipher_mode_t cipher_mode{}; /* block cipher mode */ + uint8_t iv[PGP_MAX_BLOCK_SIZE]; +} pgp_key_protection_t; + +typedef struct pgp_key_pkt_t pgp_key_pkt_t; +typedef struct pgp_userid_pkt_t pgp_userid_pkt_t; +typedef struct pgp_signature_t pgp_signature_t; + +/* Signature subpacket, see 5.2.3.1 in RFC 4880 and RFC 4880 bis 02 */ +typedef struct pgp_sig_subpkt_t { + pgp_sig_subpacket_type_t type; /* type of the subpacket */ + size_t len; /* length of the data */ + uint8_t * data; /* raw subpacket data, excluding the header */ + bool critical : 1; /* critical flag */ + bool hashed : 1; /* whether subpacket is hashed or not */ + bool parsed : 1; /* whether subpacket was successfully parsed */ + union { + uint32_t create; /* 5.2.3.4. Signature Creation Time */ + uint32_t expiry; /* 5.2.3.6. Key Expiration Time */ + /* 5.2.3.10. Signature Expiration Time */ + bool exportable; /* 5.2.3.11. Exportable Certification */ + struct { + uint8_t level; + uint8_t amount; + } trust; /* 5.2.3.13. Trust Signature */ + struct { + const char *str; + unsigned len; + } regexp; /* 5.2.3.14. Regular Expression */ + bool revocable; /* 5.2.3.12. Revocable */ + struct { + uint8_t *arr; + unsigned len; + } preferred; /* 5.2.3.7. Preferred Symmetric Algorithms */ + /* 5.2.3.8. Preferred Hash Algorithms */ + /* 5.2.3.9. Preferred Compression Algorithms */ + struct { + uint8_t klass; + pgp_pubkey_alg_t pkalg; + uint8_t * fp; + } revocation_key; /* 5.2.3.15. Revocation Key */ + uint8_t *issuer; /* 5.2.3.5. Issuer */ + struct { + uint8_t flags[4]; + unsigned nlen; + unsigned vlen; + bool human; + const uint8_t *name; + const uint8_t *value; + } notation; /* 5.2.3.16. Notation Data */ + struct { + bool no_modify; + } ks_prefs; /* 5.2.3.17. Key Server Preferences */ + struct { + const char *uri; + unsigned len; + } preferred_ks; /* 5.2.3.18. Preferred Key Server */ + bool primary_uid; /* 5.2.3.19. Primary User ID */ + struct { + const char *uri; + unsigned len; + } policy; /* 5.2.3.20. Policy URI */ + uint8_t key_flags; /* 5.2.3.21. Key Flags */ + struct { + const char *uid; + unsigned len; + } signer; /* 5.2.3.22. Signer's User ID */ + struct { + pgp_revocation_type_t code; + const char * str; + unsigned len; + } revocation_reason; /* 5.2.3.23. Reason for Revocation */ + uint8_t features; /* 5.2.3.24. Features */ + struct { + pgp_pubkey_alg_t pkalg; + pgp_hash_alg_t halg; + uint8_t * hash; + unsigned hlen; + } sig_target; /* 5.2.3.25. Signature Target */ + pgp_signature_t *sig; /* 5.2.3.27. Embedded Signature */ + struct { + uint8_t version; + uint8_t *fp; + unsigned len; + } issuer_fp; /* 5.2.3.28. Issuer Fingerprint, RFC 4880 bis 04 */ + } fields; /* parsed contents of the subpacket */ + + pgp_sig_subpkt_t() + : type(PGP_SIG_SUBPKT_UNKNOWN), len(0), data(NULL), critical(false), hashed(false), + parsed(false), fields({}){}; + pgp_sig_subpkt_t(const pgp_sig_subpkt_t &src); + pgp_sig_subpkt_t(pgp_sig_subpkt_t &&src); + pgp_sig_subpkt_t &operator=(pgp_sig_subpkt_t &&src); + pgp_sig_subpkt_t &operator=(const pgp_sig_subpkt_t &src); + ~pgp_sig_subpkt_t(); + bool parse(); +} pgp_sig_subpkt_t; + +typedef struct pgp_one_pass_sig_t pgp_one_pass_sig_t; + +typedef enum { + /* first octet */ + PGP_KEY_SERVER_NO_MODIFY = 0x80 +} pgp_key_server_prefs_t; + +typedef struct pgp_literal_hdr_t { + uint8_t format; + char fname[256]; + uint8_t fname_len; + uint32_t timestamp; +} pgp_literal_hdr_t; + +typedef struct pgp_aead_hdr_t { + int version{}; /* version of the AEAD packet */ + pgp_symm_alg_t ealg; /* underlying symmetric algorithm */ + pgp_aead_alg_t aalg; /* AEAD algorithm, i.e. EAX, OCB, etc */ + int csize{}; /* chunk size bits */ + uint8_t iv[PGP_AEAD_MAX_NONCE_LEN]; /* initial vector for the message */ + size_t ivlen{}; /* iv length */ + + pgp_aead_hdr_t() : ealg(PGP_SA_UNKNOWN), aalg(PGP_AEAD_NONE) + { + } +} pgp_aead_hdr_t; + +/** litdata_type_t */ +typedef enum { + PGP_LDT_BINARY = 'b', + PGP_LDT_TEXT = 't', + PGP_LDT_UTF8 = 'u', + PGP_LDT_LOCAL = 'l', + PGP_LDT_LOCAL2 = '1' +} pgp_litdata_enum; + +/* user revocation info */ +typedef struct pgp_subsig_t pgp_subsig_t; + +typedef struct pgp_revoke_t { + uint32_t uid{}; /* index in uid array */ + pgp_revocation_type_t code{}; /* revocation code */ + std::string reason; /* revocation reason */ + pgp_sig_id_t sigid{}; /* id of the corresponding subsig */ + + pgp_revoke_t() = default; + pgp_revoke_t(pgp_subsig_t &sig); +} pgp_revoke_t; + +typedef struct pgp_user_prefs_t { + // preferred symmetric algs (pgp_symm_alg_t) + std::vector<uint8_t> symm_algs{}; + // preferred hash algs (pgp_hash_alg_t) + std::vector<uint8_t> hash_algs{}; + // preferred compression algs (pgp_compression_type_t) + std::vector<uint8_t> z_algs{}; + // key server preferences (pgp_key_server_prefs_t) + std::vector<uint8_t> ks_prefs{}; + // preferred key server + std::string key_server{}; + + void set_symm_algs(const std::vector<uint8_t> &algs); + void add_symm_alg(pgp_symm_alg_t alg); + void set_hash_algs(const std::vector<uint8_t> &algs); + void add_hash_alg(pgp_hash_alg_t alg); + void set_z_algs(const std::vector<uint8_t> &algs); + void add_z_alg(pgp_compression_type_t alg); + void set_ks_prefs(const std::vector<uint8_t> &prefs); + void add_ks_pref(pgp_key_server_prefs_t pref); +} pgp_user_prefs_t; + +struct rnp_keygen_ecc_params_t { + pgp_curve_t curve; +}; + +struct rnp_keygen_rsa_params_t { + uint32_t modulus_bit_len; +}; + +struct rnp_keygen_dsa_params_t { + size_t p_bitlen; + size_t q_bitlen; +}; + +struct rnp_keygen_elgamal_params_t { + size_t key_bitlen; +}; + +/* structure used to hold context of key generation */ +namespace rnp { +class SecurityContext; +} + +typedef struct rnp_keygen_crypto_params_t { + // Asymmteric algorithm that user requesed key for + pgp_pubkey_alg_t key_alg; + // Hash to be used for key signature + pgp_hash_alg_t hash_alg; + // Pointer to security context + rnp::SecurityContext *ctx; + union { + struct rnp_keygen_ecc_params_t ecc; + struct rnp_keygen_rsa_params_t rsa; + struct rnp_keygen_dsa_params_t dsa; + struct rnp_keygen_elgamal_params_t elgamal; + }; +} rnp_keygen_crypto_params_t; + +typedef struct rnp_selfsig_cert_info_t { + std::string userid; /* userid, required */ + uint8_t key_flags{}; /* key flags */ + uint32_t key_expiration{}; /* key expiration time (sec), 0 = no expiration */ + pgp_user_prefs_t prefs{}; /* user preferences, optional */ + bool primary; /* mark this as the primary user id */ + + /** + * @brief Populate uid and sig packet with data stored in this struct. + * At some point we should get rid of it. + */ + void populate(pgp_userid_pkt_t &uid, pgp_signature_t &sig); +} rnp_selfsig_cert_info_t; + +typedef struct rnp_selfsig_binding_info_t { + uint8_t key_flags; + uint32_t key_expiration; +} rnp_selfsig_binding_info_t; + +typedef struct rnp_keygen_primary_desc_t { + rnp_keygen_crypto_params_t crypto{}; + rnp_selfsig_cert_info_t cert{}; +} rnp_keygen_primary_desc_t; + +typedef struct rnp_keygen_subkey_desc_t { + rnp_keygen_crypto_params_t crypto; + rnp_selfsig_binding_info_t binding; +} rnp_keygen_subkey_desc_t; + +typedef struct rnp_key_protection_params_t { + pgp_symm_alg_t symm_alg; + pgp_cipher_mode_t cipher_mode; + unsigned iterations; + pgp_hash_alg_t hash_alg; +} rnp_key_protection_params_t; + +#endif /* TYPES_H_ */ diff --git a/comm/third_party/rnp/src/lib/utils.cpp b/comm/third_party/rnp/src/lib/utils.cpp new file mode 100644 index 0000000000..3c6216c604 --- /dev/null +++ b/comm/third_party/rnp/src/lib/utils.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "types.h" +#include "str-utils.h" + +const char * +id_str_pair::lookup(const id_str_pair pair[], int id, const char *notfound) +{ + while (pair && pair->str) { + if (pair->id == id) { + return pair->str; + } + pair++; + } + return notfound; +} + +int +id_str_pair::lookup(const id_str_pair pair[], const char *str, int notfound) +{ + while (pair && pair->str) { + if (rnp::str_case_eq(str, pair->str)) { + return pair->id; + } + pair++; + } + return notfound; +} + +int +id_str_pair::lookup(const id_str_pair pair[], const std::vector<uint8_t> &bytes, int notfound) +{ + while (pair && pair->str) { + if ((strlen(pair->str) == bytes.size()) && + !memcmp(pair->str, bytes.data(), bytes.size())) { + return pair->id; + } + pair++; + } + return notfound; +} + +int +id_str_pair::lookup(const id_str_pair pair[], + const std::basic_string<uint8_t> &bytes, + int notfound) +{ + while (pair && pair->str) { + if ((strlen(pair->str) == bytes.size()) && + !memcmp(pair->str, bytes.data(), bytes.size())) { + return pair->id; + } + pair++; + } + return notfound; +} diff --git a/comm/third_party/rnp/src/lib/utils.h b/comm/third_party/rnp/src/lib/utils.h new file mode 100644 index 0000000000..3035ee5105 --- /dev/null +++ b/comm/third_party/rnp/src/lib/utils.h @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2017-2021 [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef RNP_UTILS_H_ +#define RNP_UTILS_H_ + +#include <stdio.h> +#include <limits.h> +#include "logging.h" + +/* number of elements in an array */ +#define ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a))) + +/* + * @params + * array: array of the structures to lookup + * id_field name of the field to compare against + * ret_field filed to return + * lookup_value lookup value + * ret return value + */ +#define ARRAY_LOOKUP_BY_ID(array, id_field, ret_field, lookup_value, ret) \ + do { \ + for (size_t i__ = 0; i__ < ARRAY_SIZE(array); i__++) { \ + if ((array)[i__].id_field == (lookup_value)) { \ + (ret) = (array)[i__].ret_field; \ + break; \ + } \ + } \ + } while (0) + +/* Portable way to convert bits to bytes */ + +#define BITS_TO_BYTES(b) (((b) + (CHAR_BIT - 1)) / CHAR_BIT) + +/* Load little-endian 32-bit from y to x in portable fashion */ + +inline void +LOAD32LE(uint32_t &x, const uint8_t y[4]) +{ + x = (static_cast<uint32_t>(y[3]) << 24) | (static_cast<uint32_t>(y[2]) << 16) | + (static_cast<uint32_t>(y[1]) << 8) | (static_cast<uint32_t>(y[0]) << 0); +} + +/* Store big-endian 32-bit value x in y */ +inline void +STORE32BE(uint8_t x[4], uint32_t y) +{ + x[0] = (uint8_t)(y >> 24) & 0xff; + x[1] = (uint8_t)(y >> 16) & 0xff; + x[2] = (uint8_t)(y >> 8) & 0xff; + x[3] = (uint8_t)(y >> 0) & 0xff; +} + +/* Store big-endian 64-bit value x in y */ +inline void +STORE64BE(uint8_t x[8], uint64_t y) +{ + x[0] = (uint8_t)(y >> 56) & 0xff; + x[1] = (uint8_t)(y >> 48) & 0xff; + x[2] = (uint8_t)(y >> 40) & 0xff; + x[3] = (uint8_t)(y >> 32) & 0xff; + x[4] = (uint8_t)(y >> 24) & 0xff; + x[5] = (uint8_t)(y >> 16) & 0xff; + x[6] = (uint8_t)(y >> 8) & 0xff; + x[7] = (uint8_t)(y >> 0) & 0xff; +} + +inline char * +getenv_logname(void) +{ + char *name = getenv("LOGNAME"); + if (!name) { + name = getenv("USER"); + } + return name; +} + +#endif diff --git a/comm/third_party/rnp/src/lib/version.h.in b/comm/third_party/rnp/src/lib/version.h.in new file mode 100644 index 0000000000..2382cfd95d --- /dev/null +++ b/comm/third_party/rnp/src/lib/version.h.in @@ -0,0 +1,52 @@ +/* Copyright (c) 2018 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#define RNP_VERSION_MAJOR @RNP_VERSION_MAJOR@ +#define RNP_VERSION_MINOR @RNP_VERSION_MINOR@ +#define RNP_VERSION_PATCH @RNP_VERSION_PATCH@ + +#define RNP_VERSION_STRING "@RNP_VERSION@" +#define RNP_VERSION_STRING_FULL "@RNP_VERSION_FULL@" + +#define RNP_VERSION_COMMIT_TIMESTAMP @RNP_VERSION_COMMIT_TIMESTAMP@ + +// using a 32-bit version with 10 bits per component +#define RNP_VERSION_COMPONENT_MASK 0x3ff +#define RNP_VERSION_MAJOR_SHIFT 20 +#define RNP_VERSION_MINOR_SHIFT 10 +#define RNP_VERSION_PATCH_SHIFT 0 +#define RNP_VERSION_CODE_FOR(major, minor, patch) \ + (((major & RNP_VERSION_COMPONENT_MASK) << RNP_VERSION_MAJOR_SHIFT) | \ + ((minor & RNP_VERSION_COMPONENT_MASK) << RNP_VERSION_MINOR_SHIFT) | \ + ((patch & RNP_VERSION_COMPONENT_MASK) << RNP_VERSION_PATCH_SHIFT)) + +#define RNP_VERSION_CODE \ + RNP_VERSION_CODE_FOR(RNP_VERSION_MAJOR, RNP_VERSION_MINOR, RNP_VERSION_PATCH) + +static_assert(RNP_VERSION_MAJOR <= RNP_VERSION_COMPONENT_MASK && + RNP_VERSION_MINOR <= RNP_VERSION_COMPONENT_MASK && + RNP_VERSION_PATCH <= RNP_VERSION_COMPONENT_MASK, + "version components must be within range"); + diff --git a/comm/third_party/rnp/src/librekey/g23_sexp.hpp b/comm/third_party/rnp/src/librekey/g23_sexp.hpp new file mode 100644 index 0000000000..b888680f50 --- /dev/null +++ b/comm/third_party/rnp/src/librekey/g23_sexp.hpp @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE RIBOSE, INC. AND CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RNP_G23_SEXP_HPP +#define RNP_G23_SEXP_HPP + +#include "sexp/sexp.h" +#include "sexp/ext-key-format.h" + +#define SXP_MAX_DEPTH 30 + +class gnupg_sexp_t; +typedef std::shared_ptr<gnupg_sexp_t> p_gnupg_sexp; + +class gnupg_sexp_t : public sexp::sexp_list_t { + /* write gnupg_sexp_t contents, adding padding, for the further encryption */ + rnp::secure_vector<uint8_t> write_padded(size_t padblock) const; + + public: + void + add(const std::string &str) + { + push_back(std::shared_ptr<sexp::sexp_string_t>(new sexp::sexp_string_t(str))); + }; + void + add(const uint8_t *data, size_t size) + { + push_back(std::shared_ptr<sexp::sexp_string_t>(new sexp::sexp_string_t(data, size))); + }; + void add(unsigned u); + p_gnupg_sexp add_sub(); + void add_mpi(const std::string &name, const pgp_mpi_t &val); + void add_curve(const std::string &name, const pgp_ec_key_t &key); + void add_pubkey(const pgp_key_pkt_t &key); + void add_seckey(const pgp_key_pkt_t &key); + void add_protected_seckey(pgp_key_pkt_t & seckey, + const std::string & password, + rnp::SecurityContext &ctx); + bool parse(const char *r_bytes, size_t r_length, size_t depth = 1); + bool write(pgp_dest_t &dst) const noexcept; +}; + +class gnupg_extended_private_key_t : public ext_key_format::extended_private_key_t { + public: + bool parse(const char *r_bytes, size_t r_length, size_t depth = 1); +}; + +#endif diff --git a/comm/third_party/rnp/src/librekey/kbx_blob.hpp b/comm/third_party/rnp/src/librekey/kbx_blob.hpp new file mode 100644 index 0000000000..274413c6e9 --- /dev/null +++ b/comm/third_party/rnp/src/librekey/kbx_blob.hpp @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE RIBOSE, INC. AND CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RNP_KBX_BLOB_HPP +#define RNP_KBX_BLOB_HPP + +typedef enum : uint8_t { + KBX_EMPTY_BLOB = 0, + KBX_HEADER_BLOB = 1, + KBX_PGP_BLOB = 2, + KBX_X509_BLOB = 3 +} kbx_blob_type_t; + +class kbx_blob_t { + protected: + kbx_blob_type_t type_; + std::vector<uint8_t> image_; + + uint8_t ru8(size_t idx); + uint16_t ru16(size_t idx); + uint32_t ru32(size_t idx); + + public: + virtual ~kbx_blob_t() = default; + kbx_blob_t(std::vector<uint8_t> &data); + virtual bool + parse() + { + return true; + }; + + kbx_blob_type_t + type() + { + return type_; + } + + std::vector<uint8_t> & + image() + { + return image_; + } + + uint32_t + length() const noexcept + { + return image_.size(); + } +}; + +class kbx_header_blob_t : public kbx_blob_t { + protected: + uint8_t version_{}; + uint16_t flags_{}; + uint32_t file_created_at_{}; + uint32_t last_maintenance_run_{}; + + public: + kbx_header_blob_t(std::vector<uint8_t> &data) : kbx_blob_t(data){}; + bool parse(); + + uint32_t + file_created_at() + { + return file_created_at_; + } +}; + +typedef struct { + uint8_t fp[PGP_FINGERPRINT_SIZE]; + uint32_t keyid_offset; + uint16_t flags; +} kbx_pgp_key_t; + +typedef struct { + uint32_t offset; + uint32_t length; + uint16_t flags; + uint8_t validity; +} kbx_pgp_uid_t; + +typedef struct { + uint32_t expired; +} kbx_pgp_sig_t; + +class kbx_pgp_blob_t : public kbx_blob_t { + protected: + uint8_t version_{}; + uint16_t flags_{}; + uint32_t keyblock_offset_{}; + uint32_t keyblock_length_{}; + + std::vector<uint8_t> sn_{}; + std::vector<kbx_pgp_key_t> keys_{}; + std::vector<kbx_pgp_uid_t> uids_{}; + std::vector<kbx_pgp_sig_t> sigs_{}; + + uint8_t ownertrust_{}; + uint8_t all_validity_{}; + + uint32_t recheck_after_{}; + uint32_t latest_timestamp_{}; + uint32_t blob_created_at_{}; + + public: + kbx_pgp_blob_t(std::vector<uint8_t> &data) : kbx_blob_t(data){}; + + uint32_t + keyblock_offset() + { + return keyblock_offset_; + } + + uint32_t + keyblock_length() + { + return keyblock_length_; + } + + size_t + nkeys() + { + return keys_.size(); + } + size_t + nuids() + { + return uids_.size(); + } + size_t + nsigs() + { + return sigs_.size(); + } + + bool parse(); +}; + +#endif diff --git a/comm/third_party/rnp/src/librekey/key_store_g10.cpp b/comm/third_party/rnp/src/librekey/key_store_g10.cpp new file mode 100644 index 0000000000..dcf3fe112a --- /dev/null +++ b/comm/third_party/rnp/src/librekey/key_store_g10.cpp @@ -0,0 +1,1243 @@ +/* + * Copyright (c) 2017-2022, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <memory> +#include <sstream> +#include <string.h> +#include <stdlib.h> +#include <limits.h> +#include <time.h> +#include "config.h" + +#include <librepgp/stream-packet.h> +#include "key_store_pgp.h" +#include "key_store_g10.h" + +#include "crypto/common.h" +#include "crypto/mem.h" +#include "crypto/cipher.hpp" +#include "pgp-key.h" +#include "time-utils.h" + +#include "g23_sexp.hpp" +using namespace ext_key_format; +using namespace sexp; + +#define G10_CBC_IV_SIZE 16 + +#define G10_OCB_NONCE_SIZE 12 + +#define G10_SHA1_HASH_SIZE 20 + +#define G10_PROTECTED_AT_SIZE 15 + +typedef struct format_info { + pgp_symm_alg_t cipher; + pgp_cipher_mode_t cipher_mode; + pgp_hash_alg_t hash_alg; + size_t cipher_block_size; + const char * g10_type; + size_t iv_size; + size_t tag_length; + bool with_associated_data; + bool disable_padding; +} format_info; + +static bool g10_calculated_hash(const pgp_key_pkt_t &key, + const char * protected_at, + uint8_t * checksum); + +static const format_info formats[] = {{PGP_SA_AES_128, + PGP_CIPHER_MODE_CBC, + PGP_HASH_SHA1, + 16, + "openpgp-s2k3-sha1-aes-cbc", + G10_CBC_IV_SIZE, + 0, + false, + true}, + {PGP_SA_AES_256, + PGP_CIPHER_MODE_CBC, + PGP_HASH_SHA1, + 16, + "openpgp-s2k3-sha1-aes256-cbc", + G10_CBC_IV_SIZE, + 0, + false, + true}, + {PGP_SA_AES_128, + PGP_CIPHER_MODE_OCB, + PGP_HASH_SHA1, + 16, + "openpgp-s2k3-ocb-aes", + G10_OCB_NONCE_SIZE, + 16, + true, + true}}; + +static const id_str_pair g10_alg_aliases[] = { + {PGP_PKA_RSA, "rsa"}, + {PGP_PKA_RSA, "openpgp-rsa"}, + {PGP_PKA_RSA, "oid.1.2.840.113549.1.1.1"}, + {PGP_PKA_RSA, "oid.1.2.840.113549.1.1.1"}, + {PGP_PKA_ELGAMAL, "elg"}, + {PGP_PKA_ELGAMAL, "elgamal"}, + {PGP_PKA_ELGAMAL, "openpgp-elg"}, + {PGP_PKA_ELGAMAL, "openpgp-elg-sig"}, + {PGP_PKA_DSA, "dsa"}, + {PGP_PKA_DSA, "openpgp-dsa"}, + {PGP_PKA_ECDSA, "ecc"}, + {PGP_PKA_ECDSA, "ecdsa"}, + {PGP_PKA_ECDH, "ecdh"}, + {PGP_PKA_EDDSA, "eddsa"}, + {0, NULL}, +}; + +static const id_str_pair g10_curve_aliases[] = { + {PGP_CURVE_NIST_P_256, "NIST P-256"}, + {PGP_CURVE_NIST_P_256, "1.2.840.10045.3.1.7"}, + {PGP_CURVE_NIST_P_256, "prime256v1"}, + {PGP_CURVE_NIST_P_256, "secp256r1"}, + {PGP_CURVE_NIST_P_256, "nistp256"}, + {PGP_CURVE_NIST_P_384, "NIST P-384"}, + {PGP_CURVE_NIST_P_384, "secp384r1"}, + {PGP_CURVE_NIST_P_384, "1.3.132.0.34"}, + {PGP_CURVE_NIST_P_384, "nistp384"}, + {PGP_CURVE_NIST_P_521, "NIST P-521"}, + {PGP_CURVE_NIST_P_521, "secp521r1"}, + {PGP_CURVE_NIST_P_521, "1.3.132.0.35"}, + {PGP_CURVE_NIST_P_521, "nistp521"}, + {PGP_CURVE_25519, "Curve25519"}, + {PGP_CURVE_25519, "1.3.6.1.4.1.3029.1.5.1"}, + {PGP_CURVE_ED25519, "Ed25519"}, + {PGP_CURVE_ED25519, "1.3.6.1.4.1.11591.15.1"}, + {PGP_CURVE_BP256, "brainpoolP256r1"}, + {PGP_CURVE_BP256, "1.3.36.3.3.2.8.1.1.7"}, + {PGP_CURVE_BP384, "brainpoolP384r1"}, + {PGP_CURVE_BP384, "1.3.36.3.3.2.8.1.1.11"}, + {PGP_CURVE_BP512, "brainpoolP512r1"}, + {PGP_CURVE_BP512, "1.3.36.3.3.2.8.1.1.13"}, + {PGP_CURVE_P256K1, "secp256k1"}, + {PGP_CURVE_P256K1, "1.3.132.0.10"}, + {0, NULL}, +}; + +static const id_str_pair g10_curve_names[] = { + {PGP_CURVE_NIST_P_256, "NIST P-256"}, + {PGP_CURVE_NIST_P_384, "NIST P-384"}, + {PGP_CURVE_NIST_P_521, "NIST P-521"}, + {PGP_CURVE_ED25519, "Ed25519"}, + {PGP_CURVE_25519, "Curve25519"}, + {PGP_CURVE_BP256, "brainpoolP256r1"}, + {PGP_CURVE_BP384, "brainpoolP384r1"}, + {PGP_CURVE_BP512, "brainpoolP512r1"}, + {PGP_CURVE_P256K1, "secp256k1"}, + {0, NULL}, +}; + +static const format_info * +find_format(pgp_symm_alg_t cipher, pgp_cipher_mode_t mode, pgp_hash_alg_t hash_alg) +{ + for (size_t i = 0; i < ARRAY_SIZE(formats); i++) { + if (formats[i].cipher == cipher && formats[i].cipher_mode == mode && + formats[i].hash_alg == hash_alg) { + return &formats[i]; + } + } + return NULL; +} + +static const format_info * +parse_format(const char *format, size_t format_len) +{ + for (size_t i = 0; i < ARRAY_SIZE(formats); i++) { + if (strlen(formats[i].g10_type) == format_len && + !strncmp(formats[i].g10_type, format, format_len)) { + return &formats[i]; + } + } + return NULL; +} + +#define STR_HELPER(x) #x +#define STR(x) STR_HELPER(x) + +void +gnupg_sexp_t::add(unsigned u) +{ + char s[sizeof(STR(UINT_MAX)) + 1]; + snprintf(s, sizeof(s), "%u", u); + push_back(std::make_shared<sexp_string_t>(s)); +} + +std::shared_ptr<gnupg_sexp_t> +gnupg_sexp_t::add_sub() +{ + auto res = std::make_shared<gnupg_sexp_t>(); + push_back(res); + return res; +} + +/* + * Parse S-expression + * https://people.csail.mit.edu/rivest/Sexp.txt + * sexp library supports canonical and advanced transport formats + * as well as base64 encoding of canonical + */ + +bool +gnupg_sexp_t::parse(const char *r_bytes, size_t r_length, size_t depth) +{ + bool res = false; + std::istringstream iss(std::string(r_bytes, r_length)); + try { + sexp_input_stream_t sis(&iss, depth); + sexp_list_t::parse(sis.set_byte_size(8)->get_char()); + res = true; + } catch (sexp_exception_t &e) { + RNP_LOG("%s", e.what()); + } + return res; +} + +/* + * Parse gnupg extended private key file ("G23") + * https://github.com/gpg/gnupg/blob/main/agent/keyformat.txt + */ + +bool +gnupg_extended_private_key_t::parse(const char *r_bytes, size_t r_length, size_t depth) +{ + bool res = false; + std::istringstream iss(std::string(r_bytes, r_length)); + try { + ext_key_input_stream_t g23_is(&iss, depth); + g23_is.scan(*this); + res = true; + } catch (sexp_exception_t &e) { + RNP_LOG("%s", e.what()); + } + return res; +} + +static const sexp_list_t * +lookup_var(const sexp_list_t *list, const std::string &name) noexcept +{ + const sexp_list_t *res = nullptr; + // We are looking for a list element (condition 1) + // that: + // -- has at least two SEXP elements (condition 2) + // -- has a SEXP string at 0 postion (condition 3) + // matching given name (condition 4) + auto match = [name](const std::shared_ptr<sexp_object_t> &ptr) { + bool r = false; + auto r1 = ptr->sexp_list_view(); + if (r1 && r1->size() >= 2) { // conditions (1) and (2) + auto r2 = r1->sexp_string_at(0); + if (r2 && r2 == name) // conditions (3) and (4) + r = true; + } + return r; + }; + auto r3 = std::find_if(list->begin(), list->end(), match); + if (r3 == list->end()) + RNP_LOG("Haven't got variable '%s'", name.c_str()); + else + res = (*r3)->sexp_list_view(); + return res; +} + +static const sexp_string_t * +lookup_var_data(const sexp_list_t *list, const std::string &name) noexcept +{ + const sexp_list_t *var = lookup_var(list, name); + if (!var) { + return NULL; + } + + if (!var->at(1)->is_sexp_string()) { + RNP_LOG("Expected block value"); + return NULL; + } + + return var->sexp_string_at(1); +} + +static bool +read_mpi(const sexp_list_t *list, const std::string &name, pgp_mpi_t &val) noexcept +{ + const sexp_string_t *data = lookup_var_data(list, name); + if (!data) { + return false; + } + + /* strip leading zero */ + const auto &bytes = data->get_string(); + if ((bytes.size() > 1) && !bytes[0] && (bytes[1] & 0x80)) { + return mem2mpi(&val, bytes.data() + 1, bytes.size() - 1); + } + return mem2mpi(&val, bytes.data(), bytes.size()); +} + +static bool +read_curve(const sexp_list_t *list, const std::string &name, pgp_ec_key_t &key) noexcept +{ + const sexp_string_t *data = lookup_var_data(list, name); + if (!data) { + return false; + } + + const auto &bytes = data->get_string(); + pgp_curve_t curve = static_cast<pgp_curve_t>( + id_str_pair::lookup(g10_curve_aliases, data->get_string(), PGP_CURVE_UNKNOWN)); + if (curve != PGP_CURVE_UNKNOWN) { + key.curve = curve; + return true; + } + RNP_LOG("Unknown curve: %.*s", (int) bytes.size(), (char *) bytes.data()); + return false; +} + +void +gnupg_sexp_t::add_mpi(const std::string &name, const pgp_mpi_t &mpi) +{ + auto sub_s_exp = add_sub(); + sub_s_exp->push_back(std::make_shared<sexp_string_t>(name)); + auto value_block = std::make_shared<sexp_string_t>(); + sub_s_exp->push_back(value_block); + + sexp_simple_string_t data; + size_t len = mpi_bytes(&mpi); + size_t idx; + + for (idx = 0; (idx < len) && !mpi.mpi[idx]; idx++) + ; + + if (idx < len) { + if (mpi.mpi[idx] & 0x80) { + data.append(0); + data.std::basic_string<uint8_t>::append(mpi.mpi + idx, len - idx); + } else { + data.assign(mpi.mpi + idx, mpi.mpi + len); + } + value_block->set_string(data); + } +} + +void +gnupg_sexp_t::add_curve(const std::string &name, const pgp_ec_key_t &key) +{ + const char *curve = id_str_pair::lookup(g10_curve_names, key.curve, NULL); + if (!curve) { + RNP_LOG("unknown curve"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + auto psub_s_exp = add_sub(); + psub_s_exp->add(name); + psub_s_exp->add(curve); + + if ((key.curve != PGP_CURVE_ED25519) && (key.curve != PGP_CURVE_25519)) { + return; + } + + psub_s_exp = add_sub(); + psub_s_exp->add("flags"); + psub_s_exp->add((key.curve == PGP_CURVE_ED25519) ? "eddsa" : "djb-tweak"); +} + +static bool +parse_pubkey(pgp_key_pkt_t &pubkey, const sexp_list_t *s_exp, pgp_pubkey_alg_t alg) +{ + pubkey.version = PGP_V4; + pubkey.alg = alg; + pubkey.material.alg = alg; + switch (alg) { + case PGP_PKA_DSA: + if (!read_mpi(s_exp, "p", pubkey.material.dsa.p) || + !read_mpi(s_exp, "q", pubkey.material.dsa.q) || + !read_mpi(s_exp, "g", pubkey.material.dsa.g) || + !read_mpi(s_exp, "y", pubkey.material.dsa.y)) { + return false; + } + break; + + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + if (!read_mpi(s_exp, "n", pubkey.material.rsa.n) || + !read_mpi(s_exp, "e", pubkey.material.rsa.e)) { + return false; + } + break; + + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + if (!read_mpi(s_exp, "p", pubkey.material.eg.p) || + !read_mpi(s_exp, "g", pubkey.material.eg.g) || + !read_mpi(s_exp, "y", pubkey.material.eg.y)) { + return false; + } + break; + case PGP_PKA_ECDSA: + case PGP_PKA_ECDH: + case PGP_PKA_EDDSA: + if (!read_curve(s_exp, "curve", pubkey.material.ec) || + !read_mpi(s_exp, "q", pubkey.material.ec.p)) { + return false; + } + if (pubkey.material.ec.curve == PGP_CURVE_ED25519) { + /* need to adjust it here since 'ecc' key type defaults to ECDSA */ + pubkey.alg = PGP_PKA_EDDSA; + pubkey.material.alg = PGP_PKA_EDDSA; + } + break; + default: + RNP_LOG("Unsupported public key algorithm: %d", (int) alg); + return false; + } + + return true; +} + +static bool +parse_seckey(pgp_key_pkt_t &seckey, const sexp_list_t *s_exp, pgp_pubkey_alg_t alg) +{ + switch (alg) { + case PGP_PKA_DSA: + if (!read_mpi(s_exp, "x", seckey.material.dsa.x)) { + return false; + } + break; + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + if (!read_mpi(s_exp, "d", seckey.material.rsa.d) || + !read_mpi(s_exp, "p", seckey.material.rsa.p) || + !read_mpi(s_exp, "q", seckey.material.rsa.q) || + !read_mpi(s_exp, "u", seckey.material.rsa.u)) { + return false; + } + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + if (!read_mpi(s_exp, "x", seckey.material.eg.x)) { + return false; + } + break; + case PGP_PKA_ECDSA: + case PGP_PKA_ECDH: + case PGP_PKA_EDDSA: + if (!read_mpi(s_exp, "d", seckey.material.ec.x)) { + return false; + } + break; + default: + RNP_LOG("Unsupported public key algorithm: %d", (int) alg); + return false; + } + + seckey.material.secret = true; + return true; +} + +static bool +decrypt_protected_section(const sexp_simple_string_t &encrypted_data, + const pgp_key_pkt_t & seckey, + const std::string & password, + gnupg_sexp_t & r_s_exp, + uint8_t * associated_data, + size_t associated_data_len) +{ + const format_info * info = NULL; + unsigned keysize = 0; + uint8_t derived_key[PGP_MAX_KEY_SIZE]; + uint8_t * decrypted_data = NULL; + size_t decrypted_data_len = 0; + size_t output_written = 0; + size_t input_consumed = 0; + std::unique_ptr<Cipher> dec; + bool ret = false; + + const char *decrypted_bytes; + size_t s_exp_len; + + // sanity checks + const pgp_key_protection_t &prot = seckey.sec_protection; + keysize = pgp_key_size(prot.symm_alg); + if (!keysize) { + RNP_LOG("parse_seckey: unknown symmetric algo"); + goto done; + } + // find the protection format in our table + info = find_format(prot.symm_alg, prot.cipher_mode, prot.s2k.hash_alg); + if (!info) { + RNP_LOG("Unsupported format, alg: %d, chiper_mode: %d, hash: %d", + prot.symm_alg, + prot.cipher_mode, + prot.s2k.hash_alg); + goto done; + } + + // derive the key + if (pgp_s2k_iterated(prot.s2k.hash_alg, + derived_key, + keysize, + password.c_str(), + prot.s2k.salt, + prot.s2k.iterations)) { + RNP_LOG("pgp_s2k_iterated failed"); + goto done; + } + + // decrypt + decrypted_data = (uint8_t *) malloc(encrypted_data.size()); + if (decrypted_data == NULL) { + RNP_LOG("can't allocate memory"); + goto done; + } + dec = Cipher::decryption( + info->cipher, info->cipher_mode, info->tag_length, info->disable_padding); + if (!dec || !dec->set_key(derived_key, keysize)) { + goto done; + } + if (associated_data != nullptr && associated_data_len != 0) { + if (!dec->set_ad(associated_data, associated_data_len)) { + goto done; + } + } + // Nonce shall be the last chunk of associated data + if (!dec->set_iv(prot.iv, info->iv_size)) { + goto done; + } + if (!dec->finish(decrypted_data, + encrypted_data.size(), + &output_written, + encrypted_data.data(), + encrypted_data.size(), + &input_consumed)) { + goto done; + } + decrypted_data_len = output_written; + s_exp_len = decrypted_data_len; + decrypted_bytes = (const char *) decrypted_data; + + // parse and validate the decrypted s-exp + + if (!r_s_exp.parse(decrypted_bytes, s_exp_len, SXP_MAX_DEPTH)) { + goto done; + } + if (!r_s_exp.size() || r_s_exp.at(0)->is_sexp_string()) { + RNP_LOG("Hasn't got sub s-exp with key data."); + goto done; + } + ret = true; +done: + if (!ret) { + r_s_exp.clear(); + } + secure_clear(decrypted_data, decrypted_data_len); + free(decrypted_data); + return ret; +} + +static bool +parse_protected_seckey(pgp_key_pkt_t &seckey, const sexp_list_t *list, const char *password) +{ + // find and validate the protected section + const sexp_list_t *protected_key = lookup_var(list, "protected"); + if (!protected_key) { + RNP_LOG("missing protected section"); + return false; + } + if (protected_key->size() != 4 || !protected_key->at(1)->is_sexp_string() || + protected_key->at(2)->is_sexp_string() || !protected_key->at(3)->is_sexp_string()) { + RNP_LOG("Wrong protected format, expected: (protected mode (params) " + "encrypted_octet_string)\n"); + return false; + } + + // lookup the protection format + auto & fmt_bt = protected_key->sexp_string_at(1)->get_string(); + const format_info *format = parse_format((const char *) fmt_bt.data(), fmt_bt.size()); + if (!format) { + RNP_LOG("Unsupported protected mode: '%.*s'\n", + (int) fmt_bt.size(), + (const char *) fmt_bt.data()); + return false; + } + + // fill in some fields based on the lookup above + pgp_key_protection_t &prot = seckey.sec_protection; + prot.symm_alg = format->cipher; + prot.cipher_mode = format->cipher_mode; + prot.s2k.hash_alg = format->hash_alg; + + // locate and validate the protection parameters + auto params = protected_key->sexp_list_at(2); + if (params->size() != 2 || params->at(0)->is_sexp_string() || + !params->at(1)->is_sexp_string()) { + RNP_LOG("Wrong params format, expected: ((hash salt no_of_iterations) iv)\n"); + return false; + } + + // locate and validate the (hash salt no_of_iterations) exp + auto alg = params->sexp_list_at(0); + if (alg->size() != 3 || !alg->at(0)->is_sexp_string() || !alg->at(1)->is_sexp_string() || + !alg->at(2)->is_sexp_string()) { + RNP_LOG("Wrong params sub-level format, expected: (hash salt no_of_iterations)\n"); + return false; + } + auto &hash_bt = alg->sexp_string_at(0)->get_string(); + if (hash_bt != "sha1") { + RNP_LOG("Wrong hashing algorithm, should be sha1 but %.*s\n", + (int) hash_bt.size(), + (const char *) hash_bt.data()); + return false; + } + + // fill in some constant values + prot.s2k.hash_alg = PGP_HASH_SHA1; + prot.s2k.usage = PGP_S2KU_ENCRYPTED_AND_HASHED; + prot.s2k.specifier = PGP_S2KS_ITERATED_AND_SALTED; + + // check salt size + auto &salt_bt = alg->sexp_string_at(1)->get_string(); + if (salt_bt.size() != PGP_SALT_SIZE) { + RNP_LOG("Wrong salt size, should be %d but %d\n", PGP_SALT_SIZE, (int) salt_bt.size()); + return false; + } + + // salt + memcpy(prot.s2k.salt, salt_bt.data(), salt_bt.size()); + // s2k iterations + auto iter = alg->sexp_string_at(2); + prot.s2k.iterations = iter->as_unsigned(); + if (prot.s2k.iterations == UINT_MAX) { + RNP_LOG("Wrong numbers of iteration, %.*s\n", + (int) iter->get_string().size(), + (const char *) iter->get_string().data()); + return false; + } + + // iv + auto &iv_bt = params->sexp_string_at(1)->get_string(); + if (iv_bt.size() != format->iv_size) { + RNP_LOG("Wrong nonce size, should be %zu but %zu\n", format->iv_size, iv_bt.size()); + return false; + } + memcpy(prot.iv, iv_bt.data(), iv_bt.size()); + + // we're all done if no password was provided (decryption not requested) + if (!password) { + seckey.material.secret = false; + return true; + } + + // password was provided, so decrypt + auto & enc_bt = protected_key->sexp_string_at(3)->get_string(); + gnupg_sexp_t decrypted_s_exp; + + // Build associated data (AD) that is not included in the ciphertext but that should be + // authenticated. gnupg builds AD as follows (file 'protect.c' do_encryption/do_decryption + // functions) + // -- "protected-private-key" section content + // -- less "protected" subsection + // -- serialized in canonical format + std::string associated_data; + if (format->with_associated_data) { + std::ostringstream oss(std::ios_base::binary); + sexp_output_stream_t os(&oss); + os.var_put_char('('); + for_each(list->begin(), list->end(), [&](const std::shared_ptr<sexp_object_t> &obj) { + if (obj->sexp_list_view() != protected_key) + obj->print_canonical(&os); + }); + os.var_put_char(')'); + associated_data = oss.str(); + } + + if (!decrypt_protected_section( + enc_bt, + seckey, + password, + decrypted_s_exp, + format->with_associated_data ? (uint8_t *) associated_data.data() : nullptr, + format->with_associated_data ? associated_data.length() : 0)) { + return false; + } + // see if we have a protected-at section + char protected_at[G10_PROTECTED_AT_SIZE] = {0}; + auto protected_at_data = lookup_var_data(list, "protected-at"); + if (protected_at_data) { + if (protected_at_data->get_string().size() != G10_PROTECTED_AT_SIZE) { + RNP_LOG("protected-at has wrong length: %zu, expected, %d\n", + protected_at_data->get_string().size(), + G10_PROTECTED_AT_SIZE); + return false; + } + memcpy(protected_at, + protected_at_data->get_string().data(), + protected_at_data->get_string().size()); + } + // parse MPIs + if (!parse_seckey(seckey, decrypted_s_exp.sexp_list_at(0), seckey.alg)) { + RNP_LOG("failed to parse seckey"); + return false; + } + // check hash, if present + if (decrypted_s_exp.size() > 1) { + if (decrypted_s_exp.at(1)->is_sexp_string()) { + RNP_LOG("Wrong hash block type."); + return false; + } + auto sub_el = decrypted_s_exp.sexp_list_at(1); + if (sub_el->size() < 3 || !sub_el->at(0)->is_sexp_string() || + !sub_el->at(1)->is_sexp_string() || !sub_el->at(2)->is_sexp_string()) { + RNP_LOG("Wrong hash block structure."); + return false; + } + + auto &hkey = sub_el->sexp_string_at(0)->get_string(); + if (hkey != "hash") { + RNP_LOG("Has got wrong hash block at encrypted key data."); + return false; + } + auto &halg = sub_el->sexp_string_at(1)->get_string(); + if (halg != "sha1") { + RNP_LOG("Supported only sha1 hash at encrypted private key."); + return false; + } + uint8_t checkhash[G10_SHA1_HASH_SIZE]; + if (!g10_calculated_hash(seckey, protected_at, checkhash)) { + RNP_LOG("failed to calculate hash"); + return false; + } + auto &hval = sub_el->sexp_string_at(2)->get_string(); + if (hval.size() != G10_SHA1_HASH_SIZE || + memcmp(checkhash, hval.data(), G10_SHA1_HASH_SIZE)) { + RNP_LOG("Incorrect hash at encrypted private key."); + return false; + } + } + seckey.material.secret = true; + return true; +} + +static bool +g23_parse_seckey(pgp_key_pkt_t &seckey, + const uint8_t *data, + size_t data_len, + const char * password) +{ + gnupg_extended_private_key_t g23_extended_key; + + const char *bytes = (const char *) data; + if (!g23_extended_key.parse(bytes, data_len, SXP_MAX_DEPTH)) { + RNP_LOG("Failed to parse s-exp."); + return false; + } + // Although the library parses full g23 extended key + // we extract and use g10 part only + const sexp_list_t &g10_key = g23_extended_key.key; + + /* expected format: + * (<type> + * (<algo> + * (x <mpi>) + * (y <mpi>) + * ) + * ) + */ + + if (g10_key.size() != 2 || !g10_key.at(0)->is_sexp_string() || + !g10_key.at(1)->is_sexp_list()) { + RNP_LOG("Wrong format, expected: (<type> (...))"); + return false; + } + + bool is_protected = false; + + auto &name = g10_key.sexp_string_at(0)->get_string(); + if (name == "private-key") { + is_protected = false; + } else if (name == "protected-private-key") { + is_protected = true; + } else { + RNP_LOG("Unsupported top-level block: '%.*s'", + (int) name.size(), + (const char *) name.data()); + return false; + } + + auto alg_s_exp = g10_key.sexp_list_at(1); + if (alg_s_exp->size() < 2) { + RNP_LOG("Wrong count of algorithm-level elements: %zu", alg_s_exp->size()); + return false; + } + + if (!alg_s_exp->at(0)->is_sexp_string()) { + RNP_LOG("Expected block with algorithm name, but has s-exp"); + return false; + } + + auto & alg_bt = alg_s_exp->sexp_string_at(0)->get_string(); + pgp_pubkey_alg_t alg = static_cast<pgp_pubkey_alg_t>( + id_str_pair::lookup(g10_alg_aliases, alg_bt.c_str(), PGP_PKA_NOTHING)); + if (alg == PGP_PKA_NOTHING) { + RNP_LOG( + "Unsupported algorithm: '%.*s'", (int) alg_bt.size(), (const char *) alg_bt.data()); + return false; + } + + bool ret = false; + if (!parse_pubkey(seckey, alg_s_exp, alg)) { + RNP_LOG("failed to parse pubkey"); + goto done; + } + + if (is_protected) { + if (!parse_protected_seckey(seckey, alg_s_exp, password)) { + goto done; + } + } else { + seckey.sec_protection.s2k.usage = PGP_S2KU_NONE; + seckey.sec_protection.symm_alg = PGP_SA_PLAINTEXT; + seckey.sec_protection.s2k.hash_alg = PGP_HASH_UNKNOWN; + if (!parse_seckey(seckey, alg_s_exp, alg)) { + RNP_LOG("failed to parse seckey"); + goto done; + } + } + ret = true; +done: + if (!ret) { + seckey = pgp_key_pkt_t(); + } + return ret; +} + +pgp_key_pkt_t * +g10_decrypt_seckey(const pgp_rawpacket_t &raw, + const pgp_key_pkt_t & pubkey, + const char * password) +{ + if (!password) { + return NULL; + } + auto seckey = std::unique_ptr<pgp_key_pkt_t>(new pgp_key_pkt_t(pubkey, false)); + if (!g23_parse_seckey(*seckey, raw.raw.data(), raw.raw.size(), password)) { + return NULL; + } + /* g10 has the same 'ecc' algo for ECDSA/ECDH/EDDSA. Probably should be better place to fix + * this. */ + seckey->alg = pubkey.alg; + seckey->material.alg = pubkey.material.alg; + return seckey.release(); +} + +static bool +copy_secret_fields(pgp_key_pkt_t &dst, const pgp_key_pkt_t &src) +{ + switch (src.alg) { + case PGP_PKA_DSA: + dst.material.dsa.x = src.material.dsa.x; + break; + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + dst.material.rsa.d = src.material.rsa.d; + dst.material.rsa.p = src.material.rsa.p; + dst.material.rsa.q = src.material.rsa.q; + dst.material.rsa.u = src.material.rsa.u; + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + dst.material.eg.x = src.material.eg.x; + break; + case PGP_PKA_ECDSA: + case PGP_PKA_ECDH: + case PGP_PKA_EDDSA: + dst.material.ec.x = src.material.ec.x; + break; + default: + RNP_LOG("Unsupported public key algorithm: %d", (int) src.alg); + return false; + } + + dst.material.secret = src.material.secret; + dst.sec_protection = src.sec_protection; + dst.tag = is_subkey_pkt(dst.tag) ? PGP_PKT_SECRET_SUBKEY : PGP_PKT_SECRET_KEY; + return true; +} + +bool +rnp_key_store_g10_from_src(rnp_key_store_t * key_store, + pgp_source_t * src, + const pgp_key_provider_t *key_provider) +{ + try { + /* read src to the memory */ + rnp::MemorySource memsrc(*src); + /* parse secret key: fills material and sec_protection only */ + pgp_key_pkt_t seckey; + if (!g23_parse_seckey(seckey, (uint8_t *) memsrc.memory(), memsrc.size(), NULL)) { + return false; + } + /* copy public key fields if any */ + pgp_key_t key; + if (key_provider) { + pgp_key_request_ctx_t req_ctx(PGP_OP_MERGE_INFO, false, PGP_KEY_SEARCH_GRIP); + if (!rnp_key_store_get_key_grip(&seckey.material, req_ctx.search.by.grip)) { + return false; + } + + const pgp_key_t *pubkey = pgp_request_key(key_provider, &req_ctx); + if (!pubkey) { + return false; + } + + /* public key packet has some more info then the secret part */ + key = pgp_key_t(*pubkey, true); + if (!copy_secret_fields(key.pkt(), seckey)) { + return false; + } + } else { + key.set_pkt(std::move(seckey)); + } + /* set rawpkt */ + key.set_rawpkt( + pgp_rawpacket_t((uint8_t *) memsrc.memory(), memsrc.size(), PGP_PKT_RESERVED)); + key.format = PGP_KEY_STORE_G10; + if (!rnp_key_store_add_key(key_store, &key)) { + return false; + } + return true; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } +} + +/* + * Write G10 S-exp to buffer + * + * Supported format: (1:a2:ab(3:asd1:a)) + */ +bool +gnupg_sexp_t::write(pgp_dest_t &dst) const noexcept +{ + bool res = false; + try { + std::ostringstream oss(std::ios_base::binary); + sexp_output_stream_t os(&oss); + print_canonical(&os); + const std::string &s = oss.str(); + const char * ss = s.c_str(); + dst_write(&dst, ss, s.size()); + res = (dst.werr == RNP_SUCCESS); + + } catch (...) { + } + + return res; +} + +void +gnupg_sexp_t::add_pubkey(const pgp_key_pkt_t &key) +{ + switch (key.alg) { + case PGP_PKA_DSA: + add("dsa"); + add_mpi("p", key.material.dsa.p); + add_mpi("q", key.material.dsa.q); + add_mpi("g", key.material.dsa.g); + add_mpi("y", key.material.dsa.y); + break; + case PGP_PKA_RSA_SIGN_ONLY: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA: + add("rsa"); + add_mpi("n", key.material.rsa.n); + add_mpi("e", key.material.rsa.e); + break; + case PGP_PKA_ELGAMAL: + add("elg"); + add_mpi("p", key.material.eg.p); + add_mpi("g", key.material.eg.g); + add_mpi("y", key.material.eg.y); + break; + case PGP_PKA_ECDSA: + case PGP_PKA_ECDH: + case PGP_PKA_EDDSA: + add("ecc"); + add_curve("curve", key.material.ec); + add_mpi("q", key.material.ec.p); + break; + default: + RNP_LOG("Unsupported public key algorithm: %d", (int) key.alg); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } +} + +void +gnupg_sexp_t::add_seckey(const pgp_key_pkt_t &key) +{ + switch (key.alg) { + case PGP_PKA_DSA: + add_mpi("x", key.material.dsa.x); + break; + case PGP_PKA_RSA_SIGN_ONLY: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA: + add_mpi("d", key.material.rsa.d); + add_mpi("p", key.material.rsa.p); + add_mpi("q", key.material.rsa.q); + add_mpi("u", key.material.rsa.u); + break; + case PGP_PKA_ELGAMAL: + add_mpi("x", key.material.eg.x); + break; + case PGP_PKA_ECDSA: + case PGP_PKA_ECDH: + case PGP_PKA_EDDSA: { + add_mpi("d", key.material.ec.x); + break; + } + default: + RNP_LOG("Unsupported public key algorithm: %d", (int) key.alg); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } +} + +rnp::secure_vector<uint8_t> +gnupg_sexp_t::write_padded(size_t padblock) const +{ + rnp::MemoryDest raw; + raw.set_secure(true); + + if (!write(raw.dst())) { + RNP_LOG("failed to serialize s_exp"); + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + + // add padding! + size_t padding = padblock - raw.writeb() % padblock; + for (size_t i = 0; i < padding; i++) { + raw.write("X", 1); + } + if (raw.werr()) { + RNP_LOG("failed to write padding"); + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + const uint8_t *mem = (uint8_t *) raw.memory(); + return rnp::secure_vector<uint8_t>(mem, mem + raw.writeb()); +} + +void +gnupg_sexp_t::add_protected_seckey(pgp_key_pkt_t & seckey, + const std::string & password, + rnp::SecurityContext &ctx) +{ + pgp_key_protection_t &prot = seckey.sec_protection; + if (prot.s2k.specifier != PGP_S2KS_ITERATED_AND_SALTED) { + RNP_LOG("Bad s2k specifier: %d", (int) prot.s2k.specifier); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + const format_info *format = + find_format(prot.symm_alg, prot.cipher_mode, prot.s2k.hash_alg); + if (!format) { + RNP_LOG("Unknown protection format."); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + // randomize IV and salt + ctx.rng.get(prot.iv, sizeof(prot.iv)); + ctx.rng.get(prot.s2k.salt, sizeof(prot.s2k.salt)); + + // write seckey + gnupg_sexp_t raw_s_exp; + auto psub_s_exp = raw_s_exp.add_sub(); + psub_s_exp->add_seckey(seckey); + + // calculate hash + char protected_at[G10_PROTECTED_AT_SIZE + 1]; + uint8_t checksum[G10_SHA1_HASH_SIZE]; + // TODO: how critical is it if we have a skewed timestamp here due to y2k38 problem? + struct tm tm = {}; + rnp_gmtime(ctx.time(), tm); + strftime(protected_at, sizeof(protected_at), "%Y%m%dT%H%M%S", &tm); + if (!g10_calculated_hash(seckey, protected_at, checksum)) { + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + + psub_s_exp = raw_s_exp.add_sub(); + psub_s_exp->add("hash"); + psub_s_exp->add("sha1"); + psub_s_exp->add(checksum, sizeof(checksum)); + + /* write raw secret key to the memory */ + rnp::secure_vector<uint8_t> rawkey = raw_s_exp.write_padded(format->cipher_block_size); + + /* derive encrypting key */ + unsigned keysize = pgp_key_size(prot.symm_alg); + if (!keysize) { + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + rnp::secure_array<uint8_t, PGP_MAX_KEY_SIZE> derived_key; + if (pgp_s2k_iterated(format->hash_alg, + derived_key.data(), + keysize, + password.c_str(), + prot.s2k.salt, + prot.s2k.iterations)) { + RNP_LOG("s2k key derivation failed"); + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + + /* encrypt raw key */ + std::unique_ptr<Cipher> enc( + Cipher::encryption(format->cipher, format->cipher_mode, 0, true)); + if (!enc || !enc->set_key(derived_key.data(), keysize) || + !enc->set_iv(prot.iv, format->iv_size)) { + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + + size_t output_written, input_consumed; + std::vector<uint8_t> enckey(rawkey.size()); + + if (!enc->finish(enckey.data(), + enckey.size(), + &output_written, + rawkey.data(), + rawkey.size(), + &input_consumed)) { + RNP_LOG("Encryption failed"); + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + + /* build s_exp with encrypted key */ + psub_s_exp = add_sub(); + psub_s_exp->add("protected"); + psub_s_exp->add(format->g10_type); + /* protection params: s2k, iv */ + auto psub_sub_s_exp = psub_s_exp->add_sub(); + /* s2k params: hash, salt, iterations */ + auto psub_sub_sub_s_exp = psub_sub_s_exp->add_sub(); + psub_sub_sub_s_exp->add("sha1"); + psub_sub_sub_s_exp->add(prot.s2k.salt, PGP_SALT_SIZE); + psub_sub_sub_s_exp->add(prot.s2k.iterations); + psub_sub_s_exp->add(prot.iv, format->iv_size); + /* encrypted key data itself */ + psub_s_exp->add(enckey.data(), enckey.size()); + /* protected-at */ + psub_s_exp = add_sub(); + psub_s_exp->add("protected-at"); + psub_s_exp->add((uint8_t *) protected_at, G10_PROTECTED_AT_SIZE); +} + +bool +g10_write_seckey(pgp_dest_t * dst, + pgp_key_pkt_t * seckey, + const char * password, + rnp::SecurityContext &ctx) +{ + bool is_protected = true; + + switch (seckey->sec_protection.s2k.usage) { + case PGP_S2KU_NONE: + is_protected = false; + break; + case PGP_S2KU_ENCRYPTED_AND_HASHED: + is_protected = true; + // TODO: these are forced for now, until openpgp-native is implemented + seckey->sec_protection.symm_alg = PGP_SA_AES_128; + seckey->sec_protection.cipher_mode = PGP_CIPHER_MODE_CBC; + seckey->sec_protection.s2k.hash_alg = PGP_HASH_SHA1; + break; + default: + RNP_LOG("unsupported s2k usage"); + return false; + } + + try { + gnupg_sexp_t s_exp; + s_exp.add(is_protected ? "protected-private-key" : "private-key"); + auto pkey = s_exp.add_sub(); + pkey->add_pubkey(*seckey); + + if (is_protected) { + pkey->add_protected_seckey(*seckey, password, ctx); + } else { + pkey->add_seckey(*seckey); + } + return s_exp.write(*dst) && !dst->werr; + } catch (const std::exception &e) { + RNP_LOG("Failed to write g10 key: %s", e.what()); + return false; + } +} + +static bool +g10_calculated_hash(const pgp_key_pkt_t &key, const char *protected_at, uint8_t *checksum) +{ + try { + /* populate s_exp */ + gnupg_sexp_t s_exp; + s_exp.add_pubkey(key); + s_exp.add_seckey(key); + auto s_sub_exp = s_exp.add_sub(); + s_sub_exp->add("protected-at"); + s_sub_exp->add((uint8_t *) protected_at, G10_PROTECTED_AT_SIZE); + /* write it to memdst */ + rnp::MemoryDest memdst; + memdst.set_secure(true); + if (!s_exp.write(memdst.dst())) { + RNP_LOG("Failed to write s_exp"); + return false; + } + auto hash = rnp::Hash::create(PGP_HASH_SHA1); + hash->add(memdst.memory(), memdst.writeb()); + hash->finish(checksum); + return true; + } catch (const std::exception &e) { + RNP_LOG("Failed to build s_exp: %s", e.what()); + return false; + } +} + +bool +rnp_key_store_gnupg_sexp_to_dst(pgp_key_t *key, pgp_dest_t *dest) +{ + if (key->format != PGP_KEY_STORE_G10) { + RNP_LOG("incorrect format: %d", key->format); + return false; + } + pgp_rawpacket_t &packet = key->rawpkt(); + dst_write(dest, packet.raw.data(), packet.raw.size()); + return dest->werr == RNP_SUCCESS; +} diff --git a/comm/third_party/rnp/src/librekey/key_store_g10.h b/comm/third_party/rnp/src/librekey/key_store_g10.h new file mode 100644 index 0000000000..f770628c7c --- /dev/null +++ b/comm/third_party/rnp/src/librekey/key_store_g10.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2017, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE RIBOSE, INC. AND CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RNP_KEY_STORE_G10_H +#define RNP_KEY_STORE_G10_H + +#include <rekey/rnp_key_store.h> + +bool rnp_key_store_g10_from_src(rnp_key_store_t *, pgp_source_t *, const pgp_key_provider_t *); +bool rnp_key_store_gnupg_sexp_to_dst(pgp_key_t *, pgp_dest_t *); +bool g10_write_seckey(pgp_dest_t * dst, + pgp_key_pkt_t * seckey, + const char * password, + rnp::SecurityContext &ctx); +pgp_key_pkt_t *g10_decrypt_seckey(const pgp_rawpacket_t &raw, + const pgp_key_pkt_t & pubkey, + const char * password); + +#endif // RNP_KEY_STORE_G10_H diff --git a/comm/third_party/rnp/src/librekey/key_store_kbx.cpp b/comm/third_party/rnp/src/librekey/key_store_kbx.cpp new file mode 100644 index 0000000000..bc504f6612 --- /dev/null +++ b/comm/third_party/rnp/src/librekey/key_store_kbx.cpp @@ -0,0 +1,706 @@ +/* + * Copyright (c) 2017-2022, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/types.h> +#ifdef HAVE_SYS_PARAM_H +#include <sys/param.h> +#else +#include "uniwin.h" +#endif +#include <string.h> +#include <stdint.h> +#include <time.h> +#include <inttypes.h> +#include <cassert> + +#include "key_store_pgp.h" +#include "key_store_kbx.h" +#include "pgp-key.h" +#include <librepgp/stream-sig.h> + +/* same limit with GnuPG 2.1 */ +#define BLOB_SIZE_LIMIT (5 * 1024 * 1024) +/* limit the number of keys/sigs/uids in the blob */ +#define BLOB_OBJ_LIMIT 0x8000 + +#define BLOB_HEADER_SIZE 0x5 +#define BLOB_FIRST_SIZE 0x20 +#define BLOB_KEY_SIZE 0x1C +#define BLOB_UID_SIZE 0x0C +#define BLOB_SIG_SIZE 0x04 +#define BLOB_VALIDITY_SIZE 0x10 + +uint8_t +kbx_blob_t::ru8(size_t idx) +{ + return image_[idx]; +} + +uint16_t +kbx_blob_t::ru16(size_t idx) +{ + return read_uint16(image_.data() + idx); +} + +uint32_t +kbx_blob_t::ru32(size_t idx) +{ + return read_uint32(image_.data() + idx); +} + +kbx_blob_t::kbx_blob_t(std::vector<uint8_t> &data) +{ + if (data.size() < BLOB_HEADER_SIZE) { + RNP_LOG("Too small KBX blob."); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + uint32_t len = read_uint32(data.data()); + if (len > BLOB_SIZE_LIMIT) { + RNP_LOG("Too large KBX blob."); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + if (len != data.size()) { + RNP_LOG("KBX blob size mismatch."); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + image_ = data; + type_ = (kbx_blob_type_t) ru8(4); +} + +bool +kbx_header_blob_t::parse() +{ + if (length() != BLOB_FIRST_SIZE) { + RNP_LOG("The first blob has wrong length: %" PRIu32 " but expected %d", + length(), + (int) BLOB_FIRST_SIZE); + return false; + } + + size_t idx = BLOB_HEADER_SIZE; + version_ = ru8(idx++); + if (version_ != 1) { + RNP_LOG("Wrong version, expect 1 but has %" PRIu8, version_); + return false; + } + + flags_ = ru16(idx); + idx += 2; + + // blob should contains a magic KBXf + if (memcmp(image_.data() + idx, "KBXf", 4)) { + RNP_LOG("The first blob hasn't got a KBXf magic string"); + return false; + } + idx += 4; + // RFU + idx += 4; + // File creation time + file_created_at_ = ru32(idx); + idx += 4; + // Duplicated? + file_created_at_ = ru32(idx); + // RFU +4 bytes + // RFU +4 bytes + return true; +} + +bool +kbx_pgp_blob_t::parse() +{ + if (image_.size() < 15 + BLOB_HEADER_SIZE) { + RNP_LOG("Too few data in the blob."); + return false; + } + + size_t idx = BLOB_HEADER_SIZE; + /* version */ + version_ = ru8(idx++); + if (version_ != 1) { + RNP_LOG("Wrong version: %" PRIu8, version_); + return false; + } + /* flags */ + flags_ = ru16(idx); + idx += 2; + /* keyblock offset */ + keyblock_offset_ = ru32(idx); + idx += 4; + /* keyblock length */ + keyblock_length_ = ru32(idx); + idx += 4; + + if ((keyblock_offset_ > image_.size()) || + (keyblock_offset_ > (UINT32_MAX - keyblock_length_)) || + (image_.size() < (keyblock_offset_ + keyblock_length_))) { + RNP_LOG("Wrong keyblock offset/length, blob size: %zu" + ", keyblock offset: %" PRIu32 ", length: %" PRIu32, + image_.size(), + keyblock_offset_, + keyblock_length_); + return false; + } + /* number of key blocks */ + size_t nkeys = ru16(idx); + idx += 2; + if (nkeys < 1) { + RNP_LOG("PGP blob should contains at least 1 key"); + return false; + } + if (nkeys > BLOB_OBJ_LIMIT) { + RNP_LOG("Too many keys in the PGP blob"); + return false; + } + + /* Size of the single key record */ + size_t keys_len = ru16(idx); + idx += 2; + if (keys_len < BLOB_KEY_SIZE) { + RNP_LOG( + "PGP blob needs %d bytes, but contains: %zu bytes", (int) BLOB_KEY_SIZE, keys_len); + return false; + } + + for (size_t i = 0; i < nkeys; i++) { + if (image_.size() - idx < keys_len) { + RNP_LOG("Too few bytes left for key blob"); + return false; + } + + kbx_pgp_key_t nkey = {}; + /* copy fingerprint */ + memcpy(nkey.fp, &image_[idx], 20); + idx += 20; + /* keyid offset */ + nkey.keyid_offset = ru32(idx); + idx += 4; + /* flags */ + nkey.flags = ru16(idx); + idx += 2; + /* RFU */ + idx += 2; + /* skip padding bytes if it existed */ + idx += keys_len - BLOB_KEY_SIZE; + keys_.push_back(std::move(nkey)); + } + + if (image_.size() - idx < 2) { + RNP_LOG("No data for sn_size"); + return false; + } + size_t sn_size = ru16(idx); + idx += 2; + + if (image_.size() - idx < sn_size) { + RNP_LOG("SN is %zu, while bytes left are %zu", sn_size, image_.size() - idx); + return false; + } + + if (sn_size) { + sn_ = {image_.begin() + idx, image_.begin() + idx + sn_size}; + idx += sn_size; + } + + if (image_.size() - idx < 4) { + RNP_LOG("Too few data for uids"); + return false; + } + size_t nuids = ru16(idx); + if (nuids > BLOB_OBJ_LIMIT) { + RNP_LOG("Too many uids in the PGP blob"); + return false; + } + + size_t uids_len = ru16(idx + 2); + idx += 4; + + if (uids_len < BLOB_UID_SIZE) { + RNP_LOG("Too few bytes for uid struct: %zu", uids_len); + return false; + } + + for (size_t i = 0; i < nuids; i++) { + if (image_.size() - idx < uids_len) { + RNP_LOG("Too few bytes to read uid struct."); + return false; + } + kbx_pgp_uid_t nuid = {}; + /* offset */ + nuid.offset = ru32(idx); + idx += 4; + /* length */ + nuid.length = ru32(idx); + idx += 4; + /* flags */ + nuid.flags = ru16(idx); + idx += 2; + /* validity */ + nuid.validity = ru8(idx); + idx++; + /* RFU */ + idx++; + // skip padding bytes if it existed + idx += uids_len - BLOB_UID_SIZE; + + uids_.push_back(std::move(nuid)); + } + + if (image_.size() - idx < 4) { + RNP_LOG("No data left for sigs"); + return false; + } + + size_t nsigs = ru16(idx); + if (nsigs > BLOB_OBJ_LIMIT) { + RNP_LOG("Too many sigs in the PGP blob"); + return false; + } + + size_t sigs_len = ru16(idx + 2); + idx += 4; + + if (sigs_len < BLOB_SIG_SIZE) { + RNP_LOG("Too small SIGN structure: %zu", uids_len); + return false; + } + + for (size_t i = 0; i < nsigs; i++) { + if (image_.size() - idx < sigs_len) { + RNP_LOG("Too few data for sig"); + return false; + } + + kbx_pgp_sig_t nsig = {}; + nsig.expired = ru32(idx); + idx += 4; + + // skip padding bytes if it existed + idx += (sigs_len - BLOB_SIG_SIZE); + + sigs_.push_back(nsig); + } + + if (image_.size() - idx < BLOB_VALIDITY_SIZE) { + RNP_LOG("Too few data for trust/validities"); + return false; + } + + ownertrust_ = ru8(idx); + idx++; + all_validity_ = ru8(idx); + idx++; + // RFU + idx += 2; + recheck_after_ = ru32(idx); + idx += 4; + latest_timestamp_ = ru32(idx); + idx += 4; + blob_created_at_ = ru32(idx); + // do not forget to idx += 4 on further expansion + + // here starts keyblock, UID and reserved space for future usage + + // Maybe we should add checksum verify but GnuPG never checked it + // Checksum is last 20 bytes of blob and it is SHA-1, if it invalid MD5 and starts from 4 + // zero it is MD5. + + return true; +} + +static std::unique_ptr<kbx_blob_t> +rnp_key_store_kbx_parse_blob(const uint8_t *image, size_t image_len) +{ + std::unique_ptr<kbx_blob_t> blob; + // a blob shouldn't be less of length + type + if (image_len < BLOB_HEADER_SIZE) { + RNP_LOG("Blob size is %zu but it shouldn't be less of header", image_len); + return blob; + } + + try { + std::vector<uint8_t> data(image, image + image_len); + kbx_blob_type_t type = (kbx_blob_type_t) image[4]; + + switch (type) { + case KBX_EMPTY_BLOB: + blob = std::unique_ptr<kbx_blob_t>(new kbx_blob_t(data)); + break; + case KBX_HEADER_BLOB: + blob = std::unique_ptr<kbx_blob_t>(new kbx_header_blob_t(data)); + break; + case KBX_PGP_BLOB: + blob = std::unique_ptr<kbx_blob_t>(new kbx_pgp_blob_t(data)); + break; + case KBX_X509_BLOB: + // current we doesn't parse X509 blob, so, keep it as is + blob = std::unique_ptr<kbx_blob_t>(new kbx_blob_t(data)); + break; + // unsupported blob type + default: + RNP_LOG("Unsupported blob type: %d", (int) type); + return blob; + } + + if (!blob->parse()) { + return NULL; + } + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return NULL; + } + return blob; +} + +bool +rnp_key_store_kbx_from_src(rnp_key_store_t * key_store, + pgp_source_t * src, + const pgp_key_provider_t *key_provider) +{ + try { + rnp::MemorySource mem(*src); + size_t has_bytes = mem.size(); + uint8_t * buf = (uint8_t *) mem.memory(); + + while (has_bytes > 4) { + size_t blob_length = read_uint32(buf); + if (blob_length > BLOB_SIZE_LIMIT) { + RNP_LOG("Blob size is %zu bytes but limit is %d bytes", + blob_length, + (int) BLOB_SIZE_LIMIT); + return false; + } + if (blob_length < BLOB_HEADER_SIZE) { + RNP_LOG("Too small blob header size"); + return false; + } + if (has_bytes < blob_length) { + RNP_LOG("Blob have size %zu bytes but file contains only %zu bytes", + blob_length, + has_bytes); + return false; + } + auto blob = rnp_key_store_kbx_parse_blob(buf, blob_length); + if (!blob.get()) { + RNP_LOG("Failed to parse blob"); + return false; + } + kbx_blob_t *pblob = blob.get(); + key_store->blobs.push_back(std::move(blob)); + + if (pblob->type() == KBX_PGP_BLOB) { + // parse keyblock if it existed + kbx_pgp_blob_t &pgp_blob = dynamic_cast<kbx_pgp_blob_t &>(*pblob); + if (!pgp_blob.keyblock_length()) { + RNP_LOG("PGP blob have zero size"); + return false; + } + + rnp::MemorySource blsrc(pgp_blob.image().data() + pgp_blob.keyblock_offset(), + pgp_blob.keyblock_length(), + false); + if (rnp_key_store_pgp_read_from_src(key_store, &blsrc.src())) { + return false; + } + } + + has_bytes -= blob_length; + buf += blob_length; + } + return true; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } +} + +static bool +pbuf(pgp_dest_t *dst, const void *buf, size_t len) +{ + dst_write(dst, buf, len); + return dst->werr == RNP_SUCCESS; +} + +static bool +pu8(pgp_dest_t *dst, uint8_t p) +{ + return pbuf(dst, &p, 1); +} + +static bool +pu16(pgp_dest_t *dst, uint16_t f) +{ + uint8_t p[2]; + p[0] = (uint8_t)(f >> 8); + p[1] = (uint8_t) f; + return pbuf(dst, p, 2); +} + +static bool +pu32(pgp_dest_t *dst, uint32_t f) +{ + uint8_t p[4]; + STORE32BE(p, f); + return pbuf(dst, p, 4); +} + +static bool +rnp_key_store_kbx_write_header(rnp_key_store_t *key_store, pgp_dest_t *dst) +{ + uint16_t flags = 0; + uint32_t file_created_at = key_store->secctx.time(); + + if (!key_store->blobs.empty() && (key_store->blobs[0]->type() == KBX_HEADER_BLOB)) { + kbx_header_blob_t &blob = dynamic_cast<kbx_header_blob_t &>(*key_store->blobs[0]); + file_created_at = blob.file_created_at(); + } + + return !(!pu32(dst, BLOB_FIRST_SIZE) || !pu8(dst, KBX_HEADER_BLOB) || + !pu8(dst, 1) // version + || !pu16(dst, flags) || !pbuf(dst, "KBXf", 4) || !pu32(dst, 0) // RFU + || !pu32(dst, 0) // RFU + || !pu32(dst, file_created_at) || !pu32(dst, key_store->secctx.time()) || + !pu32(dst, 0)); // RFU +} + +static bool +rnp_key_store_kbx_write_pgp(rnp_key_store_t *key_store, pgp_key_t *key, pgp_dest_t *dst) +{ + rnp::MemoryDest mem(NULL, BLOB_SIZE_LIMIT); + + if (!pu32(&mem.dst(), 0)) { // length, we don't know length of blob yet, so it's 0 + return false; + } + + if (!pu8(&mem.dst(), KBX_PGP_BLOB) || !pu8(&mem.dst(), 1)) { // type, version + return false; + } + + if (!pu16(&mem.dst(), 0)) { // flags, not used by GnuPG + return false; + } + + if (!pu32(&mem.dst(), 0) || + !pu32(&mem.dst(), 0)) { // offset and length of keyblock, update later + return false; + } + + if (!pu16(&mem.dst(), 1 + key->subkey_count())) { // number of keys in keyblock + return false; + } + if (!pu16(&mem.dst(), 28)) { // size of key info structure) + return false; + } + + if (!pbuf(&mem.dst(), key->fp().fingerprint, PGP_FINGERPRINT_SIZE) || + !pu32(&mem.dst(), mem.writeb() - 8) || // offset to keyid (part of fpr for V4) + !pu16(&mem.dst(), 0) || // flags, not used by GnuPG + !pu16(&mem.dst(), 0)) { // RFU + return false; + } + + // same as above, for each subkey + std::vector<uint32_t> subkey_sig_expirations; + for (auto &sfp : key->subkey_fps()) { + pgp_key_t *subkey = rnp_key_store_get_key_by_fpr(key_store, sfp); + if (!subkey || !pbuf(&mem.dst(), subkey->fp().fingerprint, PGP_FINGERPRINT_SIZE) || + !pu32(&mem.dst(), mem.writeb() - 8) || // offset to keyid (part of fpr for V4) + !pu16(&mem.dst(), 0) || // flags, not used by GnuPG + !pu16(&mem.dst(), 0)) { // RFU + return false; + } + // load signature expirations while we're at it + for (size_t i = 0; i < subkey->sig_count(); i++) { + uint32_t expiration = subkey->get_sig(i).sig.key_expiration(); + subkey_sig_expirations.push_back(expiration); + } + } + + if (!pu16(&mem.dst(), 0)) { // Zero size of serial number + return false; + } + + // skip serial number + if (!pu16(&mem.dst(), key->uid_count()) || !pu16(&mem.dst(), 12)) { + return false; + } + + size_t uid_start = mem.writeb(); + for (size_t i = 0; i < key->uid_count(); i++) { + if (!pu32(&mem.dst(), 0) || + !pu32(&mem.dst(), 0)) { // UID offset and length, update when blob has done + return false; + } + + if (!pu16(&mem.dst(), 0)) { // flags, (not yet used) + return false; + } + + if (!pu8(&mem.dst(), 0) || !pu8(&mem.dst(), 0)) { // Validity & RFU + return false; + } + } + + if (!pu16(&mem.dst(), key->sig_count() + subkey_sig_expirations.size()) || + !pu16(&mem.dst(), 4)) { + return false; + } + + for (size_t i = 0; i < key->sig_count(); i++) { + if (!pu32(&mem.dst(), key->get_sig(i).sig.key_expiration())) { + return false; + } + } + for (auto &expiration : subkey_sig_expirations) { + if (!pu32(&mem.dst(), expiration)) { + return false; + } + } + + if (!pu8(&mem.dst(), 0) || + !pu8(&mem.dst(), 0)) { // Assigned ownertrust & All_Validity (not yet used) + return false; + } + + if (!pu16(&mem.dst(), 0) || !pu32(&mem.dst(), 0)) { // RFU & Recheck_after + return false; + } + + if (!pu32(&mem.dst(), key_store->secctx.time()) || + !pu32(&mem.dst(), key_store->secctx.time())) { // Latest timestamp && created + return false; + } + + if (!pu32(&mem.dst(), 0)) { // Size of reserved space + return false; + } + + // wrtite UID, we might redesign PGP write and use this information from keyblob + for (size_t i = 0; i < key->uid_count(); i++) { + const pgp_userid_t &uid = key->get_uid(i); + uint8_t * p = (uint8_t *) mem.memory() + uid_start + (12 * i); + /* store absolute uid offset in the output stream */ + uint32_t pt = mem.writeb() + dst->writeb; + STORE32BE(p, pt); + /* and uid length */ + pt = uid.str.size(); + STORE32BE(p + 4, pt); + /* uid data itself */ + if (!pbuf(&mem.dst(), uid.str.c_str(), pt)) { + return false; + } + } + + /* write keyblock and fix the offset/length */ + size_t key_start = mem.writeb(); + uint32_t pt = key_start; + uint8_t *p = (uint8_t *) mem.memory() + 8; + STORE32BE(p, pt); + + key->write(mem.dst()); + if (mem.werr()) { + return false; + } + + for (auto &sfp : key->subkey_fps()) { + const pgp_key_t *subkey = rnp_key_store_get_key_by_fpr(key_store, sfp); + if (!subkey) { + return false; + } + subkey->write(mem.dst()); + if (mem.werr()) { + return false; + } + } + + /* key blob length */ + pt = mem.writeb() - key_start; + p = (uint8_t *) mem.memory() + 12; + STORE32BE(p, pt); + + // fix the length of blob + pt = mem.writeb() + 20; + p = (uint8_t *) mem.memory(); + STORE32BE(p, pt); + + // checksum + auto hash = rnp::Hash::create(PGP_HASH_SHA1); + hash->add(mem.memory(), mem.writeb()); + uint8_t checksum[PGP_SHA1_HASH_SIZE]; + assert(hash->size() == sizeof(checksum)); + hash->finish(checksum); + + if (!(pbuf(&mem.dst(), checksum, PGP_SHA1_HASH_SIZE))) { + return false; + } + + /* finally write to the output */ + dst_write(dst, mem.memory(), mem.writeb()); + return !dst->werr; +} + +static bool +rnp_key_store_kbx_write_x509(rnp_key_store_t *key_store, pgp_dest_t *dst) +{ + for (auto &blob : key_store->blobs) { + if (blob->type() != KBX_X509_BLOB) { + continue; + } + if (!pbuf(dst, blob->image().data(), blob->length())) { + return false; + } + } + return true; +} + +bool +rnp_key_store_kbx_to_dst(rnp_key_store_t *key_store, pgp_dest_t *dst) +{ + try { + if (!rnp_key_store_kbx_write_header(key_store, dst)) { + RNP_LOG("Can't write KBX header"); + return false; + } + + for (auto &key : key_store->keys) { + if (!key.is_primary()) { + continue; + } + if (!rnp_key_store_kbx_write_pgp(key_store, &key, dst)) { + RNP_LOG("Can't write PGP blobs for key %p", &key); + return false; + } + } + + if (!rnp_key_store_kbx_write_x509(key_store, dst)) { + RNP_LOG("Can't write X509 blobs"); + return false; + } + return true; + } catch (const std::exception &e) { + RNP_LOG("Failed to write KBX store: %s", e.what()); + return false; + } +} diff --git a/comm/third_party/rnp/src/librekey/key_store_kbx.h b/comm/third_party/rnp/src/librekey/key_store_kbx.h new file mode 100644 index 0000000000..68d725d4cf --- /dev/null +++ b/comm/third_party/rnp/src/librekey/key_store_kbx.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2017, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RNP_KEY_STORE_KBX_H +#define RNP_KEY_STORE_KBX_H + +#include <rekey/rnp_key_store.h> +#include "sec_profile.hpp" + +bool rnp_key_store_kbx_from_src(rnp_key_store_t *, pgp_source_t *, const pgp_key_provider_t *); +bool rnp_key_store_kbx_to_dst(rnp_key_store_t *, pgp_dest_t *); + +#endif // RNP_KEY_STORE_KBX_H diff --git a/comm/third_party/rnp/src/librekey/key_store_pgp.cpp b/comm/third_party/rnp/src/librekey/key_store_pgp.cpp new file mode 100644 index 0000000000..6edc099b2e --- /dev/null +++ b/comm/third_party/rnp/src/librekey/key_store_pgp.cpp @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2017-2020 [Ribose Inc](https://www.ribose.com). + * Copyright (c) 2009 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is originally derived from software contributed to + * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and + * carried further by Ribose Inc (https://www.ribose.com). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/* + * Copyright (c) 2005-2008 Nominet UK (www.nic.uk) + * All rights reserved. + * Contributors: Ben Laurie, Rachel Willmer. The Contributors have asserted + * their moral rights under the UK Copyright Design and Patents Act 1988 to + * be recorded as the authors of this copyright work. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. + * + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#if defined(__NetBSD__) +__COPYRIGHT("@(#) Copyright (c) 2009 The NetBSD Foundation, Inc. All rights reserved."); +__RCSID("$NetBSD: keyring.c,v 1.50 2011/06/25 00:37:44 agc Exp $"); +#endif + +#include <stdlib.h> +#include <string.h> + +#include <librepgp/stream-common.h> +#include <librepgp/stream-sig.h> +#include <librepgp/stream-packet.h> +#include <librepgp/stream-key.h> +#include "crypto/mem.h" + +#include "types.h" +#include "key_store_pgp.h" +#include "pgp-key.h" + +bool +rnp_key_store_add_transferable_subkey(rnp_key_store_t * keyring, + pgp_transferable_subkey_t *tskey, + pgp_key_t * pkey) +{ + try { + /* create subkey */ + pgp_key_t skey(*tskey, pkey); + /* add it to the storage */ + return rnp_key_store_add_key(keyring, &skey); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + RNP_LOG_KEY_PKT("failed to create subkey %s", tskey->subkey); + RNP_LOG_KEY("primary key is %s", pkey); + return false; + } +} + +bool +rnp_key_store_add_transferable_key(rnp_key_store_t *keyring, pgp_transferable_key_t *tkey) +{ + pgp_key_t *addkey = NULL; + + /* create key from transferable key */ + try { + pgp_key_t key(*tkey); + /* temporary disable key validation */ + keyring->disable_validation = true; + /* add key to the storage before subkeys */ + addkey = rnp_key_store_add_key(keyring, &key); + } catch (const std::exception &e) { + keyring->disable_validation = false; + RNP_LOG_KEY_PKT("failed to add key %s", tkey->key); + return false; + } + + if (!addkey) { + keyring->disable_validation = false; + RNP_LOG("Failed to add key to key store."); + return false; + } + + /* add subkeys */ + for (auto &subkey : tkey->subkeys) { + if (!rnp_key_store_add_transferable_subkey(keyring, &subkey, addkey)) { + RNP_LOG("Failed to add subkey to key store."); + keyring->disable_validation = false; + goto error; + } + } + + /* now validate/refresh the whole key with subkeys */ + keyring->disable_validation = false; + addkey->revalidate(*keyring); + return true; +error: + /* during key addition all fields are copied so will be cleaned below */ + rnp_key_store_remove_key(keyring, addkey, false); + return false; +} + +rnp_result_t +rnp_key_store_pgp_read_key_from_src(rnp_key_store_t &keyring, + pgp_source_t & src, + bool skiperrors) +{ + pgp_transferable_key_t key; + rnp_result_t ret = process_pgp_key_auto(src, key, true, skiperrors); + + if (ret && (!skiperrors || (ret != RNP_ERROR_BAD_FORMAT))) { + return ret; + } + + /* check whether we have primary key */ + if (key.key.tag != PGP_PKT_RESERVED) { + return rnp_key_store_add_transferable_key(&keyring, &key) ? RNP_SUCCESS : + RNP_ERROR_BAD_STATE; + } + + /* we just skipped some unexpected packets and read nothing */ + if (key.subkeys.empty()) { + return RNP_SUCCESS; + } + + return rnp_key_store_add_transferable_subkey(&keyring, &key.subkeys.front(), NULL) ? + RNP_SUCCESS : + RNP_ERROR_BAD_STATE; +} + +rnp_result_t +rnp_key_store_pgp_read_from_src(rnp_key_store_t *keyring, pgp_source_t *src, bool skiperrors) +{ + /* check whether we have transferable subkey in source */ + if (is_subkey_pkt(stream_pkt_type(*src))) { + pgp_transferable_subkey_t tskey; + rnp_result_t ret = process_pgp_subkey(*src, tskey, skiperrors); + if (ret) { + return ret; + } + return rnp_key_store_add_transferable_subkey(keyring, &tskey, NULL) ? + RNP_SUCCESS : + RNP_ERROR_BAD_STATE; + } + + /* process armored or raw transferable key packets sequence(s) */ + try { + pgp_key_sequence_t keys; + rnp_result_t ret = process_pgp_keys(*src, keys, skiperrors); + if (ret) { + return ret; + } + for (auto &key : keys.keys) { + if (!rnp_key_store_add_transferable_key(keyring, &key)) { + return RNP_ERROR_BAD_STATE; + } + } + return RNP_SUCCESS; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_BAD_PARAMETERS; + } +} + +std::vector<uint8_t> +rnp_key_to_vec(const pgp_key_t &key) +{ + rnp::MemoryDest dst; + key.write(dst.dst()); + return dst.to_vector(); +} + +static bool +do_write(rnp_key_store_t *key_store, pgp_dest_t *dst, bool secret) +{ + for (auto &key : key_store->keys) { + if (key.is_secret() != secret) { + continue; + } + // skip subkeys, they are written below (orphans are ignored) + if (!key.is_primary()) { + continue; + } + + if (key.format != PGP_KEY_STORE_GPG) { + RNP_LOG("incorrect format (conversions not supported): %d", key.format); + return false; + } + key.write(*dst); + if (dst->werr) { + return false; + } + for (auto &sfp : key.subkey_fps()) { + pgp_key_t *subkey = rnp_key_store_get_key_by_fpr(key_store, sfp); + if (!subkey) { + RNP_LOG("Missing subkey"); + continue; + } + subkey->write(*dst); + if (dst->werr) { + return false; + } + } + } + return true; +} + +bool +rnp_key_store_pgp_write_to_dst(rnp_key_store_t *key_store, pgp_dest_t *dst) +{ + // two separate passes (public keys, then secret keys) + return do_write(key_store, dst, false) && do_write(key_store, dst, true); +} diff --git a/comm/third_party/rnp/src/librekey/key_store_pgp.h b/comm/third_party/rnp/src/librekey/key_store_pgp.h new file mode 100644 index 0000000000..d3dcd06250 --- /dev/null +++ b/comm/third_party/rnp/src/librekey/key_store_pgp.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2017-2020, [Ribose Inc](https://www.ribose.com). + * Copyright (c) 2009 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is originally derived from software contributed to + * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and + * carried further by Ribose Inc (https://www.ribose.com). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/* + * Copyright (c) 2005-2008 Nominet UK (www.nic.uk) + * All rights reserved. + * Contributors: Ben Laurie, Rachel Willmer. The Contributors have asserted + * their moral rights under the UK Copyright Design and Patents Act 1988 to + * be recorded as the authors of this copyright work. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. + * + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** \file + */ + +#ifndef KEY_STORE_PGP_H_ +#define KEY_STORE_PGP_H_ + +#include <rekey/rnp_key_store.h> +#include <librepgp/stream-common.h> +#include <librepgp/stream-key.h> + +/* Read the whole keyring from the src, processing all available keys or subkeys */ +rnp_result_t rnp_key_store_pgp_read_from_src(rnp_key_store_t *keyring, + pgp_source_t * src, + bool skiperrors = false); + +/* Read the first key or subkey from the src */ +rnp_result_t rnp_key_store_pgp_read_key_from_src(rnp_key_store_t &keyring, + pgp_source_t & src, + bool skiperrors = false); + +bool rnp_key_store_pgp_write_to_dst(rnp_key_store_t *key_store, pgp_dest_t *dst); + +bool rnp_key_store_add_transferable_subkey(rnp_key_store_t * keyring, + pgp_transferable_subkey_t *tskey, + pgp_key_t * pkey); + +bool rnp_key_store_add_transferable_key(rnp_key_store_t * keyring, + pgp_transferable_key_t *tkey); + +std::vector<uint8_t> rnp_key_to_vec(const pgp_key_t &key); + +#endif /* KEY_STORE_PGP_H_ */ diff --git a/comm/third_party/rnp/src/librekey/rnp_key_store.cpp b/comm/third_party/rnp/src/librekey/rnp_key_store.cpp new file mode 100644 index 0000000000..002a51e7cc --- /dev/null +++ b/comm/third_party/rnp/src/librekey/rnp_key_store.cpp @@ -0,0 +1,803 @@ +/* + * Copyright (c) 2017-2022 [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * This code is originally derived from software contributed to + * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and + * carried further by Ribose Inc (https://www.ribose.com). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include <sys/stat.h> +#include <sys/types.h> +#ifdef HAVE_SYS_PARAM_H +#include <sys/param.h> +#else +#include "uniwin.h" +#endif + +#include <assert.h> +#include <stdio.h> +#include <string.h> +#include <stdint.h> +#include <stdlib.h> +#include <dirent.h> +#include <errno.h> +#include <algorithm> +#include <stdexcept> + +#include <rekey/rnp_key_store.h> +#include <librepgp/stream-packet.h> + +#include "key_store_pgp.h" +#include "key_store_kbx.h" +#include "key_store_g10.h" +#include "kbx_blob.hpp" + +#include "pgp-key.h" +#include "fingerprint.h" +#include "crypto/hash.hpp" +#include "crypto/mem.h" +#include "file-utils.h" +#ifdef _WIN32 +#include "str-utils.h" +#endif + +bool +rnp_key_store_load_from_path(rnp_key_store_t * key_store, + const pgp_key_provider_t *key_provider) +{ + pgp_source_t src = {}; + + if (key_store->format == PGP_KEY_STORE_G10) { + auto dir = rnp_opendir(key_store->path.c_str()); + if (!dir) { + RNP_LOG( + "Can't open G10 directory %s: %s", key_store->path.c_str(), strerror(errno)); + return false; + } + + std::string dirname; + while (!((dirname = rnp_readdir_name(dir)).empty())) { + std::string path = rnp::path::append(key_store->path, dirname); + + if (init_file_src(&src, path.c_str())) { + RNP_LOG("failed to read file %s", path.c_str()); + continue; + } + // G10 may fail to read one file, so ignore it! + if (!rnp_key_store_g10_from_src(key_store, &src, key_provider)) { + RNP_LOG("Can't parse file: %s", path.c_str()); // TODO: %S ? + } + src_close(&src); + } + rnp_closedir(dir); + return true; + } + + /* init file source and load from it */ + if (init_file_src(&src, key_store->path.c_str())) { + RNP_LOG("failed to read file %s", key_store->path.c_str()); + return false; + } + + bool rc = rnp_key_store_load_from_src(key_store, &src, key_provider); + src_close(&src); + return rc; +} + +bool +rnp_key_store_load_from_src(rnp_key_store_t * key_store, + pgp_source_t * src, + const pgp_key_provider_t *key_provider) +{ + switch (key_store->format) { + case PGP_KEY_STORE_GPG: + return rnp_key_store_pgp_read_from_src(key_store, src) == RNP_SUCCESS; + case PGP_KEY_STORE_KBX: + return rnp_key_store_kbx_from_src(key_store, src, key_provider); + case PGP_KEY_STORE_G10: + return rnp_key_store_g10_from_src(key_store, src, key_provider); + default: + RNP_LOG("Unsupported load from memory for key-store format: %d", key_store->format); + } + + return false; +} + +bool +rnp_key_store_write_to_path(rnp_key_store_t *key_store) +{ + bool rc; + pgp_dest_t keydst = {}; + + /* write g10 key store to the directory */ + if (key_store->format == PGP_KEY_STORE_G10) { + char path[MAXPATHLEN]; + + struct stat path_stat; + if (rnp_stat(key_store->path.c_str(), &path_stat) != -1) { + if (!S_ISDIR(path_stat.st_mode)) { + RNP_LOG("G10 keystore should be a directory: %s", key_store->path.c_str()); + return false; + } + } else { + if (errno != ENOENT) { + RNP_LOG("stat(%s): %s", key_store->path.c_str(), strerror(errno)); + return false; + } + if (RNP_MKDIR(key_store->path.c_str(), S_IRWXU) != 0) { + RNP_LOG("mkdir(%s, S_IRWXU): %s", key_store->path.c_str(), strerror(errno)); + return false; + } + } + + for (auto &key : key_store->keys) { + char grip[PGP_FINGERPRINT_HEX_SIZE] = {0}; + rnp::hex_encode(key.grip().data(), key.grip().size(), grip, sizeof(grip)); + snprintf(path, sizeof(path), "%s/%s.key", key_store->path.c_str(), grip); + + if (init_tmpfile_dest(&keydst, path, true)) { + RNP_LOG("failed to create file"); + return false; + } + + if (!rnp_key_store_gnupg_sexp_to_dst(&key, &keydst)) { + RNP_LOG("failed to write key to file"); + dst_close(&keydst, true); + return false; + } + + rc = dst_finish(&keydst) == RNP_SUCCESS; + dst_close(&keydst, !rc); + + if (!rc) { + return false; + } + } + + return true; + } + + /* write kbx/gpg store to the single file */ + if (init_tmpfile_dest(&keydst, key_store->path.c_str(), true)) { + RNP_LOG("failed to create keystore file"); + return false; + } + + if (!rnp_key_store_write_to_dst(key_store, &keydst)) { + RNP_LOG("failed to write keys to file"); + dst_close(&keydst, true); + return false; + } + + rc = dst_finish(&keydst) == RNP_SUCCESS; + dst_close(&keydst, !rc); + return rc; +} + +bool +rnp_key_store_write_to_dst(rnp_key_store_t *key_store, pgp_dest_t *dst) +{ + switch (key_store->format) { + case PGP_KEY_STORE_GPG: + return rnp_key_store_pgp_write_to_dst(key_store, dst); + case PGP_KEY_STORE_KBX: + return rnp_key_store_kbx_to_dst(key_store, dst); + default: + RNP_LOG("Unsupported write to memory for key-store format: %d", key_store->format); + } + + return false; +} + +void +rnp_key_store_clear(rnp_key_store_t *keyring) +{ + keyring->keybyfp.clear(); + keyring->keys.clear(); + keyring->blobs.clear(); +} + +size_t +rnp_key_store_get_key_count(const rnp_key_store_t *keyring) +{ + return keyring->keys.size(); +} + +static bool +rnp_key_store_refresh_subkey_grips(rnp_key_store_t *keyring, pgp_key_t *key) +{ + if (key->is_subkey()) { + RNP_LOG("wrong argument"); + return false; + } + + for (auto &skey : keyring->keys) { + bool found = false; + + /* if we have primary_grip then we also added to subkey_grips */ + if (!skey.is_subkey() || skey.has_primary_fp()) { + continue; + } + + for (size_t i = 0; i < skey.sig_count(); i++) { + const pgp_subsig_t &subsig = skey.get_sig(i); + + if (subsig.sig.type() != PGP_SIG_SUBKEY) { + continue; + } + if (subsig.sig.has_keyfp() && (key->fp() == subsig.sig.keyfp())) { + found = true; + break; + } + if (subsig.sig.has_keyid() && (key->keyid() == subsig.sig.keyid())) { + found = true; + break; + } + } + + if (found) { + try { + key->link_subkey_fp(skey); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } + } + } + + return true; +} + +static pgp_key_t * +rnp_key_store_add_subkey(rnp_key_store_t *keyring, pgp_key_t *srckey, pgp_key_t *oldkey) +{ + pgp_key_t *primary = NULL; + if (oldkey) { + primary = rnp_key_store_get_primary_key(keyring, oldkey); + } + if (!primary) { + primary = rnp_key_store_get_primary_key(keyring, srckey); + } + + if (oldkey) { + /* check for the weird case when same subkey has different primary keys */ + if (srckey->has_primary_fp() && oldkey->has_primary_fp() && + (srckey->primary_fp() != oldkey->primary_fp())) { + RNP_LOG_KEY("Warning: different primary keys for subkey %s", srckey); + pgp_key_t *srcprim = rnp_key_store_get_key_by_fpr(keyring, srckey->primary_fp()); + if (srcprim && (srcprim != primary)) { + srcprim->remove_subkey_fp(srckey->fp()); + } + } + /* in case we already have key let's merge it in */ + if (!oldkey->merge(*srckey, primary)) { + RNP_LOG_KEY("failed to merge subkey %s", srckey); + RNP_LOG_KEY("primary key is %s", primary); + return NULL; + } + } else { + try { + keyring->keys.emplace_back(); + oldkey = &keyring->keys.back(); + keyring->keybyfp[srckey->fp()] = std::prev(keyring->keys.end()); + *oldkey = pgp_key_t(*srckey); + if (primary) { + primary->link_subkey_fp(*oldkey); + } + } catch (const std::exception &e) { + RNP_LOG_KEY("key %s copying failed", srckey); + RNP_LOG_KEY("primary key is %s", primary); + RNP_LOG("%s", e.what()); + if (oldkey) { + keyring->keys.pop_back(); + keyring->keybyfp.erase(srckey->fp()); + } + return NULL; + } + } + + /* validate all added keys if not disabled */ + if (!keyring->disable_validation && !oldkey->validated()) { + oldkey->validate_subkey(primary, keyring->secctx); + } + if (!oldkey->refresh_data(primary, keyring->secctx)) { + RNP_LOG_KEY("Failed to refresh subkey %s data", srckey); + RNP_LOG_KEY("primary key is %s", primary); + } + return oldkey; +} + +/* add a key to keyring */ +pgp_key_t * +rnp_key_store_add_key(rnp_key_store_t *keyring, pgp_key_t *srckey) +{ + assert(srckey->type() && srckey->version()); + pgp_key_t *added_key = rnp_key_store_get_key_by_fpr(keyring, srckey->fp()); + /* we cannot merge G10 keys - so just return it */ + if (added_key && (srckey->format == PGP_KEY_STORE_G10)) { + return added_key; + } + /* different processing for subkeys */ + if (srckey->is_subkey()) { + return rnp_key_store_add_subkey(keyring, srckey, added_key); + } + + if (added_key) { + if (!added_key->merge(*srckey)) { + RNP_LOG_KEY("failed to merge key %s", srckey); + return NULL; + } + } else { + try { + keyring->keys.emplace_back(); + added_key = &keyring->keys.back(); + keyring->keybyfp[srckey->fp()] = std::prev(keyring->keys.end()); + *added_key = pgp_key_t(*srckey); + /* primary key may be added after subkeys, so let's handle this case correctly */ + if (!rnp_key_store_refresh_subkey_grips(keyring, added_key)) { + RNP_LOG_KEY("failed to refresh subkey grips for %s", added_key); + } + } catch (const std::exception &e) { + RNP_LOG_KEY("key %s copying failed", srckey); + RNP_LOG("%s", e.what()); + if (added_key) { + keyring->keys.pop_back(); + keyring->keybyfp.erase(srckey->fp()); + } + return NULL; + } + } + + /* validate all added keys if not disabled or already validated */ + if (!keyring->disable_validation && !added_key->validated()) { + added_key->revalidate(*keyring); + } else if (!added_key->refresh_data(keyring->secctx)) { + RNP_LOG_KEY("Failed to refresh key %s data", srckey); + } + return added_key; +} + +pgp_key_t * +rnp_key_store_import_key(rnp_key_store_t * keyring, + pgp_key_t * srckey, + bool pubkey, + pgp_key_import_status_t *status) +{ + /* add public key */ + pgp_key_t *exkey = rnp_key_store_get_key_by_fpr(keyring, srckey->fp()); + size_t expackets = exkey ? exkey->rawpkt_count() : 0; + try { + pgp_key_t keycp(*srckey, pubkey); + keyring->disable_validation = true; + exkey = rnp_key_store_add_key(keyring, &keycp); + keyring->disable_validation = false; + if (!exkey) { + RNP_LOG("failed to add key to the keyring"); + return NULL; + } + bool changed = exkey->rawpkt_count() > expackets; + if (changed || !exkey->validated()) { + /* this will revalidated primary key with all subkeys */ + exkey->revalidate(*keyring); + } + if (status) { + *status = changed ? (expackets ? PGP_KEY_IMPORT_STATUS_UPDATED : + PGP_KEY_IMPORT_STATUS_NEW) : + PGP_KEY_IMPORT_STATUS_UNCHANGED; + } + return exkey; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + keyring->disable_validation = false; + return NULL; + } +} + +pgp_key_t * +rnp_key_store_get_signer_key(rnp_key_store_t *store, const pgp_signature_t *sig) +{ + pgp_key_search_t search; + // prefer using the issuer fingerprint when available + if (sig->has_keyfp()) { + search.by.fingerprint = sig->keyfp(); + search.type = PGP_KEY_SEARCH_FINGERPRINT; + return rnp_key_store_search(store, &search, NULL); + } + // fall back to key id search + if (sig->has_keyid()) { + search.by.keyid = sig->keyid(); + search.type = PGP_KEY_SEARCH_KEYID; + return rnp_key_store_search(store, &search, NULL); + } + return NULL; +} + +static pgp_sig_import_status_t +rnp_key_store_import_subkey_signature(rnp_key_store_t * keyring, + pgp_key_t * key, + const pgp_signature_t *sig) +{ + if ((sig->type() != PGP_SIG_SUBKEY) && (sig->type() != PGP_SIG_REV_SUBKEY)) { + return PGP_SIG_IMPORT_STATUS_UNKNOWN; + } + pgp_key_t *primary = rnp_key_store_get_signer_key(keyring, sig); + if (!primary || !key->has_primary_fp()) { + RNP_LOG("No primary grip or primary key"); + return PGP_SIG_IMPORT_STATUS_UNKNOWN_KEY; + } + if (primary->fp() != key->primary_fp()) { + RNP_LOG("Wrong subkey signature's signer."); + return PGP_SIG_IMPORT_STATUS_UNKNOWN; + } + + try { + pgp_key_t tmpkey(key->pkt()); + tmpkey.add_sig(*sig); + if (!tmpkey.refresh_data(primary, keyring->secctx)) { + RNP_LOG("Failed to add signature to the key."); + return PGP_SIG_IMPORT_STATUS_UNKNOWN; + } + + size_t expackets = key->rawpkt_count(); + key = rnp_key_store_add_key(keyring, &tmpkey); + if (!key) { + RNP_LOG("Failed to add key with imported sig to the keyring"); + return PGP_SIG_IMPORT_STATUS_UNKNOWN; + } + return (key->rawpkt_count() > expackets) ? PGP_SIG_IMPORT_STATUS_NEW : + PGP_SIG_IMPORT_STATUS_UNCHANGED; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return PGP_SIG_IMPORT_STATUS_UNKNOWN; + } +} + +pgp_sig_import_status_t +rnp_key_store_import_key_signature(rnp_key_store_t * keyring, + pgp_key_t * key, + const pgp_signature_t *sig) +{ + if (key->is_subkey()) { + return rnp_key_store_import_subkey_signature(keyring, key, sig); + } + if ((sig->type() != PGP_SIG_DIRECT) && (sig->type() != PGP_SIG_REV_KEY)) { + RNP_LOG("Wrong signature type: %d", (int) sig->type()); + return PGP_SIG_IMPORT_STATUS_UNKNOWN; + } + + try { + pgp_key_t tmpkey(key->pkt()); + tmpkey.add_sig(*sig); + if (!tmpkey.refresh_data(keyring->secctx)) { + RNP_LOG("Failed to add signature to the key."); + return PGP_SIG_IMPORT_STATUS_UNKNOWN; + } + + size_t expackets = key->rawpkt_count(); + key = rnp_key_store_add_key(keyring, &tmpkey); + if (!key) { + RNP_LOG("Failed to add key with imported sig to the keyring"); + return PGP_SIG_IMPORT_STATUS_UNKNOWN; + } + return (key->rawpkt_count() > expackets) ? PGP_SIG_IMPORT_STATUS_NEW : + PGP_SIG_IMPORT_STATUS_UNCHANGED; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return PGP_SIG_IMPORT_STATUS_UNKNOWN; + } +} + +pgp_key_t * +rnp_key_store_import_signature(rnp_key_store_t * keyring, + const pgp_signature_t * sig, + pgp_sig_import_status_t *status) +{ + pgp_sig_import_status_t tmp_status = PGP_SIG_IMPORT_STATUS_UNKNOWN; + if (!status) { + status = &tmp_status; + } + *status = PGP_SIG_IMPORT_STATUS_UNKNOWN; + + /* we support only direct-key and key revocation signatures here */ + if ((sig->type() != PGP_SIG_DIRECT) && (sig->type() != PGP_SIG_REV_KEY)) { + return NULL; + } + + pgp_key_t *res_key = rnp_key_store_get_signer_key(keyring, sig); + if (!res_key || !res_key->is_primary()) { + *status = PGP_SIG_IMPORT_STATUS_UNKNOWN_KEY; + return NULL; + } + *status = rnp_key_store_import_key_signature(keyring, res_key, sig); + return res_key; +} + +bool +rnp_key_store_remove_key(rnp_key_store_t *keyring, const pgp_key_t *key, bool subkeys) +{ + auto it = keyring->keybyfp.find(key->fp()); + if (it == keyring->keybyfp.end()) { + return false; + } + + /* cleanup primary_grip (or subkey)/subkey_grips */ + if (key->is_primary() && key->subkey_count()) { + for (size_t i = 0; i < key->subkey_count(); i++) { + auto it = keyring->keybyfp.find(key->get_subkey_fp(i)); + if (it == keyring->keybyfp.end()) { + continue; + } + /* if subkeys are deleted then no need to update grips */ + if (subkeys) { + keyring->keys.erase(it->second); + keyring->keybyfp.erase(it); + continue; + } + it->second->unset_primary_fp(); + } + } + if (key->is_subkey() && key->has_primary_fp()) { + pgp_key_t *primary = rnp_key_store_get_primary_key(keyring, key); + if (primary) { + primary->remove_subkey_fp(key->fp()); + } + } + + keyring->keys.erase(it->second); + keyring->keybyfp.erase(it); + return true; +} + +const pgp_key_t * +rnp_key_store_get_key_by_fpr(const rnp_key_store_t *keyring, const pgp_fingerprint_t &fpr) +{ + auto it = keyring->keybyfp.find(fpr); + if (it == keyring->keybyfp.end()) { + return NULL; + } + return &*it->second; +} + +pgp_key_t * +rnp_key_store_get_key_by_fpr(rnp_key_store_t *keyring, const pgp_fingerprint_t &fpr) +{ + auto it = keyring->keybyfp.find(fpr); + if (it == keyring->keybyfp.end()) { + return NULL; + } + return &*it->second; +} + +pgp_key_t * +rnp_key_store_get_primary_key(rnp_key_store_t *keyring, const pgp_key_t *subkey) +{ + if (!subkey->is_subkey()) { + return NULL; + } + + if (subkey->has_primary_fp()) { + pgp_key_t *primary = rnp_key_store_get_key_by_fpr(keyring, subkey->primary_fp()); + return primary && primary->is_primary() ? primary : NULL; + } + + for (size_t i = 0; i < subkey->sig_count(); i++) { + const pgp_subsig_t &subsig = subkey->get_sig(i); + if (subsig.sig.type() != PGP_SIG_SUBKEY) { + continue; + } + + pgp_key_t *primary = rnp_key_store_get_signer_key(keyring, &subsig.sig); + if (primary && primary->is_primary()) { + return primary; + } + } + return NULL; +} + +static void +grip_hash_mpi(rnp::Hash &hash, const pgp_mpi_t &val, const char name, bool lzero = true) +{ + size_t len = mpi_bytes(&val); + size_t idx = 0; + for (idx = 0; (idx < len) && !val.mpi[idx]; idx++) + ; + + if (name) { + size_t hlen = idx >= len ? 0 : len - idx; + if ((len > idx) && lzero && (val.mpi[idx] & 0x80)) { + hlen++; + } + + char buf[20] = {0}; + snprintf(buf, sizeof(buf), "(1:%c%zu:", name, hlen); + hash.add(buf, strlen(buf)); + } + + if (idx < len) { + /* gcrypt prepends mpis with zero if higher bit is set */ + if (lzero && (val.mpi[idx] & 0x80)) { + uint8_t zero = 0; + hash.add(&zero, 1); + } + hash.add(val.mpi + idx, len - idx); + } + if (name) { + hash.add(")", 1); + } +} + +static void +grip_hash_ecc_hex(rnp::Hash &hash, const char *hex, char name) +{ + pgp_mpi_t mpi = {}; + mpi.len = rnp::hex_decode(hex, mpi.mpi, sizeof(mpi.mpi)); + if (!mpi.len) { + RNP_LOG("wrong hex mpi"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + /* libgcrypt doesn't add leading zero when hashes ecc mpis */ + return grip_hash_mpi(hash, mpi, name, false); +} + +static void +grip_hash_ec(rnp::Hash &hash, const pgp_ec_key_t &key) +{ + const ec_curve_desc_t *desc = get_curve_desc(key.curve); + if (!desc) { + RNP_LOG("unknown curve %d", (int) key.curve); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + /* build uncompressed point from gx and gy */ + pgp_mpi_t g = {}; + g.mpi[0] = 0x04; + g.len = 1; + size_t len = rnp::hex_decode(desc->gx, g.mpi + g.len, sizeof(g.mpi) - g.len); + if (!len) { + RNP_LOG("wrong x mpi"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + g.len += len; + len = rnp::hex_decode(desc->gy, g.mpi + g.len, sizeof(g.mpi) - g.len); + if (!len) { + RNP_LOG("wrong y mpi"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + g.len += len; + + /* p, a, b, g, n, q */ + grip_hash_ecc_hex(hash, desc->p, 'p'); + grip_hash_ecc_hex(hash, desc->a, 'a'); + grip_hash_ecc_hex(hash, desc->b, 'b'); + grip_hash_mpi(hash, g, 'g', false); + grip_hash_ecc_hex(hash, desc->n, 'n'); + + if ((key.curve == PGP_CURVE_ED25519) || (key.curve == PGP_CURVE_25519)) { + if (g.len < 1) { + RNP_LOG("wrong 25519 p"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + g.len = key.p.len - 1; + memcpy(g.mpi, key.p.mpi + 1, g.len); + grip_hash_mpi(hash, g, 'q', false); + } else { + grip_hash_mpi(hash, key.p, 'q', false); + } +} + +/* keygrip is subjectKeyHash from pkcs#15 for RSA. */ +bool +rnp_key_store_get_key_grip(const pgp_key_material_t *key, pgp_key_grip_t &grip) +{ + try { + auto hash = rnp::Hash::create(PGP_HASH_SHA1); + switch (key->alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_SIGN_ONLY: + case PGP_PKA_RSA_ENCRYPT_ONLY: + grip_hash_mpi(*hash, key->rsa.n, '\0'); + break; + case PGP_PKA_DSA: + grip_hash_mpi(*hash, key->dsa.p, 'p'); + grip_hash_mpi(*hash, key->dsa.q, 'q'); + grip_hash_mpi(*hash, key->dsa.g, 'g'); + grip_hash_mpi(*hash, key->dsa.y, 'y'); + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + grip_hash_mpi(*hash, key->eg.p, 'p'); + grip_hash_mpi(*hash, key->eg.g, 'g'); + grip_hash_mpi(*hash, key->eg.y, 'y'); + break; + case PGP_PKA_ECDH: + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: + case PGP_PKA_SM2: + grip_hash_ec(*hash, key->ec); + break; + default: + RNP_LOG("unsupported public-key algorithm %d", (int) key->alg); + return false; + } + return hash->finish(grip.data()) == grip.size(); + } catch (const std::exception &e) { + RNP_LOG("Grip calculation failed: %s", e.what()); + return false; + } +} + +pgp_key_t * +rnp_key_store_search(rnp_key_store_t * keyring, + const pgp_key_search_t *search, + pgp_key_t * after) +{ + // since keys are distinguished by fingerprint then just do map lookup + if (search->type == PGP_KEY_SEARCH_FINGERPRINT) { + pgp_key_t *key = rnp_key_store_get_key_by_fpr(keyring, search->by.fingerprint); + if (after && (after != key)) { + RNP_LOG("searching with invalid after param"); + return NULL; + } + // return NULL if after is specified + return after ? NULL : key; + } + + // if after is provided, make sure it is a member of the appropriate list + auto it = + std::find_if(keyring->keys.begin(), keyring->keys.end(), [after](const pgp_key_t &key) { + return !after || (after == &key); + }); + if (after && (it == keyring->keys.end())) { + RNP_LOG("searching with non-keyrings after param"); + return NULL; + } + if (after) { + it = std::next(it); + } + it = std::find_if(it, keyring->keys.end(), [search](const pgp_key_t &key) { + return rnp_key_matches_search(&key, search); + }); + return (it == keyring->keys.end()) ? NULL : &(*it); +} + +rnp_key_store_t::rnp_key_store_t(pgp_key_store_format_t _format, + const std::string & _path, + rnp::SecurityContext & ctx) + : secctx(ctx) +{ + if (_format == PGP_KEY_STORE_UNKNOWN) { + RNP_LOG("Invalid key store format"); + throw std::invalid_argument("format"); + } + format = _format; + path = _path; +} + +rnp_key_store_t::~rnp_key_store_t() +{ + rnp_key_store_clear(this); +} diff --git a/comm/third_party/rnp/src/librepgp/stream-armor.cpp b/comm/third_party/rnp/src/librepgp/stream-armor.cpp new file mode 100644 index 0000000000..669c3057c3 --- /dev/null +++ b/comm/third_party/rnp/src/librepgp/stream-armor.cpp @@ -0,0 +1,1287 @@ +/* + * Copyright (c) 2017-2022, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include <stdlib.h> +#include <stdio.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#else +#include "uniwin.h" +#endif +#include <string.h> +#include <algorithm> +#include "stream-def.h" +#include "stream-armor.h" +#include "stream-packet.h" +#include "str-utils.h" +#include "crypto/hash.hpp" +#include "utils.h" + +#define ARMORED_BLOCK_SIZE (4096) +#define ARMORED_PEEK_BUF_SIZE 1024 +#define ARMORED_MIN_LINE_LENGTH (16) +#define ARMORED_MAX_LINE_LENGTH (76) + +typedef struct pgp_source_armored_param_t { + pgp_source_t * readsrc; /* source to read from */ + pgp_armored_msg_t type; /* type of the message */ + char * armorhdr; /* armor header */ + char * version; /* Version: header if any */ + char * comment; /* Comment: header if any */ + char * hash; /* Hash: header if any */ + char * charset; /* Charset: header if any */ + uint8_t rest[ARMORED_BLOCK_SIZE]; /* unread decoded bytes, makes implementation easier */ + unsigned restlen; /* number of bytes in rest */ + unsigned restpos; /* index of first unread byte in rest, restpos <= restlen */ + uint8_t brest[3]; /* decoded 6-bit tail bytes */ + unsigned brestlen; /* number of bytes in brest */ + bool eofb64; /* end of base64 stream reached */ + uint8_t readcrc[3]; /* crc-24 from the armored data */ + bool has_crc; /* message contains CRC line */ + std::unique_ptr<rnp::CRC24> crc_ctx; /* CTX used to calculate CRC */ + bool noheaders; /* only base64 data, no headers */ +} pgp_source_armored_param_t; + +typedef struct pgp_dest_armored_param_t { + pgp_dest_t * writedst; + pgp_armored_msg_t type; /* type of the message */ + char eol[2]; /* end of line, all non-zeroes are written */ + unsigned lout; /* chars written in current line */ + unsigned llen; /* length of the base64 line, defaults to 76 as per RFC */ + uint8_t tail[2]; /* bytes which didn't fit into 3-byte boundary */ + unsigned tailc; /* number of bytes in tail */ + std::unique_ptr<rnp::CRC24> crc_ctx; /* CTX used to calculate CRC */ +} pgp_dest_armored_param_t; + +/* + Table for base64 lookups: + 0xff - wrong character, + 0xfe - '=' + 0xfd - eol/whitespace, + 0..0x3f - represented 6-bit number +*/ +static const uint8_t B64DEC[256] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xfd, 0xff, 0xff, 0xfd, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3e, 0xff, + 0xff, 0xff, 0x3f, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0xff, 0xff, + 0xff, 0xfe, 0xff, 0xff, 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, + 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, + 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff}; + +static bool +armor_read_padding(pgp_source_armored_param_t *param, size_t *read) +{ + char st[64]; + size_t stlen = 0; + + if (!src_peek_line(param->readsrc, st, 64, &stlen)) { + return false; + } + + if ((stlen == 1) || (stlen == 2)) { + if ((st[0] != CH_EQ) || ((stlen == 2) && (st[1] != CH_EQ))) { + return false; + } + + *read = stlen; + src_skip(param->readsrc, stlen); + return src_skip_eol(param->readsrc); + } else if (stlen == 5) { + *read = 0; + return true; + } else if ((stlen > 5) && !memcmp(st, ST_DASHES, 5)) { + /* case with absent crc and 3-byte last chunk */ + *read = 0; + return true; + } + return false; +} + +static bool +base64_read_padding(pgp_source_armored_param_t *param, size_t *read) +{ + char pad[16]; + size_t padlen = sizeof(pad); + + /* we would allow arbitrary number of whitespaces/eols after the padding */ + if (!src_read(param->readsrc, pad, padlen, &padlen)) { + return false; + } + /* strip trailing whitespaces */ + while (padlen && (B64DEC[(int) pad[padlen - 1]] == 0xfd)) { + padlen--; + } + /* check for '=' */ + for (size_t i = 0; i < padlen; i++) { + if (pad[i] != CH_EQ) { + RNP_LOG("wrong base64 padding: %.*s", (int) padlen, pad); + return false; + } + } + if (padlen > 2) { + RNP_LOG("wrong base64 padding length %zu.", padlen); + return false; + } + if (!src_eof(param->readsrc)) { + RNP_LOG("warning: extra data after the base64 stream."); + } + *read = padlen; + return true; +} + +static bool +armor_read_crc(pgp_source_t *src) +{ + uint8_t dec[4] = {0}; + char crc[8] = {0}; + size_t clen = 0; + pgp_source_armored_param_t *param = (pgp_source_armored_param_t *) src->param; + + if (!src_peek_line(param->readsrc, crc, sizeof(crc), &clen)) { + return false; + } + + if ((clen != 5) || (crc[0] != CH_EQ)) { + return false; + } + + for (int i = 0; i < 4; i++) { + if ((dec[i] = B64DEC[(uint8_t) crc[i + 1]]) >= 64) { + return false; + } + } + + param->readcrc[0] = (dec[0] << 2) | ((dec[1] >> 4) & 0x0F); + param->readcrc[1] = (dec[1] << 4) | ((dec[2] >> 2) & 0x0F); + param->readcrc[2] = (dec[2] << 6) | dec[3]; + + param->has_crc = true; + + src_skip(param->readsrc, 5); + return src_skip_eol(param->readsrc); +} + +static bool +armor_skip_chars(pgp_source_t *src, const char *chars) +{ + uint8_t ch; + size_t read; + + do { + bool found = false; + if (!src_peek(src, &ch, 1, &read)) { + return false; + } + if (!read) { + /* return true only if there is no underlying read error */ + return true; + } + for (const char *chptr = chars; *chptr; chptr++) { + if (ch == *chptr) { + src_skip(src, 1); + found = true; + break; + } + } + if (!found) { + break; + } + } while (1); + + return true; +} + +static bool +armor_read_trailer(pgp_source_t *src) +{ + char st[64]; + char str[64]; + size_t stlen; + pgp_source_armored_param_t *param = (pgp_source_armored_param_t *) src->param; + + if (!armor_skip_chars(param->readsrc, "\r\n")) { + return false; + } + + stlen = strlen(param->armorhdr); + if ((stlen > 5) && (stlen + 8 + 1 <= sizeof(st))) { + memcpy(st, ST_ARMOR_END, 8); /* 8 here is mandatory */ + memcpy(st + 8, param->armorhdr + 5, stlen - 5); + memcpy(st + stlen + 3, ST_DASHES, 5); + stlen += 8; + } else { + RNP_LOG("Internal error"); + return false; + } + if (!src_peek_eq(param->readsrc, str, stlen) || strncmp(str, st, stlen)) { + return false; + } + src_skip(param->readsrc, stlen); + (void) armor_skip_chars(param->readsrc, "\t "); + (void) src_skip_eol(param->readsrc); + return true; +} + +static bool +armored_update_crc(pgp_source_armored_param_t *param, + const void * buf, + size_t len, + bool finish = false) +{ + if (param->noheaders) { + return true; + } + try { + param->crc_ctx->add(buf, len); + if (!finish) { + return true; + } + auto crc = param->crc_ctx->finish(); + if (param->has_crc && memcmp(param->readcrc, crc.data(), 3)) { + RNP_LOG("Warning: CRC mismatch"); + } + return true; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } +} + +static bool +armored_src_read(pgp_source_t *src, void *buf, size_t len, size_t *readres) +{ + pgp_source_armored_param_t *param = (pgp_source_armored_param_t *) src->param; + uint8_t b64buf[ARMORED_BLOCK_SIZE]; /* input base64 data with spaces and so on */ + uint8_t decbuf[ARMORED_BLOCK_SIZE + 4]; /* decoded 6-bit values */ + uint8_t *bufptr = (uint8_t *) buf; /* for better readability below */ + uint8_t *bptr, *bend; /* pointer to input data in b64buf */ + uint8_t *dptr, *dend, *pend; /* pointers to decoded data in decbuf: working pointer, last + available byte, last byte to process */ + uint8_t bval; + uint32_t b24; + size_t read = 0; + size_t left = len; + size_t eqcount = 0; /* number of '=' at the end of base64 stream */ + + if (!param) { + return false; + } + + /* checking whether there are some decoded bytes */ + if (param->restpos < param->restlen) { + if (param->restlen - param->restpos >= len) { + memcpy(bufptr, ¶m->rest[param->restpos], len); + param->restpos += len; + try { + param->crc_ctx->add(bufptr, len); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } + *readres = len; + return true; + } else { + left = len - (param->restlen - param->restpos); + memcpy(bufptr, ¶m->rest[param->restpos], len - left); + param->restpos = param->restlen = 0; + bufptr += len - left; + } + } + + if (param->eofb64) { + *readres = len - left; + return true; + } + + memcpy(decbuf, param->brest, param->brestlen); + dend = decbuf + param->brestlen; + + do { + if (!src_peek(param->readsrc, b64buf, sizeof(b64buf), &read)) { + return false; + } + if (!read) { + RNP_LOG("premature end of armored input"); + return false; + } + + dptr = dend; + bptr = b64buf; + bend = b64buf + read; + /* checking input data, stripping away whitespaces, checking for end of the b64 data */ + while (bptr < bend) { + if ((bval = B64DEC[*(bptr++)]) < 64) { + *(dptr++) = bval; + } else if (bval == 0xfe) { + /* '=' means the base64 padding or the beginning of checksum */ + param->eofb64 = true; + break; + } else if (bval == 0xff) { + auto ch = *(bptr - 1); + /* OpenPGP message headers without the crc and without trailing = */ + if ((ch == CH_DASH) && !param->noheaders) { + param->eofb64 = true; + break; + } + RNP_LOG("wrong base64 character 0x%02hhX", ch); + return false; + } + } + + dend = dptr; + dptr = decbuf; + /* Processing full 4s which will go directly to the buf. + After this left < 3 or decbuf has < 4 bytes */ + if ((size_t)(dend - dptr) / 4 * 3 < left) { + pend = decbuf + (dend - dptr) / 4 * 4; + left -= (dend - dptr) / 4 * 3; + } else { + pend = decbuf + (left / 3) * 4; + left -= left / 3 * 3; + } + + /* this one would the most performance-consuming part for large chunks */ + while (dptr < pend) { + b24 = *dptr++ << 18; + b24 |= *dptr++ << 12; + b24 |= *dptr++ << 6; + b24 |= *dptr++; + *bufptr++ = b24 >> 16; + *bufptr++ = b24 >> 8; + *bufptr++ = b24 & 0xff; + } + + /* moving rest to the beginning of decbuf */ + memmove(decbuf, dptr, dend - dptr); + dend = decbuf + (dend - dptr); + + /* skip already processed data */ + if (!param->eofb64) { + /* all input is base64 data or eol/spaces, so skipping it */ + src_skip(param->readsrc, read); + /* check for eof for base64-encoded data without headers */ + if (param->noheaders && src_eof(param->readsrc)) { + src_skip(param->readsrc, read); + param->eofb64 = true; + } else { + continue; + } + } else { + /* '=' reached, bptr points on it */ + src_skip(param->readsrc, bptr - b64buf - 1); + } + + /* end of base64 data */ + if (param->noheaders) { + if (!base64_read_padding(param, &eqcount)) { + return false; + } + break; + } + /* reading b64 padding if any */ + if (!armor_read_padding(param, &eqcount)) { + RNP_LOG("wrong padding"); + return false; + } + /* reading crc */ + if (!armor_read_crc(src)) { + RNP_LOG("Warning: missing or malformed CRC line"); + } + /* reading armor trailing line */ + if (!armor_read_trailer(src)) { + RNP_LOG("wrong armor trailer"); + return false; + } + break; + } while (left >= 3); + + /* process bytes left in decbuf */ + + dptr = decbuf; + pend = decbuf + (dend - decbuf) / 4 * 4; + bptr = param->rest; + while (dptr < pend) { + b24 = *dptr++ << 18; + b24 |= *dptr++ << 12; + b24 |= *dptr++ << 6; + b24 |= *dptr++; + *bptr++ = b24 >> 16; + *bptr++ = b24 >> 8; + *bptr++ = b24 & 0xff; + } + + if (!armored_update_crc(param, buf, bufptr - (uint8_t *) buf)) { + return false; + } + + if (param->eofb64) { + if ((dend - dptr + eqcount) % 4 != 0) { + RNP_LOG("wrong b64 padding"); + return false; + } + + if (eqcount == 1) { + b24 = (*dptr << 10) | (*(dptr + 1) << 4) | (*(dptr + 2) >> 2); + *bptr++ = b24 >> 8; + *bptr++ = b24 & 0xff; + } else if (eqcount == 2) { + *bptr++ = (*dptr << 2) | (*(dptr + 1) >> 4); + } + + /* Calculate CRC after reading whole input stream */ + if (!armored_update_crc(param, param->rest, bptr - param->rest, true)) { + return false; + } + } else { + /* few bytes which do not fit to 4 boundary */ + for (int i = 0; i < dend - dptr; i++) { + param->brest[i] = *(dptr + i); + } + param->brestlen = dend - dptr; + } + + param->restlen = bptr - param->rest; + + /* check whether we have some bytes to add */ + if ((left > 0) && (param->restlen > 0)) { + read = left > param->restlen ? param->restlen : left; + memcpy(bufptr, param->rest, read); + if (!param->eofb64 && !armored_update_crc(param, bufptr, read)) { + return false; + } + left -= read; + param->restpos += read; + } + + *readres = len - left; + return true; +} + +static void +armored_src_close(pgp_source_t *src) +{ + pgp_source_armored_param_t *param = (pgp_source_armored_param_t *) src->param; + + if (param) { + free(param->armorhdr); + free(param->version); + free(param->comment); + free(param->hash); + free(param->charset); + delete param; + src->param = NULL; + } +} + +/** @brief finds armor header position in the buffer, returning beginning of header or NULL. + * hdrlen will contain the length of the header + **/ +static const char * +find_armor_header(const char *buf, size_t len, size_t *hdrlen) +{ + int st = -1; + + for (unsigned i = 0; i < len - 10; i++) { + if ((buf[i] == CH_DASH) && !strncmp(&buf[i + 1], ST_DASHES, 4)) { + st = i; + break; + } + } + + if (st < 0) { + return NULL; + } + + for (unsigned i = st + 5; i <= len - 5; i++) { + if ((buf[i] == CH_DASH) && !strncmp(&buf[i + 1], ST_DASHES, 4)) { + *hdrlen = i + 5 - st; + return &buf[st]; + } + } + + return NULL; +} + +static bool +str_equals(const char *str, size_t len, const char *another) +{ + size_t alen = strlen(another); + return (len == alen) && !memcmp(str, another, alen); +} + +static pgp_armored_msg_t +armor_str_to_data_type(const char *str, size_t len) +{ + if (!str) { + return PGP_ARMORED_UNKNOWN; + } + if (str_equals(str, len, "BEGIN PGP MESSAGE")) { + return PGP_ARMORED_MESSAGE; + } + if (str_equals(str, len, "BEGIN PGP PUBLIC KEY BLOCK") || + str_equals(str, len, "BEGIN PGP PUBLIC KEY")) { + return PGP_ARMORED_PUBLIC_KEY; + } + if (str_equals(str, len, "BEGIN PGP SECRET KEY BLOCK") || + str_equals(str, len, "BEGIN PGP SECRET KEY") || + str_equals(str, len, "BEGIN PGP PRIVATE KEY BLOCK") || + str_equals(str, len, "BEGIN PGP PRIVATE KEY")) { + return PGP_ARMORED_SECRET_KEY; + } + if (str_equals(str, len, "BEGIN PGP SIGNATURE")) { + return PGP_ARMORED_SIGNATURE; + } + if (str_equals(str, len, "BEGIN PGP SIGNED MESSAGE")) { + return PGP_ARMORED_CLEARTEXT; + } + return PGP_ARMORED_UNKNOWN; +} + +pgp_armored_msg_t +rnp_armor_guess_type(pgp_source_t *src) +{ + uint8_t ptag; + + if (!src_peek_eq(src, &ptag, 1)) { + return PGP_ARMORED_UNKNOWN; + } + + switch (get_packet_type(ptag)) { + case PGP_PKT_PK_SESSION_KEY: + case PGP_PKT_SK_SESSION_KEY: + case PGP_PKT_ONE_PASS_SIG: + case PGP_PKT_SE_DATA: + case PGP_PKT_SE_IP_DATA: + case PGP_PKT_COMPRESSED: + case PGP_PKT_LITDATA: + case PGP_PKT_MARKER: + return PGP_ARMORED_MESSAGE; + case PGP_PKT_PUBLIC_KEY: + case PGP_PKT_PUBLIC_SUBKEY: + return PGP_ARMORED_PUBLIC_KEY; + case PGP_PKT_SECRET_KEY: + case PGP_PKT_SECRET_SUBKEY: + return PGP_ARMORED_SECRET_KEY; + case PGP_PKT_SIGNATURE: + return PGP_ARMORED_SIGNATURE; + default: + return PGP_ARMORED_UNKNOWN; + } +} + +static pgp_armored_msg_t +rnp_armored_guess_type_by_readahead(pgp_source_t *src) +{ + if (!src->cache) { + return PGP_ARMORED_UNKNOWN; + } + + pgp_source_t armorsrc = {0}; + pgp_source_t memsrc = {0}; + size_t read; + // peek as much as the cache can take + bool cache_res = src_peek(src, NULL, sizeof(src->cache->buf), &read); + if (!cache_res || !read || + init_mem_src(&memsrc, + src->cache->buf + src->cache->pos, + src->cache->len - src->cache->pos, + false)) { + return PGP_ARMORED_UNKNOWN; + } + rnp_result_t res = init_armored_src(&armorsrc, &memsrc); + if (res) { + src_close(&memsrc); + RNP_LOG("failed to parse armored data"); + return PGP_ARMORED_UNKNOWN; + } + pgp_armored_msg_t guessed = rnp_armor_guess_type(&armorsrc); + src_close(&armorsrc); + src_close(&memsrc); + return guessed; +} + +pgp_armored_msg_t +rnp_armored_get_type(pgp_source_t *src) +{ + pgp_armored_msg_t guessed = rnp_armored_guess_type_by_readahead(src); + if (guessed != PGP_ARMORED_UNKNOWN) { + return guessed; + } + + char hdr[ARMORED_PEEK_BUF_SIZE]; + const char *armhdr; + size_t armhdrlen; + size_t read; + + if (!src_peek(src, hdr, sizeof(hdr), &read) || (read < 20)) { + return PGP_ARMORED_UNKNOWN; + } + if (!(armhdr = find_armor_header(hdr, read, &armhdrlen))) { + return PGP_ARMORED_UNKNOWN; + } + + return armor_str_to_data_type(armhdr + 5, armhdrlen - 10); +} + +static bool +armor_parse_header(pgp_source_t *src) +{ + char hdr[ARMORED_PEEK_BUF_SIZE]; + const char * armhdr; + size_t armhdrlen; + size_t read; + pgp_source_armored_param_t *param = (pgp_source_armored_param_t *) src->param; + + if (!src_peek(param->readsrc, hdr, sizeof(hdr), &read) || (read < 20)) { + return false; + } + + if (!(armhdr = find_armor_header(hdr, read, &armhdrlen))) { + RNP_LOG("no armor header"); + return false; + } + + /* if there are non-whitespaces before the armor header then issue warning */ + for (char *ch = hdr; ch < armhdr; ch++) { + if (B64DEC[(uint8_t) *ch] != 0xfd) { + RNP_LOG("extra data before the header line"); + break; + } + } + + param->type = armor_str_to_data_type(armhdr + 5, armhdrlen - 10); + if (param->type == PGP_ARMORED_UNKNOWN) { + RNP_LOG("unknown armor header"); + return false; + } + + if ((param->armorhdr = (char *) malloc(armhdrlen - 9)) == NULL) { + RNP_LOG("allocation failed"); + return false; + } + + memcpy(param->armorhdr, armhdr + 5, armhdrlen - 10); + param->armorhdr[armhdrlen - 10] = '\0'; + src_skip(param->readsrc, armhdr - hdr + armhdrlen); + armor_skip_chars(param->readsrc, "\t "); + return true; +} + +static bool +armor_skip_line(pgp_source_t *src) +{ + char header[ARMORED_PEEK_BUF_SIZE] = {0}; + do { + size_t hdrlen = 0; + bool res = src_peek_line(src, header, sizeof(header), &hdrlen); + if (hdrlen) { + src_skip(src, hdrlen); + } + if (res || (hdrlen < sizeof(header) - 1)) { + return res; + } + } while (1); +} + +static bool +is_base64_line(const char *line, size_t len) +{ + for (size_t i = 0; i < len && line[i]; i++) { + if (B64DEC[(uint8_t) line[i]] == 0xff) + return false; + } + return true; +} + +static bool +armor_parse_headers(pgp_source_t *src) +{ + pgp_source_armored_param_t *param = (pgp_source_armored_param_t *) src->param; + char header[ARMORED_PEEK_BUF_SIZE] = {0}; + + do { + size_t hdrlen = 0; + if (!src_peek_line(param->readsrc, header, sizeof(header), &hdrlen)) { + /* if line is too long let's cut it to the reasonable size */ + src_skip(param->readsrc, hdrlen); + if ((hdrlen != sizeof(header) - 1) || !armor_skip_line(param->readsrc)) { + RNP_LOG("failed to peek line: unexpected end of data"); + return false; + } + RNP_LOG("Too long armor header - truncated."); + header[hdrlen] = '\0'; + } else if (hdrlen) { + if (is_base64_line(header, hdrlen)) { + RNP_LOG("Warning: no empty line after the base64 headers"); + return true; + } + src_skip(param->readsrc, hdrlen); + if (rnp::is_blank_line(header, hdrlen)) { + return src_skip_eol(param->readsrc); + } + } else { + /* empty line - end of the headers */ + return src_skip_eol(param->readsrc); + } + + char *hdrval = (char *) malloc(hdrlen + 1); + if (!hdrval) { + RNP_LOG("malloc failed"); + return false; + } + + if ((hdrlen >= 9) && !strncmp(header, ST_HEADER_VERSION, 9)) { + memcpy(hdrval, header + 9, hdrlen - 8); + free(param->version); + param->version = hdrval; + } else if ((hdrlen >= 9) && !strncmp(header, ST_HEADER_COMMENT, 9)) { + memcpy(hdrval, header + 9, hdrlen - 8); + free(param->comment); + param->comment = hdrval; + } else if ((hdrlen >= 5) && !strncmp(header, ST_HEADER_HASH, 6)) { + memcpy(hdrval, header + 6, hdrlen - 5); + free(param->hash); + param->hash = hdrval; + } else if ((hdrlen >= 9) && !strncmp(header, ST_HEADER_CHARSET, 9)) { + memcpy(hdrval, header + 9, hdrlen - 8); + free(param->charset); + param->charset = hdrval; + } else { + RNP_LOG("unknown header '%s'", header); + free(hdrval); + } + + if (!src_skip_eol(param->readsrc)) { + return false; + } + } while (1); +} + +rnp_result_t +init_armored_src(pgp_source_t *src, pgp_source_t *readsrc, bool noheaders) +{ + if (!init_src_common(src, 0)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + pgp_source_armored_param_t *param = new (std::nothrow) pgp_source_armored_param_t(); + if (!param) { + RNP_LOG("allocation failed"); + return RNP_ERROR_OUT_OF_MEMORY; + } + + param->readsrc = readsrc; + param->noheaders = noheaders; + src->param = param; + src->read = armored_src_read; + src->close = armored_src_close; + src->type = PGP_STREAM_ARMORED; + + /* base64 data only */ + if (noheaders) { + return RNP_SUCCESS; + } + + /* initialize crc context */ + param->crc_ctx = rnp::CRC24::create(); + /* parsing armored header */ + rnp_result_t errcode = RNP_ERROR_GENERIC; + if (!armor_parse_header(src)) { + errcode = RNP_ERROR_BAD_FORMAT; + goto finish; + } + /* eol */ + if (!src_skip_eol(param->readsrc)) { + RNP_LOG("no eol after the armor header"); + errcode = RNP_ERROR_BAD_FORMAT; + goto finish; + } + /* parsing headers */ + if (!armor_parse_headers(src)) { + RNP_LOG("failed to parse headers"); + errcode = RNP_ERROR_BAD_FORMAT; + goto finish; + } + + /* now we are good to go with base64-encoded data */ + errcode = RNP_SUCCESS; +finish: + if (errcode) { + src_close(src); + } + return errcode; +} + +/** @brief Write message header to the dst. */ +static bool +armor_write_message_header(pgp_dest_armored_param_t *param, bool finish) +{ + const char *str = finish ? ST_ARMOR_END : ST_ARMOR_BEGIN; + dst_write(param->writedst, str, strlen(str)); + switch (param->type) { + case PGP_ARMORED_MESSAGE: + str = "MESSAGE"; + break; + case PGP_ARMORED_PUBLIC_KEY: + str = "PUBLIC KEY BLOCK"; + break; + case PGP_ARMORED_SECRET_KEY: + str = "PRIVATE KEY BLOCK"; + break; + case PGP_ARMORED_SIGNATURE: + str = "SIGNATURE"; + break; + case PGP_ARMORED_CLEARTEXT: + str = "SIGNED MESSAGE"; + break; + default: + return false; + } + dst_write(param->writedst, str, strlen(str)); + dst_write(param->writedst, ST_DASHES, strlen(ST_DASHES)); + return true; +} + +static void +armor_write_eol(pgp_dest_armored_param_t *param) +{ + if (param->eol[0]) { + dst_write(param->writedst, ¶m->eol[0], 1); + } + if (param->eol[1]) { + dst_write(param->writedst, ¶m->eol[1], 1); + } +} + +static void +armor_append_eol(pgp_dest_armored_param_t *param, uint8_t *&ptr) +{ + if (param->eol[0]) { + *ptr++ = param->eol[0]; + } + if (param->eol[1]) { + *ptr++ = param->eol[1]; + } +} + +/* Base 64 encoded table, quadruplicated to save cycles on use & 0x3f operation */ +static const uint8_t B64ENC[256] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', + 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', + 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', + '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', + 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '+', '/', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', + 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', 'A', 'B', 'C', 'D', 'E', 'F', + 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', + 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', '+', '/'}; + +static void +armored_encode3(uint8_t *out, uint8_t *in) +{ + out[0] = B64ENC[in[0] >> 2]; + out[1] = B64ENC[((in[0] << 4) | (in[1] >> 4)) & 0xff]; + out[2] = B64ENC[((in[1] << 2) | (in[2] >> 6)) & 0xff]; + out[3] = B64ENC[in[2] & 0xff]; +} + +static rnp_result_t +armored_dst_write(pgp_dest_t *dst, const void *buf, size_t len) +{ + pgp_dest_armored_param_t *param = (pgp_dest_armored_param_t *) dst->param; + if (!param) { + RNP_LOG("wrong param"); + return RNP_ERROR_BAD_PARAMETERS; + } + + /* update crc */ + bool base64 = param->type == PGP_ARMORED_BASE64; + if (!base64) { + try { + param->crc_ctx->add(buf, len); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_BAD_STATE; + } + } + + uint8_t encbuf[PGP_INPUT_CACHE_SIZE / 2]; + uint8_t *bufptr = (uint8_t *) buf; + uint8_t *bufend = bufptr + len; + uint8_t *encptr = encbuf; + /* processing tail if any */ + if (len + param->tailc < 3) { + memcpy(¶m->tail[param->tailc], buf, len); + param->tailc += len; + return RNP_SUCCESS; + } else if (param->tailc > 0) { + uint8_t dec3[3] = {0}; + memcpy(dec3, param->tail, param->tailc); + memcpy(&dec3[param->tailc], bufptr, 3 - param->tailc); + bufptr += 3 - param->tailc; + param->tailc = 0; + armored_encode3(encptr, dec3); + encptr += 4; + param->lout += 4; + if (param->lout == param->llen) { + armor_append_eol(param, encptr); + param->lout = 0; + } + } + + /* this version prints whole chunks, so rounding down to the closest 4 */ + auto adjusted_llen = param->llen & ~3; + /* number of input bytes to form a whole line of output, param->llen / 4 * 3 */ + auto inllen = (adjusted_llen >> 2) + (adjusted_llen >> 1); + /* pointer to the last full line space in encbuf */ + auto enclast = encbuf + sizeof(encbuf) - adjusted_llen - 2; + + /* processing line chunks, this is the main performance-hitting cycle */ + while (bufptr + 3 <= bufend) { + /* checking whether we have enough space in encbuf */ + if (encptr > enclast) { + dst_write(param->writedst, encbuf, encptr - encbuf); + encptr = encbuf; + } + /* setup length of the input to process in this iteration */ + uint8_t *inlend = + !param->lout ? bufptr + inllen : bufptr + ((adjusted_llen - param->lout) >> 2) * 3; + if (inlend > bufend) { + /* no enough input for the full line */ + inlend = bufptr + (bufend - bufptr) / 3 * 3; + param->lout += (inlend - bufptr) / 3 * 4; + } else { + /* we have full line of input */ + param->lout = 0; + } + + /* processing one line */ + while (bufptr < inlend) { + uint32_t t = (bufptr[0] << 16) | (bufptr[1] << 8) | (bufptr[2]); + bufptr += 3; + *encptr++ = B64ENC[(t >> 18) & 0xff]; + *encptr++ = B64ENC[(t >> 12) & 0xff]; + *encptr++ = B64ENC[(t >> 6) & 0xff]; + *encptr++ = B64ENC[t & 0xff]; + } + + /* adding line ending */ + if (!param->lout) { + armor_append_eol(param, encptr); + } + } + + dst_write(param->writedst, encbuf, encptr - encbuf); + + /* saving tail */ + param->tailc = bufend - bufptr; + memcpy(param->tail, bufptr, param->tailc); + + return RNP_SUCCESS; +} + +static rnp_result_t +armored_dst_finish(pgp_dest_t *dst) +{ + pgp_dest_armored_param_t *param = (pgp_dest_armored_param_t *) dst->param; + + /* writing tail */ + uint8_t buf[5]; + if (param->tailc == 1) { + buf[0] = B64ENC[param->tail[0] >> 2]; + buf[1] = B64ENC[(param->tail[0] << 4) & 0xff]; + buf[2] = CH_EQ; + buf[3] = CH_EQ; + dst_write(param->writedst, buf, 4); + } else if (param->tailc == 2) { + buf[0] = B64ENC[(param->tail[0] >> 2)]; + buf[1] = B64ENC[((param->tail[0] << 4) | (param->tail[1] >> 4)) & 0xff]; + buf[2] = B64ENC[(param->tail[1] << 2) & 0xff]; + buf[3] = CH_EQ; + dst_write(param->writedst, buf, 4); + } + /* Check for base64 */ + if (param->type == PGP_ARMORED_BASE64) { + return param->writedst->werr; + } + + /* writing EOL if needed */ + if ((param->tailc > 0) || (param->lout > 0)) { + armor_write_eol(param); + } + + /* writing CRC and EOL */ + // At this point crc_ctx is initialized, so call can't fail + buf[0] = CH_EQ; + try { + auto crc = param->crc_ctx->finish(); + armored_encode3(&buf[1], crc.data()); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + } + dst_write(param->writedst, buf, 5); + armor_write_eol(param); + + /* writing armor header */ + if (!armor_write_message_header(param, true)) { + return RNP_ERROR_BAD_PARAMETERS; + } + armor_write_eol(param); + return param->writedst->werr; +} + +static void +armored_dst_close(pgp_dest_t *dst, bool discard) +{ + pgp_dest_armored_param_t *param = (pgp_dest_armored_param_t *) dst->param; + + if (!param) { + return; + } + /* dst_close may be called without dst_finish on error */ + delete param; + dst->param = NULL; +} + +rnp_result_t +init_armored_dst(pgp_dest_t *dst, pgp_dest_t *writedst, pgp_armored_msg_t msgtype) +{ + if (!init_dst_common(dst, 0)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + pgp_dest_armored_param_t *param = new (std::nothrow) pgp_dest_armored_param_t(); + if (!param) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + dst->param = param; + dst->write = armored_dst_write; + dst->finish = armored_dst_finish; + dst->close = armored_dst_close; + dst->type = PGP_STREAM_ARMORED; + dst->writeb = 0; + dst->clen = 0; + + param->writedst = writedst; + param->type = msgtype; + /* Base64 message */ + if (msgtype == PGP_ARMORED_BASE64) { + /* Base64 encoding will not output EOLs but we need this to not duplicate code for a + * separate base64_dst_write function */ + param->eol[0] = 0; + param->eol[1] = 0; + param->llen = 256; + return RNP_SUCCESS; + } + /* create crc context */ + param->crc_ctx = rnp::CRC24::create(); + param->eol[0] = CH_CR; + param->eol[1] = CH_LF; + param->llen = 76; /* must be multiple of 4 */ + /* armor header */ + if (!armor_write_message_header(param, false)) { + RNP_LOG("unknown data type"); + armored_dst_close(dst, true); + return RNP_ERROR_BAD_PARAMETERS; + } + armor_write_eol(param); + /* empty line */ + armor_write_eol(param); + return RNP_SUCCESS; +} + +bool +is_armored_dest(pgp_dest_t *dst) +{ + return dst->type == PGP_STREAM_ARMORED; +} + +rnp_result_t +armored_dst_set_line_length(pgp_dest_t *dst, size_t llen) +{ + if (!dst || (llen < ARMORED_MIN_LINE_LENGTH) || (llen > ARMORED_MAX_LINE_LENGTH) || + !dst->param || !is_armored_dest(dst)) { + return RNP_ERROR_BAD_PARAMETERS; + } + auto param = (pgp_dest_armored_param_t *) dst->param; + param->llen = llen; + return RNP_SUCCESS; +} + +bool +is_armored_source(pgp_source_t *src) +{ + uint8_t buf[ARMORED_PEEK_BUF_SIZE]; + size_t read = 0; + + if (!src_peek(src, buf, sizeof(buf), &read) || (read < strlen(ST_ARMOR_BEGIN) + 1)) { + return false; + } + buf[read - 1] = 0; + return !!strstr((char *) buf, ST_ARMOR_BEGIN); +} + +bool +is_cleartext_source(pgp_source_t *src) +{ + uint8_t buf[ARMORED_PEEK_BUF_SIZE]; + size_t read = 0; + + if (!src_peek(src, buf, sizeof(buf), &read) || (read < strlen(ST_CLEAR_BEGIN))) { + return false; + } + buf[read - 1] = 0; + return !!strstr((char *) buf, ST_CLEAR_BEGIN); +} + +bool +is_base64_source(pgp_source_t &src) +{ + char buf[128]; + size_t read = 0; + + if (!src_peek(&src, buf, sizeof(buf), &read) || (read < 4)) { + return false; + } + return is_base64_line(buf, read); +} + +rnp_result_t +rnp_dearmor_source(pgp_source_t *src, pgp_dest_t *dst) +{ + rnp_result_t res = RNP_ERROR_BAD_FORMAT; + pgp_source_t armorsrc = {0}; + + /* initializing armored message */ + res = init_armored_src(&armorsrc, src); + if (res) { + return res; + } + /* Reading data from armored source and writing it to the output */ + res = dst_write_src(&armorsrc, dst); + if (res) { + RNP_LOG("dearmoring failed"); + } + + src_close(&armorsrc); + return res; +} + +rnp_result_t +rnp_armor_source(pgp_source_t *src, pgp_dest_t *dst, pgp_armored_msg_t msgtype) +{ + pgp_dest_t armordst = {0}; + rnp_result_t res = init_armored_dst(&armordst, dst, msgtype); + if (res) { + return res; + } + + res = dst_write_src(src, &armordst); + if (res) { + RNP_LOG("armoring failed"); + } + + dst_close(&armordst, res != RNP_SUCCESS); + return res; +} + +namespace rnp { + +const uint32_t ArmoredSource::AllowBinary = 0x01; +const uint32_t ArmoredSource::AllowBase64 = 0x02; +const uint32_t ArmoredSource::AllowMultiple = 0x04; + +ArmoredSource::ArmoredSource(pgp_source_t &readsrc, uint32_t flags) + : Source(), readsrc_(readsrc), multiple_(false) +{ + /* Do not dearmor already armored stream */ + bool already = readsrc_.type == PGP_STREAM_ARMORED; + /* Check for base64 source: no multiple streams allowed */ + if (!already && (flags & AllowBase64) && (is_base64_source(readsrc))) { + auto res = init_armored_src(&src_, &readsrc_, true); + if (res) { + RNP_LOG("Failed to parse base64 data."); + throw rnp::rnp_exception(res); + } + armored_ = true; + return; + } + /* Check for armored source */ + if (!already && is_armored_source(&readsrc)) { + auto res = init_armored_src(&src_, &readsrc_); + if (res) { + RNP_LOG("Failed to parse armored data."); + throw rnp::rnp_exception(res); + } + armored_ = true; + multiple_ = flags & AllowMultiple; + return; + } + /* Use binary source if allowed */ + if (!(flags & AllowBinary)) { + RNP_LOG("Non-armored data is not allowed here."); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + armored_ = false; +} + +void +ArmoredSource::restart() +{ + if (!armored_ || src_eof(&readsrc_) || src_error(&readsrc_)) { + return; + } + src_close(&src_); + auto res = init_armored_src(&src_, &readsrc_); + if (res) { + throw rnp::rnp_exception(res); + } +} + +pgp_source_t & +ArmoredSource::src() +{ + return armored_ ? src_ : readsrc_; +} +} // namespace rnp diff --git a/comm/third_party/rnp/src/librepgp/stream-armor.h b/comm/third_party/rnp/src/librepgp/stream-armor.h new file mode 100644 index 0000000000..4c91fd20a5 --- /dev/null +++ b/comm/third_party/rnp/src/librepgp/stream-armor.h @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2017-2020, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef STREAM_ARMOUR_H_ +#define STREAM_ARMOUR_H_ + +#include "stream-common.h" + +typedef enum { + PGP_ARMORED_UNKNOWN, + PGP_ARMORED_MESSAGE, + PGP_ARMORED_PUBLIC_KEY, + PGP_ARMORED_SECRET_KEY, + PGP_ARMORED_SIGNATURE, + PGP_ARMORED_CLEARTEXT, + PGP_ARMORED_BASE64 +} pgp_armored_msg_t; + +/* @brief Init dearmoring stream + * @param src allocated pgp_source_t structure + * @param readsrc source to read data from + * @return RNP_SUCCESS on success or error code otherwise + **/ +rnp_result_t init_armored_src(pgp_source_t *src, + pgp_source_t *readsrc, + bool noheaders = false); + +/* @brief Init armoring stream + * @param dst allocated pgp_dest_t structure + * @param writedst destination to write armored data to + * @param msgtype type of the message (see pgp_armored_msg_t) + * @return RNP_SUCCESS on success or error code otherwise + **/ +rnp_result_t init_armored_dst(pgp_dest_t * dst, + pgp_dest_t * writedst, + pgp_armored_msg_t msgtype); + +/* @brief Dearmor the source, outputting binary data + * @param src initialized source with armored data + * @param dst initialized dest to write binary data to + * @return RNP_SUCCESS on success or error code otherwise + **/ +rnp_result_t rnp_dearmor_source(pgp_source_t *src, pgp_dest_t *dst); + +/* @brief Armor the source, outputting base64-encoded data with headers + * @param src initialized source with binary data + * @param dst destination to write armored data + * @msgtype type of the message, to write correct armor headers + * @return RNP_SUCCESS on success or error code otherwise + **/ +rnp_result_t rnp_armor_source(pgp_source_t *src, pgp_dest_t *dst, pgp_armored_msg_t msgtype); + +/* @brief Guess the corresponding armored message type by first byte(s) of PGP message + * @param src initialized source with binary PGP message data + * @return corresponding enum element or PGP_ARMORED_UNKNOWN + **/ +pgp_armored_msg_t rnp_armor_guess_type(pgp_source_t *src); + +/* @brief Get type of the armored message by peeking header. + * @param src initialized source with armored message data. + * @return corresponding enum element or PGP_ARMORED_UNKNOWN + **/ +pgp_armored_msg_t rnp_armored_get_type(pgp_source_t *src); + +/* @brief Check whether source could be an armored source + * @param src initialized source with some data + * @return true if source could be an armored data or false otherwise + **/ +bool is_armored_source(pgp_source_t *src); + +/* @brief Check whether destination is armored + * @param dest initialized destination + * @return true if destination is armored or false otherwise + **/ +bool is_armored_dest(pgp_dest_t *dst); + +/* @brief Check whether source is cleartext signed + * @param src initialized source with some data + * @return true if source could be a cleartext signed data or false otherwise + **/ +bool is_cleartext_source(pgp_source_t *src); + +/** @brief Check whether source is base64-encoded + * @param src initialized source with some data + * @return true if source could be a base64-encoded data or false otherwise + **/ +bool is_base64_source(pgp_source_t &src); + +/** Set line length for armoring + * + * @param dst initialized dest to write armored data to + * @param llen line length in characters + * @return RNP_SUCCESS on success, or any other value on error + */ +rnp_result_t armored_dst_set_line_length(pgp_dest_t *dst, size_t llen); + +namespace rnp { + +class ArmoredSource : public Source { + pgp_source_t &readsrc_; + bool armored_; + bool multiple_; + + public: + static const uint32_t AllowBinary; + static const uint32_t AllowBase64; + static const uint32_t AllowMultiple; + + ArmoredSource(const ArmoredSource &) = delete; + ArmoredSource(ArmoredSource &&) = delete; + + ArmoredSource(pgp_source_t &readsrc, uint32_t flags = 0); + + pgp_source_t &src(); + + bool + multiple() + { + return multiple_; + } + + /* Restart dearmoring in case of multiple armored messages in a single stream */ + void restart(); +}; + +class ArmoredDest : public Dest { + pgp_dest_t &writedst_; + + public: + ArmoredDest(const ArmoredDest &) = delete; + ArmoredDest(ArmoredDest &&) = delete; + + ArmoredDest(pgp_dest_t &writedst, pgp_armored_msg_t msgtype) : Dest(), writedst_(writedst) + { + auto ret = init_armored_dst(&dst_, &writedst_, msgtype); + if (ret) { + throw rnp::rnp_exception(ret); + } + }; + + ~ArmoredDest() + { + if (!discard_) { + dst_finish(&dst_); + } + } +}; + +} // namespace rnp + +#endif diff --git a/comm/third_party/rnp/src/librepgp/stream-common.cpp b/comm/third_party/rnp/src/librepgp/stream-common.cpp new file mode 100644 index 0000000000..334f93b527 --- /dev/null +++ b/comm/third_party/rnp/src/librepgp/stream-common.cpp @@ -0,0 +1,1212 @@ +/* + * Copyright (c) 2017-2020 [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include <stdlib.h> +#include <stdio.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#include <string.h> +#else +#include "uniwin.h" +#endif +#include <sys/stat.h> +#include <stdarg.h> +#include <errno.h> +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif +#ifdef HAVE_LIMITS_H +#include <limits.h> +#endif +#include <rnp/rnp_def.h> +#include "rnp.h" +#include "stream-common.h" +#include "types.h" +#include "file-utils.h" +#include "crypto/mem.h" +#include <algorithm> +#include <memory> + +bool +src_read(pgp_source_t *src, void *buf, size_t len, size_t *readres) +{ + size_t left = len; + size_t read; + pgp_source_cache_t *cache = src->cache; + bool readahead = cache ? cache->readahead : false; + + if (src->error) { + return false; + } + + if (src->eof || (len == 0)) { + *readres = 0; + return true; + } + + // Do not read more then available if source size is known + if (src->knownsize && (src->readb + len > src->size)) { + len = src->size - src->readb; + left = len; + readahead = false; + } + + // Check whether we have cache and there is data inside + if (cache && (cache->len > cache->pos)) { + read = cache->len - cache->pos; + if (read >= len) { + memcpy(buf, &cache->buf[cache->pos], len); + cache->pos += len; + goto finish; + } else { + memcpy(buf, &cache->buf[cache->pos], read); + cache->pos += read; + buf = (uint8_t *) buf + read; + left = len - read; + } + } + + // If we got here then we have empty cache or no cache at all + while (left > 0) { + if (left > sizeof(cache->buf) || !readahead || !cache) { + // If there is no cache or chunk is larger then read directly + if (!src->read(src, buf, left, &read)) { + src->error = 1; + return false; + } + if (!read) { + src->eof = 1; + len = len - left; + goto finish; + } + left -= read; + buf = (uint8_t *) buf + read; + } else { + // Try to fill the cache to avoid small reads + if (!src->read(src, &cache->buf[0], sizeof(cache->buf), &read)) { + src->error = 1; + return false; + } + if (!read) { + src->eof = 1; + len = len - left; + goto finish; + } else if (read < left) { + memcpy(buf, &cache->buf[0], read); + left -= read; + buf = (uint8_t *) buf + read; + } else { + memcpy(buf, &cache->buf[0], left); + cache->pos = left; + cache->len = read; + goto finish; + } + } + } + +finish: + src->readb += len; + if (src->knownsize && (src->readb == src->size)) { + src->eof = 1; + } + *readres = len; + return true; +} + +bool +src_read_eq(pgp_source_t *src, void *buf, size_t len) +{ + size_t res = 0; + return src_read(src, buf, len, &res) && (res == len); +} + +bool +src_peek(pgp_source_t *src, void *buf, size_t len, size_t *peeked) +{ + pgp_source_cache_t *cache = src->cache; + if (src->error) { + return false; + } + if (!cache || (len > sizeof(cache->buf))) { + return false; + } + if (src->eof) { + *peeked = 0; + return true; + } + + size_t read = 0; + bool readahead = cache->readahead; + // Do not read more then available if source size is known + if (src->knownsize && (src->readb + len > src->size)) { + len = src->size - src->readb; + readahead = false; + } + + if (cache->len - cache->pos >= len) { + if (buf) { + memcpy(buf, &cache->buf[cache->pos], len); + } + *peeked = len; + return true; + } + + if (cache->pos > 0) { + memmove(&cache->buf[0], &cache->buf[cache->pos], cache->len - cache->pos); + cache->len -= cache->pos; + cache->pos = 0; + } + + while (cache->len < len) { + read = readahead ? sizeof(cache->buf) - cache->len : len - cache->len; + if (src->knownsize && (src->readb + read > src->size)) { + read = src->size - src->readb; + } + if (!src->read(src, &cache->buf[cache->len], read, &read)) { + src->error = 1; + return false; + } + if (!read) { + if (buf) { + memcpy(buf, &cache->buf[0], cache->len); + } + *peeked = cache->len; + return true; + } + cache->len += read; + if (cache->len >= len) { + if (buf) { + memcpy(buf, cache->buf, len); + } + *peeked = len; + return true; + } + } + return false; +} + +bool +src_peek_eq(pgp_source_t *src, void *buf, size_t len) +{ + size_t res = 0; + return src_peek(src, buf, len, &res) && (res == len); +} + +void +src_skip(pgp_source_t *src, size_t len) +{ + if (src->cache && (src->cache->len - src->cache->pos >= len)) { + src->readb += len; + src->cache->pos += len; + return; + } + + size_t res = 0; + uint8_t sbuf[16]; + if (len < sizeof(sbuf)) { + (void) src_read(src, sbuf, len, &res); + return; + } + if (src_eof(src)) { + return; + } + + void *buf = calloc(1, std::min((size_t) PGP_INPUT_CACHE_SIZE, len)); + if (!buf) { + src->error = 1; + return; + } + + while (len && !src_eof(src)) { + if (!src_read(src, buf, std::min((size_t) PGP_INPUT_CACHE_SIZE, len), &res)) { + break; + } + len -= res; + } + free(buf); +} + +rnp_result_t +src_finish(pgp_source_t *src) +{ + rnp_result_t res = RNP_SUCCESS; + if (src->finish) { + res = src->finish(src); + } + + return res; +} + +bool +src_error(const pgp_source_t *src) +{ + return src->error; +} + +bool +src_eof(pgp_source_t *src) +{ + if (src->eof) { + return true; + } + /* Error on stream read is NOT considered as eof. See src_error(). */ + uint8_t check; + size_t read = 0; + return src_peek(src, &check, 1, &read) && (read == 0); +} + +void +src_close(pgp_source_t *src) +{ + if (src->close) { + src->close(src); + } + + if (src->cache) { + free(src->cache); + src->cache = NULL; + } +} + +bool +src_skip_eol(pgp_source_t *src) +{ + uint8_t eol[2]; + size_t read; + + if (!src_peek(src, eol, 2, &read) || !read) { + return false; + } + if (eol[0] == '\n') { + src_skip(src, 1); + return true; + } + if ((read == 2) && (eol[0] == '\r') && (eol[1] == '\n')) { + src_skip(src, 2); + return true; + } + return false; +} + +bool +src_peek_line(pgp_source_t *src, char *buf, size_t len, size_t *readres) +{ + size_t scan_pos = 0; + size_t inc = 64; + len = len - 1; + + do { + size_t to_peek = scan_pos + inc; + to_peek = to_peek > len ? len : to_peek; + inc = inc * 2; + + /* inefficient, each time we again read from the beginning */ + if (!src_peek(src, buf, to_peek, readres)) { + return false; + } + + /* we continue scanning where we stopped previously */ + for (; scan_pos < *readres; scan_pos++) { + if (buf[scan_pos] == '\n') { + if ((scan_pos > 0) && (buf[scan_pos - 1] == '\r')) { + scan_pos--; + } + buf[scan_pos] = '\0'; + *readres = scan_pos; + return true; + } + } + if (*readres < to_peek) { + return false; + } + } while (scan_pos < len); + return false; +} + +bool +init_src_common(pgp_source_t *src, size_t paramsize) +{ + memset(src, 0, sizeof(*src)); + src->cache = (pgp_source_cache_t *) calloc(1, sizeof(*src->cache)); + if (!src->cache) { + RNP_LOG("cache allocation failed"); + return false; + } + src->cache->readahead = true; + if (!paramsize) { + return true; + } + src->param = calloc(1, paramsize); + if (!src->param) { + RNP_LOG("param allocation failed"); + free(src->cache); + src->cache = NULL; + return false; + } + return true; +} + +typedef struct pgp_source_file_param_t { + int fd; +} pgp_source_file_param_t; + +static bool +file_src_read(pgp_source_t *src, void *buf, size_t len, size_t *readres) +{ + pgp_source_file_param_t *param = (pgp_source_file_param_t *) src->param; + if (!param) { + return false; + } + + int64_t rres = read(param->fd, buf, len); + if (rres < 0) { + return false; + } + *readres = rres; + return true; +} + +static void +file_src_close(pgp_source_t *src) +{ + pgp_source_file_param_t *param = (pgp_source_file_param_t *) src->param; + if (param) { + if (src->type == PGP_STREAM_FILE) { + close(param->fd); + } + free(src->param); + src->param = NULL; + } +} + +static rnp_result_t +init_fd_src(pgp_source_t *src, int fd, uint64_t *size) +{ + if (!init_src_common(src, sizeof(pgp_source_file_param_t))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + pgp_source_file_param_t *param = (pgp_source_file_param_t *) src->param; + param->fd = fd; + src->read = file_src_read; + src->close = file_src_close; + src->type = PGP_STREAM_FILE; + src->size = size ? *size : 0; + src->knownsize = !!size; + + return RNP_SUCCESS; +} + +rnp_result_t +init_file_src(pgp_source_t *src, const char *path) +{ + int fd; + struct stat st; + + if (rnp_stat(path, &st) != 0) { + RNP_LOG("can't stat '%s'", path); + return RNP_ERROR_READ; + } + + /* read call may succeed on directory depending on OS type */ + if (S_ISDIR(st.st_mode)) { + RNP_LOG("source is directory"); + return RNP_ERROR_BAD_PARAMETERS; + } + + int flags = O_RDONLY; +#ifdef HAVE_O_BINARY + flags |= O_BINARY; +#else +#ifdef HAVE__O_BINARY + flags |= _O_BINARY; +#endif +#endif + fd = rnp_open(path, flags, 0); + + if (fd < 0) { + RNP_LOG("can't open '%s'", path); + return RNP_ERROR_READ; + } + uint64_t size = st.st_size; + rnp_result_t ret = init_fd_src(src, fd, &size); + if (ret) { + close(fd); + } + return ret; +} + +rnp_result_t +init_stdin_src(pgp_source_t *src) +{ + pgp_source_file_param_t *param; + + if (!init_src_common(src, sizeof(pgp_source_file_param_t))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + param = (pgp_source_file_param_t *) src->param; + param->fd = 0; + src->read = file_src_read; + src->close = file_src_close; + src->type = PGP_STREAM_STDIN; + + return RNP_SUCCESS; +} + +typedef struct pgp_source_mem_param_t { + const void *memory; + bool free; + size_t len; + size_t pos; +} pgp_source_mem_param_t; + +typedef struct pgp_dest_mem_param_t { + unsigned maxalloc; + unsigned allocated; + void * memory; + bool free; + bool discard_overflow; + bool secure; +} pgp_dest_mem_param_t; + +static bool +mem_src_read(pgp_source_t *src, void *buf, size_t len, size_t *read) +{ + pgp_source_mem_param_t *param = (pgp_source_mem_param_t *) src->param; + if (!param) { + return false; + } + + if (len > param->len - param->pos) { + len = param->len - param->pos; + } + memcpy(buf, (uint8_t *) param->memory + param->pos, len); + param->pos += len; + *read = len; + return true; +} + +static void +mem_src_close(pgp_source_t *src) +{ + pgp_source_mem_param_t *param = (pgp_source_mem_param_t *) src->param; + if (param) { + if (param->free) { + free((void *) param->memory); + } + free(src->param); + src->param = NULL; + } +} + +rnp_result_t +init_mem_src(pgp_source_t *src, const void *mem, size_t len, bool free) +{ + if (!mem && len) { + return RNP_ERROR_NULL_POINTER; + } + /* this is actually double buffering, but then src_peek will fail */ + if (!init_src_common(src, sizeof(pgp_source_mem_param_t))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + pgp_source_mem_param_t *param = (pgp_source_mem_param_t *) src->param; + param->memory = mem; + param->len = len; + param->pos = 0; + param->free = free; + src->read = mem_src_read; + src->close = mem_src_close; + src->finish = NULL; + src->size = len; + src->knownsize = 1; + src->type = PGP_STREAM_MEMORY; + + return RNP_SUCCESS; +} + +static bool +null_src_read(pgp_source_t *src, void *buf, size_t len, size_t *read) +{ + return false; +} + +rnp_result_t +init_null_src(pgp_source_t *src) +{ + memset(src, 0, sizeof(*src)); + src->read = null_src_read; + src->type = PGP_STREAM_NULL; + src->error = true; + return RNP_SUCCESS; +} + +rnp_result_t +read_mem_src(pgp_source_t *src, pgp_source_t *readsrc) +{ + pgp_dest_t dst; + rnp_result_t ret; + + if ((ret = init_mem_dest(&dst, NULL, 0))) { + return ret; + } + + if ((ret = dst_write_src(readsrc, &dst))) { + goto done; + } + + if ((ret = init_mem_src(src, mem_dest_own_memory(&dst), dst.writeb, true))) { + goto done; + } + + ret = RNP_SUCCESS; +done: + dst_close(&dst, true); + return ret; +} + +rnp_result_t +file_to_mem_src(pgp_source_t *src, const char *filename) +{ + pgp_source_t fsrc = {}; + rnp_result_t res = RNP_ERROR_GENERIC; + + if ((res = init_file_src(&fsrc, filename))) { + return res; + } + + res = read_mem_src(src, &fsrc); + src_close(&fsrc); + + return res; +} + +const void * +mem_src_get_memory(pgp_source_t *src, bool own) +{ + if (src->type != PGP_STREAM_MEMORY) { + RNP_LOG("wrong function call"); + return NULL; + } + + if (!src->param) { + return NULL; + } + + pgp_source_mem_param_t *param = (pgp_source_mem_param_t *) src->param; + if (own) { + param->free = false; + } + return param->memory; +} + +bool +init_dst_common(pgp_dest_t *dst, size_t paramsize) +{ + memset(dst, 0, sizeof(*dst)); + dst->werr = RNP_SUCCESS; + if (!paramsize) { + return true; + } + /* allocate param */ + dst->param = calloc(1, paramsize); + if (!dst->param) { + RNP_LOG("allocation failed"); + } + return dst->param; +} + +void +dst_write(pgp_dest_t *dst, const void *buf, size_t len) +{ + /* we call write function only if all previous calls succeeded */ + if ((len > 0) && (dst->write) && (dst->werr == RNP_SUCCESS)) { + /* if cache non-empty and len will overflow it then fill it and write out */ + if ((dst->clen > 0) && (dst->clen + len > sizeof(dst->cache))) { + memcpy(dst->cache + dst->clen, buf, sizeof(dst->cache) - dst->clen); + buf = (uint8_t *) buf + sizeof(dst->cache) - dst->clen; + len -= sizeof(dst->cache) - dst->clen; + dst->werr = dst->write(dst, dst->cache, sizeof(dst->cache)); + dst->writeb += sizeof(dst->cache); + dst->clen = 0; + if (dst->werr != RNP_SUCCESS) { + return; + } + } + + /* here everything will fit into the cache or cache is empty */ + if (dst->no_cache || (len > sizeof(dst->cache))) { + dst->werr = dst->write(dst, buf, len); + if (!dst->werr) { + dst->writeb += len; + } + } else { + memcpy(dst->cache + dst->clen, buf, len); + dst->clen += len; + } + } +} + +void +dst_printf(pgp_dest_t *dst, const char *format, ...) +{ + char buf[2048]; + size_t len; + va_list ap; + + va_start(ap, format); + len = vsnprintf(buf, sizeof(buf), format, ap); + va_end(ap); + + if (len >= sizeof(buf)) { + RNP_LOG("too long dst_printf"); + len = sizeof(buf) - 1; + } + dst_write(dst, buf, len); +} + +void +dst_flush(pgp_dest_t *dst) +{ + if ((dst->clen > 0) && (dst->write) && (dst->werr == RNP_SUCCESS)) { + dst->werr = dst->write(dst, dst->cache, dst->clen); + dst->writeb += dst->clen; + dst->clen = 0; + } +} + +rnp_result_t +dst_finish(pgp_dest_t *dst) +{ + rnp_result_t res = RNP_SUCCESS; + + if (!dst->finished) { + /* flush write cache in the dst */ + dst_flush(dst); + if (dst->finish) { + res = dst->finish(dst); + } + dst->finished = true; + } + + return res; +} + +void +dst_close(pgp_dest_t *dst, bool discard) +{ + if (!discard && !dst->finished) { + dst_finish(dst); + } + + if (dst->close) { + dst->close(dst, discard); + } +} + +typedef struct pgp_dest_file_param_t { + int fd; + int errcode; + bool overwrite; + std::string path; +} pgp_dest_file_param_t; + +static rnp_result_t +file_dst_write(pgp_dest_t *dst, const void *buf, size_t len) +{ + pgp_dest_file_param_t *param = (pgp_dest_file_param_t *) dst->param; + + if (!param) { + RNP_LOG("wrong param"); + return RNP_ERROR_BAD_PARAMETERS; + } + + /* we assyme that blocking I/O is used so everything is written or error received */ + ssize_t ret = write(param->fd, buf, len); + if (ret < 0) { + param->errcode = errno; + RNP_LOG("write failed, error %d", param->errcode); + return RNP_ERROR_WRITE; + } else { + param->errcode = 0; + return RNP_SUCCESS; + } +} + +static void +file_dst_close(pgp_dest_t *dst, bool discard) +{ + pgp_dest_file_param_t *param = (pgp_dest_file_param_t *) dst->param; + if (!param) { + return; + } + + if (dst->type == PGP_STREAM_FILE) { + close(param->fd); + if (discard) { + rnp_unlink(param->path.c_str()); + } + } + + delete param; + dst->param = NULL; +} + +static rnp_result_t +init_fd_dest(pgp_dest_t *dst, int fd, const char *path) +{ + if (!init_dst_common(dst, 0)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + try { + std::unique_ptr<pgp_dest_file_param_t> param(new pgp_dest_file_param_t()); + param->path = path; + param->fd = fd; + dst->param = param.release(); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + + dst->write = file_dst_write; + dst->close = file_dst_close; + dst->type = PGP_STREAM_FILE; + return RNP_SUCCESS; +} + +rnp_result_t +init_file_dest(pgp_dest_t *dst, const char *path, bool overwrite) +{ + /* check whether file/dir already exists */ + struct stat st; + if (!rnp_stat(path, &st)) { + if (!overwrite) { + RNP_LOG("file already exists: '%s'", path); + return RNP_ERROR_WRITE; + } + + /* if we are overwriting empty directory then should first remove it */ + if (S_ISDIR(st.st_mode)) { + if (rmdir(path) == -1) { + RNP_LOG("failed to remove directory: error %d", errno); + return RNP_ERROR_BAD_PARAMETERS; + } + } + } + + int flags = O_WRONLY | O_CREAT; + flags |= overwrite ? O_TRUNC : O_EXCL; +#ifdef HAVE_O_BINARY + flags |= O_BINARY; +#else +#ifdef HAVE__O_BINARY + flags |= _O_BINARY; +#endif +#endif + int fd = rnp_open(path, flags, S_IRUSR | S_IWUSR); + if (fd < 0) { + RNP_LOG("failed to create file '%s'. Error %d.", path, errno); + return RNP_ERROR_WRITE; + } + + rnp_result_t res = init_fd_dest(dst, fd, path); + if (res) { + close(fd); + } + return res; +} + +#define TMPDST_SUFFIX ".rnp-tmp.XXXXXX" + +static rnp_result_t +file_tmpdst_finish(pgp_dest_t *dst) +{ + pgp_dest_file_param_t *param = (pgp_dest_file_param_t *) dst->param; + if (!param) { + return RNP_ERROR_BAD_PARAMETERS; + } + + /* close the file */ + close(param->fd); + param->fd = -1; + + /* rename the temporary file */ + if (param->path.size() < strlen(TMPDST_SUFFIX)) { + return RNP_ERROR_BAD_PARAMETERS; + } + try { + /* remove suffix so we have required path */ + std::string origpath(param->path.begin(), param->path.end() - strlen(TMPDST_SUFFIX)); + /* check if file already exists */ + struct stat st; + if (!rnp_stat(origpath.c_str(), &st)) { + if (!param->overwrite) { + RNP_LOG("target path already exists"); + return RNP_ERROR_BAD_STATE; + } +#ifdef _WIN32 + /* rename() call on Windows fails if destination exists */ + else { + rnp_unlink(origpath.c_str()); + } +#endif + + /* we should remove dir if overwriting, file will be unlinked in rename call */ + if (S_ISDIR(st.st_mode) && rmdir(origpath.c_str())) { + RNP_LOG("failed to remove directory"); + return RNP_ERROR_BAD_STATE; + } + } + + if (rnp_rename(param->path.c_str(), origpath.c_str())) { + RNP_LOG("failed to rename temporary path to target file: %s", strerror(errno)); + return RNP_ERROR_BAD_STATE; + } + return RNP_SUCCESS; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_BAD_STATE; + } +} + +static void +file_tmpdst_close(pgp_dest_t *dst, bool discard) +{ + pgp_dest_file_param_t *param = (pgp_dest_file_param_t *) dst->param; + if (!param) { + return; + } + + /* we close file in finish function, except the case when some error occurred */ + if (!dst->finished && (dst->type == PGP_STREAM_FILE)) { + close(param->fd); + if (discard) { + rnp_unlink(param->path.c_str()); + } + } + + delete param; + dst->param = NULL; +} + +rnp_result_t +init_tmpfile_dest(pgp_dest_t *dst, const char *path, bool overwrite) +{ + try { + std::string tmp = std::string(path) + std::string(TMPDST_SUFFIX); + /* make sure tmp.data() is zero-terminated */ + tmp.push_back('\0'); +#if defined(HAVE_MKSTEMP) && !defined(_WIN32) + int fd = mkstemp(&tmp[0]); +#else + int fd = rnp_mkstemp(&tmp[0]); +#endif + if (fd < 0) { + RNP_LOG("failed to create temporary file with template '%s'. Error %d.", + tmp.c_str(), + errno); + return RNP_ERROR_WRITE; + } + rnp_result_t res = init_fd_dest(dst, fd, tmp.c_str()); + if (res) { + close(fd); + return res; + } + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_BAD_STATE; + } + + /* now let's change some parameters to handle temporary file correctly */ + pgp_dest_file_param_t *param = (pgp_dest_file_param_t *) dst->param; + param->overwrite = overwrite; + dst->finish = file_tmpdst_finish; + dst->close = file_tmpdst_close; + return RNP_SUCCESS; +} + +rnp_result_t +init_stdout_dest(pgp_dest_t *dst) +{ + rnp_result_t res = init_fd_dest(dst, STDOUT_FILENO, ""); + if (res) { + return res; + } + dst->type = PGP_STREAM_STDOUT; + return RNP_SUCCESS; +} + +static rnp_result_t +mem_dst_write(pgp_dest_t *dst, const void *buf, size_t len) +{ + pgp_dest_mem_param_t *param = (pgp_dest_mem_param_t *) dst->param; + if (!param) { + return RNP_ERROR_BAD_PARAMETERS; + } + + /* checking whether we need to realloc or discard extra bytes */ + if (param->discard_overflow && (dst->writeb >= param->allocated)) { + return RNP_SUCCESS; + } + if (param->discard_overflow && (dst->writeb + len > param->allocated)) { + len = param->allocated - dst->writeb; + } + + if (dst->writeb + len > param->allocated) { + if ((param->maxalloc > 0) && (dst->writeb + len > param->maxalloc)) { + RNP_LOG("attempt to alloc more then allowed"); + return RNP_ERROR_OUT_OF_MEMORY; + } + + /* round up to the page boundary and do it exponentially */ + size_t alloc = ((dst->writeb + len) * 2 + 4095) / 4096 * 4096; + if ((param->maxalloc > 0) && (alloc > param->maxalloc)) { + alloc = param->maxalloc; + } + + void *newalloc = param->secure ? calloc(1, alloc) : realloc(param->memory, alloc); + if (!newalloc) { + return RNP_ERROR_OUT_OF_MEMORY; + } + if (param->secure && param->memory) { + memcpy(newalloc, param->memory, dst->writeb); + secure_clear(param->memory, dst->writeb); + free(param->memory); + } + param->memory = newalloc; + param->allocated = alloc; + } + + memcpy((uint8_t *) param->memory + dst->writeb, buf, len); + return RNP_SUCCESS; +} + +static void +mem_dst_close(pgp_dest_t *dst, bool discard) +{ + pgp_dest_mem_param_t *param = (pgp_dest_mem_param_t *) dst->param; + if (!param) { + return; + } + + if (param->free) { + if (param->secure) { + secure_clear(param->memory, param->allocated); + } + free(param->memory); + } + free(param); + dst->param = NULL; +} + +rnp_result_t +init_mem_dest(pgp_dest_t *dst, void *mem, unsigned len) +{ + pgp_dest_mem_param_t *param; + + if (!init_dst_common(dst, sizeof(*param))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + param = (pgp_dest_mem_param_t *) dst->param; + + param->maxalloc = len; + param->allocated = mem ? len : 0; + param->memory = mem; + param->free = !mem; + param->secure = false; + + dst->write = mem_dst_write; + dst->close = mem_dst_close; + dst->type = PGP_STREAM_MEMORY; + dst->werr = RNP_SUCCESS; + dst->no_cache = true; + + return RNP_SUCCESS; +} + +void +mem_dest_discard_overflow(pgp_dest_t *dst, bool discard) +{ + if (dst->type != PGP_STREAM_MEMORY) { + RNP_LOG("wrong function call"); + return; + } + + pgp_dest_mem_param_t *param = (pgp_dest_mem_param_t *) dst->param; + if (param) { + param->discard_overflow = discard; + } +} + +void * +mem_dest_get_memory(pgp_dest_t *dst) +{ + if (dst->type != PGP_STREAM_MEMORY) { + RNP_LOG("wrong function call"); + return NULL; + } + + pgp_dest_mem_param_t *param = (pgp_dest_mem_param_t *) dst->param; + + if (param) { + return param->memory; + } + + return NULL; +} + +void * +mem_dest_own_memory(pgp_dest_t *dst) +{ + if (dst->type != PGP_STREAM_MEMORY) { + RNP_LOG("wrong function call"); + return NULL; + } + + pgp_dest_mem_param_t *param = (pgp_dest_mem_param_t *) dst->param; + + if (!param) { + RNP_LOG("null param"); + return NULL; + } + + dst_finish(dst); + + if (param->free) { + if (!dst->writeb) { + free(param->memory); + param->memory = NULL; + return param->memory; + } + /* it may be larger then required - let's truncate */ + void *newalloc = realloc(param->memory, dst->writeb); + if (!newalloc) { + return NULL; + } + param->memory = newalloc; + param->allocated = dst->writeb; + param->free = false; + return param->memory; + } + + /* in this case we should copy the memory */ + void *res = malloc(dst->writeb); + if (res) { + memcpy(res, param->memory, dst->writeb); + } + return res; +} + +void +mem_dest_secure_memory(pgp_dest_t *dst, bool secure) +{ + if (!dst || (dst->type != PGP_STREAM_MEMORY)) { + RNP_LOG("wrong function call"); + return; + } + pgp_dest_mem_param_t *param = (pgp_dest_mem_param_t *) dst->param; + if (param) { + param->secure = secure; + } +} + +static rnp_result_t +null_dst_write(pgp_dest_t *dst, const void *buf, size_t len) +{ + return RNP_SUCCESS; +} + +static void +null_dst_close(pgp_dest_t *dst, bool discard) +{ + ; +} + +rnp_result_t +init_null_dest(pgp_dest_t *dst) +{ + dst->param = NULL; + dst->write = null_dst_write; + dst->close = null_dst_close; + dst->type = PGP_STREAM_NULL; + dst->writeb = 0; + dst->clen = 0; + dst->werr = RNP_SUCCESS; + dst->no_cache = true; + + return RNP_SUCCESS; +} + +rnp_result_t +dst_write_src(pgp_source_t *src, pgp_dest_t *dst, uint64_t limit) +{ + const size_t bufsize = PGP_INPUT_CACHE_SIZE; + uint8_t * readbuf = (uint8_t *) malloc(bufsize); + if (!readbuf) { + return RNP_ERROR_OUT_OF_MEMORY; + } + rnp_result_t res = RNP_SUCCESS; + try { + size_t read; + uint64_t totalread = 0; + + while (!src->eof) { + if (!src_read(src, readbuf, bufsize, &read)) { + res = RNP_ERROR_GENERIC; + break; + } + if (!read) { + continue; + } + totalread += read; + if (limit && totalread > limit) { + res = RNP_ERROR_GENERIC; + break; + } + if (dst) { + dst_write(dst, readbuf, read); + if (dst->werr) { + RNP_LOG("failed to output data"); + res = RNP_ERROR_WRITE; + break; + } + } + } + } catch (...) { + free(readbuf); + throw; + } + free(readbuf); + if (res || !dst) { + return res; + } + dst_flush(dst); + return dst->werr; +} diff --git a/comm/third_party/rnp/src/librepgp/stream-common.h b/comm/third_party/rnp/src/librepgp/stream-common.h new file mode 100644 index 0000000000..02279d3bae --- /dev/null +++ b/comm/third_party/rnp/src/librepgp/stream-common.h @@ -0,0 +1,556 @@ +/* + * Copyright (c) 2017-2020, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef STREAM_COMMON_H_ +#define STREAM_COMMON_H_ + +#include <stdint.h> +#include <stdbool.h> +#include <sys/types.h> +#include "types.h" + +#define PGP_INPUT_CACHE_SIZE 32768 +#define PGP_OUTPUT_CACHE_SIZE 32768 + +#define PGP_PARTIAL_PKT_FIRST_PART_MIN_SIZE 512 + +typedef enum { + PGP_STREAM_NULL, + PGP_STREAM_FILE, + PGP_STREAM_MEMORY, + PGP_STREAM_STDIN, + PGP_STREAM_STDOUT, + PGP_STREAM_PACKET, + PGP_STREAM_PARLEN_PACKET, + PGP_STREAM_LITERAL, + PGP_STREAM_COMPRESSED, + PGP_STREAM_ENCRYPTED, + PGP_STREAM_SIGNED, + PGP_STREAM_ARMORED, + PGP_STREAM_CLEARTEXT +} pgp_stream_type_t; + +typedef struct pgp_source_t pgp_source_t; +typedef struct pgp_dest_t pgp_dest_t; + +typedef bool pgp_source_read_func_t(pgp_source_t *src, void *buf, size_t len, size_t *read); +typedef rnp_result_t pgp_source_finish_func_t(pgp_source_t *src); +typedef void pgp_source_close_func_t(pgp_source_t *src); + +typedef rnp_result_t pgp_dest_write_func_t(pgp_dest_t *dst, const void *buf, size_t len); +typedef rnp_result_t pgp_dest_finish_func_t(pgp_dest_t *src); +typedef void pgp_dest_close_func_t(pgp_dest_t *dst, bool discard); + +/* statically preallocated cache for sources */ +typedef struct pgp_source_cache_t { + uint8_t buf[PGP_INPUT_CACHE_SIZE]; + unsigned pos; /* current position in cache */ + unsigned len; /* number of bytes available in cache */ + bool readahead; /* whether read-ahead with larger chunks allowed */ +} pgp_source_cache_t; + +typedef struct pgp_source_t { + pgp_source_read_func_t * read; + pgp_source_finish_func_t *finish; + pgp_source_close_func_t * close; + pgp_stream_type_t type; + + uint64_t size; /* size of the data if available, see knownsize */ + uint64_t readb; /* number of bytes read from the stream via src_read. Do not confuse with + number of bytes as returned via the read since data may be cached */ + pgp_source_cache_t *cache; /* cache if used */ + void * param; /* source-specific additional data */ + + unsigned eof : 1; /* end of data as reported by read and empty cache */ + unsigned knownsize : 1; /* whether size of the data is known */ + unsigned error : 1; /* there were reading error */ +} pgp_source_t; + +/** @brief helper function to allocate memory for source's cache and param + * Also fills src and param with zeroes + * @param src pointer to the source structure + * @param paramsize number of bytes required for src->param + * @return true on success or false if memory allocation failed. + **/ +bool init_src_common(pgp_source_t *src, size_t paramsize); + +/** @brief read up to len bytes from the source + * While this function tries to read as much bytes as possible however it may return + * less then len bytes. Then src->eof can be checked if it's end of data. + * + * @param src source structure + * @param buf preallocated buffer which can store up to len bytes + * @param len number of bytes to read + * @param read number of read bytes will be stored here. Cannot be NULL. + * @return true on success or false otherwise + **/ +bool src_read(pgp_source_t *src, void *buf, size_t len, size_t *read); + +/** @brief shortcut to read exactly len bytes from source. See src_read for parameters. + * @return true if len bytes were read or false otherwise (i.e. less then len were read or + * read error occurred) */ +bool src_read_eq(pgp_source_t *src, void *buf, size_t len); + +/** @brief read up to len bytes and keep them in the cache/do not process + * Works only for streams with cache + * @param src source structure + * @param buf preallocated buffer which can store up to len bytes, or NULL if data should be + * discarded, just making sure that needed input is available in source + * @param len number of bytes to read. Must be less then PGP_INPUT_CACHE_SIZE. + * @param read number of bytes read will be stored here. Cannot be NULL. + * @return true on success or false otherwise + **/ +bool src_peek(pgp_source_t *src, void *buf, size_t len, size_t *read); + +/** @brief shortcut to read exactly len bytes and keep them in the cache/do not process + * Works only for streams with cache + * @return true if len bytes were read or false otherwise (i.e. less then len were read or + * read error occurred) */ +bool src_peek_eq(pgp_source_t *src, void *buf, size_t len); + +/** @brief skip up to len bytes. + * Note: use src_read() if you want to check error condition/get number of bytes + *skipped. + * @param src source structure + * @param len number of bytes to skip + **/ +void src_skip(pgp_source_t *src, size_t len); + +/** @brief notify source that all reading is done, so final data processing may be started, + * i.e. signature reading and verification and so on. Do not misuse with src_close. + * @param src allocated and initialized source structure + * @return RNP_SUCCESS or error code. If source doesn't have finish handler then also + * RNP_SUCCESS is returned + */ +rnp_result_t src_finish(pgp_source_t *src); + +/** @brief check whether there were reading error on source + * @param allocated and initialized source structure + * @return true if there were reading error or false otherwise + */ +bool src_error(const pgp_source_t *src); + +/** @brief check whether there is no more input on source + * @param src allocated and initialized source structure + * @return true if there is no more input or false otherwise. + * On read error false will be returned. + */ +bool src_eof(pgp_source_t *src); + +/** @brief close the source and deallocate all internal resources if any + */ +void src_close(pgp_source_t *src); + +/** @brief skip end of line on the source (\r\n or \n, depending on input) + * @param src allocated and initialized source + * @return true if eol was found and skipped or false otherwise + */ +bool src_skip_eol(pgp_source_t *src); + +/** @brief peek the line on the source + * @param src allocated and initialized source with data + * @param buf preallocated buffer to store the result. Result include NULL character and + * doesn't include the end of line sequence. + * @param len maximum length of data to store in buf, including terminating NULL + * @param read on success here will be stored number of bytes in the string, without the NULL + * character. + * @return true on success + * false is returned if there were eof, read error or eol was not found within the + * len. Supported eol sequences are \r\n and \n + */ +bool src_peek_line(pgp_source_t *src, char *buf, size_t len, size_t *read); + +/** @brief init file source + * @param src pre-allocated source structure + * @param path path to the file + * @return RNP_SUCCESS or error code + **/ +rnp_result_t init_file_src(pgp_source_t *src, const char *path); + +/** @brief init stdin source + * @param src pre-allocated source structure + * @return RNP_SUCCESS or error code + **/ +rnp_result_t init_stdin_src(pgp_source_t *src); + +/** @brief init memory source + * @param src pre-allocated source structure + * @param mem memory to read from + * @param len number of bytes in input + * @param free free the memory pointer on stream close or not + * @return RNP_SUCCESS or error code + **/ +rnp_result_t init_mem_src(pgp_source_t *src, const void *mem, size_t len, bool free); + +/** @brief init NULL source, which doesn't allow to read anything and always returns an error. + * @param src pre-allocated source structure + * @return always RNP_SUCCESS + **/ +rnp_result_t init_null_src(pgp_source_t *src); + +/** @brief init memory source with contents of other source + * @param src pre-allocated source structure + * @param readsrc opened source with data + * @return RNP_SUCCESS or error code + **/ +rnp_result_t read_mem_src(pgp_source_t *src, pgp_source_t *readsrc); + +/** @brief init memory source with contents of the specified file + * @param src pre-allocated source structure + * @param filename name of the file + * @return RNP_SUCCESS or error code + **/ +rnp_result_t file_to_mem_src(pgp_source_t *src, const char *filename); + +/** @brief get memory from the memory source + * @param src initialized memory source + * @param own transfer ownership of the memory + * @return pointer to the memory or NULL if it is not a memory source + **/ +const void *mem_src_get_memory(pgp_source_t *src, bool own = false); + +typedef struct pgp_dest_t { + pgp_dest_write_func_t * write; + pgp_dest_finish_func_t *finish; + pgp_dest_close_func_t * close; + pgp_stream_type_t type; + rnp_result_t werr; /* write function may set this to some error code */ + + size_t writeb; /* number of bytes written */ + void * param; /* source-specific additional data */ + bool no_cache; /* disable write caching */ + uint8_t cache[PGP_OUTPUT_CACHE_SIZE]; + unsigned clen; /* number of bytes in cache */ + bool finished; /* whether dst_finish was called on dest or not */ +} pgp_dest_t; + +/** @brief helper function to allocate memory for dest's param. + * Initializes dst and param with zeroes as well. + * @param dst dest structure + * @param paramsize number of bytes required for dst->param + * @return true on success, or false if memory allocation failed + **/ +bool init_dst_common(pgp_dest_t *dst, size_t paramsize); + +/** @brief write buffer to the destination + * + * @param dst destination structure + * @param buf buffer with data + * @param len number of bytes to write + * @return true on success or false otherwise + **/ +void dst_write(pgp_dest_t *dst, const void *buf, size_t len); + +/** @brief printf formatted string to the destination + * + * @param dst destination structure + * @param format format string, which is the same as printf() uses + * @param ... additional arguments + */ +void dst_printf(pgp_dest_t *dst, const char *format, ...); + +/** @brief do all finalization tasks after all writing is done, i.e. calculate and write + * mdc, signatures and so on. Do not misuse with dst_close. If was not called then will be + * called from the dst_close + * + * @param dst destination structure + * @return RNP_SUCCESS or error code if something went wrong + **/ +rnp_result_t dst_finish(pgp_dest_t *dst); + +/** @brief close the destination + * + * @param dst destination structure to be closed + * @param discard if this is true then all produced output should be discarded + * @return void + **/ +void dst_close(pgp_dest_t *dst, bool discard); + +/** @brief flush cached data if any. dst_write caches small writes, so data does not + * immediately go to stream write function. + * + * @param dst destination structure + * @return void + **/ +void dst_flush(pgp_dest_t *dst); + +/** @brief init file destination + * @param dst pre-allocated dest structure + * @param path path to the file + * @param overwrite overwrite existing file + * @return RNP_SUCCESS or error code + **/ +rnp_result_t init_file_dest(pgp_dest_t *dst, const char *path, bool overwrite); + +/** @brief init file destination, using the temporary file name, based on path. + * Once writing is over, dst_finish() will attempt to rename to the desired name. + * @param dst pre-allocated dest structure + * @param path path to the file + * @param overwrite overwrite existing file on rename + * @return RNP_SUCCESS or error code + **/ +rnp_result_t init_tmpfile_dest(pgp_dest_t *dst, const char *path, bool overwrite); + +/** @brief init stdout destination + * @param dst pre-allocated dest structure + * @return RNP_SUCCESS or error code + **/ +rnp_result_t init_stdout_dest(pgp_dest_t *dst); + +/** @brief init memory destination + * @param dst pre-allocated dest structure + * @param mem pointer to the pre-allocated memory buffer, or NULL if it should be allocated + * @param len number of bytes which mem can keep, or maximum amount of memory to allocate if + * mem is NULL. If len is zero in later case then allocation is not limited. + * @return RNP_SUCCESS or error code + **/ +rnp_result_t init_mem_dest(pgp_dest_t *dst, void *mem, unsigned len); + +/** @brief set whether to silently discard bytes which overflow memory of the dst. + * @param dst pre-allocated and initialized memory dest + * @param discard true to discard or false to return an error on overflow. + **/ +void mem_dest_discard_overflow(pgp_dest_t *dst, bool discard); + +/** @brief get the pointer to the memory where data is written. + * Do not retain the result, it may change between calls due to realloc + * @param dst pre-allocated and initialized memory dest + * @return pointer to the memory area or NULL if memory was not allocated + **/ +void *mem_dest_get_memory(pgp_dest_t *dst); + +/** @brief get ownership on the memory dest's contents. This must be called only before + * closing the dest + * @param dst pre-allocated and initialized memory dest + * @return pointer to the memory area or NULL if memory was not allocated (i.e. nothing was + * written to the destination). Also NULL will be returned on possible (re-)allocation + * failure, this case can be identified by non-zero dst->writeb. + **/ +void *mem_dest_own_memory(pgp_dest_t *dst); + +/** @brief mark memory dest as secure, so it will be deallocated securely + * @param dst pre-allocated and initialized memory dest + * @param secure whether memory should be considered as secure or not + * @return void + **/ +void mem_dest_secure_memory(pgp_dest_t *dst, bool secure); + +/** @brief init null destination which silently discards all the output + * @param dst pre-allocated dest structure + * @return RNP_SUCCESS or error code + **/ +rnp_result_t init_null_dest(pgp_dest_t *dst); + +/** @brief reads from source and writes to destination + * @param src initialized source + * @param dst initialized destination + * @param limit sets the maximum amount of bytes to be read, + * returning an error if the source hasn't come to eof after that amount + * if 0, no limit is imposed + * @return RNP_SUCCESS or error code + **/ +rnp_result_t dst_write_src(pgp_source_t *src, pgp_dest_t *dst, uint64_t limit = 0); + +namespace rnp { +/* Temporary wrapper to destruct stack-based pgp_source_t */ +class Source { + protected: + pgp_source_t src_; + + public: + Source(const Source &) = delete; + Source(Source &&) = delete; + + Source() : src_({}) + { + } + + virtual ~Source() + { + src_close(&src_); + } + + virtual pgp_source_t & + src() + { + return src_; + } + + size_t + size() + { + return src().size; + } + + size_t + readb() + { + return src().readb; + } + + bool + eof() + { + return src_eof(&src()); + } + + bool + error() + { + return src_error(&src()); + } +}; + +class MemorySource : public Source { + public: + MemorySource(const MemorySource &) = delete; + MemorySource(MemorySource &&) = delete; + + /** + * @brief Construct memory source object. + * + * @param mem source memory. Must be valid for the whole lifetime of the object. + * @param len size of the memory. + * @param free free memory once processing is finished. + */ + MemorySource(const void *mem, size_t len, bool free) : Source() + { + auto res = init_mem_src(&src_, mem, len, free); + if (res) { + throw std::bad_alloc(); + } + } + + /** + * @brief Construct memory source object + * + * @param vec vector with data. Must be valid for the whole lifetime of the object. + */ + MemorySource(const std::vector<uint8_t> &vec) : MemorySource(vec.data(), vec.size(), false) + { + } + + MemorySource(pgp_source_t &src) : Source() + { + auto res = read_mem_src(&src_, &src); + if (res) { + throw rnp::rnp_exception(res); + } + } + + const void * + memory(bool own = false) + { + return mem_src_get_memory(&src_, own); + } +}; + +/* Temporary wrapper to destruct stack-based pgp_dest_t */ +class Dest { + protected: + pgp_dest_t dst_; + bool discard_; + + public: + Dest(const Dest &) = delete; + Dest(Dest &&) = delete; + + Dest() : dst_({}), discard_(false) + { + } + + virtual ~Dest() + { + dst_close(&dst_, discard_); + } + + void + write(const void *buf, size_t len) + { + dst_write(&dst_, buf, len); + } + + void + set_discard(bool discard) + { + discard_ = discard; + } + + pgp_dest_t & + dst() + { + return dst_; + } + + size_t + writeb() + { + return dst_.writeb; + } + + rnp_result_t + werr() + { + return dst_.werr; + } +}; + +class MemoryDest : public Dest { + public: + MemoryDest(const MemoryDest &) = delete; + MemoryDest(MemoryDest &&) = delete; + + MemoryDest(void *mem = NULL, size_t len = 0) : Dest() + { + auto res = init_mem_dest(&dst_, mem, len); + if (res) { + throw std::bad_alloc(); + } + discard_ = true; + } + + void * + memory() + { + return mem_dest_get_memory(&dst_); + } + + void + set_secure(bool secure) + { + mem_dest_secure_memory(&dst_, secure); + } + + std::vector<uint8_t> + to_vector() + { + uint8_t *mem = (uint8_t *) memory(); + return std::vector<uint8_t>(mem, mem + writeb()); + } +}; +} // namespace rnp + +#endif diff --git a/comm/third_party/rnp/src/librepgp/stream-ctx.cpp b/comm/third_party/rnp/src/librepgp/stream-ctx.cpp new file mode 100644 index 0000000000..28b5444f45 --- /dev/null +++ b/comm/third_party/rnp/src/librepgp/stream-ctx.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2019-2020, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <string.h> +#include <assert.h> +#include "defaults.h" +#include "utils.h" +#include "stream-ctx.h" + +rnp_result_t +rnp_ctx_t::add_encryption_password(const std::string &password, + pgp_hash_alg_t halg, + pgp_symm_alg_t ealg, + size_t iterations) +{ + rnp_symmetric_pass_info_t info = {}; + + info.s2k.usage = PGP_S2KU_ENCRYPTED_AND_HASHED; + info.s2k.specifier = PGP_S2KS_ITERATED_AND_SALTED; + info.s2k.hash_alg = halg; + ctx->rng.get(info.s2k.salt, sizeof(info.s2k.salt)); + if (!iterations) { + iterations = ctx->s2k_iterations(halg); + } + if (!iterations) { + return RNP_ERROR_BAD_PARAMETERS; + } + info.s2k.iterations = pgp_s2k_encode_iterations(iterations); + info.s2k_cipher = ealg; + /* Note: we're relying on the fact that a longer-than-needed key length + * here does not change the entire derived key (it just generates unused + * extra bytes at the end). We derive a key of our maximum supported length, + * which is a bit wasteful. + * + * This is done because we do not yet know what cipher this key will actually + * end up being used with until later. + * + * An alternative would be to keep a list of actual passwords and s2k params, + * and save the key derivation for later. + */ + if (!pgp_s2k_derive_key(&info.s2k, password.c_str(), info.key.data(), info.key.size())) { + return RNP_ERROR_GENERIC; + } + passwords.push_back(info); + return RNP_SUCCESS; +} diff --git a/comm/third_party/rnp/src/librepgp/stream-ctx.h b/comm/third_party/rnp/src/librepgp/stream-ctx.h new file mode 100644 index 0000000000..b9e0c105ec --- /dev/null +++ b/comm/third_party/rnp/src/librepgp/stream-ctx.h @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2019-2020, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef STREAM_CTX_H_ +#define STREAM_CTX_H_ + +#include <stdint.h> +#include <stdbool.h> +#include <sys/types.h> +#include "types.h" +#include <string> +#include <list> +#include "pgp-key.h" +#include "crypto/mem.h" +#include "sec_profile.hpp" + +/* signature info structure */ +typedef struct rnp_signer_info_t { + pgp_key_t * key{}; + pgp_hash_alg_t halg{}; + int64_t sigcreate{}; + uint64_t sigexpire{}; +} rnp_signer_info_t; + +typedef struct rnp_symmetric_pass_info_t { + pgp_s2k_t s2k{}; + pgp_symm_alg_t s2k_cipher{}; + + rnp::secure_array<uint8_t, PGP_MAX_KEY_SIZE> key; +} rnp_symmetric_pass_info_t; + +/** rnp operation context : contains configuration data about the currently ongoing operation. + * + * Common fields which make sense for every operation: + * - overwrite : silently overwrite output file if exists + * - armor : except cleartext signing, which outputs text in clear and always armor signature, + * this controls whether output is armored (base64-encoded). For armor/dearmor operation it + * controls the direction of the conversion (true means enarmor, false - dearmor), + * - rng : random number generator + * - operation : current operation type + * + * For operations with OpenPGP embedded data (i.e. encrypted data and attached signatures): + * - filename, filemtime : to specify information about the contents of literal data packet + * - zalg, zlevel : compression algorithm and level, zlevel = 0 to disable compression + * + * For encryption operation (including encrypt-and-sign): + * - halg : hash algorithm used during key derivation for password-based encryption + * - ealg, aalg, abits : symmetric encryption algorithm and AEAD parameters if used + * - recipients : list of key ids used to encrypt data to + * - passwords : list of passwords used for password-based encryption + * - filename, filemtime, zalg, zlevel : see previous + * + * For signing of any kind (attached, detached, cleartext): + * - clearsign, detached : controls kind of the signed data. Both are mutually-exclusive. + * If both are false then attached signing is used. + * - halg : hash algorithm used to calculate signature(s) + * - signers : list of rnp_signer_info_t structures describing signing key and parameters + * - sigcreate, sigexpire : default signature(s) creation and expiration times + * - filename, filemtime, zalg, zlevel : only for attached signatures, see previous + * + * For data decryption and/or verification there is not much of fields: + * - discard: discard the output data (i.e. just decrypt and/or verify signatures) + * + */ + +typedef struct rnp_ctx_t { + std::string filename{}; /* name of the input file to store in literal data packet */ + int64_t filemtime{}; /* file modification time to store in literal data packet */ + int64_t sigcreate{}; /* signature creation time */ + uint64_t sigexpire{}; /* signature expiration time */ + bool clearsign{}; /* cleartext signature */ + bool detached{}; /* detached signature */ + pgp_hash_alg_t halg{}; /* hash algorithm */ + pgp_symm_alg_t ealg{}; /* encryption algorithm */ + int zalg{}; /* compression algorithm used */ + int zlevel{}; /* compression level */ + pgp_aead_alg_t aalg{}; /* non-zero to use AEAD */ + int abits{}; /* AEAD chunk bits */ + bool overwrite{}; /* allow to overwrite output file if exists */ + bool armor{}; /* whether to use ASCII armor on output */ + bool no_wrap{}; /* do not wrap source in literal data packet */ + std::list<pgp_key_t *> recipients{}; /* recipients of the encrypted message */ + std::list<rnp_symmetric_pass_info_t> passwords{}; /* passwords to encrypt message */ + std::list<rnp_signer_info_t> signers{}; /* keys to which sign message */ + rnp::SecurityContext * ctx{}; /* pointer to rnp::RNG */ + + rnp_ctx_t() = default; + rnp_ctx_t(const rnp_ctx_t &) = delete; + rnp_ctx_t(rnp_ctx_t &&) = delete; + + rnp_ctx_t &operator=(const rnp_ctx_t &) = delete; + rnp_ctx_t &operator=(rnp_ctx_t &&) = delete; + + rnp_result_t add_encryption_password(const std::string &password, + pgp_hash_alg_t halg, + pgp_symm_alg_t ealg, + size_t iterations = 0); +} rnp_ctx_t; + +#endif diff --git a/comm/third_party/rnp/src/librepgp/stream-def.h b/comm/third_party/rnp/src/librepgp/stream-def.h new file mode 100644 index 0000000000..7a1108f827 --- /dev/null +++ b/comm/third_party/rnp/src/librepgp/stream-def.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2017-2020, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef STREAM_DEF_H_ +#define STREAM_DEF_H_ + +#define CT_BUF_LEN 4096 +#define CH_CR ('\r') +#define CH_LF ('\n') +#define CH_EQ ('=') +#define CH_DASH ('-') +#define CH_SPACE (' ') +#define CH_TAB ('\t') +#define CH_COMMA (',') +#define ST_CR ("\r") +#define ST_LF ("\n") +#define ST_CRLF ("\r\n") +#define ST_CRLFCRLF ("\r\n\r\n") +#define ST_DASHSP ("- ") +#define ST_COMMA (",") + +#define ST_DASHES ("-----") +#define ST_ARMOR_BEGIN ("-----BEGIN PGP ") +#define ST_ARMOR_END ("-----END PGP ") +#define ST_CLEAR_BEGIN ("-----BEGIN PGP SIGNED MESSAGE-----") +#define ST_SIG_BEGIN ("\n-----BEGIN PGP SIGNATURE-----") +#define ST_HEADER_VERSION ("Version: ") +#define ST_HEADER_COMMENT ("Comment: ") +#define ST_HEADER_HASH ("Hash: ") +#define ST_HEADER_CHARSET ("Charset: ") +#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) + +/* Maximum OpenPGP packet nesting level */ +#define MAXIMUM_NESTING_LEVEL 32 +#define MAXIMUM_STREAM_PKTS 16 +#define MAXIMUM_ERROR_PKTS 64 + +/* Maximum text line length supported by GnuPG */ +#define MAXIMUM_GNUPG_LINELEN 19995 + +#endif /* !STREAM_DEF_H_ */ diff --git a/comm/third_party/rnp/src/librepgp/stream-dump.cpp b/comm/third_party/rnp/src/librepgp/stream-dump.cpp new file mode 100644 index 0000000000..416f9ae581 --- /dev/null +++ b/comm/third_party/rnp/src/librepgp/stream-dump.cpp @@ -0,0 +1,2533 @@ +/* + * Copyright (c) 2018-2020, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include <sys/stat.h> +#include <stdlib.h> +#include <stdio.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#else +#include "uniwin.h" +#endif +#include <string.h> +#include "time-utils.h" +#include "stream-def.h" +#include "stream-dump.h" +#include "stream-armor.h" +#include "stream-packet.h" +#include "stream-parse.h" +#include "types.h" +#include "ctype.h" +#include "crypto/symmetric.h" +#include "crypto/s2k.h" +#include "fingerprint.h" +#include "pgp-key.h" +#include "crypto.h" +#include "json-utils.h" +#include <algorithm> + +static const id_str_pair packet_tag_map[] = { + {PGP_PKT_RESERVED, "Reserved"}, + {PGP_PKT_PK_SESSION_KEY, "Public-Key Encrypted Session Key"}, + {PGP_PKT_SIGNATURE, "Signature"}, + {PGP_PKT_SK_SESSION_KEY, "Symmetric-Key Encrypted Session Key"}, + {PGP_PKT_ONE_PASS_SIG, "One-Pass Signature"}, + {PGP_PKT_SECRET_KEY, "Secret Key"}, + {PGP_PKT_PUBLIC_KEY, "Public Key"}, + {PGP_PKT_SECRET_SUBKEY, "Secret Subkey"}, + {PGP_PKT_COMPRESSED, "Compressed Data"}, + {PGP_PKT_SE_DATA, "Symmetrically Encrypted Data"}, + {PGP_PKT_MARKER, "Marker"}, + {PGP_PKT_LITDATA, "Literal Data"}, + {PGP_PKT_TRUST, "Trust"}, + {PGP_PKT_USER_ID, "User ID"}, + {PGP_PKT_PUBLIC_SUBKEY, "Public Subkey"}, + {PGP_PKT_RESERVED2, "reserved2"}, + {PGP_PKT_RESERVED3, "reserved3"}, + {PGP_PKT_USER_ATTR, "User Attribute"}, + {PGP_PKT_SE_IP_DATA, "Symmetric Encrypted and Integrity Protected Data"}, + {PGP_PKT_MDC, "Modification Detection Code"}, + {PGP_PKT_AEAD_ENCRYPTED, "AEAD Encrypted Data Packet"}, + {0x00, NULL}, +}; + +static const id_str_pair sig_type_map[] = { + {PGP_SIG_BINARY, "Signature of a binary document"}, + {PGP_SIG_TEXT, "Signature of a canonical text document"}, + {PGP_SIG_STANDALONE, "Standalone signature"}, + {PGP_CERT_GENERIC, "Generic User ID certification"}, + {PGP_CERT_PERSONA, "Personal User ID certification"}, + {PGP_CERT_CASUAL, "Casual User ID certification"}, + {PGP_CERT_POSITIVE, "Positive User ID certification"}, + {PGP_SIG_SUBKEY, "Subkey Binding Signature"}, + {PGP_SIG_PRIMARY, "Primary Key Binding Signature"}, + {PGP_SIG_DIRECT, "Direct-key signature"}, + {PGP_SIG_REV_KEY, "Key revocation signature"}, + {PGP_SIG_REV_SUBKEY, "Subkey revocation signature"}, + {PGP_SIG_REV_CERT, "Certification revocation signature"}, + {PGP_SIG_TIMESTAMP, "Timestamp signature"}, + {PGP_SIG_3RD_PARTY, "Third-Party Confirmation signature"}, + {0x00, NULL}, +}; + +static const id_str_pair sig_subpkt_type_map[] = { + {PGP_SIG_SUBPKT_CREATION_TIME, "signature creation time"}, + {PGP_SIG_SUBPKT_EXPIRATION_TIME, "signature expiration time"}, + {PGP_SIG_SUBPKT_EXPORT_CERT, "exportable certification"}, + {PGP_SIG_SUBPKT_TRUST, "trust signature"}, + {PGP_SIG_SUBPKT_REGEXP, "regular expression"}, + {PGP_SIG_SUBPKT_REVOCABLE, "revocable"}, + {PGP_SIG_SUBPKT_KEY_EXPIRY, "key expiration time"}, + {PGP_SIG_SUBPKT_PREFERRED_SKA, "preferred symmetric algorithms"}, + {PGP_SIG_SUBPKT_REVOCATION_KEY, "revocation key"}, + {PGP_SIG_SUBPKT_ISSUER_KEY_ID, "issuer key ID"}, + {PGP_SIG_SUBPKT_NOTATION_DATA, "notation data"}, + {PGP_SIG_SUBPKT_PREFERRED_HASH, "preferred hash algorithms"}, + {PGP_SIG_SUBPKT_PREF_COMPRESS, "preferred compression algorithms"}, + {PGP_SIG_SUBPKT_KEYSERV_PREFS, "key server preferences"}, + {PGP_SIG_SUBPKT_PREF_KEYSERV, "preferred key server"}, + {PGP_SIG_SUBPKT_PRIMARY_USER_ID, "primary user ID"}, + {PGP_SIG_SUBPKT_POLICY_URI, "policy URI"}, + {PGP_SIG_SUBPKT_KEY_FLAGS, "key flags"}, + {PGP_SIG_SUBPKT_SIGNERS_USER_ID, "signer's user ID"}, + {PGP_SIG_SUBPKT_REVOCATION_REASON, "reason for revocation"}, + {PGP_SIG_SUBPKT_FEATURES, "features"}, + {PGP_SIG_SUBPKT_SIGNATURE_TARGET, "signature target"}, + {PGP_SIG_SUBPKT_EMBEDDED_SIGNATURE, "embedded signature"}, + {PGP_SIG_SUBPKT_ISSUER_FPR, "issuer fingerprint"}, + {PGP_SIG_SUBPKT_PREFERRED_AEAD, "preferred AEAD algorithms"}, + {0x00, NULL}, +}; + +static const id_str_pair key_type_map[] = { + {PGP_PKT_SECRET_KEY, "Secret key"}, + {PGP_PKT_PUBLIC_KEY, "Public key"}, + {PGP_PKT_SECRET_SUBKEY, "Secret subkey"}, + {PGP_PKT_PUBLIC_SUBKEY, "Public subkey"}, + {0x00, NULL}, +}; + +static const id_str_pair pubkey_alg_map[] = { + {PGP_PKA_RSA, "RSA (Encrypt or Sign)"}, + {PGP_PKA_RSA_ENCRYPT_ONLY, "RSA (Encrypt-Only)"}, + {PGP_PKA_RSA_SIGN_ONLY, "RSA (Sign-Only)"}, + {PGP_PKA_ELGAMAL, "Elgamal (Encrypt-Only)"}, + {PGP_PKA_DSA, "DSA"}, + {PGP_PKA_ECDH, "ECDH"}, + {PGP_PKA_ECDSA, "ECDSA"}, + {PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN, "Elgamal"}, + {PGP_PKA_RESERVED_DH, "Reserved for DH (X9.42)"}, + {PGP_PKA_EDDSA, "EdDSA"}, + {PGP_PKA_SM2, "SM2"}, + {0x00, NULL}, +}; + +static const id_str_pair symm_alg_map[] = { + {PGP_SA_PLAINTEXT, "Plaintext"}, + {PGP_SA_IDEA, "IDEA"}, + {PGP_SA_TRIPLEDES, "TripleDES"}, + {PGP_SA_CAST5, "CAST5"}, + {PGP_SA_BLOWFISH, "Blowfish"}, + {PGP_SA_AES_128, "AES-128"}, + {PGP_SA_AES_192, "AES-192"}, + {PGP_SA_AES_256, "AES-256"}, + {PGP_SA_TWOFISH, "Twofish"}, + {PGP_SA_CAMELLIA_128, "Camellia-128"}, + {PGP_SA_CAMELLIA_192, "Camellia-192"}, + {PGP_SA_CAMELLIA_256, "Camellia-256"}, + {PGP_SA_SM4, "SM4"}, + {0x00, NULL}, +}; + +static const id_str_pair hash_alg_map[] = { + {PGP_HASH_MD5, "MD5"}, + {PGP_HASH_SHA1, "SHA1"}, + {PGP_HASH_RIPEMD, "RIPEMD160"}, + {PGP_HASH_SHA256, "SHA256"}, + {PGP_HASH_SHA384, "SHA384"}, + {PGP_HASH_SHA512, "SHA512"}, + {PGP_HASH_SHA224, "SHA224"}, + {PGP_HASH_SM3, "SM3"}, + {PGP_HASH_SHA3_256, "SHA3-256"}, + {PGP_HASH_SHA3_512, "SHA3-512"}, + {0x00, NULL}, +}; + +static const id_str_pair z_alg_map[] = { + {PGP_C_NONE, "Uncompressed"}, + {PGP_C_ZIP, "ZIP"}, + {PGP_C_ZLIB, "ZLib"}, + {PGP_C_BZIP2, "BZip2"}, + {0x00, NULL}, +}; + +static const id_str_pair aead_alg_map[] = { + {PGP_AEAD_NONE, "None"}, + {PGP_AEAD_EAX, "EAX"}, + {PGP_AEAD_OCB, "OCB"}, + {0x00, NULL}, +}; + +static const id_str_pair revoc_reason_map[] = { + {PGP_REVOCATION_NO_REASON, "No reason"}, + {PGP_REVOCATION_SUPERSEDED, "Superseded"}, + {PGP_REVOCATION_COMPROMISED, "Compromised"}, + {PGP_REVOCATION_RETIRED, "Retired"}, + {PGP_REVOCATION_NO_LONGER_VALID, "No longer valid"}, + {0x00, NULL}, +}; + +typedef struct pgp_dest_indent_param_t { + int level; + bool lstart; + pgp_dest_t *writedst; +} pgp_dest_indent_param_t; + +static rnp_result_t +indent_dst_write(pgp_dest_t *dst, const void *buf, size_t len) +{ + pgp_dest_indent_param_t *param = (pgp_dest_indent_param_t *) dst->param; + const char * line = (const char *) buf; + char indent[4] = {' ', ' ', ' ', ' '}; + + if (!len) { + return RNP_SUCCESS; + } + + do { + if (param->lstart) { + for (int i = 0; i < param->level; i++) { + dst_write(param->writedst, indent, sizeof(indent)); + } + param->lstart = false; + } + + for (size_t i = 0; i < len; i++) { + if ((line[i] == '\n') || (i == len - 1)) { + dst_write(param->writedst, line, i + 1); + param->lstart = line[i] == '\n'; + line += i + 1; + len -= i + 1; + break; + } + } + } while (len > 0); + + return RNP_SUCCESS; +} + +static void +indent_dst_close(pgp_dest_t *dst, bool discard) +{ + pgp_dest_indent_param_t *param = (pgp_dest_indent_param_t *) dst->param; + if (!param) { + return; + } + + free(param); +} + +static rnp_result_t +init_indent_dest(pgp_dest_t *dst, pgp_dest_t *origdst) +{ + pgp_dest_indent_param_t *param; + + if (!init_dst_common(dst, sizeof(*param))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + dst->write = indent_dst_write; + dst->close = indent_dst_close; + dst->finish = NULL; + dst->no_cache = true; + param = (pgp_dest_indent_param_t *) dst->param; + param->writedst = origdst; + param->lstart = true; + + return RNP_SUCCESS; +} + +static void +indent_dest_increase(pgp_dest_t *dst) +{ + pgp_dest_indent_param_t *param = (pgp_dest_indent_param_t *) dst->param; + param->level++; +} + +static void +indent_dest_decrease(pgp_dest_t *dst) +{ + pgp_dest_indent_param_t *param = (pgp_dest_indent_param_t *) dst->param; + if (param->level > 0) { + param->level--; + } +} + +static void +indent_dest_set(pgp_dest_t *dst, int level) +{ + pgp_dest_indent_param_t *param = (pgp_dest_indent_param_t *) dst->param; + param->level = level; +} + +static size_t +vsnprinthex(char *str, size_t slen, const uint8_t *buf, size_t buflen) +{ + static const char *hexes = "0123456789abcdef"; + size_t idx = 0; + + for (size_t i = 0; (i < buflen) && (i < (slen - 1) / 2); i++) { + str[idx++] = hexes[buf[i] >> 4]; + str[idx++] = hexes[buf[i] & 0xf]; + } + str[idx] = '\0'; + return buflen * 2; +} + +static void +dst_print_mpi(pgp_dest_t *dst, const char *name, pgp_mpi_t *mpi, bool dumpbin) +{ + char hex[5000]; + if (!dumpbin) { + dst_printf(dst, "%s: %d bits\n", name, (int) mpi_bits(mpi)); + } else { + vsnprinthex(hex, sizeof(hex), mpi->mpi, mpi->len); + dst_printf(dst, "%s: %d bits, %s\n", name, (int) mpi_bits(mpi), hex); + } +} + +static void +dst_print_palg(pgp_dest_t *dst, const char *name, pgp_pubkey_alg_t palg) +{ + const char *palg_name = id_str_pair::lookup(pubkey_alg_map, palg, "Unknown"); + if (!name) { + name = "public key algorithm"; + } + + dst_printf(dst, "%s: %d (%s)\n", name, (int) palg, palg_name); +} + +static void +dst_print_halg(pgp_dest_t *dst, const char *name, pgp_hash_alg_t halg) +{ + const char *halg_name = id_str_pair::lookup(hash_alg_map, halg, "Unknown"); + if (!name) { + name = "hash algorithm"; + } + + dst_printf(dst, "%s: %d (%s)\n", name, (int) halg, halg_name); +} + +static void +dst_print_salg(pgp_dest_t *dst, const char *name, pgp_symm_alg_t salg) +{ + const char *salg_name = id_str_pair::lookup(symm_alg_map, salg, "Unknown"); + if (!name) { + name = "symmetric algorithm"; + } + + dst_printf(dst, "%s: %d (%s)\n", name, (int) salg, salg_name); +} + +static void +dst_print_aalg(pgp_dest_t *dst, const char *name, pgp_aead_alg_t aalg) +{ + const char *aalg_name = id_str_pair::lookup(aead_alg_map, aalg, "Unknown"); + if (!name) { + name = "aead algorithm"; + } + + dst_printf(dst, "%s: %d (%s)\n", name, (int) aalg, aalg_name); +} + +static void +dst_print_zalg(pgp_dest_t *dst, const char *name, pgp_compression_type_t zalg) +{ + const char *zalg_name = id_str_pair::lookup(z_alg_map, zalg, "Unknown"); + if (!name) { + name = "compression algorithm"; + } + + dst_printf(dst, "%s: %d (%s)\n", name, (int) zalg, zalg_name); +} + +static void +dst_print_raw(pgp_dest_t *dst, const char *name, const void *data, size_t len) +{ + dst_printf(dst, "%s: ", name); + dst_write(dst, data, len); + dst_printf(dst, "\n"); +} + +static void +dst_print_algs( + pgp_dest_t *dst, const char *name, uint8_t *algs, size_t algc, const id_str_pair map[]) +{ + if (!name) { + name = "algorithms"; + } + + dst_printf(dst, "%s: ", name); + for (size_t i = 0; i < algc; i++) { + dst_printf( + dst, "%s%s", id_str_pair::lookup(map, algs[i], "Unknown"), i + 1 < algc ? ", " : ""); + } + dst_printf(dst, " ("); + for (size_t i = 0; i < algc; i++) { + dst_printf(dst, "%d%s", (int) algs[i], i + 1 < algc ? ", " : ""); + } + dst_printf(dst, ")\n"); +} + +static void +dst_print_sig_type(pgp_dest_t *dst, const char *name, pgp_sig_type_t sigtype) +{ + const char *sig_name = id_str_pair::lookup(sig_type_map, sigtype, "Unknown"); + if (!name) { + name = "signature type"; + } + dst_printf(dst, "%s: %d (%s)\n", name, (int) sigtype, sig_name); +} + +static void +dst_print_hex(pgp_dest_t *dst, const char *name, const uint8_t *data, size_t len, bool bytes) +{ + char hex[512]; + vsnprinthex(hex, sizeof(hex), data, len); + if (bytes) { + dst_printf(dst, "%s: 0x%s (%d bytes)\n", name, hex, (int) len); + } else { + dst_printf(dst, "%s: 0x%s\n", name, hex); + } +} + +static void +dst_print_keyid(pgp_dest_t *dst, const char *name, const pgp_key_id_t &keyid) +{ + if (!name) { + name = "key id"; + } + dst_print_hex(dst, name, keyid.data(), keyid.size(), false); +} + +static void +dst_print_s2k(pgp_dest_t *dst, pgp_s2k_t *s2k) +{ + dst_printf(dst, "s2k specifier: %d\n", (int) s2k->specifier); + if ((s2k->specifier == PGP_S2KS_EXPERIMENTAL) && s2k->gpg_ext_num) { + dst_printf(dst, "GPG extension num: %d\n", (int) s2k->gpg_ext_num); + if (s2k->gpg_ext_num == PGP_S2K_GPG_SMARTCARD) { + static_assert(sizeof(s2k->gpg_serial) == 16, "invalid s2k->gpg_serial size"); + size_t slen = s2k->gpg_serial_len > 16 ? 16 : s2k->gpg_serial_len; + dst_print_hex(dst, "card serial number", s2k->gpg_serial, slen, true); + } + return; + } + if (s2k->specifier == PGP_S2KS_EXPERIMENTAL) { + dst_print_hex(dst, + "Unknown experimental s2k", + s2k->experimental.data(), + s2k->experimental.size(), + true); + return; + } + dst_print_halg(dst, "s2k hash algorithm", s2k->hash_alg); + if ((s2k->specifier == PGP_S2KS_SALTED) || + (s2k->specifier == PGP_S2KS_ITERATED_AND_SALTED)) { + dst_print_hex(dst, "s2k salt", s2k->salt, PGP_SALT_SIZE, false); + } + if (s2k->specifier == PGP_S2KS_ITERATED_AND_SALTED) { + size_t real_iter = pgp_s2k_decode_iterations(s2k->iterations); + dst_printf(dst, "s2k iterations: %zu (encoded as %u)\n", real_iter, s2k->iterations); + } +} + +static void +dst_print_time(pgp_dest_t *dst, const char *name, uint32_t time) +{ + if (!name) { + name = "time"; + } + auto str = rnp_ctime(time).substr(0, 24); + dst_printf(dst, + "%s: %zu (%s%s)\n", + name, + (size_t) time, + rnp_y2k38_warning(time) ? ">=" : "", + str.c_str()); +} + +static void +dst_print_expiration(pgp_dest_t *dst, const char *name, uint32_t seconds) +{ + if (!name) { + name = "expiration"; + } + if (seconds) { + int days = seconds / (24 * 60 * 60); + dst_printf(dst, "%s: %zu seconds (%d days)\n", name, (size_t) seconds, days); + } else { + dst_printf(dst, "%s: 0 (never)\n", name); + } +} + +#define LINELEN 16 + +static void +dst_hexdump(pgp_dest_t *dst, const uint8_t *src, size_t length) +{ + size_t i; + char line[LINELEN + 1]; + + for (i = 0; i < length; i++) { + if (i % LINELEN == 0) { + dst_printf(dst, "%.5zu | ", i); + } + dst_printf(dst, "%.02x ", (uint8_t) src[i]); + line[i % LINELEN] = (isprint(src[i])) ? src[i] : '.'; + if (i % LINELEN == LINELEN - 1) { + line[LINELEN] = 0x0; + dst_printf(dst, " | %s\n", line); + } + } + if (i % LINELEN != 0) { + for (; i % LINELEN != 0; i++) { + dst_printf(dst, " "); + line[i % LINELEN] = ' '; + } + line[LINELEN] = 0x0; + dst_printf(dst, " | %s\n", line); + } +} + +static rnp_result_t stream_dump_packets_raw(rnp_dump_ctx_t *ctx, + pgp_source_t * src, + pgp_dest_t * dst); +static void stream_dump_signature_pkt(rnp_dump_ctx_t * ctx, + pgp_signature_t *sig, + pgp_dest_t * dst); + +static void +signature_dump_subpacket(rnp_dump_ctx_t *ctx, pgp_dest_t *dst, const pgp_sig_subpkt_t &subpkt) +{ + const char *sname = id_str_pair::lookup(sig_subpkt_type_map, subpkt.type, "Unknown"); + + switch (subpkt.type) { + case PGP_SIG_SUBPKT_CREATION_TIME: + dst_print_time(dst, sname, subpkt.fields.create); + break; + case PGP_SIG_SUBPKT_EXPIRATION_TIME: + dst_print_expiration(dst, sname, subpkt.fields.expiry); + break; + case PGP_SIG_SUBPKT_EXPORT_CERT: + dst_printf(dst, "%s: %d\n", sname, (int) subpkt.fields.exportable); + break; + case PGP_SIG_SUBPKT_TRUST: + dst_printf(dst, + "%s: amount %d, level %d\n", + sname, + (int) subpkt.fields.trust.amount, + (int) subpkt.fields.trust.level); + break; + case PGP_SIG_SUBPKT_REGEXP: + dst_print_raw(dst, sname, subpkt.fields.regexp.str, subpkt.fields.regexp.len); + break; + case PGP_SIG_SUBPKT_REVOCABLE: + dst_printf(dst, "%s: %d\n", sname, (int) subpkt.fields.revocable); + break; + case PGP_SIG_SUBPKT_KEY_EXPIRY: + dst_print_expiration(dst, sname, subpkt.fields.expiry); + break; + case PGP_SIG_SUBPKT_PREFERRED_SKA: + dst_print_algs(dst, + "preferred symmetric algorithms", + subpkt.fields.preferred.arr, + subpkt.fields.preferred.len, + symm_alg_map); + break; + case PGP_SIG_SUBPKT_REVOCATION_KEY: + dst_printf(dst, "%s\n", sname); + dst_printf(dst, "class: %d\n", (int) subpkt.fields.revocation_key.klass); + dst_print_palg(dst, NULL, subpkt.fields.revocation_key.pkalg); + dst_print_hex( + dst, "fingerprint", subpkt.fields.revocation_key.fp, PGP_FINGERPRINT_SIZE, true); + break; + case PGP_SIG_SUBPKT_ISSUER_KEY_ID: + dst_print_hex(dst, sname, subpkt.fields.issuer, PGP_KEY_ID_SIZE, false); + break; + case PGP_SIG_SUBPKT_NOTATION_DATA: { + std::string name(subpkt.fields.notation.name, + subpkt.fields.notation.name + subpkt.fields.notation.nlen); + std::vector<uint8_t> value(subpkt.fields.notation.value, + subpkt.fields.notation.value + subpkt.fields.notation.vlen); + if (subpkt.fields.notation.human) { + dst_printf(dst, "%s: %s = ", sname, name.c_str()); + dst_printf(dst, "%.*s\n", (int) value.size(), (char *) value.data()); + } else { + char hex[64]; + vsnprinthex(hex, sizeof(hex), value.data(), value.size()); + dst_printf(dst, "%s: %s = ", sname, name.c_str()); + dst_printf(dst, "0x%s (%zu bytes)\n", hex, value.size()); + } + break; + } + case PGP_SIG_SUBPKT_PREFERRED_HASH: + dst_print_algs(dst, + "preferred hash algorithms", + subpkt.fields.preferred.arr, + subpkt.fields.preferred.len, + hash_alg_map); + break; + case PGP_SIG_SUBPKT_PREF_COMPRESS: + dst_print_algs(dst, + "preferred compression algorithms", + subpkt.fields.preferred.arr, + subpkt.fields.preferred.len, + z_alg_map); + break; + case PGP_SIG_SUBPKT_KEYSERV_PREFS: + dst_printf(dst, "%s\n", sname); + dst_printf(dst, "no-modify: %d\n", (int) subpkt.fields.ks_prefs.no_modify); + break; + case PGP_SIG_SUBPKT_PREF_KEYSERV: + dst_print_raw( + dst, sname, subpkt.fields.preferred_ks.uri, subpkt.fields.preferred_ks.len); + break; + case PGP_SIG_SUBPKT_PRIMARY_USER_ID: + dst_printf(dst, "%s: %d\n", sname, (int) subpkt.fields.primary_uid); + break; + case PGP_SIG_SUBPKT_POLICY_URI: + dst_print_raw(dst, sname, subpkt.fields.policy.uri, subpkt.fields.policy.len); + break; + case PGP_SIG_SUBPKT_KEY_FLAGS: { + uint8_t flg = subpkt.fields.key_flags; + dst_printf(dst, "%s: 0x%02x ( ", sname, flg); + dst_printf(dst, "%s", flg ? "" : "none"); + dst_printf(dst, "%s", flg & PGP_KF_CERTIFY ? "certify " : ""); + dst_printf(dst, "%s", flg & PGP_KF_SIGN ? "sign " : ""); + dst_printf(dst, "%s", flg & PGP_KF_ENCRYPT_COMMS ? "encrypt_comm " : ""); + dst_printf(dst, "%s", flg & PGP_KF_ENCRYPT_STORAGE ? "encrypt_storage " : ""); + dst_printf(dst, "%s", flg & PGP_KF_SPLIT ? "split " : ""); + dst_printf(dst, "%s", flg & PGP_KF_AUTH ? "auth " : ""); + dst_printf(dst, "%s", flg & PGP_KF_SHARED ? "shared " : ""); + dst_printf(dst, ")\n"); + break; + } + case PGP_SIG_SUBPKT_SIGNERS_USER_ID: + dst_print_raw(dst, sname, subpkt.fields.signer.uid, subpkt.fields.signer.len); + break; + case PGP_SIG_SUBPKT_REVOCATION_REASON: { + int code = subpkt.fields.revocation_reason.code; + const char *reason = id_str_pair::lookup(revoc_reason_map, code, "Unknown"); + dst_printf(dst, "%s: %d (%s)\n", sname, code, reason); + dst_print_raw(dst, + "message", + subpkt.fields.revocation_reason.str, + subpkt.fields.revocation_reason.len); + break; + } + case PGP_SIG_SUBPKT_FEATURES: + dst_printf(dst, "%s: 0x%02x ( ", sname, subpkt.data[0]); + dst_printf(dst, "%s", subpkt.fields.features & PGP_KEY_FEATURE_MDC ? "mdc " : ""); + dst_printf(dst, "%s", subpkt.fields.features & PGP_KEY_FEATURE_AEAD ? "aead " : ""); + dst_printf(dst, "%s", subpkt.fields.features & PGP_KEY_FEATURE_V5 ? "v5 keys " : ""); + dst_printf(dst, ")\n"); + break; + case PGP_SIG_SUBPKT_EMBEDDED_SIGNATURE: + dst_printf(dst, "%s:\n", sname); + stream_dump_signature_pkt(ctx, subpkt.fields.sig, dst); + break; + case PGP_SIG_SUBPKT_ISSUER_FPR: + dst_print_hex( + dst, sname, subpkt.fields.issuer_fp.fp, subpkt.fields.issuer_fp.len, true); + break; + case PGP_SIG_SUBPKT_PREFERRED_AEAD: + dst_print_algs(dst, + "preferred aead algorithms", + subpkt.fields.preferred.arr, + subpkt.fields.preferred.len, + aead_alg_map); + break; + default: + if (!ctx->dump_packets) { + indent_dest_increase(dst); + dst_hexdump(dst, subpkt.data, subpkt.len); + indent_dest_decrease(dst); + } + } +} + +static void +signature_dump_subpackets(rnp_dump_ctx_t * ctx, + pgp_dest_t * dst, + pgp_signature_t *sig, + bool hashed) +{ + bool empty = true; + + for (auto &subpkt : sig->subpkts) { + if (subpkt.hashed != hashed) { + continue; + } + empty = false; + dst_printf(dst, ":type %d, len %d", (int) subpkt.type, (int) subpkt.len); + dst_printf(dst, "%s\n", subpkt.critical ? ", critical" : ""); + if (ctx->dump_packets) { + dst_printf(dst, ":subpacket contents:\n"); + indent_dest_increase(dst); + dst_hexdump(dst, subpkt.data, subpkt.len); + indent_dest_decrease(dst); + } + signature_dump_subpacket(ctx, dst, subpkt); + } + + if (empty) { + dst_printf(dst, "none\n"); + } +} + +static void +stream_dump_signature_pkt(rnp_dump_ctx_t *ctx, pgp_signature_t *sig, pgp_dest_t *dst) +{ + indent_dest_increase(dst); + + dst_printf(dst, "version: %d\n", (int) sig->version); + dst_print_sig_type(dst, "type", sig->type()); + if (sig->version < PGP_V4) { + dst_print_time(dst, "creation time", sig->creation_time); + dst_print_keyid(dst, "signing key id", sig->signer); + } + dst_print_palg(dst, NULL, sig->palg); + dst_print_halg(dst, NULL, sig->halg); + + if (sig->version >= PGP_V4) { + dst_printf(dst, "hashed subpackets:\n"); + indent_dest_increase(dst); + signature_dump_subpackets(ctx, dst, sig, true); + indent_dest_decrease(dst); + + dst_printf(dst, "unhashed subpackets:\n"); + indent_dest_increase(dst); + signature_dump_subpackets(ctx, dst, sig, false); + indent_dest_decrease(dst); + } + + dst_print_hex(dst, "lbits", sig->lbits, sizeof(sig->lbits), false); + dst_printf(dst, "signature material:\n"); + indent_dest_increase(dst); + + pgp_signature_material_t material = {}; + try { + sig->parse_material(material); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return; + } + switch (sig->palg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + dst_print_mpi(dst, "rsa s", &material.rsa.s, ctx->dump_mpi); + break; + case PGP_PKA_DSA: + dst_print_mpi(dst, "dsa r", &material.dsa.r, ctx->dump_mpi); + dst_print_mpi(dst, "dsa s", &material.dsa.s, ctx->dump_mpi); + break; + case PGP_PKA_EDDSA: + case PGP_PKA_ECDSA: + case PGP_PKA_SM2: + case PGP_PKA_ECDH: + dst_print_mpi(dst, "ecc r", &material.ecc.r, ctx->dump_mpi); + dst_print_mpi(dst, "ecc s", &material.ecc.s, ctx->dump_mpi); + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + dst_print_mpi(dst, "eg r", &material.eg.r, ctx->dump_mpi); + dst_print_mpi(dst, "eg s", &material.eg.s, ctx->dump_mpi); + break; + default: + dst_printf(dst, "unknown algorithm\n"); + } + indent_dest_decrease(dst); + indent_dest_decrease(dst); +} + +static rnp_result_t +stream_dump_signature(rnp_dump_ctx_t *ctx, pgp_source_t *src, pgp_dest_t *dst) +{ + pgp_signature_t sig; + rnp_result_t ret; + + dst_printf(dst, "Signature packet\n"); + try { + ret = sig.parse(*src); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + ret = RNP_ERROR_GENERIC; + } + if (ret) { + indent_dest_increase(dst); + dst_printf(dst, "failed to parse\n"); + indent_dest_decrease(dst); + return ret; + } + stream_dump_signature_pkt(ctx, &sig, dst); + return ret; +} + +static rnp_result_t +stream_dump_key(rnp_dump_ctx_t *ctx, pgp_source_t *src, pgp_dest_t *dst) +{ + pgp_key_pkt_t key; + rnp_result_t ret; + pgp_fingerprint_t keyfp = {}; + + try { + ret = key.parse(*src); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + ret = RNP_ERROR_GENERIC; + } + if (ret) { + return ret; + } + + dst_printf(dst, "%s packet\n", id_str_pair::lookup(key_type_map, key.tag, "Unknown")); + indent_dest_increase(dst); + + dst_printf(dst, "version: %d\n", (int) key.version); + dst_print_time(dst, "creation time", key.creation_time); + if (key.version < PGP_V4) { + dst_printf(dst, "v3 validity days: %d\n", (int) key.v3_days); + } + dst_print_palg(dst, NULL, key.alg); + dst_printf(dst, "public key material:\n"); + indent_dest_increase(dst); + + switch (key.alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + dst_print_mpi(dst, "rsa n", &key.material.rsa.n, ctx->dump_mpi); + dst_print_mpi(dst, "rsa e", &key.material.rsa.e, ctx->dump_mpi); + break; + case PGP_PKA_DSA: + dst_print_mpi(dst, "dsa p", &key.material.dsa.p, ctx->dump_mpi); + dst_print_mpi(dst, "dsa q", &key.material.dsa.q, ctx->dump_mpi); + dst_print_mpi(dst, "dsa g", &key.material.dsa.g, ctx->dump_mpi); + dst_print_mpi(dst, "dsa y", &key.material.dsa.y, ctx->dump_mpi); + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + dst_print_mpi(dst, "eg p", &key.material.eg.p, ctx->dump_mpi); + dst_print_mpi(dst, "eg g", &key.material.eg.g, ctx->dump_mpi); + dst_print_mpi(dst, "eg y", &key.material.eg.y, ctx->dump_mpi); + break; + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: + case PGP_PKA_SM2: { + const ec_curve_desc_t *cdesc = get_curve_desc(key.material.ec.curve); + dst_print_mpi(dst, "ecc p", &key.material.ec.p, ctx->dump_mpi); + dst_printf(dst, "ecc curve: %s\n", cdesc ? cdesc->pgp_name : "unknown"); + break; + } + case PGP_PKA_ECDH: { + const ec_curve_desc_t *cdesc = get_curve_desc(key.material.ec.curve); + dst_print_mpi(dst, "ecdh p", &key.material.ec.p, ctx->dump_mpi); + dst_printf(dst, "ecdh curve: %s\n", cdesc ? cdesc->pgp_name : "unknown"); + dst_print_halg(dst, "ecdh hash algorithm", key.material.ec.kdf_hash_alg); + dst_printf(dst, "ecdh key wrap algorithm: %d\n", (int) key.material.ec.key_wrap_alg); + break; + } + default: + dst_printf(dst, "unknown public key algorithm\n"); + } + indent_dest_decrease(dst); + + if (is_secret_key_pkt(key.tag)) { + dst_printf(dst, "secret key material:\n"); + indent_dest_increase(dst); + + dst_printf(dst, "s2k usage: %d\n", (int) key.sec_protection.s2k.usage); + if ((key.sec_protection.s2k.usage == PGP_S2KU_ENCRYPTED) || + (key.sec_protection.s2k.usage == PGP_S2KU_ENCRYPTED_AND_HASHED)) { + dst_print_salg(dst, NULL, key.sec_protection.symm_alg); + dst_print_s2k(dst, &key.sec_protection.s2k); + if (key.sec_protection.s2k.specifier != PGP_S2KS_EXPERIMENTAL) { + size_t bl_size = pgp_block_size(key.sec_protection.symm_alg); + if (bl_size) { + dst_print_hex(dst, "cipher iv", key.sec_protection.iv, bl_size, true); + } else { + dst_printf(dst, "cipher iv: unknown algorithm\n"); + } + } + dst_printf(dst, "encrypted secret key data: %d bytes\n", (int) key.sec_len); + } + + if (!key.sec_protection.s2k.usage) { + dst_printf(dst, "cleartext secret key data: %d bytes\n", (int) key.sec_len); + } + indent_dest_decrease(dst); + } + + pgp_key_id_t keyid = {}; + if (!pgp_keyid(keyid, key)) { + dst_print_hex(dst, "keyid", keyid.data(), keyid.size(), false); + } else { + dst_printf(dst, "keyid: failed to calculate"); + } + + if ((key.version > PGP_V3) && (ctx->dump_grips)) { + if (!pgp_fingerprint(keyfp, key)) { + dst_print_hex(dst, "fingerprint", keyfp.fingerprint, keyfp.length, false); + } else { + dst_printf(dst, "fingerprint: failed to calculate"); + } + } + + if (ctx->dump_grips) { + pgp_key_grip_t grip; + if (rnp_key_store_get_key_grip(&key.material, grip)) { + dst_print_hex(dst, "grip", grip.data(), grip.size(), false); + } else { + dst_printf(dst, "grip: failed to calculate"); + } + } + + indent_dest_decrease(dst); + return RNP_SUCCESS; +} + +static rnp_result_t +stream_dump_userid(pgp_source_t *src, pgp_dest_t *dst) +{ + pgp_userid_pkt_t uid; + rnp_result_t ret; + const char * utype; + + try { + ret = uid.parse(*src); + } catch (const std::exception &e) { + ret = RNP_ERROR_GENERIC; + } + if (ret) { + return ret; + } + + switch (uid.tag) { + case PGP_PKT_USER_ID: + utype = "UserID"; + break; + case PGP_PKT_USER_ATTR: + utype = "UserAttr"; + break; + default: + utype = "Unknown user id"; + } + + dst_printf(dst, "%s packet\n", utype); + indent_dest_increase(dst); + + switch (uid.tag) { + case PGP_PKT_USER_ID: + dst_printf(dst, "id: "); + dst_write(dst, uid.uid, uid.uid_len); + dst_printf(dst, "\n"); + break; + case PGP_PKT_USER_ATTR: + dst_printf(dst, "id: (%d bytes of data)\n", (int) uid.uid_len); + break; + default:; + } + + indent_dest_decrease(dst); + return RNP_SUCCESS; +} + +static rnp_result_t +stream_dump_pk_session_key(rnp_dump_ctx_t *ctx, pgp_source_t *src, pgp_dest_t *dst) +{ + pgp_pk_sesskey_t pkey; + pgp_encrypted_material_t material; + rnp_result_t ret; + + try { + ret = pkey.parse(*src); + if (!pkey.parse_material(material)) { + ret = RNP_ERROR_BAD_FORMAT; + } + } catch (const std::exception &e) { + ret = RNP_ERROR_GENERIC; + } + if (ret) { + return ret; + } + + dst_printf(dst, "Public-key encrypted session key packet\n"); + indent_dest_increase(dst); + + dst_printf(dst, "version: %d\n", (int) pkey.version); + dst_print_keyid(dst, NULL, pkey.key_id); + dst_print_palg(dst, NULL, pkey.alg); + dst_printf(dst, "encrypted material:\n"); + indent_dest_increase(dst); + + switch (pkey.alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + dst_print_mpi(dst, "rsa m", &material.rsa.m, ctx->dump_mpi); + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + dst_print_mpi(dst, "eg g", &material.eg.g, ctx->dump_mpi); + dst_print_mpi(dst, "eg m", &material.eg.m, ctx->dump_mpi); + break; + case PGP_PKA_SM2: + dst_print_mpi(dst, "sm2 m", &material.sm2.m, ctx->dump_mpi); + break; + case PGP_PKA_ECDH: + dst_print_mpi(dst, "ecdh p", &material.ecdh.p, ctx->dump_mpi); + if (ctx->dump_mpi) { + dst_print_hex(dst, "ecdh m", material.ecdh.m, material.ecdh.mlen, true); + } else { + dst_printf(dst, "ecdh m: %d bytes\n", (int) material.ecdh.mlen); + } + break; + default: + dst_printf(dst, "unknown public key algorithm\n"); + } + + indent_dest_decrease(dst); + indent_dest_decrease(dst); + return RNP_SUCCESS; +} + +static rnp_result_t +stream_dump_sk_session_key(pgp_source_t *src, pgp_dest_t *dst) +{ + pgp_sk_sesskey_t skey; + rnp_result_t ret; + + try { + ret = skey.parse(*src); + } catch (const std::exception &e) { + ret = RNP_ERROR_GENERIC; + } + if (ret) { + return ret; + } + + dst_printf(dst, "Symmetric-key encrypted session key packet\n"); + indent_dest_increase(dst); + dst_printf(dst, "version: %d\n", (int) skey.version); + dst_print_salg(dst, NULL, skey.alg); + if (skey.version == PGP_SKSK_V5) { + dst_print_aalg(dst, NULL, skey.aalg); + } + dst_print_s2k(dst, &skey.s2k); + if (skey.version == PGP_SKSK_V5) { + dst_print_hex(dst, "aead iv", skey.iv, skey.ivlen, true); + } + dst_print_hex(dst, "encrypted key", skey.enckey, skey.enckeylen, true); + indent_dest_decrease(dst); + + return RNP_SUCCESS; +} + +static bool +stream_dump_get_aead_hdr(pgp_source_t *src, pgp_aead_hdr_t *hdr) +{ + pgp_dest_t encdst = {}; + uint8_t encpkt[64] = {}; + + if (init_mem_dest(&encdst, &encpkt, sizeof(encpkt))) { + return false; + } + mem_dest_discard_overflow(&encdst, true); + + if (stream_read_packet(src, &encdst)) { + dst_close(&encdst, false); + return false; + } + size_t len = std::min(encdst.writeb, sizeof(encpkt)); + dst_close(&encdst, false); + + pgp_source_t memsrc = {}; + if (init_mem_src(&memsrc, encpkt, len, false)) { + return false; + } + bool res = get_aead_src_hdr(&memsrc, hdr); + src_close(&memsrc); + return res; +} + +static rnp_result_t +stream_dump_aead_encrypted(pgp_source_t *src, pgp_dest_t *dst) +{ + dst_printf(dst, "AEAD-encrypted data packet\n"); + + pgp_aead_hdr_t aead = {}; + if (!stream_dump_get_aead_hdr(src, &aead)) { + dst_printf(dst, "ERROR: failed to read AEAD header\n"); + return RNP_ERROR_READ; + } + + indent_dest_increase(dst); + + dst_printf(dst, "version: %d\n", (int) aead.version); + dst_print_salg(dst, NULL, aead.ealg); + dst_print_aalg(dst, NULL, aead.aalg); + dst_printf(dst, "chunk size: %d\n", (int) aead.csize); + dst_print_hex(dst, "initialization vector", aead.iv, aead.ivlen, true); + + indent_dest_decrease(dst); + return RNP_SUCCESS; +} + +static rnp_result_t +stream_dump_encrypted(pgp_source_t *src, pgp_dest_t *dst, int tag) +{ + switch (tag) { + case PGP_PKT_SE_DATA: + dst_printf(dst, "Symmetrically-encrypted data packet\n\n"); + break; + case PGP_PKT_SE_IP_DATA: + dst_printf(dst, "Symmetrically-encrypted integrity protected data packet\n\n"); + break; + case PGP_PKT_AEAD_ENCRYPTED: + return stream_dump_aead_encrypted(src, dst); + default: + dst_printf(dst, "Unknown encrypted data packet\n\n"); + break; + } + + return stream_skip_packet(src); +} + +static rnp_result_t +stream_dump_one_pass(pgp_source_t *src, pgp_dest_t *dst) +{ + pgp_one_pass_sig_t onepass; + rnp_result_t ret; + + try { + ret = onepass.parse(*src); + } catch (const std::exception &e) { + ret = RNP_ERROR_GENERIC; + } + if (ret) { + return ret; + } + + dst_printf(dst, "One-pass signature packet\n"); + indent_dest_increase(dst); + + dst_printf(dst, "version: %d\n", (int) onepass.version); + dst_print_sig_type(dst, NULL, onepass.type); + dst_print_halg(dst, NULL, onepass.halg); + dst_print_palg(dst, NULL, onepass.palg); + dst_print_keyid(dst, "signing key id", onepass.keyid); + dst_printf(dst, "nested: %d\n", (int) onepass.nested); + + indent_dest_decrease(dst); + return RNP_SUCCESS; +} + +static rnp_result_t +stream_dump_compressed(rnp_dump_ctx_t *ctx, pgp_source_t *src, pgp_dest_t *dst) +{ + pgp_source_t zsrc = {0}; + uint8_t zalg; + rnp_result_t ret; + + if ((ret = init_compressed_src(&zsrc, src))) { + return ret; + } + + dst_printf(dst, "Compressed data packet\n"); + indent_dest_increase(dst); + + get_compressed_src_alg(&zsrc, &zalg); + dst_print_zalg(dst, NULL, (pgp_compression_type_t) zalg); + dst_printf(dst, "Decompressed contents:\n"); + ret = stream_dump_packets_raw(ctx, &zsrc, dst); + + src_close(&zsrc); + indent_dest_decrease(dst); + return ret; +} + +static rnp_result_t +stream_dump_literal(pgp_source_t *src, pgp_dest_t *dst) +{ + pgp_source_t lsrc = {0}; + pgp_literal_hdr_t lhdr = {0}; + rnp_result_t ret; + uint8_t readbuf[16384]; + + if ((ret = init_literal_src(&lsrc, src))) { + return ret; + } + + dst_printf(dst, "Literal data packet\n"); + indent_dest_increase(dst); + + get_literal_src_hdr(&lsrc, &lhdr); + dst_printf(dst, "data format: '%c'\n", lhdr.format); + dst_printf(dst, "filename: %s (len %d)\n", lhdr.fname, (int) lhdr.fname_len); + dst_print_time(dst, "timestamp", lhdr.timestamp); + + ret = RNP_SUCCESS; + while (!src_eof(&lsrc)) { + size_t read = 0; + if (!src_read(&lsrc, readbuf, sizeof(readbuf), &read)) { + ret = RNP_ERROR_READ; + break; + } + } + + dst_printf(dst, "data bytes: %lu\n", (unsigned long) lsrc.readb); + src_close(&lsrc); + indent_dest_decrease(dst); + return ret; +} + +static rnp_result_t +stream_dump_marker(pgp_source_t &src, pgp_dest_t &dst) +{ + dst_printf(&dst, "Marker packet\n"); + indent_dest_increase(&dst); + rnp_result_t ret = stream_parse_marker(src); + dst_printf(&dst, "contents: %s\n", ret ? "invalid" : PGP_MARKER_CONTENTS); + indent_dest_decrease(&dst); + return ret; +} + +static rnp_result_t +stream_dump_packets_raw(rnp_dump_ctx_t *ctx, pgp_source_t *src, pgp_dest_t *dst) +{ + char msg[1024 + PGP_MAX_HEADER_SIZE] = {0}; + char smsg[128] = {0}; + rnp_result_t ret = RNP_ERROR_GENERIC; + + if (src_eof(src)) { + return RNP_SUCCESS; + } + + /* do not allow endless recursion */ + if (++ctx->layers > MAXIMUM_NESTING_LEVEL) { + RNP_LOG("Too many OpenPGP nested layers during the dump."); + dst_printf(dst, ":too many OpenPGP packet layers, stopping.\n"); + ret = RNP_SUCCESS; + goto finish; + } + + while (!src_eof(src)) { + pgp_packet_hdr_t hdr = {}; + size_t off = src->readb; + rnp_result_t hdrret = stream_peek_packet_hdr(src, &hdr); + if (hdrret) { + ret = hdrret; + goto finish; + } + + if (hdr.partial) { + snprintf(msg, sizeof(msg), "partial len"); + } else if (hdr.indeterminate) { + snprintf(msg, sizeof(msg), "indeterminate len"); + } else { + snprintf(msg, sizeof(msg), "len %zu", hdr.pkt_len); + } + vsnprinthex(smsg, sizeof(smsg), hdr.hdr, hdr.hdr_len); + dst_printf( + dst, ":off %zu: packet header 0x%s (tag %d, %s)\n", off, smsg, hdr.tag, msg); + + if (ctx->dump_packets) { + size_t rlen = hdr.pkt_len + hdr.hdr_len; + bool part = false; + + if (!hdr.pkt_len || (rlen > 1024 + hdr.hdr_len)) { + rlen = 1024 + hdr.hdr_len; + part = true; + } + + dst_printf(dst, ":off %zu: packet contents ", off + hdr.hdr_len); + if (!src_peek(src, msg, rlen, &rlen)) { + dst_printf(dst, "- failed to read\n"); + } else { + rlen -= hdr.hdr_len; + if (part || (rlen < hdr.pkt_len)) { + dst_printf(dst, "(first %d bytes)\n", (int) rlen); + } else { + dst_printf(dst, "(%d bytes)\n", (int) rlen); + } + indent_dest_increase(dst); + dst_hexdump(dst, (uint8_t *) msg + hdr.hdr_len, rlen); + indent_dest_decrease(dst); + } + dst_printf(dst, "\n"); + } + + switch (hdr.tag) { + case PGP_PKT_SIGNATURE: + ret = stream_dump_signature(ctx, src, dst); + break; + case PGP_PKT_SECRET_KEY: + case PGP_PKT_PUBLIC_KEY: + case PGP_PKT_SECRET_SUBKEY: + case PGP_PKT_PUBLIC_SUBKEY: + ret = stream_dump_key(ctx, src, dst); + break; + case PGP_PKT_USER_ID: + case PGP_PKT_USER_ATTR: + ret = stream_dump_userid(src, dst); + break; + case PGP_PKT_PK_SESSION_KEY: + ret = stream_dump_pk_session_key(ctx, src, dst); + break; + case PGP_PKT_SK_SESSION_KEY: + ret = stream_dump_sk_session_key(src, dst); + break; + case PGP_PKT_SE_DATA: + case PGP_PKT_SE_IP_DATA: + case PGP_PKT_AEAD_ENCRYPTED: + ctx->stream_pkts++; + ret = stream_dump_encrypted(src, dst, hdr.tag); + break; + case PGP_PKT_ONE_PASS_SIG: + ret = stream_dump_one_pass(src, dst); + break; + case PGP_PKT_COMPRESSED: + ctx->stream_pkts++; + ret = stream_dump_compressed(ctx, src, dst); + break; + case PGP_PKT_LITDATA: + ctx->stream_pkts++; + ret = stream_dump_literal(src, dst); + break; + case PGP_PKT_MARKER: + ret = stream_dump_marker(*src, *dst); + break; + case PGP_PKT_TRUST: + case PGP_PKT_MDC: + dst_printf(dst, "Skipping unhandled pkt: %d\n\n", (int) hdr.tag); + ret = stream_skip_packet(src); + break; + default: + dst_printf(dst, "Skipping Unknown pkt: %d\n\n", (int) hdr.tag); + ret = stream_skip_packet(src); + if (ret) { + goto finish; + } + } + + if (ret) { + RNP_LOG("failed to process packet"); + if (++ctx->failures > MAXIMUM_ERROR_PKTS) { + RNP_LOG("too many packet dump errors."); + goto finish; + } + } + + if (ctx->stream_pkts > MAXIMUM_STREAM_PKTS) { + RNP_LOG("Too many OpenPGP stream packets during the dump."); + dst_printf(dst, ":too many OpenPGP stream packets, stopping.\n"); + ret = RNP_SUCCESS; + goto finish; + } + } + + ret = RNP_SUCCESS; +finish: + return ret; +} + +static bool +stream_skip_cleartext(pgp_source_t *src) +{ + char buf[4096]; + size_t read = 0; + size_t siglen = strlen(ST_SIG_BEGIN); + char * hdrpos; + + while (!src_eof(src)) { + if (!src_peek(src, buf, sizeof(buf) - 1, &read) || (read <= siglen)) { + return false; + } + buf[read] = '\0'; + + if ((hdrpos = strstr(buf, ST_SIG_BEGIN))) { + /* +1 here is to skip \n on the beginning of ST_SIG_BEGIN */ + src_skip(src, hdrpos - buf + 1); + return true; + } + src_skip(src, read - siglen + 1); + } + return false; +} + +rnp_result_t +stream_dump_packets(rnp_dump_ctx_t *ctx, pgp_source_t *src, pgp_dest_t *dst) +{ + pgp_source_t armorsrc = {0}; + pgp_dest_t wrdst = {0}; + bool armored = false; + bool indent = false; + rnp_result_t ret = RNP_ERROR_GENERIC; + + ctx->layers = 0; + ctx->stream_pkts = 0; + ctx->failures = 0; + /* check whether source is cleartext - then skip till the signature */ + if (is_cleartext_source(src)) { + dst_printf(dst, ":cleartext signed data\n"); + if (!stream_skip_cleartext(src)) { + RNP_LOG("malformed cleartext signed data"); + ret = RNP_ERROR_BAD_FORMAT; + goto finish; + } + } + /* check whether source is armored */ + if (is_armored_source(src)) { + if ((ret = init_armored_src(&armorsrc, src))) { + RNP_LOG("failed to parse armored data"); + goto finish; + } + armored = true; + src = &armorsrc; + dst_printf(dst, ":armored input\n"); + } + + if (src_eof(src)) { + dst_printf(dst, ":empty input\n"); + ret = RNP_SUCCESS; + goto finish; + } + + if ((ret = init_indent_dest(&wrdst, dst))) { + RNP_LOG("failed to init indent dest"); + goto finish; + } + indent = true; + indent_dest_set(&wrdst, 0); + + ret = stream_dump_packets_raw(ctx, src, &wrdst); +finish: + if (armored) { + src_close(&armorsrc); + } + if (indent) { + dst_close(&wrdst, false); + } + return ret; +} + +static bool +obj_add_intstr_json(json_object *obj, const char *name, int val, const id_str_pair map[]) +{ + if (!obj_add_field_json(obj, name, json_object_new_int(val))) { + return false; + } + if (!map) { + return true; + } + char namestr[64] = {0}; + const char *str = id_str_pair::lookup(map, val, "Unknown"); + snprintf(namestr, sizeof(namestr), "%s.str", name); + return obj_add_field_json(obj, namestr, json_object_new_string(str)); +} + +static bool +obj_add_mpi_json(json_object *obj, const char *name, const pgp_mpi_t *mpi, bool contents) +{ + char strname[64] = {0}; + snprintf(strname, sizeof(strname), "%s.bits", name); + if (!obj_add_field_json(obj, strname, json_object_new_int(mpi_bits(mpi)))) { + return false; + } + if (!contents) { + return true; + } + snprintf(strname, sizeof(strname), "%s.raw", name); + return obj_add_hex_json(obj, strname, mpi->mpi, mpi->len); +} + +static bool +subpacket_obj_add_algs( + json_object *obj, const char *name, uint8_t *algs, size_t len, const id_str_pair map[]) +{ + json_object *jso_algs = json_object_new_array(); + if (!jso_algs || !obj_add_field_json(obj, name, jso_algs)) { + return false; + } + for (size_t i = 0; i < len; i++) { + if (!array_add_element_json(jso_algs, json_object_new_int(algs[i]))) { + return false; + } + } + if (!map) { + return true; + } + + char strname[64] = {0}; + snprintf(strname, sizeof(strname), "%s.str", name); + + jso_algs = json_object_new_array(); + if (!jso_algs || !obj_add_field_json(obj, strname, jso_algs)) { + return false; + } + for (size_t i = 0; i < len; i++) { + if (!array_add_element_json( + jso_algs, + json_object_new_string(id_str_pair::lookup(map, algs[i], "Unknown")))) { + return false; + } + } + return true; +} + +static bool +obj_add_s2k_json(json_object *obj, pgp_s2k_t *s2k) +{ + json_object *s2k_obj = json_object_new_object(); + if (!obj_add_field_json(obj, "s2k", s2k_obj)) { + return false; + } + if (!obj_add_field_json(s2k_obj, "specifier", json_object_new_int(s2k->specifier))) { + return false; + } + if ((s2k->specifier == PGP_S2KS_EXPERIMENTAL) && s2k->gpg_ext_num) { + if (!obj_add_field_json( + s2k_obj, "gpg extension", json_object_new_int(s2k->gpg_ext_num))) { + return false; + } + if (s2k->gpg_ext_num == PGP_S2K_GPG_SMARTCARD) { + size_t slen = s2k->gpg_serial_len > 16 ? 16 : s2k->gpg_serial_len; + if (!obj_add_hex_json(s2k_obj, "card serial number", s2k->gpg_serial, slen)) { + return false; + } + } + } + if (s2k->specifier == PGP_S2KS_EXPERIMENTAL) { + return obj_add_hex_json( + s2k_obj, "unknown experimental", s2k->experimental.data(), s2k->experimental.size()); + } + if (!obj_add_intstr_json(s2k_obj, "hash algorithm", s2k->hash_alg, hash_alg_map)) { + return false; + } + if (((s2k->specifier == PGP_S2KS_SALTED) || + (s2k->specifier == PGP_S2KS_ITERATED_AND_SALTED)) && + !obj_add_hex_json(s2k_obj, "salt", s2k->salt, PGP_SALT_SIZE)) { + return false; + } + if (s2k->specifier == PGP_S2KS_ITERATED_AND_SALTED) { + size_t real_iter = pgp_s2k_decode_iterations(s2k->iterations); + if (!obj_add_field_json(s2k_obj, "iterations", json_object_new_int(real_iter))) { + return false; + } + } + return true; +} + +static rnp_result_t stream_dump_signature_pkt_json(rnp_dump_ctx_t * ctx, + const pgp_signature_t *sig, + json_object * pkt); + +static bool +signature_dump_subpacket_json(rnp_dump_ctx_t * ctx, + const pgp_sig_subpkt_t &subpkt, + json_object * obj) +{ + switch (subpkt.type) { + case PGP_SIG_SUBPKT_CREATION_TIME: + return obj_add_field_json( + obj, "creation time", json_object_new_int64(subpkt.fields.create)); + case PGP_SIG_SUBPKT_EXPIRATION_TIME: + return obj_add_field_json( + obj, "expiration time", json_object_new_int64(subpkt.fields.expiry)); + case PGP_SIG_SUBPKT_EXPORT_CERT: + return obj_add_field_json( + obj, "exportable", json_object_new_boolean(subpkt.fields.exportable)); + case PGP_SIG_SUBPKT_TRUST: + return obj_add_field_json( + obj, "amount", json_object_new_int(subpkt.fields.trust.amount)) && + obj_add_field_json( + obj, "level", json_object_new_int(subpkt.fields.trust.level)); + case PGP_SIG_SUBPKT_REGEXP: + return obj_add_field_json( + obj, + "regexp", + json_object_new_string_len(subpkt.fields.regexp.str, subpkt.fields.regexp.len)); + case PGP_SIG_SUBPKT_REVOCABLE: + return obj_add_field_json( + obj, "revocable", json_object_new_boolean(subpkt.fields.revocable)); + case PGP_SIG_SUBPKT_KEY_EXPIRY: + return obj_add_field_json( + obj, "key expiration", json_object_new_int64(subpkt.fields.expiry)); + case PGP_SIG_SUBPKT_PREFERRED_SKA: + return subpacket_obj_add_algs(obj, + "algorithms", + subpkt.fields.preferred.arr, + subpkt.fields.preferred.len, + symm_alg_map); + case PGP_SIG_SUBPKT_PREFERRED_HASH: + return subpacket_obj_add_algs(obj, + "algorithms", + subpkt.fields.preferred.arr, + subpkt.fields.preferred.len, + hash_alg_map); + case PGP_SIG_SUBPKT_PREF_COMPRESS: + return subpacket_obj_add_algs(obj, + "algorithms", + subpkt.fields.preferred.arr, + subpkt.fields.preferred.len, + z_alg_map); + case PGP_SIG_SUBPKT_PREFERRED_AEAD: + return subpacket_obj_add_algs(obj, + "algorithms", + subpkt.fields.preferred.arr, + subpkt.fields.preferred.len, + aead_alg_map); + case PGP_SIG_SUBPKT_REVOCATION_KEY: + return obj_add_field_json( + obj, "class", json_object_new_int(subpkt.fields.revocation_key.klass)) && + obj_add_field_json( + obj, "algorithm", json_object_new_int(subpkt.fields.revocation_key.pkalg)) && + obj_add_hex_json( + obj, "fingerprint", subpkt.fields.revocation_key.fp, PGP_FINGERPRINT_SIZE); + case PGP_SIG_SUBPKT_ISSUER_KEY_ID: + return obj_add_hex_json(obj, "issuer keyid", subpkt.fields.issuer, PGP_KEY_ID_SIZE); + case PGP_SIG_SUBPKT_KEYSERV_PREFS: + return obj_add_field_json( + obj, "no-modify", json_object_new_boolean(subpkt.fields.ks_prefs.no_modify)); + case PGP_SIG_SUBPKT_PREF_KEYSERV: + return obj_add_field_json(obj, + "uri", + json_object_new_string_len(subpkt.fields.preferred_ks.uri, + subpkt.fields.preferred_ks.len)); + case PGP_SIG_SUBPKT_PRIMARY_USER_ID: + return obj_add_field_json( + obj, "primary", json_object_new_boolean(subpkt.fields.primary_uid)); + case PGP_SIG_SUBPKT_POLICY_URI: + return obj_add_field_json( + obj, + "uri", + json_object_new_string_len(subpkt.fields.policy.uri, subpkt.fields.policy.len)); + case PGP_SIG_SUBPKT_KEY_FLAGS: { + uint8_t flg = subpkt.fields.key_flags; + if (!obj_add_field_json(obj, "flags", json_object_new_int(flg))) { + return false; + } + json_object *jso_flg = json_object_new_array(); + if (!jso_flg || !obj_add_field_json(obj, "flags.str", jso_flg)) { + return false; + } + if ((flg & PGP_KF_CERTIFY) && + !array_add_element_json(jso_flg, json_object_new_string("certify"))) { + return false; + } + if ((flg & PGP_KF_SIGN) && + !array_add_element_json(jso_flg, json_object_new_string("sign"))) { + return false; + } + if ((flg & PGP_KF_ENCRYPT_COMMS) && + !array_add_element_json(jso_flg, json_object_new_string("encrypt_comm"))) { + return false; + } + if ((flg & PGP_KF_ENCRYPT_STORAGE) && + !array_add_element_json(jso_flg, json_object_new_string("encrypt_storage"))) { + return false; + } + if ((flg & PGP_KF_SPLIT) && + !array_add_element_json(jso_flg, json_object_new_string("split"))) { + return false; + } + if ((flg & PGP_KF_AUTH) && + !array_add_element_json(jso_flg, json_object_new_string("auth"))) { + return false; + } + if ((flg & PGP_KF_SHARED) && + !array_add_element_json(jso_flg, json_object_new_string("shared"))) { + return false; + } + return true; + } + case PGP_SIG_SUBPKT_SIGNERS_USER_ID: + return obj_add_field_json( + obj, + "uid", + json_object_new_string_len(subpkt.fields.signer.uid, subpkt.fields.signer.len)); + case PGP_SIG_SUBPKT_REVOCATION_REASON: { + if (!obj_add_intstr_json( + obj, "code", subpkt.fields.revocation_reason.code, revoc_reason_map)) { + return false; + } + return obj_add_field_json( + obj, + "message", + json_object_new_string_len(subpkt.fields.revocation_reason.str, + subpkt.fields.revocation_reason.len)); + } + case PGP_SIG_SUBPKT_FEATURES: + return obj_add_field_json( + obj, + "mdc", + json_object_new_boolean(subpkt.fields.features & PGP_KEY_FEATURE_MDC)) && + obj_add_field_json( + obj, + "aead", + json_object_new_boolean(subpkt.fields.features & PGP_KEY_FEATURE_AEAD)) && + obj_add_field_json( + obj, + "v5 keys", + json_object_new_boolean(subpkt.fields.features & PGP_KEY_FEATURE_V5)); + case PGP_SIG_SUBPKT_EMBEDDED_SIGNATURE: { + json_object *sig = json_object_new_object(); + if (!sig || !obj_add_field_json(obj, "signature", sig)) { + return false; + } + return !stream_dump_signature_pkt_json(ctx, subpkt.fields.sig, sig); + } + case PGP_SIG_SUBPKT_ISSUER_FPR: + return obj_add_hex_json( + obj, "fingerprint", subpkt.fields.issuer_fp.fp, subpkt.fields.issuer_fp.len); + case PGP_SIG_SUBPKT_NOTATION_DATA: { + bool human = subpkt.fields.notation.human; + if (!json_add(obj, "human", human) || !json_add(obj, + "name", + (char *) subpkt.fields.notation.name, + subpkt.fields.notation.nlen)) { + return false; + } + if (human) { + return json_add(obj, + "value", + (char *) subpkt.fields.notation.value, + subpkt.fields.notation.vlen); + } + return obj_add_hex_json( + obj, "value", subpkt.fields.notation.value, subpkt.fields.notation.vlen); + } + default: + if (!ctx->dump_packets) { + return obj_add_hex_json(obj, "raw", subpkt.data, subpkt.len); + } + return true; + } + return true; +} + +static json_object * +signature_dump_subpackets_json(rnp_dump_ctx_t *ctx, const pgp_signature_t *sig) +{ + json_object *res = json_object_new_array(); + + for (auto &subpkt : sig->subpkts) { + json_object *jso_subpkt = json_object_new_object(); + if (json_object_array_add(res, jso_subpkt)) { + json_object_put(jso_subpkt); + goto error; + } + + if (!obj_add_intstr_json(jso_subpkt, "type", subpkt.type, sig_subpkt_type_map)) { + goto error; + } + if (!obj_add_field_json(jso_subpkt, "length", json_object_new_int(subpkt.len))) { + goto error; + } + if (!obj_add_field_json( + jso_subpkt, "hashed", json_object_new_boolean(subpkt.hashed))) { + goto error; + } + if (!obj_add_field_json( + jso_subpkt, "critical", json_object_new_boolean(subpkt.critical))) { + goto error; + } + + if (ctx->dump_packets && + !obj_add_hex_json(jso_subpkt, "raw", subpkt.data, subpkt.len)) { + goto error; + } + + if (!signature_dump_subpacket_json(ctx, subpkt, jso_subpkt)) { + goto error; + } + } + + return res; +error: + json_object_put(res); + return NULL; +} + +static rnp_result_t +stream_dump_signature_pkt_json(rnp_dump_ctx_t * ctx, + const pgp_signature_t *sig, + json_object * pkt) +{ + json_object * material = NULL; + pgp_signature_material_t sigmaterial = {}; + rnp_result_t ret = RNP_ERROR_OUT_OF_MEMORY; + + if (!obj_add_field_json(pkt, "version", json_object_new_int(sig->version))) { + goto done; + } + if (!obj_add_intstr_json(pkt, "type", sig->type(), sig_type_map)) { + goto done; + } + + if (sig->version < PGP_V4) { + if (!obj_add_field_json( + pkt, "creation time", json_object_new_int(sig->creation_time))) { + goto done; + } + if (!obj_add_hex_json(pkt, "signer", sig->signer.data(), sig->signer.size())) { + goto done; + } + } + if (!obj_add_intstr_json(pkt, "algorithm", sig->palg, pubkey_alg_map)) { + goto done; + } + if (!obj_add_intstr_json(pkt, "hash algorithm", sig->halg, hash_alg_map)) { + goto done; + } + + if (sig->version >= PGP_V4) { + json_object *subpkts = signature_dump_subpackets_json(ctx, sig); + if (!subpkts) { + goto done; + } + if (!obj_add_field_json(pkt, "subpackets", subpkts)) { + goto done; + } + } + + if (!obj_add_hex_json(pkt, "lbits", sig->lbits, sizeof(sig->lbits))) { + goto done; + } + + material = json_object_new_object(); + if (!material || !obj_add_field_json(pkt, "material", material)) { + goto done; + } + + try { + sig->parse_material(sigmaterial); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + switch (sig->palg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + if (!obj_add_mpi_json(material, "s", &sigmaterial.rsa.s, ctx->dump_mpi)) { + goto done; + } + break; + case PGP_PKA_DSA: + if (!obj_add_mpi_json(material, "r", &sigmaterial.dsa.r, ctx->dump_mpi) || + !obj_add_mpi_json(material, "s", &sigmaterial.dsa.s, ctx->dump_mpi)) { + goto done; + } + break; + case PGP_PKA_EDDSA: + case PGP_PKA_ECDSA: + case PGP_PKA_SM2: + case PGP_PKA_ECDH: + if (!obj_add_mpi_json(material, "r", &sigmaterial.ecc.r, ctx->dump_mpi) || + !obj_add_mpi_json(material, "s", &sigmaterial.ecc.s, ctx->dump_mpi)) { + goto done; + } + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + if (!obj_add_mpi_json(material, "r", &sigmaterial.eg.r, ctx->dump_mpi) || + !obj_add_mpi_json(material, "s", &sigmaterial.eg.s, ctx->dump_mpi)) { + goto done; + } + break; + default: + break; + } + ret = RNP_SUCCESS; +done: + return ret; +} + +static rnp_result_t +stream_dump_signature_json(rnp_dump_ctx_t *ctx, pgp_source_t *src, json_object *pkt) +{ + pgp_signature_t sig; + rnp_result_t ret; + try { + ret = sig.parse(*src); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + ret = RNP_ERROR_GENERIC; + } + if (ret) { + return ret; + } + return stream_dump_signature_pkt_json(ctx, &sig, pkt); +} + +static rnp_result_t +stream_dump_key_json(rnp_dump_ctx_t *ctx, pgp_source_t *src, json_object *pkt) +{ + pgp_key_pkt_t key; + rnp_result_t ret; + pgp_key_id_t keyid = {}; + pgp_fingerprint_t keyfp = {}; + json_object * material = NULL; + + try { + ret = key.parse(*src); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + ret = RNP_ERROR_GENERIC; + } + if (ret) { + return ret; + } + + ret = RNP_ERROR_OUT_OF_MEMORY; + + if (!obj_add_field_json(pkt, "version", json_object_new_int(key.version))) { + goto done; + } + if (!obj_add_field_json(pkt, "creation time", json_object_new_int64(key.creation_time))) { + goto done; + } + if ((key.version < PGP_V4) && + !obj_add_field_json(pkt, "v3 days", json_object_new_int(key.v3_days))) { + goto done; + } + if (!obj_add_intstr_json(pkt, "algorithm", key.alg, pubkey_alg_map)) { + goto done; + } + + material = json_object_new_object(); + if (!material || !obj_add_field_json(pkt, "material", material)) { + goto done; + } + + switch (key.alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + if (!obj_add_mpi_json(material, "n", &key.material.rsa.n, ctx->dump_mpi) || + !obj_add_mpi_json(material, "e", &key.material.rsa.e, ctx->dump_mpi)) { + goto done; + } + break; + case PGP_PKA_DSA: + if (!obj_add_mpi_json(material, "p", &key.material.dsa.p, ctx->dump_mpi) || + !obj_add_mpi_json(material, "q", &key.material.dsa.q, ctx->dump_mpi) || + !obj_add_mpi_json(material, "g", &key.material.dsa.g, ctx->dump_mpi) || + !obj_add_mpi_json(material, "y", &key.material.dsa.y, ctx->dump_mpi)) { + goto done; + } + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + if (!obj_add_mpi_json(material, "p", &key.material.eg.p, ctx->dump_mpi) || + !obj_add_mpi_json(material, "g", &key.material.eg.g, ctx->dump_mpi) || + !obj_add_mpi_json(material, "y", &key.material.eg.y, ctx->dump_mpi)) { + goto done; + } + break; + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: + case PGP_PKA_SM2: { + const ec_curve_desc_t *cdesc = get_curve_desc(key.material.ec.curve); + if (!obj_add_mpi_json(material, "p", &key.material.ec.p, ctx->dump_mpi)) { + goto done; + } + if (!obj_add_field_json(material, + "curve", + json_object_new_string(cdesc ? cdesc->pgp_name : "unknown"))) { + goto done; + } + break; + } + case PGP_PKA_ECDH: { + const ec_curve_desc_t *cdesc = get_curve_desc(key.material.ec.curve); + if (!obj_add_mpi_json(material, "p", &key.material.ec.p, ctx->dump_mpi)) { + goto done; + } + if (!obj_add_field_json(material, + "curve", + json_object_new_string(cdesc ? cdesc->pgp_name : "unknown"))) { + goto done; + } + if (!obj_add_intstr_json( + material, "hash algorithm", key.material.ec.kdf_hash_alg, hash_alg_map)) { + goto done; + } + if (!obj_add_intstr_json( + material, "key wrap algorithm", key.material.ec.key_wrap_alg, symm_alg_map)) { + goto done; + } + break; + } + default: + break; + } + + if (is_secret_key_pkt(key.tag)) { + if (!obj_add_field_json( + material, "s2k usage", json_object_new_int(key.sec_protection.s2k.usage))) { + goto done; + } + if (!obj_add_s2k_json(material, &key.sec_protection.s2k)) { + goto done; + } + if (key.sec_protection.s2k.usage && + !obj_add_intstr_json( + material, "symmetric algorithm", key.sec_protection.symm_alg, symm_alg_map)) { + goto done; + } + } + + if (pgp_keyid(keyid, key) || !obj_add_hex_json(pkt, "keyid", keyid.data(), keyid.size())) { + goto done; + } + + if (ctx->dump_grips) { + if (pgp_fingerprint(keyfp, key) || + !obj_add_hex_json(pkt, "fingerprint", keyfp.fingerprint, keyfp.length)) { + goto done; + } + + pgp_key_grip_t grip; + if (!rnp_key_store_get_key_grip(&key.material, grip) || + !obj_add_hex_json(pkt, "grip", grip.data(), grip.size())) { + goto done; + } + } + ret = RNP_SUCCESS; +done: + return ret; +} + +static rnp_result_t +stream_dump_userid_json(pgp_source_t *src, json_object *pkt) +{ + pgp_userid_pkt_t uid; + rnp_result_t ret; + + try { + ret = uid.parse(*src); + } catch (const std::exception &e) { + ret = RNP_ERROR_GENERIC; + } + if (ret) { + return ret; + } + + switch (uid.tag) { + case PGP_PKT_USER_ID: + if (!obj_add_field_json( + pkt, "userid", json_object_new_string_len((char *) uid.uid, uid.uid_len))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + break; + case PGP_PKT_USER_ATTR: + if (!obj_add_hex_json(pkt, "userattr", uid.uid, uid.uid_len)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + break; + default:; + } + return RNP_SUCCESS; +} + +static rnp_result_t +stream_dump_pk_session_key_json(rnp_dump_ctx_t *ctx, pgp_source_t *src, json_object *pkt) +{ + pgp_pk_sesskey_t pkey; + pgp_encrypted_material_t pkmaterial; + rnp_result_t ret; + + try { + ret = pkey.parse(*src); + if (!pkey.parse_material(pkmaterial)) { + ret = RNP_ERROR_BAD_FORMAT; + } + } catch (const std::exception &e) { + ret = RNP_ERROR_GENERIC; + } + if (ret) { + return ret; + } + + if (!obj_add_field_json(pkt, "version", json_object_new_int(pkey.version)) || + !obj_add_hex_json(pkt, "keyid", pkey.key_id.data(), pkey.key_id.size()) || + !obj_add_intstr_json(pkt, "algorithm", pkey.alg, pubkey_alg_map)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + json_object *material = json_object_new_object(); + if (!obj_add_field_json(pkt, "material", material)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + switch (pkey.alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + if (!obj_add_mpi_json(material, "m", &pkmaterial.rsa.m, ctx->dump_mpi)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + if (!obj_add_mpi_json(material, "g", &pkmaterial.eg.g, ctx->dump_mpi) || + !obj_add_mpi_json(material, "m", &pkmaterial.eg.m, ctx->dump_mpi)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + break; + case PGP_PKA_SM2: + if (!obj_add_mpi_json(material, "m", &pkmaterial.sm2.m, ctx->dump_mpi)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + break; + case PGP_PKA_ECDH: + if (!obj_add_mpi_json(material, "p", &pkmaterial.ecdh.p, ctx->dump_mpi) || + !obj_add_field_json( + material, "m.bytes", json_object_new_int(pkmaterial.ecdh.mlen))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + if (ctx->dump_mpi && + !obj_add_hex_json(material, "m", pkmaterial.ecdh.m, pkmaterial.ecdh.mlen)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + break; + default:; + } + + return RNP_SUCCESS; +} + +static rnp_result_t +stream_dump_sk_session_key_json(pgp_source_t *src, json_object *pkt) +{ + pgp_sk_sesskey_t skey; + rnp_result_t ret; + + try { + ret = skey.parse(*src); + } catch (const std::exception &e) { + ret = RNP_ERROR_GENERIC; + } + if (ret) { + return ret; + } + + if (!obj_add_field_json(pkt, "version", json_object_new_int(skey.version)) || + !obj_add_intstr_json(pkt, "algorithm", skey.alg, symm_alg_map)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + if ((skey.version == PGP_SKSK_V5) && + !obj_add_intstr_json(pkt, "aead algorithm", skey.aalg, aead_alg_map)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + if (!obj_add_s2k_json(pkt, &skey.s2k)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + if ((skey.version == PGP_SKSK_V5) && + !obj_add_hex_json(pkt, "aead iv", skey.iv, skey.ivlen)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + if (!obj_add_hex_json(pkt, "encrypted key", skey.enckey, skey.enckeylen)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + return RNP_SUCCESS; +} + +static rnp_result_t +stream_dump_encrypted_json(pgp_source_t *src, json_object *pkt, pgp_pkt_type_t tag) +{ + if (tag != PGP_PKT_AEAD_ENCRYPTED) { + /* packet header with tag is already in pkt */ + return stream_skip_packet(src); + } + + /* dumping AEAD data */ + pgp_aead_hdr_t aead = {}; + if (!stream_dump_get_aead_hdr(src, &aead)) { + return RNP_ERROR_READ; + } + + if (!obj_add_field_json(pkt, "version", json_object_new_int(aead.version)) || + !obj_add_intstr_json(pkt, "algorithm", aead.ealg, symm_alg_map) || + !obj_add_intstr_json(pkt, "aead algorithm", aead.aalg, aead_alg_map) || + !obj_add_field_json(pkt, "chunk size", json_object_new_int(aead.csize)) || + !obj_add_hex_json(pkt, "aead iv", aead.iv, aead.ivlen)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + return RNP_SUCCESS; +} + +static rnp_result_t +stream_dump_one_pass_json(pgp_source_t *src, json_object *pkt) +{ + pgp_one_pass_sig_t onepass; + rnp_result_t ret; + + try { + ret = onepass.parse(*src); + } catch (const std::exception &e) { + ret = RNP_ERROR_GENERIC; + } + if (ret) { + return ret; + } + + if (!obj_add_field_json(pkt, "version", json_object_new_int(onepass.version))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + if (!obj_add_intstr_json(pkt, "type", onepass.type, sig_type_map)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + if (!obj_add_intstr_json(pkt, "hash algorithm", onepass.halg, hash_alg_map)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + if (!obj_add_intstr_json(pkt, "public key algorithm", onepass.palg, pubkey_alg_map)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + if (!obj_add_hex_json(pkt, "signer", onepass.keyid.data(), onepass.keyid.size())) { + return RNP_ERROR_OUT_OF_MEMORY; + } + if (!obj_add_field_json(pkt, "nested", json_object_new_boolean(onepass.nested))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + return RNP_SUCCESS; +} + +static rnp_result_t +stream_dump_marker_json(pgp_source_t &src, json_object *pkt) +{ + rnp_result_t ret = stream_parse_marker(src); + + if (!obj_add_field_json( + pkt, "contents", json_object_new_string(ret ? "invalid" : PGP_MARKER_CONTENTS))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + return ret; +} + +static rnp_result_t stream_dump_raw_packets_json(rnp_dump_ctx_t *ctx, + pgp_source_t * src, + json_object ** jso); + +static rnp_result_t +stream_dump_compressed_json(rnp_dump_ctx_t *ctx, pgp_source_t *src, json_object *pkt) +{ + pgp_source_t zsrc = {0}; + uint8_t zalg; + rnp_result_t ret; + json_object *contents = NULL; + + if ((ret = init_compressed_src(&zsrc, src))) { + return ret; + } + + get_compressed_src_alg(&zsrc, &zalg); + if (!obj_add_intstr_json(pkt, "algorithm", zalg, z_alg_map)) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + + ret = stream_dump_raw_packets_json(ctx, &zsrc, &contents); + if (!ret && !obj_add_field_json(pkt, "contents", contents)) { + json_object_put(contents); + ret = RNP_ERROR_OUT_OF_MEMORY; + } +done: + src_close(&zsrc); + return ret; +} + +static rnp_result_t +stream_dump_literal_json(pgp_source_t *src, json_object *pkt) +{ + pgp_source_t lsrc = {0}; + pgp_literal_hdr_t lhdr = {0}; + rnp_result_t ret; + uint8_t readbuf[16384]; + + if ((ret = init_literal_src(&lsrc, src))) { + return ret; + } + ret = RNP_ERROR_OUT_OF_MEMORY; + get_literal_src_hdr(&lsrc, &lhdr); + if (!obj_add_field_json( + pkt, "format", json_object_new_string_len((char *) &lhdr.format, 1))) { + goto done; + } + if (!obj_add_field_json( + pkt, "filename", json_object_new_string_len(lhdr.fname, lhdr.fname_len))) { + goto done; + } + if (!obj_add_field_json(pkt, "timestamp", json_object_new_int64(lhdr.timestamp))) { + goto done; + } + + while (!src_eof(&lsrc)) { + size_t read = 0; + if (!src_read(&lsrc, readbuf, sizeof(readbuf), &read)) { + ret = RNP_ERROR_READ; + goto done; + } + } + + if (!obj_add_field_json(pkt, "datalen", json_object_new_int64(lsrc.readb))) { + goto done; + } + ret = RNP_SUCCESS; +done: + src_close(&lsrc); + return ret; +} + +static bool +stream_dump_hdr_json(pgp_source_t *src, pgp_packet_hdr_t *hdr, json_object *pkt) +{ + rnp_result_t hdrret = stream_peek_packet_hdr(src, hdr); + if (hdrret) { + return false; + } + + json_object *jso_hdr = json_object_new_object(); + if (!jso_hdr) { + return false; + } + + if (!obj_add_field_json(jso_hdr, "offset", json_object_new_int64(src->readb))) { + goto error; + } + if (!obj_add_intstr_json(jso_hdr, "tag", hdr->tag, packet_tag_map)) { + goto error; + } + if (!obj_add_hex_json(jso_hdr, "raw", hdr->hdr, hdr->hdr_len)) { + goto error; + } + if (!hdr->partial && !hdr->indeterminate && + !obj_add_field_json(jso_hdr, "length", json_object_new_int64(hdr->pkt_len))) { + goto error; + } + if (!obj_add_field_json(jso_hdr, "partial", json_object_new_boolean(hdr->partial))) { + goto error; + } + if (!obj_add_field_json( + jso_hdr, "indeterminate", json_object_new_boolean(hdr->indeterminate))) { + goto error; + } + return obj_add_field_json(pkt, "header", jso_hdr); +error: + json_object_put(jso_hdr); + return false; +} + +static rnp_result_t +stream_dump_raw_packets_json(rnp_dump_ctx_t *ctx, pgp_source_t *src, json_object **jso) +{ + json_object *pkts = NULL; + json_object *pkt = NULL; + rnp_result_t ret = RNP_ERROR_GENERIC; + + pkts = json_object_new_array(); + if (!pkts) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + if (src_eof(src)) { + ret = RNP_SUCCESS; + goto done; + } + + /* do not allow endless recursion */ + if (++ctx->layers > MAXIMUM_NESTING_LEVEL) { + RNP_LOG("Too many OpenPGP nested layers during the dump."); + ret = RNP_SUCCESS; + goto done; + } + + while (!src_eof(src)) { + pgp_packet_hdr_t hdr = {}; + + pkt = json_object_new_object(); + if (!pkt) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + + if (!stream_dump_hdr_json(src, &hdr, pkt)) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + + if (ctx->dump_packets) { + size_t rlen = hdr.pkt_len + hdr.hdr_len; + uint8_t buf[2048 + sizeof(hdr.hdr)] = {0}; + + if (!hdr.pkt_len || (rlen > 2048 + hdr.hdr_len)) { + rlen = 2048 + hdr.hdr_len; + } + if (!src_peek(src, buf, rlen, &rlen) || (rlen < hdr.hdr_len)) { + ret = RNP_ERROR_READ; + goto done; + } + if (!obj_add_hex_json(pkt, "raw", buf + hdr.hdr_len, rlen - hdr.hdr_len)) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + } + + switch (hdr.tag) { + case PGP_PKT_SIGNATURE: + ret = stream_dump_signature_json(ctx, src, pkt); + break; + case PGP_PKT_SECRET_KEY: + case PGP_PKT_PUBLIC_KEY: + case PGP_PKT_SECRET_SUBKEY: + case PGP_PKT_PUBLIC_SUBKEY: + ret = stream_dump_key_json(ctx, src, pkt); + break; + case PGP_PKT_USER_ID: + case PGP_PKT_USER_ATTR: + ret = stream_dump_userid_json(src, pkt); + break; + case PGP_PKT_PK_SESSION_KEY: + ret = stream_dump_pk_session_key_json(ctx, src, pkt); + break; + case PGP_PKT_SK_SESSION_KEY: + ret = stream_dump_sk_session_key_json(src, pkt); + break; + case PGP_PKT_SE_DATA: + case PGP_PKT_SE_IP_DATA: + case PGP_PKT_AEAD_ENCRYPTED: + ctx->stream_pkts++; + ret = stream_dump_encrypted_json(src, pkt, hdr.tag); + break; + case PGP_PKT_ONE_PASS_SIG: + ret = stream_dump_one_pass_json(src, pkt); + break; + case PGP_PKT_COMPRESSED: + ctx->stream_pkts++; + ret = stream_dump_compressed_json(ctx, src, pkt); + break; + case PGP_PKT_LITDATA: + ctx->stream_pkts++; + ret = stream_dump_literal_json(src, pkt); + break; + case PGP_PKT_MARKER: + ret = stream_dump_marker_json(*src, pkt); + break; + case PGP_PKT_TRUST: + case PGP_PKT_MDC: + ret = stream_skip_packet(src); + break; + default: + ret = stream_skip_packet(src); + } + + if (ret) { + RNP_LOG("failed to process packet"); + if (++ctx->failures > MAXIMUM_ERROR_PKTS) { + RNP_LOG("too many packet dump errors."); + goto done; + } + ret = RNP_SUCCESS; + } + + if (json_object_array_add(pkts, pkt)) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + if (ctx->stream_pkts > MAXIMUM_STREAM_PKTS) { + RNP_LOG("Too many OpenPGP stream packets during the dump."); + ret = RNP_SUCCESS; + goto done; + } + + pkt = NULL; + } +done: + if (ret) { + json_object_put(pkts); + json_object_put(pkt); + pkts = NULL; + } + *jso = pkts; + return ret; +} + +rnp_result_t +stream_dump_packets_json(rnp_dump_ctx_t *ctx, pgp_source_t *src, json_object **jso) +{ + pgp_source_t armorsrc = {0}; + bool armored = false; + rnp_result_t ret = RNP_ERROR_GENERIC; + + ctx->layers = 0; + ctx->stream_pkts = 0; + ctx->failures = 0; + /* check whether source is cleartext - then skip till the signature */ + if (is_cleartext_source(src)) { + if (!stream_skip_cleartext(src)) { + RNP_LOG("malformed cleartext signed data"); + ret = RNP_ERROR_BAD_FORMAT; + goto finish; + } + } + /* check whether source is armored */ + if (is_armored_source(src)) { + if ((ret = init_armored_src(&armorsrc, src))) { + RNP_LOG("failed to parse armored data"); + goto finish; + } + armored = true; + src = &armorsrc; + } + + if (src_eof(src)) { + ret = RNP_ERROR_NOT_ENOUGH_DATA; + goto finish; + } + + ret = stream_dump_raw_packets_json(ctx, src, jso); +finish: + if (armored) { + src_close(&armorsrc); + } + return ret; +} diff --git a/comm/third_party/rnp/src/librepgp/stream-dump.h b/comm/third_party/rnp/src/librepgp/stream-dump.h new file mode 100644 index 0000000000..6c2fcf1d4f --- /dev/null +++ b/comm/third_party/rnp/src/librepgp/stream-dump.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2018, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef STREAM_DUMP_H_ +#define STREAM_DUMP_H_ + +#include <stdint.h> +#include <stdbool.h> +#include <sys/types.h> +#include "json_object.h" +#include "json.h" +#include "rnp.h" +#include "stream-common.h" + +typedef struct rnp_dump_ctx_t { + bool dump_mpi; + bool dump_packets; + bool dump_grips; + size_t layers; + size_t stream_pkts; + size_t failures; +} rnp_dump_ctx_t; + +rnp_result_t stream_dump_packets(rnp_dump_ctx_t *ctx, pgp_source_t *src, pgp_dest_t *dst); + +rnp_result_t stream_dump_packets_json(rnp_dump_ctx_t *ctx, + pgp_source_t * src, + json_object ** jso); + +#endif diff --git a/comm/third_party/rnp/src/librepgp/stream-key.cpp b/comm/third_party/rnp/src/librepgp/stream-key.cpp new file mode 100644 index 0000000000..8090ff7d59 --- /dev/null +++ b/comm/third_party/rnp/src/librepgp/stream-key.cpp @@ -0,0 +1,1469 @@ +/* + * Copyright (c) 2018-2022, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include <sys/stat.h> +#include <stdlib.h> +#include <stdio.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#else +#include "uniwin.h" +#endif +#include <string.h> +#include <time.h> +#include <inttypes.h> +#include "stream-def.h" +#include "stream-key.h" +#include "stream-armor.h" +#include "stream-packet.h" +#include "stream-sig.h" +#include "types.h" +#include "fingerprint.h" +#include "pgp-key.h" +#include "crypto.h" +#include "crypto/signatures.h" +#include "crypto/mem.h" +#include "../librekey/key_store_pgp.h" +#include <set> +#include <algorithm> +#include <cassert> + +/** + * @brief Add signatures from src to dst, skipping the duplicates. + * + * @param dst Vector which will contain all distinct signatures from src and dst + * @param src Vector to merge signatures from + * @return true on success or false otherwise. On failure dst may have some sigs appended. + */ +static rnp_result_t +merge_signatures(pgp_signature_list_t &dst, const pgp_signature_list_t &src) +{ + for (auto &sig : src) { + try { + if (std::find(dst.begin(), dst.end(), sig) != dst.end()) { + continue; + } + dst.emplace_back(sig); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + } + return RNP_SUCCESS; +} + +static rnp_result_t +transferable_userid_merge(pgp_transferable_userid_t &dst, const pgp_transferable_userid_t &src) +{ + if (dst.uid != src.uid) { + RNP_LOG("wrong userid merge attempt"); + return RNP_ERROR_BAD_PARAMETERS; + } + return merge_signatures(dst.signatures, src.signatures); +} + +rnp_result_t +transferable_subkey_from_key(pgp_transferable_subkey_t &dst, const pgp_key_t &key) +{ + try { + auto vec = rnp_key_to_vec(key); + rnp::MemorySource mem(vec); + return process_pgp_subkey(mem.src(), dst, false); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_GENERIC; + } +} + +rnp_result_t +transferable_subkey_merge(pgp_transferable_subkey_t &dst, const pgp_transferable_subkey_t &src) +{ + rnp_result_t ret = RNP_ERROR_GENERIC; + + if (!dst.subkey.equals(src.subkey, true)) { + RNP_LOG("wrong subkey merge call"); + return RNP_ERROR_BAD_PARAMETERS; + } + if ((ret = merge_signatures(dst.signatures, src.signatures))) { + RNP_LOG("failed to merge signatures"); + } + return ret; +} + +rnp_result_t +transferable_key_from_key(pgp_transferable_key_t &dst, const pgp_key_t &key) +{ + try { + auto vec = rnp_key_to_vec(key); + rnp::MemorySource mem(vec); + return process_pgp_key(mem.src(), dst, false); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_GENERIC; + } +} + +static pgp_transferable_userid_t * +transferable_key_has_userid(pgp_transferable_key_t &src, const pgp_userid_pkt_t &userid) +{ + for (auto &uid : src.userids) { + if (uid.uid == userid) { + return &uid; + } + } + return NULL; +} + +static pgp_transferable_subkey_t * +transferable_key_has_subkey(pgp_transferable_key_t &src, const pgp_key_pkt_t &subkey) +{ + for (auto &srcsub : src.subkeys) { + if (srcsub.subkey.equals(subkey, true)) { + return &srcsub; + } + } + return NULL; +} + +rnp_result_t +transferable_key_merge(pgp_transferable_key_t &dst, const pgp_transferable_key_t &src) +{ + rnp_result_t ret = RNP_ERROR_GENERIC; + + if (!dst.key.equals(src.key, true)) { + RNP_LOG("wrong key merge call"); + return RNP_ERROR_BAD_PARAMETERS; + } + /* direct-key signatures */ + if ((ret = merge_signatures(dst.signatures, src.signatures))) { + RNP_LOG("failed to merge signatures"); + return ret; + } + /* userids */ + for (auto &srcuid : src.userids) { + pgp_transferable_userid_t *dstuid = transferable_key_has_userid(dst, srcuid.uid); + if (dstuid) { + if ((ret = transferable_userid_merge(*dstuid, srcuid))) { + RNP_LOG("failed to merge userid"); + return ret; + } + continue; + } + /* add userid */ + try { + dst.userids.emplace_back(srcuid); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + } + + /* subkeys */ + for (auto &srcsub : src.subkeys) { + pgp_transferable_subkey_t *dstsub = transferable_key_has_subkey(dst, srcsub.subkey); + if (dstsub) { + if ((ret = transferable_subkey_merge(*dstsub, srcsub))) { + RNP_LOG("failed to merge subkey"); + return ret; + } + continue; + } + /* add subkey */ + if (is_public_key_pkt(dst.key.tag) != is_public_key_pkt(srcsub.subkey.tag)) { + RNP_LOG("warning: adding public/secret subkey to secret/public key"); + } + try { + dst.subkeys.emplace_back(srcsub); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + } + return RNP_SUCCESS; +} + +static bool +skip_pgp_packets(pgp_source_t &src, const std::set<pgp_pkt_type_t> &pkts) +{ + do { + int pkt = stream_pkt_type(src); + if (!pkt) { + break; + } + if (pkt < 0) { + return false; + } + if (pkts.find((pgp_pkt_type_t) pkt) == pkts.end()) { + return true; + } + uint64_t ppos = src.readb; + if (stream_skip_packet(&src)) { + RNP_LOG("failed to skip packet at %" PRIu64, ppos); + return false; + } + } while (1); + + return true; +} + +static rnp_result_t +process_pgp_key_signatures(pgp_source_t &src, pgp_signature_list_t &sigs, bool skiperrors) +{ + int ptag; + while ((ptag = stream_pkt_type(src)) == PGP_PKT_SIGNATURE) { + uint64_t sigpos = src.readb; + try { + pgp_signature_t sig; + rnp_result_t ret = sig.parse(src); + if (ret) { + RNP_LOG("failed to parse signature at %" PRIu64, sigpos); + if (!skiperrors) { + return ret; + } + } else { + sigs.emplace_back(std::move(sig)); + } + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + if (!skip_pgp_packets(src, {PGP_PKT_TRUST})) { + return RNP_ERROR_READ; + } + } + return ptag < 0 ? RNP_ERROR_BAD_FORMAT : RNP_SUCCESS; +} + +static rnp_result_t +process_pgp_userid(pgp_source_t &src, pgp_transferable_userid_t &uid, bool skiperrors) +{ + rnp_result_t ret; + uint64_t uidpos = src.readb; + try { + ret = uid.uid.parse(src); + } catch (const std::exception &e) { + ret = RNP_ERROR_GENERIC; + } + if (ret) { + RNP_LOG("failed to parse userid at %" PRIu64, uidpos); + return ret; + } + if (!skip_pgp_packets(src, {PGP_PKT_TRUST})) { + return RNP_ERROR_READ; + } + return process_pgp_key_signatures(src, uid.signatures, skiperrors); +} + +rnp_result_t +process_pgp_subkey(pgp_source_t &src, pgp_transferable_subkey_t &subkey, bool skiperrors) +{ + int ptag; + subkey = pgp_transferable_subkey_t(); + uint64_t keypos = src.readb; + if (!is_subkey_pkt(ptag = stream_pkt_type(src))) { + RNP_LOG("wrong subkey ptag: %d at %" PRIu64, ptag, keypos); + return RNP_ERROR_BAD_FORMAT; + } + + rnp_result_t ret = RNP_ERROR_BAD_FORMAT; + try { + ret = subkey.subkey.parse(src); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + ret = RNP_ERROR_GENERIC; + } + if (ret) { + RNP_LOG("failed to parse subkey at %" PRIu64, keypos); + subkey.subkey = {}; + return ret; + } + + if (!skip_pgp_packets(src, {PGP_PKT_TRUST})) { + return RNP_ERROR_READ; + } + + return process_pgp_key_signatures(src, subkey.signatures, skiperrors); +} + +rnp_result_t +process_pgp_key_auto(pgp_source_t & src, + pgp_transferable_key_t &key, + bool allowsub, + bool skiperrors) +{ + key = {}; + uint64_t srcpos = src.readb; + int ptag = stream_pkt_type(src); + if (is_subkey_pkt(ptag) && allowsub) { + pgp_transferable_subkey_t subkey; + rnp_result_t ret = process_pgp_subkey(src, subkey, skiperrors); + if (subkey.subkey.tag != PGP_PKT_RESERVED) { + try { + key.subkeys.push_back(std::move(subkey)); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + ret = RNP_ERROR_OUT_OF_MEMORY; + } + } + /* change error code if we didn't process anything at all */ + if (srcpos == src.readb) { + ret = RNP_ERROR_BAD_STATE; + } + return ret; + } + + rnp_result_t ret = RNP_ERROR_BAD_FORMAT; + if (!is_primary_key_pkt(ptag)) { + RNP_LOG("wrong key tag: %d at pos %" PRIu64, ptag, src.readb); + } else { + try { + ret = process_pgp_key(src, key, skiperrors); + } catch (const rnp::rnp_exception &e) { + RNP_LOG("%s", e.what()); + ret = e.code(); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + ret = RNP_ERROR_GENERIC; + } + } + if (skiperrors && (ret == RNP_ERROR_BAD_FORMAT) && + !skip_pgp_packets(src, + {PGP_PKT_TRUST, + PGP_PKT_SIGNATURE, + PGP_PKT_USER_ID, + PGP_PKT_USER_ATTR, + PGP_PKT_PUBLIC_SUBKEY, + PGP_PKT_SECRET_SUBKEY})) { + ret = RNP_ERROR_READ; + } + /* change error code if we didn't process anything at all */ + if (srcpos == src.readb) { + ret = RNP_ERROR_BAD_STATE; + } + return ret; +} + +rnp_result_t +process_pgp_keys(pgp_source_t &src, pgp_key_sequence_t &keys, bool skiperrors) +{ + bool has_secret = false; + bool has_public = false; + + keys.keys.clear(); + /* create maybe-armored stream */ + rnp::ArmoredSource armor( + src, rnp::ArmoredSource::AllowBinary | rnp::ArmoredSource::AllowMultiple); + + /* read sequence of transferable OpenPGP keys as described in RFC 4880, 11.1 - 11.2 */ + while (!armor.error()) { + /* Allow multiple armored messages in a single stream */ + if (armor.eof() && armor.multiple()) { + armor.restart(); + } + if (armor.eof()) { + break; + } + /* Attempt to read the next key */ + pgp_transferable_key_t curkey; + rnp_result_t ret = process_pgp_key_auto(armor.src(), curkey, false, skiperrors); + if (ret && (!skiperrors || (ret != RNP_ERROR_BAD_FORMAT))) { + keys.keys.clear(); + return ret; + } + /* check whether we actually read any key or just skipped erroneous packets */ + if (curkey.key.tag == PGP_PKT_RESERVED) { + continue; + } + has_secret |= (curkey.key.tag == PGP_PKT_SECRET_KEY); + has_public |= (curkey.key.tag == PGP_PKT_PUBLIC_KEY); + + keys.keys.emplace_back(std::move(curkey)); + } + + if (has_secret && has_public) { + RNP_LOG("warning! public keys are mixed together with secret ones!"); + } + + if (armor.error()) { + keys.keys.clear(); + return RNP_ERROR_READ; + } + return RNP_SUCCESS; +} + +rnp_result_t +process_pgp_key(pgp_source_t &src, pgp_transferable_key_t &key, bool skiperrors) +{ + key = pgp_transferable_key_t(); + /* create maybe-armored stream */ + rnp::ArmoredSource armor( + src, rnp::ArmoredSource::AllowBinary | rnp::ArmoredSource::AllowMultiple); + + /* main key packet */ + uint64_t keypos = armor.readb(); + int ptag = stream_pkt_type(armor.src()); + if ((ptag <= 0) || !is_primary_key_pkt(ptag)) { + RNP_LOG("wrong key packet tag: %d at %" PRIu64, ptag, keypos); + return RNP_ERROR_BAD_FORMAT; + } + + rnp_result_t ret = key.key.parse(armor.src()); + if (ret) { + RNP_LOG("failed to parse key pkt at %" PRIu64, keypos); + key.key = {}; + return ret; + } + + if (!skip_pgp_packets(armor.src(), {PGP_PKT_TRUST})) { + return RNP_ERROR_READ; + } + + /* direct-key signatures */ + if ((ret = process_pgp_key_signatures(armor.src(), key.signatures, skiperrors))) { + return ret; + } + + /* user ids/attrs with signatures */ + while ((ptag = stream_pkt_type(armor.src())) > 0) { + if ((ptag != PGP_PKT_USER_ID) && (ptag != PGP_PKT_USER_ATTR)) { + break; + } + + pgp_transferable_userid_t uid; + ret = process_pgp_userid(armor.src(), uid, skiperrors); + if ((ret == RNP_ERROR_BAD_FORMAT) && skiperrors && + skip_pgp_packets(armor.src(), {PGP_PKT_TRUST, PGP_PKT_SIGNATURE})) { + /* skip malformed uid */ + continue; + } + if (ret) { + return ret; + } + key.userids.push_back(std::move(uid)); + } + + /* subkeys with signatures */ + while ((ptag = stream_pkt_type(armor.src())) > 0) { + if (!is_subkey_pkt(ptag)) { + break; + } + + pgp_transferable_subkey_t subkey; + ret = process_pgp_subkey(armor.src(), subkey, skiperrors); + if ((ret == RNP_ERROR_BAD_FORMAT) && skiperrors && + skip_pgp_packets(armor.src(), {PGP_PKT_TRUST, PGP_PKT_SIGNATURE})) { + /* skip malformed subkey */ + continue; + } + if (ret) { + return ret; + } + key.subkeys.emplace_back(std::move(subkey)); + } + return ptag >= 0 ? RNP_SUCCESS : RNP_ERROR_BAD_FORMAT; +} + +static rnp_result_t +decrypt_secret_key_v3(pgp_crypt_t *crypt, uint8_t *dec, const uint8_t *enc, size_t len) +{ + size_t idx; + size_t pos = 0; + size_t mpilen; + size_t blsize; + + if (!(blsize = pgp_cipher_block_size(crypt))) { + RNP_LOG("wrong crypto"); + return RNP_ERROR_BAD_STATE; + } + + /* 4 RSA secret mpis with cleartext header */ + for (idx = 0; idx < 4; idx++) { + if (pos + 2 > len) { + RNP_LOG("bad v3 secret key data"); + return RNP_ERROR_BAD_FORMAT; + } + mpilen = (read_uint16(enc + pos) + 7) >> 3; + memcpy(dec + pos, enc + pos, 2); + pos += 2; + if (pos + mpilen > len) { + RNP_LOG("bad v3 secret key data"); + return RNP_ERROR_BAD_FORMAT; + } + pgp_cipher_cfb_decrypt(crypt, dec + pos, enc + pos, mpilen); + pos += mpilen; + if (mpilen < blsize) { + RNP_LOG("bad rsa v3 mpi len"); + return RNP_ERROR_BAD_FORMAT; + } + pgp_cipher_cfb_resync(crypt, enc + pos - blsize); + } + + /* sum16 */ + if (pos + 2 != len) { + return RNP_ERROR_BAD_FORMAT; + } + memcpy(dec + pos, enc + pos, 2); + return RNP_SUCCESS; +} + +static rnp_result_t +parse_secret_key_mpis(pgp_key_pkt_t &key, const uint8_t *mpis, size_t len) +{ + if (!mpis) { + return RNP_ERROR_NULL_POINTER; + } + + /* check the cleartext data */ + switch (key.sec_protection.s2k.usage) { + case PGP_S2KU_NONE: + case PGP_S2KU_ENCRYPTED: { + /* calculate and check sum16 of the cleartext */ + if (len < 2) { + RNP_LOG("No space for checksum."); + return RNP_ERROR_BAD_FORMAT; + } + uint16_t sum = 0; + len -= 2; + for (size_t idx = 0; idx < len; idx++) { + sum += mpis[idx]; + } + uint16_t expsum = read_uint16(mpis + len); + if (sum != expsum) { + RNP_LOG("Wrong key checksum, got 0x%X instead of 0x%X.", (int) sum, (int) expsum); + return RNP_ERROR_DECRYPT_FAILED; + } + break; + } + case PGP_S2KU_ENCRYPTED_AND_HASHED: { + if (len < PGP_SHA1_HASH_SIZE) { + RNP_LOG("No space for hash"); + return RNP_ERROR_BAD_FORMAT; + } + /* calculate and check sha1 hash of the cleartext */ + uint8_t hval[PGP_SHA1_HASH_SIZE]; + try { + auto hash = rnp::Hash::create(PGP_HASH_SHA1); + assert(hash->size() == sizeof(hval)); + len -= PGP_SHA1_HASH_SIZE; + hash->add(mpis, len); + if (hash->finish(hval) != PGP_SHA1_HASH_SIZE) { + return RNP_ERROR_BAD_STATE; + } + } catch (const std::exception &e) { + RNP_LOG("hash calculation failed: %s", e.what()); + return RNP_ERROR_BAD_STATE; + } + if (memcmp(hval, mpis + len, PGP_SHA1_HASH_SIZE)) { + return RNP_ERROR_DECRYPT_FAILED; + } + break; + } + default: + RNP_LOG("unknown s2k usage: %d", (int) key.sec_protection.s2k.usage); + return RNP_ERROR_BAD_PARAMETERS; + } + + try { + /* parse mpis depending on algorithm */ + pgp_packet_body_t body(mpis, len); + + switch (key.alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + if (!body.get(key.material.rsa.d) || !body.get(key.material.rsa.p) || + !body.get(key.material.rsa.q) || !body.get(key.material.rsa.u)) { + RNP_LOG("failed to parse rsa secret key data"); + return RNP_ERROR_BAD_FORMAT; + } + break; + case PGP_PKA_DSA: + if (!body.get(key.material.dsa.x)) { + RNP_LOG("failed to parse dsa secret key data"); + return RNP_ERROR_BAD_FORMAT; + } + break; + case PGP_PKA_EDDSA: + case PGP_PKA_ECDSA: + case PGP_PKA_SM2: + case PGP_PKA_ECDH: + if (!body.get(key.material.ec.x)) { + RNP_LOG("failed to parse ecc secret key data"); + return RNP_ERROR_BAD_FORMAT; + } + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + if (!body.get(key.material.eg.x)) { + RNP_LOG("failed to parse eg secret key data"); + return RNP_ERROR_BAD_FORMAT; + } + break; + default: + RNP_LOG("unknown pk alg : %d", (int) key.alg); + return RNP_ERROR_BAD_PARAMETERS; + } + + if (body.left()) { + RNP_LOG("extra data in sec key"); + return RNP_ERROR_BAD_FORMAT; + } + key.material.secret = true; + return RNP_SUCCESS; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_GENERIC; + } +} + +rnp_result_t +decrypt_secret_key(pgp_key_pkt_t *key, const char *password) +{ + if (!key) { + return RNP_ERROR_NULL_POINTER; + } + if (!is_secret_key_pkt(key->tag)) { + return RNP_ERROR_BAD_PARAMETERS; + } + /* mark material as not validated as it may be valid for public part */ + key->material.validity.reset(); + + /* check whether data is not encrypted */ + if (!key->sec_protection.s2k.usage) { + return parse_secret_key_mpis(*key, key->sec_data, key->sec_len); + } + + /* check whether secret key data present */ + if (!key->sec_len) { + RNP_LOG("No secret key data"); + return RNP_ERROR_BAD_PARAMETERS; + } + + /* data is encrypted */ + if (!password) { + return RNP_ERROR_NULL_POINTER; + } + + if (key->sec_protection.cipher_mode != PGP_CIPHER_MODE_CFB) { + RNP_LOG("unsupported secret key encryption mode"); + return RNP_ERROR_BAD_PARAMETERS; + } + + rnp::secure_array<uint8_t, PGP_MAX_KEY_SIZE> keybuf; + size_t keysize = pgp_key_size(key->sec_protection.symm_alg); + if (!keysize || + !pgp_s2k_derive_key(&key->sec_protection.s2k, password, keybuf.data(), keysize)) { + RNP_LOG("failed to derive key"); + return RNP_ERROR_BAD_PARAMETERS; + } + + try { + rnp::secure_vector<uint8_t> decdata(key->sec_len); + pgp_crypt_t crypt; + if (!pgp_cipher_cfb_start( + &crypt, key->sec_protection.symm_alg, keybuf.data(), key->sec_protection.iv)) { + RNP_LOG("failed to start cfb decryption"); + return RNP_ERROR_DECRYPT_FAILED; + } + + rnp_result_t ret = RNP_ERROR_GENERIC; + switch (key->version) { + case PGP_V3: + if (!is_rsa_key_alg(key->alg)) { + RNP_LOG("non-RSA v3 key"); + ret = RNP_ERROR_BAD_PARAMETERS; + break; + } + ret = decrypt_secret_key_v3(&crypt, decdata.data(), key->sec_data, key->sec_len); + break; + case PGP_V4: + pgp_cipher_cfb_decrypt(&crypt, decdata.data(), key->sec_data, key->sec_len); + ret = RNP_SUCCESS; + break; + default: + ret = RNP_ERROR_BAD_PARAMETERS; + } + + pgp_cipher_cfb_finish(&crypt); + if (ret) { + return ret; + } + + return parse_secret_key_mpis(*key, decdata.data(), key->sec_len); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_GENERIC; + } +} + +static void +write_secret_key_mpis(pgp_packet_body_t &body, pgp_key_pkt_t &key) +{ + /* add mpis */ + switch (key.alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + body.add(key.material.rsa.d); + body.add(key.material.rsa.p); + body.add(key.material.rsa.q); + body.add(key.material.rsa.u); + break; + case PGP_PKA_DSA: + body.add(key.material.dsa.x); + break; + case PGP_PKA_EDDSA: + case PGP_PKA_ECDSA: + case PGP_PKA_SM2: + case PGP_PKA_ECDH: + body.add(key.material.ec.x); + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + body.add(key.material.eg.x); + break; + default: + RNP_LOG("unknown pk alg : %d", (int) key.alg); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + /* add sum16 if sha1 is not used */ + if (key.sec_protection.s2k.usage != PGP_S2KU_ENCRYPTED_AND_HASHED) { + uint16_t sum = 0; + for (size_t i = 0; i < body.size(); i++) { + sum += body.data()[i]; + } + body.add_uint16(sum); + return; + } + + /* add sha1 hash */ + auto hash = rnp::Hash::create(PGP_HASH_SHA1); + hash->add(body.data(), body.size()); + uint8_t hval[PGP_SHA1_HASH_SIZE]; + assert(sizeof(hval) == hash->size()); + if (hash->finish(hval) != PGP_SHA1_HASH_SIZE) { + RNP_LOG("failed to finish hash"); + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + body.add(hval, PGP_SHA1_HASH_SIZE); +} + +rnp_result_t +encrypt_secret_key(pgp_key_pkt_t *key, const char *password, rnp::RNG &rng) +{ + if (!is_secret_key_pkt(key->tag) || !key->material.secret) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (key->sec_protection.s2k.usage && + (key->sec_protection.cipher_mode != PGP_CIPHER_MODE_CFB)) { + RNP_LOG("unsupported secret key encryption mode"); + return RNP_ERROR_BAD_PARAMETERS; + } + + try { + /* build secret key data */ + pgp_packet_body_t body(PGP_PKT_RESERVED); + body.mark_secure(); + write_secret_key_mpis(body, *key); + + /* check whether data is not encrypted */ + if (key->sec_protection.s2k.usage == PGP_S2KU_NONE) { + secure_clear(key->sec_data, key->sec_len); + free(key->sec_data); + key->sec_data = (uint8_t *) malloc(body.size()); + if (!key->sec_data) { + RNP_LOG("allocation failed"); + return RNP_ERROR_OUT_OF_MEMORY; + } + memcpy(key->sec_data, body.data(), body.size()); + key->sec_len = body.size(); + return RNP_SUCCESS; + } + if (key->version < PGP_V4) { + RNP_LOG("encryption of v3 keys is not supported"); + return RNP_ERROR_BAD_PARAMETERS; + } + + /* data is encrypted */ + size_t keysize = pgp_key_size(key->sec_protection.symm_alg); + size_t blsize = pgp_block_size(key->sec_protection.symm_alg); + if (!keysize || !blsize) { + RNP_LOG("wrong symm alg"); + return RNP_ERROR_BAD_PARAMETERS; + } + /* generate iv and s2k salt */ + rng.get(key->sec_protection.iv, blsize); + if ((key->sec_protection.s2k.specifier != PGP_S2KS_SIMPLE)) { + rng.get(key->sec_protection.s2k.salt, PGP_SALT_SIZE); + } + /* derive key */ + rnp::secure_array<uint8_t, PGP_MAX_KEY_SIZE> keybuf; + if (!pgp_s2k_derive_key(&key->sec_protection.s2k, password, keybuf.data(), keysize)) { + RNP_LOG("failed to derive key"); + return RNP_ERROR_BAD_PARAMETERS; + } + /* encrypt sec data */ + pgp_crypt_t crypt; + if (!pgp_cipher_cfb_start( + &crypt, key->sec_protection.symm_alg, keybuf.data(), key->sec_protection.iv)) { + RNP_LOG("failed to start cfb encryption"); + return RNP_ERROR_DECRYPT_FAILED; + } + pgp_cipher_cfb_encrypt(&crypt, body.data(), body.data(), body.size()); + pgp_cipher_cfb_finish(&crypt); + secure_clear(key->sec_data, key->sec_len); + free(key->sec_data); + key->sec_data = (uint8_t *) malloc(body.size()); + if (!key->sec_data) { + RNP_LOG("allocation failed"); + return RNP_ERROR_OUT_OF_MEMORY; + } + memcpy(key->sec_data, body.data(), body.size()); + key->sec_len = body.size(); + /* cleanup cleartext fields */ + forget_secret_key_fields(&key->material); + return RNP_SUCCESS; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_GENERIC; + } +} + +void +forget_secret_key_fields(pgp_key_material_t *key) +{ + if (!key || !key->secret) { + return; + } + + switch (key->alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + mpi_forget(&key->rsa.d); + mpi_forget(&key->rsa.p); + mpi_forget(&key->rsa.q); + mpi_forget(&key->rsa.u); + break; + case PGP_PKA_DSA: + mpi_forget(&key->dsa.x); + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + mpi_forget(&key->eg.x); + break; + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: + case PGP_PKA_SM2: + case PGP_PKA_ECDH: + mpi_forget(&key->ec.x); + break; + default: + RNP_LOG("unknown key algorithm: %d", (int) key->alg); + } + + key->secret = false; +} + +pgp_userid_pkt_t::pgp_userid_pkt_t(const pgp_userid_pkt_t &src) +{ + tag = src.tag; + uid_len = src.uid_len; + uid = NULL; + if (src.uid) { + uid = (uint8_t *) malloc(uid_len); + if (!uid) { + throw std::bad_alloc(); + } + memcpy(uid, src.uid, uid_len); + } +} + +pgp_userid_pkt_t::pgp_userid_pkt_t(pgp_userid_pkt_t &&src) +{ + tag = src.tag; + uid_len = src.uid_len; + uid = src.uid; + src.uid = NULL; +} + +pgp_userid_pkt_t & +pgp_userid_pkt_t::operator=(pgp_userid_pkt_t &&src) +{ + if (this == &src) { + return *this; + } + tag = src.tag; + uid_len = src.uid_len; + free(uid); + uid = src.uid; + src.uid = NULL; + return *this; +} + +pgp_userid_pkt_t & +pgp_userid_pkt_t::operator=(const pgp_userid_pkt_t &src) +{ + if (this == &src) { + return *this; + } + tag = src.tag; + uid_len = src.uid_len; + free(uid); + uid = NULL; + if (src.uid) { + uid = (uint8_t *) malloc(uid_len); + if (!uid) { + throw std::bad_alloc(); + } + memcpy(uid, src.uid, uid_len); + } + return *this; +} + +bool +pgp_userid_pkt_t::operator==(const pgp_userid_pkt_t &src) const +{ + return (tag == src.tag) && (uid_len == src.uid_len) && !memcmp(uid, src.uid, uid_len); +} + +bool +pgp_userid_pkt_t::operator!=(const pgp_userid_pkt_t &src) const +{ + return !(*this == src); +} + +pgp_userid_pkt_t::~pgp_userid_pkt_t() +{ + free(uid); +} + +void +pgp_userid_pkt_t::write(pgp_dest_t &dst) const +{ + if ((tag != PGP_PKT_USER_ID) && (tag != PGP_PKT_USER_ATTR)) { + RNP_LOG("wrong userid tag"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + if (uid_len && !uid) { + RNP_LOG("null but non-empty userid"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + pgp_packet_body_t pktbody(tag); + if (uid) { + pktbody.add(uid, uid_len); + } + pktbody.write(dst); +} + +rnp_result_t +pgp_userid_pkt_t::parse(pgp_source_t &src) +{ + /* check the tag */ + int stag = stream_pkt_type(src); + if ((stag != PGP_PKT_USER_ID) && (stag != PGP_PKT_USER_ATTR)) { + RNP_LOG("wrong userid tag: %d", stag); + return RNP_ERROR_BAD_FORMAT; + } + + pgp_packet_body_t pkt(PGP_PKT_RESERVED); + rnp_result_t res = pkt.read(src); + if (res) { + return res; + } + + /* userid type, i.e. tag */ + tag = (pgp_pkt_type_t) stag; + free(uid); + uid = (uint8_t *) malloc(pkt.size()); + if (!uid) { + RNP_LOG("allocation failed"); + return RNP_ERROR_OUT_OF_MEMORY; + } + memcpy(uid, pkt.data(), pkt.size()); + uid_len = pkt.size(); + return RNP_SUCCESS; +} + +pgp_key_pkt_t::pgp_key_pkt_t(const pgp_key_pkt_t &src, bool pubonly) +{ + if (pubonly && is_secret_key_pkt(src.tag)) { + tag = (src.tag == PGP_PKT_SECRET_KEY) ? PGP_PKT_PUBLIC_KEY : PGP_PKT_PUBLIC_SUBKEY; + } else { + tag = src.tag; + } + version = src.version; + creation_time = src.creation_time; + alg = src.alg; + v3_days = src.v3_days; + hashed_len = src.hashed_len; + hashed_data = NULL; + if (src.hashed_data) { + hashed_data = (uint8_t *) malloc(hashed_len); + if (!hashed_data) { + throw std::bad_alloc(); + } + memcpy(hashed_data, src.hashed_data, hashed_len); + } + material = src.material; + if (pubonly) { + forget_secret_key_fields(&material); + sec_len = 0; + sec_data = NULL; + sec_protection = {}; + return; + } + sec_len = src.sec_len; + sec_data = NULL; + if (src.sec_data) { + sec_data = (uint8_t *) malloc(sec_len); + if (!sec_data) { + free(hashed_data); + hashed_data = NULL; + throw std::bad_alloc(); + } + memcpy(sec_data, src.sec_data, sec_len); + } + sec_protection = src.sec_protection; +} + +pgp_key_pkt_t::pgp_key_pkt_t(pgp_key_pkt_t &&src) +{ + tag = src.tag; + version = src.version; + creation_time = src.creation_time; + alg = src.alg; + v3_days = src.v3_days; + hashed_len = src.hashed_len; + hashed_data = src.hashed_data; + src.hashed_data = NULL; + material = src.material; + forget_secret_key_fields(&src.material); + sec_len = src.sec_len; + sec_data = src.sec_data; + src.sec_data = NULL; + sec_protection = src.sec_protection; +} + +pgp_key_pkt_t & +pgp_key_pkt_t::operator=(pgp_key_pkt_t &&src) +{ + if (this == &src) { + return *this; + } + tag = src.tag; + version = src.version; + creation_time = src.creation_time; + alg = src.alg; + v3_days = src.v3_days; + hashed_len = src.hashed_len; + free(hashed_data); + hashed_data = src.hashed_data; + src.hashed_data = NULL; + material = src.material; + forget_secret_key_fields(&src.material); + secure_clear(sec_data, sec_len); + free(sec_data); + sec_len = src.sec_len; + sec_data = src.sec_data; + src.sec_data = NULL; + src.sec_len = 0; + sec_protection = src.sec_protection; + return *this; +} + +pgp_key_pkt_t & +pgp_key_pkt_t::operator=(const pgp_key_pkt_t &src) +{ + if (this == &src) { + return *this; + } + tag = src.tag; + version = src.version; + creation_time = src.creation_time; + alg = src.alg; + v3_days = src.v3_days; + hashed_len = src.hashed_len; + free(hashed_data); + hashed_data = NULL; + if (src.hashed_data) { + hashed_data = (uint8_t *) malloc(hashed_len); + if (!hashed_data) { + throw std::bad_alloc(); + } + memcpy(hashed_data, src.hashed_data, hashed_len); + } + material = src.material; + secure_clear(sec_data, sec_len); + free(sec_data); + sec_data = NULL; + sec_len = src.sec_len; + if (src.sec_data) { + sec_data = (uint8_t *) malloc(sec_len); + if (!sec_data) { + free(hashed_data); + hashed_data = NULL; + throw std::bad_alloc(); + } + memcpy(sec_data, src.sec_data, sec_len); + } + sec_protection = src.sec_protection; + return *this; +} + +pgp_key_pkt_t::~pgp_key_pkt_t() +{ + forget_secret_key_fields(&material); + free(hashed_data); + secure_clear(sec_data, sec_len); + free(sec_data); +} + +void +pgp_key_pkt_t::write(pgp_dest_t &dst) +{ + if (!is_key_pkt(tag)) { + RNP_LOG("wrong key tag"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + if (!hashed_data) { + fill_hashed_data(); + } + + pgp_packet_body_t pktbody(tag); + /* all public key data is written in hashed_data */ + pktbody.add(hashed_data, hashed_len); + /* if we have public key then we do not need further processing */ + if (!is_secret_key_pkt(tag)) { + pktbody.write(dst); + return; + } + + /* secret key fields should be pre-populated in sec_data field */ + if ((sec_protection.s2k.specifier != PGP_S2KS_EXPERIMENTAL) && (!sec_data || !sec_len)) { + RNP_LOG("secret key data is not populated"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + pktbody.add_byte(sec_protection.s2k.usage); + + switch (sec_protection.s2k.usage) { + case PGP_S2KU_NONE: + break; + case PGP_S2KU_ENCRYPTED_AND_HASHED: + case PGP_S2KU_ENCRYPTED: { + pktbody.add_byte(sec_protection.symm_alg); + pktbody.add(sec_protection.s2k); + if (sec_protection.s2k.specifier != PGP_S2KS_EXPERIMENTAL) { + size_t blsize = pgp_block_size(sec_protection.symm_alg); + if (!blsize) { + RNP_LOG("wrong block size"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + pktbody.add(sec_protection.iv, blsize); + } + break; + } + default: + RNP_LOG("wrong s2k usage"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + if (sec_len) { + /* if key is stored on card, or exported via gpg --export-secret-subkeys, then + * sec_data is empty */ + pktbody.add(sec_data, sec_len); + } + pktbody.write(dst); +} + +rnp_result_t +pgp_key_pkt_t::parse(pgp_source_t &src) +{ + /* check the key tag */ + int atag = stream_pkt_type(src); + if (!is_key_pkt(atag)) { + RNP_LOG("wrong key packet tag: %d", atag); + return RNP_ERROR_BAD_FORMAT; + } + + pgp_packet_body_t pkt((pgp_pkt_type_t) atag); + /* Read the packet into memory */ + rnp_result_t res = pkt.read(src); + if (res) { + return res; + } + /* key type, i.e. tag */ + tag = (pgp_pkt_type_t) atag; + /* version */ + uint8_t ver = 0; + if (!pkt.get(ver) || (ver < PGP_V2) || (ver > PGP_V4)) { + RNP_LOG("wrong key packet version"); + return RNP_ERROR_BAD_FORMAT; + } + version = (pgp_version_t) ver; + /* creation time */ + if (!pkt.get(creation_time)) { + return RNP_ERROR_BAD_FORMAT; + } + /* v3: validity days */ + if ((version < PGP_V4) && !pkt.get(v3_days)) { + return RNP_ERROR_BAD_FORMAT; + } + /* key algorithm */ + uint8_t analg = 0; + if (!pkt.get(analg)) { + return RNP_ERROR_BAD_FORMAT; + } + alg = (pgp_pubkey_alg_t) analg; + material.alg = (pgp_pubkey_alg_t) analg; + /* v3 keys must be RSA-only */ + if ((version < PGP_V4) && !is_rsa_key_alg(alg)) { + RNP_LOG("wrong v3 pk algorithm"); + return RNP_ERROR_BAD_FORMAT; + } + /* algorithm specific fields */ + switch (alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + if (!pkt.get(material.rsa.n) || !pkt.get(material.rsa.e)) { + return RNP_ERROR_BAD_FORMAT; + } + break; + case PGP_PKA_DSA: + if (!pkt.get(material.dsa.p) || !pkt.get(material.dsa.q) || !pkt.get(material.dsa.g) || + !pkt.get(material.dsa.y)) { + return RNP_ERROR_BAD_FORMAT; + } + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + if (!pkt.get(material.eg.p) || !pkt.get(material.eg.g) || !pkt.get(material.eg.y)) { + return RNP_ERROR_BAD_FORMAT; + } + break; + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: + case PGP_PKA_SM2: + if (!pkt.get(material.ec.curve) || !pkt.get(material.ec.p)) { + return RNP_ERROR_BAD_FORMAT; + } + break; + case PGP_PKA_ECDH: { + if (!pkt.get(material.ec.curve) || !pkt.get(material.ec.p)) { + return RNP_ERROR_BAD_FORMAT; + } + /* read KDF parameters. At the moment should be 0x03 0x01 halg ealg */ + uint8_t len = 0, halg = 0, walg = 0; + if (!pkt.get(len) || (len != 3)) { + return RNP_ERROR_BAD_FORMAT; + } + if (!pkt.get(len) || (len != 1)) { + return RNP_ERROR_BAD_FORMAT; + } + if (!pkt.get(halg) || !pkt.get(walg)) { + return RNP_ERROR_BAD_FORMAT; + } + material.ec.kdf_hash_alg = (pgp_hash_alg_t) halg; + material.ec.key_wrap_alg = (pgp_symm_alg_t) walg; + break; + } + default: + RNP_LOG("unknown key algorithm: %d", (int) alg); + return RNP_ERROR_BAD_FORMAT; + } + /* fill hashed data used for signatures */ + if (!(hashed_data = (uint8_t *) malloc(pkt.size() - pkt.left()))) { + RNP_LOG("allocation failed"); + return RNP_ERROR_OUT_OF_MEMORY; + } + memcpy(hashed_data, pkt.data(), pkt.size() - pkt.left()); + hashed_len = pkt.size() - pkt.left(); + + /* secret key fields if any */ + if (is_secret_key_pkt(tag)) { + uint8_t usage = 0; + if (!pkt.get(usage)) { + RNP_LOG("failed to read key protection"); + return RNP_ERROR_BAD_FORMAT; + } + sec_protection.s2k.usage = (pgp_s2k_usage_t) usage; + sec_protection.cipher_mode = PGP_CIPHER_MODE_CFB; + + switch (sec_protection.s2k.usage) { + case PGP_S2KU_NONE: + break; + case PGP_S2KU_ENCRYPTED: + case PGP_S2KU_ENCRYPTED_AND_HASHED: { + /* we have s2k */ + uint8_t salg = 0; + if (!pkt.get(salg) || !pkt.get(sec_protection.s2k)) { + RNP_LOG("failed to read key protection"); + return RNP_ERROR_BAD_FORMAT; + } + sec_protection.symm_alg = (pgp_symm_alg_t) salg; + break; + } + default: + /* old-style: usage is symmetric algorithm identifier */ + sec_protection.symm_alg = (pgp_symm_alg_t) usage; + sec_protection.s2k.usage = PGP_S2KU_ENCRYPTED; + sec_protection.s2k.specifier = PGP_S2KS_SIMPLE; + sec_protection.s2k.hash_alg = PGP_HASH_MD5; + break; + } + + /* iv */ + if (sec_protection.s2k.usage && + (sec_protection.s2k.specifier != PGP_S2KS_EXPERIMENTAL)) { + size_t bl_size = pgp_block_size(sec_protection.symm_alg); + if (!bl_size || !pkt.get(sec_protection.iv, bl_size)) { + RNP_LOG("failed to read iv"); + return RNP_ERROR_BAD_FORMAT; + } + } + + /* encrypted/cleartext secret MPIs are left */ + size_t asec_len = pkt.left(); + if (!asec_len) { + sec_data = NULL; + } else { + if (!(sec_data = (uint8_t *) calloc(1, asec_len))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + if (!pkt.get(sec_data, asec_len)) { + return RNP_ERROR_BAD_STATE; + } + } + sec_len = asec_len; + } + + if (pkt.left()) { + RNP_LOG("extra %d bytes in key packet", (int) pkt.left()); + return RNP_ERROR_BAD_FORMAT; + } + return RNP_SUCCESS; +} + +void +pgp_key_pkt_t::fill_hashed_data() +{ + /* we don't have a need to write v2-v3 signatures */ + if (version != PGP_V4) { + RNP_LOG("unknown key version %d", (int) version); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + pgp_packet_body_t hbody(PGP_PKT_RESERVED); + hbody.add_byte(version); + hbody.add_uint32(creation_time); + hbody.add_byte(alg); + /* Algorithm specific fields */ + switch (alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + hbody.add(material.rsa.n); + hbody.add(material.rsa.e); + break; + case PGP_PKA_DSA: + hbody.add(material.dsa.p); + hbody.add(material.dsa.q); + hbody.add(material.dsa.g); + hbody.add(material.dsa.y); + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + hbody.add(material.eg.p); + hbody.add(material.eg.g); + hbody.add(material.eg.y); + break; + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: + case PGP_PKA_SM2: + hbody.add(material.ec.curve); + hbody.add(material.ec.p); + break; + case PGP_PKA_ECDH: + hbody.add(material.ec.curve); + hbody.add(material.ec.p); + hbody.add_byte(3); + hbody.add_byte(1); + hbody.add_byte(material.ec.kdf_hash_alg); + hbody.add_byte(material.ec.key_wrap_alg); + break; + default: + RNP_LOG("unknown key algorithm: %d", (int) alg); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + hashed_data = (uint8_t *) malloc(hbody.size()); + if (!hashed_data) { + RNP_LOG("allocation failed"); + throw rnp::rnp_exception(RNP_ERROR_OUT_OF_MEMORY); + } + memcpy(hashed_data, hbody.data(), hbody.size()); + hashed_len = hbody.size(); +} + +bool +pgp_key_pkt_t::equals(const pgp_key_pkt_t &key, bool pubonly) const noexcept +{ + /* check tag. We allow public/secret key comparison here */ + if (pubonly) { + if (is_subkey_pkt(tag) && !is_subkey_pkt(key.tag)) { + return false; + } + if (is_key_pkt(tag) && !is_key_pkt(key.tag)) { + return false; + } + } else if (tag != key.tag) { + return false; + } + /* check basic fields */ + if ((version != key.version) || (alg != key.alg) || (creation_time != key.creation_time)) { + return false; + } + /* check key material */ + return key_material_equal(&material, &key.material); +} + +pgp_transferable_subkey_t::pgp_transferable_subkey_t(const pgp_transferable_subkey_t &src, + bool pubonly) +{ + subkey = pgp_key_pkt_t(src.subkey, pubonly); + signatures = src.signatures; +} + +pgp_transferable_key_t::pgp_transferable_key_t(const pgp_transferable_key_t &src, bool pubonly) +{ + key = pgp_key_pkt_t(src.key, pubonly); + userids = src.userids; + subkeys = src.subkeys; + signatures = src.signatures; +} diff --git a/comm/third_party/rnp/src/librepgp/stream-key.h b/comm/third_party/rnp/src/librepgp/stream-key.h new file mode 100644 index 0000000000..a19a986455 --- /dev/null +++ b/comm/third_party/rnp/src/librepgp/stream-key.h @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2018, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef STREAM_KEY_H_ +#define STREAM_KEY_H_ + +#include <stdint.h> +#include <stdbool.h> +#include <sys/types.h> +#include "rnp.h" +#include "stream-common.h" +#include "stream-sig.h" +#include "stream-packet.h" + +/** Struct to hold a key packet. May contain public or private key/subkey */ +typedef struct pgp_key_pkt_t { + pgp_pkt_type_t tag; /* packet tag: public key/subkey or private key/subkey */ + pgp_version_t version; /* Key packet version */ + uint32_t creation_time; /* Key creation time */ + pgp_pubkey_alg_t alg; + uint16_t v3_days; /* v2/v3 validity time */ + + uint8_t *hashed_data; /* key's hashed data used for signature calculation */ + size_t hashed_len; + + pgp_key_material_t material; + + /* secret key data, if available. sec_len == 0, sec_data == NULL for public key/subkey */ + pgp_key_protection_t sec_protection; + uint8_t * sec_data; + size_t sec_len; + + pgp_key_pkt_t() + : tag(PGP_PKT_RESERVED), version(PGP_VUNKNOWN), creation_time(0), alg(PGP_PKA_NOTHING), + v3_days(0), hashed_data(NULL), hashed_len(0), material({}), sec_protection({}), + sec_data(NULL), sec_len(0){}; + pgp_key_pkt_t(const pgp_key_pkt_t &src, bool pubonly = false); + pgp_key_pkt_t(pgp_key_pkt_t &&src); + pgp_key_pkt_t &operator=(pgp_key_pkt_t &&src); + pgp_key_pkt_t &operator=(const pgp_key_pkt_t &src); + ~pgp_key_pkt_t(); + + void write(pgp_dest_t &dst); + rnp_result_t parse(pgp_source_t &src); + /** @brief Fills the hashed (signed) data part of the key packet. Must be called before + * pgp_key_pkt_t::write() on the newly generated key */ + void fill_hashed_data(); + bool equals(const pgp_key_pkt_t &key, bool pubonly = false) const noexcept; +} pgp_key_pkt_t; + +/* userid/userattr with all the corresponding signatures */ +typedef struct pgp_transferable_userid_t { + pgp_userid_pkt_t uid; + pgp_signature_list_t signatures; +} pgp_transferable_userid_t; + +/* subkey with all corresponding signatures */ +typedef struct pgp_transferable_subkey_t { + pgp_key_pkt_t subkey; + pgp_signature_list_t signatures; + + pgp_transferable_subkey_t() = default; + pgp_transferable_subkey_t(const pgp_transferable_subkey_t &src, bool pubonly = false); + pgp_transferable_subkey_t &operator=(const pgp_transferable_subkey_t &) = default; +} pgp_transferable_subkey_t; + +/* transferable key with userids, subkeys and revocation signatures */ +typedef struct pgp_transferable_key_t { + pgp_key_pkt_t key; /* main key packet */ + std::vector<pgp_transferable_userid_t> userids; + std::vector<pgp_transferable_subkey_t> subkeys; + pgp_signature_list_t signatures; + + pgp_transferable_key_t() = default; + pgp_transferable_key_t(const pgp_transferable_key_t &src, bool pubonly = false); + pgp_transferable_key_t &operator=(const pgp_transferable_key_t &) = default; +} pgp_transferable_key_t; + +/* sequence of OpenPGP transferable keys */ +typedef struct pgp_key_sequence_t { + std::vector<pgp_transferable_key_t> keys; +} pgp_key_sequence_t; + +rnp_result_t transferable_key_from_key(pgp_transferable_key_t &dst, const pgp_key_t &key); + +rnp_result_t transferable_key_merge(pgp_transferable_key_t & dst, + const pgp_transferable_key_t &src); + +rnp_result_t transferable_subkey_from_key(pgp_transferable_subkey_t &dst, + const pgp_key_t & key); + +rnp_result_t transferable_subkey_merge(pgp_transferable_subkey_t & dst, + const pgp_transferable_subkey_t &src); + +/* Process single primary key or subkey, skipping all key-related packets on error. + If key.key.tag is zero, then (on success) result is subkey and it is stored in + key.subkeys[0]. + If returns RNP_ERROR_BAD_FORMAT then some packets failed parsing, but still key may contain + successfully read key or subkey. +*/ +rnp_result_t process_pgp_key_auto(pgp_source_t & src, + pgp_transferable_key_t &key, + bool allowsub, + bool skiperrors); + +rnp_result_t process_pgp_keys(pgp_source_t &src, pgp_key_sequence_t &keys, bool skiperrors); + +rnp_result_t process_pgp_key(pgp_source_t &src, pgp_transferable_key_t &key, bool skiperrors); + +rnp_result_t process_pgp_subkey(pgp_source_t & src, + pgp_transferable_subkey_t &subkey, + bool skiperrors); + +rnp_result_t decrypt_secret_key(pgp_key_pkt_t *key, const char *password); + +rnp_result_t encrypt_secret_key(pgp_key_pkt_t *key, const char *password, rnp::RNG &rng); + +void forget_secret_key_fields(pgp_key_material_t *key); + +#endif diff --git a/comm/third_party/rnp/src/librepgp/stream-packet.cpp b/comm/third_party/rnp/src/librepgp/stream-packet.cpp new file mode 100644 index 0000000000..49dd63d09c --- /dev/null +++ b/comm/third_party/rnp/src/librepgp/stream-packet.cpp @@ -0,0 +1,1228 @@ +/* + * Copyright (c) 2017-2020, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include <sys/stat.h> +#include <stdlib.h> +#include <stdio.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#else +#include "uniwin.h" +#endif +#include <string.h> +#include <inttypes.h> +#include <rnp/rnp_def.h> +#include "types.h" +#include "crypto.h" +#include "crypto/mem.h" +#include "stream-packet.h" +#include "stream-key.h" +#include <algorithm> + +uint32_t +read_uint32(const uint8_t *buf) +{ + return ((uint32_t) buf[0] << 24) | ((uint32_t) buf[1] << 16) | ((uint32_t) buf[2] << 8) | + (uint32_t) buf[3]; +} + +uint16_t +read_uint16(const uint8_t *buf) +{ + return ((uint16_t) buf[0] << 8) | buf[1]; +} + +void +write_uint16(uint8_t *buf, uint16_t val) +{ + buf[0] = val >> 8; + buf[1] = val & 0xff; +} + +size_t +write_packet_len(uint8_t *buf, size_t len) +{ + if (len < 192) { + buf[0] = len; + return 1; + } else if (len < 8192 + 192) { + buf[0] = ((len - 192) >> 8) + 192; + buf[1] = (len - 192) & 0xff; + return 2; + } else { + buf[0] = 0xff; + STORE32BE(&buf[1], len); + return 5; + } +} + +int +get_packet_type(uint8_t ptag) +{ + if (!(ptag & PGP_PTAG_ALWAYS_SET)) { + return -1; + } + + if (ptag & PGP_PTAG_NEW_FORMAT) { + return (int) (ptag & PGP_PTAG_NF_CONTENT_TAG_MASK); + } else { + return (int) ((ptag & PGP_PTAG_OF_CONTENT_TAG_MASK) >> PGP_PTAG_OF_CONTENT_TAG_SHIFT); + } +} + +int +stream_pkt_type(pgp_source_t &src) +{ + if (src_eof(&src)) { + return 0; + } + size_t hdrneed = 0; + if (!stream_pkt_hdr_len(src, hdrneed)) { + return -1; + } + uint8_t hdr[PGP_MAX_HEADER_SIZE]; + if (!src_peek_eq(&src, hdr, hdrneed)) { + return -1; + } + return get_packet_type(hdr[0]); +} + +bool +stream_pkt_hdr_len(pgp_source_t &src, size_t &hdrlen) +{ + uint8_t buf[2]; + + if (!src_peek_eq(&src, buf, 2) || !(buf[0] & PGP_PTAG_ALWAYS_SET)) { + return false; + } + + if (buf[0] & PGP_PTAG_NEW_FORMAT) { + if (buf[1] < 192) { + hdrlen = 2; + } else if (buf[1] < 224) { + hdrlen = 3; + } else if (buf[1] < 255) { + hdrlen = 2; + } else { + hdrlen = 6; + } + return true; + } + + switch (buf[0] & PGP_PTAG_OF_LENGTH_TYPE_MASK) { + case PGP_PTAG_OLD_LEN_1: + hdrlen = 2; + return true; + case PGP_PTAG_OLD_LEN_2: + hdrlen = 3; + return true; + case PGP_PTAG_OLD_LEN_4: + hdrlen = 5; + return true; + case PGP_PTAG_OLD_LEN_INDETERMINATE: + hdrlen = 1; + return true; + default: + return false; + } +} + +static bool +get_pkt_len(uint8_t *hdr, size_t *pktlen) +{ + if (hdr[0] & PGP_PTAG_NEW_FORMAT) { + // 1-byte length + if (hdr[1] < 192) { + *pktlen = hdr[1]; + return true; + } + // 2-byte length + if (hdr[1] < 224) { + *pktlen = ((size_t)(hdr[1] - 192) << 8) + (size_t) hdr[2] + 192; + return true; + } + // partial length - we do not allow it here + if (hdr[1] < 255) { + return false; + } + // 4-byte length + *pktlen = read_uint32(&hdr[2]); + return true; + } + + switch (hdr[0] & PGP_PTAG_OF_LENGTH_TYPE_MASK) { + case PGP_PTAG_OLD_LEN_1: + *pktlen = hdr[1]; + return true; + case PGP_PTAG_OLD_LEN_2: + *pktlen = read_uint16(&hdr[1]); + return true; + case PGP_PTAG_OLD_LEN_4: + *pktlen = read_uint32(&hdr[1]); + return true; + default: + return false; + } +} + +bool +stream_read_pkt_len(pgp_source_t *src, size_t *pktlen) +{ + uint8_t buf[6] = {}; + size_t read = 0; + + if (!stream_pkt_hdr_len(*src, read)) { + return false; + } + + if (!src_read_eq(src, buf, read)) { + return false; + } + + return get_pkt_len(buf, pktlen); +} + +bool +stream_read_partial_chunk_len(pgp_source_t *src, size_t *clen, bool *last) +{ + uint8_t hdr[5] = {}; + size_t read = 0; + + if (!src_read(src, hdr, 1, &read)) { + RNP_LOG("failed to read header"); + return false; + } + if (read < 1) { + RNP_LOG("wrong eof"); + return false; + } + + *last = true; + // partial length + if ((hdr[0] >= 224) && (hdr[0] < 255)) { + *last = false; + *clen = get_partial_pkt_len(hdr[0]); + return true; + } + // 1-byte length + if (hdr[0] < 192) { + *clen = hdr[0]; + return true; + } + // 2-byte length + if (hdr[0] < 224) { + if (!src_read_eq(src, &hdr[1], 1)) { + RNP_LOG("wrong 2-byte length"); + return false; + } + *clen = ((size_t)(hdr[0] - 192) << 8) + (size_t) hdr[1] + 192; + return true; + } + // 4-byte length + if (!src_read_eq(src, &hdr[1], 4)) { + RNP_LOG("wrong 4-byte length"); + return false; + } + *clen = ((size_t) hdr[1] << 24) | ((size_t) hdr[2] << 16) | ((size_t) hdr[3] << 8) | + (size_t) hdr[4]; + return true; +} + +bool +stream_old_indeterminate_pkt_len(pgp_source_t *src) +{ + uint8_t ptag = 0; + if (!src_peek_eq(src, &ptag, 1)) { + return false; + } + return !(ptag & PGP_PTAG_NEW_FORMAT) && + ((ptag & PGP_PTAG_OF_LENGTH_TYPE_MASK) == PGP_PTAG_OLD_LEN_INDETERMINATE); +} + +bool +stream_partial_pkt_len(pgp_source_t *src) +{ + uint8_t hdr[2] = {}; + if (!src_peek_eq(src, hdr, 2)) { + return false; + } + return (hdr[0] & PGP_PTAG_NEW_FORMAT) && (hdr[1] >= 224) && (hdr[1] < 255); +} + +size_t +get_partial_pkt_len(uint8_t blen) +{ + return 1 << (blen & 0x1f); +} + +rnp_result_t +stream_peek_packet_hdr(pgp_source_t *src, pgp_packet_hdr_t *hdr) +{ + size_t hlen = 0; + memset(hdr, 0, sizeof(*hdr)); + if (!stream_pkt_hdr_len(*src, hlen)) { + uint8_t hdr2[2] = {0}; + if (!src_peek_eq(src, hdr2, 2)) { + RNP_LOG("pkt header read failed"); + return RNP_ERROR_READ; + } + + RNP_LOG("bad packet header: 0x%02x%02x", hdr2[0], hdr2[1]); + return RNP_ERROR_BAD_FORMAT; + } + + if (!src_peek_eq(src, hdr->hdr, hlen)) { + RNP_LOG("failed to read pkt header"); + return RNP_ERROR_READ; + } + + hdr->hdr_len = hlen; + hdr->tag = (pgp_pkt_type_t) get_packet_type(hdr->hdr[0]); + + if (stream_partial_pkt_len(src)) { + hdr->partial = true; + } else if (stream_old_indeterminate_pkt_len(src)) { + hdr->indeterminate = true; + } else { + (void) get_pkt_len(hdr->hdr, &hdr->pkt_len); + } + + return RNP_SUCCESS; +} + +static rnp_result_t +stream_read_packet_partial(pgp_source_t *src, pgp_dest_t *dst) +{ + uint8_t hdr = 0; + if (!src_read_eq(src, &hdr, 1)) { + return RNP_ERROR_READ; + } + + bool last = false; + size_t partlen = 0; + if (!stream_read_partial_chunk_len(src, &partlen, &last)) { + return RNP_ERROR_BAD_FORMAT; + } + + uint8_t *buf = (uint8_t *) malloc(PGP_INPUT_CACHE_SIZE); + if (!buf) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + while (partlen > 0) { + size_t read = std::min(partlen, (size_t) PGP_INPUT_CACHE_SIZE); + if (!src_read_eq(src, buf, read)) { + free(buf); + return RNP_ERROR_READ; + } + if (dst) { + dst_write(dst, buf, read); + } + partlen -= read; + if (partlen > 0) { + continue; + } + if (last) { + break; + } + if (!stream_read_partial_chunk_len(src, &partlen, &last)) { + free(buf); + return RNP_ERROR_BAD_FORMAT; + } + } + free(buf); + return RNP_SUCCESS; +} + +rnp_result_t +stream_read_packet(pgp_source_t *src, pgp_dest_t *dst) +{ + if (stream_old_indeterminate_pkt_len(src)) { + return dst_write_src(src, dst, PGP_MAX_OLD_LEN_INDETERMINATE_PKT_SIZE); + } + + if (stream_partial_pkt_len(src)) { + return stream_read_packet_partial(src, dst); + } + + try { + pgp_packet_body_t body(PGP_PKT_RESERVED); + rnp_result_t ret = body.read(*src); + if (dst) { + body.write(*dst, false); + } + return ret; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_GENERIC; + } +} + +rnp_result_t +stream_skip_packet(pgp_source_t *src) +{ + return stream_read_packet(src, NULL); +} + +rnp_result_t +stream_parse_marker(pgp_source_t &src) +{ + try { + pgp_packet_body_t pkt(PGP_PKT_MARKER); + rnp_result_t res = pkt.read(src); + if (res) { + return res; + } + if ((pkt.size() != PGP_MARKER_LEN) || + memcmp(pkt.data(), PGP_MARKER_CONTENTS, PGP_MARKER_LEN)) { + return RNP_ERROR_BAD_FORMAT; + } + return RNP_SUCCESS; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } +} + +bool +is_key_pkt(int tag) +{ + switch (tag) { + case PGP_PKT_PUBLIC_KEY: + case PGP_PKT_PUBLIC_SUBKEY: + case PGP_PKT_SECRET_KEY: + case PGP_PKT_SECRET_SUBKEY: + return true; + default: + return false; + } +} + +bool +is_subkey_pkt(int tag) +{ + return (tag == PGP_PKT_PUBLIC_SUBKEY) || (tag == PGP_PKT_SECRET_SUBKEY); +} + +bool +is_primary_key_pkt(int tag) +{ + return (tag == PGP_PKT_PUBLIC_KEY) || (tag == PGP_PKT_SECRET_KEY); +} + +bool +is_public_key_pkt(int tag) +{ + switch (tag) { + case PGP_PKT_PUBLIC_KEY: + case PGP_PKT_PUBLIC_SUBKEY: + return true; + default: + return false; + } +} + +bool +is_secret_key_pkt(int tag) +{ + switch (tag) { + case PGP_PKT_SECRET_KEY: + case PGP_PKT_SECRET_SUBKEY: + return true; + default: + return false; + } +} + +bool +is_rsa_key_alg(pgp_pubkey_alg_t alg) +{ + switch (alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + return true; + default: + return false; + } +} + +pgp_packet_body_t::pgp_packet_body_t(pgp_pkt_type_t tag) +{ + data_.reserve(16); + tag_ = tag; + secure_ = is_secret_key_pkt(tag); +} + +pgp_packet_body_t::pgp_packet_body_t(const uint8_t *data, size_t len) +{ + data_.assign(data, data + len); + tag_ = PGP_PKT_RESERVED; + secure_ = false; +} + +pgp_packet_body_t::~pgp_packet_body_t() +{ + if (secure_) { + secure_clear(data_.data(), data_.size()); + } +} + +uint8_t * +pgp_packet_body_t::data() noexcept +{ + return data_.data(); +} + +size_t +pgp_packet_body_t::size() const noexcept +{ + return data_.size(); +} + +size_t +pgp_packet_body_t::left() const noexcept +{ + return data_.size() - pos_; +} + +bool +pgp_packet_body_t::get(uint8_t &val) noexcept +{ + if (pos_ >= data_.size()) { + return false; + } + val = data_[pos_++]; + return true; +} + +bool +pgp_packet_body_t::get(uint16_t &val) noexcept +{ + if (pos_ + 2 > data_.size()) { + return false; + } + val = read_uint16(data_.data() + pos_); + pos_ += 2; + return true; +} + +bool +pgp_packet_body_t::get(uint32_t &val) noexcept +{ + if (pos_ + 4 > data_.size()) { + return false; + } + val = read_uint32(data_.data() + pos_); + pos_ += 4; + return true; +} + +bool +pgp_packet_body_t::get(uint8_t *val, size_t len) noexcept +{ + if (pos_ + len > data_.size()) { + return false; + } + memcpy(val, data_.data() + pos_, len); + pos_ += len; + return true; +} + +bool +pgp_packet_body_t::get(pgp_key_id_t &val) noexcept +{ + static_assert(std::tuple_size<pgp_key_id_t>::value == PGP_KEY_ID_SIZE, + "pgp_key_id_t size mismatch"); + return get(val.data(), val.size()); +} + +bool +pgp_packet_body_t::get(pgp_mpi_t &val) noexcept +{ + uint16_t bits = 0; + if (!get(bits)) { + return false; + } + size_t len = (bits + 7) >> 3; + if (len > PGP_MPINT_SIZE) { + RNP_LOG("too large mpi"); + return false; + } + if (!len) { + RNP_LOG("0 mpi"); + return false; + } + if (!get(val.mpi, len)) { + RNP_LOG("failed to read mpi body"); + return false; + } + /* check the mpi bit count */ + val.len = len; + size_t mbits = mpi_bits(&val); + if (mbits != bits) { + RNP_LOG( + "Warning! Wrong mpi bit count: got %" PRIu16 ", but actual is %zu", bits, mbits); + } + return true; +} + +bool +pgp_packet_body_t::get(pgp_curve_t &val) noexcept +{ + uint8_t oidlen = 0; + if (!get(oidlen)) { + return false; + } + uint8_t oid[MAX_CURVE_OID_HEX_LEN] = {0}; + if (!oidlen || (oidlen == 0xff) || (oidlen > sizeof(oid))) { + RNP_LOG("unsupported curve oid len: %" PRIu8, oidlen); + return false; + } + if (!get(oid, oidlen)) { + return false; + } + pgp_curve_t res = find_curve_by_OID(oid, oidlen); + if (res == PGP_CURVE_MAX) { + RNP_LOG("unsupported curve"); + return false; + } + val = res; + return true; +} + +bool +pgp_packet_body_t::get(pgp_s2k_t &s2k) noexcept +{ + uint8_t spec = 0, halg = 0; + if (!get(spec) || !get(halg)) { + return false; + } + s2k.specifier = (pgp_s2k_specifier_t) spec; + s2k.hash_alg = (pgp_hash_alg_t) halg; + + switch (s2k.specifier) { + case PGP_S2KS_SIMPLE: + return true; + case PGP_S2KS_SALTED: + return get(s2k.salt, PGP_SALT_SIZE); + case PGP_S2KS_ITERATED_AND_SALTED: { + uint8_t iter = 0; + if (!get(s2k.salt, PGP_SALT_SIZE) || !get(iter)) { + return false; + } + s2k.iterations = iter; + return true; + } + case PGP_S2KS_EXPERIMENTAL: { + try { + s2k.experimental = {data_.begin() + pos_, data_.end()}; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } + uint8_t gnu[3] = {0}; + if (!get(gnu, 3) || memcmp(gnu, "GNU", 3)) { + RNP_LOG("Unknown experimental s2k. Skipping."); + pos_ = data_.size(); + s2k.gpg_ext_num = PGP_S2K_GPG_NONE; + return true; + } + uint8_t ext_num = 0; + if (!get(ext_num)) { + return false; + } + if ((ext_num != PGP_S2K_GPG_NO_SECRET) && (ext_num != PGP_S2K_GPG_SMARTCARD)) { + RNP_LOG("Unsupported gpg extension num: %" PRIu8 ", skipping", ext_num); + pos_ = data_.size(); + s2k.gpg_ext_num = PGP_S2K_GPG_NONE; + return true; + } + s2k.gpg_ext_num = (pgp_s2k_gpg_extension_t) ext_num; + if (s2k.gpg_ext_num == PGP_S2K_GPG_NO_SECRET) { + return true; + } + if (!get(s2k.gpg_serial_len)) { + RNP_LOG("Failed to get GPG serial len"); + return false; + } + size_t len = s2k.gpg_serial_len; + if (s2k.gpg_serial_len > 16) { + RNP_LOG("Warning: gpg_serial_len is %d", (int) len); + len = 16; + } + if (!get(s2k.gpg_serial, len)) { + RNP_LOG("Failed to get GPG serial"); + return false; + } + return true; + } + default: + RNP_LOG("unknown s2k specifier: %d", (int) s2k.specifier); + return false; + } +} + +void +pgp_packet_body_t::add(const void *data, size_t len) +{ + data_.insert(data_.end(), (uint8_t *) data, (uint8_t *) data + len); +} + +void +pgp_packet_body_t::add_byte(uint8_t bt) +{ + data_.push_back(bt); +} + +void +pgp_packet_body_t::add_uint16(uint16_t val) +{ + uint8_t bytes[2]; + write_uint16(bytes, val); + add(bytes, 2); +} + +void +pgp_packet_body_t::add_uint32(uint32_t val) +{ + uint8_t bytes[4]; + STORE32BE(bytes, val); + add(bytes, 4); +} + +void +pgp_packet_body_t::add(const pgp_key_id_t &val) +{ + add(val.data(), val.size()); +} + +void +pgp_packet_body_t::add(const pgp_mpi_t &val) +{ + if (!val.len) { + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + unsigned idx = 0; + while ((idx < val.len - 1) && (!val.mpi[idx])) { + idx++; + } + + unsigned bits = (val.len - idx - 1) << 3; + unsigned hibyte = val.mpi[idx]; + while (hibyte) { + bits++; + hibyte = hibyte >> 1; + } + + uint8_t hdr[2] = {(uint8_t)(bits >> 8), (uint8_t)(bits & 0xff)}; + add(hdr, 2); + add(val.mpi + idx, val.len - idx); +} + +void +pgp_packet_body_t::add_subpackets(const pgp_signature_t &sig, bool hashed) +{ + pgp_packet_body_t spbody(PGP_PKT_RESERVED); + + for (auto &subpkt : sig.subpkts) { + if (subpkt.hashed != hashed) { + continue; + } + + uint8_t splen[6]; + size_t lenlen = write_packet_len(splen, subpkt.len + 1); + spbody.add(splen, lenlen); + spbody.add_byte(subpkt.type | (subpkt.critical << 7)); + spbody.add(subpkt.data, subpkt.len); + } + + if (spbody.data_.size() > 0xffff) { + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + add_uint16(spbody.data_.size()); + add(spbody.data_.data(), spbody.data_.size()); +} + +void +pgp_packet_body_t::add(const pgp_curve_t curve) +{ + const ec_curve_desc_t *desc = get_curve_desc(curve); + if (!desc) { + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + add_byte((uint8_t) desc->OIDhex_len); + add(desc->OIDhex, (uint8_t) desc->OIDhex_len); +} + +void +pgp_packet_body_t::add(const pgp_s2k_t &s2k) +{ + add_byte(s2k.specifier); + add_byte(s2k.hash_alg); + + switch (s2k.specifier) { + case PGP_S2KS_SIMPLE: + return; + case PGP_S2KS_SALTED: + add(s2k.salt, PGP_SALT_SIZE); + return; + case PGP_S2KS_ITERATED_AND_SALTED: { + unsigned iter = s2k.iterations; + if (iter > 255) { + iter = pgp_s2k_encode_iterations(iter); + } + add(s2k.salt, PGP_SALT_SIZE); + add_byte(iter); + return; + } + case PGP_S2KS_EXPERIMENTAL: { + if ((s2k.gpg_ext_num != PGP_S2K_GPG_NO_SECRET) && + (s2k.gpg_ext_num != PGP_S2K_GPG_SMARTCARD)) { + RNP_LOG("Unknown experimental s2k."); + add(s2k.experimental.data(), s2k.experimental.size()); + return; + } + add("GNU", 3); + add_byte(s2k.gpg_ext_num); + if (s2k.gpg_ext_num == PGP_S2K_GPG_SMARTCARD) { + static_assert(sizeof(s2k.gpg_serial) == 16, "invalid gpg serial length"); + size_t slen = s2k.gpg_serial_len > 16 ? 16 : s2k.gpg_serial_len; + add_byte(s2k.gpg_serial_len); + add(s2k.gpg_serial, slen); + } + return; + } + default: + RNP_LOG("unknown s2k specifier"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } +} + +rnp_result_t +pgp_packet_body_t::read(pgp_source_t &src) noexcept +{ + /* Make sure we have enough data for packet header */ + if (!src_peek_eq(&src, hdr_, 2)) { + return RNP_ERROR_READ; + } + + /* Read the packet header and length */ + size_t len = 0; + if (!stream_pkt_hdr_len(src, len)) { + return RNP_ERROR_BAD_FORMAT; + } + if (!src_peek_eq(&src, hdr_, len)) { + return RNP_ERROR_READ; + } + hdr_len_ = len; + + int ptag = get_packet_type(hdr_[0]); + if ((ptag < 0) || ((tag_ != PGP_PKT_RESERVED) && (tag_ != ptag))) { + RNP_LOG("tag mismatch: %d vs %d", (int) tag_, ptag); + return RNP_ERROR_BAD_FORMAT; + } + tag_ = (pgp_pkt_type_t) ptag; + + if (!stream_read_pkt_len(&src, &len)) { + return RNP_ERROR_READ; + } + + /* early exit for the empty packet */ + if (!len) { + return RNP_SUCCESS; + } + + if (len > PGP_MAX_PKT_SIZE) { + RNP_LOG("too large packet"); + return RNP_ERROR_BAD_FORMAT; + } + + /* Read the packet contents */ + try { + data_.resize(len); + } catch (const std::exception &e) { + RNP_LOG("malloc of %d bytes failed, %s", (int) len, e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + + size_t read = 0; + if (!src_read(&src, data_.data(), len, &read) || (read != len)) { + RNP_LOG("read %d instead of %d", (int) read, (int) len); + return RNP_ERROR_READ; + } + pos_ = 0; + return RNP_SUCCESS; +} + +void +pgp_packet_body_t::write(pgp_dest_t &dst, bool hdr) noexcept +{ + if (hdr) { + uint8_t hdrbt[6] = { + (uint8_t)(tag_ | PGP_PTAG_ALWAYS_SET | PGP_PTAG_NEW_FORMAT), 0, 0, 0, 0, 0}; + size_t hlen = 1 + write_packet_len(&hdrbt[1], data_.size()); + dst_write(&dst, hdrbt, hlen); + } + dst_write(&dst, data_.data(), data_.size()); +} + +void +pgp_packet_body_t::mark_secure(bool secure) noexcept +{ + secure_ = secure; +} + +void +pgp_sk_sesskey_t::write(pgp_dest_t &dst) const +{ + pgp_packet_body_t pktbody(PGP_PKT_SK_SESSION_KEY); + /* version and algorithm fields */ + pktbody.add_byte(version); + pktbody.add_byte(alg); + if (version == PGP_SKSK_V5) { + pktbody.add_byte(aalg); + } + /* S2K specifier */ + pktbody.add_byte(s2k.specifier); + pktbody.add_byte(s2k.hash_alg); + + switch (s2k.specifier) { + case PGP_S2KS_SIMPLE: + break; + case PGP_S2KS_SALTED: + pktbody.add(s2k.salt, sizeof(s2k.salt)); + break; + case PGP_S2KS_ITERATED_AND_SALTED: + pktbody.add(s2k.salt, sizeof(s2k.salt)); + pktbody.add_byte(s2k.iterations); + break; + default: + RNP_LOG("Unexpected s2k specifier: %d", (int) s2k.specifier); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + /* v5 : iv */ + if (version == PGP_SKSK_V5) { + pktbody.add(iv, ivlen); + } + /* encrypted key and auth tag for v5 */ + if (enckeylen) { + pktbody.add(enckey, enckeylen); + } + /* write packet */ + pktbody.write(dst); +} + +rnp_result_t +pgp_sk_sesskey_t::parse(pgp_source_t &src) +{ + pgp_packet_body_t pkt(PGP_PKT_SK_SESSION_KEY); + rnp_result_t res = pkt.read(src); + if (res) { + return res; + } + + /* version */ + uint8_t bt; + if (!pkt.get(bt) || ((bt != PGP_SKSK_V4) && (bt != PGP_SKSK_V5))) { + RNP_LOG("wrong packet version"); + return RNP_ERROR_BAD_FORMAT; + } + version = bt; + /* symmetric algorithm */ + if (!pkt.get(bt)) { + RNP_LOG("failed to get symm alg"); + return RNP_ERROR_BAD_FORMAT; + } + alg = (pgp_symm_alg_t) bt; + + if (version == PGP_SKSK_V5) { + /* aead algorithm */ + if (!pkt.get(bt)) { + RNP_LOG("failed to get aead alg"); + return RNP_ERROR_BAD_FORMAT; + } + aalg = (pgp_aead_alg_t) bt; + if ((aalg != PGP_AEAD_EAX) && (aalg != PGP_AEAD_OCB)) { + RNP_LOG("unsupported AEAD algorithm : %d", (int) aalg); + return RNP_ERROR_BAD_PARAMETERS; + } + } + + /* s2k */ + if (!pkt.get(s2k)) { + RNP_LOG("failed to parse s2k"); + return RNP_ERROR_BAD_FORMAT; + } + + /* v4 key */ + if (version == PGP_SKSK_V4) { + /* encrypted session key if present */ + size_t keylen = pkt.left(); + if (keylen) { + if (keylen > PGP_MAX_KEY_SIZE + 1) { + RNP_LOG("too long esk"); + return RNP_ERROR_BAD_FORMAT; + } + if (!pkt.get(enckey, keylen)) { + RNP_LOG("failed to get key"); + return RNP_ERROR_BAD_FORMAT; + } + } + enckeylen = keylen; + return RNP_SUCCESS; + } + + /* v5: iv + esk + tag. For both EAX and OCB ivlen and taglen are 16 octets */ + size_t noncelen = pgp_cipher_aead_nonce_len(aalg); + size_t taglen = pgp_cipher_aead_tag_len(aalg); + size_t keylen = 0; + + if (pkt.left() > noncelen + taglen + PGP_MAX_KEY_SIZE) { + RNP_LOG("too long esk"); + return RNP_ERROR_BAD_FORMAT; + } + if (pkt.left() < noncelen + taglen + 8) { + RNP_LOG("too short esk"); + return RNP_ERROR_BAD_FORMAT; + } + /* iv */ + if (!pkt.get(iv, noncelen)) { + RNP_LOG("failed to get iv"); + return RNP_ERROR_BAD_FORMAT; + } + ivlen = noncelen; + + /* key */ + keylen = pkt.left(); + if (!pkt.get(enckey, keylen)) { + RNP_LOG("failed to get key"); + return RNP_ERROR_BAD_FORMAT; + } + enckeylen = keylen; + return RNP_SUCCESS; +} + +void +pgp_pk_sesskey_t::write(pgp_dest_t &dst) const +{ + pgp_packet_body_t pktbody(PGP_PKT_PK_SESSION_KEY); + pktbody.add_byte(version); + pktbody.add(key_id); + pktbody.add_byte(alg); + pktbody.add(material_buf.data(), material_buf.size()); + pktbody.write(dst); +} + +rnp_result_t +pgp_pk_sesskey_t::parse(pgp_source_t &src) +{ + pgp_packet_body_t pkt(PGP_PKT_PK_SESSION_KEY); + rnp_result_t res = pkt.read(src); + if (res) { + return res; + } + /* version */ + uint8_t bt = 0; + if (!pkt.get(bt) || (bt != PGP_PKSK_V3)) { + RNP_LOG("wrong packet version"); + return RNP_ERROR_BAD_FORMAT; + } + version = bt; + /* key id */ + if (!pkt.get(key_id)) { + RNP_LOG("failed to get key id"); + return RNP_ERROR_BAD_FORMAT; + } + /* public key algorithm */ + if (!pkt.get(bt)) { + RNP_LOG("failed to get palg"); + return RNP_ERROR_BAD_FORMAT; + } + alg = (pgp_pubkey_alg_t) bt; + + /* raw signature material */ + if (!pkt.left()) { + RNP_LOG("No encrypted material"); + return RNP_ERROR_BAD_FORMAT; + } + try { + material_buf.resize(pkt.left()); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + /* we cannot fail here */ + pkt.get(material_buf.data(), material_buf.size()); + /* check whether it can be parsed */ + pgp_encrypted_material_t material = {}; + if (!parse_material(material)) { + return RNP_ERROR_BAD_FORMAT; + } + return RNP_SUCCESS; +} + +bool +pgp_pk_sesskey_t::parse_material(pgp_encrypted_material_t &material) const +{ + pgp_packet_body_t pkt(material_buf.data(), material_buf.size()); + switch (alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + /* RSA m */ + if (!pkt.get(material.rsa.m)) { + RNP_LOG("failed to get rsa m"); + return false; + } + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + /* ElGamal g, m */ + if (!pkt.get(material.eg.g) || !pkt.get(material.eg.m)) { + RNP_LOG("failed to get elgamal mpis"); + return false; + } + break; + case PGP_PKA_SM2: + /* SM2 m */ + if (!pkt.get(material.sm2.m)) { + RNP_LOG("failed to get sm2 m"); + return false; + } + break; + case PGP_PKA_ECDH: { + /* ECDH ephemeral point */ + if (!pkt.get(material.ecdh.p)) { + RNP_LOG("failed to get ecdh p"); + return false; + } + /* ECDH m */ + uint8_t bt = 0; + if (!pkt.get(bt)) { + RNP_LOG("failed to get ecdh m len"); + return false; + } + if (bt > ECDH_WRAPPED_KEY_SIZE) { + RNP_LOG("wrong ecdh m len"); + return false; + } + material.ecdh.mlen = bt; + if (!pkt.get(material.ecdh.m, bt)) { + RNP_LOG("failed to get ecdh m len"); + return false; + } + break; + } + default: + RNP_LOG("unknown pk alg %d", (int) alg); + return false; + } + + if (pkt.left()) { + RNP_LOG("extra %d bytes in pk packet", (int) pkt.left()); + return false; + } + return true; +} + +void +pgp_pk_sesskey_t::write_material(const pgp_encrypted_material_t &material) +{ + pgp_packet_body_t pktbody(PGP_PKT_PK_SESSION_KEY); + + switch (alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + pktbody.add(material.rsa.m); + break; + case PGP_PKA_SM2: + pktbody.add(material.sm2.m); + break; + case PGP_PKA_ECDH: + pktbody.add(material.ecdh.p); + pktbody.add_byte(material.ecdh.mlen); + pktbody.add(material.ecdh.m, material.ecdh.mlen); + break; + case PGP_PKA_ELGAMAL: + pktbody.add(material.eg.g); + pktbody.add(material.eg.m); + break; + default: + RNP_LOG("Unknown pk alg: %d", (int) alg); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + material_buf = {pktbody.data(), pktbody.data() + pktbody.size()}; +} + +void +pgp_one_pass_sig_t::write(pgp_dest_t &dst) const +{ + pgp_packet_body_t pktbody(PGP_PKT_ONE_PASS_SIG); + pktbody.add_byte(version); + pktbody.add_byte(type); + pktbody.add_byte(halg); + pktbody.add_byte(palg); + pktbody.add(keyid); + pktbody.add_byte(nested); + pktbody.write(dst); +} + +rnp_result_t +pgp_one_pass_sig_t::parse(pgp_source_t &src) +{ + pgp_packet_body_t pkt(PGP_PKT_ONE_PASS_SIG); + /* Read the packet into memory */ + rnp_result_t res = pkt.read(src); + if (res) { + return res; + } + + uint8_t buf[13] = {0}; + if ((pkt.size() != 13) || !pkt.get(buf, 13)) { + return RNP_ERROR_BAD_FORMAT; + } + /* version */ + if (buf[0] != 3) { + RNP_LOG("wrong packet version"); + return RNP_ERROR_BAD_FORMAT; + } + version = buf[0]; + /* signature type */ + type = (pgp_sig_type_t) buf[1]; + /* hash algorithm */ + halg = (pgp_hash_alg_t) buf[2]; + /* pk algorithm */ + palg = (pgp_pubkey_alg_t) buf[3]; + /* key id */ + static_assert(std::tuple_size<decltype(keyid)>::value == PGP_KEY_ID_SIZE, + "pgp_one_pass_sig_t.keyid size mismatch"); + memcpy(keyid.data(), &buf[4], PGP_KEY_ID_SIZE); + /* nested flag */ + nested = buf[12]; + return RNP_SUCCESS; +} diff --git a/comm/third_party/rnp/src/librepgp/stream-packet.h b/comm/third_party/rnp/src/librepgp/stream-packet.h new file mode 100644 index 0000000000..f88c96f22d --- /dev/null +++ b/comm/third_party/rnp/src/librepgp/stream-packet.h @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2017-2020 [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef STREAM_PACKET_H_ +#define STREAM_PACKET_H_ + +#include <stdint.h> +#include <stdbool.h> +#include <sys/types.h> +#include "types.h" +#include "stream-common.h" + +/* maximum size of the 'small' packet */ +#define PGP_MAX_PKT_SIZE 0x100000 + +/* maximum size of indeterminate-size packet allowed with old length format */ +#define PGP_MAX_OLD_LEN_INDETERMINATE_PKT_SIZE 0x40000000 + +typedef struct pgp_packet_hdr_t { + pgp_pkt_type_t tag; /* packet tag */ + uint8_t hdr[PGP_MAX_HEADER_SIZE]; /* PGP packet header, needed for AEAD */ + size_t hdr_len; /* length of the header */ + size_t pkt_len; /* packet body length if non-partial and non-indeterminate */ + bool partial; /* partial length packet */ + bool indeterminate; /* indeterminate length packet */ +} pgp_packet_hdr_t; + +/* structure for convenient writing or parsing of non-stream packets */ +typedef struct pgp_packet_body_t { + private: + pgp_pkt_type_t tag_; /* packet tag */ + std::vector<uint8_t> data_; /* packet bytes */ + /* fields below are filled only for parsed packet */ + uint8_t hdr_[PGP_MAX_HEADER_SIZE]{}; /* packet header bytes */ + size_t hdr_len_{}; /* number of bytes in hdr */ + size_t pos_{}; /* current read position in packet data */ + bool secure_{}; /* contents of the packet are secure so must be wiped in the destructor */ + public: + /** @brief initialize writing of packet body + * @param tag tag of the packet + **/ + pgp_packet_body_t(pgp_pkt_type_t tag); + /** @brief init packet body (without headers) with memory. Used for easier data parsing. + * @param data buffer with packet body part + * @param len number of available bytes in mem + */ + pgp_packet_body_t(const uint8_t *data, size_t len); + + pgp_packet_body_t(const pgp_packet_body_t &src) = delete; + pgp_packet_body_t(pgp_packet_body_t &&src) = delete; + pgp_packet_body_t &operator=(const pgp_packet_body_t &) = delete; + pgp_packet_body_t &operator=(pgp_packet_body_t &&) = delete; + ~pgp_packet_body_t(); + + /** @brief pointer to the data, kept in the packet */ + uint8_t *data() noexcept; + /** @brief number of bytes, kept in the packet (without the header) */ + size_t size() const noexcept; + /** @brief number of bytes left to read */ + size_t left() const noexcept; + /** @brief get next byte from the packet body, populated with read() call. + * @param val result will be stored here on success + * @return true on success or false otherwise (if end of the packet is reached) + **/ + bool get(uint8_t &val) noexcept; + /** @brief get next big-endian uint16 from the packet body, populated with read() call. + * @param val result will be stored here on success + * @return true on success or false otherwise (if end of the packet is reached) + **/ + bool get(uint16_t &val) noexcept; + /** @brief get next big-endian uint32 from the packet body, populated with read() call. + * @param val result will be stored here on success + * @return true on success or false otherwise (if end of the packet is reached) + **/ + bool get(uint32_t &val) noexcept; + /** @brief get some bytes from the packet body, populated with read() call. + * @param val packet body bytes will be stored here. Must be capable of storing len bytes. + * @param len number of bytes to read + * @return true on success or false otherwise (if end of the packet is reached) + **/ + bool get(uint8_t *val, size_t len) noexcept; + /** @brief get next keyid from the packet body, populated with read() call. + * @param val result will be stored here on success + * @return true on success or false otherwise (if end of the packet is reached) + **/ + bool get(pgp_key_id_t &val) noexcept; + /** @brief get next mpi from the packet body, populated with read() call. + * @param val result will be stored here on success + * @return true on success or false otherwise (if end of the packet is reached + * or mpi is ill-formed) + **/ + bool get(pgp_mpi_t &val) noexcept; + /** @brief Read ECC key curve and convert it to pgp_curve_t */ + bool get(pgp_curve_t &val) noexcept; + /** @brief read s2k from the packet */ + bool get(pgp_s2k_t &s2k) noexcept; + /** @brief append some bytes to the packet body */ + void add(const void *data, size_t len); + /** @brief append single byte to the packet body */ + void add_byte(uint8_t bt); + /** @brief append big endian 16-bit value to the packet body */ + void add_uint16(uint16_t val); + /** @brief append big endian 32-bit value to the packet body */ + void add_uint32(uint32_t val); + /** @brief append keyid to the packet body */ + void add(const pgp_key_id_t &val); + /** @brief add pgp mpi (including header) to the packet body */ + void add(const pgp_mpi_t &val); + /** + * @brief add pgp signature subpackets (including their length) to the packet body + * @param sig signature, containing subpackets + * @param hashed whether write hashed or not hashed subpackets + */ + void add_subpackets(const pgp_signature_t &sig, bool hashed); + /** @brief add ec curve description to the packet body */ + void add(const pgp_curve_t curve); + /** @brief add s2k description to the packet body */ + void add(const pgp_s2k_t &s2k); + /** @brief read 'short-length' packet body (including tag and length bytes) from the source + * @param src source to read from + * @return RNP_SUCCESS or error code if operation failed + **/ + rnp_result_t read(pgp_source_t &src) noexcept; + /** @brief write packet header, length and body to the dst + * @param dst destination to write to. + * @param hdr write packet's header or not + **/ + void write(pgp_dest_t &dst, bool hdr = true) noexcept; + /** @brief mark contents as secure, so secure_clear() must be called in the destructor */ + void mark_secure(bool secure = true) noexcept; +} pgp_packet_body_t; + +/** public-key encrypted session key packet */ +typedef struct pgp_pk_sesskey_t { + unsigned version{}; + pgp_key_id_t key_id{}; + pgp_pubkey_alg_t alg{}; + std::vector<uint8_t> material_buf{}; + + void write(pgp_dest_t &dst) const; + rnp_result_t parse(pgp_source_t &src); + /** + * @brief Parse encrypted material which is stored in packet in raw. + * @param material on success parsed material will be stored here. + * @return true on success or false otherwise. May also throw an exception. + */ + bool parse_material(pgp_encrypted_material_t &material) const; + /** + * @brief Write encrypted material to the material_buf. + * @param material populated encrypted material. + */ + void write_material(const pgp_encrypted_material_t &material); +} pgp_pk_sesskey_t; + +/** pkp_sk_sesskey_t */ +typedef struct pgp_sk_sesskey_t { + unsigned version{}; + pgp_symm_alg_t alg{}; + pgp_s2k_t s2k{}; + uint8_t enckey[PGP_MAX_KEY_SIZE + PGP_AEAD_MAX_TAG_LEN + 1]{}; + unsigned enckeylen{}; + /* v5 specific fields */ + pgp_aead_alg_t aalg{}; + uint8_t iv[PGP_MAX_BLOCK_SIZE]{}; + unsigned ivlen{}; + + void write(pgp_dest_t &dst) const; + rnp_result_t parse(pgp_source_t &src); +} pgp_sk_sesskey_t; + +/** pgp_one_pass_sig_t */ +typedef struct pgp_one_pass_sig_t { + uint8_t version{}; + pgp_sig_type_t type{}; + pgp_hash_alg_t halg{}; + pgp_pubkey_alg_t palg{}; + pgp_key_id_t keyid{}; + unsigned nested{}; + + void write(pgp_dest_t &dst) const; + rnp_result_t parse(pgp_source_t &src); +} pgp_one_pass_sig_t; + +/** Struct to hold userid or userattr packet. We don't parse userattr now, just storing the + * binary blob as it is. It may be distinguished by tag field. + */ +typedef struct pgp_userid_pkt_t { + pgp_pkt_type_t tag; + uint8_t * uid; + size_t uid_len; + + pgp_userid_pkt_t() : tag(PGP_PKT_RESERVED), uid(NULL), uid_len(0){}; + pgp_userid_pkt_t(const pgp_userid_pkt_t &src); + pgp_userid_pkt_t(pgp_userid_pkt_t &&src); + pgp_userid_pkt_t &operator=(pgp_userid_pkt_t &&src); + pgp_userid_pkt_t &operator=(const pgp_userid_pkt_t &src); + bool operator==(const pgp_userid_pkt_t &src) const; + bool operator!=(const pgp_userid_pkt_t &src) const; + ~pgp_userid_pkt_t(); + + void write(pgp_dest_t &dst) const; + rnp_result_t parse(pgp_source_t &src); +} pgp_userid_pkt_t; + +uint16_t read_uint16(const uint8_t *buf); + +uint32_t read_uint32(const uint8_t *buf); + +void write_uint16(uint8_t *buf, uint16_t val); + +/** @brief write new packet length + * @param buf pre-allocated buffer, must have 5 bytes + * @param len packet length + * @return number of bytes, saved in buf + **/ +size_t write_packet_len(uint8_t *buf, size_t len); + +/** @brief get packet type from the packet header byte + * @param ptag first byte of the packet header + * @return packet type or -1 if ptag is wrong + **/ +int get_packet_type(uint8_t ptag); + +/** @brief peek the packet type from the stream + * @param src source to peek from + * @return packet tag or -1 if read failed or packet header is malformed + */ +int stream_pkt_type(pgp_source_t &src); + +/** @brief Peek length of the packet header. Returns false on error. + * @param src source to read length from + * @param hdrlen header length will be put here on success. Cannot be NULL. + * @return true on success or false if there is a read error or packet length + * is ill-formed + **/ +bool stream_pkt_hdr_len(pgp_source_t &src, size_t &hdrlen); + +bool stream_old_indeterminate_pkt_len(pgp_source_t *src); + +bool stream_partial_pkt_len(pgp_source_t *src); + +size_t get_partial_pkt_len(uint8_t blen); + +/** @brief Read packet length for fixed-size (say, small) packet. Returns false on error. + * Will also read packet tag byte. We do not allow partial length here as well as large + * packets (so ignoring possible size_t overflow) + * + * @param src source to read length from + * @param pktlen packet length will be stored here on success. Cannot be NULL. + * @return true on success or false if there is read error or packet length is ill-formed + **/ +bool stream_read_pkt_len(pgp_source_t *src, size_t *pktlen); + +/** @brief Read partial packet chunk length. + * + * @param src source to read length from + * @param clen chunk length will be stored here on success. Cannot be NULL. + * @param last will be set to true if chunk is last (i.e. has non-partial length) + * @return true on success or false if there is read error or packet length is ill-formed + **/ +bool stream_read_partial_chunk_len(pgp_source_t *src, size_t *clen, bool *last); + +/** @brief get and parse OpenPGP packet header to the structure. + * Note: this will not read but just peek required bytes. + * + * @param src source to read from + * @param hdr header structure + * @return RNP_SUCCESS or error code if operation failed + **/ +rnp_result_t stream_peek_packet_hdr(pgp_source_t *src, pgp_packet_hdr_t *hdr); + +/* Packet handling functions */ + +/** @brief read OpenPGP packet from the stream, and write its contents to another stream. + * @param src source with packet data + * @param dst destination to write packet contents. All write failures on dst + * will be ignored. Can be NULL if you need just to skip packet. + * @return RNP_SUCCESS or error code if operation failed. + */ +rnp_result_t stream_read_packet(pgp_source_t *src, pgp_dest_t *dst); + +rnp_result_t stream_skip_packet(pgp_source_t *src); + +rnp_result_t stream_parse_marker(pgp_source_t &src); + +/* Public/Private key or Subkey */ + +bool is_key_pkt(int tag); + +bool is_subkey_pkt(int tag); + +bool is_primary_key_pkt(int tag); + +bool is_public_key_pkt(int tag); + +bool is_secret_key_pkt(int tag); + +bool is_rsa_key_alg(pgp_pubkey_alg_t alg); + +#endif diff --git a/comm/third_party/rnp/src/librepgp/stream-parse.cpp b/comm/third_party/rnp/src/librepgp/stream-parse.cpp new file mode 100644 index 0000000000..5ec4d64be9 --- /dev/null +++ b/comm/third_party/rnp/src/librepgp/stream-parse.cpp @@ -0,0 +1,2636 @@ +/* + * Copyright (c) 2017-2023, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include <sys/stat.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <string> +#include <vector> +#include <time.h> +#include <cinttypes> +#include <cassert> +#include <rnp/rnp_def.h> +#include "stream-ctx.h" +#include "stream-def.h" +#include "stream-parse.h" +#include "stream-armor.h" +#include "stream-packet.h" +#include "stream-sig.h" +#include "str-utils.h" +#include "types.h" +#include "crypto/s2k.h" +#include "crypto.h" +#include "crypto/signatures.h" +#include "fingerprint.h" +#include "pgp-key.h" + +#ifdef HAVE_ZLIB_H +#include <zlib.h> +#endif +#ifdef HAVE_BZLIB_H +#include <bzlib.h> +#endif + +typedef enum pgp_message_t { + PGP_MESSAGE_UNKNOWN = 0, + PGP_MESSAGE_NORMAL, + PGP_MESSAGE_DETACHED, + PGP_MESSAGE_CLEARTEXT +} pgp_message_t; + +typedef struct pgp_processing_ctx_t { + pgp_parse_handler_t handler; + pgp_source_t * signed_src; + pgp_source_t * literal_src; + pgp_message_t msg_type; + pgp_dest_t output; + std::list<pgp_source_t> sources; + + ~pgp_processing_ctx_t(); +} pgp_processing_ctx_t; + +/* common fields for encrypted, compressed and literal data */ +typedef struct pgp_source_packet_param_t { + pgp_source_t * readsrc; /* source to read from, could be partial*/ + pgp_source_t * origsrc; /* original source passed to init_*_src */ + pgp_packet_hdr_t hdr; /* packet header info */ +} pgp_source_packet_param_t; + +typedef struct pgp_source_encrypted_param_t { + pgp_source_packet_param_t pkt{}; /* underlying packet-related params */ + std::vector<pgp_sk_sesskey_t> symencs; /* array of sym-encrypted session keys */ + std::vector<pgp_pk_sesskey_t> pubencs; /* array of pk-encrypted session keys */ + rnp::AuthType auth_type; /* Authentication type */ + bool auth_validated{}; /* Auth tag (MDC or AEAD) was already validated */ + pgp_crypt_t decrypt{}; /* decrypting crypto */ + std::unique_ptr<rnp::Hash> mdc; /* mdc SHA1 hash */ + 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 */ + + pgp_source_encrypted_param_t() : auth_type(rnp::AuthType::None), salg(PGP_SA_UNKNOWN) + { + } + + bool + use_cfb() + { + return auth_type != rnp::AuthType::AEADv1; + } +} pgp_source_encrypted_param_t; + +typedef struct pgp_source_signed_param_t { + pgp_parse_handler_t *handler; /* parsing handler with callbacks */ + pgp_source_t * readsrc; /* source to read from */ + bool detached; /* detached signature */ + bool cleartext; /* source is cleartext signed */ + bool clr_eod; /* cleartext data is over */ + bool clr_fline; /* first line of the cleartext */ + bool clr_mline; /* in the middle of the very long line */ + uint8_t out[CT_BUF_LEN]; /* cleartext output cache for easier parsing */ + size_t outlen; /* total bytes in out */ + size_t outpos; /* offset of first available byte in out */ + bool max_line_warn; /* warning about too long line is already issued */ + size_t text_line_len; /* length of a current line in a text document */ + long stripped_crs; /* number of trailing CR characters stripped from the end of the last + processed chunk */ + + std::vector<pgp_one_pass_sig_t> onepasses; /* list of one-pass singatures */ + std::list<pgp_signature_t> sigs; /* list of signatures */ + std::vector<pgp_signature_info_t> siginfos; /* signature validation info */ + rnp::HashList hashes; /* hash contexts */ + rnp::HashList txt_hashes; /* hash contexts for text-mode sigs */ + + pgp_source_signed_param_t() = default; + ~pgp_source_signed_param_t() = default; +} pgp_source_signed_param_t; + +typedef struct pgp_source_compressed_param_t { + pgp_source_packet_param_t pkt; /* underlying packet-related params */ + pgp_compression_type_t alg; + union { + z_stream z; + bz_stream bz; + }; + uint8_t in[PGP_INPUT_CACHE_SIZE / 2]; + size_t inpos; + size_t inlen; + bool zend; +} pgp_source_compressed_param_t; + +typedef struct pgp_source_literal_param_t { + pgp_source_packet_param_t pkt; /* underlying packet-related params */ + pgp_literal_hdr_t hdr; /* literal packet fields */ +} pgp_source_literal_param_t; + +typedef struct pgp_source_partial_param_t { + pgp_source_t *readsrc; /* source to read from */ + int type; /* type of the packet */ + size_t psize; /* size of the current part */ + size_t pleft; /* bytes left to read from the current part */ + bool last; /* current part is last */ +} pgp_source_partial_param_t; + +static bool +is_pgp_source(pgp_source_t &src) +{ + uint8_t buf; + if (!src_peek_eq(&src, &buf, 1)) { + return false; + } + + switch (get_packet_type(buf)) { + case PGP_PKT_PK_SESSION_KEY: + case PGP_PKT_SK_SESSION_KEY: + case PGP_PKT_ONE_PASS_SIG: + case PGP_PKT_SIGNATURE: + case PGP_PKT_SE_DATA: + case PGP_PKT_SE_IP_DATA: + case PGP_PKT_COMPRESSED: + case PGP_PKT_LITDATA: + case PGP_PKT_MARKER: + return true; + default: + return false; + } +} + +static bool +partial_pkt_src_read(pgp_source_t *src, void *buf, size_t len, size_t *readres) +{ + if (src->eof) { + *readres = 0; + return true; + } + + pgp_source_partial_param_t *param = (pgp_source_partial_param_t *) src->param; + if (!param) { + return false; + } + + size_t read; + size_t write = 0; + while (len > 0) { + if (!param->pleft && param->last) { + // we have the last chunk + *readres = write; + return true; + } + if (!param->pleft) { + // reading next chunk + if (!stream_read_partial_chunk_len(param->readsrc, &read, ¶m->last)) { + return false; + } + param->psize = read; + param->pleft = read; + } + + if (!param->pleft) { + *readres = write; + return true; + } + + read = param->pleft > len ? len : param->pleft; + if (!src_read(param->readsrc, buf, read, &read)) { + RNP_LOG("failed to read data chunk"); + return false; + } + if (!read) { + RNP_LOG("unexpected eof"); + *readres = write; + return true; + } + write += read; + len -= read; + buf = (uint8_t *) buf + read; + param->pleft -= read; + } + + *readres = write; + return true; +} + +static void +partial_pkt_src_close(pgp_source_t *src) +{ + pgp_source_partial_param_t *param = (pgp_source_partial_param_t *) src->param; + if (param) { + free(src->param); + src->param = NULL; + } +} + +static rnp_result_t +init_partial_pkt_src(pgp_source_t *src, pgp_source_t *readsrc, pgp_packet_hdr_t &hdr) +{ + pgp_source_partial_param_t *param; + if (!init_src_common(src, sizeof(*param))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + assert(hdr.partial); + /* we are sure that header is indeterminate */ + param = (pgp_source_partial_param_t *) src->param; + param->type = hdr.tag; + param->psize = get_partial_pkt_len(hdr.hdr[1]); + param->pleft = param->psize; + param->last = false; + param->readsrc = readsrc; + + src->read = partial_pkt_src_read; + src->close = partial_pkt_src_close; + src->type = PGP_STREAM_PARLEN_PACKET; + + if (param->psize < PGP_PARTIAL_PKT_FIRST_PART_MIN_SIZE) { + RNP_LOG("first part of partial length packet sequence has size %d and that's less " + "than allowed by the protocol", + (int) param->psize); + } + + return RNP_SUCCESS; +} + +static bool +literal_src_read(pgp_source_t *src, void *buf, size_t len, size_t *read) +{ + pgp_source_literal_param_t *param = (pgp_source_literal_param_t *) src->param; + if (!param) { + return false; + } + return src_read(param->pkt.readsrc, buf, len, read); +} + +static void +literal_src_close(pgp_source_t *src) +{ + pgp_source_literal_param_t *param = (pgp_source_literal_param_t *) src->param; + if (param) { + if (param->pkt.hdr.partial) { + src_close(param->pkt.readsrc); + free(param->pkt.readsrc); + param->pkt.readsrc = NULL; + } + + free(src->param); + src->param = NULL; + } +} + +static bool +compressed_src_read(pgp_source_t *src, void *buf, size_t len, size_t *readres) +{ + pgp_source_compressed_param_t *param = (pgp_source_compressed_param_t *) src->param; + if (!param) { + return false; + } + + if (src->eof || param->zend) { + *readres = 0; + return true; + } + + if (param->alg == PGP_C_NONE) { + if (!src_read(param->pkt.readsrc, buf, len, readres)) { + RNP_LOG("failed to read uncompressed data"); + return false; + } + return true; + } + if ((param->alg == PGP_C_ZIP) || (param->alg == PGP_C_ZLIB)) { + param->z.next_out = (Bytef *) buf; + param->z.avail_out = len; + param->z.next_in = param->in + param->inpos; + param->z.avail_in = param->inlen - param->inpos; + + while ((param->z.avail_out > 0) && (!param->zend)) { + if (param->z.avail_in == 0) { + size_t read = 0; + if (!src_read(param->pkt.readsrc, param->in, sizeof(param->in), &read)) { + RNP_LOG("failed to read data"); + return false; + } + param->z.next_in = param->in; + param->z.avail_in = read; + param->inlen = read; + param->inpos = 0; + } + int ret = inflate(¶m->z, Z_SYNC_FLUSH); + if (ret == Z_STREAM_END) { + param->zend = true; + if (param->z.avail_in > 0) { + RNP_LOG("data beyond the end of z stream"); + } + break; + } + if (ret != Z_OK) { + RNP_LOG("inflate error %d", ret); + return false; + } + if (!param->z.avail_in && src_eof(param->pkt.readsrc)) { + RNP_LOG("unexpected end of zlib stream"); + return false; + } + } + param->inpos = param->z.next_in - param->in; + *readres = len - param->z.avail_out; + return true; + } +#ifdef HAVE_BZLIB_H + if (param->alg == PGP_C_BZIP2) { + param->bz.next_out = (char *) buf; + param->bz.avail_out = len; + param->bz.next_in = (char *) (param->in + param->inpos); + param->bz.avail_in = param->inlen - param->inpos; + + while ((param->bz.avail_out > 0) && (!param->zend)) { + if (param->bz.avail_in == 0) { + size_t read = 0; + if (!src_read(param->pkt.readsrc, param->in, sizeof(param->in), &read)) { + RNP_LOG("failed to read data"); + return false; + } + param->bz.next_in = (char *) param->in; + param->bz.avail_in = read; + param->inlen = read; + param->inpos = 0; + } + int ret = BZ2_bzDecompress(¶m->bz); + if (ret == BZ_STREAM_END) { + param->zend = true; + if (param->bz.avail_in > 0) { + RNP_LOG("data beyond the end of z stream"); + } + break; + } + if (ret != BZ_OK) { + RNP_LOG("bzdecompress error %d", ret); + return false; + } + if (!param->bz.avail_in && src_eof(param->pkt.readsrc)) { + RNP_LOG("unexpected end of bzip stream"); + return false; + } + } + + param->inpos = (uint8_t *) param->bz.next_in - param->in; + *readres = len - param->bz.avail_out; + return true; + } +#endif + return false; +} + +static void +compressed_src_close(pgp_source_t *src) +{ + pgp_source_compressed_param_t *param = (pgp_source_compressed_param_t *) src->param; + if (!param) { + return; + } + + if (param->pkt.hdr.partial) { + src_close(param->pkt.readsrc); + free(param->pkt.readsrc); + param->pkt.readsrc = NULL; + } + +#ifdef HAVE_BZLIB_H + if (param->alg == PGP_C_BZIP2) { + BZ2_bzDecompressEnd(¶m->bz); + } +#endif + if ((param->alg == PGP_C_ZIP) || (param->alg == PGP_C_ZLIB)) { + inflateEnd(¶m->z); + } + + free(src->param); + src->param = NULL; +} + +#if defined(ENABLE_AEAD) +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; + + /* set chunk index for additional data */ + STORE64BE(param->aead_ad + param->aead_adlen - 8, idx); + + if (last) { + uint64_t total = idx * param->chunklen; + if (idx && param->chunkin) { + total -= param->chunklen - param->chunkin; + } + + if (!param->chunkin) { + /* reset the crypto in case we had empty chunk before the last one */ + pgp_cipher_aead_reset(¶m->decrypt); + } + STORE64BE(param->aead_ad + param->aead_adlen, total); + param->aead_adlen += 8; + } + + if (!pgp_cipher_aead_set_ad(¶m->decrypt, param->aead_ad, param->aead_adlen)) { + RNP_LOG("failed to set ad"); + return false; + } + + /* setup chunk */ + param->chunkidx = idx; + param->chunkin = 0; + + /* set chunk index for nonce */ + 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); +} + +/* read and decrypt bytes to the cache. Should be called only on empty cache. */ +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; + + param->cachepos = 0; + param->cachelen = 0; + + if (param->auth_validated) { + return true; + } + + /* 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; + + if (read >= param->chunklen - param->chunkin) { + read = param->chunklen - param->chunkin; + chunkend = true; + } else { + read = read - read % pgp_cipher_aead_granularity(¶m->decrypt); + } + + if (!src_read(param->pkt.readsrc, param->cache, read, &read)) { + return false; + } + + /* checking whether we have enough input for the final tags */ + if (!src_peek(param->pkt.readsrc, param->cache + read, taglen * 2, &tagread)) { + return false; + } + + if (tagread < taglen * 2) { + /* this would mean the end of the stream */ + if ((param->chunkin == 0) && (read + tagread == taglen)) { + /* we have empty chunk and final tag */ + chunkend = false; + lastchunk = true; + } else if (read + tagread >= 2 * taglen) { + /* we have end of chunk and final tag */ + chunkend = true; + lastchunk = true; + } else { + RNP_LOG("unexpected end of data"); + return false; + } + } + + if (!chunkend && !lastchunk) { + param->chunkin += read; + res = pgp_cipher_aead_update(¶m->decrypt, param->cache, param->cache, read); + if (res) { + param->cachelen = read; + } + return res; + } + + if (chunkend) { + if (tagread > taglen) { + src_skip(param->pkt.readsrc, tagread - taglen); + } + + res = pgp_cipher_aead_finish( + ¶m->decrypt, param->cache, param->cache, read + tagread - taglen); + if (!res) { + RNP_LOG("failed to finalize aead chunk"); + return res; + } + param->cachelen = read + tagread - 2 * taglen; + param->chunkin += param->cachelen; + } + + size_t chunkidx = param->chunkidx; + if (chunkend && param->chunkin) { + chunkidx++; + } + + if (!(res = encrypted_start_aead_chunk(param, chunkidx, lastchunk))) { + RNP_LOG("failed to start aead chunk"); + return res; + } + + if (lastchunk) { + if (tagread > 0) { + src_skip(param->pkt.readsrc, tagread); + } + + 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; + } + + return res; +} +#endif + +static bool +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; + + do { + /* check whether we have something in the cache */ + cbytes = param->cachelen - param->cachepos; + if (cbytes > 0) { + if (cbytes >= left) { + memcpy(buf, param->cache + param->cachepos, left); + param->cachepos += left; + if (param->cachepos == param->cachelen) { + param->cachepos = param->cachelen = 0; + } + *read = len; + return true; + } + memcpy(buf, param->cache + param->cachepos, cbytes); + buf = (uint8_t *) buf + cbytes; + left -= cbytes; + param->cachepos = param->cachelen = 0; + } + + /* read something into cache */ + if (!encrypted_src_read_aead_part(param)) { + return false; + } + } while ((left > 0) && (param->cachelen > 0)); + + *read = len - left; + return true; +#endif +} + +static bool +encrypted_src_read_cfb(pgp_source_t *src, void *buf, size_t len, size_t *readres) +{ + pgp_source_encrypted_param_t *param = (pgp_source_encrypted_param_t *) src->param; + if (param == NULL) { + return false; + } + + if (src->eof) { + *readres = 0; + return true; + } + + size_t read; + if (!src_read(param->pkt.readsrc, buf, len, &read)) { + return false; + } + if (!read) { + *readres = 0; + return true; + } + + bool parsemdc = false; + uint8_t mdcbuf[MDC_V1_SIZE]; + if (param->auth_type == rnp::AuthType::MDC) { + size_t mdcread = 0; + /* make sure there are always 22 bytes left on input */ + if (!src_peek(param->pkt.readsrc, mdcbuf, MDC_V1_SIZE, &mdcread) || + (mdcread + read < MDC_V1_SIZE)) { + RNP_LOG("wrong mdc read state"); + return false; + } + if (mdcread < MDC_V1_SIZE) { + src_skip(param->pkt.readsrc, mdcread); + size_t mdcsub = MDC_V1_SIZE - mdcread; + memmove(&mdcbuf[mdcsub], mdcbuf, mdcread); + memcpy(mdcbuf, (uint8_t *) buf + read - mdcsub, mdcsub); + read -= mdcsub; + parsemdc = true; + } + } + + pgp_cipher_cfb_decrypt(¶m->decrypt, (uint8_t *) buf, (uint8_t *) buf, read); + + if (param->auth_type == rnp::AuthType::MDC) { + try { + param->mdc->add(buf, read); + + if (parsemdc) { + pgp_cipher_cfb_decrypt(¶m->decrypt, mdcbuf, mdcbuf, MDC_V1_SIZE); + pgp_cipher_cfb_finish(¶m->decrypt); + param->mdc->add(mdcbuf, 2); + uint8_t hash[PGP_SHA1_HASH_SIZE] = {0}; + param->mdc->finish(hash); + param->mdc = nullptr; + + if ((mdcbuf[0] != MDC_PKT_TAG) || (mdcbuf[1] != MDC_V1_SIZE - 2)) { + RNP_LOG("mdc header check failed"); + return false; + } + + if (memcmp(&mdcbuf[2], hash, PGP_SHA1_HASH_SIZE) != 0) { + RNP_LOG("mdc hash check failed"); + return false; + } + param->auth_validated = true; + } + } catch (const std::exception &e) { + RNP_LOG("mdc update failed: %s", e.what()); + return false; + } + } + *readres = read; + return true; +} + +static rnp_result_t +encrypted_src_finish(pgp_source_t *src) +{ + pgp_source_encrypted_param_t *param = (pgp_source_encrypted_param_t *) src->param; + + /* report to the handler that decryption is finished */ + if (param->handler->on_decryption_done) { + bool validated = (param->auth_type != rnp::AuthType::None) && param->auth_validated; + param->handler->on_decryption_done(validated, param->handler->param); + } + + if ((param->auth_type == rnp::AuthType::None) || param->auth_validated) { + return RNP_SUCCESS; + } + switch (param->auth_type) { + case rnp::AuthType::MDC: + RNP_LOG("mdc was not validated"); + break; + case rnp::AuthType::AEADv1: + RNP_LOG("aead last chunk was not validated"); + break; + default: + RNP_LOG("auth was not validated"); + break; + } + return RNP_ERROR_BAD_STATE; +} + +static void +encrypted_src_close(pgp_source_t *src) +{ + pgp_source_encrypted_param_t *param = (pgp_source_encrypted_param_t *) src->param; + if (!param) { + return; + } + if (param->pkt.hdr.partial) { + src_close(param->pkt.readsrc); + free(param->pkt.readsrc); + param->pkt.readsrc = NULL; + } + + if (!param->use_cfb()) { +#if defined(ENABLE_AEAD) + pgp_cipher_aead_destroy(¶m->decrypt); +#endif + } else { + pgp_cipher_cfb_finish(¶m->decrypt); + } + + delete param; + src->param = NULL; +} + +static void +add_hash_for_sig(pgp_source_signed_param_t *param, pgp_sig_type_t stype, pgp_hash_alg_t halg) +{ + /* Cleartext always uses param->hashes instead of param->txt_hashes */ + if (!param->cleartext && (stype == PGP_SIG_TEXT)) { + param->txt_hashes.add_alg(halg); + } + param->hashes.add_alg(halg); +} + +static const rnp::Hash * +get_hash_for_sig(pgp_source_signed_param_t ¶m, pgp_signature_info_t &sinfo) +{ + /* Cleartext always uses param->hashes instead of param->txt_hashes */ + if (!param.cleartext && (sinfo.sig->type() == PGP_SIG_TEXT)) { + return param.txt_hashes.get(sinfo.sig->halg); + } + return param.hashes.get(sinfo.sig->halg); +} + +static void +signed_validate_signature(pgp_source_signed_param_t ¶m, pgp_signature_info_t &sinfo) +{ + /* Check signature type */ + if (!sinfo.sig->is_document()) { + RNP_LOG("Invalid document signature type: %d", (int) sinfo.sig->type()); + sinfo.valid = false; + return; + } + /* Find signing key */ + pgp_key_request_ctx_t keyctx(PGP_OP_VERIFY, false, PGP_KEY_SEARCH_FINGERPRINT); + + /* Get signer's fp or keyid */ + if (sinfo.sig->has_keyfp()) { + keyctx.search.by.fingerprint = sinfo.sig->keyfp(); + } else if (sinfo.sig->has_keyid()) { + keyctx.search.type = PGP_KEY_SEARCH_KEYID; + keyctx.search.by.keyid = sinfo.sig->keyid(); + } else { + RNP_LOG("cannot get signer's key fp or id from signature."); + sinfo.unknown = true; + return; + } + /* Get the public key */ + pgp_key_t *key = pgp_request_key(param.handler->key_provider, &keyctx); + if (!key) { + /* fallback to secret key */ + keyctx.secret = true; + if (!(key = pgp_request_key(param.handler->key_provider, &keyctx))) { + RNP_LOG("signer's key not found"); + sinfo.no_signer = true; + return; + } + } + try { + /* Get the hash context and clone it. */ + auto hash = get_hash_for_sig(param, sinfo); + if (!hash) { + RNP_LOG("failed to get hash context."); + return; + } + auto shash = hash->clone(); + key->validate_sig(sinfo, *shash, *param.handler->ctx->ctx); + } catch (const std::exception &e) { + RNP_LOG("Signature validation failed: %s", e.what()); + sinfo.valid = false; + } +} + +static long +stripped_line_len(uint8_t *begin, uint8_t *end) +{ + uint8_t *stripped_end = end; + + while (stripped_end >= begin && (*stripped_end == CH_CR || *stripped_end == CH_LF)) { + stripped_end--; + } + + return stripped_end - begin + 1; +} + +static void +signed_src_update(pgp_source_t *src, const void *buf, size_t len) +{ + if (!len) { + return; + } + /* check for extremely unlikely pointer overflow/wrap case */ + if (((uint8_t *) buf + len) < ((uint8_t *) buf + len - 1)) { + signed_src_update(src, buf, len - 1); + uint8_t last = *((uint8_t *) buf + len - 1); + signed_src_update(src, &last, 1); + } + pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) src->param; + try { + param->hashes.add(buf, len); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + } + /* update text-mode sig hashes */ + if (param->txt_hashes.hashes.empty()) { + return; + } + + uint8_t *ch = (uint8_t *) buf; + uint8_t *linebeg = ch; + uint8_t *end = (uint8_t *) buf + len; + /* we support LF and CRLF line endings */ + while (ch < end) { + /* continue if not reached LF */ + if (*ch != CH_LF) { + if (*ch != CH_CR && param->stripped_crs > 0) { + while (param->stripped_crs--) { + try { + param->txt_hashes.add(ST_CR, 1); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + } + } + param->stripped_crs = 0; + } + + if (!param->max_line_warn && param->text_line_len >= MAXIMUM_GNUPG_LINELEN) { + RNP_LOG("Canonical text document signature: line is too long, may cause " + "incompatibility with other implementations. Consider using binary " + "signature instead."); + param->max_line_warn = true; + } + + ch++; + param->text_line_len++; + continue; + } + /* reached eol: dump line contents */ + param->stripped_crs = 0; + param->text_line_len = 0; + if (ch > linebeg) { + long stripped_len = stripped_line_len(linebeg, ch); + if (stripped_len > 0) { + try { + param->txt_hashes.add(linebeg, stripped_len); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + } + } + } + /* dump EOL */ + try { + param->txt_hashes.add(ST_CRLF, 2); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + } + ch++; + linebeg = ch; + } + /* check if we have undumped line contents */ + if (linebeg < end) { + long stripped_len = stripped_line_len(linebeg, end - 1); + if (stripped_len < end - linebeg) { + param->stripped_crs = end - linebeg - stripped_len; + } + if (stripped_len > 0) { + try { + param->txt_hashes.add(linebeg, stripped_len); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + } + } + } +} + +static bool +signed_src_read(pgp_source_t *src, void *buf, size_t len, size_t *read) +{ + pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) src->param; + if (!param) { + return false; + } + return src_read(param->readsrc, buf, len, read); +} + +static void +signed_src_close(pgp_source_t *src) +{ + pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) src->param; + if (!param) { + return; + } + delete param; + src->param = NULL; +} + +#define MAX_SIGNATURES 16384 + +static rnp_result_t +signed_read_single_signature(pgp_source_signed_param_t *param, + pgp_source_t * readsrc, + pgp_signature_t ** sig) +{ + uint8_t ptag; + if (!src_peek_eq(readsrc, &ptag, 1)) { + RNP_LOG("failed to read signature packet header"); + return RNP_ERROR_READ; + } + + int ptype = get_packet_type(ptag); + if (ptype != PGP_PKT_SIGNATURE) { + RNP_LOG("unexpected packet %d", ptype); + return RNP_ERROR_BAD_FORMAT; + } + + if (param->siginfos.size() >= MAX_SIGNATURES) { + RNP_LOG("Too many signatures in the stream."); + return RNP_ERROR_BAD_FORMAT; + } + + try { + param->siginfos.emplace_back(); + pgp_signature_info_t &siginfo = param->siginfos.back(); + pgp_signature_t readsig; + if (readsig.parse(*readsrc)) { + RNP_LOG("failed to parse signature"); + siginfo.unknown = true; + if (sig) { + *sig = NULL; + } + return RNP_SUCCESS; + } + param->sigs.push_back(std::move(readsig)); + siginfo.sig = ¶m->sigs.back(); + if (sig) { + *sig = siginfo.sig; + } + return RNP_SUCCESS; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } +} + +static rnp_result_t +signed_read_cleartext_signatures(pgp_source_t &src, pgp_source_signed_param_t *param) +{ + try { + rnp::ArmoredSource armor(*param->readsrc); + while (!armor.eof()) { + auto ret = signed_read_single_signature(param, &armor.src(), NULL); + if (ret) { + return ret; + } + } + return RNP_SUCCESS; + } catch (const rnp::rnp_exception &e) { + RNP_LOG("%s", e.what()); + return e.code(); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_BAD_FORMAT; + } +} + +static rnp_result_t +signed_read_signatures(pgp_source_t *src) +{ + pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) src->param; + + /* reading signatures */ + for (auto op = param->onepasses.rbegin(); op != param->onepasses.rend(); op++) { + pgp_signature_t *sig = NULL; + rnp_result_t ret = signed_read_single_signature(param, src, &sig); + /* we have more onepasses then signatures */ + if (ret == RNP_ERROR_READ) { + RNP_LOG("Warning: premature end of signatures"); + return param->siginfos.size() ? RNP_SUCCESS : ret; + } + if (ret) { + return ret; + } + if (sig && !sig->matches_onepass(*op)) { + RNP_LOG("Warning: signature doesn't match one-pass"); + } + } + return RNP_SUCCESS; +} + +static rnp_result_t +signed_src_finish(pgp_source_t *src) +{ + pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) src->param; + rnp_result_t ret = RNP_ERROR_GENERIC; + + if (param->cleartext) { + ret = signed_read_cleartext_signatures(*src, param); + } else { + ret = signed_read_signatures(src); + } + + if (ret) { + return ret; + } + + if (!src_eof(src)) { + RNP_LOG("warning: unexpected data on the stream end"); + } + + /* validating signatures */ + for (auto &sinfo : param->siginfos) { + if (!sinfo.sig) { + continue; + } + signed_validate_signature(*param, sinfo); + } + + /* checking the validation results */ + ret = RNP_ERROR_SIGNATURE_INVALID; + for (auto &sinfo : param->siginfos) { + if (sinfo.valid) { + /* If we have at least one valid signature then data is safe to process */ + ret = RNP_SUCCESS; + break; + } + } + + /* call the callback with signature infos */ + if (param->handler->on_signatures) { + param->handler->on_signatures(param->siginfos, param->handler->param); + } + return ret; +} + +/* + * str is a string to tokenize. + * delims is a string containing a list of delimiter characters. + * result is a container<string_type> that supports push_back. + */ +template <typename T> +static void +tokenize(const typename T::value_type &str, const typename T::value_type &delims, T &result) +{ + typedef typename T::value_type::size_type string_size_t; + const string_size_t npos = T::value_type::npos; + + result.clear(); + string_size_t current; + string_size_t next = 0; + do { + next = str.find_first_not_of(delims, next); + if (next == npos) { + break; + } + current = next; + next = str.find_first_of(delims, current); + string_size_t count = (next == npos) ? npos : (next - current); + result.push_back(str.substr(current, count)); + } while (next != npos); +} + +static bool +cleartext_parse_headers(pgp_source_t *src) +{ + pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) src->param; + char hdr[1024] = {0}; + char * hval; + pgp_hash_alg_t halg; + size_t hdrlen; + + do { + if (!src_peek_line(param->readsrc, hdr, sizeof(hdr), &hdrlen)) { + RNP_LOG("failed to peek line"); + return false; + } + + if (!hdrlen) { + break; + } + + if (rnp::is_blank_line(hdr, hdrlen)) { + src_skip(param->readsrc, hdrlen); + break; + } + + try { + if ((hdrlen >= 6) && !strncmp(hdr, ST_HEADER_HASH, 6)) { + hval = hdr + 6; + + std::string remainder = hval; + + const std::string delimiters = ", \t"; + std::vector<std::string> tokens; + + tokenize(remainder, delimiters, tokens); + + for (const auto &token : tokens) { + if ((halg = rnp::Hash::alg(token.c_str())) == PGP_HASH_UNKNOWN) { + RNP_LOG("unknown halg: %s", token.c_str()); + continue; + } + add_hash_for_sig(param, PGP_SIG_TEXT, halg); + } + } else { + RNP_LOG("unknown header '%s'", hdr); + } + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } + + src_skip(param->readsrc, hdrlen); + + if (!src_skip_eol(param->readsrc)) { + return false; + } + } while (1); + + /* we have exactly one empty line after the headers */ + return src_skip_eol(param->readsrc); +} + +static void +cleartext_process_line(pgp_source_t *src, const uint8_t *buf, size_t len, bool eol) +{ + pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) src->param; + uint8_t * bufen = (uint8_t *) buf + len - 1; + + /* check for dashes only if we are not in the middle */ + if (!param->clr_mline && (len > 0) && (buf[0] == CH_DASH)) { + if ((len > 1) && (buf[1] == CH_SPACE)) { + buf += 2; + len -= 2; + } else if ((len > 5) && !memcmp(buf, ST_DASHES, 5)) { + param->clr_eod = true; + return; + } else { + RNP_LOG("dash at the line begin"); + } + } + + /* hash eol if it is not the first line and we are not in the middle */ + if (!param->clr_fline && !param->clr_mline) { + /* we hash \r\n after the previous line to not hash the last eol before the sig */ + signed_src_update(src, ST_CRLF, 2); + } + + if (!len) { + return; + } + + if (len + param->outlen > sizeof(param->out)) { + RNP_LOG("wrong state"); + return; + } + + /* if we have eol after this line then strip trailing spaces and tabs */ + if (eol) { + for (; (bufen >= buf) && + ((*bufen == CH_SPACE) || (*bufen == CH_TAB) || (*bufen == CH_CR)); + bufen--) + ; + } + + if ((len = bufen + 1 - buf)) { + memcpy(param->out + param->outlen, buf, len); + param->outlen += len; + signed_src_update(src, buf, len); + } +} + +static bool +cleartext_src_read(pgp_source_t *src, void *buf, size_t len, size_t *readres) +{ + pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) src->param; + if (!param) { + return false; + } + + uint8_t srcb[CT_BUF_LEN]; + uint8_t *cur, *en, *bg; + size_t read = 0; + size_t origlen = len; + + read = param->outlen - param->outpos; + if (read >= len) { + memcpy(buf, param->out + param->outpos, len); + param->outpos += len; + if (param->outpos == param->outlen) { + param->outpos = param->outlen = 0; + } + *readres = len; + return true; + } else if (read > 0) { + memcpy(buf, param->out + param->outpos, read); + len -= read; + buf = (uint8_t *) buf + read; + param->outpos = param->outlen = 0; + } + + if (param->clr_eod) { + *readres = origlen - len; + return true; + } + + do { + if (!src_peek(param->readsrc, srcb, sizeof(srcb), &read)) { + return false; + } else if (!read) { + break; + } + + /* processing data line by line, eol could be \n or \r\n */ + for (cur = srcb, bg = srcb, en = cur + read; cur < en; cur++) { + if ((*cur == CH_LF) || + ((*cur == CH_CR) && (cur + 1 < en) && (*(cur + 1) == CH_LF))) { + cleartext_process_line(src, bg, cur - bg, true); + /* processing eol */ + if (param->clr_eod) { + break; + } + + /* processing eol */ + param->clr_fline = false; + param->clr_mline = false; + if (*cur == CH_CR) { + param->out[param->outlen++] = *cur++; + } + param->out[param->outlen++] = *cur; + bg = cur + 1; + } + } + + /* if line is larger then 4k then just dump it out */ + if ((bg == srcb) && !param->clr_eod) { + /* if last char is \r, and it's not the end of stream, then do not dump it */ + if ((en > bg) && (*(en - 1) == CH_CR) && (read > 1)) { + en--; + } + cleartext_process_line(src, bg, en - bg, false); + param->clr_mline = true; + bg = en; + } + src_skip(param->readsrc, bg - srcb); + + /* put data from the param->out to buf */ + read = param->outlen > len ? len : param->outlen; + memcpy(buf, param->out, read); + buf = (uint8_t *) buf + read; + len -= read; + + if (read == param->outlen) { + param->outlen = 0; + } else { + param->outpos = read; + } + + /* we got to the signature marker */ + if (param->clr_eod || !len) { + break; + } + } while (1); + + *readres = origlen - len; + return true; +} + +static bool +encrypted_decrypt_cfb_header(pgp_source_encrypted_param_t *param, + pgp_symm_alg_t alg, + uint8_t * key) +{ + pgp_crypt_t crypt; + uint8_t enchdr[PGP_MAX_BLOCK_SIZE + 2]; + uint8_t dechdr[PGP_MAX_BLOCK_SIZE + 2]; + unsigned blsize; + + if (!(blsize = pgp_block_size(alg))) { + return false; + } + + /* reading encrypted header to check the password validity */ + if (!src_peek_eq(param->pkt.readsrc, enchdr, blsize + 2)) { + RNP_LOG("failed to read encrypted header"); + return false; + } + + /* having symmetric key in keybuf let's decrypt blocksize + 2 bytes and check them */ + if (!pgp_cipher_cfb_start(&crypt, alg, key, NULL)) { + RNP_LOG("failed to start cipher"); + return false; + } + + pgp_cipher_cfb_decrypt(&crypt, dechdr, enchdr, blsize + 2); + + if ((dechdr[blsize] != dechdr[blsize - 2]) || (dechdr[blsize + 1] != dechdr[blsize - 1])) { + RNP_LOG("checksum check failed"); + goto error; + } + + src_skip(param->pkt.readsrc, blsize + 2); + param->decrypt = crypt; + + /* init mdc if it is here */ + /* RFC 4880, 5.13: Unlike the Symmetrically Encrypted Data Packet, no special CFB + * resynchronization is done after encrypting this prefix data. */ + if (param->auth_type == rnp::AuthType::None) { + pgp_cipher_cfb_resync(¶m->decrypt, enchdr + 2); + return true; + } + + try { + param->mdc = rnp::Hash::create(PGP_HASH_SHA1); + param->mdc->add(dechdr, blsize + 2); + } catch (const std::exception &e) { + RNP_LOG("cannot create sha1 hash: %s", e.what()); + goto error; + } + return true; +error: + pgp_cipher_cfb_finish(&crypt); + return false; +} + +static bool +encrypted_start_aead(pgp_source_encrypted_param_t *param, pgp_symm_alg_t alg, uint8_t *key) +{ +#if !defined(ENABLE_AEAD) + RNP_LOG("AEAD is not enabled."); + return false; +#else + size_t gran; + + if (alg != param->aead_hdr.ealg) { + return false; + } + + /* initialize cipher with key */ + if (!pgp_cipher_aead_init( + ¶m->decrypt, param->aead_hdr.ealg, param->aead_hdr.aalg, key, true)) { + return false; + } + + gran = pgp_cipher_aead_granularity(¶m->decrypt); + if (gran > sizeof(param->cache)) { + RNP_LOG("wrong granularity"); + return false; + } + + return encrypted_start_aead_chunk(param, 0, false); +#endif +} + +static bool +encrypted_try_key(pgp_source_encrypted_param_t *param, + pgp_pk_sesskey_t * sesskey, + pgp_key_pkt_t * seckey, + rnp::SecurityContext & ctx) +{ + pgp_encrypted_material_t encmaterial; + try { + if (!sesskey->parse_material(encmaterial)) { + return false; + } + seckey->material.validate(ctx, false); + if (!seckey->material.valid()) { + RNP_LOG("Attempt to decrypt using the key with invalid material."); + return false; + } + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } + + rnp::secure_array<uint8_t, PGP_MPINT_SIZE> decbuf; + /* Decrypting session key value */ + rnp_result_t err; + bool res = false; + pgp_key_material_t *keymaterial = &seckey->material; + size_t declen = 0; + switch (sesskey->alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + err = rsa_decrypt_pkcs1( + &ctx.rng, decbuf.data(), &declen, &encmaterial.rsa, &keymaterial->rsa); + if (err) { + RNP_LOG("RSA decryption failure"); + return false; + } + break; + case PGP_PKA_SM2: +#if defined(ENABLE_SM2) + declen = decbuf.size(); + err = sm2_decrypt(decbuf.data(), &declen, &encmaterial.sm2, &keymaterial->ec); + if (err != RNP_SUCCESS) { + RNP_LOG("SM2 decryption failure, error %x", (int) err); + return false; + } + break; +#else + RNP_LOG("SM2 decryption is not available."); + return false; +#endif + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: { + const rnp_result_t ret = elgamal_decrypt_pkcs1( + &ctx.rng, decbuf.data(), &declen, &encmaterial.eg, &keymaterial->eg); + if (ret) { + RNP_LOG("ElGamal decryption failure [%X]", ret); + return false; + } + break; + } + case PGP_PKA_ECDH: { + if (!curve_supported(keymaterial->ec.curve)) { + RNP_LOG("ECDH decrypt: curve %d is not supported.", (int) keymaterial->ec.curve); + return false; + } + pgp_fingerprint_t fingerprint; + if (pgp_fingerprint(fingerprint, *seckey)) { + RNP_LOG("ECDH fingerprint calculation failed"); + return false; + } + if ((keymaterial->ec.curve == PGP_CURVE_25519) && + !x25519_bits_tweaked(keymaterial->ec)) { + RNP_LOG("Warning: bits of 25519 secret key are not tweaked."); + } + declen = decbuf.size(); + err = ecdh_decrypt_pkcs5( + decbuf.data(), &declen, &encmaterial.ecdh, &keymaterial->ec, fingerprint); + if (err != RNP_SUCCESS) { + RNP_LOG("ECDH decryption error %u", err); + return false; + } + break; + } + default: + RNP_LOG("unsupported public key algorithm %d\n", seckey->alg); + return false; + } + + /* Check algorithm and key length */ + if (!pgp_is_sa_supported(decbuf[0])) { + RNP_LOG("Unsupported symmetric algorithm %" PRIu8, decbuf[0]); + return false; + } + + pgp_symm_alg_t salg = static_cast<pgp_symm_alg_t>(decbuf[0]); + size_t keylen = pgp_key_size(salg); + if (declen != keylen + 3) { + RNP_LOG("invalid symmetric key length"); + return false; + } + + /* Validate checksum */ + rnp::secure_array<unsigned, 1> checksum; + for (unsigned i = 1; i <= keylen; i++) { + checksum[0] += decbuf[i]; + } + + if ((checksum[0] & 0xffff) != + (decbuf[keylen + 2] | ((unsigned) decbuf[keylen + 1] << 8))) { + RNP_LOG("wrong checksum\n"); + return false; + } + + if (param->use_cfb()) { + /* Decrypt header */ + res = encrypted_decrypt_cfb_header(param, salg, &decbuf[1]); + } else { + /* Start AEAD decrypting, assuming we have correct key */ + res = encrypted_start_aead(param, salg, &decbuf[1]); + } + if (res) { + param->salg = salg; + } + return res; +} + +#if defined(ENABLE_AEAD) +static bool +encrypted_sesk_set_ad(pgp_crypt_t *crypt, pgp_sk_sesskey_t *skey) +{ + /* TODO: this method is exact duplicate as in stream-write.c. Not sure where to put it */ + uint8_t ad_data[4]; + + ad_data[0] = PGP_PKT_SK_SESSION_KEY | PGP_PTAG_ALWAYS_SET | PGP_PTAG_NEW_FORMAT; + ad_data[1] = skey->version; + ad_data[2] = skey->alg; + ad_data[3] = skey->aalg; + + return pgp_cipher_aead_set_ad(crypt, ad_data, 4); +} +#endif + +static int +encrypted_try_password(pgp_source_encrypted_param_t *param, const char *password) +{ + bool keyavail = false; /* tried password at least once */ + + for (auto &skey : param->symencs) { + rnp::secure_array<uint8_t, PGP_MAX_KEY_SIZE + 1> keybuf; + /* deriving symmetric key from password */ + size_t keysize = pgp_key_size(skey.alg); + if (!keysize || !pgp_s2k_derive_key(&skey.s2k, password, keybuf.data(), keysize)) { + continue; + } + pgp_crypt_t crypt; + pgp_symm_alg_t alg; + + if (skey.version == PGP_SKSK_V4) { + /* v4 symmetrically-encrypted session key */ + if (skey.enckeylen > 0) { + /* decrypting session key */ + if (!pgp_cipher_cfb_start(&crypt, skey.alg, keybuf.data(), NULL)) { + continue; + } + + pgp_cipher_cfb_decrypt(&crypt, keybuf.data(), skey.enckey, skey.enckeylen); + pgp_cipher_cfb_finish(&crypt); + + alg = (pgp_symm_alg_t) keybuf[0]; + keysize = pgp_key_size(alg); + if (!keysize || (keysize + 1 != skey.enckeylen)) { + continue; + } + memmove(keybuf.data(), keybuf.data() + 1, keysize); + } else { + alg = (pgp_symm_alg_t) skey.alg; + } + + if (!pgp_block_size(alg)) { + continue; + } + keyavail = true; + } else if (skey.version == PGP_SKSK_V5) { +#if !defined(ENABLE_AEAD) + continue; +#else + /* v5 AEAD-encrypted session key */ + size_t taglen = pgp_cipher_aead_tag_len(skey.aalg); + size_t ceklen = pgp_key_size(param->aead_hdr.ealg); + if (!taglen || !ceklen || (ceklen + taglen != skey.enckeylen)) { + RNP_LOG("CEK len/alg mismatch"); + continue; + } + alg = skey.alg; + + /* initialize cipher */ + if (!pgp_cipher_aead_init(&crypt, skey.alg, skey.aalg, keybuf.data(), true)) { + continue; + } + + /* set additional data */ + if (!encrypted_sesk_set_ad(&crypt, &skey)) { + RNP_LOG("failed to set ad"); + continue; + } + + /* calculate nonce */ + uint8_t nonce[PGP_AEAD_MAX_NONCE_LEN]; + size_t noncelen = pgp_cipher_aead_nonce(skey.aalg, skey.iv, nonce, 0); + + /* start cipher, decrypt key and verify tag */ + keyavail = + pgp_cipher_aead_start(&crypt, nonce, noncelen) && + pgp_cipher_aead_finish(&crypt, keybuf.data(), skey.enckey, skey.enckeylen); + pgp_cipher_aead_destroy(&crypt); + + /* we have decrypted key so let's start decryption */ + if (!keyavail) { + continue; + } +#endif + } else { + continue; + } + + /* Decrypt header for CFB */ + if (param->use_cfb() && !encrypted_decrypt_cfb_header(param, alg, keybuf.data())) { + continue; + } + if (!param->use_cfb() && + !encrypted_start_aead(param, param->aead_hdr.ealg, keybuf.data())) { + continue; + } + + param->salg = param->use_cfb() ? alg : param->aead_hdr.ealg; + /* inform handler that we used this symenc */ + if (param->handler->on_decryption_start) { + param->handler->on_decryption_start(NULL, &skey, param->handler->param); + } + return 1; + } + + if (!param->use_cfb() && pgp_block_size(param->aead_hdr.ealg)) { + /* we know aead symm alg even if we wasn't able to start decryption */ + param->salg = param->aead_hdr.ealg; + } + + if (!keyavail) { + RNP_LOG("no supported sk available"); + return -1; + } + return 0; +} + +/** @brief Initialize common to stream packets params, including partial data source */ +static rnp_result_t +init_packet_params(pgp_source_packet_param_t ¶m) +{ + param.origsrc = NULL; + + /* save packet header */ + rnp_result_t ret = stream_peek_packet_hdr(param.readsrc, ¶m.hdr); + if (ret) { + return ret; + } + src_skip(param.readsrc, param.hdr.hdr_len); + if (!param.hdr.partial) { + return RNP_SUCCESS; + } + + /* initialize partial reader if needed */ + pgp_source_t *partsrc = (pgp_source_t *) calloc(1, sizeof(*partsrc)); + if (!partsrc) { + return RNP_ERROR_OUT_OF_MEMORY; + } + rnp_result_t errcode = init_partial_pkt_src(partsrc, param.readsrc, param.hdr); + if (errcode) { + free(partsrc); + return errcode; + } + param.origsrc = param.readsrc; + param.readsrc = partsrc; + return RNP_SUCCESS; +} + +rnp_result_t +init_literal_src(pgp_source_t *src, pgp_source_t *readsrc) +{ + rnp_result_t ret = RNP_ERROR_GENERIC; + pgp_source_literal_param_t *param; + uint8_t format = 0; + uint8_t nlen = 0; + uint8_t timestamp[4]; + + if (!init_src_common(src, sizeof(*param))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + param = (pgp_source_literal_param_t *) src->param; + param->pkt.readsrc = readsrc; + src->read = literal_src_read; + src->close = literal_src_close; + src->type = PGP_STREAM_LITERAL; + + /* Reading packet length/checking whether it is partial */ + if ((ret = init_packet_params(param->pkt))) { + goto finish; + } + + /* data format */ + if (!src_read_eq(param->pkt.readsrc, &format, 1)) { + RNP_LOG("failed to read data format"); + ret = RNP_ERROR_READ; + goto finish; + } + + switch (format) { + case 'b': + case 't': + case 'u': + case 'l': + case '1': + break; + default: + RNP_LOG("unknown data format %" PRIu8, format); + ret = RNP_ERROR_BAD_FORMAT; + goto finish; + } + param->hdr.format = format; + /* file name */ + if (!src_read_eq(param->pkt.readsrc, &nlen, 1)) { + RNP_LOG("failed to read file name length"); + ret = RNP_ERROR_READ; + goto finish; + } + if (nlen && !src_read_eq(param->pkt.readsrc, param->hdr.fname, nlen)) { + RNP_LOG("failed to read file name"); + ret = RNP_ERROR_READ; + goto finish; + } + param->hdr.fname[nlen] = 0; + param->hdr.fname_len = nlen; + /* timestamp */ + if (!src_read_eq(param->pkt.readsrc, timestamp, 4)) { + RNP_LOG("failed to read file timestamp"); + ret = RNP_ERROR_READ; + goto finish; + } + param->hdr.timestamp = read_uint32(timestamp); + + if (!param->pkt.hdr.indeterminate && !param->pkt.hdr.partial) { + /* format filename-length filename timestamp */ + const uint16_t nbytes = 1 + 1 + nlen + 4; + if (param->pkt.hdr.pkt_len < nbytes) { + ret = RNP_ERROR_BAD_FORMAT; + goto finish; + } + src->size = param->pkt.hdr.pkt_len - nbytes; + src->knownsize = 1; + } + ret = RNP_SUCCESS; +finish: + if (ret != RNP_SUCCESS) { + src_close(src); + } + return ret; +} + +bool +get_literal_src_hdr(pgp_source_t *src, pgp_literal_hdr_t *hdr) +{ + pgp_source_literal_param_t *param; + + if (src->type != PGP_STREAM_LITERAL) { + RNP_LOG("wrong stream"); + return false; + } + + param = (pgp_source_literal_param_t *) src->param; + *hdr = param->hdr; + return true; +} + +rnp_result_t +init_compressed_src(pgp_source_t *src, pgp_source_t *readsrc) +{ + rnp_result_t errcode = RNP_ERROR_GENERIC; + pgp_source_compressed_param_t *param; + uint8_t alg; + int zret; + + if (!init_src_common(src, sizeof(*param))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + param = (pgp_source_compressed_param_t *) src->param; + param->pkt.readsrc = readsrc; + src->read = compressed_src_read; + src->close = compressed_src_close; + src->type = PGP_STREAM_COMPRESSED; + + /* Reading packet length/checking whether it is partial */ + errcode = init_packet_params(param->pkt); + if (errcode != RNP_SUCCESS) { + goto finish; + } + + /* Reading compression algorithm */ + if (!src_read_eq(param->pkt.readsrc, &alg, 1)) { + RNP_LOG("failed to read compression algorithm"); + errcode = RNP_ERROR_READ; + goto finish; + } + + /* Initializing decompression */ + switch (alg) { + case PGP_C_NONE: + break; + case PGP_C_ZIP: + case PGP_C_ZLIB: + (void) memset(¶m->z, 0x0, sizeof(param->z)); + zret = + alg == PGP_C_ZIP ? (int) inflateInit2(¶m->z, -15) : (int) inflateInit(¶m->z); + if (zret != Z_OK) { + RNP_LOG("failed to init zlib, error %d", zret); + errcode = RNP_ERROR_READ; + goto finish; + } + break; +#ifdef HAVE_BZLIB_H + case PGP_C_BZIP2: + (void) memset(¶m->bz, 0x0, sizeof(param->bz)); + zret = BZ2_bzDecompressInit(¶m->bz, 0, 0); + if (zret != BZ_OK) { + RNP_LOG("failed to init bz, error %d", zret); + errcode = RNP_ERROR_READ; + goto finish; + } + break; +#endif + default: + RNP_LOG("unknown compression algorithm: %d", (int) alg); + errcode = RNP_ERROR_BAD_FORMAT; + goto finish; + } + param->alg = (pgp_compression_type_t) alg; + param->inlen = 0; + param->inpos = 0; + + errcode = RNP_SUCCESS; +finish: + if (errcode != RNP_SUCCESS) { + src_close(src); + } + return errcode; +} + +bool +get_compressed_src_alg(pgp_source_t *src, uint8_t *alg) +{ + pgp_source_compressed_param_t *param; + + if (src->type != PGP_STREAM_COMPRESSED) { + RNP_LOG("wrong stream"); + return false; + } + + param = (pgp_source_compressed_param_t *) src->param; + *alg = param->alg; + return true; +} + +bool +get_aead_src_hdr(pgp_source_t *src, pgp_aead_hdr_t *hdr) +{ + uint8_t hdrbt[4] = {0}; + + if (!src_read_eq(src, hdrbt, 4)) { + return false; + } + + hdr->version = hdrbt[0]; + hdr->ealg = (pgp_symm_alg_t) hdrbt[1]; + hdr->aalg = (pgp_aead_alg_t) hdrbt[2]; + hdr->csize = hdrbt[3]; + + if (!(hdr->ivlen = pgp_cipher_aead_nonce_len(hdr->aalg))) { + RNP_LOG("wrong aead nonce length: alg %d", (int) hdr->aalg); + return false; + } + + return src_read_eq(src, hdr->iv, hdr->ivlen); +} + +#define MAX_RECIPIENTS 16384 + +static rnp_result_t +encrypted_read_packet_data(pgp_source_encrypted_param_t *param) +{ + int ptype; + /* Reading pk/sk encrypted session key(s) */ + try { + size_t errors = 0; + bool stop = false; + while (!stop) { + if (param->pubencs.size() + param->symencs.size() + errors > MAX_RECIPIENTS) { + RNP_LOG("Too many recipients of the encrypted message. Aborting."); + return RNP_ERROR_BAD_STATE; + } + uint8_t ptag; + if (!src_peek_eq(param->pkt.readsrc, &ptag, 1)) { + RNP_LOG("failed to read packet header"); + return RNP_ERROR_READ; + } + ptype = get_packet_type(ptag); + switch (ptype) { + case PGP_PKT_SK_SESSION_KEY: { + pgp_sk_sesskey_t skey; + rnp_result_t ret = skey.parse(*param->pkt.readsrc); + if (ret == RNP_ERROR_READ) { + RNP_LOG("SKESK: Premature end of data."); + return ret; + } + if (ret) { + RNP_LOG("Failed to parse SKESK, skipping."); + errors++; + continue; + } + param->symencs.push_back(skey); + break; + } + case PGP_PKT_PK_SESSION_KEY: { + pgp_pk_sesskey_t pkey; + rnp_result_t ret = pkey.parse(*param->pkt.readsrc); + if (ret == RNP_ERROR_READ) { + RNP_LOG("PKESK: Premature end of data."); + return ret; + } + if (ret) { + RNP_LOG("Failed to parse PKESK, skipping."); + errors++; + continue; + } + param->pubencs.push_back(pkey); + break; + } + case PGP_PKT_SE_DATA: + case PGP_PKT_SE_IP_DATA: + case PGP_PKT_AEAD_ENCRYPTED: + stop = true; + break; + default: + RNP_LOG("unknown packet type: %d", ptype); + return RNP_ERROR_BAD_FORMAT; + } + } + } catch (const rnp::rnp_exception &e) { + RNP_LOG("%s: %d", e.what(), e.code()); + return e.code(); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_GENERIC; + } + + /* Reading packet length/checking whether it is partial */ + rnp_result_t errcode = init_packet_params(param->pkt); + if (errcode) { + return errcode; + } + + /* Reading header of encrypted packet */ + if (ptype == PGP_PKT_AEAD_ENCRYPTED) { + param->auth_type = rnp::AuthType::AEADv1; + uint8_t hdr[4]; + if (!src_peek_eq(param->pkt.readsrc, hdr, 4)) { + return RNP_ERROR_READ; + } + + if (!get_aead_src_hdr(param->pkt.readsrc, ¶m->aead_hdr)) { + RNP_LOG("failed to read AEAD header"); + return RNP_ERROR_READ; + } + + /* check AEAD encrypted data packet header */ + if (param->aead_hdr.version != 1) { + RNP_LOG("unknown aead ver: %d", param->aead_hdr.version); + return RNP_ERROR_BAD_FORMAT; + } + if ((param->aead_hdr.aalg != PGP_AEAD_EAX) && (param->aead_hdr.aalg != PGP_AEAD_OCB)) { + RNP_LOG("unknown aead alg: %d", (int) param->aead_hdr.aalg); + return RNP_ERROR_BAD_FORMAT; + } + if (param->aead_hdr.csize > 56) { + RNP_LOG("too large chunk size: %d", param->aead_hdr.csize); + return RNP_ERROR_BAD_FORMAT; + } + if (param->aead_hdr.csize > 16) { + RNP_LOG("Warning: AEAD chunk bits > 16."); + } + param->chunklen = 1L << (param->aead_hdr.csize + 6); + + /* build additional data */ + param->aead_adlen = 13; + param->aead_ad[0] = param->pkt.hdr.hdr[0]; + memcpy(param->aead_ad + 1, hdr, 4); + memset(param->aead_ad + 5, 0, 8); + } else if (ptype == PGP_PKT_SE_IP_DATA) { + uint8_t mdcver; + if (!src_read_eq(param->pkt.readsrc, &mdcver, 1)) { + return RNP_ERROR_READ; + } + + if (mdcver != 1) { + RNP_LOG("unknown mdc ver: %d", (int) mdcver); + return RNP_ERROR_BAD_FORMAT; + } + param->auth_type = rnp::AuthType::MDC; + } + param->auth_validated = false; + + return RNP_SUCCESS; +} + +#define MAX_HIDDEN_TRIES 64 + +static rnp_result_t +init_encrypted_src(pgp_parse_handler_t *handler, pgp_source_t *src, pgp_source_t *readsrc) +{ + if (!init_src_common(src, 0)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + pgp_source_encrypted_param_t *param = new (std::nothrow) pgp_source_encrypted_param_t(); + if (!param) { + return RNP_ERROR_OUT_OF_MEMORY; + } + src->param = param; + param->pkt.readsrc = readsrc; + param->handler = handler; + + src->close = encrypted_src_close; + src->finish = encrypted_src_finish; + src->type = PGP_STREAM_ENCRYPTED; + + /* Read the packet-related information */ + rnp_result_t errcode = encrypted_read_packet_data(param); + if (errcode) { + goto finish; + } + + src->read = !param->use_cfb() ? encrypted_src_read_aead : encrypted_src_read_cfb; + + /* Obtaining the symmetric key */ + if (!handler->password_provider) { + RNP_LOG("no password provider"); + errcode = RNP_ERROR_BAD_PARAMETERS; + goto finish; + } + + /* informing handler about the available pubencs/symencs */ + if (handler->on_recipients) { + handler->on_recipients(param->pubencs, param->symencs, handler->param); + } + + bool have_key; + have_key = false; + /* Trying public-key decryption */ + if (!param->pubencs.empty()) { + if (!handler->key_provider) { + RNP_LOG("no key provider"); + errcode = RNP_ERROR_BAD_PARAMETERS; + goto finish; + } + + pgp_key_request_ctx_t keyctx(PGP_OP_DECRYPT, true, PGP_KEY_SEARCH_KEYID); + + size_t pubidx = 0; + size_t hidden_tries = 0; + errcode = RNP_ERROR_NO_SUITABLE_KEY; + while (pubidx < param->pubencs.size()) { + auto &pubenc = param->pubencs[pubidx]; + keyctx.search.by.keyid = pubenc.key_id; + /* Get the key if any */ + pgp_key_t *seckey = pgp_request_key(handler->key_provider, &keyctx); + if (!seckey) { + pubidx++; + continue; + } + /* Check whether key fits our needs */ + bool hidden = pubenc.key_id == pgp_key_id_t({}); + if (!hidden || (++hidden_tries >= MAX_HIDDEN_TRIES)) { + pubidx++; + } + if (!seckey->has_secret() || !seckey->can_encrypt()) { + continue; + } + /* Check whether key is of required algorithm for hidden keyid */ + if (hidden && seckey->alg() != pubenc.alg) { + continue; + } + /* Decrypt key */ + rnp::KeyLocker seclock(*seckey); + if (!seckey->unlock(*handler->password_provider, PGP_OP_DECRYPT)) { + errcode = RNP_ERROR_BAD_PASSWORD; + continue; + } + + /* Try to initialize the decryption */ + rnp::LogStop logstop(hidden); + if (encrypted_try_key(param, &pubenc, &seckey->pkt(), *handler->ctx->ctx)) { + have_key = true; + /* inform handler that we used this pubenc */ + if (handler->on_decryption_start) { + handler->on_decryption_start(&pubenc, NULL, handler->param); + } + break; + } + } + } + + /* Trying password-based decryption */ + if (!have_key && !param->symencs.empty()) { + rnp::secure_array<char, MAX_PASSWORD_LENGTH> password; + pgp_password_ctx_t pass_ctx(PGP_OP_DECRYPT_SYM); + if (!pgp_request_password( + handler->password_provider, &pass_ctx, password.data(), password.size())) { + errcode = RNP_ERROR_BAD_PASSWORD; + goto finish; + } + + int intres = encrypted_try_password(param, password.data()); + if (intres > 0) { + have_key = true; + } else if (intres < 0) { + errcode = RNP_ERROR_NOT_SUPPORTED; + } else { + errcode = RNP_ERROR_BAD_PASSWORD; + } + } + + /* report decryption start to the handler */ + if (handler->on_decryption_info) { + handler->on_decryption_info(param->auth_type == rnp::AuthType::MDC, + param->aead_hdr.aalg, + param->salg, + handler->param); + } + + if (!have_key) { + RNP_LOG("failed to obtain decrypting key or password"); + if (!errcode) { + errcode = RNP_ERROR_NO_SUITABLE_KEY; + } + goto finish; + } + errcode = RNP_SUCCESS; +finish: + if (errcode != RNP_SUCCESS) { + src_close(src); + } + return errcode; +} + +static rnp_result_t +init_cleartext_signed_src(pgp_source_t *src) +{ + char buf[64]; + size_t hdrlen = strlen(ST_CLEAR_BEGIN); + pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) src->param; + + /* checking header line */ + if (!src_read_eq(param->readsrc, buf, hdrlen)) { + RNP_LOG("failed to read header"); + return RNP_ERROR_READ; + } + + if (memcmp(ST_CLEAR_BEGIN, buf, hdrlen)) { + RNP_LOG("wrong header"); + return RNP_ERROR_BAD_FORMAT; + } + + /* eol */ + if (!src_skip_eol(param->readsrc)) { + RNP_LOG("no eol after the cleartext header"); + return RNP_ERROR_BAD_FORMAT; + } + + /* parsing Hash headers */ + if (!cleartext_parse_headers(src)) { + return RNP_ERROR_BAD_FORMAT; + } + + /* now we are good to go */ + param->clr_fline = true; + return RNP_SUCCESS; +} + +#define MAX_SIG_ERRORS 65536 + +static rnp_result_t +init_signed_src(pgp_parse_handler_t *handler, pgp_source_t *src, pgp_source_t *readsrc) +{ + rnp_result_t errcode = RNP_ERROR_GENERIC; + pgp_source_signed_param_t *param; + uint8_t ptag; + int ptype; + pgp_signature_t * sig = NULL; + bool cleartext; + size_t sigerrors = 0; + + if (!init_src_common(src, 0)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + try { + param = new pgp_source_signed_param_t(); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + src->param = param; + + cleartext = is_cleartext_source(readsrc); + param->readsrc = readsrc; + param->handler = handler; + param->cleartext = cleartext; + param->stripped_crs = 0; + src->read = cleartext ? cleartext_src_read : signed_src_read; + src->close = signed_src_close; + src->finish = signed_src_finish; + src->type = cleartext ? PGP_STREAM_CLEARTEXT : PGP_STREAM_SIGNED; + + /* we need key provider to validate signatures */ + if (!handler->key_provider) { + RNP_LOG("no key provider"); + errcode = RNP_ERROR_BAD_PARAMETERS; + goto finish; + } + + if (cleartext) { + errcode = init_cleartext_signed_src(src); + goto finish; + } + + /* Reading one-pass and signature packets */ + while (true) { + /* stop early if we are in zip-bomb with erroneous packets */ + if (sigerrors >= MAX_SIG_ERRORS) { + RNP_LOG("Too many one-pass/signature errors. Stopping."); + errcode = RNP_ERROR_BAD_FORMAT; + goto finish; + } + + size_t readb = readsrc->readb; + if (!src_peek_eq(readsrc, &ptag, 1)) { + RNP_LOG("failed to read packet header"); + errcode = RNP_ERROR_READ; + goto finish; + } + + ptype = get_packet_type(ptag); + + if (ptype == PGP_PKT_ONE_PASS_SIG) { + if (param->onepasses.size() >= MAX_SIGNATURES) { + RNP_LOG("Too many one-pass signatures."); + errcode = RNP_ERROR_BAD_FORMAT; + goto finish; + } + pgp_one_pass_sig_t onepass; + try { + errcode = onepass.parse(*readsrc); + } catch (const std::exception &e) { + errcode = RNP_ERROR_GENERIC; + } + if (errcode) { + if (errcode == RNP_ERROR_READ) { + goto finish; + } + if (readb == readsrc->readb) { + errcode = RNP_ERROR_BAD_FORMAT; + goto finish; + } + sigerrors++; + continue; + } + + try { + param->onepasses.push_back(onepass); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + errcode = RNP_ERROR_OUT_OF_MEMORY; + goto finish; + } + + /* adding hash context */ + try { + add_hash_for_sig(param, onepass.type, onepass.halg); + } catch (const std::exception &e) { + RNP_LOG("Failed to create hash %d for onepass %d : %s.", + (int) onepass.halg, + (int) onepass.type, + e.what()); + errcode = RNP_ERROR_BAD_PARAMETERS; + goto finish; + } + + if (onepass.nested) { + /* despite the name non-zero value means that it is the last one-pass */ + break; + } + } else if (ptype == PGP_PKT_SIGNATURE) { + /* no need to check the error here - we already know tag */ + if (signed_read_single_signature(param, readsrc, &sig)) { + sigerrors++; + } + /* adding hash context */ + if (sig) { + try { + add_hash_for_sig(param, sig->type(), sig->halg); + } catch (const std::exception &e) { + RNP_LOG("Failed to create hash %d for sig %d : %s.", + (int) sig->halg, + (int) sig->type(), + e.what()); + errcode = RNP_ERROR_BAD_PARAMETERS; + goto finish; + } + } + } else { + break; + } + + /* check if we are not it endless loop */ + if (readb == readsrc->readb) { + errcode = RNP_ERROR_BAD_FORMAT; + goto finish; + } + /* for detached signature we'll get eof */ + if (src_eof(readsrc)) { + param->detached = true; + break; + } + } + + /* checking what we have now */ + if (param->onepasses.empty() && param->sigs.empty()) { + RNP_LOG("no signatures"); + errcode = RNP_ERROR_BAD_PARAMETERS; + goto finish; + } + if (!param->onepasses.empty() && !param->sigs.empty()) { + RNP_LOG("warning: one-passes are mixed with signatures"); + } + + errcode = RNP_SUCCESS; +finish: + if (errcode != RNP_SUCCESS) { + src_close(src); + } + + return errcode; +} + +pgp_processing_ctx_t::~pgp_processing_ctx_t() +{ + for (auto &src : sources) { + src_close(&src); + } +} + +/** @brief build PGP source sequence down to the literal data packet + * + **/ +static rnp_result_t +init_packet_sequence(pgp_processing_ctx_t &ctx, pgp_source_t &src) +{ + pgp_source_t *lsrc = &src; + size_t srcnum = ctx.sources.size(); + + while (1) { + uint8_t ptag = 0; + if (!src_peek_eq(lsrc, &ptag, 1)) { + RNP_LOG("cannot read packet tag"); + return RNP_ERROR_READ; + } + + int type = get_packet_type(ptag); + if (type < 0) { + RNP_LOG("wrong pkt tag %d", (int) ptag); + return RNP_ERROR_BAD_FORMAT; + } + + if (ctx.sources.size() - srcnum == MAXIMUM_NESTING_LEVEL) { + RNP_LOG("Too many nested OpenPGP packets"); + return RNP_ERROR_BAD_FORMAT; + } + + pgp_source_t psrc = {}; + rnp_result_t ret = RNP_ERROR_BAD_FORMAT; + switch (type) { + case PGP_PKT_PK_SESSION_KEY: + case PGP_PKT_SK_SESSION_KEY: + ret = init_encrypted_src(&ctx.handler, &psrc, lsrc); + break; + case PGP_PKT_ONE_PASS_SIG: + case PGP_PKT_SIGNATURE: + ret = init_signed_src(&ctx.handler, &psrc, lsrc); + break; + case PGP_PKT_COMPRESSED: + ret = init_compressed_src(&psrc, lsrc); + break; + case PGP_PKT_LITDATA: + if ((lsrc != &src) && (lsrc->type != PGP_STREAM_ENCRYPTED) && + (lsrc->type != PGP_STREAM_SIGNED) && (lsrc->type != PGP_STREAM_COMPRESSED)) { + RNP_LOG("unexpected literal pkt"); + ret = RNP_ERROR_BAD_FORMAT; + break; + } + ret = init_literal_src(&psrc, lsrc); + break; + case PGP_PKT_MARKER: + if (ctx.sources.size() != srcnum) { + RNP_LOG("Warning: marker packet wrapped in pgp stream."); + } + ret = stream_parse_marker(*lsrc); + if (ret) { + RNP_LOG("Invalid marker packet"); + return ret; + } + continue; + default: + RNP_LOG("unexpected pkt %d", type); + ret = RNP_ERROR_BAD_FORMAT; + } + + if (ret) { + return ret; + } + + try { + ctx.sources.push_back(psrc); + lsrc = &ctx.sources.back(); + } catch (const std::exception &e) { + src_close(&psrc); + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + + if (lsrc->type == PGP_STREAM_LITERAL) { + ctx.literal_src = lsrc; + ctx.msg_type = PGP_MESSAGE_NORMAL; + return RNP_SUCCESS; + } + if (lsrc->type == PGP_STREAM_SIGNED) { + ctx.signed_src = lsrc; + pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) lsrc->param; + if (param->detached) { + ctx.msg_type = PGP_MESSAGE_DETACHED; + return RNP_SUCCESS; + } + } + } +} + +static rnp_result_t +init_cleartext_sequence(pgp_processing_ctx_t &ctx, pgp_source_t &src) +{ + pgp_source_t clrsrc = {}; + rnp_result_t res; + + if ((res = init_signed_src(&ctx.handler, &clrsrc, &src))) { + return res; + } + try { + ctx.sources.push_back(clrsrc); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + src_close(&clrsrc); + return RNP_ERROR_OUT_OF_MEMORY; + } + return RNP_SUCCESS; +} + +static rnp_result_t +init_armored_sequence(pgp_processing_ctx_t &ctx, pgp_source_t &src) +{ + pgp_source_t armorsrc = {}; + rnp_result_t res; + + if ((res = init_armored_src(&armorsrc, &src))) { + return res; + } + + try { + ctx.sources.push_back(armorsrc); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + src_close(&armorsrc); + return RNP_ERROR_OUT_OF_MEMORY; + } + return init_packet_sequence(ctx, ctx.sources.back()); +} + +rnp_result_t +process_pgp_source(pgp_parse_handler_t *handler, pgp_source_t &src) +{ + rnp_result_t res = RNP_ERROR_BAD_FORMAT; + rnp_result_t fres; + pgp_processing_ctx_t ctx = {}; + pgp_source_t * decsrc = NULL; + pgp_source_t datasrc = {0}; + pgp_dest_t * outdest = NULL; + bool closeout = true; + uint8_t * readbuf = NULL; + + ctx.handler = *handler; + /* Building readers sequence. Checking whether it is binary data */ + if (is_pgp_source(src)) { + res = init_packet_sequence(ctx, src); + } else { + /* Trying armored or cleartext data */ + if (is_cleartext_source(&src)) { + /* Initializing cleartext message */ + res = init_cleartext_sequence(ctx, src); + } else if (is_armored_source(&src)) { + /* Initializing armored message */ + res = init_armored_sequence(ctx, src); + } else { + RNP_LOG("not an OpenPGP data provided"); + res = RNP_ERROR_BAD_FORMAT; + goto finish; + } + } + + if (res != RNP_SUCCESS) { + goto finish; + } + + if ((readbuf = (uint8_t *) calloc(1, PGP_INPUT_CACHE_SIZE)) == NULL) { + RNP_LOG("allocation failure"); + res = RNP_ERROR_OUT_OF_MEMORY; + goto finish; + } + + if (ctx.msg_type == PGP_MESSAGE_DETACHED) { + /* detached signature case */ + if (!handler->ctx->detached) { + RNP_LOG("Unexpected detached signature input."); + res = RNP_ERROR_BAD_STATE; + goto finish; + } + if (!handler->src_provider || !handler->src_provider(handler, &datasrc)) { + RNP_LOG("no data source for detached signature verification"); + res = RNP_ERROR_READ; + goto finish; + } + + while (!datasrc.eof) { + size_t read = 0; + if (!src_read(&datasrc, readbuf, PGP_INPUT_CACHE_SIZE, &read)) { + res = RNP_ERROR_GENERIC; + break; + } + if (read > 0) { + signed_src_update(ctx.signed_src, readbuf, read); + } + } + src_close(&datasrc); + } else { + if (handler->ctx->detached) { + RNP_LOG("Detached signature expected."); + res = RNP_ERROR_BAD_STATE; + goto finish; + } + /* file processing case */ + decsrc = &ctx.sources.back(); + char * filename = NULL; + uint32_t mtime = 0; + + if (ctx.literal_src) { + auto *param = static_cast<pgp_source_literal_param_t *>(ctx.literal_src->param); + filename = param->hdr.fname; + mtime = param->hdr.timestamp; + } + + if (!handler->dest_provider || + !handler->dest_provider(handler, &outdest, &closeout, filename, mtime)) { + res = RNP_ERROR_WRITE; + goto finish; + } + + /* reading the input */ + while (!decsrc->eof) { + size_t read = 0; + if (!src_read(decsrc, readbuf, PGP_INPUT_CACHE_SIZE, &read)) { + res = RNP_ERROR_GENERIC; + break; + } + if (!read) { + continue; + } + if (ctx.signed_src) { + signed_src_update(ctx.signed_src, readbuf, read); + } + dst_write(outdest, readbuf, read); + if (outdest->werr != RNP_SUCCESS) { + RNP_LOG("failed to output data"); + res = RNP_ERROR_WRITE; + break; + } + } + } + + /* finalizing the input. Signatures are checked on this step */ + if (res == RNP_SUCCESS) { + for (auto &ctxsrc : ctx.sources) { + fres = src_finish(&ctxsrc); + if (fres) { + res = fres; + } + } + } + + if (closeout && (ctx.msg_type != PGP_MESSAGE_DETACHED)) { + dst_close(outdest, res != RNP_SUCCESS); + } + +finish: + free(readbuf); + return res; +} diff --git a/comm/third_party/rnp/src/librepgp/stream-parse.h b/comm/third_party/rnp/src/librepgp/stream-parse.h new file mode 100644 index 0000000000..4f22b9a6ca --- /dev/null +++ b/comm/third_party/rnp/src/librepgp/stream-parse.h @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2017, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef STREAM_PARSE_H_ +#define STREAM_PARSE_H_ + +#include <stdint.h> +#include <stdbool.h> +#include <sys/types.h> +#include "rnp.h" +#include "stream-common.h" +#include "stream-ctx.h" +#include "stream-packet.h" + +typedef struct pgp_parse_handler_t pgp_parse_handler_t; +typedef struct pgp_signature_info_t pgp_signature_info_t; +typedef bool pgp_destination_func_t(pgp_parse_handler_t *handler, + pgp_dest_t ** dst, + bool * closedst, + const char * filename, + uint32_t mtime); +typedef bool pgp_source_func_t(pgp_parse_handler_t *handler, pgp_source_t *src); +typedef void pgp_signatures_func_t(const std::vector<pgp_signature_info_t> &sigs, void *param); + +typedef void pgp_on_recipients_func_t(const std::vector<pgp_pk_sesskey_t> &recipients, + const std::vector<pgp_sk_sesskey_t> &passwords, + void * param); +typedef void pgp_decryption_start_func_t(pgp_pk_sesskey_t *pubenc, + pgp_sk_sesskey_t *symenc, + void * param); +typedef void pgp_decryption_info_func_t(bool mdc, + pgp_aead_alg_t aead, + pgp_symm_alg_t salg, + void * param); +typedef void pgp_decryption_done_func_t(bool validated, void *param); + +/* handler used to return needed information during pgp source processing */ +typedef struct pgp_parse_handler_t { + pgp_password_provider_t *password_provider; /* if NULL then default will be used */ + pgp_key_provider_t * key_provider; /* must be set when key is required, i.e. during + signing/verification/public key encryption and + deryption */ + pgp_destination_func_t *dest_provider; /* called when destination stream is required */ + pgp_source_func_t * src_provider; /* required to provider source during the detached + signature verification */ + pgp_on_recipients_func_t * on_recipients; /* called before decryption start */ + pgp_decryption_start_func_t *on_decryption_start; /* called when decryption key obtained */ + pgp_decryption_info_func_t * on_decryption_info; /* called when decryption is started */ + pgp_decryption_done_func_t * on_decryption_done; /* called when decryption is finished */ + pgp_signatures_func_t * on_signatures; /* for signature verification results */ + + rnp_ctx_t *ctx; /* operation context */ + void * param; /* additional parameters */ +} pgp_parse_handler_t; + +/* @brief Process the OpenPGP source: file, memory, stdin + * Function will parse input data, provided by any source conforming to pgp_source_t, + * autodetecting whether it is armored, cleartext or binary. + * @param handler handler to respond on stream reader callbacks + * @param src initialized source with cache + * @return RNP_SUCCESS on success or error code otherwise + **/ +rnp_result_t process_pgp_source(pgp_parse_handler_t *handler, pgp_source_t &src); + +/* @brief Init source with OpenPGP compressed data packet + * @param src allocated pgp_source_t structure + * @param readsrc source to read compressed data from + * @return RNP_SUCCESS on success or error code otherwise + */ +rnp_result_t init_compressed_src(pgp_source_t *src, pgp_source_t *readsrc); + +/* @brief Get compression algorithm used in compressed source + * @param src compressed source, initialized with init_compressed_src + * @param alg algorithm will be written here. Cannot be NULL. + * @return true if operation succeeded and alg is populate or false otherwise + */ +bool get_compressed_src_alg(pgp_source_t *src, uint8_t *alg); + +/* @brief Init source with OpenPGP literal data packet + * @param src allocated pgp_source_t structure + * @param readsrc source to read literal data from + * @return RNP_SUCCESS on success or error code otherwise + */ +rnp_result_t init_literal_src(pgp_source_t *src, pgp_source_t *readsrc); + +/* @brief Get the literal data packet information fields (not the OpenPGP packet header) + * @param src literal data source, initialized with init_literal_src + * @param hdr pointer to header structure, where result will be stored + * @return true on success or false otherwise + */ +bool get_literal_src_hdr(pgp_source_t *src, pgp_literal_hdr_t *hdr); + +/* @brief Get the AEAD-encrypted packet information fields (not the OpenPGP packet header) + * @param src AEAD-encrypted data source (starting from packet data itself, not the header) + * @param hdr pointer to header structure, where result will be stored + * @return true on success or false otherwise + */ +bool get_aead_src_hdr(pgp_source_t *src, pgp_aead_hdr_t *hdr); + +#endif diff --git a/comm/third_party/rnp/src/librepgp/stream-sig.cpp b/comm/third_party/rnp/src/librepgp/stream-sig.cpp new file mode 100644 index 0000000000..6f3bc81fe1 --- /dev/null +++ b/comm/third_party/rnp/src/librepgp/stream-sig.cpp @@ -0,0 +1,1557 @@ +/* + * Copyright (c) 2018-2022, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include <stdlib.h> +#include <stdio.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#else +#include "uniwin.h" +#endif +#include <string.h> +#include <type_traits> +#include <stdexcept> +#include <rnp/rnp_def.h> +#include "types.h" +#include "stream-sig.h" +#include "stream-packet.h" +#include "stream-armor.h" +#include "pgp-key.h" +#include "crypto/signatures.h" + +#include <time.h> + +void +signature_hash_key(const pgp_key_pkt_t &key, rnp::Hash &hash) +{ + uint8_t hdr[3] = {0x99, 0x00, 0x00}; + if (key.hashed_data) { + write_uint16(hdr + 1, key.hashed_len); + hash.add(hdr, 3); + hash.add(key.hashed_data, key.hashed_len); + return; + } + + /* call self recursively if hashed data is not filled, to overcome const restriction */ + pgp_key_pkt_t keycp(key, true); + keycp.fill_hashed_data(); + signature_hash_key(keycp, hash); +} + +void +signature_hash_userid(const pgp_userid_pkt_t &uid, rnp::Hash &hash, pgp_version_t sigver) +{ + if (sigver < PGP_V4) { + hash.add(uid.uid, uid.uid_len); + return; + } + + uint8_t hdr[5] = {0}; + switch (uid.tag) { + case PGP_PKT_USER_ID: + hdr[0] = 0xB4; + break; + case PGP_PKT_USER_ATTR: + hdr[0] = 0xD1; + break; + default: + RNP_LOG("wrong uid"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + STORE32BE(hdr + 1, uid.uid_len); + hash.add(hdr, 5); + hash.add(uid.uid, uid.uid_len); +} + +std::unique_ptr<rnp::Hash> +signature_hash_certification(const pgp_signature_t & sig, + const pgp_key_pkt_t & key, + const pgp_userid_pkt_t &userid) +{ + auto hash = signature_init(key.material, sig.halg); + signature_hash_key(key, *hash); + signature_hash_userid(userid, *hash, sig.version); + return hash; +} + +std::unique_ptr<rnp::Hash> +signature_hash_binding(const pgp_signature_t &sig, + const pgp_key_pkt_t & key, + const pgp_key_pkt_t & subkey) +{ + auto hash = signature_init(key.material, sig.halg); + signature_hash_key(key, *hash); + signature_hash_key(subkey, *hash); + return hash; +} + +std::unique_ptr<rnp::Hash> +signature_hash_direct(const pgp_signature_t &sig, const pgp_key_pkt_t &key) +{ + auto hash = signature_init(key.material, sig.halg); + signature_hash_key(key, *hash); + return hash; +} + +rnp_result_t +process_pgp_signatures(pgp_source_t &src, pgp_signature_list_t &sigs) +{ + sigs.clear(); + /* Allow binary or armored input, including multiple armored messages */ + rnp::ArmoredSource armor( + src, rnp::ArmoredSource::AllowBinary | rnp::ArmoredSource::AllowMultiple); + /* read sequence of OpenPGP signatures */ + while (!armor.error()) { + if (armor.eof() && armor.multiple()) { + armor.restart(); + } + if (armor.eof()) { + break; + } + int ptag = stream_pkt_type(armor.src()); + if (ptag != PGP_PKT_SIGNATURE) { + RNP_LOG("wrong signature tag: %d", ptag); + sigs.clear(); + return RNP_ERROR_BAD_FORMAT; + } + + sigs.emplace_back(); + rnp_result_t ret = sigs.back().parse(armor.src()); + if (ret) { + sigs.clear(); + return ret; + } + } + if (armor.error()) { + sigs.clear(); + return RNP_ERROR_READ; + } + return RNP_SUCCESS; +} + +pgp_sig_subpkt_t::pgp_sig_subpkt_t(const pgp_sig_subpkt_t &src) +{ + type = src.type; + len = src.len; + data = (uint8_t *) malloc(len); + if (!data) { + throw std::bad_alloc(); + } + memcpy(data, src.data, len); + critical = src.critical; + hashed = src.hashed; + parsed = false; + parse(); +} + +pgp_sig_subpkt_t::pgp_sig_subpkt_t(pgp_sig_subpkt_t &&src) +{ + type = src.type; + len = src.len; + data = src.data; + src.data = NULL; + critical = src.critical; + hashed = src.hashed; + parsed = src.parsed; + memcpy(&fields, &src.fields, sizeof(fields)); + src.fields = {}; +} + +pgp_sig_subpkt_t & +pgp_sig_subpkt_t::operator=(pgp_sig_subpkt_t &&src) +{ + if (&src == this) { + return *this; + } + + if (parsed && (type == PGP_SIG_SUBPKT_EMBEDDED_SIGNATURE)) { + delete fields.sig; + } + type = src.type; + len = src.len; + free(data); + data = src.data; + src.data = NULL; + critical = src.critical; + hashed = src.hashed; + parsed = src.parsed; + fields = src.fields; + src.fields = {}; + return *this; +} + +pgp_sig_subpkt_t & +pgp_sig_subpkt_t::operator=(const pgp_sig_subpkt_t &src) +{ + if (&src == this) { + return *this; + } + + if (parsed && (type == PGP_SIG_SUBPKT_EMBEDDED_SIGNATURE)) { + delete fields.sig; + } + type = src.type; + len = src.len; + free(data); + data = (uint8_t *) malloc(len); + if (!data) { + throw std::bad_alloc(); + } + memcpy(data, src.data, len); + critical = src.critical; + hashed = src.hashed; + parsed = false; + fields = {}; + parse(); + return *this; +} + +bool +pgp_sig_subpkt_t::parse() +{ + bool oklen = true; + bool checked = true; + + switch (type) { + case PGP_SIG_SUBPKT_CREATION_TIME: + if (!hashed) { + RNP_LOG("creation time subpacket must be hashed"); + checked = false; + } + if ((oklen = len == 4)) { + fields.create = read_uint32(data); + } + break; + case PGP_SIG_SUBPKT_EXPIRATION_TIME: + case PGP_SIG_SUBPKT_KEY_EXPIRY: + if ((oklen = len == 4)) { + fields.expiry = read_uint32(data); + } + break; + case PGP_SIG_SUBPKT_EXPORT_CERT: + if ((oklen = len == 1)) { + fields.exportable = data[0] != 0; + } + break; + case PGP_SIG_SUBPKT_TRUST: + if ((oklen = len == 2)) { + fields.trust.level = data[0]; + fields.trust.amount = data[1]; + } + break; + case PGP_SIG_SUBPKT_REGEXP: + fields.regexp.str = (const char *) data; + fields.regexp.len = len; + break; + case PGP_SIG_SUBPKT_REVOCABLE: + if ((oklen = len == 1)) { + fields.revocable = data[0] != 0; + } + break; + case PGP_SIG_SUBPKT_PREFERRED_SKA: + case PGP_SIG_SUBPKT_PREFERRED_HASH: + case PGP_SIG_SUBPKT_PREF_COMPRESS: + case PGP_SIG_SUBPKT_PREFERRED_AEAD: + fields.preferred.arr = data; + fields.preferred.len = len; + break; + case PGP_SIG_SUBPKT_REVOCATION_KEY: + if ((oklen = len == 22)) { + fields.revocation_key.klass = data[0]; + fields.revocation_key.pkalg = (pgp_pubkey_alg_t) data[1]; + fields.revocation_key.fp = &data[2]; + } + break; + case PGP_SIG_SUBPKT_ISSUER_KEY_ID: + if ((oklen = len == 8)) { + fields.issuer = data; + } + break; + case PGP_SIG_SUBPKT_NOTATION_DATA: + if ((oklen = len >= 8)) { + memcpy(fields.notation.flags, data, 4); + fields.notation.human = fields.notation.flags[0] & 0x80; + fields.notation.nlen = read_uint16(&data[4]); + fields.notation.vlen = read_uint16(&data[6]); + if (len != 8 + fields.notation.nlen + fields.notation.vlen) { + oklen = false; + } else { + fields.notation.name = data + 8; + fields.notation.value = fields.notation.name + fields.notation.nlen; + } + } + break; + case PGP_SIG_SUBPKT_KEYSERV_PREFS: + if ((oklen = len >= 1)) { + fields.ks_prefs.no_modify = (data[0] & 0x80) != 0; + } + break; + case PGP_SIG_SUBPKT_PREF_KEYSERV: + fields.preferred_ks.uri = (const char *) data; + fields.preferred_ks.len = len; + break; + case PGP_SIG_SUBPKT_PRIMARY_USER_ID: + if ((oklen = len == 1)) { + fields.primary_uid = data[0] != 0; + } + break; + case PGP_SIG_SUBPKT_POLICY_URI: + fields.policy.uri = (const char *) data; + fields.policy.len = len; + break; + case PGP_SIG_SUBPKT_KEY_FLAGS: + if ((oklen = len >= 1)) { + fields.key_flags = data[0]; + } + break; + case PGP_SIG_SUBPKT_SIGNERS_USER_ID: + fields.signer.uid = (const char *) data; + fields.signer.len = len; + break; + case PGP_SIG_SUBPKT_REVOCATION_REASON: + if ((oklen = len >= 1)) { + fields.revocation_reason.code = (pgp_revocation_type_t) data[0]; + fields.revocation_reason.str = (const char *) &data[1]; + fields.revocation_reason.len = len - 1; + } + break; + case PGP_SIG_SUBPKT_FEATURES: + if ((oklen = len >= 1)) { + fields.features = data[0]; + } + break; + case PGP_SIG_SUBPKT_SIGNATURE_TARGET: + if ((oklen = len >= 18)) { + fields.sig_target.pkalg = (pgp_pubkey_alg_t) data[0]; + fields.sig_target.halg = (pgp_hash_alg_t) data[1]; + fields.sig_target.hash = &data[2]; + fields.sig_target.hlen = len - 2; + } + break; + case PGP_SIG_SUBPKT_EMBEDDED_SIGNATURE: + try { + /* parse signature */ + pgp_packet_body_t pkt(data, len); + pgp_signature_t sig; + oklen = checked = !sig.parse(pkt); + if (checked) { + fields.sig = new pgp_signature_t(std::move(sig)); + } + break; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } + case PGP_SIG_SUBPKT_ISSUER_FPR: + if ((oklen = len >= 21)) { + fields.issuer_fp.version = data[0]; + fields.issuer_fp.fp = &data[1]; + fields.issuer_fp.len = len - 1; + } + break; + case PGP_SIG_SUBPKT_PRIVATE_100: + case PGP_SIG_SUBPKT_PRIVATE_101: + case PGP_SIG_SUBPKT_PRIVATE_102: + case PGP_SIG_SUBPKT_PRIVATE_103: + case PGP_SIG_SUBPKT_PRIVATE_104: + case PGP_SIG_SUBPKT_PRIVATE_105: + case PGP_SIG_SUBPKT_PRIVATE_106: + case PGP_SIG_SUBPKT_PRIVATE_107: + case PGP_SIG_SUBPKT_PRIVATE_108: + case PGP_SIG_SUBPKT_PRIVATE_109: + case PGP_SIG_SUBPKT_PRIVATE_110: + oklen = true; + checked = !critical; + if (!checked) { + RNP_LOG("unknown critical private subpacket %d", (int) type); + } + break; + case PGP_SIG_SUBPKT_RESERVED_1: + case PGP_SIG_SUBPKT_RESERVED_8: + case PGP_SIG_SUBPKT_PLACEHOLDER: + case PGP_SIG_SUBPKT_RESERVED_13: + case PGP_SIG_SUBPKT_RESERVED_14: + case PGP_SIG_SUBPKT_RESERVED_15: + case PGP_SIG_SUBPKT_RESERVED_17: + case PGP_SIG_SUBPKT_RESERVED_18: + case PGP_SIG_SUBPKT_RESERVED_19: + /* do not report reserved/placeholder subpacket */ + return !critical; + default: + RNP_LOG("unknown subpacket : %d", (int) type); + return !critical; + } + + if (!oklen) { + RNP_LOG("wrong len %d of subpacket type %d", (int) len, (int) type); + } else { + parsed = 1; + } + return oklen && checked; +} + +pgp_sig_subpkt_t::~pgp_sig_subpkt_t() +{ + if (parsed && (type == PGP_SIG_SUBPKT_EMBEDDED_SIGNATURE)) { + delete fields.sig; + } + free(data); +} + +pgp_signature_t::pgp_signature_t(const pgp_signature_t &src) +{ + version = src.version; + type_ = src.type_; + palg = src.palg; + halg = src.halg; + memcpy(lbits, src.lbits, sizeof(src.lbits)); + creation_time = src.creation_time; + signer = src.signer; + + hashed_len = src.hashed_len; + hashed_data = NULL; + if (src.hashed_data) { + if (!(hashed_data = (uint8_t *) malloc(hashed_len))) { + throw std::bad_alloc(); + } + memcpy(hashed_data, src.hashed_data, hashed_len); + } + material_len = src.material_len; + material_buf = NULL; + if (src.material_buf) { + if (!(material_buf = (uint8_t *) malloc(material_len))) { + throw std::bad_alloc(); + } + memcpy(material_buf, src.material_buf, material_len); + } + subpkts = src.subpkts; +} + +pgp_signature_t::pgp_signature_t(pgp_signature_t &&src) +{ + version = src.version; + type_ = src.type_; + palg = src.palg; + halg = src.halg; + memcpy(lbits, src.lbits, sizeof(src.lbits)); + creation_time = src.creation_time; + signer = src.signer; + hashed_len = src.hashed_len; + hashed_data = src.hashed_data; + src.hashed_data = NULL; + material_len = src.material_len; + material_buf = src.material_buf; + src.material_buf = NULL; + subpkts = std::move(src.subpkts); +} + +pgp_signature_t & +pgp_signature_t::operator=(pgp_signature_t &&src) +{ + if (this == &src) { + return *this; + } + + version = src.version; + type_ = src.type_; + palg = src.palg; + halg = src.halg; + memcpy(lbits, src.lbits, sizeof(src.lbits)); + creation_time = src.creation_time; + signer = src.signer; + hashed_len = src.hashed_len; + free(hashed_data); + hashed_data = src.hashed_data; + src.hashed_data = NULL; + material_len = src.material_len; + free(material_buf); + material_buf = src.material_buf; + src.material_buf = NULL; + subpkts = std::move(src.subpkts); + + return *this; +} + +pgp_signature_t & +pgp_signature_t::operator=(const pgp_signature_t &src) +{ + if (this == &src) { + return *this; + } + + version = src.version; + type_ = src.type_; + palg = src.palg; + halg = src.halg; + memcpy(lbits, src.lbits, sizeof(src.lbits)); + creation_time = src.creation_time; + signer = src.signer; + + hashed_len = src.hashed_len; + free(hashed_data); + hashed_data = NULL; + if (src.hashed_data) { + if (!(hashed_data = (uint8_t *) malloc(hashed_len))) { + throw std::bad_alloc(); + } + memcpy(hashed_data, src.hashed_data, hashed_len); + } + material_len = src.material_len; + free(material_buf); + material_buf = NULL; + if (src.material_buf) { + if (!(material_buf = (uint8_t *) malloc(material_len))) { + throw std::bad_alloc(); + } + memcpy(material_buf, src.material_buf, material_len); + } + subpkts = src.subpkts; + + return *this; +} + +bool +pgp_signature_t::operator==(const pgp_signature_t &src) const +{ + if ((lbits[0] != src.lbits[0]) || (lbits[1] != src.lbits[1])) { + return false; + } + if ((hashed_len != src.hashed_len) || memcmp(hashed_data, src.hashed_data, hashed_len)) { + return false; + } + return (material_len == src.material_len) && + !memcmp(material_buf, src.material_buf, material_len); +} + +bool +pgp_signature_t::operator!=(const pgp_signature_t &src) const +{ + return !(*this == src); +} + +pgp_signature_t::~pgp_signature_t() +{ + free(hashed_data); + free(material_buf); +} + +pgp_sig_id_t +pgp_signature_t::get_id() const +{ + auto hash = rnp::Hash::create(PGP_HASH_SHA1); + hash->add(hashed_data, hashed_len); + hash->add(material_buf, material_len); + pgp_sig_id_t res = {0}; + static_assert(std::tuple_size<decltype(res)>::value == PGP_SHA1_HASH_SIZE, + "pgp_sig_id_t size mismatch"); + hash->finish(res.data()); + return res; +} + +pgp_sig_subpkt_t * +pgp_signature_t::get_subpkt(pgp_sig_subpacket_type_t stype, bool hashed) +{ + if (version < PGP_V4) { + return NULL; + } + for (auto &subpkt : subpkts) { + /* if hashed is false then accept any hashed/not hashed subpacket */ + if ((subpkt.type == stype) && (!hashed || subpkt.hashed)) { + return &subpkt; + } + } + return NULL; +} + +const pgp_sig_subpkt_t * +pgp_signature_t::get_subpkt(pgp_sig_subpacket_type_t stype, bool hashed) const +{ + if (version < PGP_V4) { + return NULL; + } + for (auto &subpkt : subpkts) { + /* if hashed is false then accept any hashed/not hashed subpacket */ + if ((subpkt.type == stype) && (!hashed || subpkt.hashed)) { + return &subpkt; + } + } + return NULL; +} + +bool +pgp_signature_t::has_subpkt(pgp_sig_subpacket_type_t stype, bool hashed) const +{ + if (version < PGP_V4) { + return false; + } + for (auto &subpkt : subpkts) { + /* if hashed is false then accept any hashed/not hashed subpacket */ + if ((subpkt.type == stype) && (!hashed || subpkt.hashed)) { + return true; + } + } + return false; +} + +bool +pgp_signature_t::has_keyid() const +{ + return (version < PGP_V4) || has_subpkt(PGP_SIG_SUBPKT_ISSUER_KEY_ID, false) || + has_keyfp(); +} + +pgp_key_id_t +pgp_signature_t::keyid() const noexcept +{ + /* version 3 uses signature field */ + if (version < PGP_V4) { + return signer; + } + + /* version 4 and up use subpackets */ + pgp_key_id_t res{}; + static_assert(std::tuple_size<decltype(res)>::value == PGP_KEY_ID_SIZE, + "pgp_key_id_t size mismatch"); + + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_ISSUER_KEY_ID, false); + if (subpkt) { + memcpy(res.data(), subpkt->fields.issuer, PGP_KEY_ID_SIZE); + return res; + } + if ((subpkt = get_subpkt(PGP_SIG_SUBPKT_ISSUER_FPR))) { + memcpy(res.data(), + subpkt->fields.issuer_fp.fp + subpkt->fields.issuer_fp.len - PGP_KEY_ID_SIZE, + PGP_KEY_ID_SIZE); + return res; + } + return res; +} + +void +pgp_signature_t::set_keyid(const pgp_key_id_t &id) +{ + if (version < PGP_V4) { + signer = id; + return; + } + + static_assert(std::tuple_size<std::remove_reference<decltype(id)>::type>::value == + PGP_KEY_ID_SIZE, + "pgp_key_id_t size mismatch"); + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_ISSUER_KEY_ID, PGP_KEY_ID_SIZE, true); + subpkt.parsed = true; + subpkt.hashed = false; + memcpy(subpkt.data, id.data(), PGP_KEY_ID_SIZE); + subpkt.fields.issuer = subpkt.data; +} + +bool +pgp_signature_t::has_keyfp() const +{ + if (version < PGP_V4) { + return false; + } + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_ISSUER_FPR); + return subpkt && (subpkt->fields.issuer_fp.len <= PGP_FINGERPRINT_SIZE); +} + +pgp_fingerprint_t +pgp_signature_t::keyfp() const noexcept +{ + pgp_fingerprint_t res{}; + if (version < PGP_V4) { + return res; + } + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_ISSUER_FPR); + if (!subpkt || (subpkt->fields.issuer_fp.len > sizeof(res.fingerprint))) { + return res; + } + res.length = subpkt->fields.issuer_fp.len; + memcpy(res.fingerprint, subpkt->fields.issuer_fp.fp, subpkt->fields.issuer_fp.len); + return res; +} + +void +pgp_signature_t::set_keyfp(const pgp_fingerprint_t &fp) +{ + if (version < PGP_V4) { + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_ISSUER_FPR, 1 + fp.length, true); + subpkt.parsed = true; + subpkt.hashed = true; + subpkt.data[0] = 4; + memcpy(subpkt.data + 1, fp.fingerprint, fp.length); + subpkt.fields.issuer_fp.len = fp.length; + subpkt.fields.issuer_fp.version = subpkt.data[0]; + subpkt.fields.issuer_fp.fp = subpkt.data + 1; +} + +uint32_t +pgp_signature_t::creation() const +{ + if (version < PGP_V4) { + return creation_time; + } + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_CREATION_TIME); + return subpkt ? subpkt->fields.create : 0; +} + +void +pgp_signature_t::set_creation(uint32_t ctime) +{ + if (version < PGP_V4) { + creation_time = ctime; + return; + } + + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_CREATION_TIME, 4, true); + subpkt.parsed = true; + subpkt.hashed = true; + STORE32BE(subpkt.data, ctime); + subpkt.fields.create = ctime; +} + +uint32_t +pgp_signature_t::expiration() const +{ + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_EXPIRATION_TIME); + return subpkt ? subpkt->fields.expiry : 0; +} + +void +pgp_signature_t::set_expiration(uint32_t etime) +{ + if (version < PGP_V4) { + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_EXPIRATION_TIME, 4, true); + subpkt.parsed = true; + subpkt.hashed = true; + STORE32BE(subpkt.data, etime); + subpkt.fields.expiry = etime; +} + +uint32_t +pgp_signature_t::key_expiration() const +{ + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_KEY_EXPIRY); + return subpkt ? subpkt->fields.expiry : 0; +} + +void +pgp_signature_t::set_key_expiration(uint32_t etime) +{ + if (version < PGP_V4) { + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_KEY_EXPIRY, 4, true); + subpkt.parsed = true; + subpkt.hashed = true; + STORE32BE(subpkt.data, etime); + subpkt.fields.expiry = etime; +} + +uint8_t +pgp_signature_t::key_flags() const +{ + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_KEY_FLAGS); + return subpkt ? subpkt->fields.key_flags : 0; +} + +void +pgp_signature_t::set_key_flags(uint8_t flags) +{ + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_KEY_FLAGS, 1, true); + subpkt.parsed = true; + subpkt.hashed = true; + subpkt.data[0] = flags; + subpkt.fields.key_flags = flags; +} + +bool +pgp_signature_t::primary_uid() const +{ + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_PRIMARY_USER_ID); + return subpkt ? subpkt->fields.primary_uid : false; +} + +void +pgp_signature_t::set_primary_uid(bool primary) +{ + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_PRIMARY_USER_ID, 1, true); + subpkt.parsed = true; + subpkt.hashed = true; + subpkt.data[0] = primary; + subpkt.fields.primary_uid = primary; +} + +std::vector<uint8_t> +pgp_signature_t::preferred(pgp_sig_subpacket_type_t type) const +{ + const pgp_sig_subpkt_t *subpkt = get_subpkt(type); + return subpkt ? std::vector<uint8_t>(subpkt->fields.preferred.arr, + subpkt->fields.preferred.arr + + subpkt->fields.preferred.len) : + std::vector<uint8_t>(); +} + +void +pgp_signature_t::set_preferred(const std::vector<uint8_t> &data, pgp_sig_subpacket_type_t type) +{ + if (version < PGP_V4) { + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + + if (data.empty()) { + pgp_sig_subpkt_t *subpkt = get_subpkt(type); + if (subpkt) { + remove_subpkt(subpkt); + } + return; + } + + pgp_sig_subpkt_t &subpkt = add_subpkt(type, data.size(), true); + subpkt.parsed = true; + subpkt.hashed = true; + memcpy(subpkt.data, data.data(), data.size()); + subpkt.fields.preferred.arr = subpkt.data; + subpkt.fields.preferred.len = data.size(); +} + +std::vector<uint8_t> +pgp_signature_t::preferred_symm_algs() const +{ + return preferred(PGP_SIG_SUBPKT_PREFERRED_SKA); +} + +void +pgp_signature_t::set_preferred_symm_algs(const std::vector<uint8_t> &algs) +{ + set_preferred(algs, PGP_SIG_SUBPKT_PREFERRED_SKA); +} + +std::vector<uint8_t> +pgp_signature_t::preferred_hash_algs() const +{ + return preferred(PGP_SIG_SUBPKT_PREFERRED_HASH); +} + +void +pgp_signature_t::set_preferred_hash_algs(const std::vector<uint8_t> &algs) +{ + set_preferred(algs, PGP_SIG_SUBPKT_PREFERRED_HASH); +} + +std::vector<uint8_t> +pgp_signature_t::preferred_z_algs() const +{ + return preferred(PGP_SIG_SUBPKT_PREF_COMPRESS); +} + +void +pgp_signature_t::set_preferred_z_algs(const std::vector<uint8_t> &algs) +{ + set_preferred(algs, PGP_SIG_SUBPKT_PREF_COMPRESS); +} + +uint8_t +pgp_signature_t::key_server_prefs() const +{ + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_KEYSERV_PREFS); + return subpkt ? subpkt->data[0] : 0; +} + +void +pgp_signature_t::set_key_server_prefs(uint8_t prefs) +{ + if (version < PGP_V4) { + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_KEYSERV_PREFS, 1, true); + subpkt.parsed = true; + subpkt.hashed = true; + subpkt.data[0] = prefs; + subpkt.fields.ks_prefs.no_modify = prefs & 0x80; +} + +std::string +pgp_signature_t::key_server() const +{ + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_PREF_KEYSERV); + return subpkt ? std::string((char *) subpkt->data, subpkt->len) : ""; +} + +void +pgp_signature_t::set_key_server(const std::string &uri) +{ + if (version < PGP_V4) { + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + + if (uri.empty()) { + pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_PREF_KEYSERV); + if (subpkt) { + remove_subpkt(subpkt); + } + return; + } + + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_PREF_KEYSERV, uri.size(), true); + subpkt.parsed = true; + subpkt.hashed = true; + memcpy(subpkt.data, uri.data(), uri.size()); + subpkt.fields.preferred_ks.uri = (char *) subpkt.data; + subpkt.fields.preferred_ks.len = uri.size(); +} + +uint8_t +pgp_signature_t::trust_level() const +{ + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_TRUST); + return subpkt ? subpkt->fields.trust.level : 0; +} + +uint8_t +pgp_signature_t::trust_amount() const +{ + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_TRUST); + return subpkt ? subpkt->fields.trust.amount : 0; +} + +void +pgp_signature_t::set_trust(uint8_t level, uint8_t amount) +{ + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_TRUST, 2, true); + subpkt.parsed = true; + subpkt.hashed = true; + subpkt.data[0] = level; + subpkt.data[1] = amount; + subpkt.fields.trust.level = level; + subpkt.fields.trust.amount = amount; +} + +bool +pgp_signature_t::revocable() const +{ + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_REVOCABLE); + return subpkt ? subpkt->fields.revocable : true; +} + +void +pgp_signature_t::set_revocable(bool status) +{ + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_REVOCABLE, 1, true); + subpkt.parsed = true; + subpkt.hashed = true; + subpkt.data[0] = status; + subpkt.fields.revocable = status; +} + +std::string +pgp_signature_t::revocation_reason() const +{ + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_REVOCATION_REASON); + return subpkt ? std::string(subpkt->fields.revocation_reason.str, + subpkt->fields.revocation_reason.len) : + ""; +} + +pgp_revocation_type_t +pgp_signature_t::revocation_code() const +{ + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_REVOCATION_REASON); + return subpkt ? subpkt->fields.revocation_reason.code : PGP_REVOCATION_NO_REASON; +} + +void +pgp_signature_t::set_revocation_reason(pgp_revocation_type_t code, const std::string &reason) +{ + size_t datalen = 1 + reason.size(); + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_REVOCATION_REASON, datalen, true); + subpkt.hashed = true; + subpkt.data[0] = code; + memcpy(subpkt.data + 1, reason.data(), reason.size()); + + if (!subpkt.parse()) { + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } +} + +bool +pgp_signature_t::key_has_features(pgp_key_feature_t flags) const +{ + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_FEATURES); + return subpkt ? subpkt->data[0] & flags : false; +} + +void +pgp_signature_t::set_key_features(pgp_key_feature_t flags) +{ + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_FEATURES, 1, true); + subpkt.hashed = true; + subpkt.data[0] = flags; + subpkt.fields.features = flags; + subpkt.parsed = true; +} + +std::string +pgp_signature_t::signer_uid() const +{ + const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_SIGNERS_USER_ID); + return subpkt ? std::string(subpkt->fields.signer.uid, subpkt->fields.signer.len) : ""; +} + +void +pgp_signature_t::set_signer_uid(const std::string &uid) +{ + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_SIGNERS_USER_ID, uid.size(), true); + subpkt.hashed = true; + memcpy(subpkt.data, uid.data(), uid.size()); + subpkt.fields.signer.uid = (const char *) subpkt.data; + subpkt.fields.signer.len = subpkt.len; + subpkt.parsed = true; +} + +void +pgp_signature_t::add_notation(const std::string & name, + const std::vector<uint8_t> &value, + bool human, + bool critical) +{ + auto nlen = name.size(); + auto vlen = value.size(); + if ((nlen > 0xffff) || (vlen > 0xffff)) { + RNP_LOG("wrong length"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + auto &subpkt = add_subpkt(PGP_SIG_SUBPKT_NOTATION_DATA, 8 + nlen + vlen, false); + subpkt.hashed = true; + subpkt.critical = critical; + if (human) { + subpkt.data[0] = 0x80; + } + write_uint16(subpkt.data + 4, nlen); + write_uint16(subpkt.data + 6, vlen); + memcpy(subpkt.data + 8, name.data(), nlen); + memcpy(subpkt.data + 8 + nlen, value.data(), vlen); + if (!subpkt.parse()) { + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } +} + +void +pgp_signature_t::add_notation(const std::string &name, const std::string &value, bool critical) +{ + add_notation(name, std::vector<uint8_t>(value.begin(), value.end()), true, critical); +} + +void +pgp_signature_t::set_embedded_sig(const pgp_signature_t &esig) +{ + pgp_rawpacket_t esigpkt(esig); + rnp::MemorySource mem(esigpkt.raw); + size_t len = 0; + stream_read_pkt_len(&mem.src(), &len); + if (!len || (len > 0xffff) || (len >= esigpkt.raw.size())) { + RNP_LOG("wrong pkt len"); + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_EMBEDDED_SIGNATURE, len, true); + subpkt.hashed = false; + size_t skip = esigpkt.raw.size() - len; + memcpy(subpkt.data, esigpkt.raw.data() + skip, len); + subpkt.fields.sig = new pgp_signature_t(esig); + subpkt.parsed = true; +} + +pgp_sig_subpkt_t & +pgp_signature_t::add_subpkt(pgp_sig_subpacket_type_t type, size_t datalen, bool reuse) +{ + if (version < PGP_V4) { + RNP_LOG("wrong signature version"); + throw std::invalid_argument("version"); + } + + uint8_t *newdata = (uint8_t *) calloc(1, datalen); + if (!newdata) { + RNP_LOG("Allocation failed"); + throw std::bad_alloc(); + } + + pgp_sig_subpkt_t *subpkt = NULL; + if (reuse && (subpkt = get_subpkt(type))) { + *subpkt = {}; + } else { + subpkts.push_back({}); + subpkt = &subpkts.back(); + } + + subpkt->data = newdata; + subpkt->type = type; + subpkt->len = datalen; + return *subpkt; +} + +void +pgp_signature_t::remove_subpkt(pgp_sig_subpkt_t *subpkt) +{ + for (auto it = subpkts.begin(); it < subpkts.end(); it++) { + if (&*it == subpkt) { + subpkts.erase(it); + return; + } + } +} + +bool +pgp_signature_t::matches_onepass(const pgp_one_pass_sig_t &onepass) const +{ + if (!has_keyid()) { + return false; + } + return (halg == onepass.halg) && (palg == onepass.palg) && (type_ == onepass.type) && + (onepass.keyid == keyid()); +} + +rnp_result_t +pgp_signature_t::parse_v3(pgp_packet_body_t &pkt) +{ + /* parse v3-specific fields, not the whole signature */ + uint8_t buf[16] = {}; + if (!pkt.get(buf, 16)) { + RNP_LOG("cannot get enough bytes"); + return RNP_ERROR_BAD_FORMAT; + } + /* length of hashed data, 5 */ + if (buf[0] != 5) { + RNP_LOG("wrong length of hashed data"); + return RNP_ERROR_BAD_FORMAT; + } + /* hashed data */ + free(hashed_data); + if (!(hashed_data = (uint8_t *) malloc(5))) { + RNP_LOG("allocation failed"); + return RNP_ERROR_OUT_OF_MEMORY; + } + memcpy(hashed_data, &buf[1], 5); + hashed_len = 5; + /* signature type */ + type_ = (pgp_sig_type_t) buf[1]; + /* creation time */ + creation_time = read_uint32(&buf[2]); + /* signer's key id */ + static_assert(std::tuple_size<decltype(signer)>::value == PGP_KEY_ID_SIZE, + "v3 signer field size mismatch"); + memcpy(signer.data(), &buf[6], PGP_KEY_ID_SIZE); + /* public key algorithm */ + palg = (pgp_pubkey_alg_t) buf[14]; + /* hash algorithm */ + halg = (pgp_hash_alg_t) buf[15]; + return RNP_SUCCESS; +} + +#define MAX_SUBPACKETS 64 + +bool +pgp_signature_t::parse_subpackets(uint8_t *buf, size_t len, bool hashed) +{ + bool res = true; + + while (len > 0) { + if (subpkts.size() >= MAX_SUBPACKETS) { + RNP_LOG("too many signature subpackets"); + return false; + } + if (len < 2) { + RNP_LOG("got single byte %d", (int) *buf); + return false; + } + + /* subpacket length */ + size_t splen; + if (*buf < 192) { + splen = *buf; + buf++; + len--; + } else if (*buf < 255) { + splen = ((buf[0] - 192) << 8) + buf[1] + 192; + buf += 2; + len -= 2; + } else { + if (len < 5) { + RNP_LOG("got 4-byte len but only %d bytes in buffer", (int) len); + return false; + } + splen = read_uint32(&buf[1]); + buf += 5; + len -= 5; + } + + if (splen < 1) { + RNP_LOG("got subpacket with 0 length"); + return false; + } + + /* subpacket data */ + if (len < splen) { + RNP_LOG("got subpacket len %d, while only %d bytes left", (int) splen, (int) len); + return false; + } + + pgp_sig_subpkt_t subpkt; + if (!(subpkt.data = (uint8_t *) malloc(splen - 1))) { + RNP_LOG("subpacket data allocation failed"); + return false; + } + + subpkt.type = (pgp_sig_subpacket_type_t)(*buf & 0x7f); + subpkt.critical = !!(*buf & 0x80); + subpkt.hashed = hashed; + subpkt.parsed = 0; + memcpy(subpkt.data, buf + 1, splen - 1); + subpkt.len = splen - 1; + + res = res && subpkt.parse(); + subpkts.push_back(std::move(subpkt)); + len -= splen; + buf += splen; + } + return res; +} + +rnp_result_t +pgp_signature_t::parse_v4(pgp_packet_body_t &pkt) +{ + /* parse v4-specific fields, not the whole signature */ + uint8_t buf[5]; + if (!pkt.get(buf, 5)) { + RNP_LOG("cannot get first 5 bytes"); + return RNP_ERROR_BAD_FORMAT; + } + + /* signature type */ + type_ = (pgp_sig_type_t) buf[0]; + /* public key algorithm */ + palg = (pgp_pubkey_alg_t) buf[1]; + /* hash algorithm */ + halg = (pgp_hash_alg_t) buf[2]; + /* hashed subpackets length */ + uint16_t splen = read_uint16(&buf[3]); + /* hashed subpackets length + 2 bytes of length of unhashed subpackets */ + if (pkt.left() < (size_t)(splen + 2)) { + RNP_LOG("wrong packet or hashed subpackets length"); + return RNP_ERROR_BAD_FORMAT; + } + /* building hashed data */ + free(hashed_data); + if (!(hashed_data = (uint8_t *) malloc(splen + 6))) { + RNP_LOG("allocation failed"); + return RNP_ERROR_OUT_OF_MEMORY; + } + hashed_data[0] = version; + memcpy(hashed_data + 1, buf, 5); + + if (!pkt.get(hashed_data + 6, splen)) { + RNP_LOG("cannot get hashed subpackets data"); + return RNP_ERROR_BAD_FORMAT; + } + hashed_len = splen + 6; + /* parsing hashed subpackets */ + if (!parse_subpackets(hashed_data + 6, splen, true)) { + RNP_LOG("failed to parse hashed subpackets"); + return RNP_ERROR_BAD_FORMAT; + } + /* reading unhashed subpackets */ + if (!pkt.get(splen)) { + RNP_LOG("cannot get unhashed len"); + return RNP_ERROR_BAD_FORMAT; + } + if (pkt.left() < splen) { + RNP_LOG("not enough data for unhashed subpackets"); + return RNP_ERROR_BAD_FORMAT; + } + std::vector<uint8_t> spbuf(splen); + if (!pkt.get(spbuf.data(), splen)) { + RNP_LOG("read of unhashed subpackets failed"); + return RNP_ERROR_READ; + } + if (!parse_subpackets(spbuf.data(), splen, false)) { + RNP_LOG("failed to parse unhashed subpackets"); + return RNP_ERROR_BAD_FORMAT; + } + return RNP_SUCCESS; +} + +rnp_result_t +pgp_signature_t::parse(pgp_packet_body_t &pkt) +{ + uint8_t ver = 0; + if (!pkt.get(ver)) { + return RNP_ERROR_BAD_FORMAT; + } + version = (pgp_version_t) ver; + + /* v3 or v4 signature body */ + rnp_result_t res; + if ((ver == PGP_V2) || (ver == PGP_V3)) { + res = parse_v3(pkt); + } else if (ver == PGP_V4) { + res = parse_v4(pkt); + } else { + RNP_LOG("unknown signature version: %d", (int) ver); + res = RNP_ERROR_BAD_FORMAT; + } + + if (res) { + return res; + } + + /* left 16 bits of the hash */ + if (!pkt.get(lbits, 2)) { + RNP_LOG("not enough data for hash left bits"); + return RNP_ERROR_BAD_FORMAT; + } + /* raw signature material */ + material_len = pkt.left(); + if (!material_len) { + RNP_LOG("No signature material"); + return RNP_ERROR_BAD_FORMAT; + } + material_buf = (uint8_t *) malloc(material_len); + if (!material_buf) { + RNP_LOG("Allocation failed"); + return RNP_ERROR_OUT_OF_MEMORY; + } + /* we cannot fail here */ + pkt.get(material_buf, material_len); + /* check whether it can be parsed */ + pgp_signature_material_t material = {}; + if (!parse_material(material)) { + return RNP_ERROR_BAD_FORMAT; + } + return RNP_SUCCESS; +} + +rnp_result_t +pgp_signature_t::parse(pgp_source_t &src) +{ + pgp_packet_body_t pkt(PGP_PKT_SIGNATURE); + rnp_result_t res = pkt.read(src); + if (res) { + return res; + } + return parse(pkt); +} + +bool +pgp_signature_t::parse_material(pgp_signature_material_t &material) const +{ + pgp_packet_body_t pkt(material_buf, material_len); + + switch (palg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_SIGN_ONLY: + if (!pkt.get(material.rsa.s)) { + return false; + } + break; + case PGP_PKA_DSA: + if (!pkt.get(material.dsa.r) || !pkt.get(material.dsa.s)) { + return false; + } + break; + case PGP_PKA_EDDSA: + if (version < PGP_V4) { + RNP_LOG("Warning! v3 EdDSA signature."); + } + [[fallthrough]]; + case PGP_PKA_ECDSA: + case PGP_PKA_SM2: + case PGP_PKA_ECDH: + if (!pkt.get(material.ecc.r) || !pkt.get(material.ecc.s)) { + return false; + } + break; + case PGP_PKA_ELGAMAL: /* we support reading it but will not validate */ + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + if (!pkt.get(material.eg.r) || !pkt.get(material.eg.s)) { + return false; + } + break; + default: + RNP_LOG("Unknown pk algorithm : %d", (int) palg); + return false; + } + + if (pkt.left()) { + RNP_LOG("extra %d bytes in signature packet", (int) pkt.left()); + return false; + } + return true; +} + +void +pgp_signature_t::write(pgp_dest_t &dst) const +{ + if ((version < PGP_V2) || (version > PGP_V4)) { + RNP_LOG("don't know version %d", (int) version); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + pgp_packet_body_t pktbody(PGP_PKT_SIGNATURE); + + if (version < PGP_V4) { + /* for v3 signatures hashed data includes only type + creation_time */ + pktbody.add_byte(version); + pktbody.add_byte(hashed_len); + pktbody.add(hashed_data, hashed_len); + pktbody.add(signer); + pktbody.add_byte(palg); + pktbody.add_byte(halg); + } else { + /* for v4 sig->hashed_data must contain most of signature fields */ + pktbody.add(hashed_data, hashed_len); + pktbody.add_subpackets(*this, false); + } + pktbody.add(lbits, 2); + /* write mpis */ + pktbody.add(material_buf, material_len); + pktbody.write(dst); +} + +void +pgp_signature_t::write_material(const pgp_signature_material_t &material) +{ + pgp_packet_body_t pktbody(PGP_PKT_SIGNATURE); + switch (palg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_SIGN_ONLY: + pktbody.add(material.rsa.s); + break; + case PGP_PKA_DSA: + pktbody.add(material.dsa.r); + pktbody.add(material.dsa.s); + break; + case PGP_PKA_EDDSA: + case PGP_PKA_ECDSA: + case PGP_PKA_SM2: + case PGP_PKA_ECDH: + pktbody.add(material.ecc.r); + pktbody.add(material.ecc.s); + break; + case PGP_PKA_ELGAMAL: /* we support writing it but will not generate */ + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + pktbody.add(material.eg.r); + pktbody.add(material.eg.s); + break; + default: + RNP_LOG("Unknown pk algorithm : %d", (int) palg); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + free(material_buf); + material_buf = (uint8_t *) malloc(pktbody.size()); + if (!material_buf) { + RNP_LOG("allocation failed"); + throw rnp::rnp_exception(RNP_ERROR_OUT_OF_MEMORY); + } + memcpy(material_buf, pktbody.data(), pktbody.size()); + material_len = pktbody.size(); +} + +void +pgp_signature_t::fill_hashed_data() +{ + /* we don't have a need to write v2-v3 signatures */ + if ((version < PGP_V2) || (version > PGP_V4)) { + RNP_LOG("don't know version %d", (int) version); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + pgp_packet_body_t hbody(PGP_PKT_RESERVED); + if (version < PGP_V4) { + hbody.add_byte(type()); + hbody.add_uint32(creation_time); + } else { + hbody.add_byte(version); + hbody.add_byte(type()); + hbody.add_byte(palg); + hbody.add_byte(halg); + hbody.add_subpackets(*this, true); + } + + free(hashed_data); + hashed_data = (uint8_t *) malloc(hbody.size()); + if (!hashed_data) { + RNP_LOG("allocation failed"); + throw std::bad_alloc(); + } + memcpy(hashed_data, hbody.data(), hbody.size()); + hashed_len = hbody.size(); +} + +void +rnp_selfsig_cert_info_t::populate(pgp_userid_pkt_t &uid, pgp_signature_t &sig) +{ + /* populate signature */ + sig.set_type(PGP_CERT_POSITIVE); + if (key_expiration) { + sig.set_key_expiration(key_expiration); + } + if (key_flags) { + sig.set_key_flags(key_flags); + } + if (primary) { + sig.set_primary_uid(true); + } + if (!prefs.symm_algs.empty()) { + sig.set_preferred_symm_algs(prefs.symm_algs); + } + if (!prefs.hash_algs.empty()) { + sig.set_preferred_hash_algs(prefs.hash_algs); + } + if (!prefs.z_algs.empty()) { + sig.set_preferred_z_algs(prefs.z_algs); + } + if (!prefs.ks_prefs.empty()) { + sig.set_key_server_prefs(prefs.ks_prefs[0]); + } + if (!prefs.key_server.empty()) { + sig.set_key_server(prefs.key_server); + } + /* populate uid */ + uid.tag = PGP_PKT_USER_ID; + uid.uid_len = userid.size(); + if (!(uid.uid = (uint8_t *) malloc(uid.uid_len))) { + RNP_LOG("alloc failed"); + throw rnp::rnp_exception(RNP_ERROR_OUT_OF_MEMORY); + } + memcpy(uid.uid, userid.data(), uid.uid_len); +} diff --git a/comm/third_party/rnp/src/librepgp/stream-sig.h b/comm/third_party/rnp/src/librepgp/stream-sig.h new file mode 100644 index 0000000000..4f36c381f1 --- /dev/null +++ b/comm/third_party/rnp/src/librepgp/stream-sig.h @@ -0,0 +1,437 @@ +/* + * Copyright (c) 2018-2022, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef STREAM_SIG_H_ +#define STREAM_SIG_H_ + +#include <stdint.h> +#include <stdbool.h> +#include <sys/types.h> +#include "rnp.h" +#include "stream-common.h" +#include "stream-packet.h" + +typedef struct pgp_signature_t { + private: + pgp_sig_type_t type_; + std::vector<uint8_t> preferred(pgp_sig_subpacket_type_t type) const; + void set_preferred(const std::vector<uint8_t> &data, pgp_sig_subpacket_type_t type); + rnp_result_t parse_v3(pgp_packet_body_t &pkt); + rnp_result_t parse_v4(pgp_packet_body_t &pkt); + bool parse_subpackets(uint8_t *buf, size_t len, bool hashed); + + public: + pgp_version_t version; + /* common v3 and v4 fields */ + pgp_pubkey_alg_t palg; + pgp_hash_alg_t halg; + uint8_t lbits[2]; + uint8_t * hashed_data; + size_t hashed_len; + uint8_t * material_buf; /* raw signature material */ + size_t material_len; /* raw signature material length */ + + /* v3 - only fields */ + uint32_t creation_time; + pgp_key_id_t signer; + + /* v4 - only fields */ + std::vector<pgp_sig_subpkt_t> subpkts; + + pgp_signature_t() + : type_(PGP_SIG_BINARY), version(PGP_VUNKNOWN), palg(PGP_PKA_NOTHING), + halg(PGP_HASH_UNKNOWN), hashed_data(NULL), hashed_len(0), material_buf(NULL), + material_len(0), creation_time(0){}; + pgp_signature_t(const pgp_signature_t &src); + pgp_signature_t(pgp_signature_t &&src); + pgp_signature_t &operator=(pgp_signature_t &&src); + pgp_signature_t &operator=(const pgp_signature_t &src); + bool operator==(const pgp_signature_t &src) const; + bool operator!=(const pgp_signature_t &src) const; + ~pgp_signature_t(); + + /* @brief Get signature's type */ + pgp_sig_type_t + type() const + { + return type_; + }; + void + set_type(pgp_sig_type_t atype) + { + type_ = atype; + }; + + bool + is_document() const + { + return (type_ == PGP_SIG_BINARY) || (type_ == PGP_SIG_TEXT); + }; + + /** @brief Calculate the unique signature identifier by hashing signature's fields. */ + pgp_sig_id_t get_id() const; + + /** + * @brief Get v4 signature's subpacket of the specified type and hashedness. + * @param stype subpacket type. + * @param hashed If true (default), then will search for subpacket only in hashed (i.e. + * covered by signature) area, otherwise will search in both hashed and non-hashed areas. + * @return pointer to the subpacket, or NULL if subpacket was not found. + */ + pgp_sig_subpkt_t * get_subpkt(pgp_sig_subpacket_type_t stype, bool hashed = true); + const pgp_sig_subpkt_t *get_subpkt(pgp_sig_subpacket_type_t stype, + bool hashed = true) const; + /* @brief Check whether v4 signature has subpacket of the specified type/hashedness */ + bool has_subpkt(pgp_sig_subpacket_type_t stype, bool hashed = true) const; + /* @brief Check whether signature has signing key id (via v3 field, or v4 key id/key fp + * subpacket) */ + bool has_keyid() const; + /** + * @brief Get signer's key id if available. Availability may be checked via has_keyid(). + * @return signer's key id if available, or empty (zero-filled) keyid otherwise. + */ + pgp_key_id_t keyid() const noexcept; + /** @brief Set the signer's key id for the signature being populated. Version should be set + * prior of setting key id. */ + void set_keyid(const pgp_key_id_t &id); + /** + * @brief Check whether signature has valid issuer fingerprint subpacket. + * @return true if there is one, and it can be safely returned via keyfp() method or false + * otherwise. + */ + bool has_keyfp() const; + /** + * @brief Get signing key's fingerprint if it is available. Availability may be checked via + * has_keyfp() method. + * @return fingerprint (or empty zero-size fp in case it is unavailable) + */ + pgp_fingerprint_t keyfp() const noexcept; + + /** @brief Set signing key's fingerprint. Works only for signatures with version 4 and up, + * so version should be set prior to fingerprint. */ + void set_keyfp(const pgp_fingerprint_t &fp); + + /** + * @brief Get signature's creation time + * @return time in seconds since the Jan 1, 1970 UTC. 0 is the default value and returned + * even if creation time is not available + */ + uint32_t creation() const; + + /** + * @brief Set signature's creation time + * @param ctime creation time in seconds since the Jan 1, 1970 UTC. + */ + void set_creation(uint32_t ctime); + + /** + * @brief Get the signature's expiration time + * @return expiration time in seconds since the creation time. 0 if signature never + * expires. + */ + uint32_t expiration() const; + + /** + * @brief Set the signature's expiration time + * @param etime expiration time + */ + void set_expiration(uint32_t etime); + + /** + * @brief Get the key expiration time + * @return expiration time in seconds since the creation time. 0 if key never expires. + */ + uint32_t key_expiration() const; + + /** + * @brief Set the key expiration time + * @param etime expiration time + */ + void set_key_expiration(uint32_t etime); + + /** + * @brief Get the key flags + * @return byte of key flags. If there is no corresponding subpackets then 0 is returned. + */ + uint8_t key_flags() const; + + /** + * @brief Set the key flags + * @param flags byte of key flags + */ + void set_key_flags(uint8_t flags); + + /** + * @brief Get the primary user id flag + * @return true if user id is marked as primary or false otherwise + */ + bool primary_uid() const; + + /** + * @brief Set the primary user id flag + * @param primary true if user id should be marked as primary + */ + void set_primary_uid(bool primary); + + /** @brief Get preferred symmetric algorithms if any. If there are no ones then empty + * vector is returned. */ + std::vector<uint8_t> preferred_symm_algs() const; + + /** @brief Set the preferred symmetric algorithms. If empty vector is passed then + * corresponding subpacket is deleted. */ + void set_preferred_symm_algs(const std::vector<uint8_t> &algs); + + /** @brief Get preferred hash algorithms if any. If there are no ones then empty vector is + * returned.*/ + std::vector<uint8_t> preferred_hash_algs() const; + + /** @brief Set the preferred hash algorithms. If empty vector is passed then corresponding + * subpacket is deleted. */ + void set_preferred_hash_algs(const std::vector<uint8_t> &algs); + + /** @brief Get preferred compression algorithms if any. If there are no ones then empty + * vector is returned.*/ + std::vector<uint8_t> preferred_z_algs() const; + + /** @brief Set the preferred compression algorithms. If empty vector is passed then + * corresponding subpacket is deleted. */ + void set_preferred_z_algs(const std::vector<uint8_t> &algs); + + /** @brief Get key server preferences flags. If subpacket is not available then 0 is + * returned. */ + uint8_t key_server_prefs() const; + + /** @brief Set key server preferences flags. */ + void set_key_server_prefs(uint8_t prefs); + + /** @brief Get preferred key server URI, if available. Otherwise empty string is returned. + */ + std::string key_server() const; + + /** @brief Set preferred key server URI. If it is empty string then subpacket is deleted if + * it is available. */ + void set_key_server(const std::string &uri); + + /** @brief Get trust level, if available. Otherwise will return 0. See RFC 4880, 5.2.3.14. + * for the detailed information on trust level and amount. + */ + uint8_t trust_level() const; + + /** @brief Get trust amount, if available. Otherwise will return 0. See RFC 4880, 5.2.3.14. + * for the detailed information on trust level and amount. + */ + uint8_t trust_amount() const; + + /** @brief Set the trust level and amount. See RFC 4880, 5.2.3.14. + * for the detailed information on trust level and amount. + */ + void set_trust(uint8_t level, uint8_t amount); + + /** @brief check whether signature is revocable. True by default. + */ + bool revocable() const; + + /** @brief Set the signature's revocability status. + */ + void set_revocable(bool status); + + /** @brief Get the key/subkey revocation reason in humand-readable form. If there is no + * revocation reason subpacket, then empty string will be returned. + */ + std::string revocation_reason() const; + + /** @brief Get the key/subkey revocation code. If there is no revocation reason subpacket, + * then PGP_REVOCATION_NO_REASON will be rerturned. See the RFC 4880, 5.2.3.24 for + * the detailed explanation. + */ + pgp_revocation_type_t revocation_code() const; + + /** @brief Set the revocation reason and code for key/subkey revocation signature. See the + * RFC 4880, 5.2.3.24 for the detailed explanation. + */ + void set_revocation_reason(pgp_revocation_type_t code, const std::string &reason); + + /** + * @brief Check whether signer's key supports certain feature(s). Makes sense only for + * self-signature, for more details see the RFC 4880bis, 5.2.3.25. If there is + * no corresponding subpacket then false will be returned. + * @param flags one or more flags, combined via bitwise OR operation. + * @return true if key is claimed to support all of the features listed in flags, or false + * otherwise + */ + bool key_has_features(pgp_key_feature_t flags) const; + + /** + * @brief Set the features supported by the signer's key, makes sense only for + * self-signature. For more details see the RFC 4880bis, 5.2.3.25. + * @param flags one or more flags, combined via bitwise OR operation. + */ + void set_key_features(pgp_key_feature_t flags); + + /** @brief Get signer's user id, if available. Otherwise empty string is returned. See the + * RFC 4880bis, 5.2.3.23 for details. + */ + std::string signer_uid() const; + + /** + * @brief Set the signer's uid, responcible for the signature creation. See the RFC + * 4880bis, 5.2.3.23 for details. + */ + void set_signer_uid(const std::string &uid); + + /** + * @brief Add notation. + */ + void add_notation(const std::string & name, + const std::vector<uint8_t> &value, + bool human = true, + bool critical = false); + + /** + * @brief Add human-readable notation. + */ + void add_notation(const std::string &name, + const std::string &value, + bool critical = false); + + /** + * @brief Set the embedded signature. + * @param esig populated and calculated embedded signature. + */ + void set_embedded_sig(const pgp_signature_t &esig); + + /** + * @brief Add subpacket of the specified type to v4 signature + * @param type type of the subpacket + * @param datalen length of the subpacket body + * @param reuse replace already existing subpacket of the specified type if any + * @return reference to the subpacket structure or throws an exception + */ + pgp_sig_subpkt_t &add_subpkt(pgp_sig_subpacket_type_t type, size_t datalen, bool reuse); + + /** + * @brief Remove signature's subpacket + * @param subpkt subpacket to remove. If not in the subpackets list then no action is + * taken. + */ + void remove_subpkt(pgp_sig_subpkt_t *subpkt); + + /** + * @brief Check whether signature packet matches one-pass signature packet. + * @param onepass reference to the read one-pass signature packet + * @return true if sig corresponds to onepass or false otherwise + */ + bool matches_onepass(const pgp_one_pass_sig_t &onepass) const; + + /** + * @brief Parse signature body (i.e. without checking the packet header). + * + * @param pkt packet body with data. + * @return RNP_SUCCESS or error code if failed. May also throw an exception. + */ + rnp_result_t parse(pgp_packet_body_t &pkt); + + /** + * @brief Parse signature packet from source. + * + * @param src source with data. + * @return RNP_SUCCESS or error code if failed. May also throw an exception. + */ + rnp_result_t parse(pgp_source_t &src); + + /** + * @brief Parse signature material, stored in the signature in raw. + * + * @param material on success parsed material will be stored here. + * @return true on success or false otherwise. May also throw an exception. + */ + bool parse_material(pgp_signature_material_t &material) const; + + /** + * @brief Write signature to the destination. May throw an exception. + */ + void write(pgp_dest_t &dst) const; + + /** + * @brief Write the signature material's raw representation. May throw an exception. + * + * @param material populated signature material. + */ + void write_material(const pgp_signature_material_t &material); + + /** + * @brief Fill signature's hashed data. This includes all the fields from signature which + * are hashed after the previous document or key fields. + */ + void fill_hashed_data(); +} pgp_signature_t; + +typedef std::vector<pgp_signature_t> pgp_signature_list_t; + +/* information about the validated signature */ +typedef struct pgp_signature_info_t { + pgp_signature_t *sig{}; /* signature, or NULL if there were parsing error */ + bool valid{}; /* signature is cryptographically valid (but may be expired) */ + bool unknown{}; /* signature is unknown - parsing error, wrong version, etc */ + bool no_signer{}; /* no signer's public key available */ + bool expired{}; /* signature is expired */ + bool signer_valid{}; /* assume that signing key is valid */ + bool ignore_expiry{}; /* ignore signer's key expiration time */ +} pgp_signature_info_t; + +/** + * @brief Hash key packet. Used in signatures and v4 fingerprint calculation. + * Throws exception on error. + * @param key key packet, must be populated + * @param hash initialized hash context + */ +void signature_hash_key(const pgp_key_pkt_t &key, rnp::Hash &hash); + +void signature_hash_userid(const pgp_userid_pkt_t &uid, rnp::Hash &hash, pgp_version_t sigver); + +std::unique_ptr<rnp::Hash> signature_hash_certification(const pgp_signature_t & sig, + const pgp_key_pkt_t & key, + const pgp_userid_pkt_t &userid); + +std::unique_ptr<rnp::Hash> signature_hash_binding(const pgp_signature_t &sig, + const pgp_key_pkt_t & key, + const pgp_key_pkt_t & subkey); + +std::unique_ptr<rnp::Hash> signature_hash_direct(const pgp_signature_t &sig, + const pgp_key_pkt_t & key); + +/** + * @brief Parse stream with signatures to the signatures list. + * Can handle binary or armored stream with signatures, including stream with multiple + * armored signatures. + * + * @param src signatures stream, cannot be NULL. + * @param sigs on success parsed signature structures will be put here. + * @return RNP_SUCCESS or error code otherwise. + */ +rnp_result_t process_pgp_signatures(pgp_source_t &src, pgp_signature_list_t &sigs); + +#endif diff --git a/comm/third_party/rnp/src/librepgp/stream-write.cpp b/comm/third_party/rnp/src/librepgp/stream-write.cpp new file mode 100644 index 0000000000..60d867ae8d --- /dev/null +++ b/comm/third_party/rnp/src/librepgp/stream-write.cpp @@ -0,0 +1,1973 @@ +/* + * Copyright (c) 2017-2023, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include <sys/stat.h> +#include <stdlib.h> +#include <stdio.h> +#ifdef HAVE_UNISTD_H +#include <sys/param.h> +#include <unistd.h> +#else +#include "uniwin.h" +#endif +#include <string.h> +#ifdef HAVE_ZLIB_H +#include <zlib.h> +#endif +#ifdef HAVE_BZLIB_H +#include <bzlib.h> +#endif +#include <rnp/rnp_def.h> +#include "stream-def.h" +#include "stream-ctx.h" +#include "stream-write.h" +#include "stream-packet.h" +#include "stream-armor.h" +#include "stream-sig.h" +#include "pgp-key.h" +#include "fingerprint.h" +#include "types.h" +#include "crypto/signatures.h" +#include "defaults.h" +#include <time.h> +#include <algorithm> + +/* 8192 bytes, as GnuPG */ +#define PGP_PARTIAL_PKT_SIZE_BITS (13) +#define PGP_PARTIAL_PKT_BLOCK_SIZE (1 << PGP_PARTIAL_PKT_SIZE_BITS) + +/* common fields for encrypted, compressed and literal data */ +typedef struct pgp_dest_packet_param_t { + pgp_dest_t *writedst; /* destination to write to, could be partial */ + pgp_dest_t *origdst; /* original dest passed to init_*_dst */ + bool partial; /* partial length packet */ + bool indeterminate; /* indeterminate length packet */ + int tag; /* packet tag */ + uint8_t hdr[PGP_MAX_HEADER_SIZE]; /* header, including length, as it was written */ + size_t hdrlen; /* number of bytes in hdr */ +} pgp_dest_packet_param_t; + +typedef struct pgp_dest_compressed_param_t { + pgp_dest_packet_param_t pkt; + pgp_compression_type_t alg; + union { + z_stream z; + bz_stream bz; + }; + bool zstarted; /* whether we initialize zlib/bzip2 */ + uint8_t cache[PGP_INPUT_CACHE_SIZE / 2]; /* pre-allocated cache for compression */ + size_t len; /* number of bytes cached */ +} pgp_dest_compressed_param_t; + +typedef struct pgp_dest_encrypted_param_t { + pgp_dest_packet_param_t pkt; /* underlying packet-related params */ + rnp_ctx_t * ctx; /* rnp operation context with additional parameters */ + rnp::AuthType auth_type; /* Authentication type: MDC, AEAD or none */ + pgp_crypt_t encrypt; /* encrypting crypto */ + std::unique_ptr<rnp::Hash> mdc; /* mdc SHA1 hash */ + pgp_aead_alg_t aalg; /* AEAD algorithm used */ + uint8_t iv[PGP_AEAD_MAX_NONCE_LEN]; /* iv for AEAD mode */ + uint8_t ad[PGP_AEAD_MAX_AD_LEN]; /* additional data for AEAD mode */ + size_t adlen; /* length of additional data, including chunk idx */ + size_t chunklen; /* length of the AEAD chunk in bytes */ + size_t chunkout; /* how many bytes from the chunk were written out */ + size_t chunkidx; /* index of the current AEAD chunk */ + size_t cachelen; /* how many bytes are in cache, for AEAD */ + uint8_t cache[PGP_AEAD_CACHE_LEN]; /* pre-allocated cache for encryption */ +} pgp_dest_encrypted_param_t; + +typedef struct pgp_dest_signer_info_t { + pgp_one_pass_sig_t onepass; + pgp_key_t * key; + pgp_hash_alg_t halg; + int64_t sigcreate; + uint64_t sigexpire; +} pgp_dest_signer_info_t; + +typedef struct pgp_dest_signed_param_t { + pgp_dest_t * writedst; /* destination to write to */ + rnp_ctx_t * ctx; /* rnp operation context with additional parameters */ + pgp_password_provider_t *password_provider; /* password provider from write handler */ + std::vector<pgp_dest_signer_info_t> siginfos; /* list of pgp_dest_signer_info_t */ + rnp::HashList hashes; /* hashes to pass raw data through and then sign */ + bool clr_start; /* we are on the start of the line */ + uint8_t clr_buf[CT_BUF_LEN]; /* buffer to hold partial line data */ + size_t clr_buflen; /* number of bytes in buffer */ + + pgp_dest_signed_param_t() = default; + ~pgp_dest_signed_param_t() = default; +} pgp_dest_signed_param_t; + +typedef struct pgp_dest_partial_param_t { + pgp_dest_t *writedst; + uint8_t part[PGP_PARTIAL_PKT_BLOCK_SIZE]; + uint8_t parthdr; /* header byte for the current part */ + size_t partlen; /* length of the current part, up to PARTIAL_PKT_BLOCK_SIZE */ + size_t len; /* bytes cached in part */ +} pgp_dest_partial_param_t; + +static rnp_result_t +partial_dst_write(pgp_dest_t *dst, const void *buf, size_t len) +{ + pgp_dest_partial_param_t *param = (pgp_dest_partial_param_t *) dst->param; + if (!param) { + RNP_LOG("wrong param"); + return RNP_ERROR_BAD_PARAMETERS; + } + + if (len > param->partlen - param->len) { + /* we have full part - in block and in buf */ + size_t wrlen = param->partlen - param->len; + dst_write(param->writedst, ¶m->parthdr, 1); + dst_write(param->writedst, param->part, param->len); + dst_write(param->writedst, buf, wrlen); + + buf = (uint8_t *) buf + wrlen; + len -= wrlen; + param->len = 0; + + /* writing all full parts directly from buf */ + while (len >= param->partlen) { + dst_write(param->writedst, ¶m->parthdr, 1); + dst_write(param->writedst, buf, param->partlen); + buf = (uint8_t *) buf + param->partlen; + len -= param->partlen; + } + } + + /* caching rest of the buf */ + if (len > 0) { + memcpy(¶m->part[param->len], buf, len); + param->len += len; + } + + return RNP_SUCCESS; +} + +static rnp_result_t +partial_dst_finish(pgp_dest_t *dst) +{ + pgp_dest_partial_param_t *param = (pgp_dest_partial_param_t *) dst->param; + uint8_t hdr[5]; + int lenlen; + + lenlen = write_packet_len(hdr, param->len); + dst_write(param->writedst, hdr, lenlen); + dst_write(param->writedst, param->part, param->len); + + return param->writedst->werr; +} + +static void +partial_dst_close(pgp_dest_t *dst, bool discard) +{ + pgp_dest_partial_param_t *param = (pgp_dest_partial_param_t *) dst->param; + + if (!param) { + return; + } + + free(param); + dst->param = NULL; +} + +static rnp_result_t +init_partial_pkt_dst(pgp_dest_t *dst, pgp_dest_t *writedst) +{ + pgp_dest_partial_param_t *param; + + if (!init_dst_common(dst, sizeof(*param))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + param = (pgp_dest_partial_param_t *) dst->param; + param->writedst = writedst; + param->partlen = PGP_PARTIAL_PKT_BLOCK_SIZE; + param->parthdr = 0xE0 | PGP_PARTIAL_PKT_SIZE_BITS; + dst->param = param; + dst->write = partial_dst_write; + dst->finish = partial_dst_finish; + dst->close = partial_dst_close; + dst->type = PGP_STREAM_PARLEN_PACKET; + + return RNP_SUCCESS; +} + +/** @brief helper function for streamed packets (literal, encrypted and compressed). + * Allocates part len destination if needed and writes header + **/ +static bool +init_streamed_packet(pgp_dest_packet_param_t *param, pgp_dest_t *dst) +{ + rnp_result_t ret; + + if (param->partial) { + param->hdr[0] = param->tag | PGP_PTAG_ALWAYS_SET | PGP_PTAG_NEW_FORMAT; + dst_write(dst, ¶m->hdr, 1); + + if ((param->writedst = (pgp_dest_t *) calloc(1, sizeof(*param->writedst))) == NULL) { + RNP_LOG("part len dest allocation failed"); + return false; + } + ret = init_partial_pkt_dst(param->writedst, dst); + if (ret != RNP_SUCCESS) { + free(param->writedst); + param->writedst = NULL; + return false; + } + param->origdst = dst; + + param->hdr[1] = ((pgp_dest_partial_param_t *) param->writedst->param)->parthdr; + param->hdrlen = 2; + return true; + } + + if (param->indeterminate) { + if (param->tag > 0xf) { + RNP_LOG("indeterminate tag > 0xf"); + } + + param->hdr[0] = ((param->tag & 0xf) << PGP_PTAG_OF_CONTENT_TAG_SHIFT) | + PGP_PTAG_OLD_LEN_INDETERMINATE; + param->hdrlen = 1; + dst_write(dst, ¶m->hdr, 1); + + param->writedst = dst; + param->origdst = dst; + return true; + } + + RNP_LOG("wrong call"); + return false; +} + +static rnp_result_t +finish_streamed_packet(pgp_dest_packet_param_t *param) +{ + if (param->partial) { + return dst_finish(param->writedst); + } + return RNP_SUCCESS; +} + +static void +close_streamed_packet(pgp_dest_packet_param_t *param, bool discard) +{ + if (param->partial) { + dst_close(param->writedst, discard); + free(param->writedst); + param->writedst = NULL; + } +} + +static rnp_result_t +encrypted_dst_write_cfb(pgp_dest_t *dst, const void *buf, size_t len) +{ + pgp_dest_encrypted_param_t *param = (pgp_dest_encrypted_param_t *) dst->param; + size_t sz; + + if (!param) { + RNP_LOG("wrong param"); + return RNP_ERROR_BAD_PARAMETERS; + } + + if (param->auth_type == rnp::AuthType::MDC) { + try { + param->mdc->add(buf, len); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_BAD_STATE; + } + } + + while (len > 0) { + sz = len > sizeof(param->cache) ? sizeof(param->cache) : len; + pgp_cipher_cfb_encrypt(¶m->encrypt, param->cache, (const uint8_t *) buf, sz); + dst_write(param->pkt.writedst, param->cache, sz); + len -= sz; + buf = (uint8_t *) buf + sz; + } + + return RNP_SUCCESS; +} + +#if defined(ENABLE_AEAD) +static rnp_result_t +encrypted_start_aead_chunk(pgp_dest_encrypted_param_t *param, size_t idx, bool last) +{ + uint8_t nonce[PGP_AEAD_MAX_NONCE_LEN]; + size_t nlen; + size_t taglen; + bool res; + uint64_t total; + + taglen = pgp_cipher_aead_tag_len(param->aalg); + + /* finish the previous chunk if needed*/ + if ((idx > 0) && (param->chunkout + param->cachelen > 0)) { + if (param->cachelen + taglen > sizeof(param->cache)) { + RNP_LOG("wrong state in aead"); + return RNP_ERROR_BAD_STATE; + } + + if (!pgp_cipher_aead_finish( + ¶m->encrypt, param->cache, param->cache, param->cachelen)) { + return RNP_ERROR_BAD_STATE; + } + + dst_write(param->pkt.writedst, param->cache, param->cachelen + taglen); + } + + /* set chunk index for additional data */ + STORE64BE(param->ad + param->adlen - 8, idx); + + if (last) { + if (!(param->chunkout + param->cachelen)) { + /* we need to clearly reset it since cipher was initialized but not finished */ + pgp_cipher_aead_reset(¶m->encrypt); + } + + total = idx * param->chunklen; + if (param->cachelen + param->chunkout) { + if (param->chunklen < (param->cachelen + param->chunkout)) { + RNP_LOG("wrong last chunk state in aead"); + return RNP_ERROR_BAD_STATE; + } + total -= param->chunklen - param->cachelen - param->chunkout; + } + + STORE64BE(param->ad + param->adlen, total); + param->adlen += 8; + } + if (!pgp_cipher_aead_set_ad(¶m->encrypt, param->ad, param->adlen)) { + RNP_LOG("failed to set ad"); + return RNP_ERROR_BAD_STATE; + } + + /* set chunk index for nonce */ + nlen = pgp_cipher_aead_nonce(param->aalg, param->iv, nonce, idx); + + /* start cipher */ + res = pgp_cipher_aead_start(¶m->encrypt, nonce, nlen); + + /* write final authentication tag */ + if (last) { + res = res && pgp_cipher_aead_finish(¶m->encrypt, param->cache, param->cache, 0); + if (res) { + dst_write(param->pkt.writedst, param->cache, taglen); + } + } + + param->chunkidx = idx; + param->chunkout = 0; + + return res ? RNP_SUCCESS : RNP_ERROR_BAD_PARAMETERS; +} +#endif + +static rnp_result_t +encrypted_dst_write_aead(pgp_dest_t *dst, const void *buf, size_t len) +{ +#if !defined(ENABLE_AEAD) + RNP_LOG("AEAD is not enabled."); + return RNP_ERROR_WRITE; +#else + pgp_dest_encrypted_param_t *param = (pgp_dest_encrypted_param_t *) dst->param; + + size_t sz; + size_t gran; + rnp_result_t res; + + if (!param) { + RNP_LOG("wrong param"); + return RNP_ERROR_BAD_PARAMETERS; + } + + if (!len) { + return RNP_SUCCESS; + } + + /* because of botan's FFI granularity we need to make things a bit complicated */ + gran = pgp_cipher_aead_granularity(¶m->encrypt); + + if (param->cachelen > param->chunklen - param->chunkout) { + RNP_LOG("wrong AEAD cache state"); + return RNP_ERROR_BAD_STATE; + } + + while (len > 0) { + sz = std::min(sizeof(param->cache) - 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; + + if (param->cachelen == param->chunklen - param->chunkout) { + /* we have the tail of the chunk in cache */ + if ((res = encrypted_start_aead_chunk(param, param->chunkidx + 1, false))) { + return res; + } + param->cachelen = 0; + } 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)) { + return RNP_ERROR_BAD_STATE; + } + dst_write(param->pkt.writedst, param->cache, gransz); + memmove(param->cache, param->cache + gransz, param->cachelen - gransz); + param->cachelen -= gransz; + param->chunkout += gransz; + } + + len -= sz; + buf = (uint8_t *) buf + sz; + } + + return RNP_SUCCESS; +#endif +} + +static rnp_result_t +encrypted_dst_finish(pgp_dest_t *dst) +{ + pgp_dest_encrypted_param_t *param = (pgp_dest_encrypted_param_t *) dst->param; + + if (param->auth_type == rnp::AuthType::AEADv1) { +#if !defined(ENABLE_AEAD) + RNP_LOG("AEAD is not enabled."); + rnp_result_t res = RNP_ERROR_NOT_IMPLEMENTED; +#else + size_t chunks = param->chunkidx; + /* if we didn't write anything in current chunk then discard it and restart */ + if (param->chunkout || param->cachelen) { + chunks++; + } + + rnp_result_t res = encrypted_start_aead_chunk(param, chunks, true); + pgp_cipher_aead_destroy(¶m->encrypt); +#endif + if (res) { + finish_streamed_packet(¶m->pkt); + return res; + } + } else if (param->auth_type == rnp::AuthType::MDC) { + uint8_t mdcbuf[MDC_V1_SIZE]; + mdcbuf[0] = MDC_PKT_TAG; + mdcbuf[1] = MDC_V1_SIZE - 2; + try { + param->mdc->add(mdcbuf, 2); + param->mdc->finish(&mdcbuf[2]); + param->mdc = nullptr; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_BAD_STATE; + } + pgp_cipher_cfb_encrypt(¶m->encrypt, mdcbuf, mdcbuf, MDC_V1_SIZE); + dst_write(param->pkt.writedst, mdcbuf, MDC_V1_SIZE); + } + + return finish_streamed_packet(¶m->pkt); +} + +static void +encrypted_dst_close(pgp_dest_t *dst, bool discard) +{ + pgp_dest_encrypted_param_t *param = (pgp_dest_encrypted_param_t *) dst->param; + + if (!param) { + return; + } + + if (param->auth_type == rnp::AuthType::AEADv1) { +#if defined(ENABLE_AEAD) + pgp_cipher_aead_destroy(¶m->encrypt); +#endif + } else { + pgp_cipher_cfb_finish(¶m->encrypt); + } + close_streamed_packet(¶m->pkt, discard); + delete param; + dst->param = NULL; +} + +static rnp_result_t +encrypted_add_recipient(pgp_write_handler_t *handler, + pgp_dest_t * dst, + pgp_key_t * userkey, + const uint8_t * key, + const unsigned keylen) +{ + pgp_pk_sesskey_t pkey; + pgp_dest_encrypted_param_t *param = (pgp_dest_encrypted_param_t *) dst->param; + rnp_result_t ret = RNP_ERROR_GENERIC; + + /* Use primary key if good for encryption, otherwise look in subkey list */ + userkey = find_suitable_key(PGP_OP_ENCRYPT, userkey, handler->key_provider); + if (!userkey) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + + /* Fill pkey */ + pkey.version = PGP_PKSK_V3; + pkey.alg = userkey->alg(); + pkey.key_id = userkey->keyid(); + + /* Encrypt the session key */ + rnp::secure_array<uint8_t, PGP_MAX_KEY_SIZE + 3> enckey; + enckey[0] = param->ctx->ealg; + memcpy(&enckey[1], key, keylen); + + /* Calculate checksum */ + rnp::secure_array<unsigned, 1> checksum; + + for (unsigned i = 1; i <= keylen; i++) { + checksum[0] += enckey[i]; + } + enckey[keylen + 1] = (checksum[0] >> 8) & 0xff; + enckey[keylen + 2] = checksum[0] & 0xff; + + pgp_encrypted_material_t material; + + switch (userkey->alg()) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: { + ret = rsa_encrypt_pkcs1(&handler->ctx->ctx->rng, + &material.rsa, + enckey.data(), + keylen + 3, + &userkey->material().rsa); + if (ret) { + RNP_LOG("rsa_encrypt_pkcs1 failed"); + return ret; + } + break; + } + case PGP_PKA_SM2: { +#if defined(ENABLE_SM2) + ret = sm2_encrypt(&handler->ctx->ctx->rng, + &material.sm2, + enckey.data(), + keylen + 3, + PGP_HASH_SM3, + &userkey->material().ec); + if (ret) { + RNP_LOG("sm2_encrypt failed"); + return ret; + } + break; +#else + RNP_LOG("sm2_encrypt is not available"); + return RNP_ERROR_NOT_IMPLEMENTED; +#endif + } + case PGP_PKA_ECDH: { + if (!curve_supported(userkey->material().ec.curve)) { + RNP_LOG("ECDH encrypt: curve %d is not supported.", + (int) userkey->material().ec.curve); + return RNP_ERROR_NOT_SUPPORTED; + } + ret = ecdh_encrypt_pkcs5(&handler->ctx->ctx->rng, + &material.ecdh, + enckey.data(), + keylen + 3, + &userkey->material().ec, + userkey->fp()); + if (ret) { + RNP_LOG("ECDH encryption failed %d", ret); + return ret; + } + break; + } + case PGP_PKA_ELGAMAL: { + ret = elgamal_encrypt_pkcs1(&handler->ctx->ctx->rng, + &material.eg, + enckey.data(), + keylen + 3, + &userkey->material().eg); + if (ret) { + RNP_LOG("pgp_elgamal_public_encrypt failed"); + return ret; + } + break; + } + default: + RNP_LOG("unsupported alg: %d", (int) userkey->alg()); + return ret; + } + + /* Writing symmetric key encrypted session key packet */ + try { + pkey.write_material(material); + pkey.write(*param->pkt.origdst); + return param->pkt.origdst->werr; + } catch (const std::exception &e) { + return RNP_ERROR_WRITE; + } +} + +#if defined(ENABLE_AEAD) +static bool +encrypted_sesk_set_ad(pgp_crypt_t *crypt, pgp_sk_sesskey_t *skey) +{ + uint8_t ad_data[4]; + + ad_data[0] = PGP_PKT_SK_SESSION_KEY | PGP_PTAG_ALWAYS_SET | PGP_PTAG_NEW_FORMAT; + ad_data[1] = skey->version; + ad_data[2] = skey->alg; + ad_data[3] = skey->aalg; + + return pgp_cipher_aead_set_ad(crypt, ad_data, 4); +} +#endif + +static rnp_result_t +encrypted_add_password(rnp_symmetric_pass_info_t * pass, + pgp_dest_encrypted_param_t *param, + uint8_t * key, + const unsigned keylen, + bool singlepass) +{ + pgp_sk_sesskey_t skey = {}; + pgp_crypt_t kcrypt; + + skey.s2k = pass->s2k; + + if (param->auth_type != rnp::AuthType::AEADv1) { + skey.version = PGP_SKSK_V4; + if (singlepass) { + /* if there are no public keys then we do not encrypt session key in the packet */ + skey.alg = param->ctx->ealg; + skey.enckeylen = 0; + memcpy(key, pass->key.data(), keylen); + } else { + /* We may use different algo for CEK and KEK */ + skey.enckeylen = keylen + 1; + skey.enckey[0] = param->ctx->ealg; + memcpy(&skey.enckey[1], key, keylen); + skey.alg = pass->s2k_cipher; + if (!pgp_cipher_cfb_start(&kcrypt, skey.alg, pass->key.data(), NULL)) { + RNP_LOG("key encryption failed"); + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_cipher_cfb_encrypt(&kcrypt, skey.enckey, skey.enckey, skey.enckeylen); + pgp_cipher_cfb_finish(&kcrypt); + } + } else { +#if !defined(ENABLE_AEAD) + RNP_LOG("AEAD support is not enabled."); + return RNP_ERROR_NOT_IMPLEMENTED; +#else + /* AEAD-encrypted v5 packet */ + if ((param->ctx->aalg != PGP_AEAD_EAX) && (param->ctx->aalg != PGP_AEAD_OCB)) { + RNP_LOG("unsupported AEAD algorithm"); + return RNP_ERROR_BAD_PARAMETERS; + } + + skey.version = PGP_SKSK_V5; + skey.alg = pass->s2k_cipher; + skey.aalg = param->ctx->aalg; + skey.ivlen = pgp_cipher_aead_nonce_len(skey.aalg); + skey.enckeylen = keylen + pgp_cipher_aead_tag_len(skey.aalg); + + try { + param->ctx->ctx->rng.get(skey.iv, skey.ivlen); + } catch (const std::exception &e) { + return RNP_ERROR_RNG; + } + + /* initialize cipher */ + if (!pgp_cipher_aead_init(&kcrypt, skey.alg, skey.aalg, pass->key.data(), false)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + /* set additional data */ + if (!encrypted_sesk_set_ad(&kcrypt, &skey)) { + return RNP_ERROR_BAD_STATE; + } + + /* calculate nonce */ + uint8_t nonce[PGP_AEAD_MAX_NONCE_LEN]; + size_t nlen = pgp_cipher_aead_nonce(skey.aalg, skey.iv, nonce, 0); + + /* start cipher, encrypt key and get tag */ + bool res = pgp_cipher_aead_start(&kcrypt, nonce, nlen) && + pgp_cipher_aead_finish(&kcrypt, skey.enckey, key, keylen); + + pgp_cipher_aead_destroy(&kcrypt); + + if (!res) { + return RNP_ERROR_BAD_STATE; + } +#endif + } + + /* Writing symmetric key encrypted session key packet */ + try { + skey.write(*param->pkt.origdst); + } catch (const std::exception &e) { + return RNP_ERROR_WRITE; + } + return param->pkt.origdst->werr; +} + +static rnp_result_t +encrypted_start_cfb(pgp_dest_encrypted_param_t *param, uint8_t *enckey) +{ + uint8_t mdcver = 1; + uint8_t enchdr[PGP_MAX_BLOCK_SIZE + 2]; /* encrypted header */ + unsigned blsize; + + if (param->auth_type == rnp::AuthType::MDC) { + /* initializing the mdc */ + dst_write(param->pkt.writedst, &mdcver, 1); + + try { + param->mdc = rnp::Hash::create(PGP_HASH_SHA1); + } catch (const std::exception &e) { + RNP_LOG("cannot create sha1 hash: %s", e.what()); + return RNP_ERROR_GENERIC; + } + } + + /* initializing the crypto */ + if (!pgp_cipher_cfb_start(¶m->encrypt, param->ctx->ealg, enckey, NULL)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + /* generating and writing iv/password check bytes */ + blsize = pgp_block_size(param->ctx->ealg); + try { + param->ctx->ctx->rng.get(enchdr, blsize); + enchdr[blsize] = enchdr[blsize - 2]; + enchdr[blsize + 1] = enchdr[blsize - 1]; + + if (param->auth_type == rnp::AuthType::MDC) { + param->mdc->add(enchdr, blsize + 2); + } + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_BAD_STATE; + } + + pgp_cipher_cfb_encrypt(¶m->encrypt, enchdr, enchdr, blsize + 2); + + /* RFC 4880, 5.13: Unlike the Symmetrically Encrypted Data Packet, no special CFB + * resynchronization is done after encrypting this prefix data. */ + if (param->auth_type == rnp::AuthType::None) { + pgp_cipher_cfb_resync(¶m->encrypt, enchdr + 2); + } + + dst_write(param->pkt.writedst, enchdr, blsize + 2); + + return RNP_SUCCESS; +} + +static rnp_result_t +encrypted_start_aead(pgp_dest_encrypted_param_t *param, uint8_t *enckey) +{ +#if !defined(ENABLE_AEAD) + RNP_LOG("AEAD support is not enabled."); + return RNP_ERROR_NOT_IMPLEMENTED; +#else + uint8_t hdr[4 + PGP_AEAD_MAX_NONCE_LEN]; + size_t nlen; + + if (pgp_block_size(param->ctx->ealg) != 16) { + return RNP_ERROR_BAD_PARAMETERS; + } + + /* fill header */ + hdr[0] = 1; + hdr[1] = param->ctx->ealg; + hdr[2] = param->ctx->aalg; + hdr[3] = param->ctx->abits; + + /* generate iv */ + nlen = pgp_cipher_aead_nonce_len(param->ctx->aalg); + try { + param->ctx->ctx->rng.get(param->iv, nlen); + } catch (const std::exception &e) { + return RNP_ERROR_RNG; + } + memcpy(hdr + 4, param->iv, nlen); + + /* output header */ + dst_write(param->pkt.writedst, hdr, 4 + nlen); + + /* initialize encryption */ + param->chunklen = 1L << (hdr[3] + 6); + param->chunkout = 0; + + /* fill additional/authenticated data */ + param->ad[0] = PGP_PKT_AEAD_ENCRYPTED | PGP_PTAG_ALWAYS_SET | PGP_PTAG_NEW_FORMAT; + memcpy(param->ad + 1, hdr, 4); + memset(param->ad + 5, 0, 8); + param->adlen = 13; + + /* initialize cipher */ + if (!pgp_cipher_aead_init( + ¶m->encrypt, param->ctx->ealg, param->ctx->aalg, enckey, false)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + return encrypted_start_aead_chunk(param, 0, false); +#endif +} + +static rnp_result_t +init_encrypted_dst(pgp_write_handler_t *handler, pgp_dest_t *dst, pgp_dest_t *writedst) +{ + pgp_dest_encrypted_param_t *param; + bool singlepass = true; + unsigned pkeycount = 0; + unsigned skeycount = 0; + unsigned keylen; + rnp_result_t ret = RNP_ERROR_GENERIC; + + keylen = pgp_key_size(handler->ctx->ealg); + if (!keylen) { + RNP_LOG("unknown symmetric algorithm"); + return RNP_ERROR_BAD_PARAMETERS; + } + + if (handler->ctx->aalg) { + if ((handler->ctx->aalg != PGP_AEAD_EAX) && (handler->ctx->aalg != PGP_AEAD_OCB)) { + RNP_LOG("unknown AEAD algorithm: %d", (int) handler->ctx->aalg); + return RNP_ERROR_BAD_PARAMETERS; + } + + if ((pgp_block_size(handler->ctx->ealg) != 16)) { + RNP_LOG("wrong AEAD symmetric algorithm"); + return RNP_ERROR_BAD_PARAMETERS; + } + + if ((handler->ctx->abits < 0) || (handler->ctx->abits > 16)) { + RNP_LOG("wrong AEAD chunk bits: %d", handler->ctx->abits); + return RNP_ERROR_BAD_PARAMETERS; + } + } + + if (!init_dst_common(dst, 0)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + try { + param = new pgp_dest_encrypted_param_t(); + dst->param = param; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + param->auth_type = + handler->ctx->aalg == PGP_AEAD_NONE ? rnp::AuthType::MDC : rnp::AuthType::AEADv1; + param->aalg = handler->ctx->aalg; + param->ctx = handler->ctx; + param->pkt.origdst = writedst; + dst->write = param->auth_type == rnp::AuthType::AEADv1 ? encrypted_dst_write_aead : + encrypted_dst_write_cfb; + dst->finish = encrypted_dst_finish; + dst->close = encrypted_dst_close; + dst->type = PGP_STREAM_ENCRYPTED; + + pkeycount = handler->ctx->recipients.size(); + skeycount = handler->ctx->passwords.size(); + + rnp::secure_array<uint8_t, PGP_MAX_KEY_SIZE> enckey; /* content encryption key */ + if (!pkeycount && !skeycount) { + RNP_LOG("no recipients"); + ret = RNP_ERROR_BAD_PARAMETERS; + goto finish; + } + + if ((pkeycount > 0) || (skeycount > 1) || (param->auth_type == rnp::AuthType::AEADv1)) { + try { + handler->ctx->ctx->rng.get(enckey.data(), keylen); + } catch (const std::exception &e) { + ret = RNP_ERROR_RNG; + goto finish; + } + singlepass = false; + } + + /* Configuring and writing pk-encrypted session keys */ + for (auto recipient : handler->ctx->recipients) { + ret = encrypted_add_recipient(handler, dst, recipient, enckey.data(), keylen); + if (ret) { + goto finish; + } + } + + /* Configuring and writing sk-encrypted session key(s) */ + for (auto &passinfo : handler->ctx->passwords) { + ret = encrypted_add_password(&passinfo, param, enckey.data(), keylen, singlepass); + if (ret != RNP_SUCCESS) { + goto finish; + } + } + + /* Initializing partial packet writer */ + param->pkt.partial = true; + param->pkt.indeterminate = false; + if (param->auth_type == rnp::AuthType::AEADv1) { + param->pkt.tag = PGP_PKT_AEAD_ENCRYPTED; + } else { + /* We do not generate PGP_PKT_SE_DATA, leaving this just in case */ + param->pkt.tag = + param->auth_type == rnp::AuthType::MDC ? PGP_PKT_SE_IP_DATA : PGP_PKT_SE_DATA; + } + + /* initializing partial data length writer */ + /* we may use intederminate len packet here as well, for compatibility or so on */ + if (!init_streamed_packet(¶m->pkt, writedst)) { + RNP_LOG("failed to init streamed packet"); + ret = RNP_ERROR_BAD_PARAMETERS; + goto finish; + } + + if (param->auth_type == rnp::AuthType::AEADv1) { + /* initialize AEAD encryption */ + ret = encrypted_start_aead(param, enckey.data()); + } else { + /* initialize old CFB or CFB with MDC */ + ret = encrypted_start_cfb(param, enckey.data()); + } +finish: + handler->ctx->passwords.clear(); + if (ret) { + encrypted_dst_close(dst, true); + } + return ret; +} + +static rnp_result_t +signed_dst_write(pgp_dest_t *dst, const void *buf, size_t len) +{ + pgp_dest_signed_param_t *param = (pgp_dest_signed_param_t *) dst->param; + dst_write(param->writedst, buf, len); + return RNP_SUCCESS; +} + +static void +cleartext_dst_writeline(pgp_dest_signed_param_t *param, + const uint8_t * buf, + size_t len, + bool eol) +{ + const uint8_t *ptr; + + /* dash-escaping line if needed */ + if (param->clr_start && len && + ((buf[0] == CH_DASH) || ((len >= 4) && !strncmp((const char *) buf, ST_FROM, 4)))) { + dst_write(param->writedst, ST_DASHSP, 2); + } + + /* output data */ + dst_write(param->writedst, buf, len); + + try { + if (eol) { + bool hashcrlf = false; + ptr = buf + len - 1; + + /* skipping trailing characters - space, tab, carriage return, line feed */ + while ((ptr >= buf) && ((*ptr == CH_SPACE) || (*ptr == CH_TAB) || + (*ptr == CH_CR) || (*ptr == CH_LF))) { + if (*ptr == CH_LF) { + hashcrlf = true; + } + ptr--; + } + + /* hashing line body and \r\n */ + param->hashes.add(buf, ptr + 1 - buf); + if (hashcrlf) { + param->hashes.add(ST_CRLF, 2); + } + param->clr_start = hashcrlf; + } else if (len > 0) { + /* hashing just line's data */ + param->hashes.add(buf, len); + param->clr_start = false; + } + } catch (const std::exception &e) { + RNP_LOG("failed to hash data: %s", e.what()); + } +} + +static size_t +cleartext_dst_scanline(const uint8_t *buf, size_t len, bool *eol) +{ + for (const uint8_t *ptr = buf, *end = buf + len; ptr < end; ptr++) { + if (*ptr == CH_LF) { + if (eol) { + *eol = true; + } + return ptr - buf + 1; + } + } + + if (eol) { + *eol = false; + } + return len; +} + +static rnp_result_t +cleartext_dst_write(pgp_dest_t *dst, const void *buf, size_t len) +{ + const uint8_t * linebg = (const uint8_t *) buf; + size_t linelen; + size_t cplen; + bool eol; + pgp_dest_signed_param_t *param = (pgp_dest_signed_param_t *) dst->param; + + if (param->clr_buflen > 0) { + /* number of edge cases may happen here */ + linelen = cleartext_dst_scanline(linebg, len, &eol); + + if (param->clr_buflen + linelen < sizeof(param->clr_buf)) { + /* fits into buffer */ + memcpy(param->clr_buf + param->clr_buflen, linebg, linelen); + param->clr_buflen += linelen; + if (!eol) { + /* do not write the line if we don't have whole */ + return RNP_SUCCESS; + } + + cleartext_dst_writeline(param, param->clr_buf, param->clr_buflen, true); + param->clr_buflen = 0; + } else { + /* we have line longer than 4k */ + cplen = sizeof(param->clr_buf) - param->clr_buflen; + memcpy(param->clr_buf + param->clr_buflen, linebg, cplen); + cleartext_dst_writeline(param, param->clr_buf, sizeof(param->clr_buf), false); + + if (eol || (linelen >= sizeof(param->clr_buf))) { + cleartext_dst_writeline(param, linebg + cplen, linelen - cplen, eol); + param->clr_buflen = 0; + } else { + param->clr_buflen = linelen - cplen; + memcpy(param->clr_buf, linebg + cplen, param->clr_buflen); + return RNP_SUCCESS; + } + } + + linebg += linelen; + len -= linelen; + } + + /* if we get here then we don't have data in param->clr_buf */ + while (len > 0) { + linelen = cleartext_dst_scanline(linebg, len, &eol); + + if (!eol && (linelen < sizeof(param->clr_buf))) { + memcpy(param->clr_buf, linebg, linelen); + param->clr_buflen = linelen; + return RNP_SUCCESS; + } + + cleartext_dst_writeline(param, linebg, linelen, eol); + linebg += linelen; + len -= linelen; + } + + return RNP_SUCCESS; +} + +static void +signed_fill_signature(pgp_dest_signed_param_t ¶m, + pgp_signature_t & sig, + pgp_dest_signer_info_t & signer) +{ + /* fill signature fields, assuming sign_init was called on it */ + if (signer.sigcreate) { + sig.set_creation(signer.sigcreate); + } + sig.set_expiration(signer.sigexpire); + sig.fill_hashed_data(); + + auto listh = param.hashes.get(sig.halg); + if (!listh) { + RNP_LOG("failed to obtain hash"); + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + + /* decrypt the secret key if needed */ + rnp::KeyLocker keylock(*signer.key); + if (signer.key->encrypted() && + !signer.key->unlock(*param.password_provider, PGP_OP_SIGN)) { + RNP_LOG("wrong secret key password"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PASSWORD); + } + /* calculate the signature */ + signature_calculate(sig, signer.key->material(), *listh->clone(), *param.ctx->ctx); +} + +static rnp_result_t +signed_write_signature(pgp_dest_signed_param_t *param, + pgp_dest_signer_info_t * signer, + pgp_dest_t * writedst) +{ + try { + pgp_signature_t sig; + if (signer->onepass.version) { + signer->key->sign_init(sig, signer->onepass.halg, param->ctx->ctx->time()); + sig.palg = signer->onepass.palg; + sig.set_type(signer->onepass.type); + } else { + signer->key->sign_init(sig, signer->halg, param->ctx->ctx->time()); + /* line below should be checked */ + sig.set_type(param->ctx->detached ? PGP_SIG_BINARY : PGP_SIG_TEXT); + } + signed_fill_signature(*param, sig, *signer); + sig.write(*writedst); + return writedst->werr; + } catch (const rnp::rnp_exception &e) { + return e.code(); + } catch (const std::exception &e) { + RNP_LOG("Failed to write signature: %s", e.what()); + return RNP_ERROR_WRITE; + } +} + +static rnp_result_t +signed_dst_finish(pgp_dest_t *dst) +{ + rnp_result_t ret; + pgp_dest_signed_param_t *param = (pgp_dest_signed_param_t *) dst->param; + + /* attached signature, we keep onepasses in order of signatures */ + for (auto &sinfo : param->siginfos) { + if ((ret = signed_write_signature(param, &sinfo, param->writedst))) { + RNP_LOG("failed to calculate signature"); + return ret; + } + } + + return RNP_SUCCESS; +} + +static rnp_result_t +signed_detached_dst_finish(pgp_dest_t *dst) +{ + rnp_result_t ret; + pgp_dest_signed_param_t *param = (pgp_dest_signed_param_t *) dst->param; + + /* just calculating and writing signatures to the output */ + for (auto &sinfo : param->siginfos) { + if ((ret = signed_write_signature(param, &sinfo, param->writedst))) { + RNP_LOG("failed to calculate detached signature"); + return ret; + } + } + + return RNP_SUCCESS; +} + +static rnp_result_t +cleartext_dst_finish(pgp_dest_t *dst) +{ + pgp_dest_signed_param_t *param = (pgp_dest_signed_param_t *) dst->param; + + /* writing cached line if any */ + if (param->clr_buflen > 0) { + cleartext_dst_writeline(param, param->clr_buf, param->clr_buflen, true); + } + /* trailing \r\n which is not hashed */ + dst_write(param->writedst, ST_CRLF, 2); + + /* writing signatures to the armored stream, which outputs to param->writedst */ + try { + rnp::ArmoredDest armor(*param->writedst, PGP_ARMORED_SIGNATURE); + armor.set_discard(true); + for (auto &sinfo : param->siginfos) { + auto ret = signed_write_signature(param, &sinfo, &armor.dst()); + if (ret) { + return ret; + } + } + armor.set_discard(false); + return RNP_SUCCESS; + } catch (const std::exception &e) { + RNP_LOG("Failed to write armored signature: %s", e.what()); + return RNP_ERROR_WRITE; + } +} + +static void +signed_dst_close(pgp_dest_t *dst, bool discard) +{ + pgp_dest_signed_param_t *param = (pgp_dest_signed_param_t *) dst->param; + if (!param) { + return; + } + delete param; + dst->param = NULL; +} + +static void +signed_dst_update(pgp_dest_t *dst, const void *buf, size_t len) +{ + pgp_dest_signed_param_t *param = (pgp_dest_signed_param_t *) dst->param; + param->hashes.add(buf, len); +} + +static rnp_result_t +signed_add_signer(pgp_dest_signed_param_t *param, rnp_signer_info_t *signer, bool last) +{ + pgp_dest_signer_info_t sinfo = {}; + + if (!signer->key->is_secret()) { + RNP_LOG("secret key required for signing"); + return RNP_ERROR_BAD_PARAMETERS; + } + /* validate signing key material if didn't before */ + signer->key->pkt().material.validate(*param->ctx->ctx, false); + if (!signer->key->pkt().material.valid()) { + RNP_LOG("attempt to sign to the key with invalid material"); + return RNP_ERROR_NO_SUITABLE_KEY; + } + + /* copy fields */ + sinfo.key = signer->key; + sinfo.sigcreate = signer->sigcreate; + sinfo.sigexpire = signer->sigexpire; + + /* Add hash to the list */ + sinfo.halg = pgp_hash_adjust_alg_to_key(signer->halg, &signer->key->pkt()); + try { + param->hashes.add_alg(sinfo.halg); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_BAD_PARAMETERS; + } + + // Do not add onepass for detached/clearsign + if (param->ctx->detached || param->ctx->clearsign) { + sinfo.onepass.version = 0; + try { + param->siginfos.push_back(sinfo); + return RNP_SUCCESS; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + } + + // Setup and add onepass + sinfo.onepass.version = 3; + sinfo.onepass.type = PGP_SIG_BINARY; + sinfo.onepass.halg = sinfo.halg; + sinfo.onepass.palg = sinfo.key->alg(); + sinfo.onepass.keyid = sinfo.key->keyid(); + sinfo.onepass.nested = false; + try { + param->siginfos.push_back(sinfo); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + + // write onepasses in reverse order so signature order will match signers list + if (!last) { + return RNP_SUCCESS; + } + try { + for (auto it = param->siginfos.rbegin(); it != param->siginfos.rend(); it++) { + pgp_dest_signer_info_t &sinfo = *it; + sinfo.onepass.nested = &sinfo == ¶m->siginfos.front(); + sinfo.onepass.write(*param->writedst); + } + return param->writedst->werr; + } catch (const std::exception &e) { + return RNP_ERROR_WRITE; + } +} + +static rnp_result_t +init_signed_dst(pgp_write_handler_t *handler, pgp_dest_t *dst, pgp_dest_t *writedst) +{ + pgp_dest_signed_param_t *param; + rnp_result_t ret = RNP_ERROR_GENERIC; + + if (!handler->key_provider) { + RNP_LOG("no key provider"); + return RNP_ERROR_BAD_PARAMETERS; + } + + if (!init_dst_common(dst, 0)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + try { + param = new pgp_dest_signed_param_t(); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + + dst->param = param; + param->writedst = writedst; + param->ctx = handler->ctx; + param->password_provider = handler->password_provider; + if (param->ctx->clearsign) { + dst->type = PGP_STREAM_CLEARTEXT; + dst->write = cleartext_dst_write; + dst->finish = cleartext_dst_finish; + param->clr_start = true; + } else { + dst->type = PGP_STREAM_SIGNED; + dst->write = signed_dst_write; + dst->finish = param->ctx->detached ? signed_detached_dst_finish : signed_dst_finish; + } + dst->close = signed_dst_close; + + /* Getting signer's infos, writing one-pass signatures if needed */ + for (auto &sg : handler->ctx->signers) { + ret = signed_add_signer(param, &sg, &sg == &handler->ctx->signers.back()); + if (ret) { + RNP_LOG("failed to add one-pass signature for signer"); + goto finish; + } + } + + /* Do we have any signatures? */ + if (param->hashes.hashes.empty()) { + ret = RNP_ERROR_BAD_PARAMETERS; + goto finish; + } + + /* Writing headers for cleartext signed document */ + if (param->ctx->clearsign) { + dst_write(param->writedst, ST_CLEAR_BEGIN, strlen(ST_CLEAR_BEGIN)); + dst_write(param->writedst, ST_CRLF, strlen(ST_CRLF)); + dst_write(param->writedst, ST_HEADER_HASH, strlen(ST_HEADER_HASH)); + + for (const auto &hash : param->hashes.hashes) { + auto hname = rnp::Hash::name(hash->alg()); + dst_write(param->writedst, hname, strlen(hname)); + if (&hash != ¶m->hashes.hashes.back()) { + dst_write(param->writedst, ST_COMMA, 1); + } + } + + dst_write(param->writedst, ST_CRLFCRLF, strlen(ST_CRLFCRLF)); + } + + ret = RNP_SUCCESS; +finish: + if (ret != RNP_SUCCESS) { + signed_dst_close(dst, true); + } + + return ret; +} + +static rnp_result_t +compressed_dst_write(pgp_dest_t *dst, const void *buf, size_t len) +{ + pgp_dest_compressed_param_t *param = (pgp_dest_compressed_param_t *) dst->param; + int zret; + + if (!param) { + RNP_LOG("wrong param"); + return RNP_ERROR_BAD_PARAMETERS; + } + + if ((param->alg == PGP_C_ZIP) || (param->alg == PGP_C_ZLIB)) { + param->z.next_in = (unsigned char *) buf; + param->z.avail_in = len; + param->z.next_out = param->cache + param->len; + param->z.avail_out = sizeof(param->cache) - param->len; + + while (param->z.avail_in > 0) { + zret = deflate(¶m->z, Z_NO_FLUSH); + /* Z_OK, Z_BUF_ERROR are ok for us, Z_STREAM_END will not happen here */ + if (zret == Z_STREAM_ERROR) { + RNP_LOG("wrong deflate state"); + return RNP_ERROR_BAD_STATE; + } + + /* writing only full blocks, the rest will be written in close */ + if (param->z.avail_out == 0) { + dst_write(param->pkt.writedst, param->cache, sizeof(param->cache)); + param->len = 0; + param->z.next_out = param->cache; + param->z.avail_out = sizeof(param->cache); + } + } + + param->len = sizeof(param->cache) - param->z.avail_out; + return RNP_SUCCESS; + } else if (param->alg == PGP_C_BZIP2) { +#ifdef HAVE_BZLIB_H + param->bz.next_in = (char *) buf; + param->bz.avail_in = len; + param->bz.next_out = (char *) (param->cache + param->len); + param->bz.avail_out = sizeof(param->cache) - param->len; + + while (param->bz.avail_in > 0) { + zret = BZ2_bzCompress(¶m->bz, BZ_RUN); + if (zret < 0) { + RNP_LOG("error %d", zret); + return RNP_ERROR_BAD_STATE; + } + + /* writing only full blocks, the rest will be written in close */ + if (param->bz.avail_out == 0) { + dst_write(param->pkt.writedst, param->cache, sizeof(param->cache)); + param->len = 0; + param->bz.next_out = (char *) param->cache; + param->bz.avail_out = sizeof(param->cache); + } + } + + param->len = sizeof(param->cache) - param->bz.avail_out; + return RNP_SUCCESS; +#else + return RNP_ERROR_NOT_IMPLEMENTED; +#endif + } else { + RNP_LOG("unknown algorithm"); + return RNP_ERROR_BAD_PARAMETERS; + } +} + +static rnp_result_t +compressed_dst_finish(pgp_dest_t *dst) +{ + int zret; + pgp_dest_compressed_param_t *param = (pgp_dest_compressed_param_t *) dst->param; + + if ((param->alg == PGP_C_ZIP) || (param->alg == PGP_C_ZLIB)) { + param->z.next_in = Z_NULL; + param->z.avail_in = 0; + param->z.next_out = param->cache + param->len; + param->z.avail_out = sizeof(param->cache) - param->len; + do { + zret = deflate(¶m->z, Z_FINISH); + + if (zret == Z_STREAM_ERROR) { + RNP_LOG("wrong deflate state"); + return RNP_ERROR_BAD_STATE; + } + + if (param->z.avail_out == 0) { + dst_write(param->pkt.writedst, param->cache, sizeof(param->cache)); + param->len = 0; + param->z.next_out = param->cache; + param->z.avail_out = sizeof(param->cache); + } + } while (zret != Z_STREAM_END); + + param->len = sizeof(param->cache) - param->z.avail_out; + dst_write(param->pkt.writedst, param->cache, param->len); + } +#ifdef HAVE_BZLIB_H + if (param->alg == PGP_C_BZIP2) { + param->bz.next_in = NULL; + param->bz.avail_in = 0; + param->bz.next_out = (char *) (param->cache + param->len); + param->bz.avail_out = sizeof(param->cache) - param->len; + + do { + zret = BZ2_bzCompress(¶m->bz, BZ_FINISH); + if (zret < 0) { + RNP_LOG("wrong bzip2 state %d", zret); + return RNP_ERROR_BAD_STATE; + } + + /* writing only full blocks, the rest will be written in close */ + if (param->bz.avail_out == 0) { + dst_write(param->pkt.writedst, param->cache, sizeof(param->cache)); + param->len = 0; + param->bz.next_out = (char *) param->cache; + param->bz.avail_out = sizeof(param->cache); + } + } while (zret != BZ_STREAM_END); + + param->len = sizeof(param->cache) - param->bz.avail_out; + dst_write(param->pkt.writedst, param->cache, param->len); + } +#endif + + if (param->pkt.writedst->werr) { + return param->pkt.writedst->werr; + } + + return finish_streamed_packet(¶m->pkt); +} + +static void +compressed_dst_close(pgp_dest_t *dst, bool discard) +{ + pgp_dest_compressed_param_t *param = (pgp_dest_compressed_param_t *) dst->param; + + if (!param) { + return; + } + + if (param->zstarted) { + if ((param->alg == PGP_C_ZIP) || (param->alg == PGP_C_ZLIB)) { + deflateEnd(¶m->z); + } +#ifdef HAVE_BZLIB_H + if (param->alg == PGP_C_BZIP2) { + BZ2_bzCompressEnd(¶m->bz); + } +#endif + } + + close_streamed_packet(¶m->pkt, discard); + free(param); + dst->param = NULL; +} + +static rnp_result_t +init_compressed_dst(pgp_write_handler_t *handler, pgp_dest_t *dst, pgp_dest_t *writedst) +{ + pgp_dest_compressed_param_t *param; + rnp_result_t ret = RNP_ERROR_GENERIC; + uint8_t buf; + int zret; + + if (!init_dst_common(dst, sizeof(*param))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + param = (pgp_dest_compressed_param_t *) dst->param; + dst->write = compressed_dst_write; + dst->finish = compressed_dst_finish; + dst->close = compressed_dst_close; + dst->type = PGP_STREAM_COMPRESSED; + param->alg = (pgp_compression_type_t) handler->ctx->zalg; + param->pkt.partial = true; + param->pkt.indeterminate = false; + param->pkt.tag = PGP_PKT_COMPRESSED; + + /* initializing partial length or indeterminate packet, writing header */ + if (!init_streamed_packet(¶m->pkt, writedst)) { + RNP_LOG("failed to init streamed packet"); + ret = RNP_ERROR_BAD_PARAMETERS; + goto finish; + } + + /* compression algorithm */ + buf = param->alg; + dst_write(param->pkt.writedst, &buf, 1); + + /* initializing compression */ + switch (param->alg) { + case PGP_C_ZIP: + case PGP_C_ZLIB: + (void) memset(¶m->z, 0x0, sizeof(param->z)); + if (param->alg == PGP_C_ZIP) { + zret = deflateInit2( + ¶m->z, handler->ctx->zlevel, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY); + } else { + zret = deflateInit(¶m->z, handler->ctx->zlevel); + } + + if (zret != Z_OK) { + RNP_LOG("failed to init zlib, error %d", zret); + ret = RNP_ERROR_NOT_SUPPORTED; + goto finish; + } + break; +#ifdef HAVE_BZLIB_H + case PGP_C_BZIP2: + (void) memset(¶m->bz, 0x0, sizeof(param->bz)); + zret = BZ2_bzCompressInit(¶m->bz, handler->ctx->zlevel, 0, 0); + if (zret != BZ_OK) { + RNP_LOG("failed to init bz, error %d", zret); + ret = RNP_ERROR_NOT_SUPPORTED; + goto finish; + } + break; +#endif + default: + RNP_LOG("unknown compression algorithm"); + ret = RNP_ERROR_NOT_SUPPORTED; + goto finish; + } + param->zstarted = true; + ret = RNP_SUCCESS; +finish: + if (ret != RNP_SUCCESS) { + compressed_dst_close(dst, true); + } + + return ret; +} + +static rnp_result_t +literal_dst_write(pgp_dest_t *dst, const void *buf, size_t len) +{ + pgp_dest_packet_param_t *param = (pgp_dest_packet_param_t *) dst->param; + + if (!param) { + RNP_LOG("wrong param"); + return RNP_ERROR_BAD_PARAMETERS; + } + + dst_write(param->writedst, buf, len); + return RNP_SUCCESS; +} + +static rnp_result_t +literal_dst_finish(pgp_dest_t *dst) +{ + return finish_streamed_packet((pgp_dest_packet_param_t *) dst->param); +} + +static void +literal_dst_close(pgp_dest_t *dst, bool discard) +{ + pgp_dest_packet_param_t *param = (pgp_dest_packet_param_t *) dst->param; + + if (!param) { + return; + } + + close_streamed_packet(param, discard); + free(param); + dst->param = NULL; +} + +static rnp_result_t +init_literal_dst(pgp_write_handler_t *handler, pgp_dest_t *dst, pgp_dest_t *writedst) +{ + pgp_dest_packet_param_t *param; + rnp_result_t ret = RNP_ERROR_GENERIC; + size_t flen = 0; + uint8_t buf[4]; + + if (!init_dst_common(dst, sizeof(*param))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + param = (pgp_dest_packet_param_t *) dst->param; + dst->write = literal_dst_write; + dst->finish = literal_dst_finish; + dst->close = literal_dst_close; + dst->type = PGP_STREAM_LITERAL; + param->partial = true; + param->indeterminate = false; + param->tag = PGP_PKT_LITDATA; + + /* initializing partial length or indeterminate packet, writing header */ + if (!init_streamed_packet(param, writedst)) { + RNP_LOG("failed to init streamed packet"); + ret = RNP_ERROR_BAD_PARAMETERS; + goto finish; + } + /* content type - forcing binary now */ + buf[0] = (uint8_t) 'b'; + /* filename */ + flen = handler->ctx->filename.size(); + if (flen > 255) { + RNP_LOG("filename too long, truncating"); + flen = 255; + } + buf[1] = (uint8_t) flen; + dst_write(param->writedst, buf, 2); + if (flen) { + dst_write(param->writedst, handler->ctx->filename.c_str(), flen); + } + /* timestamp */ + STORE32BE(buf, handler->ctx->filemtime); + dst_write(param->writedst, buf, 4); + ret = RNP_SUCCESS; +finish: + if (ret != RNP_SUCCESS) { + literal_dst_close(dst, true); + } + + return ret; +} + +static rnp_result_t +process_stream_sequence(pgp_source_t *src, + pgp_dest_t * streams, + unsigned count, + pgp_dest_t * sstream, + pgp_dest_t * wstream) +{ + std::unique_ptr<uint8_t[]> readbuf(new (std::nothrow) uint8_t[PGP_INPUT_CACHE_SIZE]); + if (!readbuf) { + RNP_LOG("allocation failure"); + return RNP_ERROR_OUT_OF_MEMORY; + } + + /* processing source stream */ + while (!src->eof) { + size_t read = 0; + if (!src_read(src, readbuf.get(), PGP_INPUT_CACHE_SIZE, &read)) { + RNP_LOG("failed to read from source"); + return RNP_ERROR_READ; + } else if (!read) { + continue; + } + + if (sstream) { + signed_dst_update(sstream, readbuf.get(), read); + } + + if (wstream) { + dst_write(wstream, readbuf.get(), read); + + for (int i = count - 1; i >= 0; i--) { + if (streams[i].werr) { + RNP_LOG("failed to process data"); + return RNP_ERROR_WRITE; + } + } + } + } + + /* finalizing destinations */ + for (int i = count - 1; i >= 0; i--) { + rnp_result_t ret = dst_finish(&streams[i]); + if (ret) { + RNP_LOG("failed to finish stream"); + return ret; + } + } + return RNP_SUCCESS; +} + +rnp_result_t +rnp_sign_src(pgp_write_handler_t *handler, pgp_source_t *src, pgp_dest_t *dst) +{ + /* stack of the streams would be as following: + [armoring stream] - if armoring is enabled + [compressing stream, partial writing stream] - compression is enabled, and not detached + signing stream + literal data stream, partial writing stream - if not detached or cleartext signature + */ + pgp_dest_t dests[4]; + unsigned destc = 0; + rnp_result_t ret = RNP_ERROR_GENERIC; + rnp_ctx_t & ctx = *handler->ctx; + pgp_dest_t * wstream = NULL; + pgp_dest_t * sstream = NULL; + + /* pushing armoring stream, which will write to the output */ + if (ctx.armor && !ctx.clearsign) { + pgp_armored_msg_t msgt = ctx.detached ? PGP_ARMORED_SIGNATURE : PGP_ARMORED_MESSAGE; + ret = init_armored_dst(&dests[destc], dst, msgt); + if (ret) { + goto finish; + } + destc++; + } + + /* if compression is enabled then pushing compressing stream */ + if (!ctx.detached && !ctx.clearsign && (ctx.zlevel > 0)) { + if ((ret = + init_compressed_dst(handler, &dests[destc], destc ? &dests[destc - 1] : dst))) { + goto finish; + } + destc++; + } + + /* pushing signing stream, which will use handler->ctx to distinguish between + * attached/detached/cleartext signature */ + if ((ret = init_signed_dst(handler, &dests[destc], destc ? &dests[destc - 1] : dst))) { + goto finish; + } + if (!ctx.clearsign) { + sstream = &dests[destc]; + } + if (!ctx.detached) { + wstream = &dests[destc]; + } + destc++; + + /* pushing literal data stream, if not detached/cleartext signature */ + if (!ctx.no_wrap && !ctx.detached && !ctx.clearsign) { + if ((ret = init_literal_dst(handler, &dests[destc], &dests[destc - 1]))) { + goto finish; + } + wstream = &dests[destc]; + destc++; + } + + /* process source with streams stack */ + ret = process_stream_sequence(src, dests, destc, sstream, wstream); +finish: + for (int i = destc - 1; i >= 0; i--) { + dst_close(&dests[i], ret); + } + return ret; +} + +rnp_result_t +rnp_encrypt_sign_src(pgp_write_handler_t *handler, pgp_source_t *src, pgp_dest_t *dst) +{ + /* stack of the streams would be as following: + [armoring stream] - if armoring is enabled + [encrypting stream, partial writing stream] + [compressing stream, partial writing stream] - compression is enabled + signing stream + literal data stream, partial writing stream + */ + pgp_dest_t dests[5]; + size_t destc = 0; + rnp_result_t ret = RNP_SUCCESS; + rnp_ctx_t & ctx = *handler->ctx; + pgp_dest_t * sstream = NULL; + + /* we may use only attached signatures here */ + if (ctx.clearsign || ctx.detached) { + RNP_LOG("cannot clearsign or sign detached together with encryption"); + return RNP_ERROR_BAD_PARAMETERS; + } + + /* pushing armoring stream, which will write to the output */ + if (ctx.armor) { + if ((ret = init_armored_dst(&dests[destc], dst, PGP_ARMORED_MESSAGE))) { + goto finish; + } + destc++; + } + + /* pushing encrypting stream, which will write to the output or armoring stream */ + if ((ret = init_encrypted_dst(handler, &dests[destc], destc ? &dests[destc - 1] : dst))) { + goto finish; + } + destc++; + + /* if compression is enabled then pushing compressing stream */ + if (ctx.zlevel > 0) { + if ((ret = init_compressed_dst(handler, &dests[destc], &dests[destc - 1]))) { + goto finish; + } + destc++; + } + + /* pushing signing stream if we have signers */ + if (!ctx.signers.empty()) { + if ((ret = init_signed_dst(handler, &dests[destc], &dests[destc - 1]))) { + goto finish; + } + sstream = &dests[destc]; + destc++; + } + + /* pushing literal data stream */ + if (!ctx.no_wrap) { + if ((ret = init_literal_dst(handler, &dests[destc], &dests[destc - 1]))) { + goto finish; + } + destc++; + } + + /* process source with streams stack */ + ret = process_stream_sequence(src, dests, destc, sstream, &dests[destc - 1]); +finish: + for (size_t i = destc; i > 0; i--) { + dst_close(&dests[i - 1], ret); + } + return ret; +} + +rnp_result_t +rnp_compress_src(pgp_source_t &src, pgp_dest_t &dst, pgp_compression_type_t zalg, int zlevel) +{ + pgp_write_handler_t handler = {}; + rnp_ctx_t ctx; + ctx.zalg = zalg; + ctx.zlevel = zlevel; + handler.ctx = &ctx; + + pgp_dest_t compressed = {}; + rnp_result_t ret = init_compressed_dst(&handler, &compressed, &dst); + if (ret) { + goto done; + } + ret = dst_write_src(&src, &compressed); +done: + dst_close(&compressed, ret); + return ret; +} + +rnp_result_t +rnp_wrap_src(pgp_source_t &src, pgp_dest_t &dst, const std::string &filename, uint32_t modtime) +{ + pgp_write_handler_t handler = {}; + rnp_ctx_t ctx; + ctx.filename = filename; + ctx.filemtime = modtime; + handler.ctx = &ctx; + + pgp_dest_t literal = {}; + rnp_result_t ret = init_literal_dst(&handler, &literal, &dst); + if (ret) { + goto done; + } + + ret = dst_write_src(&src, &literal); +done: + dst_close(&literal, ret); + return ret; +} + +rnp_result_t +rnp_raw_encrypt_src(pgp_source_t & src, + pgp_dest_t & dst, + const std::string & password, + rnp::SecurityContext &secctx) +{ + pgp_write_handler_t handler = {}; + rnp_ctx_t ctx; + + ctx.ctx = &secctx; + ctx.ealg = DEFAULT_PGP_SYMM_ALG; + handler.ctx = &ctx; + pgp_dest_t encrypted = {}; + + rnp_result_t ret = RNP_ERROR_GENERIC; + try { + ret = + ctx.add_encryption_password(password, DEFAULT_PGP_HASH_ALG, DEFAULT_PGP_SYMM_ALG); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + goto done; + } + if (ret) { + goto done; + } + + ret = init_encrypted_dst(&handler, &encrypted, &dst); + if (ret) { + goto done; + } + + ret = dst_write_src(&src, &encrypted); +done: + dst_close(&encrypted, ret); + return ret; +} diff --git a/comm/third_party/rnp/src/librepgp/stream-write.h b/comm/third_party/rnp/src/librepgp/stream-write.h new file mode 100644 index 0000000000..49431f9152 --- /dev/null +++ b/comm/third_party/rnp/src/librepgp/stream-write.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2017, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef STREAM_WRITE_H_ +#define STREAM_WRITE_H_ + +#include <stdint.h> +#include <stdbool.h> +#include <sys/types.h> +#include "rnp.h" +#include "stream-common.h" +#include "stream-ctx.h" + +typedef struct pgp_write_handler_t { + pgp_password_provider_t *password_provider; + pgp_key_provider_t * key_provider; + rnp_ctx_t * ctx; + + void *param; +} pgp_write_handler_t; + +/** @brief sign the input data, producing attached, detached or cleartext signature. + * Type of the signature is controlled by clearsign and detached fields of the + * rnp_ctx_t structure + * @param handler handler to respond on stream processor callbacks, and additional processing + * parameters, including rnp_ctx_t + * @param src input source: file, stdin, memory, whatever else conforming to pgp_source_t + * @param dst output destination: file, stdout, memory, whatever else conforming to pgp_dest_t + **/ +rnp_result_t rnp_sign_src(pgp_write_handler_t *handler, pgp_source_t *src, pgp_dest_t *dst); + +/** @brief encrypt and sign the input data. Signatures will be enrypted together with data. + * @param handler handler handler to respond on stream processor callbacks, and additional + * processing parameters, including rnp_ctx_t + * @param src input source: file, stdin, memory, whatever else conforming to pgp_source_t + * @param dst output destination: file, stdout, memory, whatever else conforming to pgp_dest_t + **/ +rnp_result_t rnp_encrypt_sign_src(pgp_write_handler_t *handler, + pgp_source_t * src, + pgp_dest_t * dst); + +/* Following functions are used only in tests currently. Later could be used in CLI for debug + * commands like --wrap-literal, --encrypt-raw, --compress-raw, etc. */ + +rnp_result_t rnp_compress_src(pgp_source_t & src, + pgp_dest_t & dst, + pgp_compression_type_t zalg, + int zlevel); + +rnp_result_t rnp_wrap_src(pgp_source_t & src, + pgp_dest_t & dst, + const std::string &filename, + uint32_t modtime); + +rnp_result_t rnp_raw_encrypt_src(pgp_source_t & src, + pgp_dest_t & dst, + const std::string & password, + rnp::SecurityContext &secctx); + +#endif diff --git a/comm/third_party/rnp/src/libsexp/LICENSE.md b/comm/third_party/rnp/src/libsexp/LICENSE.md new file mode 100644 index 0000000000..2e579d2074 --- /dev/null +++ b/comm/third_party/rnp/src/libsexp/LICENSE.md @@ -0,0 +1,34 @@ +Original MIT License +==================== + +Copyright 1997 Ronald L. Rivest, Butler Lampson +Copyright 1997 MIT Laboratory for Computer Science + +The code is available under the "MIT License" (open source). + +License text available at: +https://opensource.org/licenses/MIT + + +Ribose MIT License +==================== + +Copyright 2021-2022 Ribose Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/comm/third_party/rnp/src/libsexp/README.adoc b/comm/third_party/rnp/src/libsexp/README.adoc new file mode 100644 index 0000000000..69317257d4 --- /dev/null +++ b/comm/third_party/rnp/src/libsexp/README.adoc @@ -0,0 +1,195 @@ += S-expressions parser and generator library in C\++ (SEXP in C++) + +image:https://github.com/rnpgp/sexp/workflows/build-and-test/badge.svg["Build status Ubuntu/macOS/Windows", link="https://github.com/rnpgp/sexp/actions?workflow=build-and-test"] +image:https://github.com/rnpgp/sexp/workflows/build-and-test-rh/badge.svg["Build status CentOS/Fedora", link="https://github.com/rnpgp/sexp/actions?workflow=build-and-test-rh"] +image:https://github.com/rnpgp/sexp/workflows/build-and-test-deb/badge.svg["Build status Debian", link="https://github.com/rnpgp/sexp/actions?workflow=build-and-test-deb"] +image:https://github.com/rnpgp/sexp/workflows/build-and-test-msys/badge.svg["Build status MSys", link="https://github.com/rnpgp/sexp/actions?workflow=build-and-test-msys"] + + +image:https://codecov.io/gh/rnpgp/sexp/branch/feat/g23/graph/badge.svg["Code coverage", link="https://codecov.io/gh/rnpgp/sexp"] +image:https://github.com/rnpgp/sexp/workflows/CodeQL/badge.svg["CodeQL analysis", link="https://github.com/rnpgp/sexp/actions?workflow=CodeQL"] +image:https://scan.coverity.com/projects/27150/badge.svg["Coverity Scan Build Status", link="https://scan.coverity.com/projects/rnpgp-sexp"] + + +== Purpose + +This is a C++ library for working with S-expressions. This implementation +is derived from the reference SEXP C library developed by Professors Ronald Rivest +and Butler Lampson of MIT LCS (now CSAIL). + +This library differs from the original C implementation in the following ways: + +* It aims to be reuseable in C++ implementations and is importable via CMake. +* It includes a test suite for correctness testing and tests against malformed + S-expressions. +* It supports, and is tested against, all major platforms, including: +** Ubuntu, Debian, Fedora, CentOS +** macOS +** Windows +** msys +* It implements additional interface to work with S-expressions wrapped by GnuPG 2.3+ extended format as defined at https://github.com/gpg/gnupg/blob/master/agent/keyformat.txt + +The original C library is available at: + +* http://people.csail.mit.edu/rivest/sexp.html + + +== Background + +S-expressions are a data structure for representing complex data as a variation +on https://en.wikipedia.org/wiki/Lisp_(programming_language)[LISP] S-expressions. + +S-expressions were originally adopted for use in +http://theory.lcs.mit.edu/~cis/sdsi.html[SDSI] and +http://world.std.com/~cme/html/spki.html[SPKI]. + +SDSI has been developed by Professors +https://people.csail.mit.edu/rivest/index.html[Ronald L. Rivest] and Butler +Lampson of http://www.lcs.mit.edu/[MIT's Laboratory for Computer Science], +members of +http://theory.lcs.mit.edu/~cis[LCS's Cryptography and Information Security] +research group. + +NOTE: SDSI research has been supported by DARPA contract DABT63-96-C-0018, +"Security for Distributed Computer Systems". + +NOTE: SPKI has been developed by +http://www.clark.net/pub/cme/home.html[Carl Ellison] and others in the IETF SPKI +working group. + + +== Usage guide for S-expressions + +* https://people.csail.mit.edu/rivest/Sexp.txt[SEXP 1.0 guide] + + +== Code + +The library is a deep rework to C++ of the original +https://people.csail.mit.edu/rivest/sexp.html[SEXP library] that maintains full +support of original specification. + +While most applications will not need anything but the simple canonical and +transport formats; however, the code here is considerably more complex because +it also supports the advanced format, both for input and for output. + + +== Building and installation + +[source,sh] +---- +mkdir build +cd build +cmake .. +cmake --build . +ctest +cmake --install . +---- + + +== CMake script options + +`WITH_SEXP_TESTS:BOOL`:: +build tests (default: `ON`) + +`DOWNLOAD_GTEST`:: +if tests are build download googletest from github (default: `ON`) +when this option is set to `OFF` googletest binary package is a prerequisite for SEXP tests + +`WITH_SEXP_CLI:BOOL`:: +build the `sexp` client application (default: `ON`) + +`WITH_SANITIZERS:BOOL`:: +build with address and other sanitizers (default: `OFF`) +(requires clang compiler) + + + +== SEXP command-line utility + +The `sexp` command-line utility is reference parser and generator of S-expressions. +It can read, parse and print out SEXP in all defined formats. + +=== sexp switches: +[options="header"] +|======================================================================================================= +| Switch | Description | Default +3+| Input +| -i <filename> | input file name | read input from console (stdin) +| -p | prompt input if reading from console | disabled +| -s | treat input as a single SEXP string | disabled, input is treated as S-expression +3+| Output +| -o <filename> | output file name: | write output to console (stdout) +| -a | generate advanced transport format | enabled if no format is specified +| -b | generate base-64 transport format | disabled +| -c | generate canonical format | disabled +| -l | suppress linefeeds after output | disabled +| -w <width> | set output line width (0 implies no constraint)| 75 +3+| Miscellaneous +| -x | execute repeatedly until EOF | process single S-expression then exit +| -h | print help message and exit | +|======================================================================================================= + +Running without switches implies: -p -a -b -c -x + +=== Usage examples: +Prompt for S-expressions input from console, parse and output it to `certificate.dat` in base64 transport format +[source] +---- +sexp -o certificate.dat -p -b + +> Input: +> (aa bb (cc dd)) +> +> Writing base64 (of canonical) output to 'certificate.dat' +---- + +Parse all S-expressions from `certificate.dat`, output them to console in advanced transport format with no prompts +[source] +---- +sexp -i certificate.dat -x + +> (2:aa2:bb(2:cc2:dd)) +---- + +Parse S-expressions from `certificate.dat`, output it to console in canonical, base64 and advanced format with prompts and no width limitation +[source] +---- +sexp -i certificate.dat -a -b -c -p -w 0 + +> Reading input from certificate.dat +> +> Canonical output: +> (2:aa2:bb(2:cc2:dd)) +> Base64 (of canonical) output: +> {KDI6YWEyOmJiKDI6Y2MyOmRkKSk=} +> Advanced transport output: +> (aa bb (cc dd)) +---- + +Repeatedly prompt for S-expressions input from console, parse and output it console in advanced, base64 and canonical formats +[source] +---- +sexp -p -a -b -c -x +---- +or just +---- +sexp + +> Input: +> (abc def (ghi jkl)) +> +> Canonical output: +> (3:abc3:def(3:ghi3:jkl)) +> Base64 (of canonical) output: +> {KDM6YWJjMzpkZWYoMzpnaGkzOmprbCkp} +> Advanced transport output: +> (abc def (ghi jkl)) +> +> Input: +> ^C +---- + +== License + +The code is made available as open-source software under the MIT License. diff --git a/comm/third_party/rnp/src/libsexp/include/sexp/ext-key-format.h b/comm/third_party/rnp/src/libsexp/include/sexp/ext-key-format.h new file mode 100644 index 0000000000..fc031a3473 --- /dev/null +++ b/comm/third_party/rnp/src/libsexp/include/sexp/ext-key-format.h @@ -0,0 +1,99 @@ +/** + * + * Copyright (c) 2022, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * This file is a part of RNP sexp library + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#pragma once + +#include <map> +#include "sexp.h" + +namespace ext_key_format { + +void ext_key_error( + sexp::sexp_exception_t::severity level, const char *msg, size_t c1, size_t c2, int pos); + +class ext_key_input_stream_t; + +class extended_private_key_t { + public: + // Comparison of names is done case insensitively !!! + struct ci_less { + // case-independent (ci) compare_less binary function + bool operator()(const std::string &s1, const std::string &s2) const + { + return std::lexicographical_compare( + s1.begin(), s1.end(), s2.begin(), s2.end(), [](char a, char b) { + return std::tolower(a) < std::tolower(b); + }); + } + }; + + // C++ 11 compatible version (no std::equals) + static bool iequals(const std::string &a, const std::string &b) + { + size_t sz = a.size(); + if (b.size() != sz) + return false; + for (size_t i = 0; i < sz; ++i) + if (tolower(a[i]) != tolower(b[i])) + return false; + return true; + } + + typedef std::multimap<std::string, std::string, ci_less> fields_map_t; + + sexp::sexp_list_t key; + fields_map_t fields; + + void parse(ext_key_input_stream_t &is); +}; + +class ext_key_input_stream_t : public sexp::sexp_input_stream_t { + private: + static const bool namechar[256]; /* true if allowed in the name field */ + + static bool is_newline_char(int c) { return c == '\r' || c == '\n'; }; + static bool is_namechar(int c) { return ((c >= 0 && c <= 255) && namechar[c]); } + + bool is_scanning_value; + bool has_key; + + int skip_line(void); + virtual int read_char(void); + std::string scan_name(int c); + std::string scan_value(void); + + public: + ext_key_input_stream_t(std::istream *i, size_t md = 0) + : sexp_input_stream_t(i, md), is_scanning_value(false), has_key(false) + { + } + virtual ~ext_key_input_stream_t() = default; + void scan(extended_private_key_t &extended_key); +}; +} // namespace ext_key_format diff --git a/comm/third_party/rnp/src/libsexp/include/sexp/sexp-error.h b/comm/third_party/rnp/src/libsexp/include/sexp/sexp-error.h new file mode 100644 index 0000000000..332a63bcad --- /dev/null +++ b/comm/third_party/rnp/src/libsexp/include/sexp/sexp-error.h @@ -0,0 +1,77 @@ +/** + * + * Copyright (c) 2022, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * This file is a part of RNP sexp library + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#pragma once + +#include <cstdint> +#include <exception> +#include <iostream> +#include <string> + +namespace sexp { + +class sexp_exception_t : public std::exception { + public: + enum severity { error = 0, warning = 1 }; + + protected: + static severity verbosity; + static bool interactive; + + int position; // May be EOF aka -1 + severity level; + std::string message; + + public: + sexp_exception_t(std::string error_message, + severity error_level, + int error_position, + const char *prefix = "SEXP") + : position{error_position}, level{error_level}, + message{format(prefix, error_message, error_level, error_position)} {}; + + static std::string format(std::string prf, + std::string message, + severity level, + int position); + + static bool shall_throw(severity level) { return level == error || verbosity != error; }; + virtual const char *what(void) const throw() { return message.c_str(); }; + severity get_level(void) const { return level; }; + uint32_t get_position(void) const { return position; }; + static severity get_verbosity(void) { return verbosity; }; + static bool is_interactive(void) { return interactive; }; + static void set_verbosity(severity new_verbosity) { verbosity = new_verbosity; }; + static void set_interactive(bool new_interactive) { interactive = new_interactive; }; +}; + +void sexp_error( + sexp_exception_t::severity level, const char *msg, size_t c1, size_t c2, int pos); + +} // namespace sexp diff --git a/comm/third_party/rnp/src/libsexp/include/sexp/sexp.h b/comm/third_party/rnp/src/libsexp/include/sexp/sexp.h new file mode 100644 index 0000000000..52cad55751 --- /dev/null +++ b/comm/third_party/rnp/src/libsexp/include/sexp/sexp.h @@ -0,0 +1,435 @@ +/**
+ *
+ * Copyright (c) 2022-2023, [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ * This file is a part of RNP sexp library
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Original copyright
+ *
+ * SEXP standard header file: sexp.h
+ * Ronald L. Rivest
+ * 6/29/1997
+ */
+
+#pragma once
+
+#include <climits>
+#include <limits>
+#include <cctype>
+#include <locale>
+#include <cstring>
+#include <memory>
+#include <algorithm>
+#include <iostream>
+#include <string>
+#include <vector>
+#include <cassert>
+
+#include "sexp-error.h"
+
+namespace sexp {
+/*
+ * SEXP octet_t definitions
+ * We maintain some presumable redundancy with ctype
+ * However, we do enforce 'C' locale this way
+ */
+
+class sexp_char_defs_t {
+ protected:
+ static const bool base64digit[256]; /* true if c is base64 digit */
+ static const bool tokenchar[256]; /* true if c can be in a token */
+ static const unsigned char values[256][3]; /* values of c as { dec. hex, base64 } digit */
+ static std::locale c_locale;
+
+ static bool is_white_space(int c)
+ {
+ return c >= 0 && c <= 255 && std::isspace((char) c, c_locale);
+ };
+ static bool is_dec_digit(int c)
+ {
+ return c >= 0 && c <= 255 && std::isdigit((char) c, c_locale);
+ };
+ static bool is_hex_digit(int c)
+ {
+ return c >= 0 && c <= 255 && std::isxdigit((char) c, c_locale);
+ };
+ static bool is_base64_digit(int c) { return c >= 0 && c <= 255 && base64digit[c]; };
+ static bool is_token_char(int c) { return c >= 0 && c <= 255 && tokenchar[c]; };
+ static bool is_alpha(int c)
+ {
+ return c >= 0 && c <= 255 && std::isalpha((char) c, c_locale);
+ };
+
+ /* decvalue(c) is value of c as dec digit */
+ static unsigned char decvalue(int c) { return (c >= 0 && c <= 255) ? values[c][0] : 0; };
+ /* hexvalue(c) is value of c as a hex digit */
+ static unsigned char hexvalue(int c) { return (c >= 0 && c <= 255) ? values[c][1] : 0; };
+ /* base64value(c) is value of c as base64 digit */
+ static unsigned char base64value(int c)
+ {
+ return (c >= 0 && c <= 255) ? values[c][2] : 0;
+ };
+};
+
+class sexp_string_t;
+class sexp_list_t;
+
+class sexp_output_stream_t;
+class sexp_input_stream_t;
+
+/*
+ * SEXP simple string
+ */
+
+typedef uint8_t octet_t;
+
+class sexp_simple_string_t : public std::basic_string<octet_t>, private sexp_char_defs_t {
+ public:
+ sexp_simple_string_t(void) = default;
+ sexp_simple_string_t(const octet_t *dt) : std::basic_string<octet_t>{dt} {}
+ sexp_simple_string_t(const octet_t *bt, size_t ln) : std::basic_string<octet_t>{bt, ln} {}
+ sexp_simple_string_t &append(int c)
+ {
+ (*this) += (octet_t)(c & 0xFF);
+ return *this;
+ }
+ // Returns length for printing simple string as a token
+ size_t advanced_length_token(void) const { return length(); }
+ // Returns length for printing simple string as a base64 string
+ size_t advanced_length_base64(void) const { return (2 + 4 * ((length() + 2) / 3)); }
+ // Returns length for printing simple string ss in quoted-string mode
+ size_t advanced_length_quoted(void) const { return (1 + length() + 1); }
+ // Returns length for printing simple string ss in hexadecimal mode
+ size_t advanced_length_hexadecimal(void) const { return (1 + 2 * length() + 1); }
+ size_t advanced_length(sexp_output_stream_t *os) const;
+
+ sexp_output_stream_t *print_canonical_verbatim(sexp_output_stream_t *os) const;
+ sexp_output_stream_t *print_advanced(sexp_output_stream_t *os) const;
+ sexp_output_stream_t *print_token(sexp_output_stream_t *os) const;
+ sexp_output_stream_t *print_quoted(sexp_output_stream_t *os) const;
+ sexp_output_stream_t *print_hexadecimal(sexp_output_stream_t *os) const;
+ sexp_output_stream_t *print_base64(sexp_output_stream_t *os) const;
+
+ bool can_print_as_quoted_string(void) const;
+ bool can_print_as_token(const sexp_output_stream_t *os) const;
+
+ bool operator==(const char *right) const noexcept
+ {
+ return length() == std::strlen(right) && std::memcmp(data(), right, length()) == 0;
+ }
+
+ bool operator!=(const char *right) const noexcept
+ {
+ return length() != std::strlen(right) || std::memcmp(data(), right, length()) != 0;
+ }
+
+ unsigned as_unsigned() const noexcept
+ {
+ return empty() ? std::numeric_limits<uint32_t>::max() :
+ (unsigned) atoi(reinterpret_cast<const char *>(c_str()));
+ }
+};
+
+inline bool operator==(const sexp_simple_string_t *left, const std::string &right) noexcept
+{
+ return *left == right.c_str();
+}
+
+inline bool operator!=(const sexp_simple_string_t *left, const std::string &right) noexcept
+{
+ return *left != right.c_str();
+}
+
+/*
+ * SEXP object
+ */
+
+class sexp_object_t {
+ public:
+ virtual ~sexp_object_t(){};
+
+ virtual sexp_output_stream_t *print_canonical(sexp_output_stream_t *os) const = 0;
+ virtual sexp_output_stream_t *print_advanced(sexp_output_stream_t *os) const;
+ virtual size_t advanced_length(sexp_output_stream_t *os) const = 0;
+
+ virtual sexp_list_t * sexp_list_view(void) noexcept { return nullptr; }
+ virtual sexp_string_t *sexp_string_view(void) noexcept { return nullptr; }
+ virtual bool is_sexp_list(void) const noexcept { return false; }
+ virtual bool is_sexp_string(void) const noexcept { return false; }
+
+ virtual const sexp_list_t *sexp_list_at(
+ std::vector<std::shared_ptr<sexp_object_t>>::size_type pos) const noexcept
+ {
+ return nullptr;
+ }
+ virtual const sexp_string_t *sexp_string_at(
+ std::vector<std::shared_ptr<sexp_object_t>>::size_type pos) const noexcept
+ {
+ return nullptr;
+ }
+ virtual const sexp_simple_string_t *sexp_simple_string_at(
+ std::vector<std::shared_ptr<sexp_object_t>>::size_type pos) const noexcept
+ {
+ return nullptr;
+ }
+ virtual bool operator==(const char *right) const noexcept { return false; }
+ virtual bool operator!=(const char *right) const noexcept { return true; }
+ virtual unsigned as_unsigned() const noexcept
+ {
+ return std::numeric_limits<uint32_t>::max();
+ }
+};
+
+/*
+ * SEXP string
+ */
+
+class sexp_string_t : public sexp_object_t {
+ protected:
+ bool with_presentation_hint;
+ sexp_simple_string_t presentation_hint;
+ sexp_simple_string_t data_string;
+
+ public:
+ sexp_string_t(const octet_t *dt) : with_presentation_hint(false), data_string(dt) {}
+ sexp_string_t(const octet_t *bt, size_t ln)
+ : with_presentation_hint(false), data_string(bt, ln)
+ {
+ }
+ sexp_string_t(const std::string &str)
+ : with_presentation_hint(false),
+ data_string(reinterpret_cast<const octet_t *>(str.data()))
+ {
+ }
+ sexp_string_t(void) : with_presentation_hint(false) {}
+ sexp_string_t(sexp_input_stream_t *sis) { parse(sis); };
+
+ const bool has_presentation_hint(void) const noexcept { return with_presentation_hint; }
+ const sexp_simple_string_t &get_string(void) const noexcept { return data_string; }
+ const sexp_simple_string_t &set_string(const sexp_simple_string_t &ss)
+ {
+ return data_string = ss;
+ }
+ const sexp_simple_string_t &get_presentation_hint(void) const noexcept
+ {
+ return presentation_hint;
+ }
+ const sexp_simple_string_t &set_presentation_hint(const sexp_simple_string_t &ph)
+ {
+ with_presentation_hint = true;
+ return presentation_hint = ph;
+ }
+
+ virtual sexp_output_stream_t *print_canonical(sexp_output_stream_t *os) const;
+ virtual sexp_output_stream_t *print_advanced(sexp_output_stream_t *os) const;
+ virtual size_t advanced_length(sexp_output_stream_t *os) const;
+
+ virtual sexp_string_t *sexp_string_view(void) noexcept { return this; }
+ virtual bool is_sexp_string(void) const noexcept { return true; }
+
+ virtual bool operator==(const char *right) const noexcept { return data_string == right; }
+ virtual bool operator!=(const char *right) const noexcept { return data_string != right; }
+
+ void parse(sexp_input_stream_t *sis);
+ virtual unsigned as_unsigned() const noexcept { return data_string.as_unsigned(); }
+};
+
+inline bool operator==(const sexp_string_t *left, const std::string &right) noexcept
+{
+ return *left == right.c_str();
+}
+
+inline bool operator!=(const sexp_string_t *left, const std::string &right) noexcept
+{
+ return *left != right.c_str();
+}
+
+/*
+ * SEXP list
+ */
+
+class sexp_list_t : public sexp_object_t, public std::vector<std::shared_ptr<sexp_object_t>> {
+ public:
+ virtual ~sexp_list_t() {}
+
+ virtual sexp_output_stream_t *print_canonical(sexp_output_stream_t *os) const;
+ virtual sexp_output_stream_t *print_advanced(sexp_output_stream_t *os) const;
+ virtual size_t advanced_length(sexp_output_stream_t *os) const;
+
+ virtual sexp_list_t *sexp_list_view(void) noexcept { return this; }
+ virtual bool is_sexp_list(void) const noexcept { return true; }
+
+ virtual const sexp_list_t *sexp_list_at(size_type pos) const noexcept
+ {
+ return pos < size() ? (*at(pos)).sexp_list_view() : nullptr;
+ }
+ virtual const sexp_string_t *sexp_string_at(size_type pos) const noexcept
+ {
+ return pos < size() ? (*at(pos)).sexp_string_view() : nullptr;
+ }
+ const sexp_simple_string_t *sexp_simple_string_at(size_type pos) const noexcept
+ {
+ auto s = sexp_string_at(pos);
+ return s != nullptr ? &s->get_string() : nullptr;
+ }
+
+ void parse(sexp_input_stream_t *sis);
+};
+
+/*
+ * SEXP input stream
+ */
+
+class sexp_input_stream_t : public sexp_char_defs_t {
+ protected:
+ std::istream *input_file;
+ uint32_t byte_size; /* 4 or 6 or 8 == currently scanning mode */
+ int next_char; /* character currently being scanned */
+ uint32_t bits; /* Bits waiting to be used */
+ uint32_t n_bits; /* number of such bits waiting to be used */
+ int count; /* number of 8-bit characters output by get_char */
+ size_t depth; /* current depth of nested SEXP lists */
+ size_t max_depth; /* maximum allowed depth of nested SEXP lists, 0 if no limit */
+
+ virtual int read_char(void);
+
+ public:
+ sexp_input_stream_t(std::istream *i, size_t max_depth = 0);
+ virtual ~sexp_input_stream_t() = default;
+ sexp_input_stream_t *set_input(std::istream *i, size_t max_depth = 0);
+ sexp_input_stream_t *set_byte_size(uint32_t new_byte_size);
+ uint32_t get_byte_size(void) { return byte_size; }
+ sexp_input_stream_t *get_char(void);
+ sexp_input_stream_t *skip_white_space(void);
+ sexp_input_stream_t *skip_char(int c);
+ sexp_input_stream_t *increase_depth(void)
+ {
+ if (max_depth != 0 && ++depth > max_depth)
+ sexp_error(sexp_exception_t::error,
+ "Maximum allowed SEXP list depth (%u) is exceeded",
+ max_depth,
+ 0,
+ count);
+ return this;
+ }
+ sexp_input_stream_t *decrease_depth(void)
+ {
+ depth--;
+ return this;
+ }
+
+ std::shared_ptr<sexp_object_t> scan_to_eof();
+ std::shared_ptr<sexp_object_t> scan_object(void);
+ std::shared_ptr<sexp_string_t> scan_string(void);
+ std::shared_ptr<sexp_list_t> scan_list(void);
+ sexp_simple_string_t scan_simple_string(void);
+ void scan_token(sexp_simple_string_t &ss);
+ void scan_verbatim_string(sexp_simple_string_t &ss, uint32_t length);
+ void scan_quoted_string(sexp_simple_string_t &ss, uint32_t length);
+ void scan_hexadecimal_string(sexp_simple_string_t &ss, uint32_t length);
+ void scan_base64_string(sexp_simple_string_t &ss, uint32_t length);
+ uint32_t scan_decimal_string(void);
+
+ int get_next_char(void) const { return next_char; }
+ int set_next_char(int c) { return next_char = c; }
+};
+
+/*
+ * SEXP output stream
+ */
+
+class sexp_output_stream_t {
+ public:
+ const uint32_t default_line_length = 75;
+ enum sexp_print_mode { /* PRINTING MODES */
+ canonical = 1, /* standard for hashing and tranmission */
+ base64 = 2, /* base64 version of canonical */
+ advanced = 3 /* pretty-printed */
+ };
+
+ protected:
+ std::ostream * output_file;
+ uint32_t base64_count; /* number of hex or base64 chars printed this region */
+ uint32_t byte_size; /* 4 or 6 or 8 depending on output mode */
+ uint32_t bits; /* bits waiting to go out */
+ uint32_t n_bits; /* number of bits waiting to go out */
+ sexp_print_mode mode; /* base64, advanced, or canonical */
+ uint32_t column; /* column where next character will go */
+ uint32_t max_column; /* max usable column, or 0 if no maximum */
+ uint32_t indent; /* current indentation level (starts at 0) */
+ public:
+ sexp_output_stream_t(std::ostream *o);
+ sexp_output_stream_t *set_output(std::ostream *o);
+ sexp_output_stream_t *put_char(int c); /* output a character */
+ sexp_output_stream_t *new_line(sexp_print_mode mode); /* go to next line (and indent) */
+ sexp_output_stream_t *var_put_char(int c);
+ sexp_output_stream_t *flush(void);
+ sexp_output_stream_t *print_decimal(uint64_t n);
+
+ sexp_output_stream_t *change_output_byte_size(int newByteSize, sexp_print_mode mode);
+
+ sexp_output_stream_t *print_canonical(const std::shared_ptr<sexp_object_t> &obj)
+ {
+ return obj->print_canonical(this);
+ }
+ sexp_output_stream_t *print_advanced(const std::shared_ptr<sexp_object_t> &obj)
+ {
+ return obj->print_advanced(this);
+ };
+ sexp_output_stream_t *print_base64(const std::shared_ptr<sexp_object_t> &obj);
+ sexp_output_stream_t *print_canonical(const sexp_simple_string_t *ss)
+ {
+ return ss->print_canonical_verbatim(this);
+ }
+ sexp_output_stream_t *print_advanced(const sexp_simple_string_t *ss)
+ {
+ return ss->print_advanced(this);
+ };
+
+ uint32_t get_byte_size(void) const { return byte_size; }
+ uint32_t get_column(void) const { return column; }
+ sexp_output_stream_t *reset_column(void)
+ {
+ column = 0;
+ return this;
+ }
+ uint32_t get_max_column(void) const { return max_column; }
+ sexp_output_stream_t *set_max_column(uint32_t mc)
+ {
+ max_column = mc;
+ return this;
+ }
+ sexp_output_stream_t *inc_indent(void)
+ {
+ ++indent;
+ return this;
+ }
+ sexp_output_stream_t *dec_indent(void)
+ {
+ --indent;
+ return this;
+ }
+};
+
+} // namespace sexp
diff --git a/comm/third_party/rnp/src/libsexp/src/ext-key-format.cpp b/comm/third_party/rnp/src/libsexp/src/ext-key-format.cpp new file mode 100644 index 0000000000..a0c0d04b73 --- /dev/null +++ b/comm/third_party/rnp/src/libsexp/src/ext-key-format.cpp @@ -0,0 +1,314 @@ +/** + * + * Copyright (c) 2022, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * This file is a part of RNP sexp library + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <sexp/ext-key-format.h> + +using namespace sexp; + +namespace ext_key_format { + +void ext_key_error( + sexp_exception_t::severity level, const char *msg, size_t c1, size_t c2, int pos) +{ + char tmp[256]; + sexp_exception_t::severity l = (sexp_exception_t::severity) level; + snprintf(tmp, sizeof(tmp) / sizeof(tmp[0]), msg, c1, c2); + if (sexp_exception_t::shall_throw(l)) + throw sexp_exception_t(tmp, l, pos, "EXTENDED KEY FORMAT"); + if (sexp_exception_t::is_interactive()) { + std::cout.flush() << std::endl + << "*** " + << sexp_exception_t::format("EXTENDED KEY FORMAT", tmp, l, pos) + << " ***" << std::endl; + } +} + +// Valid characters are all ASCII letters, numbers and the hyphen. +// true if allowed in the name field +const bool ext_key_input_stream_t::namechar[256] = { + /* 0x00 */ false, /* 0x01 */ false, /* 0x02 */ false, + /* 0x03 */ false, /* 0x04 */ false, /* 0x05 */ false, + /* 0x06 */ false, /* 0x07 */ false, /* 0x08 */ false, + /* 0x09 */ false, /* 0x0a */ false, /* 0x0b */ false, + /* 0x0c */ false, /* 0x0d */ false, /* 0x0e */ false, + /* 0x0f */ false, /* 0x10 */ false, /* 0x11 */ false, + /* 0x12 */ false, /* 0x13 */ false, /* 0x14 */ false, + /* 0x15 */ false, /* 0x16 */ false, /* 0x17 */ false, + /* 0x18 */ false, /* 0x19 */ false, /* 0x1a */ false, + /* 0x1b */ false, /* 0x1c */ false, /* 0x1d */ false, + /* 0x1e */ false, /* 0x1f */ false, /* 0x20 */ false, + /* 0x21 ! */ false, /* 0x22 " */ false, /* 0x23 # */ false, + /* 0x24 $ */ false, /* 0x25 % */ false, /* 0x26 & */ false, + /* 0x27 ' */ false, /* 0x28 ( */ false, /* 0x29 ) */ false, + /* 0x2a * */ false, /* 0x2b + */ false, /* 0x2c , */ false, + /* 0x2d - */ true, /* 0x2e . */ false, /* 0x2f / */ false, + /* 0x30 0 */ true, /* 0x31 1 */ true, /* 0x32 2 */ true, + /* 0x33 3 */ true, /* 0x34 4 */ true, /* 0x35 5 */ true, + /* 0x36 6 */ true, /* 0x37 7 */ true, /* 0x38 8 */ true, + /* 0x39 9 */ true, /* 0x3a : */ false, /* 0x3b ; */ false, + /* 0x3c < */ false, /* 0x3d = */ false, /* 0x3e > */ false, + /* 0x3f ? */ false, /* 0x40 @ */ false, /* 0x41 A */ true, + /* 0x42 B */ true, /* 0x43 C */ true, /* 0x44 D */ true, + /* 0x45 E */ true, /* 0x46 F */ true, /* 0x47 G */ true, + /* 0x48 H */ true, /* 0x49 I */ true, /* 0x4a J */ true, + /* 0x4b K */ true, /* 0x4c L */ true, /* 0x4d M */ true, + /* 0x4e N */ true, /* 0x4f O */ true, /* 0x50 P */ true, + /* 0x51 Q */ true, /* 0x52 R */ true, /* 0x53 S */ true, + /* 0x54 T */ true, /* 0x55 U */ true, /* 0x56 V */ true, + /* 0x57 W */ true, /* 0x58 X */ true, /* 0x59 Y */ true, + /* 0x5a Z */ true, /* 0x5b [ */ false, /* 0x5c \ */ false, + /* 0x5d ] */ false, /* 0x5e ^ */ false, /* 0x5f _ */ false, + /* 0x60 ` */ false, /* 0x61 a */ true, /* 0x62 b */ true, + /* 0x63 c */ true, /* 0x64 d */ true, /* 0x65 e */ true, + /* 0x66 f */ true, /* 0x67 g */ true, /* 0x68 h */ true, + /* 0x69 i */ true, /* 0x6a j */ true, /* 0x6b k */ true, + /* 0x6c l */ true, /* 0x6d m */ true, /* 0x6e n */ true, + /* 0x6f o */ true, /* 0x70 p */ true, /* 0x71 q */ true, + /* 0x72 r */ true, /* 0x73 s */ true, /* 0x74 t */ true, + /* 0x75 u */ true, /* 0x76 v */ true, /* 0x77 w */ true, + /* 0x78 x */ true, /* 0x79 y */ true, /* 0x7a z */ true, + /* 0x7b { */ false, /* 0x7c | */ false, /* 0x7d } */ false, + /* 0x7e ~ */ false, /* 0x7f */ false, /* 0x80 */ false, + /* 0x81 */ false, /* 0x82 */ false, /* 0x83 */ false, + /* 0x84 */ false, /* 0x85 */ false, /* 0x86 */ false, + /* 0x87 */ false, /* 0x88 */ false, /* 0x89 */ false, + /* 0x8a */ false, /* 0x8b */ false, /* 0x8c */ false, + /* 0x8d */ false, /* 0x8e */ false, /* 0x8f */ false, + /* 0x90 */ false, /* 0x91 */ false, /* 0x92 */ false, + /* 0x93 */ false, /* 0x94 */ false, /* 0x95 */ false, + /* 0x96 */ false, /* 0x97 */ false, /* 0x98 */ false, + /* 0x99 */ false, /* 0x9a */ false, /* 0x9b */ false, + /* 0x9c */ false, /* 0x9d */ false, /* 0x9e */ false, + /* 0x9f */ false, /* 0xa0 */ false, /* 0xa1 */ false, + /* 0xa2 */ false, /* 0xa3 */ false, /* 0xa4 */ false, + /* 0xa5 */ false, /* 0xa6 */ false, /* 0xa7 */ false, + /* 0xa8 */ false, /* 0xa9 */ false, /* 0xaa */ false, + /* 0xab */ false, /* 0xac */ false, /* 0xad */ false, + /* 0xae */ false, /* 0xaf */ false, /* 0xb0 */ false, + /* 0xb1 */ false, /* 0xb2 */ false, /* 0xb3 */ false, + /* 0xb4 */ false, /* 0xb5 */ false, /* 0xb6 */ false, + /* 0xb7 */ false, /* 0xb8 */ false, /* 0xb9 */ false, + /* 0xba */ false, /* 0xbb */ false, /* 0xbc */ false, + /* 0xbd */ false, /* 0xbe */ false, /* 0xbf */ false, + /* 0xc0 */ false, /* 0xc1 */ false, /* 0xc2 */ false, + /* 0xc3 */ false, /* 0xc4 */ false, /* 0xc5 */ false, + /* 0xc6 */ false, /* 0xc7 */ false, /* 0xc8 */ false, + /* 0xc9 */ false, /* 0xca */ false, /* 0xcb */ false, + /* 0xcc */ false, /* 0xcd */ false, /* 0xce */ false, + /* 0xcf */ false, /* 0xd0 */ false, /* 0xd1 */ false, + /* 0xd2 */ false, /* 0xd3 */ false, /* 0xd4 */ false, + /* 0xd5 */ false, /* 0xd6 */ false, /* 0xd7 */ false, + /* 0xd8 */ false, /* 0xd9 */ false, /* 0xda */ false, + /* 0xdb */ false, /* 0xdc */ false, /* 0xdd */ false, + /* 0xde */ false, /* 0xdf */ false, /* 0xe0 */ false, + /* 0xe1 */ false, /* 0xe2 */ false, /* 0xe3 */ false, + /* 0xe4 */ false, /* 0xe5 */ false, /* 0xe6 */ false, + /* 0xe7 */ false, /* 0xe8 */ false, /* 0xe9 */ false, + /* 0xea */ false, /* 0xeb */ false, /* 0xec */ false, + /* 0xed */ false, /* 0xee */ false, /* 0xef */ false, + /* 0xf0 */ false, /* 0xf1 */ false, /* 0xf2 */ false, + /* 0xf3 */ false, /* 0xf4 */ false, /* 0xf5 */ false, + /* 0xf6 */ false, /* 0xf7 */ false, /* 0xf8 */ false, + /* 0xf9 */ false, /* 0xfa */ false, /* 0xfb */ false, + /* 0xfc */ false, /* 0xfd */ false, /* 0xfe */ false}; + +/* + * ext_key_input_stream_t::skip_line + */ +int ext_key_input_stream_t::skip_line(void) +{ + int c; + do { + c = input_file->get(); + } while (!is_newline_char(c) && c != EOF); + return c; +} + +/* + * ext_key_input_stream_t::read_char + */ +int ext_key_input_stream_t::read_char(void) +{ + int lookahead_1 = input_file->get(); + count++; + if (is_scanning_value && is_newline_char(lookahead_1)) { + while (true) { + int lookahead_2 = input_file->peek(); + if (lookahead_1 == '\r' && lookahead_2 == '\n') { + lookahead_1 = input_file->get(); + count++; + lookahead_2 = input_file->peek(); + } + if (lookahead_2 == ' ') { + input_file->get(); + count++; + lookahead_2 = input_file->peek(); + if (lookahead_2 == '#') { + lookahead_1 = skip_line(); + continue; + } + if (is_newline_char(lookahead_2)) { + lookahead_1 = lookahead_2; + continue; + } + lookahead_1 = input_file->get(); + count++; + } + return lookahead_1; + } + } + return lookahead_1; +} + +/* + * ext_key_input_stream_t::scan_name + * A name must start with a letter and end with a colon. Valid characters are all ASCII + * letters, numbers and the hyphen. Comparison of names is done case insensitively. Names may + * be used several times to represent an array of values. Note that the name “Key” is special + * in that it is madandory must occur only once. + */ + +std::string ext_key_input_stream_t::scan_name(int c) +{ + std::string name; + if (!is_alpha(c)) { + ext_key_error(sexp_exception_t::error, + isprint(next_char) ? + "unexpected character '%c' (0x%x) found starting a name field" : + "unexpected character '0x%x' found starting a name field", + c, + c, + count); + } else { + name += (char) c; + c = read_char(); + while (c != ':') { + if (c == EOF) { + ext_key_error(sexp_exception_t::error, "unexpected end of file", 0, 0, count); + } + if (is_newline_char(c)) { + ext_key_error(sexp_exception_t::error, "unexpected end of line", 0, 0, count); + } + if (!is_namechar(c)) { + ext_key_error(sexp_exception_t::error, + isprint(next_char) ? + "unexpected character '%c' (0x%x) found in a name field" : + "unexpected character '0x%x' found in a name field", + c, + c, + count); + } + name += (int) c; + c = read_char(); + } + } + return name; +} + +/* + * ext_key_input_stream_t::scan_value + * Values are UTF-8 encoded strings. Values can be wrapped at any point, and continued in + * the next line indicated by leading whitespace. A continuation line with one leading space + * does not introduce a blank so that the lines can be effectively concatenated. A blank + * line as part of a continuation line encodes a newline. + */ +std::string ext_key_input_stream_t::scan_value(void) +{ + std::string value; + int c; + do { + c = read_char(); + } while (is_white_space(c)); + while (c != EOF && !is_newline_char(c)) { + value += c; + c = read_char(); + } + return value; +} + +/* + * ext_key_input_stream_t::scan + * GnuPG 2.3+ uses a new format to store private keys that is both more flexible and easier to + * read and edit by human beings. The new format stores name, value-pairs using the common mail + * and http header convention. + */ +void ext_key_input_stream_t::scan(extended_private_key_t &res) +{ + set_byte_size(8); + int c = read_char(); + if (c == '(') { + set_next_char(c); + res.key.parse(this); + has_key = true; + } else { + while (c != EOF) { + // Comparison of names is done case insensitively + std::string name = scan_name(c); + // The name “Key” is special in that it is mandatory and must occur only once. + // The associated value holds the actual S-expression with the cryptographic key. + // The S-expression is formatted using the ‘Advanced Format’ + // (GCRYSEXP_FMT_ADVANCED) that avoids non-printable characters so that the file + // can be easily inspected and edited. + is_scanning_value = true; + if (extended_private_key_t::iequals(name, "key")) { + if (has_key) { + ext_key_error(sexp_exception_t::error, + "'key' field must occur only once", + 0, + 0, + count); + } + do { + c = read_char(); + } while (is_white_space(c)); + set_next_char(c); + res.key.parse(this); + has_key = true; + } else { + std::string value = scan_value(); + res.fields.insert(std::pair<std::string, std::string>{name, value}); + } + c = read_char(); + is_scanning_value = false; + } + } + if (!has_key) { + ext_key_error(sexp_exception_t::error, "missing mandatory 'key' field", 0, 0, count); + } +} + +/* + * extended_private_key_t::parse + */ +void extended_private_key_t::parse(ext_key_input_stream_t &is) +{ + is.scan(*this); +} + +} // namespace ext_key_format
\ No newline at end of file diff --git a/comm/third_party/rnp/src/libsexp/src/sexp-char-defs.cpp b/comm/third_party/rnp/src/libsexp/src/sexp-char-defs.cpp new file mode 100644 index 0000000000..8a727d5737 --- /dev/null +++ b/comm/third_party/rnp/src/libsexp/src/sexp-char-defs.cpp @@ -0,0 +1,351 @@ +/** + * + * Copyright (c) 2022, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * This file is a part of RNP sexp library + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Original copyright + * + * SEXP implementation code sexp-input.c + * Ron Rivest + * 7/21/1997 + */ + +#include <sexp/sexp.h> + +namespace sexp { + +/**************************************/ +/* CHARACTER ROUTINES AND DEFINITIONS */ +/**************************************/ +std::locale sexp_char_defs_t::c_locale{"C"}; + +const unsigned char sexp_char_defs_t::values[256][3] = + {/* values of c as { dec. hex, base64 } digit */ + {/* 0x00 */ 0x00, 0x00, 0x00}, {/* 0x01 */ 0x00, 0x00, 0x00}, + {/* 0x02 */ 0x00, 0x00, 0x00}, {/* 0x03 */ 0x00, 0x00, 0x00}, + {/* 0x04 */ 0x00, 0x00, 0x00}, {/* 0x05 */ 0x00, 0x00, 0x00}, + {/* 0x06 */ 0x00, 0x00, 0x00}, {/* 0x07 */ 0x00, 0x00, 0x00}, + {/* 0x08 */ 0x00, 0x00, 0x00}, {/* 0x09 */ 0x00, 0x00, 0x00}, + {/* 0x0a */ 0x00, 0x00, 0x00}, {/* 0x0b */ 0x00, 0x00, 0x00}, + {/* 0x0c */ 0x00, 0x00, 0x00}, {/* 0x0d */ 0x00, 0x00, 0x00}, + {/* 0x0e */ 0x00, 0x00, 0x00}, {/* 0x0f */ 0x00, 0x00, 0x00}, + {/* 0x10 */ 0x00, 0x00, 0x00}, {/* 0x11 */ 0x00, 0x00, 0x00}, + {/* 0x12 */ 0x00, 0x00, 0x00}, {/* 0x13 */ 0x00, 0x00, 0x00}, + {/* 0x14 */ 0x00, 0x00, 0x00}, {/* 0x15 */ 0x00, 0x00, 0x00}, + {/* 0x16 */ 0x00, 0x00, 0x00}, {/* 0x17 */ 0x00, 0x00, 0x00}, + {/* 0x18 */ 0x00, 0x00, 0x00}, {/* 0x19 */ 0x00, 0x00, 0x00}, + {/* 0x1a */ 0x00, 0x00, 0x00}, {/* 0x1b */ 0x00, 0x00, 0x00}, + {/* 0x1c */ 0x00, 0x00, 0x00}, {/* 0x1d */ 0x00, 0x00, 0x00}, + {/* 0x1e */ 0x00, 0x00, 0x00}, {/* 0x1f */ 0x00, 0x00, 0x00}, + {/* 0x20 */ 0x00, 0x00, 0x00}, {/* 0x21 ! */ 0x00, 0x00, 0x00}, + {/* 0x22 " */ 0x00, 0x00, 0x00}, {/* 0x23 # */ 0x00, 0x00, 0x00}, + {/* 0x24 $ */ 0x00, 0x00, 0x00}, {/* 0x25 % */ 0x00, 0x00, 0x00}, + {/* 0x26 & */ 0x00, 0x00, 0x00}, {/* 0x27 ' */ 0x00, 0x00, 0x00}, + {/* 0x28 ( */ 0x00, 0x00, 0x00}, {/* 0x29 ) */ 0x00, 0x00, 0x00}, + {/* 0x2a * */ 0x00, 0x00, 0x00}, {/* 0x2b + */ 0x00, 0x00, 0x3e}, + {/* 0x2c , */ 0x00, 0x00, 0x00}, {/* 0x2d - */ 0x00, 0x00, 0x00}, + {/* 0x2e . */ 0x00, 0x00, 0x00}, {/* 0x2f / */ 0x00, 0x00, 0x3f}, + {/* 0x30 0 */ 0x00, 0x00, 0x34}, {/* 0x31 1 */ 0x01, 0x01, 0x35}, + {/* 0x32 2 */ 0x02, 0x02, 0x36}, {/* 0x33 3 */ 0x03, 0x03, 0x37}, + {/* 0x34 4 */ 0x04, 0x04, 0x38}, {/* 0x35 5 */ 0x05, 0x05, 0x39}, + {/* 0x36 6 */ 0x06, 0x06, 0x3a}, {/* 0x37 7 */ 0x07, 0x07, 0x3b}, + {/* 0x38 8 */ 0x08, 0x08, 0x3c}, {/* 0x39 9 */ 0x09, 0x09, 0x3d}, + {/* 0x3a : */ 0x00, 0x00, 0x00}, {/* 0x3b ; */ 0x00, 0x00, 0x00}, + {/* 0x3c < */ 0x00, 0x00, 0x00}, {/* 0x3d = */ 0x00, 0x00, 0x00}, + {/* 0x3e > */ 0x00, 0x00, 0x00}, {/* 0x3f ? */ 0x00, 0x00, 0x00}, + {/* 0x40 @ */ 0x00, 0x00, 0x00}, {/* 0x41 A */ 0x00, 0x0a, 0x00}, + {/* 0x42 B */ 0x00, 0x0b, 0x01}, {/* 0x43 C */ 0x00, 0x0c, 0x02}, + {/* 0x44 D */ 0x00, 0x0d, 0x03}, {/* 0x45 E */ 0x00, 0x0e, 0x04}, + {/* 0x46 F */ 0x00, 0x0f, 0x05}, {/* 0x47 G */ 0x00, 0x00, 0x06}, + {/* 0x48 H */ 0x00, 0x00, 0x07}, {/* 0x49 I */ 0x00, 0x00, 0x08}, + {/* 0x4a J */ 0x00, 0x00, 0x09}, {/* 0x4b K */ 0x00, 0x00, 0x0a}, + {/* 0x4c L */ 0x00, 0x00, 0x0b}, {/* 0x4d M */ 0x00, 0x00, 0x0c}, + {/* 0x4e N */ 0x00, 0x00, 0x0d}, {/* 0x4f O */ 0x00, 0x00, 0x0e}, + {/* 0x50 P */ 0x00, 0x00, 0x0f}, {/* 0x51 Q */ 0x00, 0x00, 0x10}, + {/* 0x52 R */ 0x00, 0x00, 0x11}, {/* 0x53 S */ 0x00, 0x00, 0x12}, + {/* 0x54 T */ 0x00, 0x00, 0x13}, {/* 0x55 U */ 0x00, 0x00, 0x14}, + {/* 0x56 V */ 0x00, 0x00, 0x15}, {/* 0x57 W */ 0x00, 0x00, 0x16}, + {/* 0x58 X */ 0x00, 0x00, 0x17}, {/* 0x59 Y */ 0x00, 0x00, 0x18}, + {/* 0x5a Z */ 0x00, 0x00, 0x19}, {/* 0x5b [ */ 0x00, 0x00, 0x00}, + {/* 0x5c \ */ 0x00, 0x00, 0x00}, {/* 0x5d ] */ 0x00, 0x00, 0x00}, + {/* 0x5e ^ */ 0x00, 0x00, 0x00}, {/* 0x5f _ */ 0x00, 0x00, 0x00}, + {/* 0x60 ` */ 0x00, 0x00, 0x00}, {/* 0x61 a */ 0x00, 0x0a, 0x1a}, + {/* 0x62 b */ 0x00, 0x0b, 0x1b}, {/* 0x63 c */ 0x00, 0x0c, 0x1c}, + {/* 0x64 d */ 0x00, 0x0d, 0x1d}, {/* 0x65 e */ 0x00, 0x0e, 0x1e}, + {/* 0x66 f */ 0x00, 0x0f, 0x1f}, {/* 0x67 g */ 0x00, 0x00, 0x20}, + {/* 0x68 h */ 0x00, 0x00, 0x21}, {/* 0x69 i */ 0x00, 0x00, 0x22}, + {/* 0x6a j */ 0x00, 0x00, 0x23}, {/* 0x6b k */ 0x00, 0x00, 0x24}, + {/* 0x6c l */ 0x00, 0x00, 0x25}, {/* 0x6d m */ 0x00, 0x00, 0x26}, + {/* 0x6e n */ 0x00, 0x00, 0x27}, {/* 0x6f o */ 0x00, 0x00, 0x28}, + {/* 0x70 p */ 0x00, 0x00, 0x29}, {/* 0x71 q */ 0x00, 0x00, 0x2a}, + {/* 0x72 r */ 0x00, 0x00, 0x2b}, {/* 0x73 s */ 0x00, 0x00, 0x2c}, + {/* 0x74 t */ 0x00, 0x00, 0x2d}, {/* 0x75 u */ 0x00, 0x00, 0x2e}, + {/* 0x76 v */ 0x00, 0x00, 0x2f}, {/* 0x77 w */ 0x00, 0x00, 0x30}, + {/* 0x78 x */ 0x00, 0x00, 0x31}, {/* 0x79 y */ 0x00, 0x00, 0x32}, + {/* 0x7a z */ 0x00, 0x00, 0x33}, {/* 0x7b { */ 0x00, 0x00, 0x00}, + {/* 0x7c | */ 0x00, 0x00, 0x00}, {/* 0x7d } */ 0x00, 0x00, 0x00}, + {/* 0x7e ~ */ 0x00, 0x00, 0x00}, {/* 0x7f */ 0x00, 0x00, 0x00}, + {/* 0x80 */ 0x00, 0x00, 0x00}, {/* 0x81 */ 0x00, 0x00, 0x00}, + {/* 0x82 */ 0x00, 0x00, 0x00}, {/* 0x83 */ 0x00, 0x00, 0x00}, + {/* 0x84 */ 0x00, 0x00, 0x00}, {/* 0x85 */ 0x00, 0x00, 0x00}, + {/* 0x86 */ 0x00, 0x00, 0x00}, {/* 0x87 */ 0x00, 0x00, 0x00}, + {/* 0x88 */ 0x00, 0x00, 0x00}, {/* 0x89 */ 0x00, 0x00, 0x00}, + {/* 0x8a */ 0x00, 0x00, 0x00}, {/* 0x8b */ 0x00, 0x00, 0x00}, + {/* 0x8c */ 0x00, 0x00, 0x00}, {/* 0x8d */ 0x00, 0x00, 0x00}, + {/* 0x8e */ 0x00, 0x00, 0x00}, {/* 0x8f */ 0x00, 0x00, 0x00}, + {/* 0x90 */ 0x00, 0x00, 0x00}, {/* 0x91 */ 0x00, 0x00, 0x00}, + {/* 0x92 */ 0x00, 0x00, 0x00}, {/* 0x93 */ 0x00, 0x00, 0x00}, + {/* 0x94 */ 0x00, 0x00, 0x00}, {/* 0x95 */ 0x00, 0x00, 0x00}, + {/* 0x96 */ 0x00, 0x00, 0x00}, {/* 0x97 */ 0x00, 0x00, 0x00}, + {/* 0x98 */ 0x00, 0x00, 0x00}, {/* 0x99 */ 0x00, 0x00, 0x00}, + {/* 0x9a */ 0x00, 0x00, 0x00}, {/* 0x9b */ 0x00, 0x00, 0x00}, + {/* 0x9c */ 0x00, 0x00, 0x00}, {/* 0x9d */ 0x00, 0x00, 0x00}, + {/* 0x9e */ 0x00, 0x00, 0x00}, {/* 0x9f */ 0x00, 0x00, 0x00}, + {/* 0xa0 */ 0x00, 0x00, 0x00}, {/* 0xa1 */ 0x00, 0x00, 0x00}, + {/* 0xa2 */ 0x00, 0x00, 0x00}, {/* 0xa3 */ 0x00, 0x00, 0x00}, + {/* 0xa4 */ 0x00, 0x00, 0x00}, {/* 0xa5 */ 0x00, 0x00, 0x00}, + {/* 0xa6 */ 0x00, 0x00, 0x00}, {/* 0xa7 */ 0x00, 0x00, 0x00}, + {/* 0xa8 */ 0x00, 0x00, 0x00}, {/* 0xa9 */ 0x00, 0x00, 0x00}, + {/* 0xaa */ 0x00, 0x00, 0x00}, {/* 0xab */ 0x00, 0x00, 0x00}, + {/* 0xac */ 0x00, 0x00, 0x00}, {/* 0xad */ 0x00, 0x00, 0x00}, + {/* 0xae */ 0x00, 0x00, 0x00}, {/* 0xaf */ 0x00, 0x00, 0x00}, + {/* 0xb0 */ 0x00, 0x00, 0x00}, {/* 0xb1 */ 0x00, 0x00, 0x00}, + {/* 0xb2 */ 0x00, 0x00, 0x00}, {/* 0xb3 */ 0x00, 0x00, 0x00}, + {/* 0xb4 */ 0x00, 0x00, 0x00}, {/* 0xb5 */ 0x00, 0x00, 0x00}, + {/* 0xb6 */ 0x00, 0x00, 0x00}, {/* 0xb7 */ 0x00, 0x00, 0x00}, + {/* 0xb8 */ 0x00, 0x00, 0x00}, {/* 0xb9 */ 0x00, 0x00, 0x00}, + {/* 0xba */ 0x00, 0x00, 0x00}, {/* 0xbb */ 0x00, 0x00, 0x00}, + {/* 0xbc */ 0x00, 0x00, 0x00}, {/* 0xbd */ 0x00, 0x00, 0x00}, + {/* 0xbe */ 0x00, 0x00, 0x00}, {/* 0xbf */ 0x00, 0x00, 0x00}, + {/* 0xc0 */ 0x00, 0x00, 0x00}, {/* 0xc1 */ 0x00, 0x00, 0x00}, + {/* 0xc2 */ 0x00, 0x00, 0x00}, {/* 0xc3 */ 0x00, 0x00, 0x00}, + {/* 0xc4 */ 0x00, 0x00, 0x00}, {/* 0xc5 */ 0x00, 0x00, 0x00}, + {/* 0xc6 */ 0x00, 0x00, 0x00}, {/* 0xc7 */ 0x00, 0x00, 0x00}, + {/* 0xc8 */ 0x00, 0x00, 0x00}, {/* 0xc9 */ 0x00, 0x00, 0x00}, + {/* 0xca */ 0x00, 0x00, 0x00}, {/* 0xcb */ 0x00, 0x00, 0x00}, + {/* 0xcc */ 0x00, 0x00, 0x00}, {/* 0xcd */ 0x00, 0x00, 0x00}, + {/* 0xce */ 0x00, 0x00, 0x00}, {/* 0xcf */ 0x00, 0x00, 0x00}, + {/* 0xd0 */ 0x00, 0x00, 0x00}, {/* 0xd1 */ 0x00, 0x00, 0x00}, + {/* 0xd2 */ 0x00, 0x00, 0x00}, {/* 0xd3 */ 0x00, 0x00, 0x00}, + {/* 0xd4 */ 0x00, 0x00, 0x00}, {/* 0xd5 */ 0x00, 0x00, 0x00}, + {/* 0xd6 */ 0x00, 0x00, 0x00}, {/* 0xd7 */ 0x00, 0x00, 0x00}, + {/* 0xd8 */ 0x00, 0x00, 0x00}, {/* 0xd9 */ 0x00, 0x00, 0x00}, + {/* 0xda */ 0x00, 0x00, 0x00}, {/* 0xdb */ 0x00, 0x00, 0x00}, + {/* 0xdc */ 0x00, 0x00, 0x00}, {/* 0xdd */ 0x00, 0x00, 0x00}, + {/* 0xde */ 0x00, 0x00, 0x00}, {/* 0xdf */ 0x00, 0x00, 0x00}, + {/* 0xe0 */ 0x00, 0x00, 0x00}, {/* 0xe1 */ 0x00, 0x00, 0x00}, + {/* 0xe2 */ 0x00, 0x00, 0x00}, {/* 0xe3 */ 0x00, 0x00, 0x00}, + {/* 0xe4 */ 0x00, 0x00, 0x00}, {/* 0xe5 */ 0x00, 0x00, 0x00}, + {/* 0xe6 */ 0x00, 0x00, 0x00}, {/* 0xe7 */ 0x00, 0x00, 0x00}, + {/* 0xe8 */ 0x00, 0x00, 0x00}, {/* 0xe9 */ 0x00, 0x00, 0x00}, + {/* 0xea */ 0x00, 0x00, 0x00}, {/* 0xeb */ 0x00, 0x00, 0x00}, + {/* 0xec */ 0x00, 0x00, 0x00}, {/* 0xed */ 0x00, 0x00, 0x00}, + {/* 0xee */ 0x00, 0x00, 0x00}, {/* 0xef */ 0x00, 0x00, 0x00}, + {/* 0xf0 */ 0x00, 0x00, 0x00}, {/* 0xf1 */ 0x00, 0x00, 0x00}, + {/* 0xf2 */ 0x00, 0x00, 0x00}, {/* 0xf3 */ 0x00, 0x00, 0x00}, + {/* 0xf4 */ 0x00, 0x00, 0x00}, {/* 0xf5 */ 0x00, 0x00, 0x00}, + {/* 0xf6 */ 0x00, 0x00, 0x00}, {/* 0xf7 */ 0x00, 0x00, 0x00}, + {/* 0xf8 */ 0x00, 0x00, 0x00}, {/* 0xf9 */ 0x00, 0x00, 0x00}, + {/* 0xfa */ 0x00, 0x00, 0x00}, {/* 0xfb */ 0x00, 0x00, 0x00}, + {/* 0xfc */ 0x00, 0x00, 0x00}, {/* 0xfd */ 0x00, 0x00, 0x00}, + {/* 0xfe */ 0x00, 0x00, 0x00}, {/* 0xff */ 0x00, 0x00, 0x00}}; + +const bool sexp_char_defs_t::base64digit[256] = + {/* c is base64 digit */ + /* 0x00 */ false, /* 0x01 */ false, /* 0x02 */ false, + /* 0x03 */ false, /* 0x04 */ false, /* 0x05 */ false, + /* 0x06 */ false, /* 0x07 */ false, /* 0x08 */ false, + /* 0x09 */ false, /* 0x0a */ false, /* 0x0b */ false, + /* 0x0c */ false, /* 0x0d */ false, /* 0x0e */ false, + /* 0x0f */ false, /* 0x10 */ false, /* 0x11 */ false, + /* 0x12 */ false, /* 0x13 */ false, /* 0x14 */ false, + /* 0x15 */ false, /* 0x16 */ false, /* 0x17 */ false, + /* 0x18 */ false, /* 0x19 */ false, /* 0x1a */ false, + /* 0x1b */ false, /* 0x1c */ false, /* 0x1d */ false, + /* 0x1e */ false, /* 0x1f */ false, /* 0x20 */ false, + /* 0x21 ! */ false, /* 0x22 " */ false, /* 0x23 # */ false, + /* 0x24 $ */ false, /* 0x25 % */ false, /* 0x26 & */ false, + /* 0x27 ' */ false, /* 0x28 ( */ false, /* 0x29 ) */ false, + /* 0x2a * */ false, /* 0x2b + */ true, /* 0x2c , */ false, + /* 0x2d - */ false, /* 0x2e . */ false, /* 0x2f / */ true, + /* 0x30 0 */ true, /* 0x31 1 */ true, /* 0x32 2 */ true, + /* 0x33 3 */ true, /* 0x34 4 */ true, /* 0x35 5 */ true, + /* 0x36 6 */ true, /* 0x37 7 */ true, /* 0x38 8 */ true, + /* 0x39 9 */ true, /* 0x3a : */ false, /* 0x3b ; */ false, + /* 0x3c < */ false, /* 0x3d = */ false, /* 0x3e > */ false, + /* 0x3f ? */ false, /* 0x40 @ */ false, /* 0x41 A */ true, + /* 0x42 B */ true, /* 0x43 C */ true, /* 0x44 D */ true, + /* 0x45 E */ true, /* 0x46 F */ true, /* 0x47 G */ true, + /* 0x48 H */ true, /* 0x49 I */ true, /* 0x4a J */ true, + /* 0x4b K */ true, /* 0x4c L */ true, /* 0x4d M */ true, + /* 0x4e N */ true, /* 0x4f O */ true, /* 0x50 P */ true, + /* 0x51 Q */ true, /* 0x52 R */ true, /* 0x53 S */ true, + /* 0x54 T */ true, /* 0x55 U */ true, /* 0x56 V */ true, + /* 0x57 W */ true, /* 0x58 X */ true, /* 0x59 Y */ true, + /* 0x5a Z */ true, /* 0x5b [ */ false, /* 0x5c \ */ false, + /* 0x5d ] */ false, /* 0x5e ^ */ false, /* 0x5f _ */ false, + /* 0x60 ` */ false, /* 0x61 a */ true, /* 0x62 b */ true, + /* 0x63 c */ true, /* 0x64 d */ true, /* 0x65 e */ true, + /* 0x66 f */ true, /* 0x67 g */ true, /* 0x68 h */ true, + /* 0x69 i */ true, /* 0x6a j */ true, /* 0x6b k */ true, + /* 0x6c l */ true, /* 0x6d m */ true, /* 0x6e n */ true, + /* 0x6f o */ true, /* 0x70 p */ true, /* 0x71 q */ true, + /* 0x72 r */ true, /* 0x73 s */ true, /* 0x74 t */ true, + /* 0x75 u */ true, /* 0x76 v */ true, /* 0x77 w */ true, + /* 0x78 x */ true, /* 0x79 y */ true, /* 0x7a z */ true, + /* 0x7b { */ false, /* 0x7c | */ false, /* 0x7d } */ false, + /* 0x7e ~ */ false, /* 0x7f */ false, /* 0x80 */ false, + /* 0x81 */ false, /* 0x82 */ false, /* 0x83 */ false, + /* 0x84 */ false, /* 0x85 */ false, /* 0x86 */ false, + /* 0x87 */ false, /* 0x88 */ false, /* 0x89 */ false, + /* 0x8a */ false, /* 0x8b */ false, /* 0x8c */ false, + /* 0x8d */ false, /* 0x8e */ false, /* 0x8f */ false, + /* 0x90 */ false, /* 0x91 */ false, /* 0x92 */ false, + /* 0x93 */ false, /* 0x94 */ false, /* 0x95 */ false, + /* 0x96 */ false, /* 0x97 */ false, /* 0x98 */ false, + /* 0x99 */ false, /* 0x9a */ false, /* 0x9b */ false, + /* 0x9c */ false, /* 0x9d */ false, /* 0x9e */ false, + /* 0x9f */ false, /* 0xa0 */ false, /* 0xa1 */ false, + /* 0xa2 */ false, /* 0xa3 */ false, /* 0xa4 */ false, + /* 0xa5 */ false, /* 0xa6 */ false, /* 0xa7 */ false, + /* 0xa8 */ false, /* 0xa9 */ false, /* 0xaa */ false, + /* 0xab */ false, /* 0xac */ false, /* 0xad */ false, + /* 0xae */ false, /* 0xaf */ false, /* 0xb0 */ false, + /* 0xb1 */ false, /* 0xb2 */ false, /* 0xb3 */ false, + /* 0xb4 */ false, /* 0xb5 */ false, /* 0xb6 */ false, + /* 0xb7 */ false, /* 0xb8 */ false, /* 0xb9 */ false, + /* 0xba */ false, /* 0xbb */ false, /* 0xbc */ false, + /* 0xbd */ false, /* 0xbe */ false, /* 0xbf */ false, + /* 0xc0 */ false, /* 0xc1 */ false, /* 0xc2 */ false, + /* 0xc3 */ false, /* 0xc4 */ false, /* 0xc5 */ false, + /* 0xc6 */ false, /* 0xc7 */ false, /* 0xc8 */ false, + /* 0xc9 */ false, /* 0xca */ false, /* 0xcb */ false, + /* 0xcc */ false, /* 0xcd */ false, /* 0xce */ false, + /* 0xcf */ false, /* 0xd0 */ false, /* 0xd1 */ false, + /* 0xd2 */ false, /* 0xd3 */ false, /* 0xd4 */ false, + /* 0xd5 */ false, /* 0xd6 */ false, /* 0xd7 */ false, + /* 0xd8 */ false, /* 0xd9 */ false, /* 0xda */ false, + /* 0xdb */ false, /* 0xdc */ false, /* 0xdd */ false, + /* 0xde */ false, /* 0xdf */ false, /* 0xe0 */ false, + /* 0xe1 */ false, /* 0xe2 */ false, /* 0xe3 */ false, + /* 0xe4 */ false, /* 0xe5 */ false, /* 0xe6 */ false, + /* 0xe7 */ false, /* 0xe8 */ false, /* 0xe9 */ false, + /* 0xea */ false, /* 0xeb */ false, /* 0xec */ false, + /* 0xed */ false, /* 0xee */ false, /* 0xef */ false, + /* 0xf0 */ false, /* 0xf1 */ false, /* 0xf2 */ false, + /* 0xf3 */ false, /* 0xf4 */ false, /* 0xf5 */ false, + /* 0xf6 */ false, /* 0xf7 */ false, /* 0xf8 */ false, + /* 0xf9 */ false, /* 0xfa */ false, /* 0xfb */ false, + /* 0xfc */ false, /* 0xfd */ false, /* 0xfe */ false}; + +const bool sexp_char_defs_t::tokenchar[256] = + {/* c can be in a token */ + /* 0x00 */ false, /* 0x01 */ false, /* 0x02 */ false, + /* 0x03 */ false, /* 0x04 */ false, /* 0x05 */ false, + /* 0x06 */ false, /* 0x07 */ false, /* 0x08 */ false, + /* 0x09 */ false, /* 0x0a */ false, /* 0x0b */ false, + /* 0x0c */ false, /* 0x0d */ false, /* 0x0e */ false, + /* 0x0f */ false, /* 0x10 */ false, /* 0x11 */ false, + /* 0x12 */ false, /* 0x13 */ false, /* 0x14 */ false, + /* 0x15 */ false, /* 0x16 */ false, /* 0x17 */ false, + /* 0x18 */ false, /* 0x19 */ false, /* 0x1a */ false, + /* 0x1b */ false, /* 0x1c */ false, /* 0x1d */ false, + /* 0x1e */ false, /* 0x1f */ false, /* 0x20 */ false, + /* 0x21 ! */ false, /* 0x22 " */ false, /* 0x23 # */ false, + /* 0x24 $ */ false, /* 0x25 % */ false, /* 0x26 & */ false, + /* 0x27 ' */ false, /* 0x28 ( */ false, /* 0x29 ) */ false, + /* 0x2a * */ true, /* 0x2b + */ true, /* 0x2c , */ false, + /* 0x2d - */ true, /* 0x2e . */ true, /* 0x2f / */ true, + /* 0x30 0 */ true, /* 0x31 1 */ true, /* 0x32 2 */ true, + /* 0x33 3 */ true, /* 0x34 4 */ true, /* 0x35 5 */ true, + /* 0x36 6 */ true, /* 0x37 7 */ true, /* 0x38 8 */ true, + /* 0x39 9 */ true, /* 0x3a : */ true, /* 0x3b ; */ false, + /* 0x3c < */ false, /* 0x3d = */ true, /* 0x3e > */ false, + /* 0x3f ? */ false, /* 0x40 @ */ false, /* 0x41 A */ true, + /* 0x42 B */ true, /* 0x43 C */ true, /* 0x44 D */ true, + /* 0x45 E */ true, /* 0x46 F */ true, /* 0x47 G */ true, + /* 0x48 H */ true, /* 0x49 I */ true, /* 0x4a J */ true, + /* 0x4b K */ true, /* 0x4c L */ true, /* 0x4d M */ true, + /* 0x4e N */ true, /* 0x4f O */ true, /* 0x50 P */ true, + /* 0x51 Q */ true, /* 0x52 R */ true, /* 0x53 S */ true, + /* 0x54 T */ true, /* 0x55 U */ true, /* 0x56 V */ true, + /* 0x57 W */ true, /* 0x58 X */ true, /* 0x59 Y */ true, + /* 0x5a Z */ true, /* 0x5b [ */ false, /* 0x5c \ */ false, + /* 0x5d ] */ false, /* 0x5e ^ */ false, /* 0x5f _ */ true, + /* 0x60 ` */ false, /* 0x61 a */ true, /* 0x62 b */ true, + /* 0x63 c */ true, /* 0x64 d */ true, /* 0x65 e */ true, + /* 0x66 f */ true, /* 0x67 g */ true, /* 0x68 h */ true, + /* 0x69 i */ true, /* 0x6a j */ true, /* 0x6b k */ true, + /* 0x6c l */ true, /* 0x6d m */ true, /* 0x6e n */ true, + /* 0x6f o */ true, /* 0x70 p */ true, /* 0x71 q */ true, + /* 0x72 r */ true, /* 0x73 s */ true, /* 0x74 t */ true, + /* 0x75 u */ true, /* 0x76 v */ true, /* 0x77 w */ true, + /* 0x78 x */ true, /* 0x79 y */ true, /* 0x7a z */ true, + /* 0x7b { */ false, /* 0x7c | */ false, /* 0x7d } */ false, + /* 0x7e ~ */ false, /* 0x7f */ false, /* 0x80 */ false, + /* 0x81 */ false, /* 0x82 */ false, /* 0x83 */ false, + /* 0x84 */ false, /* 0x85 */ false, /* 0x86 */ false, + /* 0x87 */ false, /* 0x88 */ false, /* 0x89 */ false, + /* 0x8a */ false, /* 0x8b */ false, /* 0x8c */ false, + /* 0x8d */ false, /* 0x8e */ false, /* 0x8f */ false, + /* 0x90 */ false, /* 0x91 */ false, /* 0x92 */ false, + /* 0x93 */ false, /* 0x94 */ false, /* 0x95 */ false, + /* 0x96 */ false, /* 0x97 */ false, /* 0x98 */ false, + /* 0x99 */ false, /* 0x9a */ false, /* 0x9b */ false, + /* 0x9c */ false, /* 0x9d */ false, /* 0x9e */ false, + /* 0x9f */ false, /* 0xa0 */ false, /* 0xa1 */ false, + /* 0xa2 */ false, /* 0xa3 */ false, /* 0xa4 */ false, + /* 0xa5 */ false, /* 0xa6 */ false, /* 0xa7 */ false, + /* 0xa8 */ false, /* 0xa9 */ false, /* 0xaa */ false, + /* 0xab */ false, /* 0xac */ false, /* 0xad */ false, + /* 0xae */ false, /* 0xaf */ false, /* 0xb0 */ false, + /* 0xb1 */ false, /* 0xb2 */ false, /* 0xb3 */ false, + /* 0xb4 */ false, /* 0xb5 */ false, /* 0xb6 */ false, + /* 0xb7 */ false, /* 0xb8 */ false, /* 0xb9 */ false, + /* 0xba */ false, /* 0xbb */ false, /* 0xbc */ false, + /* 0xbd */ false, /* 0xbe */ false, /* 0xbf */ false, + /* 0xc0 */ false, /* 0xc1 */ false, /* 0xc2 */ false, + /* 0xc3 */ false, /* 0xc4 */ false, /* 0xc5 */ false, + /* 0xc6 */ false, /* 0xc7 */ false, /* 0xc8 */ false, + /* 0xc9 */ false, /* 0xca */ false, /* 0xcb */ false, + /* 0xcc */ false, /* 0xcd */ false, /* 0xce */ false, + /* 0xcf */ false, /* 0xd0 */ false, /* 0xd1 */ false, + /* 0xd2 */ false, /* 0xd3 */ false, /* 0xd4 */ false, + /* 0xd5 */ false, /* 0xd6 */ false, /* 0xd7 */ false, + /* 0xd8 */ false, /* 0xd9 */ false, /* 0xda */ false, + /* 0xdb */ false, /* 0xdc */ false, /* 0xdd */ false, + /* 0xde */ false, /* 0xdf */ false, /* 0xe0 */ false, + /* 0xe1 */ false, /* 0xe2 */ false, /* 0xe3 */ false, + /* 0xe4 */ false, /* 0xe5 */ false, /* 0xe6 */ false, + /* 0xe7 */ false, /* 0xe8 */ false, /* 0xe9 */ false, + /* 0xea */ false, /* 0xeb */ false, /* 0xec */ false, + /* 0xed */ false, /* 0xee */ false, /* 0xef */ false, + /* 0xf0 */ false, /* 0xf1 */ false, /* 0xf2 */ false, + /* 0xf3 */ false, /* 0xf4 */ false, /* 0xf5 */ false, + /* 0xf6 */ false, /* 0xf7 */ false, /* 0xf8 */ false, + /* 0xf9 */ false, /* 0xfa */ false, /* 0xfb */ false, + /* 0xfc */ false, /* 0xfd */ false, /* 0xfe */ false}; + +} // namespace sexp diff --git a/comm/third_party/rnp/src/libsexp/src/sexp-error.cpp b/comm/third_party/rnp/src/libsexp/src/sexp-error.cpp new file mode 100644 index 0000000000..992b680405 --- /dev/null +++ b/comm/third_party/rnp/src/libsexp/src/sexp-error.cpp @@ -0,0 +1,62 @@ +/** + * + * Copyright (c) 2022, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * This file is a part of RNP sexp library + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <sexp/sexp-error.h> + +namespace sexp { + +sexp_exception_t::severity sexp_exception_t::verbosity = sexp_exception_t::error; +bool sexp_exception_t::interactive = false; + +std::string sexp_exception_t::format(std::string prf, + std::string message, + severity level, + int position) +{ + std::string r = prf + (level == error ? " ERROR: " : " WARNING: ") + message; + if (position >= 0) + r += " at position " + std::to_string(position); + return r; +}; + +void sexp_error( + sexp_exception_t::severity level, const char *msg, size_t c1, size_t c2, int pos) +{ + char tmp[256]; + sexp_exception_t::severity l = (sexp_exception_t::severity) level; + snprintf(tmp, sizeof(tmp) / sizeof(tmp[0]), msg, c1, c2); + if (sexp_exception_t::shall_throw(l)) + throw sexp_exception_t(tmp, l, pos); + if (sexp_exception_t::is_interactive()) { + std::cout.flush() << std::endl + << "*** " << sexp_exception_t::format("SEXP", tmp, l, pos) << " ***" + << std::endl; + } +} +} // namespace sexp
\ No newline at end of file diff --git a/comm/third_party/rnp/src/libsexp/src/sexp-input.cpp b/comm/third_party/rnp/src/libsexp/src/sexp-input.cpp new file mode 100644 index 0000000000..daa8984551 --- /dev/null +++ b/comm/third_party/rnp/src/libsexp/src/sexp-input.cpp @@ -0,0 +1,507 @@ +/** + * + * Copyright (c) 2022-2023, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * This file is a part of RNP sexp library + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Original copyright + * + * SEXP implementation code sexp-input.c + * Ron Rivest + * 7/21/1997 + */ + +#include <sexp/sexp.h> + +namespace sexp { + +/* + * newSexpInputStream() + * Creates and initializes a new sexp_input_stream_t object. + * (Prefixes stream with one blank, and initializes stream + * so that it reads from standard input.) + */ +std::istream *input_file; +uint32_t byte_size; /* 4 or 6 or 8 == currently scanning mode */ +int next_char; /* character currently being scanned */ +uint32_t bits; /* Bits waiting to be used */ +uint32_t n_bits; /* number of such bits waiting to be used */ +int count; /* number of 8-bit characters output by get_char */ + +sexp_input_stream_t::sexp_input_stream_t(std::istream *i, size_t m_depth) +{ + set_input(i, m_depth); +} + +/* + * sexp_input_stream_t::set_input(std::istream *i) + */ + +sexp_input_stream_t *sexp_input_stream_t::set_input(std::istream *i, size_t m_depth) +{ + input_file = i; + byte_size = 8; + next_char = ' '; + bits = 0; + n_bits = 0; + count = -1; + depth = 0; + max_depth = m_depth; + return this; +} + +/* + * sexp_input_stream_t::set_byte_size(newByteSize) + */ +sexp_input_stream_t *sexp_input_stream_t::set_byte_size(uint32_t newByteSize) +{ + byte_size = newByteSize; + n_bits = 0; + bits = 0; + return this; +} + +int sexp_input_stream_t::read_char(void) +{ + count++; + return input_file->get(); +} + +/* + * sexp_input_stream_t::get_char() + * This is one possible character input routine for an input stream. + * (This version uses the standard input stream.) + * get_char places next 8-bit character into is->next_char. + * It also updates the count of number of 8-bit characters read. + * The value EOF is obtained when no more input is available. + * This code handles 4-bit/6-bit/8-bit channels. + */ +sexp_input_stream_t *sexp_input_stream_t::get_char(void) +{ + int c; + if (next_char == EOF) { + byte_size = 8; + return this; + } + + while (true) { + c = next_char = read_char(); + if (c == EOF) + return this; + if ((byte_size == 6 && (c == '|' || c == '}')) || (byte_size == 4 && (c == '#'))) { + // end of region reached; return terminating character, after checking for + // unused bits + if (n_bits > 0 && (((1 << n_bits) - 1) & bits) != 0) { + sexp_error(sexp_exception_t::warning, + "%d-bit region ended with %d unused bits left-over", + byte_size, + n_bits, + count); + } + return set_byte_size(8); + } else if (byte_size != 8 && is_white_space(c)) + ; /* ignore white space in hex and base64 regions */ + else if (byte_size == 6 && c == '=') + ; /* ignore equals signs in base64 regions */ + else if (byte_size == 8) { + return this; + } else if (byte_size < 8) { + bits = bits << byte_size; + n_bits += byte_size; + if (byte_size == 6 && is_base64_digit(c)) + bits = bits | base64value(c); + else if (byte_size == 4 && is_hex_digit(c)) + bits = bits | hexvalue(c); + else { + sexp_error(sexp_exception_t::error, + "character '%c' found in %u-bit coding region", + next_char, + byte_size, + count); + } + if (n_bits >= 8) { + next_char = (bits >> (n_bits - 8)) & 0xFF; + n_bits -= 8; + return this; + } + } + } +} + +/* + * sexp_input_stream_t::skip_white_space + * Skip over any white space on the given sexp_input_stream_t. + */ +sexp_input_stream_t *sexp_input_stream_t::skip_white_space(void) +{ + while (is_white_space(next_char)) + get_char(); + return this; +} + +/* + * sexp_input_stream_t::skip_char(c) + * Skip the following input character on input stream is, if it is + * equal to the character c. If it is not equal, then an error occurs. + */ +sexp_input_stream_t *sexp_input_stream_t::skip_char(int c) +{ + if (next_char != c) + sexp_error(sexp_exception_t::error, + "character '%c' found where '%c' was expected", + next_char, + c, + count); + return get_char(); +} + +/* + * sexp_input_stream_t::scan_token(ss) + * scan one or more characters into simple string ss as a token. + */ +void sexp_input_stream_t::scan_token(sexp_simple_string_t &ss) +{ + skip_white_space(); + while (is_token_char(next_char)) { + ss.append(next_char); + get_char(); + } +} + +/* + * sexp_input_stream_t::scan_to_eof(void) + * scan one or more characters (until EOF reached) + * return an object that is just that string + */ +std::shared_ptr<sexp_object_t> sexp_input_stream_t::scan_to_eof(void) +{ + sexp_simple_string_t ss; + std::shared_ptr<sexp_string_t> s(new sexp_string_t()); + skip_white_space(); + while (next_char != EOF) { + ss.append(next_char); + get_char(); + } + s->set_string(ss); + return s; +} + +/* + * scan_decimal_string(is) + * returns long integer that is value of decimal number + */ +uint32_t sexp_input_stream_t::scan_decimal_string(void) +{ + uint32_t value = 0; + uint32_t i = 0; + while (is_dec_digit(next_char)) { + value = value * 10 + decvalue(next_char); + get_char(); + if (i++ > 8) + sexp_error(sexp_exception_t::error, "Decimal number is too long", 0, 0, count); + } + return value; +} + +/* + * sexp_input_stream_t::scan_verbatim_string(is,ss,length) + * Reads verbatim string of given length into simple string ss. + */ +void sexp_input_stream_t::scan_verbatim_string(sexp_simple_string_t &ss, uint32_t length) +{ + skip_white_space()->skip_char(':'); + + // Some length is specified always, this is ensured by the caller's logic + assert(length != std::numeric_limits<uint32_t>::max()); + for (uint32_t i = 0; i < length; i++) { + ss.append(next_char); + get_char(); + } +} + +/* + * sexp_input_stream_t::scan_quoted_string(ss,length) + * Reads quoted string of given length into simple string ss. + * Handles ordinary C escapes. + * If of indefinite length, length is std::numeric_limits<uint32_t>::max(). + */ +void sexp_input_stream_t::scan_quoted_string(sexp_simple_string_t &ss, uint32_t length) +{ + skip_char('"'); + while (ss.length() <= length) { + if (next_char == '\"') { + if (length == std::numeric_limits<uint32_t>::max() || (ss.length() == length)) { + skip_char('\"'); + return; + } else + sexp_error(sexp_exception_t::error, + "Declared length was %d, but quoted string ended too early", + (int) length, + 0, + count); + } else if (next_char == '\\') /* handle escape sequence */ + { + get_char(); + switch (next_char) { + case 'b': + ss.append('\b'); + break; + case 't': + ss.append('\t'); + break; + case 'v': + ss.append('\v'); + break; + case 'n': + ss.append('\n'); + break; + case 'f': + ss.append('\f'); + break; + case 'r': + ss.append('\r'); + break; + case '\"': + ss.append('\"'); + break; + case '\'': + ss.append('\''); + break; + case '\\': + ss.append('\\'); + break; + case 'x': /* hexadecimal number */ + { + int j, val; + val = 0; + get_char(); + for (j = 0; j < 2; j++) { + if (is_hex_digit(next_char)) { + val = ((val << 4) | hexvalue(next_char)); + if (j < 1) { + get_char(); + } + } else + sexp_error(sexp_exception_t::error, + "Hex character \x5cx%x... too short", + val, + 0, + count); + } + ss.append(val); + } break; + case '\n': /* ignore backslash line feed */ + get_char(); /* also ignore following carriage-return if present */ + if (next_char != '\r') + continue; + break; + case '\r': /* ignore backslash carriage-return */ + get_char(); /* also ignore following linefeed if present */ + if (next_char != '\n') + continue; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': { /* octal number */ + int j, val; + val = 0; + for (j = 0; j < 3; j++) { + if (next_char >= '0' && next_char <= '7') { + val = ((val << 3) | (next_char - '0')); + if (j < 2) + get_char(); + } else + sexp_error(sexp_exception_t::error, + "Octal character \\%o... too short", + val, + 0, + count); + } + if (val > 255) + sexp_error(sexp_exception_t::error, + "Octal character \\%o... too big", + val, + 0, + count); + ss.append(val); + } break; + default: + sexp_error(sexp_exception_t::error, + "Unknown escape sequence \\%c", + next_char, + 0, + count); + } + } /* end of handling escape sequence */ + else if (next_char == EOF) { + sexp_error(sexp_exception_t::error, "unexpected end of file", 0, 0, count); + } else { + ss.append(next_char); + } + get_char(); + } /* end of main while loop */ +} + +/* + * scan_hexadecimal_string(ss,length) + * Reads hexadecimal string into simple string ss. + * String is of given length result, or length = std::numeric_limits<uint32_t>::max() + * if indefinite length. + */ +void sexp_input_stream_t::scan_hexadecimal_string(sexp_simple_string_t &ss, uint32_t length) +{ + set_byte_size(4)->skip_char('#'); + while (next_char != EOF && (next_char != '#' || get_byte_size() == 4)) { + ss.append(next_char); + get_char(); + } + skip_char('#'); + if (ss.length() != length && length != std::numeric_limits<uint32_t>::max()) + sexp_error(sexp_exception_t::warning, + "Hex string has length %d different than declared length %d", + ss.length(), + length, + count); +} + +/* + * sexp_input_stream_t::scan_base64_string(ss,length) + * Reads base64 string into simple string ss. + * String is of given length result, or length = std::numeric_limits<uint32_t>::max() + * if indefinite length. + */ +void sexp_input_stream_t::scan_base64_string(sexp_simple_string_t &ss, uint32_t length) +{ + set_byte_size(6)->skip_char('|'); + while (next_char != EOF && (next_char != '|' || get_byte_size() == 6)) { + ss.append(next_char); + get_char(); + } + skip_char('|'); + if (ss.length() != length && length != std::numeric_limits<uint32_t>::max()) + sexp_error(sexp_exception_t::warning, + "Base64 string has length %d different than declared length %d", + ss.length(), + length, + count); +} + +/* + * sexp_input_stream_t::scan_simple_string(void) + * Reads and returns a simple string from the input stream. + * Determines type of simple string from the initial character, and + * dispatches to appropriate routine based on that. + */ +sexp_simple_string_t sexp_input_stream_t::scan_simple_string(void) +{ + int length; + sexp_simple_string_t ss; + skip_white_space(); + /* Note that it is important in the following code to test for token-ness + * before checking the other cases, so that a token may begin with ":", + * which would otherwise be treated as a verbatim string missing a length. + */ + if (is_token_char(next_char) && !is_dec_digit(next_char)) { + scan_token(ss); + } else { + length = is_dec_digit(next_char) ? scan_decimal_string() : + std::numeric_limits<uint32_t>::max(); + + switch (next_char) { + case '\"': + scan_quoted_string(ss, length); + break; + case '#': + scan_hexadecimal_string(ss, length); + break; + case '|': + scan_base64_string(ss, length); + break; + case ':': + // ':' is 'tokenchar', so some length shall be defined + scan_verbatim_string(ss, length); + break; + default: { + const char *const msg = (next_char == EOF) ? "unexpected end of file" : + isprint(next_char) ? "illegal character '%c' (0x%x)" : + "illegal character 0x%x"; + sexp_error(sexp_exception_t::error, msg, next_char, next_char, count); + } + } + } + + if (ss.length() == 0) + sexp_error(sexp_exception_t::warning, "Simple string has zero length", 0, 0, count); + return ss; +} + +/* + * sexp_input_stream_t::scan_string(void) + * Reads and returns a string [presentationhint]string from input stream. + */ +std::shared_ptr<sexp_string_t> sexp_input_stream_t::scan_string(void) +{ + std::shared_ptr<sexp_string_t> s(new sexp_string_t()); + s->parse(this); + return s; +} + +/* + * sexp_input_stream_t::scan_list(void) + * Read and return a sexp_list_t from the input stream. + */ +std::shared_ptr<sexp_list_t> sexp_input_stream_t::scan_list(void) +{ + std::shared_ptr<sexp_list_t> list(new sexp_list_t()); + list->parse(this); + return list; +} + +/* + * sexp_input_stream_t::scan_object(void) + * Reads and returns a sexp_object_t from the given input stream. + */ +std::shared_ptr<sexp_object_t> sexp_input_stream_t::scan_object(void) +{ + std::shared_ptr<sexp_object_t> object; + skip_white_space(); + if (next_char == '{' && byte_size != 6) { + set_byte_size(6)->skip_char('{'); + object = scan_object(); + skip_char('}'); + } else { + if (next_char == '(') + object = scan_list(); + else + object = scan_string(); + } + return object; +} + +} // namespace sexp diff --git a/comm/third_party/rnp/src/libsexp/src/sexp-main.cpp b/comm/third_party/rnp/src/libsexp/src/sexp-main.cpp new file mode 100644 index 0000000000..7406bdfd5e --- /dev/null +++ b/comm/third_party/rnp/src/libsexp/src/sexp-main.cpp @@ -0,0 +1,237 @@ +/** + * + * Copyright (c) 2022-2023, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * This file is a part of RNP sexp library + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR + *CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + *EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + *PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + *BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + *IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Original copyright + * + * SEXP implementation code sexp-main.c + * Ron Rivest + * 6/29/1997 + **/ + +#include <fstream> + +#include <sexp/sexp.h> + +using namespace sexp; + +const char *help = "The program 'sexp' reads, parses, and prints out S-expressions.\n" + " INPUT:\n" + " Input is normally taken from stdin, but this can be changed:\n" + " -i filename -- takes input from file instead.\n" + " -p -- prompts user for console input\n" + " Input is normally parsed, but this can be changed:\n" + " -s -- treat input up to EOF as a single string\n" + " CONTROL LOOP:\n" + " The main routine typically reads one S-expression, prints it out " + "again, \n" + " and stops. This may be modified:\n" + " -x -- execute main loop repeatedly until EOF\n" + " OUTPUT:\n" + " Output is normally written to stdout, but this can be changed:\n" + " -o filename -- write output to file instead\n" + " The output format is normally canonical, but this can be changed:\n" + " -a -- write output in advanced transport format\n" + " -b -- write output in base-64 output format\n" + " -c -- write output in canonical format\n" + " -l -- suppress linefeeds after output\n" + " More than one output format can be requested at once.\n" + " There is normally a line-width of 75 on output, but:\n" + " -w width -- changes line width to specified width.\n" + " (0 implies no line-width constraint)\n" + " Running without switches implies: -p -a -b -c -x\n" + " Typical usage: cat certificate-file | sexp -a -x \n"; + +/*************************************************************************/ +/* main(argc,argv) + */ +int main(int argc, char **argv) +{ + char *c; + bool swa = true, swb = true, swc = true, swp = true, sws = false, swx = true, swl = false; + int i; + int ret = -1; + sexp_exception_t::set_interactive(true); + std::ifstream * ifs = nullptr; + sexp_input_stream_t * is = nullptr; + std::ofstream * ofs = nullptr; + sexp_output_stream_t *os = nullptr; + std::string ofname; + std::string ifname; + try { + std::shared_ptr<sexp_object_t> object; + + is = new sexp_input_stream_t(&std::cin); + os = new sexp_output_stream_t(&std::cout); + + if (argc > 1) + swa = swb = swc = swp = sws = swx = swl = false; + for (i = 1; i < argc; i++) { + c = argv[i]; + if (*c != '-') + throw sexp_exception_t( + std::string("Unrecognized switch ") + c, sexp_exception_t::error, EOF); + c++; + if (*c == 'a') + swa = true; /* advanced output */ + else if (*c == 'b') + swb = true; /* base-64 output */ + else if (*c == 'c') + swc = true; /* canonical output */ + else if (*c == 'h') { /* help */ + std::cout << help; + exit(0); + } else if (*c == 'i') { /* input file */ + if (i + 1 < argc) + i++; + ifs = new std::ifstream(argv[i], std::ifstream::binary); + if (ifs->fail()) + sexp_error(sexp_exception_t::error, "Can't open input file.", 0, 0, EOF); + is->set_input(ifs); + ifname = argv[i]; + } else if (*c == 'l') + swl = true; /* suppress linefeeds after output */ + else if (*c == 'o') { /* output file */ + if (i + 1 < argc) + i++; + ofs = new std::ofstream(argv[i], std::ifstream::binary); + if (ofs->fail()) + sexp_error(sexp_exception_t::error, "Can't open output file.", 0, 0, EOF); + os->set_output(ofs); + ofname = argv[i]; + } else if (*c == 'p') + swp = true; /* prompt for input */ + else if (*c == 's') + sws = true; /* treat input as one big string */ + else if (*c == 'w') { /* set output width */ + if (i + 1 < argc) + i++; + os->set_max_column(atoi(argv[i])); + } else if (*c == 'x') + swx = true; /* execute repeatedly */ + else + throw sexp_exception_t( + std::string("Unrecognized switch ") + argv[i], sexp_exception_t::error, EOF); + } + + if (swa == false && swb == false && swc == false) + swc = true; /* must have some output format! */ + + /* main loop */ + if (swp == 0) + is->get_char(); + else + is->set_next_char(-2); /* this is not EOF */ + while (is->get_next_char() != EOF) { + if (swp) { + if (ifname.empty()) + std::cout << "Input:"; + else + std::cout << "Reading input from " << ifname; + std::cout << std::endl; + std::cout.flush(); + } + + is->set_byte_size(8); + if (is->get_next_char() == -2) + is->get_char(); + + is->skip_white_space(); + if (is->get_next_char() == EOF) + break; + + object = sws ? is->scan_to_eof() : is->scan_object(); + + if (swp) + std::cout << std::endl; + + if (swc) { + if (swp) { + if (ofname.empty()) + std::cout << "Canonical output:" << std::endl; + else + std::cout << "Writing canonical output to '" << ofname << "'"; + } + object->print_canonical(os); + if (!swl) { + std::cout << std::endl; + } + } + + if (swb) { + if (swp) { + if (ofname.empty()) + std::cout << "Base64 (of canonical) output:" << std::endl; + else + std::cout << "Writing base64 (of canonical) output to '" << ofname + << "'"; + } + os->set_output(ofs ? ofs : &std::cout)->print_base64(object); + if (!swl) { + std::cout << std::endl; + std::cout.flush(); + } + } + + if (swa) { + if (swp) { + if (ofname.empty()) + std::cout << "Advanced transport output:" << std::endl; + else + std::cout << "Writing advanced transport output to '" << ofname << "'"; + } + os->set_output(ofs ? ofs : &std::cout)->print_advanced(object); + if (!swl) { + std::cout << std::endl; + std::cout.flush(); + } + } + + if (!swx) + break; + if (!swp) + is->skip_white_space(); + else if (!swl) { + std::cout << std::endl; + std::cout.flush(); + } + } + ret = 0; + } catch (sexp_exception_t &e) { + std::cout << e.what() << std::endl; + } catch (...) { + std::cout << "UNEXPECTED ERROR" << std::endl; + } + if (is) + delete is; + if (ifs) + delete ifs; + if (os) + delete os; + if (ofs) + delete ofs; + return ret; +}
\ No newline at end of file diff --git a/comm/third_party/rnp/src/libsexp/src/sexp-object.cpp b/comm/third_party/rnp/src/libsexp/src/sexp-object.cpp new file mode 100644 index 0000000000..4669cb4006 --- /dev/null +++ b/comm/third_party/rnp/src/libsexp/src/sexp-object.cpp @@ -0,0 +1,194 @@ +/**
+ *
+ * Copyright (c) 2022-2023, [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ * This file is a part of RNP sexp library
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Original copyright
+ *
+ * SEXP implementation code sexp-output.c
+ * Ron Rivest
+ * 5/5/1997
+ */
+
+#include <sexp/sexp.h>
+
+namespace sexp {
+
+/*
+ * sexp_string_t::parse(sis)
+ * Parses the strin from input stream
+ */
+
+void sexp_string_t::parse(sexp_input_stream_t *sis)
+{
+ if (sis->get_next_char() == '[') { /* scan presentation hint */
+ sis->skip_char('[');
+ set_presentation_hint(sis->scan_simple_string());
+ sis->skip_white_space()->skip_char(']')->skip_white_space();
+ }
+ set_string(sis->scan_simple_string());
+}
+
+/*
+ * sexp_string_t::print_canonical(os)
+ * Prints out sexp string onto output stream os
+ */
+sexp_output_stream_t *sexp_string_t::print_canonical(sexp_output_stream_t *os) const
+{
+ if (with_presentation_hint) {
+ os->var_put_char('[');
+ presentation_hint.print_canonical_verbatim(os);
+ os->var_put_char(']');
+ }
+ data_string.print_canonical_verbatim(os);
+ return os;
+}
+
+/*
+ * sexp_string_t::print_advanced(os)
+ * Prints out sexp string onto output stream os
+ */
+sexp_output_stream_t *sexp_string_t::print_advanced(sexp_output_stream_t *os) const
+{
+ sexp_object_t::print_advanced(os);
+ if (with_presentation_hint) {
+ os->put_char('[');
+ presentation_hint.print_advanced(os);
+ os->put_char(']');
+ }
+ data_string.print_advanced(os);
+ return os;
+}
+
+/*
+ * sexp_string_t::advanced_length(os)
+ * Returns length of printed image of string
+ */
+size_t sexp_string_t::advanced_length(sexp_output_stream_t *os) const
+{
+ size_t len = 0;
+ if (with_presentation_hint)
+ len += 2 + presentation_hint.advanced_length(os);
+ len += data_string.advanced_length(os);
+ return len;
+}
+
+/*
+ * sexp_list_t::parse(sis)
+ * Parses the list from input stream
+ */
+
+void sexp_list_t::parse(sexp_input_stream_t *sis)
+{
+ sis->skip_char('(')->increase_depth()->skip_white_space();
+ if (sis->get_next_char() == ')') {
+ ;
+ } else {
+ push_back(sis->scan_object());
+ }
+
+ while (true) {
+ sis->skip_white_space();
+ if (sis->get_next_char() == ')') { /* we just grabbed last element of list */
+ sis->skip_char(')')->decrease_depth();
+ return;
+
+ } else {
+ push_back(sis->scan_object());
+ }
+ }
+}
+
+/*
+ * sexp_list_t::print_canonical(os)
+ * Prints out the list "list" onto output stream os
+ */
+sexp_output_stream_t *sexp_list_t::print_canonical(sexp_output_stream_t *os) const
+{
+ os->var_put_char('(');
+ std::for_each(begin(), end(), [os](const std::shared_ptr<sexp_object_t> &obj) {
+ obj->print_canonical(os);
+ });
+ os->var_put_char(')');
+ return os;
+}
+
+/*
+ * sexp_list_t::print_advanced(os)
+ * Prints out the list onto output stream os.
+ * Uses print-length to determine length of the image. If it all fits
+ * on the current line, then it is printed that way. Otherwise, it is
+ * written out in "vertical" mode, with items of the list starting in
+ * the same column on successive lines.
+ */
+sexp_output_stream_t *sexp_list_t::print_advanced(sexp_output_stream_t *os) const
+{
+ sexp_object_t::print_advanced(os);
+ int vertical = false;
+ int firstelement = true;
+ os->put_char('(')->inc_indent();
+ vertical = (advanced_length(os) > os->get_max_column() - os->get_column());
+
+ std::for_each(begin(), end(), [&](const std::shared_ptr<sexp_object_t> &obj) {
+ if (!firstelement) {
+ if (vertical)
+ os->new_line(sexp_output_stream_t::advanced);
+ else
+ os->put_char(' ');
+ }
+ obj->print_advanced(os);
+ firstelement = false;
+ });
+
+ if (os->get_max_column() > 0 && os->get_column() > os->get_max_column() - 2)
+ os->new_line(sexp_output_stream_t::advanced);
+ return os->dec_indent()->put_char(')');
+}
+
+/*
+ * sexp_list_t::advanced_length(os)
+ * Returns length of printed image of list given as iterator
+ */
+size_t sexp_list_t::advanced_length(sexp_output_stream_t *os) const
+{
+ size_t len = 1; /* for left paren */
+ std::for_each(begin(), end(), [&](const std::shared_ptr<sexp_object_t> &obj) {
+ len += obj->advanced_length(os);
+ });
+ return (len + 1); /* for final paren */
+}
+
+/*
+ * sexp_object_t::print_advanced(os)
+ * Prints out object on output stream os
+ */
+sexp_output_stream_t *sexp_object_t::print_advanced(sexp_output_stream_t *os) const
+{
+ if (os->get_max_column() > 0 && os->get_column() > os->get_max_column() - 4)
+ os->new_line(sexp_output_stream_t::advanced);
+ return os;
+}
+
+} // namespace sexp
\ No newline at end of file diff --git a/comm/third_party/rnp/src/libsexp/src/sexp-output.cpp b/comm/third_party/rnp/src/libsexp/src/sexp-output.cpp new file mode 100644 index 0000000000..33e42fbba5 --- /dev/null +++ b/comm/third_party/rnp/src/libsexp/src/sexp-output.cpp @@ -0,0 +1,208 @@ +/** + * + * Copyright (c) 2022-2023, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * This file is a part of RNP sexp library + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Original copyright + * + * SEXP implementation code sexp-output.c + * Ron Rivest + * 5/5/1997 + */ + +#include <sexp/sexp.h> + +namespace sexp { + +static const char *hexDigits = "0123456789ABCDEF"; +static const char *base64Digits = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +/* + * sexp_output_stream_t::sexp_output_stream_t + * Creates and initializes new sexp_output_stream_t object. + */ +sexp_output_stream_t::sexp_output_stream_t(std::ostream *o) +{ + set_output(o); +} + +/* + * sexp_output_stream_t::set_output + * Re-initializes new sexp_output_stream_t object. + */ +sexp_output_stream_t *sexp_output_stream_t::set_output(std::ostream *o) +{ + output_file = o; + byte_size = 8; + bits = 0; + n_bits = 0; + mode = canonical; + column = 0; + max_column = default_line_length; + indent = 0; + base64_count = 0; + return this; +} + +/* + * sexp_output_stream_t::put_char(c) + * Puts the character c out on the output stream os. + * Keeps track of the "column" the next output char will go to. + */ +sexp_output_stream_t *sexp_output_stream_t::put_char(int c) +{ + output_file->put(c); + column++; + return this; +} + +/* + * sexp_output_stream_t::var_put_char(c) + * put_char with variable sized output bytes considered. + * int c; -- this is always an eight-bit byte being output + */ +sexp_output_stream_t *sexp_output_stream_t::var_put_char(int c) +{ + c &= 0xFF; + bits = (bits << 8) | c; + n_bits += 8; + while (n_bits >= byte_size) { + if ((byte_size == 6 || byte_size == 4 || c == '}' || c == '{' || c == '#' || + c == '|') && + max_column > 0 && column >= max_column) + new_line(mode); + if (byte_size == 4) + put_char(hexDigits[(bits >> (n_bits - 4)) & 0x0F]); + else if (byte_size == 6) + put_char(base64Digits[(bits >> (n_bits - 6)) & 0x3F]); + else if (byte_size == 8) + put_char(bits & 0xFF); + n_bits -= byte_size; + base64_count++; + } + return this; +} + +/* + * sexp_output_stream_t::change_output_byte_size(newByteSize,newMode) + * Change os->byte_size to newByteSize + * record mode in output stream for automatic line breaks + */ +sexp_output_stream_t *sexp_output_stream_t::change_output_byte_size(int newByteSize, + sexp_print_mode newMode) +{ + if (newByteSize != 4 && newByteSize != 6 && newByteSize != 8) + sexp_error(sexp_exception_t::error, "Illegal output base %d", newByteSize, 0, EOF); + if (newByteSize != 8 && byte_size != 8) + sexp_error(sexp_exception_t::error, + "Illegal change of output byte size from %d to %d", + byte_size, + newByteSize, + EOF); + byte_size = newByteSize; + n_bits = 0; + bits = 0; + base64_count = 0; + mode = newMode; + return this; +} + +/* + * sexp_output_stream_t::flush() + * flush out any remaining bits + */ +sexp_output_stream_t *sexp_output_stream_t::flush(void) +{ + if (n_bits > 0) { + assert(byte_size == 6); + put_char(base64Digits[(bits << (6 - n_bits)) & 0x3F]); + n_bits = 0; + base64_count++; + } + if (byte_size == 6) { /* and add switch here */ + while ((base64_count & 3) != 0) { + if (max_column > 0 && column >= max_column) + new_line(mode); + put_char('='); + base64_count++; + } + } + return this; +} + +/* + * sexp_output_stream_t::new_line(mode) + * Outputs a newline symbol to the output stream os. + * For advanced mode, also outputs indentation as one blank per + * indentation level (but never indents more than half of max_column). + * Resets column for next output character. + */ +sexp_output_stream_t *sexp_output_stream_t::new_line(sexp_print_mode mode) +{ + if (mode == advanced || mode == base64) { + put_char('\n'); + column = 0; + } + if (mode == advanced) { + for (uint32_t i = 0; i < indent && (4 * i) < max_column; i++) + put_char(' '); + } + return this; +} + +/* + * sexp_output_stream_t::print_decimal(n) + * print out n in decimal to output stream os + */ +sexp_output_stream_t *sexp_output_stream_t::print_decimal(uint64_t n) +{ + char buffer[20]; // 64*ln(2)/ln(10) + snprintf(buffer, + sizeof(buffer) / sizeof(buffer[0]), +#ifdef _WIN32 + "%llu", +#else + "%lu", +#endif + n); // since itoa is not a part of any standard + for (uint32_t i = 0; buffer[i] != 0; i++) + var_put_char(buffer[i]); + return this; +} + +/* + * base64 MODE + * Same as canonical, except all characters get put out as base 64 ones + */ + +sexp_output_stream_t *sexp_output_stream_t::print_base64( + const std::shared_ptr<sexp_object_t> &object) +{ + change_output_byte_size(8, base64)->var_put_char('{')->change_output_byte_size(6, base64); + print_canonical(object); + return flush()->change_output_byte_size(8, base64)->var_put_char('}'); +} +} // namespace sexp diff --git a/comm/third_party/rnp/src/libsexp/src/sexp-simple-string.cpp b/comm/third_party/rnp/src/libsexp/src/sexp-simple-string.cpp new file mode 100644 index 0000000000..612ef22705 --- /dev/null +++ b/comm/third_party/rnp/src/libsexp/src/sexp-simple-string.cpp @@ -0,0 +1,197 @@ +/**
+ *
+ * Copyright (c) 2022, [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ * This file is a part of RNP sexp library
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Original copyright
+ *
+ * SEXP implementation code sexp-output.c
+ * Ron Rivest
+ * 5/5/1997
+ */
+
+#include <sexp/sexp.h>
+
+namespace sexp {
+/*
+ * sexp_simple_string_t::print_canonical_verbatim(os)
+ * Print out simple string on output stream os as verbatim string.
+ */
+sexp_output_stream_t *sexp_simple_string_t::print_canonical_verbatim(
+ sexp_output_stream_t *os) const
+{
+ const octet_t *c = c_str();
+ /* print out len: */
+ os->print_decimal(length())->var_put_char(':');
+ /* print characters in fragment */
+ for (uint32_t i = 0; i < length(); i++)
+ os->var_put_char((int) *c++);
+ return os;
+}
+
+/*
+ * sexp_simple_string_t::advanced_length(os)
+ * Returns length of printed image of s
+ */
+size_t sexp_simple_string_t::advanced_length(sexp_output_stream_t *os) const
+{
+ if (can_print_as_token(os))
+ return advanced_length_token();
+ else if (can_print_as_quoted_string())
+ return advanced_length_quoted();
+ else if (length() <= 4 && os->get_byte_size() == 8)
+ return advanced_length_hexadecimal();
+ else if (os->get_byte_size() == 8)
+ return advanced_length_base64();
+ else
+ return 0; /* an error condition */
+}
+
+/*
+ * sexp_simple_string_t::print_token(os)
+ * Prints out simple string ss as a token (assumes that this is OK).
+ * May run over max-column, but there is no fragmentation allowed...
+ */
+sexp_output_stream_t *sexp_simple_string_t::print_token(sexp_output_stream_t *os) const
+{
+ const octet_t *c = c_str();
+ if (os->get_max_column() > 0 && os->get_column() > (os->get_max_column() - length()))
+ os->new_line(sexp_output_stream_t::advanced);
+ for (uint32_t i = 0; i < length(); i++)
+ os->put_char((int) (*c++));
+ return os;
+}
+
+/*
+ * sexp_simple_string_t::print_base64(os)
+ * Prints out simple string ss as a base64 value.
+ */
+sexp_output_stream_t *sexp_simple_string_t::print_base64(sexp_output_stream_t *os) const
+{
+ const octet_t *c = c_str();
+ os->var_put_char('|')->change_output_byte_size(6, sexp_output_stream_t::advanced);
+ for (uint32_t i = 0; i < length(); i++)
+ os->var_put_char((int) (*c++));
+ return os->flush()
+ ->change_output_byte_size(8, sexp_output_stream_t::advanced)
+ ->var_put_char('|');
+}
+
+/*
+ * sexp_simple_string_t::print_hexadecimal(os)
+ * Prints out simple string as a hexadecimal value.
+ */
+sexp_output_stream_t *sexp_simple_string_t::print_hexadecimal(sexp_output_stream_t *os) const
+{
+ const octet_t *c = c_str();
+ os->put_char('#')->change_output_byte_size(4, sexp_output_stream_t::advanced);
+ for (uint32_t i = 0; i < length(); i++)
+ os->var_put_char((int) (*c++));
+ return os->flush()
+ ->change_output_byte_size(8, sexp_output_stream_t::advanced)
+ ->put_char('#');
+}
+
+/*
+ * sexp_simple_string_t::print_quoted(os)
+ * Prints out simple string ss as a quoted string
+ * This code assumes that all characters are tokenchars and blanks,
+ * so no escape sequences need to be generated.
+ * May run over max-column, but there is no fragmentation allowed...
+ */
+sexp_output_stream_t *sexp_simple_string_t::print_quoted(sexp_output_stream_t *os) const
+{
+ const octet_t *c = c_str();
+ os->put_char('\"');
+ for (uint32_t i = 0; i < length(); i++) {
+ if (os->get_max_column() > 0 && os->get_column() >= os->get_max_column() - 2) {
+ os->put_char('\\')->put_char('\n');
+ os->reset_column();
+ }
+ os->put_char(*c++);
+ }
+ return os->put_char('\"');
+}
+
+/*
+ * sexp_simple_string_t::print_advanced(os)
+ * Prints out simple string onto output stream ss
+ */
+sexp_output_stream_t *sexp_simple_string_t::print_advanced(sexp_output_stream_t *os) const
+{
+ if (can_print_as_token(os))
+ print_token(os);
+ else if (can_print_as_quoted_string())
+ print_quoted(os);
+ else if (length() <= 4 && os->get_byte_size() == 8)
+ print_hexadecimal(os);
+ else if (os->get_byte_size() == 8)
+ print_base64(os);
+ else
+ sexp_error(sexp_exception_t::error,
+ "Can't print in advanced mode with restricted output character set",
+ 0,
+ 0,
+ EOF);
+ return os;
+}
+
+/*
+ * sexp_simple_string_t::can_print_as_quoted_string(void)
+ * Returns true if simple string can be printed as a quoted string.
+ * Must have only tokenchars and blanks.
+ */
+bool sexp_simple_string_t::can_print_as_quoted_string(void) const
+{
+ const octet_t *c = c_str();
+ for (uint32_t i = 0; i < length(); i++, c++) {
+ if (!is_token_char((int) (*c)) && *c != ' ')
+ return false;
+ }
+ return true;
+}
+
+/*
+ * sexp_simple_string_t::can_print_as_token(os)
+ * Returns true if simple string can be printed as a token.
+ * Doesn't begin with a digit, and all characters are tokenchars.
+ */
+bool sexp_simple_string_t::can_print_as_token(const sexp_output_stream_t *os) const
+{
+ const octet_t *c = c_str();
+ if (length() <= 0)
+ return false;
+ if (is_dec_digit((int) *c))
+ return false;
+ if (os->get_max_column() > 0 && os->get_column() + length() >= os->get_max_column())
+ return false;
+ for (uint32_t i = 0; i < length(); i++) {
+ if (!is_token_char((int) (*c++)))
+ return false;
+ }
+ return true;
+}
+
+} // namespace sexp
diff --git a/comm/third_party/rnp/src/libsexp/version.txt b/comm/third_party/rnp/src/libsexp/version.txt new file mode 100644 index 0000000000..100435be13 --- /dev/null +++ b/comm/third_party/rnp/src/libsexp/version.txt @@ -0,0 +1 @@ +0.8.2 diff --git a/comm/third_party/rnp/src/rnp/CMakeLists.txt b/comm/third_party/rnp/src/rnp/CMakeLists.txt new file mode 100644 index 0000000000..d3199e7e1f --- /dev/null +++ b/comm/third_party/rnp/src/rnp/CMakeLists.txt @@ -0,0 +1,100 @@ +# Copyright (c) 2018-2020 Ribose Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS +# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +if(MSVC) + # remove extra ${Configuration} subfolder + set(ArchiveOutputDir ${CMAKE_BINARY_DIR}\\src\\rnp) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${ArchiveOutputDir}) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_MINSIZEREL ${ArchiveOutputDir}) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE ${ArchiveOutputDir}) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELWITHDEBINFO ${ArchiveOutputDir}) + + set(RuntimeOutputDir ${CMAKE_BINARY_DIR}\\src\\rnp) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${RuntimeOutputDir}) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL ${RuntimeOutputDir}) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${RuntimeOutputDir}) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO ${RuntimeOutputDir}) + + find_path(GETOPT_INCLUDE_DIR + NAMES getopt.h + ) + find_library(GETOPT_LIBRARY + NAMES getopt + ) + find_path(DIRENT_INCLUDE_DIR + NAMES dirent.h + ) +endif() + +# for the headers +find_package(JSON-C 0.11 REQUIRED) + +add_executable(rnp + rnp.cpp + fficli.cpp + rnpcfg.cpp + ../rnpkeys/tui.cpp +) + +if(BUILD_SHARED_LIBS) + target_sources(rnp PRIVATE ../lib/logging.cpp $<TARGET_OBJECTS:rnp-common>) +endif(BUILD_SHARED_LIBS) + +target_include_directories(rnp + PRIVATE + "${PROJECT_SOURCE_DIR}/src" + "${PROJECT_SOURCE_DIR}/src/lib" + "${JSON-C_INCLUDE_DIRS}" +) +if(MSVC) + target_include_directories(rnp + PRIVATE + "${GETOPT_INCLUDE_DIR}" + "${DIRENT_INCLUDE_DIR}" + ) +endif() + +target_link_libraries(rnp + PRIVATE + librnp + JSON-C::JSON-C +) +if(MSVC) + target_link_libraries(rnp + PRIVATE + "${GETOPT_LIBRARY}" + ) +endif(MSVC) + +include(GNUInstallDirs) +install(TARGETS rnp + RUNTIME + DESTINATION "${CMAKE_INSTALL_BINDIR}" + COMPONENT cli +) + +# Build and install man page +if (ENABLE_DOC) + add_adoc_man("${CMAKE_CURRENT_SOURCE_DIR}/rnp.1.adoc" ${RNP_VERSION}) +endif() diff --git a/comm/third_party/rnp/src/rnp/fficli.cpp b/comm/third_party/rnp/src/rnp/fficli.cpp new file mode 100644 index 0000000000..fa118eea23 --- /dev/null +++ b/comm/third_party/rnp/src/rnp/fficli.cpp @@ -0,0 +1,3229 @@ +/* + * Copyright (c) 2019-2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include <sys/types.h> +#include <sys/stat.h> +#include <stdbool.h> +#include <errno.h> +#include <stdarg.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <string> +#include <vector> +#include <iterator> +#include <cassert> +#include <ctype.h> +#ifdef _MSC_VER +#include "uniwin.h" +#else +#include <sys/param.h> +#include <unistd.h> +#endif + +#ifndef _WIN32 +#include <termios.h> +#ifdef HAVE_SYS_RESOURCE_H +#include <sys/resource.h> +#endif +#endif + +#ifdef _WIN32 +#include <crtdbg.h> +#endif + +#include "fficli.h" +#include "str-utils.h" +#include "file-utils.h" +#include "time-utils.h" +#include "defaults.h" + +#ifndef RNP_USE_STD_REGEX +#include <regex.h> +#else +#include <regex> +#endif + +#ifdef HAVE_SYS_RESOURCE_H +/* When system resource consumption limit controls are available this + * can be used to attempt to disable core dumps which may leak + * sensitive data. + * + * Returns false if disabling core dumps failed, returns true if disabling + * core dumps succeeded. errno will be set to the result from setrlimit in + * the event of failure. + */ +static bool +disable_core_dumps(void) +{ + struct rlimit limit; + int error; + + errno = 0; + memset(&limit, 0, sizeof(limit)); + error = setrlimit(RLIMIT_CORE, &limit); + + if (error == 0) { + error = getrlimit(RLIMIT_CORE, &limit); + if (error) { + ERR_MSG("Warning - cannot turn off core dumps"); + return false; + } else if (limit.rlim_cur == 0) { + return true; // disabling core dumps ok + } else { + return false; // failed for some reason? + } + } + return false; +} +#endif + +#ifdef _WIN32 +#include <windows.h> +#include <stdexcept> + +static std::vector<std::string> +get_utf8_args() +{ + int arg_nb; + wchar_t **arg_w; + + arg_w = CommandLineToArgvW(GetCommandLineW(), &arg_nb); + if (!arg_w) { + throw std::runtime_error("CommandLineToArgvW failed"); + } + + try { + std::vector<std::string> result; + result.reserve(arg_nb); + for (int i = 0; i < arg_nb; i++) { + auto utf8 = wstr_to_utf8(arg_w[i]); + result.push_back(utf8); + } + LocalFree(arg_w); + return result; + } catch (...) { + LocalFree(arg_w); + throw; + } +} + +void +rnp_win_clear_args(int argc, char **argv) +{ + for (int i = 0; i < argc; i++) { + if (argv[i]) { + free(argv[i]); + } + } + delete argv; +} + +bool +rnp_win_substitute_cmdline_args(int *argc, char ***argv) +{ + int argc_utf8 = 0; + char **argv_utf8_cstrs = NULL; + try { + auto argv_utf8_strings = get_utf8_args(); + argc_utf8 = argv_utf8_strings.size(); + *argc = argc_utf8; + argv_utf8_cstrs = new (std::nothrow) char *[argc_utf8 + 1](); + if (!argv_utf8_cstrs) { + throw std::bad_alloc(); + } + for (int i = 0; i < argc_utf8; i++) { + auto arg_utf8 = strdup(argv_utf8_strings[i].c_str()); + if (!arg_utf8) { + throw std::bad_alloc(); + } + argv_utf8_cstrs[i] = arg_utf8; + } + /* argv must be terminated with NULL string */ + argv_utf8_cstrs[argc_utf8] = NULL; + } catch (...) { + if (argv_utf8_cstrs) { + rnp_win_clear_args(argc_utf8, argv_utf8_cstrs); + } + throw; + } + *argc = argc_utf8; + *argv = argv_utf8_cstrs; + return true; +} +#endif + +static bool +set_pass_fd(FILE **file, int passfd) +{ + if (!file) { + return false; + } + *file = rnp_fdopen(passfd, "r"); + if (!*file) { + ERR_MSG("Cannot open fd %d for reading", passfd); + return false; + } + return true; +} + +static char * +ptimestr(char *dest, size_t size, time_t t) +{ + struct tm tm = {}; + rnp_gmtime(t, tm); + (void) snprintf(dest, + size, + "%s%04d-%02d-%02d", + rnp_y2k38_warning(t) ? ">=" : "", + tm.tm_year + 1900, + tm.tm_mon + 1, + tm.tm_mday); + return dest; +} + +static bool +cli_rnp_get_confirmation(const cli_rnp_t *rnp, const char *msg, ...) +{ + char reply[10]; + va_list ap; + + while (true) { + va_start(ap, msg); + vfprintf(rnp->userio_out, msg, ap); + va_end(ap); + fprintf(rnp->userio_out, " (y/N) "); + fflush(rnp->userio_out); + + if (fgets(reply, sizeof(reply), rnp->userio_in) == NULL) { + return false; + } + + rnp::strip_eol(reply); + + if (strlen(reply) > 0) { + if (toupper(reply[0]) == 'Y') { + return true; + } else if (toupper(reply[0]) == 'N') { + return false; + } + + fprintf(rnp->userio_out, "Sorry, response '%s' not understood.\n", reply); + } else { + return false; + } + } + + return false; +} + +static bool +rnp_ask_filename(const std::string &msg, std::string &res, cli_rnp_t &rnp) +{ + fprintf(rnp.userio_out, "%s", msg.c_str()); + fflush(rnp.userio_out); + char fname[128] = {0}; + std::string path; + do { + if (!fgets(fname, sizeof(fname), rnp.userio_in)) { + return false; + } + path = path + std::string(fname); + if (rnp::strip_eol(path)) { + res = path; + return true; + } + if (path.size() >= 2048) { + fprintf(rnp.userio_out, "%s", "Too long filename, aborting."); + fflush(rnp.userio_out); + return false; + } + } while (1); +} + +/** @brief checks whether file exists already and asks user for the new filename + * @param path output file name with path. May be an empty string, then user is asked for it. + * @param res resulting output path will be stored here. + * @param rnp initialized cli_rnp_t structure with additional data + * @return true on success, or false otherwise (user cancels the operation) + **/ + +static bool +rnp_get_output_filename(const std::string &path, std::string &res, cli_rnp_t &rnp) +{ + std::string newpath = path; + if (newpath.empty() && + !rnp_ask_filename("Please enter the output filename: ", newpath, rnp)) { + return false; + } + + while (true) { + if (!rnp_file_exists(newpath.c_str())) { + res = newpath; + return true; + } + if (rnp.cfg().get_bool(CFG_OVERWRITE) || + cli_rnp_get_confirmation( + &rnp, + "File '%s' already exists. Would you like to overwrite it?", + newpath.c_str())) { + rnp_unlink(newpath.c_str()); + res = newpath; + return true; + } + + if (!rnp_ask_filename("Please enter the new filename: ", newpath, rnp)) { + return false; + } + if (newpath.empty()) { + return false; + } + } +} + +static bool +stdin_getpass(const char *prompt, char *buffer, size_t size, cli_rnp_t &rnp) +{ +#ifndef _WIN32 + struct termios saved_flags, noecho_flags; + bool restore_ttyflags = false; +#endif + bool ok = false; + FILE *in = NULL; + FILE *out = NULL; + FILE *userio_in = rnp.userio_in ? rnp.userio_in : stdin; + + // validate args + if (!buffer) { + goto end; + } + // doesn't hurt + *buffer = '\0'; + + if (!rnp.cfg().get_bool(CFG_NOTTY)) { +#ifndef _WIN32 + in = fopen("/dev/tty", "w+ce"); +#endif + out = in; + } + + if (!in) { + in = userio_in; + out = rnp.userio_out ? rnp.userio_out : stdout; + } + + // TODO: Implement alternative for hiding password entry on Windows + // TODO: avoid duplicate termios code with pass-provider.cpp +#ifndef _WIN32 + // save the original termios + if (tcgetattr(fileno(in), &saved_flags) == 0) { + noecho_flags = saved_flags; + // disable echo in the local modes + noecho_flags.c_lflag = (noecho_flags.c_lflag & ~ECHO) | ECHONL | ISIG; + restore_ttyflags = (tcsetattr(fileno(in), TCSANOW, &noecho_flags) == 0); + } +#endif + if (prompt) { + fputs(prompt, out); + } + if (fgets(buffer, size, in) == NULL) { + goto end; + } + + rnp::strip_eol(buffer); + ok = true; +end: +#ifndef _WIN32 + if (restore_ttyflags) { + tcsetattr(fileno(in), TCSAFLUSH, &saved_flags); + } +#endif + if (in && (in != userio_in)) { + fclose(in); + } + return ok; +} + +static bool +ffi_pass_callback_stdin(rnp_ffi_t ffi, + void * app_ctx, + rnp_key_handle_t key, + const char * pgp_context, + char buf[], + size_t buf_len) +{ + char * keyid = NULL; + char target[64] = {0}; + char prompt[128] = {0}; + char * buffer = NULL; + bool ok = false; + bool protect = false; + bool add_subkey = false; + bool decrypt_symmetric = false; + bool encrypt_symmetric = false; + bool is_primary = false; + cli_rnp_t *rnp = static_cast<cli_rnp_t *>(app_ctx); + + if (!ffi || !pgp_context) { + goto done; + } + + if (!strcmp(pgp_context, "protect")) { + protect = true; + } else if (!strcmp(pgp_context, "add subkey")) { + add_subkey = true; + } else if (!strcmp(pgp_context, "decrypt (symmetric)")) { + decrypt_symmetric = true; + } else if (!strcmp(pgp_context, "encrypt (symmetric)")) { + encrypt_symmetric = true; + } + + if (!decrypt_symmetric && !encrypt_symmetric) { + rnp_key_get_keyid(key, &keyid); + snprintf(target, sizeof(target), "key 0x%s", keyid); + rnp_buffer_destroy(keyid); + (void) rnp_key_is_primary(key, &is_primary); + } + + if ((protect || add_subkey) && rnp->reuse_password_for_subkey && !is_primary) { + char *primary_fprint = NULL; + if (rnp_key_get_primary_fprint(key, &primary_fprint) == RNP_SUCCESS && + !rnp->reuse_primary_fprint.empty() && + rnp->reuse_primary_fprint == primary_fprint) { + strncpy(buf, rnp->reused_password, buf_len); + ok = true; + } + + rnp_buffer_clear(rnp->reused_password, strnlen(rnp->reused_password, buf_len)); + free(rnp->reused_password); + rnp->reused_password = NULL; + rnp->reuse_password_for_subkey = false; + rnp_buffer_destroy(primary_fprint); + if (ok) + return true; + } + + buffer = (char *) calloc(1, buf_len); + if (!buffer) { + return false; + } +start: + if (decrypt_symmetric) { + snprintf(prompt, sizeof(prompt), "Enter password to decrypt data: "); + } else if (encrypt_symmetric) { + snprintf(prompt, sizeof(prompt), "Enter password to encrypt data: "); + } else { + snprintf(prompt, sizeof(prompt), "Enter password for %s to %s: ", target, pgp_context); + } + + if (!stdin_getpass(prompt, buf, buf_len, *rnp)) { + goto done; + } + if (protect || encrypt_symmetric) { + if (protect) { + snprintf(prompt, sizeof(prompt), "Repeat password for %s: ", target); + } else { + snprintf(prompt, sizeof(prompt), "Repeat password: "); + } + + if (!stdin_getpass(prompt, buffer, buf_len, *rnp)) { + goto done; + } + if (strcmp(buf, buffer) != 0) { + fputs("\nPasswords do not match!", rnp->userio_out); + // currently will loop forever + goto start; + } + if (strnlen(buf, buf_len) == 0 && !rnp->cfg().get_bool(CFG_FORCE)) { + if (!cli_rnp_get_confirmation( + rnp, "Password is empty. The key will be left unprotected. Are you sure?")) { + goto start; + } + } + } + if ((protect || add_subkey) && is_primary) { + if (cli_rnp_get_confirmation( + rnp, "Would you like to use the same password to protect subkey(s)?")) { + char *primary_fprint = NULL; + rnp->reuse_password_for_subkey = true; + rnp_key_get_fprint(key, &primary_fprint); + rnp->reuse_primary_fprint = primary_fprint; + rnp->reused_password = strdup(buf); + rnp_buffer_destroy(primary_fprint); + } + } + ok = true; +done: + fputs("", rnp->userio_out); + rnp_buffer_clear(buffer, buf_len); + free(buffer); + return ok; +} + +static bool +ffi_pass_callback_file(rnp_ffi_t ffi, + void * app_ctx, + rnp_key_handle_t key, + const char * pgp_context, + char buf[], + size_t buf_len) +{ + if (!app_ctx || !buf || !buf_len) { + return false; + } + + FILE *fp = (FILE *) app_ctx; + if (!fgets(buf, buf_len, fp)) { + return false; + } + rnp::strip_eol(buf); + return true; +} + +static bool +ffi_pass_callback_string(rnp_ffi_t ffi, + void * app_ctx, + rnp_key_handle_t key, + const char * pgp_context, + char buf[], + size_t buf_len) +{ + if (!app_ctx || !buf || !buf_len) { + return false; + } + + const char *pswd = (const char *) app_ctx; + if (strlen(pswd) >= buf_len) { + return false; + } + + strncpy(buf, pswd, buf_len); + return true; +} + +static void +ffi_key_callback(rnp_ffi_t ffi, + void * app_ctx, + const char *identifier_type, + const char *identifier, + bool secret) +{ + cli_rnp_t *rnp = static_cast<cli_rnp_t *>(app_ctx); + + if (rnp::str_case_eq(identifier_type, "keyid") && + rnp::str_case_eq(identifier, "0000000000000000")) { + if (rnp->hidden_msg) { + return; + } + ERR_MSG("This message has hidden recipient. Will attempt to use all secret keys for " + "decryption."); + rnp->hidden_msg = true; + } +} + +#ifdef _WIN32 +void +rnpffiInvalidParameterHandler(const wchar_t *expression, + const wchar_t *function, + const wchar_t *file, + unsigned int line, + uintptr_t pReserved) +{ + // do nothing as within release CRT all params are NULL +} +#endif + +cli_rnp_t::~cli_rnp_t() +{ + end(); +#ifdef _WIN32 + if (subst_argv) { + rnp_win_clear_args(subst_argc, subst_argv); + } +#endif +} + +int +cli_rnp_t::ret_code(bool success) +{ + return success ? EXIT_SUCCESS : EXIT_FAILURE; +} + +#ifdef _WIN32 +void +cli_rnp_t::substitute_args(int *argc, char ***argv) +{ + rnp_win_substitute_cmdline_args(argc, argv); + subst_argc = *argc; + subst_argv = *argv; +} +#endif + +bool +cli_rnp_t::init(const rnp_cfg &cfg) +{ + cfg_.copy(cfg); + + /* Configure user's io streams. */ + if (!cfg_.get_bool(CFG_NOTTY)) { + userio_in = (isatty(fileno(stdin)) ? stdin : fopen("/dev/tty", "r")); + userio_in = (userio_in ? userio_in : stdin); + userio_out = (isatty(fileno(stdout)) ? stdout : fopen("/dev/tty", "a+")); + userio_out = (userio_out ? userio_out : stdout); + } else { + userio_in = stdin; + userio_out = stdout; + } + +#ifndef _WIN32 + /* If system resource constraints are in effect then attempt to + * disable core dumps. + */ + bool coredumps = true; + if (!cfg_.get_bool(CFG_COREDUMPS)) { +#ifdef HAVE_SYS_RESOURCE_H + coredumps = !disable_core_dumps(); +#endif + } + + if (coredumps) { + ERR_MSG("warning: core dumps may be enabled, sensitive data may be leaked to disk"); + } +#endif + +#ifdef _WIN32 + /* Setup invalid parameter handler for Windows */ + _invalid_parameter_handler handler = rnpffiInvalidParameterHandler; + _set_invalid_parameter_handler(handler); + _CrtSetReportMode(_CRT_ASSERT, 0); +#endif + + /* Configure the results stream. */ + // TODO: UTF8? + const std::string &ress = cfg_.get_str(CFG_IO_RESS); + if (ress.empty() || (ress == "<stderr>")) { + resfp = stderr; + } else if (ress == "<stdout>") { + resfp = stdout; + } else if (!(resfp = rnp_fopen(ress.c_str(), "w"))) { + ERR_MSG("Cannot open results %s for writing", ress.c_str()); + return false; + } + + bool res = false; + const std::string pformat = pubformat(); + const std::string sformat = secformat(); + if (pformat.empty() || sformat.empty()) { + ERR_MSG("Unknown public or secret keyring format"); + return false; + } + if (rnp_ffi_create(&ffi, pformat.c_str(), sformat.c_str())) { + ERR_MSG("Failed to initialize FFI"); + return false; + } + + // by default use stdin password provider + if (rnp_ffi_set_pass_provider(ffi, ffi_pass_callback_stdin, this)) { + goto done; + } + + // set key provider, currently for informational purposes only + if (rnp_ffi_set_key_provider(ffi, ffi_key_callback, this)) { + goto done; + } + + // setup file/pipe password input if requested + if (cfg_.get_int(CFG_PASSFD, -1) >= 0) { + if (!set_pass_fd(&passfp, cfg_.get_int(CFG_PASSFD))) { + goto done; + } + if (rnp_ffi_set_pass_provider(ffi, ffi_pass_callback_file, passfp)) { + goto done; + } + } + // setup current time if requested + if (cfg_.has(CFG_CURTIME)) { + rnp_set_timestamp(ffi, cfg_.time()); + } + pswdtries = MAX_PASSWORD_ATTEMPTS; + res = true; +done: + if (!res) { + rnp_ffi_destroy(ffi); + ffi = NULL; + } + return res; +} + +void +cli_rnp_t::end() +{ + if (passfp) { + fclose(passfp); + passfp = NULL; + } + if (resfp && (resfp != stderr) && (resfp != stdout)) { + fclose(resfp); + resfp = NULL; + } + if (userio_in && userio_in != stdin) { + fclose(userio_in); + } + userio_in = NULL; + if (userio_out && userio_out != stdout) { + fclose(userio_out); + } + userio_out = NULL; + rnp_ffi_destroy(ffi); + ffi = NULL; + cfg_.clear(); + reuse_primary_fprint.clear(); + if (reused_password) { + rnp_buffer_clear(reused_password, strlen(reused_password)); + free(reused_password); + reused_password = NULL; + } + reuse_password_for_subkey = false; +} + +bool +cli_rnp_t::load_keyring(bool secret) +{ + const std::string &path = secret ? secpath() : pubpath(); + bool dir = secret && (secformat() == RNP_KEYSTORE_G10); + if (!rnp::path::exists(path, dir)) { + return true; + } + + rnp_input_t keyin = NULL; + if (rnp_input_from_path(&keyin, path.c_str())) { + ERR_MSG("Warning: failed to open keyring at path '%s' for reading.", path.c_str()); + return true; + } + + const char * format = secret ? secformat().c_str() : pubformat().c_str(); + uint32_t flags = secret ? RNP_LOAD_SAVE_SECRET_KEYS : RNP_LOAD_SAVE_PUBLIC_KEYS; + rnp_result_t ret = rnp_load_keys(ffi, format, keyin, flags); + if (ret) { + ERR_MSG("Error: failed to load keyring from '%s'", path.c_str()); + } + rnp_input_destroy(keyin); + + if (ret) { + return false; + } + + size_t keycount = 0; + if (secret) { + (void) rnp_get_secret_key_count(ffi, &keycount); + } else { + (void) rnp_get_public_key_count(ffi, &keycount); + } + if (!keycount) { + ERR_MSG("Warning: no keys were loaded from the keyring '%s'.", path.c_str()); + } + return true; +} + +bool +cli_rnp_t::load_keyrings(bool loadsecret) +{ + /* Read public keys */ + if (rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC)) { + ERR_MSG("failed to clear public keyring"); + return false; + } + + if (!load_keyring(false)) { + return false; + } + + /* Only read secret keys if we need to */ + if (loadsecret) { + if (rnp_unload_keys(ffi, RNP_KEY_UNLOAD_SECRET)) { + ERR_MSG("failed to clear secret keyring"); + return false; + } + + if (!load_keyring(true)) { + return false; + } + } + if (defkey().empty()) { + set_defkey(); + } + return true; +} + +void +cli_rnp_t::set_defkey() +{ + rnp_identifier_iterator_t it = NULL; + rnp_key_handle_t handle = NULL; + const char * grip = NULL; + + cfg_.unset(CFG_KR_DEF_KEY); + if (rnp_identifier_iterator_create(ffi, &it, "grip")) { + ERR_MSG("failed to create key iterator"); + return; + } + + while (!rnp_identifier_iterator_next(it, &grip)) { + bool is_subkey = false; + bool is_secret = false; + + if (!grip) { + break; + } + if (rnp_locate_key(ffi, "grip", grip, &handle)) { + ERR_MSG("failed to locate key"); + continue; + } + if (rnp_key_is_sub(handle, &is_subkey) || is_subkey) { + goto next; + } + if (rnp_key_have_secret(handle, &is_secret)) { + goto next; + } + if (!cfg_.has(CFG_KR_DEF_KEY) || is_secret) { + cfg_.set_str(CFG_KR_DEF_KEY, grip); + /* if we have secret primary key then use it as default */ + if (is_secret) { + break; + } + } + next: + rnp_key_handle_destroy(handle); + handle = NULL; + } + rnp_key_handle_destroy(handle); + rnp_identifier_iterator_destroy(it); +} + +bool +cli_rnp_t::is_cv25519_subkey(rnp_key_handle_t handle) +{ + bool primary = false; + if (rnp_key_is_primary(handle, &primary)) { + ERR_MSG("Error: failed to check for subkey."); + return false; + } + if (primary) { + return false; + } + char *alg = NULL; + if (rnp_key_get_alg(handle, &alg)) { + ERR_MSG("Error: failed to check key's alg."); + return false; + } + bool ecdh = !strcmp(alg, RNP_ALGNAME_ECDH); + rnp_buffer_destroy(alg); + if (!ecdh) { + return false; + } + char *curve = NULL; + if (rnp_key_get_curve(handle, &curve)) { + ERR_MSG("Error: failed to check key's curve."); + return false; + } + bool cv25519 = !strcmp(curve, "Curve25519"); + rnp_buffer_destroy(curve); + return cv25519; +} + +bool +cli_rnp_t::get_protection(rnp_key_handle_t handle, + std::string & hash, + std::string & cipher, + size_t & iterations) +{ + bool prot = false; + if (rnp_key_is_protected(handle, &prot)) { + ERR_MSG("Error: failed to check key's protection."); + return false; + } + if (!prot) { + hash = ""; + cipher = ""; + iterations = 0; + return true; + } + + char *val = NULL; + try { + if (rnp_key_get_protection_hash(handle, &val)) { + ERR_MSG("Error: failed to retrieve key's protection hash."); + return false; + } + hash = val; + rnp_buffer_destroy(val); + if (rnp_key_get_protection_cipher(handle, &val)) { + ERR_MSG("Error: failed to retrieve key's protection cipher."); + return false; + } + cipher = val; + rnp_buffer_destroy(val); + if (rnp_key_get_protection_iterations(handle, &iterations)) { + ERR_MSG("Error: failed to retrieve key's protection iterations."); + return false; + } + return true; + } catch (const std::exception &e) { + ERR_MSG("Error: failed to retrieve key's properties: %s", e.what()); + rnp_buffer_destroy(val); + return false; + } +} + +bool +cli_rnp_t::check_cv25519_bits(rnp_key_handle_t key, char *prot_password, bool &tweaked) +{ + /* unlock key first to check whether bits are tweaked */ + if (prot_password && rnp_key_unlock(key, prot_password)) { + ERR_MSG("Error: failed to unlock key. Did you specify valid password?"); + return false; + } + rnp_result_t ret = rnp_key_25519_bits_tweaked(key, &tweaked); + if (ret) { + ERR_MSG("Error: failed to check whether key's bits are tweaked."); + } + if (prot_password) { + rnp_key_lock(key); + } + return !ret; +} + +bool +cli_rnp_t::fix_cv25519_subkey(const std::string &key, bool checkonly) +{ + std::vector<rnp_key_handle_t> keys; + if (!cli_rnp_keys_matching_string( + this, keys, key, CLI_SEARCH_SECRET | CLI_SEARCH_SUBKEYS)) { + ERR_MSG("Secret keys matching '%s' not found.", key.c_str()); + return false; + } + bool res = false; + std::string prot_hash; + std::string prot_cipher; + size_t prot_iterations; + char * prot_password = NULL; + bool tweaked = false; + + if (keys.size() > 1) { + ERR_MSG( + "Ambiguous input: too many keys found for '%s'. Did you use keyid or fingerprint?", + key.c_str()); + goto done; + } + cli_rnp_print_key_info(userio_out, ffi, keys[0], true, false); + if (!is_cv25519_subkey(keys[0])) { + ERR_MSG("Error: specified key is not Curve25519 ECDH subkey."); + goto done; + } + + if (!get_protection(keys[0], prot_hash, prot_cipher, prot_iterations)) { + goto done; + } + + if (!prot_hash.empty() && + (rnp_request_password(ffi, keys[0], "unprotect", &prot_password) || !prot_password)) { + ERR_MSG("Error: failed to obtain protection password."); + goto done; + } + + if (!check_cv25519_bits(keys[0], prot_password, tweaked)) { + goto done; + } + + if (checkonly) { + fprintf(userio_out, + tweaked ? "Cv25519 key bits are set correctly and do not require fixing.\n" : + "Warning: Cv25519 key bits need fixing.\n"); + res = tweaked; + goto done; + } + + if (tweaked) { + ERR_MSG("Warning: key's bits are fixed already, no action is required."); + res = true; + goto done; + } + + /* now unprotect so we can tweak bits */ + if (!prot_hash.empty()) { + if (rnp_key_unprotect(keys[0], prot_password)) { + ERR_MSG("Error: failed to unprotect key. Did you specify valid password?"); + goto done; + } + if (rnp_key_unlock(keys[0], NULL)) { + ERR_MSG("Error: failed to unlock key."); + goto done; + } + } + + /* tweak key bits and protect back */ + if (rnp_key_25519_bits_tweak(keys[0])) { + ERR_MSG("Error: failed to tweak key's bits."); + goto done; + } + + if (!prot_hash.empty() && rnp_key_protect(keys[0], + prot_password, + prot_cipher.c_str(), + NULL, + prot_hash.c_str(), + prot_iterations)) { + ERR_MSG("Error: failed to protect key back."); + goto done; + } + + res = cli_rnp_save_keyrings(this); +done: + clear_key_handles(keys); + if (prot_password) { + rnp_buffer_clear(prot_password, strlen(prot_password) + 1); + rnp_buffer_destroy(prot_password); + } + return res; +} + +bool +cli_rnp_t::set_key_expire(const std::string &key) +{ + std::vector<rnp_key_handle_t> keys; + if (!cli_rnp_keys_matching_string( + this, keys, key, CLI_SEARCH_SECRET | CLI_SEARCH_SUBKEYS)) { + ERR_MSG("Secret keys matching '%s' not found.", key.c_str()); + return false; + } + bool res = false; + uint32_t expiration = 0; + if (keys.size() > 1) { + ERR_MSG("Ambiguous input: too many keys found for '%s'.", key.c_str()); + goto done; + } + if (!cfg().get_expiration(CFG_SET_KEY_EXPIRE, expiration) || + rnp_key_set_expiration(keys[0], expiration)) { + ERR_MSG("Failed to set key expiration."); + goto done; + } + res = cli_rnp_save_keyrings(this); +done: + if (res) { + cli_rnp_print_key_info(stdout, ffi, keys[0], true, false); + } + clear_key_handles(keys); + return res; +} + +bool +cli_rnp_t::add_new_subkey(const std::string &key) +{ + rnp_cfg &lcfg = cfg(); + if (!cli_rnp_set_generate_params(lcfg, true)) { + ERR_MSG("Subkey generation setup failed."); + return false; + } + std::vector<rnp_key_handle_t> keys; + if (!cli_rnp_keys_matching_string(this, keys, key, CLI_SEARCH_SECRET)) { + ERR_MSG("Secret keys matching '%s' not found.", key.c_str()); + return false; + } + bool res = false; + rnp_op_generate_t genkey = NULL; + rnp_key_handle_t subkey = NULL; + char * password = NULL; + + if (keys.size() > 1) { + ERR_MSG("Ambiguous input: too many keys found for '%s'.", key.c_str()); + goto done; + } + if (rnp_op_generate_subkey_create( + &genkey, ffi, keys[0], cfg().get_cstr(CFG_KG_SUBKEY_ALG))) { + ERR_MSG("Failed to initialize subkey generation."); + goto done; + } + if (cfg().has(CFG_KG_SUBKEY_BITS) && + rnp_op_generate_set_bits(genkey, cfg().get_int(CFG_KG_SUBKEY_BITS))) { + ERR_MSG("Failed to set subkey bits."); + goto done; + } + if (cfg().has(CFG_KG_SUBKEY_CURVE) && + rnp_op_generate_set_curve(genkey, cfg().get_cstr(CFG_KG_SUBKEY_CURVE))) { + ERR_MSG("Failed to set subkey curve."); + goto done; + } + if (cfg().has(CFG_KG_SUBKEY_EXPIRATION)) { + uint32_t expiration = 0; + if (!cfg().get_expiration(CFG_KG_SUBKEY_EXPIRATION, expiration) || + rnp_op_generate_set_expiration(genkey, expiration)) { + ERR_MSG("Failed to set subkey expiration."); + goto done; + } + } + // TODO : set DSA qbits + if (rnp_op_generate_set_hash(genkey, cfg().get_cstr(CFG_KG_HASH))) { + ERR_MSG("Failed to set hash algorithm."); + goto done; + } + if (rnp_op_generate_execute(genkey) || rnp_op_generate_get_key(genkey, &subkey)) { + ERR_MSG("Subkey generation failed."); + goto done; + } + if (rnp_request_password(ffi, subkey, "protect", &password)) { + ERR_MSG("Failed to obtain protection password."); + goto done; + } + if (*password) { + rnp_result_t ret = rnp_key_protect(subkey, + password, + cfg().get_cstr(CFG_KG_PROT_ALG), + NULL, + cfg().get_cstr(CFG_KG_PROT_HASH), + cfg().get_int(CFG_KG_PROT_ITERATIONS)); + rnp_buffer_clear(password, strlen(password) + 1); + rnp_buffer_destroy(password); + if (ret) { + ERR_MSG("Failed to protect key."); + goto done; + } + } else { + rnp_buffer_destroy(password); + } + res = cli_rnp_save_keyrings(this); +done: + if (res) { + cli_rnp_print_key_info(stdout, ffi, keys[0], true, false); + if (subkey) { + cli_rnp_print_key_info(stdout, ffi, subkey, true, false); + } + } + clear_key_handles(keys); + rnp_op_generate_destroy(genkey); + rnp_key_handle_destroy(subkey); + return res; +} + +bool +cli_rnp_t::edit_key(const std::string &key) +{ + int edit_options = 0; + + if (cfg().get_bool(CFG_CHK_25519_BITS)) { + edit_options++; + } + if (cfg().get_bool(CFG_FIX_25519_BITS)) { + edit_options++; + } + if (cfg().get_bool(CFG_ADD_SUBKEY)) { + edit_options++; + } + if (cfg().has(CFG_SET_KEY_EXPIRE)) { + edit_options++; + } + + if (!edit_options) { + ERR_MSG("You should specify one of the editing options for --edit-key."); + return false; + } + if (edit_options > 1) { + ERR_MSG("Only one key edit option can be executed at a time."); + return false; + } + + if (cfg().get_bool(CFG_CHK_25519_BITS)) { + return fix_cv25519_subkey(key, true); + } + if (cfg().get_bool(CFG_FIX_25519_BITS)) { + return fix_cv25519_subkey(key, false); + } + + if (cfg().get_bool(CFG_ADD_SUBKEY)) { + return add_new_subkey(key); + } + + if (cfg().has(CFG_SET_KEY_EXPIRE)) { + return set_key_expire(key); + } + + return false; +} + +const char * +json_obj_get_str(json_object *obj, const char *key) +{ + json_object *fld = NULL; + if (!json_object_object_get_ex(obj, key, &fld)) { + return NULL; + } + return json_object_get_string(fld); +} + +static char * +cli_key_usage_str(rnp_key_handle_t key, char *buf) +{ + char *orig = buf; + bool allow = false; + + if (!rnp_key_allows_usage(key, "encrypt", &allow) && allow) { + *buf++ = 'E'; + } + allow = false; + if (!rnp_key_allows_usage(key, "sign", &allow) && allow) { + *buf++ = 'S'; + } + allow = false; + if (!rnp_key_allows_usage(key, "certify", &allow) && allow) { + *buf++ = 'C'; + } + allow = false; + if (!rnp_key_allows_usage(key, "authenticate", &allow) && allow) { + *buf++ = 'A'; + } + *buf = '\0'; + return orig; +} + +std::string +cli_rnp_escape_string(const std::string &src) +{ + static const int SPECIAL_CHARS_COUNT = 0x20; + static const char *escape_map[SPECIAL_CHARS_COUNT + 1] = { + "\\x00", "\\x01", "\\x02", "\\x03", "\\x04", "\\x05", "\\x06", "\\x07", + "\\b", "\\x09", "\\n", "\\v", "\\f", "\\r", "\\x0e", "\\x0f", + "\\x10", "\\x11", "\\x12", "\\x13", "\\x14", "\\x15", "\\x16", "\\x17", + "\\x18", "\\x19", "\\x1a", "\\x1b", "\\x1c", "\\x1d", "\\x1e", "\\x1f", + "\\x20" // space should not be auto-replaced + }; + std::string result; + // we want to replace leading and trailing spaces with escape codes to make them visible + auto original_len = src.length(); + std::string rtrimmed = src; + bool leading_space = true; + rtrimmed.erase(rtrimmed.find_last_not_of(0x20) + 1); + result.reserve(original_len); + for (char const &c : rtrimmed) { + leading_space &= c == 0x20; + if (leading_space || (c >= 0 && c < SPECIAL_CHARS_COUNT)) { + result.append(escape_map[(int) c]); + } else { + result.push_back(c); + } + } + // printing trailing spaces + for (auto pos = rtrimmed.length(); pos < original_len; pos++) { + result.append(escape_map[0x20]); + } + return result; +} + +static const std::string alg_aliases[] = { + "3DES", "TRIPLEDES", "3-DES", "TRIPLEDES", "CAST-5", "CAST5", + "AES", "AES128", "AES-128", "AES128", "AES-192", "AES192", + "AES-256", "AES256", "CAMELLIA-128", "CAMELLIA128", "CAMELLIA-192", "CAMELLIA192", + "CAMELLIA-256", "CAMELLIA256", "SHA", "SHA1", "SHA-1", "SHA1", + "SHA-224", "SHA224", "SHA-256", "SHA256", "SHA-384", "SHA384", + "SHA-512", "SHA512", "RIPEMD-160", "RIPEMD160"}; + +const std::string +cli_rnp_alg_to_ffi(const std::string alg) +{ + size_t count = sizeof(alg_aliases) / sizeof(alg_aliases[0]); + assert((count % 2) == 0); + for (size_t idx = 0; idx < count; idx += 2) { + if (rnp::str_case_eq(alg, alg_aliases[idx])) { + return alg_aliases[idx + 1]; + } + } + return alg; +} + +bool +cli_rnp_set_hash(rnp_cfg &cfg, const std::string &hash) +{ + bool supported = false; + auto &alg = cli_rnp_alg_to_ffi(hash); + if (rnp_supports_feature(RNP_FEATURE_HASH_ALG, alg.c_str(), &supported) || !supported) { + ERR_MSG("Unsupported hash algorithm: %s", hash.c_str()); + return false; + } + cfg.set_str(CFG_HASH, alg); + return true; +} + +bool +cli_rnp_set_cipher(rnp_cfg &cfg, const std::string &cipher) +{ + bool supported = false; + auto &alg = cli_rnp_alg_to_ffi(cipher); + if (rnp_supports_feature(RNP_FEATURE_SYMM_ALG, alg.c_str(), &supported) || !supported) { + ERR_MSG("Unsupported encryption algorithm: %s", cipher.c_str()); + return false; + } + cfg.set_str(CFG_CIPHER, alg); + return true; +} + +#ifndef RNP_USE_STD_REGEX +static std::string +cli_rnp_unescape_for_regcomp(const std::string &src) +{ + std::string result; + result.reserve(src.length()); + regex_t r = {}; + regmatch_t matches[1]; + if (regcomp(&r, "\\\\x[0-9a-f]([0-9a-f])?", REG_EXTENDED | REG_ICASE) != 0) + return src; + + int offset = 0; + while (regexec(&r, src.c_str() + offset, 1, matches, 0) == 0) { + result.append(src, offset, matches[0].rm_so); + int hexoff = matches[0].rm_so + 2; + std::string hex; + hex.push_back(src[offset + hexoff]); + if (hexoff + 1 < matches[0].rm_eo) { + hex.push_back(src[offset + hexoff + 1]); + } + char decoded = stoi(hex, 0, 16); + if ((decoded >= 0x7B && decoded <= 0x7D) || (decoded >= 0x24 && decoded <= 0x2E) || + decoded == 0x5C || decoded == 0x5E) { + result.push_back('\\'); + result.push_back(decoded); + } else if ((decoded == '[' || decoded == ']') && + /* not enclosed in [] */ (result.empty() || result.back() != '[')) { + result.push_back('['); + result.push_back(decoded); + result.push_back(']'); + } else { + result.push_back(decoded); + } + offset += matches[0].rm_eo; + } + + result.append(src.begin() + offset, src.end()); + + return result; +} +#endif + +/* Convert key algorithm constant to one displayed to the user */ +static const char * +cli_rnp_normalize_key_alg(const char *alg) +{ + if (!strcmp(alg, RNP_ALGNAME_EDDSA)) { + return "EdDSA"; + } + if (!strcmp(alg, RNP_ALGNAME_ELGAMAL)) { + return "ElGamal"; + } + return alg; +} + +static void +cli_rnp_print_sig_info(FILE *fp, rnp_ffi_t ffi, rnp_signature_handle_t sig) +{ + uint32_t creation = 0; + (void) rnp_signature_get_creation(sig, &creation); + + char *keyfp = NULL; + char *keyid = NULL; + (void) rnp_signature_get_key_fprint(sig, &keyfp); + (void) rnp_signature_get_keyid(sig, &keyid); + + char * signer_uid = NULL; + rnp_key_handle_t signer = NULL; + if (keyfp) { + /* Fingerprint lookup is faster */ + (void) rnp_locate_key(ffi, "fingerprint", keyfp, &signer); + } else if (keyid) { + (void) rnp_locate_key(ffi, "keyid", keyid, &signer); + } + if (signer) { + /* signer primary uid */ + (void) rnp_key_get_primary_uid(signer, &signer_uid); + } + + /* signer key id */ + fprintf(fp, "sig %s ", keyid ? rnp::lowercase(keyid) : "[no key id]"); + /* signature creation time */ + char buf[64] = {0}; + fprintf(fp, "%s", ptimestr(buf, sizeof(buf), creation)); + /* signer's userid */ + fprintf(fp, " %s", signer_uid ? signer_uid : "[unknown]"); + /* signature validity */ + const char * valmsg = NULL; + rnp_result_t validity = rnp_signature_is_valid(sig, 0); + switch (validity) { + case RNP_SUCCESS: + valmsg = ""; + break; + case RNP_ERROR_SIGNATURE_EXPIRED: + valmsg = " [expired]"; + break; + case RNP_ERROR_SIGNATURE_INVALID: + valmsg = " [invalid]"; + break; + default: + valmsg = " [unverified]"; + } + fprintf(fp, "%s\n", valmsg); + + (void) rnp_key_handle_destroy(signer); + rnp_buffer_destroy(keyid); + rnp_buffer_destroy(keyfp); + rnp_buffer_destroy(signer_uid); +} + +void +cli_rnp_print_key_info(FILE *fp, rnp_ffi_t ffi, rnp_key_handle_t key, bool psecret, bool psigs) +{ + char buf[64] = {0}; + const char *header = NULL; + bool secret = false; + bool primary = false; + + /* header */ + if (rnp_key_have_secret(key, &secret) || rnp_key_is_primary(key, &primary)) { + fprintf(fp, "Key error.\n"); + return; + } + + if (psecret && secret) { + header = primary ? "sec" : "ssb"; + } else { + header = primary ? "pub" : "sub"; + } + if (primary) { + fprintf(fp, "\n"); + } + fprintf(fp, "%s ", header); + + /* key bits */ + uint32_t bits = 0; + rnp_key_get_bits(key, &bits); + fprintf(fp, "%d/", (int) bits); + /* key algorithm */ + char *alg = NULL; + (void) rnp_key_get_alg(key, &alg); + fprintf(fp, "%s ", cli_rnp_normalize_key_alg(alg)); + /* key id */ + char *keyid = NULL; + (void) rnp_key_get_keyid(key, &keyid); + fprintf(fp, "%s", rnp::lowercase(keyid)); + /* key creation time */ + uint32_t create = 0; + (void) rnp_key_get_creation(key, &create); + fprintf(fp, " %s", ptimestr(buf, sizeof(buf), create)); + /* key usage */ + bool valid = false; + bool expired = false; + bool revoked = false; + (void) rnp_key_is_valid(key, &valid); + (void) rnp_key_is_expired(key, &expired); + (void) rnp_key_is_revoked(key, &revoked); + if (valid || expired || revoked) { + fprintf(fp, " [%s]", cli_key_usage_str(key, buf)); + } else { + fprintf(fp, " [INVALID]"); + } + /* key expiration */ + uint32_t expiry = 0; + (void) rnp_key_get_expiration(key, &expiry); + if (expiry) { + ptimestr(buf, sizeof(buf), create + expiry); + fprintf(fp, " [%s %s]", expired ? "EXPIRED" : "EXPIRES", buf); + } + /* key is revoked */ + if (revoked) { + fprintf(fp, " [REVOKED]"); + } + /* fingerprint */ + char *keyfp = NULL; + (void) rnp_key_get_fprint(key, &keyfp); + fprintf(fp, "\n %s\n", rnp::lowercase(keyfp)); + /* direct-key or binding signatures */ + if (psigs) { + size_t sigs = 0; + (void) rnp_key_get_signature_count(key, &sigs); + for (size_t i = 0; i < sigs; i++) { + rnp_signature_handle_t sig = NULL; + (void) rnp_key_get_signature_at(key, i, &sig); + if (!sig) { + continue; + } + cli_rnp_print_sig_info(fp, ffi, sig); + rnp_signature_handle_destroy(sig); + } + } + /* user ids */ + size_t uids = 0; + (void) rnp_key_get_uid_count(key, &uids); + for (size_t i = 0; i < uids; i++) { + rnp_uid_handle_t uid = NULL; + + if (rnp_key_get_uid_handle_at(key, i, &uid)) { + continue; + } + bool revoked = false; + bool valid = false; + char *uid_str = NULL; + (void) rnp_uid_is_revoked(uid, &revoked); + (void) rnp_uid_is_valid(uid, &valid); + (void) rnp_key_get_uid_at(key, i, &uid_str); + + /* userid itself with revocation status */ + fprintf(fp, "uid %s", cli_rnp_escape_string(uid_str).c_str()); + fprintf(fp, "%s\n", revoked ? " [REVOKED]" : valid ? "" : " [INVALID]"); + rnp_buffer_destroy(uid_str); + + /* print signatures only if requested */ + if (!psigs) { + (void) rnp_uid_handle_destroy(uid); + continue; + } + + size_t sigs = 0; + (void) rnp_uid_get_signature_count(uid, &sigs); + for (size_t j = 0; j < sigs; j++) { + rnp_signature_handle_t sig = NULL; + (void) rnp_uid_get_signature_at(uid, j, &sig); + if (!sig) { + continue; + } + cli_rnp_print_sig_info(fp, ffi, sig); + rnp_signature_handle_destroy(sig); + } + (void) rnp_uid_handle_destroy(uid); + } + + rnp_buffer_destroy(alg); + rnp_buffer_destroy(keyid); + rnp_buffer_destroy(keyfp); +} + +bool +cli_rnp_save_keyrings(cli_rnp_t *rnp) +{ + rnp_output_t output = NULL; + rnp_result_t pub_ret = 0; + rnp_result_t sec_ret = 0; + const std::string &ppath = rnp->pubpath(); + const std::string &spath = rnp->secpath(); + + // check whether we have G10 secret keyring - then need to create directory + if (rnp->secformat() == "G10") { + struct stat path_stat; + if (rnp_stat(spath.c_str(), &path_stat) != -1) { + if (!S_ISDIR(path_stat.st_mode)) { + ERR_MSG("G10 keystore should be a directory: %s", spath.c_str()); + return false; + } + } else { + if (errno != ENOENT) { + ERR_MSG("stat(%s): %s", spath.c_str(), strerror(errno)); + return false; + } + if (RNP_MKDIR(spath.c_str(), S_IRWXU) != 0) { + ERR_MSG("mkdir(%s, S_IRWXU): %s", spath.c_str(), strerror(errno)); + return false; + } + } + } + + // public keyring + if (!(pub_ret = rnp_output_to_path(&output, ppath.c_str()))) { + pub_ret = + rnp_save_keys(rnp->ffi, rnp->pubformat().c_str(), output, RNP_LOAD_SAVE_PUBLIC_KEYS); + rnp_output_destroy(output); + } + if (pub_ret) { + ERR_MSG("failed to write pubring to path '%s'", ppath.c_str()); + } + + // secret keyring + if (!(sec_ret = rnp_output_to_path(&output, spath.c_str()))) { + sec_ret = + rnp_save_keys(rnp->ffi, rnp->secformat().c_str(), output, RNP_LOAD_SAVE_SECRET_KEYS); + rnp_output_destroy(output); + } + if (sec_ret) { + ERR_MSG("failed to write secring to path '%s'", spath.c_str()); + } + + return !pub_ret && !sec_ret; +} + +bool +cli_rnp_generate_key(cli_rnp_t *rnp, const char *username) +{ + /* set key generation parameters to rnp_cfg_t */ + rnp_cfg &cfg = rnp->cfg(); + if (!cli_rnp_set_generate_params(cfg)) { + ERR_MSG("Key generation setup failed."); + return false; + } + /* generate the primary key */ + rnp_op_generate_t genkey = NULL; + rnp_key_handle_t primary = NULL; + rnp_key_handle_t subkey = NULL; + bool res = false; + + if (rnp_op_generate_create(&genkey, rnp->ffi, cfg.get_cstr(CFG_KG_PRIMARY_ALG))) { + ERR_MSG("Failed to initialize key generation."); + return false; + } + if (username && rnp_op_generate_set_userid(genkey, username)) { + ERR_MSG("Failed to set userid."); + goto done; + } + if (cfg.has(CFG_KG_PRIMARY_BITS) && + rnp_op_generate_set_bits(genkey, cfg.get_int(CFG_KG_PRIMARY_BITS))) { + ERR_MSG("Failed to set key bits."); + goto done; + } + if (cfg.has(CFG_KG_PRIMARY_CURVE) && + rnp_op_generate_set_curve(genkey, cfg.get_cstr(CFG_KG_PRIMARY_CURVE))) { + ERR_MSG("Failed to set key curve."); + goto done; + } + if (cfg.has(CFG_KG_PRIMARY_EXPIRATION)) { + uint32_t expiration = 0; + if (!cfg.get_expiration(CFG_KG_PRIMARY_EXPIRATION, expiration) || + rnp_op_generate_set_expiration(genkey, expiration)) { + ERR_MSG("Failed to set primary key expiration."); + goto done; + } + } + // TODO : set DSA qbits + if (rnp_op_generate_set_hash(genkey, cfg.get_cstr(CFG_KG_HASH))) { + ERR_MSG("Failed to set hash algorithm."); + goto done; + } + + fprintf(rnp->userio_out, "Generating a new key...\n"); + if (rnp_op_generate_execute(genkey) || rnp_op_generate_get_key(genkey, &primary)) { + ERR_MSG("Primary key generation failed."); + goto done; + } + + if (!cfg.has(CFG_KG_SUBKEY_ALG)) { + res = true; + goto done; + } + + rnp_op_generate_destroy(genkey); + genkey = NULL; + if (rnp_op_generate_subkey_create( + &genkey, rnp->ffi, primary, cfg.get_cstr(CFG_KG_SUBKEY_ALG))) { + ERR_MSG("Failed to initialize subkey generation."); + goto done; + } + if (cfg.has(CFG_KG_SUBKEY_BITS) && + rnp_op_generate_set_bits(genkey, cfg.get_int(CFG_KG_SUBKEY_BITS))) { + ERR_MSG("Failed to set subkey bits."); + goto done; + } + if (cfg.has(CFG_KG_SUBKEY_CURVE) && + rnp_op_generate_set_curve(genkey, cfg.get_cstr(CFG_KG_SUBKEY_CURVE))) { + ERR_MSG("Failed to set subkey curve."); + goto done; + } + if (cfg.has(CFG_KG_SUBKEY_EXPIRATION)) { + uint32_t expiration = 0; + if (!cfg.get_expiration(CFG_KG_SUBKEY_EXPIRATION, expiration) || + rnp_op_generate_set_expiration(genkey, expiration)) { + ERR_MSG("Failed to set subkey expiration."); + goto done; + } + } + // TODO : set DSA qbits + if (rnp_op_generate_set_hash(genkey, cfg.get_cstr(CFG_KG_HASH))) { + ERR_MSG("Failed to set hash algorithm."); + goto done; + } + if (rnp_op_generate_execute(genkey) || rnp_op_generate_get_key(genkey, &subkey)) { + ERR_MSG("Subkey generation failed."); + goto done; + } + + // protect + for (auto key : {primary, subkey}) { + char *password = NULL; + if (rnp_request_password(rnp->ffi, key, "protect", &password)) { + ERR_MSG("Failed to obtain protection password."); + goto done; + } + if (*password) { + rnp_result_t ret = rnp_key_protect(key, + password, + cfg.get_cstr(CFG_KG_PROT_ALG), + NULL, + cfg.get_cstr(CFG_KG_PROT_HASH), + cfg.get_int(CFG_KG_PROT_ITERATIONS)); + rnp_buffer_clear(password, strlen(password) + 1); + rnp_buffer_destroy(password); + if (ret) { + ERR_MSG("Failed to protect key."); + goto done; + } + } else { + rnp_buffer_destroy(password); + } + } + res = cli_rnp_save_keyrings(rnp); +done: + if (res) { + cli_rnp_print_key_info(stdout, rnp->ffi, primary, true, false); + if (subkey) { + cli_rnp_print_key_info(stdout, rnp->ffi, subkey, true, false); + } + } + rnp_op_generate_destroy(genkey); + rnp_key_handle_destroy(primary); + rnp_key_handle_destroy(subkey); + return res; +} + +static bool +key_matches_string(rnp_key_handle_t handle, const std::string &str) +{ + bool matches = false; + char * id = NULL; + size_t idlen = 0; +#ifndef RNP_USE_STD_REGEX + regex_t r = {}; +#else + std::regex re; +#endif + size_t uid_count = 0; + bool boolres = false; + + if (str.empty()) { + matches = true; + goto done; + } + if (rnp::is_hex(str) && (str.length() >= RNP_KEYID_SIZE)) { + std::string hexstr = rnp::strip_hex(str); + size_t len = hexstr.length(); + + /* check whether it's key id */ + if ((len == RNP_KEYID_SIZE * 2) || (len == RNP_KEYID_SIZE)) { + if (rnp_key_get_keyid(handle, &id)) { + goto done; + } + if ((idlen = strlen(id)) < len) { + goto done; + } + if (strncasecmp(hexstr.c_str(), id + idlen - len, len) == 0) { + matches = true; + goto done; + } + rnp_buffer_destroy(id); + id = NULL; + } + + /* check fingerprint */ + if (len == RNP_FP_SIZE * 2) { + if (rnp_key_get_fprint(handle, &id)) { + goto done; + } + if (strlen(id) != len) { + goto done; + } + if (strncasecmp(hexstr.c_str(), id, len) == 0) { + matches = true; + goto done; + } + rnp_buffer_destroy(id); + id = NULL; + } + + /* check grip */ + if (len == RNP_GRIP_SIZE * 2) { + if (rnp_key_get_grip(handle, &id)) { + goto done; + } + if (strlen(id) != len) { + goto done; + } + if (strncasecmp(hexstr.c_str(), id, len) == 0) { + matches = true; + goto done; + } + rnp_buffer_destroy(id); + id = NULL; + } + /* let then search for hex userid */ + } + + /* no need to check for userid over the subkey */ + if (rnp_key_is_sub(handle, &boolres) || boolres) { + goto done; + } + if (rnp_key_get_uid_count(handle, &uid_count) || (uid_count == 0)) { + goto done; + } + +#ifndef RNP_USE_STD_REGEX + /* match on full name or email address as a NOSUB, ICASE regexp */ + if (regcomp(&r, cli_rnp_unescape_for_regcomp(str).c_str(), REG_EXTENDED | REG_ICASE) != + 0) { + goto done; + } +#else + try { + re.assign(str, std::regex_constants::ECMAScript | std::regex_constants::icase); + } catch (const std::exception &e) { + ERR_MSG("Invalid regular expression : %s, error %s.", str.c_str(), e.what()); + goto done; + } +#endif + + for (size_t idx = 0; idx < uid_count; idx++) { + if (rnp_key_get_uid_at(handle, idx, &id)) { + goto regdone; + } +#ifndef RNP_USE_STD_REGEX + if (regexec(&r, id, 0, NULL, 0) == 0) { + matches = true; + goto regdone; + } +#else + if (std::regex_search(id, re)) { + matches = true; + goto regdone; + } +#endif + rnp_buffer_destroy(id); + id = NULL; + } + +regdone: +#ifndef RNP_USE_STD_REGEX + regfree(&r); +#endif +done: + rnp_buffer_destroy(id); + return matches; +} + +static bool +key_matches_flags(rnp_key_handle_t key, int flags) +{ + /* check whether secret key search is requested */ + bool secret = false; + if (rnp_key_have_secret(key, &secret)) { + return false; + } + if ((flags & CLI_SEARCH_SECRET) && !secret) { + return false; + } + /* check whether no subkeys allowed */ + bool subkey = false; + if (rnp_key_is_sub(key, &subkey)) { + return false; + } + if (!subkey) { + return true; + } + if (!(flags & CLI_SEARCH_SUBKEYS)) { + return false; + } + /* check whether subkeys should be put after primary (if it is available) */ + if ((flags & CLI_SEARCH_SUBKEYS_AFTER) != CLI_SEARCH_SUBKEYS_AFTER) { + return true; + } + + char *grip = NULL; + if (rnp_key_get_primary_grip(key, &grip)) { + return false; + } + if (!grip) { + return true; + } + rnp_buffer_destroy(grip); + return false; +} + +void +clear_key_handles(std::vector<rnp_key_handle_t> &keys) +{ + for (auto handle : keys) { + rnp_key_handle_destroy(handle); + } + keys.clear(); +} + +static bool +add_key_to_array(rnp_ffi_t ffi, + std::vector<rnp_key_handle_t> &keys, + rnp_key_handle_t key, + int flags) +{ + bool subkey = false; + bool subkeys = (flags & CLI_SEARCH_SUBKEYS_AFTER) == CLI_SEARCH_SUBKEYS_AFTER; + if (rnp_key_is_sub(key, &subkey)) { + return false; + } + + try { + keys.push_back(key); + } catch (const std::exception &e) { + ERR_MSG("%s", e.what()); + return false; + } + if (!subkeys || subkey) { + return true; + } + + std::vector<rnp_key_handle_t> subs; + size_t sub_count = 0; + if (rnp_key_get_subkey_count(key, &sub_count)) { + goto error; + } + + try { + for (size_t i = 0; i < sub_count; i++) { + rnp_key_handle_t sub_handle = NULL; + if (rnp_key_get_subkey_at(key, i, &sub_handle)) { + goto error; + } + subs.push_back(sub_handle); + } + std::move(subs.begin(), subs.end(), std::back_inserter(keys)); + } catch (const std::exception &e) { + ERR_MSG("%s", e.what()); + goto error; + } + return true; +error: + keys.pop_back(); + clear_key_handles(subs); + return false; +} + +bool +cli_rnp_keys_matching_string(cli_rnp_t * rnp, + std::vector<rnp_key_handle_t> &keys, + const std::string & str, + int flags) +{ + bool res = false; + rnp_identifier_iterator_t it = NULL; + rnp_key_handle_t handle = NULL; + const char * fp = NULL; + + /* iterate through the keys */ + if (rnp_identifier_iterator_create(rnp->ffi, &it, "fingerprint")) { + return false; + } + + while (!rnp_identifier_iterator_next(it, &fp)) { + if (!fp) { + break; + } + if (rnp_locate_key(rnp->ffi, "fingerprint", fp, &handle) || !handle) { + goto done; + } + if (!key_matches_flags(handle, flags) || !key_matches_string(handle, str.c_str())) { + rnp_key_handle_destroy(handle); + continue; + } + if (!add_key_to_array(rnp->ffi, keys, handle, flags)) { + rnp_key_handle_destroy(handle); + goto done; + } + if (flags & CLI_SEARCH_FIRST_ONLY) { + res = true; + goto done; + } + } + res = !keys.empty(); +done: + rnp_identifier_iterator_destroy(it); + return res; +} + +bool +cli_rnp_keys_matching_strings(cli_rnp_t * rnp, + std::vector<rnp_key_handle_t> & keys, + const std::vector<std::string> &strs, + int flags) +{ + bool res = false; + clear_key_handles(keys); + + for (const std::string &str : strs) { + if (!cli_rnp_keys_matching_string(rnp, keys, str, flags & ~CLI_SEARCH_DEFAULT)) { + ERR_MSG("Cannot find key matching \"%s\"", str.c_str()); + goto done; + } + } + + /* search for default key */ + if (keys.empty() && (flags & CLI_SEARCH_DEFAULT)) { + if (rnp->defkey().empty()) { + ERR_MSG("No userid or default key for operation"); + goto done; + } + cli_rnp_keys_matching_string(rnp, keys, rnp->defkey(), flags & ~CLI_SEARCH_DEFAULT); + if (keys.empty()) { + ERR_MSG("Default key not found"); + } + } + res = !keys.empty(); +done: + if (!res) { + clear_key_handles(keys); + } + return res; +} + +static bool +rnp_cfg_set_ks_info(rnp_cfg &cfg) +{ + if (cfg.get_bool(CFG_KEYSTORE_DISABLED)) { + cfg.set_str(CFG_KR_PUB_PATH, ""); + cfg.set_str(CFG_KR_SEC_PATH, ""); + cfg.set_str(CFG_KR_PUB_FORMAT, RNP_KEYSTORE_GPG); + cfg.set_str(CFG_KR_SEC_FORMAT, RNP_KEYSTORE_GPG); + return true; + } + + /* getting path to keyrings. If it is specified by user in 'homedir' param then it is + * considered as the final path */ + bool defhomedir = false; + std::string homedir = cfg.get_str(CFG_HOMEDIR); + if (homedir.empty()) { + homedir = rnp::path::HOME(); + defhomedir = true; + } + + /* Check whether $HOME or homedir exists */ + struct stat st; + if (rnp_stat(homedir.c_str(), &st) || rnp_access(homedir.c_str(), R_OK | W_OK)) { + ERR_MSG("Home directory '%s' does not exist or is not writable!", homedir.c_str()); + return false; + } + + /* creating home dir if needed */ + if (defhomedir) { + char *rnphome = NULL; + if (rnp_get_default_homedir(&rnphome)) { + ERR_MSG("Failed to obtain default home directory."); + return false; + } + homedir = rnphome; + rnp_buffer_destroy(rnphome); + if (!rnp::path::exists(homedir, true) && RNP_MKDIR(homedir.c_str(), 0700) == -1 && + errno != EEXIST) { + ERR_MSG("Cannot mkdir '%s' errno = %d", homedir.c_str(), errno); + return false; + } + } + + /* detecting key storage format */ + std::string ks_format = cfg.get_str(CFG_KEYSTOREFMT); + if (ks_format.empty()) { + char *pub_format = NULL; + char *sec_format = NULL; + char *pubpath = NULL; + char *secpath = NULL; + rnp_detect_homedir_info(homedir.c_str(), &pub_format, &pubpath, &sec_format, &secpath); + bool detected = pub_format && sec_format && pubpath && secpath; + if (detected) { + cfg.set_str(CFG_KR_PUB_FORMAT, pub_format); + cfg.set_str(CFG_KR_SEC_FORMAT, sec_format); + cfg.set_str(CFG_KR_PUB_PATH, pubpath); + cfg.set_str(CFG_KR_SEC_PATH, secpath); + } else { + /* default to GPG */ + ks_format = RNP_KEYSTORE_GPG; + } + rnp_buffer_destroy(pub_format); + rnp_buffer_destroy(sec_format); + rnp_buffer_destroy(pubpath); + rnp_buffer_destroy(secpath); + if (detected) { + return true; + } + } + + std::string pub_format = RNP_KEYSTORE_GPG; + std::string sec_format = RNP_KEYSTORE_GPG; + std::string pubpath; + std::string secpath; + + if (ks_format == RNP_KEYSTORE_GPG) { + pubpath = rnp::path::append(homedir, PUBRING_GPG); + secpath = rnp::path::append(homedir, SECRING_GPG); + } else if (ks_format == RNP_KEYSTORE_GPG21) { + pubpath = rnp::path::append(homedir, PUBRING_KBX); + secpath = rnp::path::append(homedir, SECRING_G10); + pub_format = RNP_KEYSTORE_KBX; + sec_format = RNP_KEYSTORE_G10; + } else if (ks_format == RNP_KEYSTORE_KBX) { + pubpath = rnp::path::append(homedir, PUBRING_KBX); + secpath = rnp::path::append(homedir, SECRING_KBX); + pub_format = RNP_KEYSTORE_KBX; + sec_format = RNP_KEYSTORE_KBX; + } else if (ks_format == RNP_KEYSTORE_G10) { + pubpath = rnp::path::append(homedir, PUBRING_G10); + secpath = rnp::path::append(homedir, SECRING_G10); + pub_format = RNP_KEYSTORE_G10; + sec_format = RNP_KEYSTORE_G10; + } else { + ERR_MSG("Unsupported keystore format: \"%s\"", ks_format.c_str()); + return false; + } + + /* Check whether homedir is empty */ + if (rnp::path::empty(homedir)) { + ERR_MSG("Keyring directory '%s' is empty.\nUse \"rnpkeys\" command to generate a new " + "key or import existing keys from the file or GnuPG keyrings.", + homedir.c_str()); + } + + cfg.set_str(CFG_KR_PUB_PATH, pubpath); + cfg.set_str(CFG_KR_SEC_PATH, secpath); + cfg.set_str(CFG_KR_PUB_FORMAT, pub_format); + cfg.set_str(CFG_KR_SEC_FORMAT, sec_format); + return true; +} + +static void +rnp_cfg_set_defkey(rnp_cfg &cfg) +{ + /* If a userid has been given, we'll use it. */ + std::string userid = cfg.get_count(CFG_USERID) ? cfg.get_str(CFG_USERID, 0) : ""; + if (!userid.empty()) { + cfg.set_str(CFG_KR_DEF_KEY, userid); + } +} + +bool +cli_cfg_set_keystore_info(rnp_cfg &cfg) +{ + /* detecting keystore paths and format */ + if (!rnp_cfg_set_ks_info(cfg)) { + return false; + } + + /* default key/userid */ + rnp_cfg_set_defkey(cfg); + return true; +} + +static bool +is_stdinout_spec(const std::string &spec) +{ + return spec.empty() || (spec == "-"); +} + +rnp_input_t +cli_rnp_input_from_specifier(cli_rnp_t &rnp, const std::string &spec, bool *is_path) +{ + rnp_input_t input = NULL; + rnp_result_t res = RNP_ERROR_GENERIC; + bool path = false; + if (is_stdinout_spec(spec)) { + /* input from stdin */ + res = rnp_input_from_stdin(&input); + } else if ((spec.size() > 4) && (spec.compare(0, 4, "env:") == 0)) { + /* input from an environment variable */ + const char *envval = getenv(spec.c_str() + 4); + if (!envval) { + ERR_MSG("Failed to get value of the environment variable '%s'.", spec.c_str() + 4); + return NULL; + } + res = rnp_input_from_memory(&input, (const uint8_t *) envval, strlen(envval), true); + } else { + /* input from path */ + res = rnp_input_from_path(&input, spec.c_str()); + path = true; + } + + if (res) { + return NULL; + } + if (is_path) { + *is_path = path; + } + return input; +} + +rnp_output_t +cli_rnp_output_to_specifier(cli_rnp_t &rnp, const std::string &spec, bool discard) +{ + rnp_output_t output = NULL; + rnp_result_t res = RNP_ERROR_GENERIC; + std::string path = spec; + if (discard) { + res = rnp_output_to_null(&output); + } else if (is_stdinout_spec(spec)) { + res = rnp_output_to_stdout(&output); + } else if (!rnp_get_output_filename(spec, path, rnp)) { + if (spec.empty()) { + ERR_MSG("Operation failed: no output filename specified"); + } else { + ERR_MSG("Operation failed: file '%s' already exists.", spec.c_str()); + } + res = RNP_ERROR_BAD_PARAMETERS; + } else { + res = rnp_output_to_file(&output, path.c_str(), RNP_OUTPUT_FILE_OVERWRITE); + } + return res ? NULL : output; +} + +bool +cli_rnp_export_keys(cli_rnp_t *rnp, const char *filter) +{ + bool secret = rnp->cfg().get_bool(CFG_SECRET); + int flags = secret ? CLI_SEARCH_SECRET : 0; + std::vector<rnp_key_handle_t> keys; + + if (!cli_rnp_keys_matching_string(rnp, keys, filter, flags)) { + ERR_MSG("Key(s) matching '%s' not found.", filter); + return false; + } + + rnp_output_t output = NULL; + rnp_output_t armor = NULL; + uint32_t base_flags = secret ? RNP_KEY_EXPORT_SECRET : RNP_KEY_EXPORT_PUBLIC; + bool result = false; + + output = cli_rnp_output_to_specifier(*rnp, rnp->cfg().get_str(CFG_OUTFILE)); + if (!output) { + goto done; + } + + /* We need single armored stream for all of the keys */ + if (rnp_output_to_armor(output, &armor, secret ? "secret key" : "public key")) { + goto done; + } + + for (auto key : keys) { + uint32_t flags = base_flags; + bool primary = false; + + if (rnp_key_is_primary(key, &primary)) { + goto done; + } + if (primary) { + flags = flags | RNP_KEY_EXPORT_SUBKEYS; + } + + if (rnp_key_export(key, armor, flags)) { + goto done; + } + } + result = !rnp_output_finish(armor); +done: + rnp_output_destroy(armor); + rnp_output_destroy(output); + clear_key_handles(keys); + return result; +} + +bool +cli_rnp_export_revocation(cli_rnp_t *rnp, const char *key) +{ + std::vector<rnp_key_handle_t> keys; + if (!cli_rnp_keys_matching_string(rnp, keys, key, 0)) { + ERR_MSG("Key matching '%s' not found.", key); + return false; + } + if (keys.size() > 1) { + ERR_MSG("Ambiguous input: too many keys found for '%s'.", key); + clear_key_handles(keys); + return false; + } + rnp_output_t output = NULL; + bool result = false; + + output = cli_rnp_output_to_specifier(*rnp, rnp->cfg().get_str(CFG_OUTFILE)); + if (!output) { + goto done; + } + + result = !rnp_key_export_revocation(keys[0], + output, + RNP_KEY_EXPORT_ARMORED, + rnp->cfg().get_cstr(CFG_HASH), + rnp->cfg().get_cstr(CFG_REV_TYPE), + rnp->cfg().get_cstr(CFG_REV_REASON)); +done: + rnp_output_destroy(output); + clear_key_handles(keys); + return result; +} + +bool +cli_rnp_revoke_key(cli_rnp_t *rnp, const char *key) +{ + std::vector<rnp_key_handle_t> keys; + if (!cli_rnp_keys_matching_string(rnp, keys, key, CLI_SEARCH_SUBKEYS)) { + ERR_MSG("Key matching '%s' not found.", key); + return false; + } + bool res = false; + bool revoked = false; + rnp_result_t ret = 0; + + if (keys.size() > 1) { + ERR_MSG("Ambiguous input: too many keys found for '%s'.", key); + goto done; + } + if (rnp_key_is_revoked(keys[0], &revoked)) { + ERR_MSG("Error getting key revocation status."); + goto done; + } + if (revoked && !rnp->cfg().get_bool(CFG_FORCE)) { + ERR_MSG("Error: key '%s' is revoked already. Use --force to generate another " + "revocation signature.", + key); + goto done; + } + + ret = rnp_key_revoke(keys[0], + 0, + rnp->cfg().get_cstr(CFG_HASH), + rnp->cfg().get_cstr(CFG_REV_TYPE), + rnp->cfg().get_cstr(CFG_REV_REASON)); + if (ret) { + ERR_MSG("Failed to revoke a key: error %d", (int) ret); + goto done; + } + res = cli_rnp_save_keyrings(rnp); + /* print info about the revoked key */ + if (res) { + bool subkey = false; + char *grip = NULL; + if (rnp_key_is_sub(keys[0], &subkey)) { + ERR_MSG("Failed to get key info"); + goto done; + } + ret = + subkey ? rnp_key_get_primary_grip(keys[0], &grip) : rnp_key_get_grip(keys[0], &grip); + if (ret || !grip) { + ERR_MSG("Failed to get primary key grip."); + goto done; + } + clear_key_handles(keys); + if (!cli_rnp_keys_matching_string(rnp, keys, grip, CLI_SEARCH_SUBKEYS_AFTER)) { + ERR_MSG("Failed to search for revoked key."); + rnp_buffer_destroy(grip); + goto done; + } + rnp_buffer_destroy(grip); + for (auto handle : keys) { + cli_rnp_print_key_info(rnp->userio_out, rnp->ffi, handle, false, false); + } + } +done: + clear_key_handles(keys); + return res; +} + +bool +cli_rnp_remove_key(cli_rnp_t *rnp, const char *key) +{ + std::vector<rnp_key_handle_t> keys; + if (!cli_rnp_keys_matching_string(rnp, keys, key, CLI_SEARCH_SUBKEYS)) { + ERR_MSG("Key matching '%s' not found.", key); + return false; + } + bool res = false; + bool secret = false; + bool primary = false; + uint32_t flags = RNP_KEY_REMOVE_PUBLIC; + rnp_result_t ret = 0; + + if (keys.size() > 1) { + ERR_MSG("Ambiguous input: too many keys found for '%s'.", key); + goto done; + } + if (rnp_key_have_secret(keys[0], &secret)) { + ERR_MSG("Error getting secret key presence."); + goto done; + } + if (rnp_key_is_primary(keys[0], &primary)) { + ERR_MSG("Key error."); + goto done; + } + + if (secret) { + flags |= RNP_KEY_REMOVE_SECRET; + } + if (primary) { + flags |= RNP_KEY_REMOVE_SUBKEYS; + } + + if (secret && !rnp->cfg().get_bool(CFG_FORCE)) { + if (!cli_rnp_get_confirmation( + rnp, + "Key '%s' has corresponding secret key. Do you really want to delete it?", + key)) { + goto done; + } + } + + ret = rnp_key_remove(keys[0], flags); + + if (ret) { + ERR_MSG("Failed to remove the key: error %d", (int) ret); + goto done; + } + res = cli_rnp_save_keyrings(rnp); +done: + clear_key_handles(keys); + return res; +} + +bool +cli_rnp_add_key(cli_rnp_t *rnp) +{ + const std::string &path = rnp->cfg().get_str(CFG_KEYFILE); + if (path.empty()) { + return false; + } + + rnp_input_t input = cli_rnp_input_from_specifier(*rnp, path, NULL); + if (!input) { + ERR_MSG("failed to open key from %s", path.c_str()); + return false; + } + + bool res = !rnp_import_keys( + rnp->ffi, input, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS, NULL); + rnp_input_destroy(input); + + // set default key if we didn't have one + if (res && rnp->defkey().empty()) { + rnp->set_defkey(); + } + return res; +} + +static bool +strip_extension(std::string &src) +{ + size_t dpos = src.find_last_of('.'); + if (dpos == std::string::npos) { + return false; + } + src.resize(dpos); + return true; +} + +static bool +has_extension(const std::string &path, const std::string &ext) +{ + if (path.length() < ext.length()) { + return false; + } + return path.compare(path.length() - ext.length(), ext.length(), ext) == 0; +} + +static std::string +output_extension(const rnp_cfg &cfg, Operation op) +{ + switch (op) { + case Operation::EncryptOrSign: { + bool armor = cfg.get_bool(CFG_ARMOR); + if (cfg.get_bool(CFG_DETACHED)) { + return armor ? EXT_ASC : EXT_SIG; + } + if (cfg.get_bool(CFG_CLEARTEXT)) { + return EXT_ASC; + } + return armor ? EXT_ASC : EXT_PGP; + } + case Operation::Enarmor: + return EXT_ASC; + default: + return ""; + } +} + +static bool +has_pgp_extension(const std::string &path) +{ + return has_extension(path, EXT_PGP) || has_extension(path, EXT_ASC) || + has_extension(path, EXT_GPG); +} + +static std::string +output_strip_extension(Operation op, const std::string &in) +{ + std::string out = in; + if ((op == Operation::Verify) && (has_pgp_extension(out))) { + strip_extension(out); + return out; + } + if ((op == Operation::Dearmor) && (has_extension(out, EXT_ASC))) { + strip_extension(out); + return out; + } + return ""; +} + +static std::string +extract_filename(const std::string path) +{ + size_t lpos = path.find_last_of("/\\"); + if (lpos == std::string::npos) { + return path; + } + return path.substr(lpos + 1); +} + +bool +cli_rnp_t::init_io(Operation op, rnp_input_t *input, rnp_output_t *output) +{ + const std::string &in = cfg().get_str(CFG_INFILE); + bool is_pathin = true; + if (input) { + *input = cli_rnp_input_from_specifier(*this, in, &is_pathin); + if (!*input) { + return false; + } + } + /* Update CFG_SETFNAME to insert into literal packet */ + if (!cfg().has(CFG_SETFNAME) && !is_pathin) { + cfg().set_str(CFG_SETFNAME, ""); + } + + if (!output) { + return true; + } + std::string out = cfg().get_str(CFG_OUTFILE); + bool discard = (op == Operation::Verify) && out.empty() && cfg().get_bool(CFG_NO_OUTPUT); + + if (out.empty() && is_pathin && !discard) { + /* Attempt to guess whether to add or strip extension for known cases */ + std::string ext = output_extension(cfg(), op); + if (!ext.empty()) { + out = in + ext; + } else { + out = output_strip_extension(op, in); + } + } + + *output = cli_rnp_output_to_specifier(*this, out, discard); + if (!*output && input) { + rnp_input_destroy(*input); + *input = NULL; + } + return *output; +} + +bool +cli_rnp_dump_file(cli_rnp_t *rnp) +{ + rnp_input_t input = NULL; + rnp_output_t output = NULL; + uint32_t flags = 0; + uint32_t jflags = 0; + + if (rnp->cfg().get_bool(CFG_GRIPS)) { + flags |= RNP_DUMP_GRIP; + jflags |= RNP_JSON_DUMP_GRIP; + } + if (rnp->cfg().get_bool(CFG_MPIS)) { + flags |= RNP_DUMP_MPI; + jflags |= RNP_JSON_DUMP_MPI; + } + if (rnp->cfg().get_bool(CFG_RAW)) { + flags |= RNP_DUMP_RAW; + jflags |= RNP_JSON_DUMP_RAW; + } + + rnp_result_t ret = 0; + if (!rnp->init_io(Operation::Dump, &input, &output)) { + ERR_MSG("failed to open source or create output"); + ret = 1; + goto done; + } + + if (rnp->cfg().get_bool(CFG_JSON)) { + char *json = NULL; + ret = rnp_dump_packets_to_json(input, jflags, &json); + if (!ret) { + size_t len = strlen(json); + size_t written = 0; + ret = rnp_output_write(output, json, len, &written); + if (written < len) { + ret = RNP_ERROR_WRITE; + } + // add trailing empty line + if (!ret) { + ret = rnp_output_write(output, "\n", 1, &written); + } + if (written < 1) { + ret = RNP_ERROR_WRITE; + } + rnp_buffer_destroy(json); + } + } else { + ret = rnp_dump_packets_to_output(input, output, flags); + } + rnp_input_destroy(input); + rnp_output_destroy(output); +done: + return !ret; +} + +bool +cli_rnp_armor_file(cli_rnp_t *rnp) +{ + rnp_input_t input = NULL; + rnp_output_t output = NULL; + + if (!rnp->init_io(Operation::Enarmor, &input, &output)) { + ERR_MSG("failed to open source or create output"); + return false; + } + rnp_result_t ret = rnp_enarmor(input, output, rnp->cfg().get_cstr(CFG_ARMOR_DATA_TYPE)); + rnp_input_destroy(input); + rnp_output_destroy(output); + return !ret; +} + +bool +cli_rnp_dearmor_file(cli_rnp_t *rnp) +{ + rnp_input_t input = NULL; + rnp_output_t output = NULL; + + if (!rnp->init_io(Operation::Dearmor, &input, &output)) { + ERR_MSG("failed to open source or create output"); + return false; + } + + rnp_result_t ret = rnp_dearmor(input, output); + rnp_input_destroy(input); + rnp_output_destroy(output); + return !ret; +} + +static bool +cli_rnp_sign(const rnp_cfg &cfg, cli_rnp_t *rnp, rnp_input_t input, rnp_output_t output) +{ + rnp_op_sign_t op = NULL; + rnp_result_t ret = RNP_ERROR_GENERIC; + bool cleartext = cfg.get_bool(CFG_CLEARTEXT); + bool detached = cfg.get_bool(CFG_DETACHED); + + if (cleartext) { + ret = rnp_op_sign_cleartext_create(&op, rnp->ffi, input, output); + } else if (detached) { + ret = rnp_op_sign_detached_create(&op, rnp->ffi, input, output); + } else { + ret = rnp_op_sign_create(&op, rnp->ffi, input, output); + } + + if (ret) { + ERR_MSG("failed to initialize signing"); + return false; + } + + /* setup sign operation via cfg */ + bool res = false; + std::vector<std::string> signers; + std::vector<rnp_key_handle_t> signkeys; + + if (!cleartext) { + rnp_op_sign_set_armor(op, cfg.get_bool(CFG_ARMOR)); + } + + if (!cleartext && !detached) { + if (cfg.has(CFG_SETFNAME)) { + if (rnp_op_sign_set_file_name(op, cfg.get_str(CFG_SETFNAME).c_str())) { + goto done; + } + } else if (cfg.has(CFG_INFILE)) { + const std::string &fname = cfg.get_str(CFG_INFILE); + if (rnp_op_sign_set_file_name(op, extract_filename(fname).c_str())) { + goto done; + } + rnp_op_sign_set_file_mtime(op, rnp_filemtime(fname.c_str())); + } + if (rnp_op_sign_set_compression(op, cfg.get_cstr(CFG_ZALG), cfg.get_int(CFG_ZLEVEL))) { + goto done; + } + } + + if (rnp_op_sign_set_hash(op, cfg.get_hashalg().c_str())) { + goto done; + } + rnp_op_sign_set_creation_time(op, cfg.get_sig_creation()); + { + uint32_t expiration = 0; + if (cfg.get_expiration(CFG_EXPIRATION, expiration)) { + rnp_op_sign_set_expiration_time(op, expiration); + } + } + + /* signing keys */ + signers = cfg.get_list(CFG_SIGNERS); + if (!cli_rnp_keys_matching_strings(rnp, + signkeys, + signers, + CLI_SEARCH_SECRET | CLI_SEARCH_DEFAULT | + CLI_SEARCH_SUBKEYS | CLI_SEARCH_FIRST_ONLY)) { + ERR_MSG("Failed to build signing keys list"); + goto done; + } + for (rnp_key_handle_t key : signkeys) { + if (rnp_op_sign_add_signature(op, key, NULL)) { + ERR_MSG("Failed to add signature"); + goto done; + } + } + + /* execute sign operation */ + res = !rnp_op_sign_execute(op); +done: + clear_key_handles(signkeys); + rnp_op_sign_destroy(op); + return res; +} + +static bool +cli_rnp_encrypt_and_sign(const rnp_cfg &cfg, + cli_rnp_t * rnp, + rnp_input_t input, + rnp_output_t output) +{ + rnp_op_encrypt_t op = NULL; + + if (rnp_op_encrypt_create(&op, rnp->ffi, input, output)) { + ERR_MSG("failed to initialize encryption"); + return false; + } + + std::string fname; + std::string aalg; + std::vector<rnp_key_handle_t> enckeys; + std::vector<rnp_key_handle_t> signkeys; + bool res = false; + rnp_result_t ret; + + rnp_op_encrypt_set_armor(op, cfg.get_bool(CFG_ARMOR)); + + if (cfg.has(CFG_SETFNAME)) { + if (rnp_op_encrypt_set_file_name(op, cfg.get_str(CFG_SETFNAME).c_str())) { + goto done; + } + } else if (cfg.has(CFG_INFILE)) { + const std::string &fname = cfg.get_str(CFG_INFILE); + if (rnp_op_encrypt_set_file_name(op, extract_filename(fname).c_str())) { + goto done; + } + rnp_op_encrypt_set_file_mtime(op, rnp_filemtime(fname.c_str())); + } + + if (rnp_op_encrypt_set_compression(op, cfg.get_cstr(CFG_ZALG), cfg.get_int(CFG_ZLEVEL))) { + goto done; + } + if (rnp_op_encrypt_set_cipher(op, cfg.get_cstr(CFG_CIPHER))) { + goto done; + } + if (rnp_op_encrypt_set_hash(op, cfg.get_hashalg().c_str())) { + goto done; + } + aalg = cfg.has(CFG_AEAD) ? cfg.get_str(CFG_AEAD) : "None"; + if (rnp_op_encrypt_set_aead(op, aalg.c_str())) { + goto done; + } + if (cfg.has(CFG_AEAD_CHUNK) && + rnp_op_encrypt_set_aead_bits(op, cfg.get_int(CFG_AEAD_CHUNK))) { + goto done; + } + if (cfg.has(CFG_NOWRAP) && rnp_op_encrypt_set_flags(op, RNP_ENCRYPT_NOWRAP)) { + goto done; + } + + /* adding passwords if password-based encryption is used */ + if (cfg.get_bool(CFG_ENCRYPT_SK)) { + std::string halg = cfg.get_hashalg(); + std::string ealg = cfg.get_str(CFG_CIPHER); + size_t iterations = cfg.get_int(CFG_S2K_ITER); + size_t msec = cfg.get_int(CFG_S2K_MSEC); + + if (msec != DEFAULT_S2K_MSEC) { + if (rnp_calculate_iterations(halg.c_str(), msec, &iterations)) { + ERR_MSG("Failed to calculate S2K iterations"); + goto done; + } + } + + for (int i = 0; i < cfg.get_int(CFG_PASSWORDC, 1); i++) { + if (rnp_op_encrypt_add_password( + op, NULL, halg.c_str(), iterations, ealg.c_str())) { + ERR_MSG("Failed to add encrypting password"); + goto done; + } + } + } + + /* adding encrypting keys if pk-encryption is used */ + if (cfg.get_bool(CFG_ENCRYPT_PK)) { + std::vector<std::string> keynames = cfg.get_list(CFG_RECIPIENTS); + if (!cli_rnp_keys_matching_strings(rnp, + enckeys, + keynames, + CLI_SEARCH_DEFAULT | CLI_SEARCH_SUBKEYS | + CLI_SEARCH_FIRST_ONLY)) { + ERR_MSG("Failed to build recipients key list"); + goto done; + } + for (rnp_key_handle_t key : enckeys) { + if (rnp_op_encrypt_add_recipient(op, key)) { + ERR_MSG("Failed to add recipient"); + goto done; + } + } + } + + /* adding signatures if encrypt-and-sign is used */ + if (cfg.get_bool(CFG_SIGN_NEEDED)) { + rnp_op_encrypt_set_creation_time(op, cfg.get_sig_creation()); + uint32_t expiration; + if (cfg.get_expiration(CFG_EXPIRATION, expiration)) { + rnp_op_encrypt_set_expiration_time(op, expiration); + } + + /* signing keys */ + std::vector<std::string> keynames = cfg.get_list(CFG_SIGNERS); + if (!cli_rnp_keys_matching_strings(rnp, + signkeys, + keynames, + CLI_SEARCH_SECRET | CLI_SEARCH_DEFAULT | + CLI_SEARCH_SUBKEYS | CLI_SEARCH_FIRST_ONLY)) { + ERR_MSG("Failed to build signing keys list"); + goto done; + } + for (rnp_key_handle_t key : signkeys) { + if (rnp_op_encrypt_add_signature(op, key, NULL)) { + ERR_MSG("Failed to add signature"); + goto done; + } + } + } + + /* execute encrypt or encrypt-and-sign operation */ + ret = rnp_op_encrypt_execute(op); + res = (ret == RNP_SUCCESS); + if (ret != RNP_SUCCESS) { + ERR_MSG("Operation failed: %s", rnp_result_to_string(ret)); + } +done: + clear_key_handles(signkeys); + clear_key_handles(enckeys); + rnp_op_encrypt_destroy(op); + return res; +} + +bool +cli_rnp_setup(cli_rnp_t *rnp) +{ + /* unset CFG_PASSWD and empty CFG_PASSWD are different cases */ + if (rnp->cfg().has(CFG_PASSWD)) { + const std::string &passwd = rnp->cfg().get_str(CFG_PASSWD); + if (rnp_ffi_set_pass_provider( + rnp->ffi, ffi_pass_callback_string, (void *) passwd.c_str())) { + return false; + } + } + rnp->pswdtries = rnp->cfg().get_pswdtries(); + return true; +} + +bool +cli_rnp_check_weak_hash(cli_rnp_t *rnp) +{ + if (rnp->cfg().has(CFG_WEAK_HASH)) { + return true; + } + + uint32_t security_level = 0; + + if (rnp_get_security_rule(rnp->ffi, + RNP_FEATURE_HASH_ALG, + rnp->cfg().get_hashalg().c_str(), + rnp->cfg().time(), + NULL, + NULL, + &security_level)) { + ERR_MSG("Failed to get security rules for hash algorithm \'%s\'!", + rnp->cfg().get_hashalg().c_str()); + return false; + } + + if (security_level < RNP_SECURITY_DEFAULT) { + ERR_MSG("Hash algorithm \'%s\' is cryptographically weak!", + rnp->cfg().get_hashalg().c_str()); + return false; + } + /* TODO: check other weak algorithms and key sizes */ + return true; +} + +bool +cli_rnp_protect_file(cli_rnp_t *rnp) +{ + rnp_input_t input = NULL; + rnp_output_t output = NULL; + + if (!rnp->init_io(Operation::EncryptOrSign, &input, &output)) { + ERR_MSG("failed to open source or create output"); + return false; + } + + bool res = false; + bool sign = rnp->cfg().get_bool(CFG_SIGN_NEEDED); + bool encrypt = rnp->cfg().get_bool(CFG_ENCRYPT_PK) || rnp->cfg().get_bool(CFG_ENCRYPT_SK); + if (sign && !encrypt) { + res = cli_rnp_sign(rnp->cfg(), rnp, input, output); + } else if (encrypt) { + res = cli_rnp_encrypt_and_sign(rnp->cfg(), rnp, input, output); + } else { + ERR_MSG("No operation specified"); + } + + rnp_input_destroy(input); + rnp_output_destroy(output); + return res; +} + +/* helper function which prints something like 'using RSA (Sign-Only) key 0x0102030405060708 */ +static void +cli_rnp_print_sig_key_info(FILE *resfp, rnp_signature_handle_t sig) +{ + char *keyid = NULL; + char *alg = NULL; + + (void) rnp_signature_get_keyid(sig, &keyid); + rnp::lowercase(keyid); + (void) rnp_signature_get_alg(sig, &alg); + + fprintf(resfp, + "using %s key %s\n", + cli_rnp_normalize_key_alg(alg), + keyid ? keyid : "0000000000000000"); + rnp_buffer_destroy(keyid); + rnp_buffer_destroy(alg); +} + +static void +cli_rnp_print_signatures(cli_rnp_t *rnp, const std::vector<rnp_op_verify_signature_t> &sigs) +{ + unsigned invalidc = 0; + unsigned unknownc = 0; + unsigned validc = 0; + std::string title = "UNKNOWN signature"; + FILE * resfp = rnp->resfp; + + for (auto sig : sigs) { + rnp_result_t status = rnp_op_verify_signature_get_status(sig); + switch (status) { + case RNP_SUCCESS: + title = "Good signature"; + validc++; + break; + case RNP_ERROR_SIGNATURE_EXPIRED: + title = "EXPIRED signature"; + invalidc++; + break; + case RNP_ERROR_SIGNATURE_INVALID: + title = "BAD signature"; + invalidc++; + break; + case RNP_ERROR_KEY_NOT_FOUND: + title = "NO PUBLIC KEY for signature"; + unknownc++; + break; + case RNP_ERROR_SIGNATURE_UNKNOWN: + title = "UNKNOWN signature"; + unknownc++; + break; + default: + title = "UNKNOWN signature status"; + break; + } + + if (status == RNP_ERROR_SIGNATURE_UNKNOWN) { + fprintf(resfp, "%s\n", title.c_str()); + continue; + } + + uint32_t create = 0; + uint32_t expiry = 0; + rnp_op_verify_signature_get_times(sig, &create, &expiry); + + time_t crtime = create; + auto str = rnp_ctime(crtime); + fprintf(resfp, + "%s made %s%s", + title.c_str(), + rnp_y2k38_warning(crtime) ? ">=" : "", + str.c_str()); + if (expiry) { + crtime = rnp_timeadd(crtime, expiry); + str = rnp_ctime(crtime); + fprintf( + resfp, "Valid until %s%s\n", rnp_y2k38_warning(crtime) ? ">=" : "", str.c_str()); + } + + rnp_signature_handle_t handle = NULL; + if (rnp_op_verify_signature_get_handle(sig, &handle)) { + ERR_MSG("Failed to obtain signature handle."); + continue; + } + + cli_rnp_print_sig_key_info(resfp, handle); + rnp_key_handle_t key = NULL; + + if ((status != RNP_ERROR_KEY_NOT_FOUND) && !rnp_signature_get_signer(handle, &key)) { + cli_rnp_print_key_info(resfp, rnp->ffi, key, false, false); + rnp_key_handle_destroy(key); + } + rnp_signature_handle_destroy(handle); + } + + if (!sigs.size()) { + ERR_MSG("No signature(s) found - is this a signed file?"); + return; + } + if (!invalidc && !unknownc) { + ERR_MSG("Signature(s) verified successfully"); + return; + } + /* Show a proper error message if there are invalid/unknown signatures */ + auto si = invalidc > 1 ? "s" : ""; + auto su = unknownc > 1 ? "s" : ""; + auto fail = "Signature verification failure: "; + if (invalidc && !unknownc) { + ERR_MSG("%s%u invalid signature%s", fail, invalidc, si); + } else if (!invalidc && unknownc) { + ERR_MSG("%s%u unknown signature%s", fail, unknownc, su); + } else { + ERR_MSG("%s%u invalid signature%s, %u unknown signature%s", + fail, + invalidc, + si, + unknownc, + su); + } +} + +static void +cli_rnp_inform_of_hidden_recipient(rnp_op_verify_t op) +{ + size_t recipients = 0; + rnp_op_verify_get_recipient_count(op, &recipients); + if (!recipients) { + return; + } + for (size_t idx = 0; idx < recipients; idx++) { + rnp_recipient_handle_t recipient = NULL; + rnp_op_verify_get_recipient_at(op, idx, &recipient); + char *keyid = NULL; + rnp_recipient_get_keyid(recipient, &keyid); + bool hidden = keyid && !strcmp(keyid, "0000000000000000"); + rnp_buffer_destroy(keyid); + if (hidden) { + ERR_MSG("Warning: message has hidden recipient, but it was ignored. Use " + "--allow-hidden to override this."); + break; + } + } +} + +bool +cli_rnp_process_file(cli_rnp_t *rnp) +{ + rnp_input_t input = NULL; + if (!rnp->init_io(Operation::Verify, &input, NULL)) { + ERR_MSG("failed to open source"); + return false; + } + + char *contents = NULL; + if (rnp_guess_contents(input, &contents)) { + ERR_MSG("failed to check source contents"); + rnp_input_destroy(input); + return false; + } + + /* source data for detached signature verification */ + rnp_input_t source = NULL; + rnp_output_t output = NULL; + rnp_op_verify_t verify = NULL; + rnp_result_t ret = RNP_ERROR_GENERIC; + bool res = false; + std::vector<rnp_op_verify_signature_t> sigs; + size_t scount = 0; + + if (rnp::str_case_eq(contents, "signature")) { + /* detached signature */ + std::string in = rnp->cfg().get_str(CFG_INFILE); + std::string src = rnp->cfg().get_str(CFG_SOURCE); + if (is_stdinout_spec(in) && is_stdinout_spec(src)) { + ERR_MSG("Detached signature and signed source cannot be both stdin."); + goto done; + } + if (src.empty() && !has_extension(in, EXT_SIG) && !has_extension(in, EXT_ASC)) { + ERR_MSG("Unsupported detached signature extension. Use --source to override."); + goto done; + } + if (src.empty()) { + src = in; + /* cannot fail as we checked for extension previously */ + strip_extension(src); + } + source = cli_rnp_input_from_specifier(*rnp, src, NULL); + if (!source) { + ERR_MSG("Failed to open source for detached signature verification."); + goto done; + } + + ret = rnp_op_verify_detached_create(&verify, rnp->ffi, source, input); + if (!ret) { + /* Currently CLI requires all signatures to be valid for success */ + ret = rnp_op_verify_set_flags(verify, RNP_VERIFY_REQUIRE_ALL_SIGS); + } + } else { + if (!rnp->init_io(Operation::Verify, NULL, &output)) { + ERR_MSG("Failed to create output stream."); + goto done; + } + ret = rnp_op_verify_create(&verify, rnp->ffi, input, output); + if (!ret) { + uint32_t flags = 0; + if (!rnp->cfg().get_bool(CFG_NO_OUTPUT)) { + /* This would happen if user requested decryption instead of verification */ + flags = flags | RNP_VERIFY_IGNORE_SIGS_ON_DECRYPT; + } else { + /* Currently CLI requires all signatures to be valid for success */ + flags = flags | RNP_VERIFY_REQUIRE_ALL_SIGS; + } + if (rnp->cfg().get_bool(CFG_ALLOW_HIDDEN)) { + /* Allow hidden recipient */ + flags = flags | RNP_VERIFY_ALLOW_HIDDEN_RECIPIENT; + } + ret = rnp_op_verify_set_flags(verify, flags); + } + } + if (ret) { + ERR_MSG("Failed to initialize verification/decryption operation."); + goto done; + } + + res = !rnp_op_verify_execute(verify); + + /* Check whether we had hidden recipient on verification/decryption failure */ + if (!res && !rnp->cfg().get_bool(CFG_ALLOW_HIDDEN)) { + cli_rnp_inform_of_hidden_recipient(verify); + } + + rnp_op_verify_get_signature_count(verify, &scount); + if (!scount) { + goto done; + } + + for (size_t i = 0; i < scount; i++) { + rnp_op_verify_signature_t sig = NULL; + if (rnp_op_verify_get_signature_at(verify, i, &sig)) { + ERR_MSG("Failed to obtain signature info."); + res = false; + goto done; + } + try { + sigs.push_back(sig); + } catch (const std::exception &e) { + ERR_MSG("%s", e.what()); + res = false; + goto done; + } + } + cli_rnp_print_signatures(rnp, sigs); +done: + rnp_buffer_destroy(contents); + rnp_input_destroy(input); + rnp_input_destroy(source); + rnp_output_destroy(output); + rnp_op_verify_destroy(verify); + return res; +} + +void +cli_rnp_print_praise(void) +{ + printf("%s\n%s\n", PACKAGE_STRING, PACKAGE_BUGREPORT); + printf("Backend: %s\n", rnp_backend_string()); + printf("Backend version: %s\n", rnp_backend_version()); + printf("Supported algorithms:\n"); + cli_rnp_print_feature(stdout, RNP_FEATURE_PK_ALG, "Public key"); + cli_rnp_print_feature(stdout, RNP_FEATURE_SYMM_ALG, "Encryption"); + cli_rnp_print_feature(stdout, RNP_FEATURE_AEAD_ALG, "AEAD"); + cli_rnp_print_feature(stdout, RNP_FEATURE_PROT_MODE, "Key protection"); + cli_rnp_print_feature(stdout, RNP_FEATURE_HASH_ALG, "Hash"); + cli_rnp_print_feature(stdout, RNP_FEATURE_COMP_ALG, "Compression"); + cli_rnp_print_feature(stdout, RNP_FEATURE_CURVE, "Curves"); + printf("Please report security issues at (https://www.rnpgp.org/feedback) and\n" + "general bugs at https://github.com/rnpgp/rnp/issues.\n"); +} + +void +cli_rnp_print_feature(FILE *fp, const char *type, const char *printed_type) +{ + char * result = NULL; + size_t count; + if (rnp_supported_features(type, &result) != RNP_SUCCESS) { + ERR_MSG("Failed to list supported features: %s", type); + return; + } + json_object *jso = json_tokener_parse(result); + if (!jso) { + ERR_MSG("Failed to parse JSON with features: %s", type); + goto done; + } + fprintf(fp, "%s: ", printed_type); + count = json_object_array_length(jso); + for (size_t idx = 0; idx < count; idx++) { + json_object *val = json_object_array_get_idx(jso, idx); + fprintf(fp, " %s%s", json_object_get_string(val), idx < count - 1 ? "," : ""); + } + fputs("\n", fp); + fflush(fp); + json_object_put(jso); +done: + rnp_buffer_destroy(result); +} diff --git a/comm/third_party/rnp/src/rnp/fficli.h b/comm/third_party/rnp/src/rnp/fficli.h new file mode 100644 index 0000000000..7db29dc1af --- /dev/null +++ b/comm/third_party/rnp/src/rnp/fficli.h @@ -0,0 +1,295 @@ +/* + * Copyright (c) 2019-2021, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef FFICLI_H_ +#define FFICLI_H_ + +#include <stddef.h> +#include <stdbool.h> +#include <time.h> +#include "rnp/rnp.h" +#include "rnp/rnp_err.h" +#include "config.h" +#include "rnpcfg.h" +#include "json.h" + +enum class Operation { EncryptOrSign, Verify, Enarmor, Dearmor, Dump }; + +class cli_rnp_t { + private: + rnp_cfg cfg_{}; +#ifdef _WIN32 + int subst_argc{}; + char **subst_argv{}; +#endif + bool load_keyring(bool secret); + bool is_cv25519_subkey(rnp_key_handle_t handle); + bool get_protection(rnp_key_handle_t handle, + std::string & hash, + std::string & cipher, + size_t & iterations); + bool check_cv25519_bits(rnp_key_handle_t key, char *prot_password, bool &tweaked); + + public: + rnp_ffi_t ffi{}; + FILE * resfp{}; /* where to put result messages, defaults to stdout */ + FILE * passfp{}; /* file pointer for password input */ + FILE * userio_in{}; /* file pointer for user's inputs */ + FILE * userio_out{}; /* file pointer for user's outputs */ + int pswdtries{}; /* number of password tries, -1 for unlimited */ + bool reuse_password_for_subkey{}; + std::string reuse_primary_fprint; + char * reused_password{}; + bool hidden_msg{}; /* true if hidden recipient message was displayed */ + + static int ret_code(bool success); + + ~cli_rnp_t(); +#ifdef _WIN32 + void substitute_args(int *argc, char ***argv); +#endif + bool init(const rnp_cfg &cfg); + void end(); + + bool init_io(Operation op, rnp_input_t *input, rnp_output_t *output); + + bool load_keyrings(bool loadsecret = false); + + const std::string & + defkey() + { + return cfg_.get_str(CFG_KR_DEF_KEY); + } + + void set_defkey(); + + const std::string & + pubpath() + { + return cfg_.get_str(CFG_KR_PUB_PATH); + } + + const std::string & + secpath() + { + return cfg_.get_str(CFG_KR_SEC_PATH); + } + + const std::string & + pubformat() + { + return cfg_.get_str(CFG_KR_PUB_FORMAT); + } + + const std::string & + secformat() + { + return cfg_.get_str(CFG_KR_SEC_FORMAT); + } + + rnp_cfg & + cfg() + { + return cfg_; + } + + bool fix_cv25519_subkey(const std::string &key, bool checkonly = false); + + bool add_new_subkey(const std::string &key); + + bool set_key_expire(const std::string &key); + + bool edit_key(const std::string &key); +}; + +typedef enum cli_search_flags_t { + CLI_SEARCH_SECRET = 1 << 0, /* search secret keys only */ + CLI_SEARCH_SUBKEYS = 1 << 1, /* add subkeys as well */ + CLI_SEARCH_FIRST_ONLY = 1 << 2, /* return only first key matching */ + CLI_SEARCH_SUBKEYS_AFTER = + (1 << 3) | CLI_SEARCH_SUBKEYS, /* put subkeys after the primary key */ + CLI_SEARCH_DEFAULT = 1 << 4 /* add default key if nothing found */ +} cli_search_flags_t; + +/** + * @brief Set keystore parameters to the rnp_cfg_t. This includes keyring paths, types and + * default key. + * + * @param cfg pointer to the allocated rnp_cfg_t structure + * @return true on success or false otherwise. + * @return false + */ +bool cli_cfg_set_keystore_info(rnp_cfg &cfg); + +/** + * @brief Create input object from the specifier, which may represent: + * - path + * - stdin (if `-` or empty string is passed) + * - environment variable contents, if path looks like `env:VARIABLE_NAME` + * @param rnp initialized CLI rnp object + * @param spec specifier + * @param is_path optional parameter. If specifier is path (not stdin, env variable), then true + * will be stored here, false otherwise. May be NULL if this information is not + * needed. + * @return rnp_input_t object or NULL if operation failed. + */ +rnp_input_t cli_rnp_input_from_specifier(cli_rnp_t & rnp, + const std::string &spec, + bool * is_path); + +/** + * @brief Create output object from the specifier, which may represent: + * - path + * - stdout (if `-` or empty string is passed) + * + * @param rnp initialized CLI rnp object + * @param spec specifier + * @param discard just discard output + * @return rnp_output_t or NULL if operation failed. + */ +rnp_output_t cli_rnp_output_to_specifier(cli_rnp_t & rnp, + const std::string &spec, + bool discard = false); + +bool cli_rnp_save_keyrings(cli_rnp_t *rnp); +void cli_rnp_print_key_info( + FILE *fp, rnp_ffi_t ffi, rnp_key_handle_t key, bool psecret, bool psigs); +bool cli_rnp_set_generate_params(rnp_cfg &cfg, bool subkey = false); +bool cli_rnp_generate_key(cli_rnp_t *rnp, const char *username); +/** + * @brief Find key(s) matching set of flags and search string. + * + * @param rnp initialized cli_rnp_t object. + * @param keys search results will be added here, leaving already existing items. + * @param str search string: may be part of the userid, keyid, fingerprint or grip. + * @param flags combination of the following flags: + * CLI_SEARCH_SECRET : require key to be secret, + * CLI_SEARCH_SUBKEYS : include subkeys to the results (see + * CLI_SEARCH_SUBKEYS_AFTER description). + * CLI_SEARCH_FIRST_ONLY : include only first key found + * CLI_SEARCH_SUBKEYS_AFTER : for each primary key add its subkeys after the main + * key. This changes behaviour of subkey search, since those will be added only + * if subkey is orphaned or primary key matches search. + * @return true if operation succeeds and at least one key is found, or false otherwise. + */ +bool cli_rnp_keys_matching_string(cli_rnp_t * rnp, + std::vector<rnp_key_handle_t> &keys, + const std::string & str, + int flags); +/** + * @brief Find key(s) matching set of flags and search string(s). + * + * @param rnp initialized cli_rnp_t object. + * @param keys search results will be put here, overwriting vector's contents. + * @param strs set of search strings, may be empty. + * @param flags the same flags as for cli_rnp_keys_matching_string(), except additional one: + * CLI_SEARCH_DEFAULT : if no key is found then default key from cli_rnp_t will be + * searched. + * @return true if operation succeeds and at least one key is found for each search string, or + * false otherwise. + */ +bool cli_rnp_keys_matching_strings(cli_rnp_t * rnp, + std::vector<rnp_key_handle_t> & keys, + const std::vector<std::string> &strs, + int flags); +bool cli_rnp_export_keys(cli_rnp_t *rnp, const char *filter); +bool cli_rnp_export_revocation(cli_rnp_t *rnp, const char *key); +bool cli_rnp_revoke_key(cli_rnp_t *rnp, const char *key); +bool cli_rnp_remove_key(cli_rnp_t *rnp, const char *key); +bool cli_rnp_add_key(cli_rnp_t *rnp); +bool cli_rnp_dump_file(cli_rnp_t *rnp); +bool cli_rnp_armor_file(cli_rnp_t *rnp); +bool cli_rnp_dearmor_file(cli_rnp_t *rnp); +bool cli_rnp_check_weak_hash(cli_rnp_t *rnp); +bool cli_rnp_setup(cli_rnp_t *rnp); +bool cli_rnp_protect_file(cli_rnp_t *rnp); +bool cli_rnp_process_file(cli_rnp_t *rnp); +std::string cli_rnp_escape_string(const std::string &src); +void cli_rnp_print_praise(void); +void cli_rnp_print_feature(FILE *fp, const char *type, const char *printed_type); +/** + * @brief Convert algorithm name representation to one used by FFI. + * I.e. aes-128 to AES128, 3DES to TRIPLEDES, SHA-1 to SHA1 and so on. + * + * @param alg algorithm string + * @return string with FFI algorithm's name. In case alias is not found the source string will + * be returned. + */ +const std::string cli_rnp_alg_to_ffi(const std::string alg); + +/** + * @brief Attempt to set hash algorithm using the value provided. + * + * @param cfg config + * @param hash algorithm name. + * @return true if algorithm is supported and set correctly, or false otherwise. + */ +bool cli_rnp_set_hash(rnp_cfg &cfg, const std::string &hash); + +/** + * @brief Attempt to set symmetric cipher algorithm using the value provided. + * + * @param cfg config + * @param cipher algorithm name. + * @return true if algorithm is supported and set correctly, or false otherwise. + */ +bool cli_rnp_set_cipher(rnp_cfg &cfg, const std::string &cipher); + +void clear_key_handles(std::vector<rnp_key_handle_t> &keys); + +const char *json_obj_get_str(json_object *obj, const char *key); + +#ifdef _WIN32 +bool rnp_win_substitute_cmdline_args(int *argc, char ***argv); +void rnp_win_clear_args(int argc, char **argv); +#endif + +/* TODO: we should decide what to do with functions/constants/defines below */ +#define RNP_KEYID_SIZE 8 +#define RNP_FP_SIZE 20 +#define RNP_GRIP_SIZE 20 + +#define ERR_MSG(...) \ + do { \ + (void) fprintf((stderr), __VA_ARGS__); \ + (void) fprintf((stderr), "\n"); \ + } while (0) + +#define EXT_ASC (".asc") +#define EXT_SIG (".sig") +#define EXT_PGP (".pgp") +#define EXT_GPG (".gpg") + +#define SUBDIRECTORY_GNUPG ".gnupg" +#define SUBDIRECTORY_RNP ".rnp" +#define PUBRING_KBX "pubring.kbx" +#define SECRING_KBX "secring.kbx" +#define PUBRING_GPG "pubring.gpg" +#define SECRING_GPG "secring.gpg" +#define PUBRING_G10 "public-keys-v1.d" +#define SECRING_G10 "private-keys-v1.d" + +#endif diff --git a/comm/third_party/rnp/src/rnp/moz.build b/comm/third_party/rnp/src/rnp/moz.build new file mode 100644 index 0000000000..d9ef04d51b --- /dev/null +++ b/comm/third_party/rnp/src/rnp/moz.build @@ -0,0 +1,64 @@ +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +Program("rnp-cli") + +include("../../../rnpdefs.mozbuild") + +USE_LIBS += ["rnp"] + +LOCAL_INCLUDES = [ + "!../lib", + "..", + "../../include", + "../common", + "../lib", +] + +if CONFIG["MZLA_SYSTEM_JSONC"]: + CXXFLAGS += CONFIG["MZLA_JSONC_CFLAGS"] + LDFLAGS += CONFIG["MZLA_JSONC_LIBS"] + ["-Wl,-rpath,$ORIGIN"] + +else: + USE_LIBS += ["json-c"] + LOCAL_INCLUDES += ["!/comm/third_party/json-c", "/comm/third_party/json-c"] + LDFLAGS += ["-Wl,-rpath,$ORIGIN"] + +SOURCES += [ + "../common/file-utils.cpp", + "../common/str-utils.cpp", + "../common/time-utils.cpp", + "../lib/logging.cpp", + "../rnpkeys/tui.cpp", + "fficli.cpp", + "rnp.cpp", + "rnpcfg.cpp", +] + +if CONFIG["CC_TYPE"] == "clang-cl": + CXXFLAGS += [ + "/EHs", + "-Wno-deprecated-declarations", + ] + LOCAL_INCLUDES += [ + "/comm/third_party/niwcompat", + ] + + OS_LIBS += ["shell32"] + + if CONFIG["CPU_ARCH"] == "x86": + LDFLAGS += ["clang_rt.builtins-i386.lib"] + elif CONFIG["CPU_ARCH"] == "x86_64": + LDFLAGS += ["clang_rt.builtins-x86_64.lib"] + + SOURCES += [ + "/comm/third_party/niwcompat/getopt.c", + ] + SOURCES["rnpcfg.cpp"].flags += [ + "-FI", + "%s/comm/third_party/niwcompat/extra_include.h" % TOPSRCDIR, + ] + DEFINES["MOZILLA_CONFIG_H"] = True + DEFINES["_CRT_SECURE_NO_WARNINGS"] = True diff --git a/comm/third_party/rnp/src/rnp/rnp.1.adoc b/comm/third_party/rnp/src/rnp/rnp.1.adoc new file mode 100644 index 0000000000..ce1173e49a --- /dev/null +++ b/comm/third_party/rnp/src/rnp/rnp.1.adoc @@ -0,0 +1,431 @@ += rnp(1) +RNP +:doctype: manpage +:release-version: {component-version} +:man manual: RNP Manual +:man source: RNP {release-version} + +== NAME + +RNP - OpenPGP-compatible signatures and encryption. + +== SYNOPSIS + +*rnp* [_--homedir_ _dir_] [_OPTIONS_] _COMMAND_ [_INPUT_FILE_, ...] ... + + +== DESCRIPTION + +The _rnp_ command-line utility is part of the _RNP_ suite and +provides OpenPGP signing and encryption functionality +compliant with IETF RFC 4880. + +_rnp_ does not allow manipulation of keys or keyrings -- +please use _rnpkeys(1)_ for that purpose. + +=== BASICS + +By default, *rnp* will apply a _COMMAND_, additionally configured with _OPTIONS_, +to all _INPUT_FILE_(s) or _stdin_ if no _INPUT_FILE_ is given. +There are some special cases for _INPUT_FILE_ : + +* _-_ (dash) substitutes to _stdin_ +* env:VARIABLE_NAME substitutes to the contents of environment variable VARIABLE_NAME + +Depending on the input, output may be written: + +* if *--output* option is given output is written to the path specified (or to the *stdout* if *-* is used) +* to the _INPUT_FILE_ with a removed or added file extension (_.pgp_, _.gpg_, _.asc_, _.sig_), depending on operation. +* to the _stdout_ if input was read from the _stdin_. + +If output file already exists, it will *not* be overwritten, unless *--overwrite* option is given. + +Without the *--armor* option, output will be in binary. + +If _COMMAND_ requires public or private keys, *rnp* will look for the keyrings in *~/.rnp*. The options *--homedir* and *--keyfile* override this (see below). + +If _COMMAND_ needs a password, *rnp* will ask for it via *stdin* or *tty*, +unless the *--password* or *--pass-fd* option was specified. + + +== COMMANDS + +=== INFORMATIONAL + +*-h*, *--help*:: +Displays a short help message. No options are expected. + +*-V*, *--version*:: +Displays version information. No options are expected. + + +=== ENCRYPTION AND SIGNING + +*-e*, *--encrypt*:: +Encrypt data with public key(s), and optionally sign, if the *--sign* command is added. + ++ +You would likely want to specify one or more *--recipient*(s) or pick a *--cipher* (instead of the default). ++ +Additional options: + +*--recipient*::: +Specify one or more recipients. + +*--cipher*::: +Select a specific cipher. + +*-z 0..9*, *--zlib*, *--zip*, *--bzip*::: +Select a compression algorithm and level. + +*--armor*::: +Output ASCII data instead of binary via the *--armor* option. If the input file is _file.ext_, and *--output* is not specified, then the data will be written (depending on *--armor* option) to _file.ext.pgp_ or _file.ext.asc_. + + +*--no-wrap*::: +Do not wrap the output in literal data packet. This could be used to encrypt a file which is already signed or encrypted. +By default this would also disable compression, use option *-z* to override. + +*--overwrite*::: +If the destination file already exists, and the *--overwrite* option is not given, the caller will be asked for the permission to overwrite or to provide a new file name. Please see the *OPTIONS* section for more information. + +*-c*, *--symmetric*:: +Encrypt data with password(s). + ++ +Can be combined with the commands *--encrypt* and *--sign*. ++ +Options that apply to the *--encrypt* command also apply here. ++ +Additional options: + +*--passwords*::: +Encryption to multiple passwords is possible with *--passwords* option. Each password would be asked via stdin/tty unless *--password* or *--pass-fd* is specified. + + +*-s*, *--sign*:: +Digitally sign data, using one or more secret keys you own. + ++ +Public-key or password-based encryption may be added via the *--encrypt* and *--symmetric* commands. + ++ +Additional options: + +*-u*, *--userid*::: +By default, the first secret key you own will be selected for signing. Apply this option to select a different key or to use multiple keys. + +*--detach*::: +By default, the signature is stored together with signed data. This option detaches the data signature to a separate file (_file.ext.sig_). + +*--hash*::: +You may want to use *--hash* option to override default hash algorithm settings. As with encryption, output may be converted to ascii via the *--armor* option. + ++ +Compression options also apply here. Since the secret key is usually stored encrypted, you will be asked for the password to decrypt it via _stdin_/_tty_ unless *--password* or *--pass-fd* is specified. + +*--clearsign*:: +Digitally sign text data, producing human-readable output with the signature attached. + ++ +In this mode, data cannot be additionally encrypted or compressed. ++ +Other signing options, *--hash*, *-u*, *--password*, can still be used here. + +=== DECRYPTION AND VERIFICATION + +*-d*, *--decrypt*:: +Decrypt and verify data from the _INPUT_FILE_ or stdin. + ++ +If the data is signed, signature verification information will be printed to _stdout_/_tty_. ++ +Additional options: + +*--output*::: +Override the default output selection with a file name or stdout specifier (*_-_*). For the default output path selection see the *BASICS* section. + +*--password*, *--pass-fd*::: +Depending on encryption options, you may be asked for the password of one of your secret keys, or for the encryption password. These options override that behavior such that you can input the password through automated means. + +*-v*, *--verify*:: +Verify signature(s) without writing embedded data out, if any (unless option _--output_ is specified). + ++ +To verify the detached signature of a file _file.ext_, the detached signature file in the file name pattern of _file.ext.sig_ or _file.ext.asc_ must exist. + ++ +Also you may use option *--source* to specify the exact source for the signed data. + ++ +If data is encrypted, you may be asked for password as in the *--decrypt* command. + +=== OTHER COMMANDS + +*--list-packets*:: +Show detailed information about the OpenPGP data in _INPUT_FILE_ or stdin. +Useful for curiosity, troubleshooting or debugging. + ++ +Additional options can be used: + +*--json*::: output JSON data instead of human-readable information +*--grips*::: print out key fingerprints and grips +*--mpi*::: print out all MPI values +*--raw*::: print raw, hex-encoded packets too + +*--enarmor*[=_msg_|_pubkey_|_seckey_|_sign_]:: +Convert binary data to the ASCII-armored as per OpenPGP standard. +This includes the `-----BEGIN PGP MESSAGE-----` header and footer, +and Base64-encoded data. + ++ +Output for _file.ext_ will be written to _file.ext.asc_ (if it does not exist) +or to _stdout_. + ++ +The following OpenPGP headers may be specified: ++ +-- +*msg* (default) ::: _-----BEGIN PGP MESSAGE-----_ +*pubkey*::: _-----BEGIN PGP PUBLIC KEY BLOCK-----_ +*seckey*::: _-----BEGIN PGP SECRET KEY BLOCK-----_ +*sign*::: _-----BEGIN PGP SIGNATURE-----_ +-- ++ +Additional options: + +*--overwrite*::: +Forcefully overwrite existing destination file if it exists. + +*--output*::: +Specify destination file path. + + +*--dearmor*:: +Attempts to convert data from an armored format to the binary format. + ++ +The _file.ext.asc_ output file would be written to _file.ext_. +If the destination file already exists, it will prompt the user +for a new filename. ++ +Additional options: + +*--overwrite*::: +Forcefully overwrite existing destination file if it exists. + +*--output*::: +Specify destination file path. + + +== OPTIONS + +*--home*, *--homedir* _DIR_:: +Change homedir (where RNP looks for keyrings) to the specified value. + ++ +The default homedir is _~/.rnp_ . + +*-f*, *--keyfile* _PATH_:: +Instead of loading keyrings, use key(s) from the file specified. + +*-u*, *--userid* _KEY_:: +Specify one or more signing keys, searching for it via the given value _KEY_. +See *rnpkeys(1)* on how to find valid values. + +*-r*, *--recipient* _KEY_:: +Add the message recipient, i.e. the public key to which message will be encrypted to. +See *rnpkeys(1)* on how to find valid values. + +*--armor*, *--ascii*:: +Apply ASCII armoring to the output, so that the resulting output +can be transferred as plain text. + ++ +See IETF RFC 4880 for more details. + +*--detach*, *--detached*:: +Create a detached signature. + +*--output* _PATH_:: +Write data processing related output to the file specified. + ++ +If not specified, the output filename will be guessed from +the input filename/extension or the command will prompt the user +via _stdin_/_tty_. + +*--overwrite*:: +Overwrite already existing files without prompt. + +*--source*:: +Specify signed data for the detached signature verification (_-_ and _env:_ substitutions may be used here). + + +*--hash* _ALGORITHM_:: +Set hash algorithm which to be used for signing and derivation +of the encryption key from a password. + ++ +The default value is _SHA256_. + +*--cipher* _ALGORITHM_:: +Set the symmetric algorithm used during encryption. + ++ +The default value is _AES256_. + +*--aead* [_EAX_, _OCB_]:: +Enable AEAD encryption and select algorithm to be used. + +*--aead-chunk-bits* _BITS_:: +Change AEAD chunk size bits, from 0 to 16 (actual chunk size would be 1 << (6 + bits)). See OpenPGP documentation for the details. + + +*--zip*, *--zlib*, *--bzip2*:: +Select corresponding algorithm to compress data with. +Please refer to IETF RFC 4880 for details. + +*-z* _0..9_:: +Set compression level for the compression algorithms. + ++ +*9* is the highest compression level, where *0* disables compression. ++ +The default value is *6*. + +*--pass-fd* _FD_:: +Specify a file descriptor to read passwords from instead of from _stdin_/_tty_. + ++ +Useful for automated or non-interactive sessions. + +*--password* _PASSWORD_:: +Use the specified password when it is needed. + ++ +WARNING: Not recommended for production use due to potential security issues. +Use *--pass-fd* for batch operations instead. + +*--passwords* _COUNT_:: +Set the number of passwords for *--symmetric* encryption. + ++ +While not commonly used, you may encrypt a message to any reasonable number of passwords. + +*--creation* _TIME_:: +Override signature creation time. + ++ +By default, creation time is set to the current local computer time. + ++ +*TIME* could be specified in the ISO 8601-1:2019 date format (_yyyy-mm-dd_), or in the UNIX timestamp format. + +*--expiration* _TIME_:: +Set signature expiration time, counting from the creation time. + ++ +By default, signatures do not expire. + ++ +A specific expiration time can be specified as: + +*** expiration date in the ISO 8601:2019 date format (_yyyy-mm-dd_); or +*** hours/days/months/years since creation time with the syntax of _20h_/_30d_/_1m_/_1y_; +*** number of seconds. + +*--keystore-format* _GPG_|_KBX_|_G10_|_G21_:: +Set keystore format. + ++ +RNP automatically detects the keystore format. + ++ +This option allows the auto-detection behavior to be overridden. + +*--notty*:: +Disable use of tty. + ++ +By default RNP would detect whether TTY is attached and use it for user prompts. + ++ +This option overrides default behaviour so user input may be passed in batch mode. + +*--current-time* _TIME_:: +Override system's time with a specified value. + ++ +By default RNP uses system's time in all signature/key checks, however in some scenarios it could be needed to override this. + ++ +*TIME* may be specified in the same way as *--creation*. + +*--set-filename* _FNAME_:: +Override or set a file name, stored inside of OpenPGP message. + ++ +By default RNP will store input filename (or empty string for *stdin*/*env* input) in the resulting OpenPGP message during encryption or embedded signing. +This option allows to override this. Special value *_CONSOLE* may be used for "for your eyes only"-message. Refer OpenPGP documentation for the details. + +*--allow-hidden* :: +Allow hidden recipient support. + ++ +Sender of an encrypted message may wish to hide recipient's key by setting a Key ID field to all zeroes. +In this case receiver has to try every available secret key, checking for a valid decrypted session key. This option is disabled by default. + +== EXIT STATUS + +_0_:: + Success. + +_Non-zero_:: + Failure. + + +== EXAMPLES + +The following examples demonstrate method of usage of the _rnp_ command. + +=== EXAMPLE 1 + +*rnp* *--homedir* _.rnp_ *--encrypt* *-r* _0x6E69636B6F6C6179_ +*--output* _document.txt.encrypted_ _document.txt_ + +Load keyrings from the _.rnp_ folder, +encrypt the _document.txt_ file using the +key with keyid _0x6E69636B6F6C6179_. + +=== EXAMPLE 2 + +*rnp* *--keyfile* _john-sec.asc_ *-s* *--detach* *--hash* _SHA512_ _document.txt_ + +Generate a detached signature over the file _document.txt_, using the +secret key stored in the file. +Additionally override the hash algorithm to _SHA512_. + +=== EXAMPLE 3 + +*rnp* *--keyfile* _john-pub.asc_ *--verify* _document.txt.sig_ + +Verify detached signature, using the key stored in the _john-pub.asc_ file. +The signed data is assumed to be available from the file _document.txt_. + +=== EXAMPLE 4 + +*rnp* *-e* *-c* *-s* *--passwords* _3_ +*-r* _0x526F6E616C642054_ +*-r* "_john@doe.com_" +*-u* _0x44616E69656C2057_ +_document.txt_ + +Encrypt _document.txt_ with 2 keys (specified via _keyid_ +_0x526F6E616C642054_ and _userid_ _john@doe.com_), and 3 passwords, +so *any* of these may be used to decrypt the resulting file. + +Additionally, the message will be signed with key _0x44616E69656C2057_. + +=== EXAMPLE 5 + +*printf* _"Message"_ | *rnp* *--keyfile* _env:PGP_ENCRYPTION_KEY_ *-e* *-* *--armor* + +Encrypt message, passed via stdin, using the key, stored in environment variable *PGP_ENCRYPTION_KEY*, add ascii armoring, and print result to the stdout. + +== BUGS + +Please report _issues_ via the RNP public issue tracker at: +https://github.com/rnpgp/rnp/issues. + +_Security reports_ or _security-sensitive feedback_ should be reported +according to the instructions at: +https://www.rnpgp.org/feedback. + + +== AUTHORS + +*RNP* is an open source project led by Ribose and has +received contributions from numerous individuals and +organizations. + + +== RESOURCES + +*Web site*: https://www.rnpgp.org + +*Source repository*: https://github.com/rnpgp/rnp + + +== COPYING + +Copyright \(C) 2017-2021 Ribose. +The RNP software suite is _freely licensed_: +please refer to the *LICENSE* file for details. + + +== SEE ALSO + +*rnpkeys(1)*, *librnp(3)* diff --git a/comm/third_party/rnp/src/rnp/rnp.cpp b/comm/third_party/rnp/src/rnp/rnp.cpp new file mode 100644 index 0000000000..30d3ac4a63 --- /dev/null +++ b/comm/third_party/rnp/src/rnp/rnp.cpp @@ -0,0 +1,695 @@ +/* + * Copyright (c) 2017-2021 [Ribose Inc](https://www.ribose.com). + * Copyright (c) 2009 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is originally derived from software contributed to + * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and + * carried further by Ribose Inc (https://www.ribose.com). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/* Command line program to perform rnp operations */ +#include <sys/types.h> +#include <sys/stat.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <string> +#ifdef _MSC_VER +#include "uniwin.h" +#else +#include <sys/param.h> +#include <unistd.h> +#include <getopt.h> +#endif +#include <fcntl.h> +#include <stdbool.h> +#include <assert.h> +#include <time.h> +#include <errno.h> + +#include "fficli.h" +#include "str-utils.h" +#include "logging.h" + +static const char *usage = + "Sign, verify, encrypt, decrypt, inspect OpenPGP data.\n" + "Usage: rnp --command [options] [files]\n" + "Commands:\n" + " -h, --help This help message.\n" + " -V, --version Print RNP version information.\n" + " -e, --encrypt Encrypt data using the public key(s).\n" + " -r, --recipient Specify recipient's key via uid/keyid/fingerprint.\n" + " --cipher name Specify symmetric cipher, used for encryption.\n" + " --aead[=EAX, OCB] Use AEAD for encryption.\n" + " -z 0..9 Set the compression level.\n" + " --[zip,zlib,bzip] Use the corresponding compression algorithm.\n" + " --armor Apply ASCII armor to the encryption/signing output.\n" + " --no-wrap Do not wrap the output in a literal data packet.\n" + " -c, --symmetric Encrypt data using the password(s).\n" + " --passwords num Encrypt to the specified number of passwords.\n" + " -s, --sign Sign data. May be combined with encryption.\n" + " --detach Produce detached signature.\n" + " -u, --userid Specify signing key(s) via uid/keyid/fingerprint.\n" + " --hash Specify hash algorithm, used during signing.\n" + " --allow-weak-hash Allow usage of a weak hash algorithm.\n" + " --clearsign Cleartext-sign data.\n" + " -d, --decrypt Decrypt and output data, verifying signatures.\n" + " -v, --verify Verify signatures, without outputting data.\n" + " --source Specify source for the detached signature.\n" + " --dearmor Strip ASCII armor from the data, outputting binary.\n" + " --enarmor Add ASCII armor to the data.\n" + " --list-packets List OpenPGP packets from the input.\n" + " --json Use JSON output instead of human-readable.\n" + " --grips Dump key fingerprints and grips.\n" + " --mpi Dump MPI values from packets.\n" + " --raw Dump raw packet contents as well.\n" + "\n" + "Other options:\n" + " --homedir path Override home directory (default is ~/.rnp/).\n" + " -f, --keyfile Load key(s) only from the file specified.\n" + " --output [file, -] Write data to the specified file or stdout.\n" + " --overwrite Overwrite output file without a prompt.\n" + " --password Password used during operation.\n" + " --pass-fd num Read password(s) from the file descriptor.\n" + " --s2k-iterations Set the number of iterations for the S2K process.\n" + " --s2k-msec Calculate S2K iterations value based on a provided time in " + "milliseconds.\n" + " --notty Do not output anything to the TTY.\n" + " --current-time Override system's time.\n" + " --set-filename Override file name, stored inside of OpenPGP message.\n" + "\n" + "See man page for a detailed listing and explanation.\n" + "\n"; + +enum optdefs { + /* Commands as they are get via CLI */ + CMD_ENCRYPT = 260, + CMD_DECRYPT, + CMD_SIGN, + CMD_CLEARSIGN, + CMD_VERIFY, + CMD_VERIFY_CAT, + CMD_SYM_ENCRYPT, + CMD_DEARMOR, + CMD_ENARMOR, + CMD_LIST_PACKETS, + CMD_VERSION, + CMD_HELP, + + /* OpenPGP data processing commands. Sign/Encrypt/Decrypt mapped to these */ + CMD_PROTECT, + CMD_PROCESS, + + /* Options */ + OPT_KEY_STORE_FORMAT, + OPT_USERID, + OPT_RECIPIENT, + OPT_ARMOR, + OPT_HOMEDIR, + OPT_DETACHED, + OPT_HASH_ALG, + OPT_ALLOW_WEAK_HASH, + OPT_OUTPUT, + OPT_RESULTS, + OPT_COREDUMPS, + OPT_PASSWDFD, + OPT_PASSWD, + OPT_PASSWORDS, + OPT_EXPIRATION, + OPT_CREATION, + OPT_CIPHER, + OPT_NUMTRIES, + OPT_ZALG_ZIP, + OPT_ZALG_ZLIB, + OPT_ZALG_BZIP, + OPT_ZLEVEL, + OPT_OVERWRITE, + OPT_AEAD, + OPT_AEAD_CHUNK, + OPT_KEYFILE, + OPT_JSON, + OPT_GRIPS, + OPT_MPIS, + OPT_RAW, + OPT_NOTTY, + OPT_SOURCE, + OPT_NOWRAP, + OPT_CURTIME, + OPT_SETFNAME, + OPT_ALLOW_HIDDEN, + OPT_S2K_ITER, + OPT_S2K_MSEC, + + /* debug */ + OPT_DEBUG +}; + +#define EXIT_ERROR 2 + +static struct option options[] = { + /* file manipulation commands */ + {"encrypt", no_argument, NULL, CMD_ENCRYPT}, + {"decrypt", no_argument, NULL, CMD_DECRYPT}, + {"sign", no_argument, NULL, CMD_SIGN}, + {"clearsign", no_argument, NULL, CMD_CLEARSIGN}, + {"verify", no_argument, NULL, CMD_VERIFY}, + {"verify-cat", no_argument, NULL, CMD_VERIFY_CAT}, + {"symmetric", no_argument, NULL, CMD_SYM_ENCRYPT}, + {"dearmor", no_argument, NULL, CMD_DEARMOR}, + {"enarmor", optional_argument, NULL, CMD_ENARMOR}, + /* file listing commands */ + {"list-packets", no_argument, NULL, CMD_LIST_PACKETS}, + /* debugging commands */ + {"help", no_argument, NULL, CMD_HELP}, + {"version", no_argument, NULL, CMD_VERSION}, + {"debug", required_argument, NULL, OPT_DEBUG}, + /* options */ + {"coredumps", no_argument, NULL, OPT_COREDUMPS}, + {"keystore-format", required_argument, NULL, OPT_KEY_STORE_FORMAT}, + {"userid", required_argument, NULL, OPT_USERID}, + {"recipient", required_argument, NULL, OPT_RECIPIENT}, + {"home", required_argument, NULL, OPT_HOMEDIR}, + {"homedir", required_argument, NULL, OPT_HOMEDIR}, + {"keyfile", required_argument, NULL, OPT_KEYFILE}, + {"ascii", no_argument, NULL, OPT_ARMOR}, + {"armor", no_argument, NULL, OPT_ARMOR}, + {"armour", no_argument, NULL, OPT_ARMOR}, + {"detach", no_argument, NULL, OPT_DETACHED}, + {"detached", no_argument, NULL, OPT_DETACHED}, + {"hash", required_argument, NULL, OPT_HASH_ALG}, + {"pass-fd", required_argument, NULL, OPT_PASSWDFD}, + {"password", required_argument, NULL, OPT_PASSWD}, + {"passwords", required_argument, NULL, OPT_PASSWORDS}, + {"output", required_argument, NULL, OPT_OUTPUT}, + {"results", required_argument, NULL, OPT_RESULTS}, + {"creation", required_argument, NULL, OPT_CREATION}, + {"expiration", required_argument, NULL, OPT_EXPIRATION}, + {"expiry", required_argument, NULL, OPT_EXPIRATION}, + {"cipher", required_argument, NULL, OPT_CIPHER}, + {"numtries", required_argument, NULL, OPT_NUMTRIES}, + {"zip", no_argument, NULL, OPT_ZALG_ZIP}, + {"zlib", no_argument, NULL, OPT_ZALG_ZLIB}, + {"bzip", no_argument, NULL, OPT_ZALG_BZIP}, + {"bzip2", no_argument, NULL, OPT_ZALG_BZIP}, + {"overwrite", no_argument, NULL, OPT_OVERWRITE}, + {"aead", optional_argument, NULL, OPT_AEAD}, + {"aead-chunk-bits", required_argument, NULL, OPT_AEAD_CHUNK}, + {"json", no_argument, NULL, OPT_JSON}, + {"grips", no_argument, NULL, OPT_GRIPS}, + {"mpi", no_argument, NULL, OPT_MPIS}, + {"raw", no_argument, NULL, OPT_RAW}, + {"notty", no_argument, NULL, OPT_NOTTY}, + {"source", required_argument, NULL, OPT_SOURCE}, + {"no-wrap", no_argument, NULL, OPT_NOWRAP}, + {"current-time", required_argument, NULL, OPT_CURTIME}, + {"set-filename", required_argument, NULL, OPT_SETFNAME}, + {"allow-hidden", no_argument, NULL, OPT_ALLOW_HIDDEN}, + {"s2k-iterations", required_argument, NULL, OPT_S2K_ITER}, + {"s2k-msec", required_argument, NULL, OPT_S2K_MSEC}, + {"allow-weak-hash", no_argument, NULL, OPT_ALLOW_WEAK_HASH}, + + {NULL, 0, NULL, 0}, +}; + +/* print a usage message */ +static void +print_usage(const char *usagemsg) +{ + cli_rnp_print_praise(); + puts(usagemsg); +} + +/* do a command once for a specified config */ +static bool +rnp_cmd(cli_rnp_t *rnp) +{ + bool ret = false; + + switch (rnp->cfg().get_int(CFG_COMMAND)) { + case CMD_PROTECT: + ret = cli_rnp_protect_file(rnp); + break; + case CMD_PROCESS: + ret = cli_rnp_process_file(rnp); + break; + case CMD_LIST_PACKETS: + ret = cli_rnp_dump_file(rnp); + break; + case CMD_DEARMOR: + ret = cli_rnp_dearmor_file(rnp); + break; + case CMD_ENARMOR: + ret = cli_rnp_armor_file(rnp); + break; + case CMD_VERSION: + cli_rnp_print_praise(); + ret = true; + break; + default: + print_usage(usage); + ret = true; + } + + return ret; +} + +static bool +setcmd(rnp_cfg &cfg, int cmd, const char *arg) +{ + int newcmd = cmd; + + /* set file processing command to one of PROTECT or PROCESS */ + switch (cmd) { + case CMD_ENCRYPT: + cfg.set_bool(CFG_ENCRYPT_PK, true); + if (cfg.get_bool(CFG_ENCRYPT_SK)) { + cfg.set_bool(CFG_KEYSTORE_DISABLED, false); + } + newcmd = CMD_PROTECT; + break; + case CMD_SYM_ENCRYPT: + cfg.set_bool(CFG_ENCRYPT_SK, true); + if (!cfg.get_bool(CFG_ENCRYPT_PK) && !cfg.get_bool(CFG_SIGN_NEEDED)) { + cfg.set_bool(CFG_KEYSTORE_DISABLED, true); + } + newcmd = CMD_PROTECT; + break; + case CMD_CLEARSIGN: + cfg.set_bool(CFG_CLEARTEXT, true); + [[fallthrough]]; + case CMD_SIGN: + cfg.set_bool(CFG_NEEDSSECKEY, true); + cfg.set_bool(CFG_SIGN_NEEDED, true); + if (cfg.get_bool(CFG_ENCRYPT_SK)) { + cfg.set_bool(CFG_KEYSTORE_DISABLED, false); + } + newcmd = CMD_PROTECT; + break; + case CMD_DECRYPT: + /* for decryption, we probably need a seckey */ + cfg.set_bool(CFG_NEEDSSECKEY, true); + newcmd = CMD_PROCESS; + break; + case CMD_VERIFY: + /* single verify will discard output, decrypt will not */ + cfg.set_bool(CFG_NO_OUTPUT, true); + [[fallthrough]]; + case CMD_VERIFY_CAT: + newcmd = CMD_PROCESS; + break; + case CMD_LIST_PACKETS: + cfg.set_bool(CFG_KEYSTORE_DISABLED, true); + break; + case CMD_DEARMOR: + cfg.set_bool(CFG_KEYSTORE_DISABLED, true); + break; + case CMD_ENARMOR: { + std::string msgt = arg ? arg : ""; + if (msgt.empty() || (msgt == "msg")) { + msgt = "message"; + } else if (msgt == "pubkey") { + msgt = "public key"; + } else if (msgt == "seckey") { + msgt = "secret key"; + } else if (msgt == "sign") { + msgt = "signature"; + } else { + ERR_MSG("Wrong enarmor argument: %s", arg); + return false; + } + + if (!msgt.empty()) { + cfg.set_str(CFG_ARMOR_DATA_TYPE, msgt); + } + cfg.set_bool(CFG_KEYSTORE_DISABLED, true); + break; + } + case CMD_HELP: + case CMD_VERSION: + break; + default: + newcmd = CMD_HELP; + break; + } + + if (cfg.has(CFG_COMMAND) && cfg.get_int(CFG_COMMAND) != newcmd) { + ERR_MSG("Conflicting commands!"); + return false; + } + + cfg.set_int(CFG_COMMAND, newcmd); + return true; +} + +/* set an option */ +static bool +setoption(rnp_cfg &cfg, int val, const char *arg) +{ + switch (val) { + /* redirect commands to setcmd */ + case CMD_ENCRYPT: + case CMD_SIGN: + case CMD_CLEARSIGN: + case CMD_DECRYPT: + case CMD_SYM_ENCRYPT: + case CMD_VERIFY: + case CMD_VERIFY_CAT: + case CMD_LIST_PACKETS: + case CMD_DEARMOR: + case CMD_ENARMOR: + case CMD_HELP: + case CMD_VERSION: + return setcmd(cfg, val, arg); + /* options */ + case OPT_COREDUMPS: +#ifdef _WIN32 + ERR_MSG("warning: --coredumps doesn't make sense on windows systems."); +#endif + cfg.set_bool(CFG_COREDUMPS, true); + return true; + case OPT_KEY_STORE_FORMAT: + cfg.set_str(CFG_KEYSTOREFMT, arg); + return true; + case OPT_USERID: + cfg.add_str(CFG_SIGNERS, arg); + return true; + case OPT_RECIPIENT: + cfg.add_str(CFG_RECIPIENTS, arg); + return true; + case OPT_ARMOR: + cfg.set_bool(CFG_ARMOR, true); + return true; + case OPT_DETACHED: + cfg.set_bool(CFG_DETACHED, true); + return true; + case OPT_HOMEDIR: + cfg.set_str(CFG_HOMEDIR, arg); + return true; + case OPT_KEYFILE: + cfg.set_str(CFG_KEYFILE, arg); + cfg.set_bool(CFG_KEYSTORE_DISABLED, true); + return true; + case OPT_HASH_ALG: + return cli_rnp_set_hash(cfg, arg); + case OPT_ALLOW_WEAK_HASH: + cfg.set_bool(CFG_WEAK_HASH, true); + return true; + case OPT_PASSWDFD: + cfg.set_str(CFG_PASSFD, arg); + return true; + case OPT_PASSWD: + cfg.set_str(CFG_PASSWD, arg); + return true; + case OPT_PASSWORDS: { + int count = 0; + if (!rnp::str_to_int(arg, count) || (count <= 0)) { + ERR_MSG("Incorrect value for --passwords option: %s", arg); + return false; + } + + cfg.set_int(CFG_PASSWORDC, count); + cfg.set_bool(CFG_ENCRYPT_SK, true); + return true; + } + case OPT_OUTPUT: + cfg.set_str(CFG_OUTFILE, arg); + return true; + case OPT_RESULTS: + cfg.set_str(CFG_RESULTS, arg); + return true; + case OPT_EXPIRATION: + cfg.set_str(CFG_EXPIRATION, arg); + return true; + case OPT_CREATION: + cfg.set_str(CFG_CREATION, arg); + return true; + case OPT_CIPHER: + return cli_rnp_set_cipher(cfg, arg); + case OPT_NUMTRIES: + cfg.set_str(CFG_NUMTRIES, arg); + return true; + case OPT_ZALG_ZIP: + cfg.set_str(CFG_ZALG, "ZIP"); + return true; + case OPT_ZALG_ZLIB: + cfg.set_str(CFG_ZALG, "ZLib"); + return true; + case OPT_ZALG_BZIP: + cfg.set_str(CFG_ZALG, "BZip2"); + return true; + case OPT_AEAD: { + std::string argstr = arg ? arg : ""; + if ((argstr == "1") || rnp::str_case_eq(argstr, "eax")) { + argstr = "EAX"; + } else if (argstr.empty() || (argstr == "2") || rnp::str_case_eq(argstr, "ocb")) { + argstr = "OCB"; + } else { + ERR_MSG("Wrong AEAD algorithm: %s", argstr.c_str()); + return false; + } + cfg.set_str(CFG_AEAD, argstr); + return true; + } + case OPT_AEAD_CHUNK: { + int bits = 0; + if (!rnp::str_to_int(arg, bits) || (bits < 0) || (bits > 16)) { + ERR_MSG("Wrong argument value %s for aead-chunk-bits, must be 0..16.", arg); + return false; + } + cfg.set_int(CFG_AEAD_CHUNK, bits); + return true; + } + case OPT_OVERWRITE: + cfg.set_bool(CFG_OVERWRITE, true); + return true; + case OPT_JSON: + cfg.set_bool(CFG_JSON, true); + return true; + case OPT_GRIPS: + cfg.set_bool(CFG_GRIPS, true); + return true; + case OPT_MPIS: + cfg.set_bool(CFG_MPIS, true); + return true; + case OPT_RAW: + cfg.set_bool(CFG_RAW, true); + return true; + case OPT_NOTTY: + cfg.set_bool(CFG_NOTTY, true); + return true; + case OPT_SOURCE: + cfg.set_str(CFG_SOURCE, arg); + return true; + case OPT_NOWRAP: + cfg.set_bool(CFG_NOWRAP, true); + cfg.set_int(CFG_ZLEVEL, 0); + return true; + case OPT_CURTIME: + cfg.set_str(CFG_CURTIME, arg); + return true; + case OPT_SETFNAME: + cfg.set_str(CFG_SETFNAME, arg); + return true; + case OPT_ALLOW_HIDDEN: + cfg.set_bool(CFG_ALLOW_HIDDEN, true); + return true; + case OPT_S2K_ITER: { + int iterations = 0; + if (!rnp::str_to_int(arg, iterations) || !iterations) { + ERR_MSG("Wrong iterations value: %s", arg); + return false; + } + cfg.set_int(CFG_S2K_ITER, iterations); + return true; + } + case OPT_S2K_MSEC: { + int msec = 0; + if (!rnp::str_to_int(arg, msec) || !msec) { + ERR_MSG("Invalid s2k msec value: %s", arg); + return false; + } + cfg.set_int(CFG_S2K_MSEC, msec); + return true; + } + case OPT_DEBUG: + ERR_MSG("Option --debug is deprecated, ignoring."); + return true; + default: + return setcmd(cfg, CMD_HELP, arg); + } + + return false; +} + +static bool +set_short_option(rnp_cfg &cfg, int ch, const char *arg) +{ + switch (ch) { + case 'V': + return setcmd(cfg, CMD_VERSION, arg); + case 'd': + return setcmd(cfg, CMD_DECRYPT, arg); + case 'e': + return setcmd(cfg, CMD_ENCRYPT, arg); + case 'c': + return setcmd(cfg, CMD_SYM_ENCRYPT, arg); + case 's': + return setcmd(cfg, CMD_SIGN, arg); + case 'v': + return setcmd(cfg, CMD_VERIFY, arg); + case 'r': + if (!strlen(optarg)) { + ERR_MSG("Recipient should not be empty"); + } else { + cfg.add_str(CFG_RECIPIENTS, optarg); + } + break; + case 'u': + if (!optarg) { + ERR_MSG("No userid argument provided"); + return false; + } + cfg.add_str(CFG_SIGNERS, optarg); + break; + case 'z': + if ((strlen(optarg) != 1) || (optarg[0] < '0') || (optarg[0] > '9')) { + ERR_MSG("Bad compression level: %s. Should be 0..9", optarg); + } else { + cfg.set_int(CFG_ZLEVEL, optarg[0] - '0'); + } + break; + case 'f': + if (!optarg) { + ERR_MSG("No keyfile argument provided"); + return false; + } + cfg.set_str(CFG_KEYFILE, optarg); + cfg.set_bool(CFG_KEYSTORE_DISABLED, true); + break; + case 'h': + [[fallthrough]]; + default: + return setcmd(cfg, CMD_HELP, optarg); + } + + return true; +} + +#ifndef RNP_RUN_TESTS +int +main(int argc, char **argv) +#else +int rnp_main(int argc, char **argv); +int +rnp_main(int argc, char **argv) +#endif +{ + if (argc < 2) { + print_usage(usage); + return EXIT_ERROR; + } + + cli_rnp_t rnp = {}; +#if !defined(RNP_RUN_TESTS) && defined(_WIN32) + try { + rnp.substitute_args(&argc, &argv); + } catch (std::exception &ex) { + RNP_LOG("Error converting arguments ('%s')", ex.what()); + return EXIT_ERROR; + } +#endif + + rnp_cfg cfg; + cfg.load_defaults(); + + /* TODO: These options should be set after initialising the context. */ + int optindex = 0; + int ch; + while ((ch = getopt_long(argc, argv, "S:Vdecr:su:vz:f:h", options, &optindex)) != -1) { + /* Check for unsupported command/option */ + if (ch == '?') { + print_usage(usage); + return EXIT_FAILURE; + } + + bool res = ch >= CMD_ENCRYPT ? setoption(cfg, options[optindex].val, optarg) : + set_short_option(cfg, ch, optarg); + if (!res) { + return EXIT_ERROR; + } + } + + switch (cfg.get_int(CFG_COMMAND)) { + case CMD_HELP: + print_usage(usage); + return EXIT_SUCCESS; + case CMD_VERSION: + cli_rnp_print_praise(); + return EXIT_SUCCESS; + default:; + } + + if (!cli_cfg_set_keystore_info(cfg)) { + ERR_MSG("fatal: cannot set keystore info"); + return EXIT_ERROR; + } + + if (!rnp.init(cfg)) { + ERR_MSG("fatal: cannot initialise"); + return EXIT_ERROR; + } + + if (!cli_rnp_check_weak_hash(&rnp)) { + ERR_MSG("Weak hash algorithm detected. Pass --allow-weak-hash option if you really " + "want to use it."); + return EXIT_ERROR; + } + + bool disable_ks = rnp.cfg().get_bool(CFG_KEYSTORE_DISABLED); + if (!disable_ks && !rnp.load_keyrings(rnp.cfg().get_bool(CFG_NEEDSSECKEY))) { + ERR_MSG("fatal: failed to load keys"); + return EXIT_ERROR; + } + + /* load the keyfile if any */ + if (disable_ks && !rnp.cfg().get_str(CFG_KEYFILE).empty() && !cli_rnp_add_key(&rnp)) { + ERR_MSG("fatal: failed to load key(s) from the file"); + return EXIT_ERROR; + } + + if (!cli_rnp_setup(&rnp)) { + return EXIT_ERROR; + } + + /* now do the required action for each of the command line args */ + if (optind == argc) { + return cli_rnp_t::ret_code(rnp_cmd(&rnp)); + } + bool success = true; + for (int i = optind; i < argc; i++) { + rnp.cfg().set_str(CFG_INFILE, argv[i]); + success = success && rnp_cmd(&rnp); + } + return cli_rnp_t::ret_code(success); +} diff --git a/comm/third_party/rnp/src/rnp/rnpcfg.cpp b/comm/third_party/rnp/src/rnp/rnpcfg.cpp new file mode 100644 index 0000000000..e1c35a30ae --- /dev/null +++ b/comm/third_party/rnp/src/rnp/rnpcfg.cpp @@ -0,0 +1,572 @@ +/* + * Copyright (c) 2017-2020 [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include <limits.h> +#ifdef HAVE_SYS_PARAM_H +#include <sys/param.h> +#else +#include "uniwin.h" +#endif +#include <sys/stat.h> +#include <time.h> +#include <errno.h> +#include <stdexcept> +#include <inttypes.h> + +#include "config.h" +#include "rnpcfg.h" +#include "defaults.h" +#include "utils.h" +#include "time-utils.h" +#include <rnp/rnp.h> + +// must be placed after include "utils.h" +#ifndef RNP_USE_STD_REGEX +#include <regex.h> +#else +#include <regex> +#endif + +typedef enum rnp_cfg_val_type_t { + RNP_CFG_VAL_NULL = 0, + RNP_CFG_VAL_INT = 1, + RNP_CFG_VAL_BOOL = 2, + RNP_CFG_VAL_STRING = 3, + RNP_CFG_VAL_LIST = 4 +} rnp_cfg_val_type_t; + +class rnp_cfg_val { + rnp_cfg_val_type_t type_; + + public: + rnp_cfg_val(rnp_cfg_val_type_t t) : type_(t){}; + rnp_cfg_val_type_t + type() const + { + return type_; + }; + + virtual ~rnp_cfg_val(){}; +}; + +class rnp_cfg_int_val : public rnp_cfg_val { + int val_; + + public: + rnp_cfg_int_val(int val) : rnp_cfg_val(RNP_CFG_VAL_INT), val_(val){}; + int + val() const + { + return val_; + }; +}; + +class rnp_cfg_bool_val : public rnp_cfg_val { + bool val_; + + public: + rnp_cfg_bool_val(bool val) : rnp_cfg_val(RNP_CFG_VAL_BOOL), val_(val){}; + bool + val() const + { + return val_; + }; +}; + +class rnp_cfg_str_val : public rnp_cfg_val { + std::string val_; + + public: + rnp_cfg_str_val(const std::string &val) : rnp_cfg_val(RNP_CFG_VAL_STRING), val_(val){}; + const std::string & + val() const + { + return val_; + }; +}; + +class rnp_cfg_list_val : public rnp_cfg_val { + std::vector<std::string> val_; + + public: + rnp_cfg_list_val() : rnp_cfg_val(RNP_CFG_VAL_LIST), val_(){}; + std::vector<std::string> & + val() + { + return val_; + }; + const std::vector<std::string> & + val() const + { + return val_; + }; +}; + +void +rnp_cfg::load_defaults() +{ + set_bool(CFG_OVERWRITE, false); + set_str(CFG_OUTFILE, ""); + set_str(CFG_ZALG, DEFAULT_Z_ALG); + set_int(CFG_ZLEVEL, DEFAULT_Z_LEVEL); + set_str(CFG_CIPHER, DEFAULT_SYMM_ALG); + set_int(CFG_NUMTRIES, MAX_PASSWORD_ATTEMPTS); + set_int(CFG_S2K_MSEC, DEFAULT_S2K_MSEC); +} + +void +rnp_cfg::set_str(const std::string &key, const std::string &val) +{ + unset(key); + vals_[key] = new rnp_cfg_str_val(val); +} + +void +rnp_cfg::set_str(const std::string &key, const char *val) +{ + unset(key); + vals_[key] = new rnp_cfg_str_val(val); +} + +void +rnp_cfg::set_int(const std::string &key, int val) +{ + unset(key); + vals_[key] = new rnp_cfg_int_val(val); +} + +void +rnp_cfg::set_bool(const std::string &key, bool val) +{ + unset(key); + vals_[key] = new rnp_cfg_bool_val(val); +} + +void +rnp_cfg::unset(const std::string &key) +{ + if (!vals_.count(key)) { + return; + } + delete vals_[key]; + vals_.erase(key); +} + +void +rnp_cfg::add_str(const std::string &key, const std::string &val) +{ + if (!vals_.count(key)) { + vals_[key] = new rnp_cfg_list_val(); + } + if (vals_[key]->type() != RNP_CFG_VAL_LIST) { + RNP_LOG("expected list val for \"%s\"", key.c_str()); + throw std::invalid_argument("type"); + } + (dynamic_cast<rnp_cfg_list_val &>(*vals_[key])).val().push_back(val); +} + +bool +rnp_cfg::has(const std::string &key) const +{ + return vals_.count(key); +} + +const std::string & +rnp_cfg::get_str(const std::string &key) const +{ + if (!has(key) || (vals_.at(key)->type() != RNP_CFG_VAL_STRING)) { + return empty_str_; + } + return (dynamic_cast<const rnp_cfg_str_val &>(*vals_.at(key))).val(); +} + +const char * +rnp_cfg::get_cstr(const std::string &key) const +{ + if (!has(key) || (vals_.at(key)->type() != RNP_CFG_VAL_STRING)) { + return NULL; + } + return (dynamic_cast<const rnp_cfg_str_val &>(*vals_.at(key))).val().c_str(); +} + +int +rnp_cfg::get_int(const std::string &key, int def) const +{ + if (!has(key)) { + return def; + } + const rnp_cfg_val *val = vals_.at(key); + switch (val->type()) { + case RNP_CFG_VAL_INT: + return (dynamic_cast<const rnp_cfg_int_val &>(*val)).val(); + case RNP_CFG_VAL_BOOL: + return (dynamic_cast<const rnp_cfg_bool_val &>(*val)).val(); + case RNP_CFG_VAL_STRING: + return atoi((dynamic_cast<const rnp_cfg_str_val &>(*val)).val().c_str()); + default: + return def; + } +} + +bool +rnp_cfg::get_bool(const std::string &key) const +{ + if (!has(key)) { + return false; + } + const rnp_cfg_val *val = vals_.at(key); + switch (val->type()) { + case RNP_CFG_VAL_INT: + return (dynamic_cast<const rnp_cfg_int_val &>(*val)).val(); + case RNP_CFG_VAL_BOOL: + return (dynamic_cast<const rnp_cfg_bool_val &>(*val)).val(); + case RNP_CFG_VAL_STRING: { + const std::string &str = (dynamic_cast<const rnp_cfg_str_val &>(*val)).val(); + return !strcasecmp(str.c_str(), "true") || (atoi(str.c_str()) > 0); + } + default: + return false; + } +} + +size_t +rnp_cfg::get_count(const std::string &key) const +{ + if (!has(key) || (vals_.at(key)->type() != RNP_CFG_VAL_LIST)) { + return 0; + } + return (dynamic_cast<const rnp_cfg_list_val &>(*vals_.at(key))).val().size(); +} + +const std::string & +rnp_cfg::get_str(const std::string &key, size_t idx) const +{ + if (get_count(key) <= idx) { + RNP_LOG("idx is out of bounds for \"%s\"", key.c_str()); + throw std::invalid_argument("idx"); + } + return (dynamic_cast<const rnp_cfg_list_val &>(*vals_.at(key))).val().at(idx); +} + +std::vector<std::string> +rnp_cfg::get_list(const std::string &key) const +{ + if (!has(key)) { + /* it's okay to return empty list */ + return std::vector<std::string>(); + } + if (vals_.at(key)->type() != RNP_CFG_VAL_LIST) { + RNP_LOG("no list at the key \"%s\"", key.c_str()); + throw std::invalid_argument("key"); + } + return (dynamic_cast<const rnp_cfg_list_val &>(*vals_.at(key))).val(); +} + +int +rnp_cfg::get_pswdtries() const +{ + const std::string &numtries = get_str(CFG_NUMTRIES); + int num = atoi(numtries.c_str()); + if (numtries.empty() || (num <= 0)) { + return MAX_PASSWORD_ATTEMPTS; + } else if (numtries == "unlimited") { + return INFINITE_ATTEMPTS; + } + return num; +} + +const std::string +rnp_cfg::get_hashalg() const +{ + const std::string hash_alg = get_str(CFG_HASH); + if (!hash_alg.empty()) { + return hash_alg; + } + return DEFAULT_HASH_ALG; +} + +bool +rnp_cfg::get_expiration(const std::string &key, uint32_t &seconds) const +{ + if (!has(key)) { + return false; + } + const std::string &val = get_str(key); + uint64_t delta; + uint64_t t; + if (parse_date(val, t)) { + uint64_t now = time(); + if (t > now) { + delta = t - now; + if (delta > UINT32_MAX) { + RNP_LOG("Expiration time exceeds 32-bit value"); + return false; + } + seconds = delta; + return true; + } + return false; + } + const char *reg = "^([0-9]+)([hdwmy]?)$"; +#ifndef RNP_USE_STD_REGEX + static regex_t r; + static int compiled; + regmatch_t matches[3]; + + if (!compiled) { + compiled = 1; + if (regcomp(&r, reg, REG_EXTENDED | REG_ICASE)) { + RNP_LOG("failed to compile regexp"); + return false; + } + } + if (regexec(&r, val.c_str(), ARRAY_SIZE(matches), matches, 0)) { + return false; + } + auto delta_str = &val.c_str()[matches[1].rm_so]; + char mult = val.c_str()[matches[2].rm_so]; +#else + static std::regex re(reg, std::regex_constants::extended | std::regex_constants::icase); + std::smatch result; + + if (!std::regex_search(val, result, re)) { + return false; + } + std::string delta_stdstr = result[1].str(); + const char *delta_str = delta_stdstr.c_str(); + char mult = result[2].str()[0]; +#endif + errno = 0; + delta = strtoul(delta_str, NULL, 10); + if (errno || delta > UINT_MAX) { + RNP_LOG("Invalid expiration '%s'.", delta_str); + return false; + } + switch (std::tolower(mult)) { + case 'h': + delta *= 60 * 60; + break; + case 'd': + delta *= 60 * 60 * 24; + break; + case 'w': + delta *= 60 * 60 * 24 * 7; + break; + case 'm': + delta *= 60 * 60 * 24 * 31; + break; + case 'y': + delta *= 60 * 60 * 24 * 365; + break; + } + if (delta > UINT32_MAX) { + RNP_LOG("Expiration value exceed 32 bit."); + return false; + } + seconds = delta; + return true; +} + +bool +rnp_cfg::extract_timestamp(const std::string &st, uint64_t &t) const +{ + if (st.empty()) { + return false; + } + if (parse_date(st, t)) { + return true; + } + /* Check if string is UNIX timestamp */ + for (auto c : st) { + if (!isdigit(c)) { + return false; + } + } + t = std::stoll(st); + return true; +} + +uint64_t +rnp_cfg::get_sig_creation() const +{ + uint64_t t = 0; + if (extract_timestamp(get_str(CFG_CREATION), t)) { + return t; + } + return time(); +} + +uint64_t +rnp_cfg::time() const +{ + uint64_t t = 0; + if (extract_timestamp(get_str(CFG_CURTIME), t)) { + return t; + } + return ::time(NULL); +} + +void +rnp_cfg::copy(const rnp_cfg &src) +{ + for (const auto &it : src.vals_) { + if (has(it.first)) { + unset(it.first); + } + rnp_cfg_val *val = NULL; + switch (it.second->type()) { + case RNP_CFG_VAL_INT: + val = new rnp_cfg_int_val(dynamic_cast<const rnp_cfg_int_val &>(*it.second)); + break; + case RNP_CFG_VAL_BOOL: + val = new rnp_cfg_bool_val(dynamic_cast<const rnp_cfg_bool_val &>(*it.second)); + break; + case RNP_CFG_VAL_STRING: + val = new rnp_cfg_str_val(dynamic_cast<const rnp_cfg_str_val &>(*it.second)); + break; + case RNP_CFG_VAL_LIST: + val = new rnp_cfg_list_val(dynamic_cast<const rnp_cfg_list_val &>(*it.second)); + break; + default: + continue; + } + vals_[it.first] = val; + } +} + +void +rnp_cfg::clear() +{ + for (const auto &it : vals_) { + delete it.second; + } + vals_.clear(); +} + +rnp_cfg::~rnp_cfg() +{ + clear(); +} + +/** + * @brief Get number of days in month. + * + * @param year number of year, i.e. 2021 + * @param month number of month, 1..12 + * @return number of days (28..31) or 0 if month is wrong. + */ +static int +days_in_month(int year, int month) +{ + switch (month) { + case 1: + case 3: + case 5: + case 7: + case 8: + case 10: + case 12: + return 31; + case 4: + case 6: + case 9: + case 11: + return 30; + case 2: { + bool leap_year = !(year % 400) || (!(year % 4) && (year % 100)); + return leap_year ? 29 : 28; + } + default: + return 0; + } +} + +bool +rnp_cfg::parse_date(const std::string &s, uint64_t &t) const +{ + /* fill time zone information */ + struct tm tm; + rnp_localtime(::time(NULL), tm); + tm.tm_hour = 0; + tm.tm_min = 0; + tm.tm_sec = 0; + const char *reg = "^([0-9]{4})[-/\\.]([0-9]{2})[-/\\.]([0-9]{2})$"; +#ifndef RNP_USE_STD_REGEX + static regex_t r; + static int compiled; + + if (!compiled) { + compiled = 1; + if (regcomp(&r, reg, REG_EXTENDED)) { + RNP_LOG("failed to compile regexp"); + return false; + } + } + regmatch_t matches[4]; + if (regexec(&r, s.c_str(), ARRAY_SIZE(matches), matches, 0)) { + return false; + } + int year = strtol(&s[matches[1].rm_so], NULL, 10); + int mon = strtol(&s[matches[2].rm_so], NULL, 10); + int mday = strtol(&s[matches[3].rm_so], NULL, 10); +#else + static std::regex re(reg, std::regex_constants::extended); + std::smatch result; + + if (!std::regex_search(s, result, re)) { + return false; + } + int year = std::stoi(result[1].str()); + int mon = std::stoi(result[2].str()); + int mday = std::stoi(result[3].str()); +#endif + if (year < 1970 || mon < 1 || mon > 12 || !mday || (mday > days_in_month(year, mon))) { + RNP_LOG("invalid date: %s.", s.c_str()); + return false; + } + tm.tm_year = year - 1900; + tm.tm_mon = mon - 1; + tm.tm_mday = mday; + /* line below is required to correctly handle DST changes */ + tm.tm_isdst = -1; + + struct tm check_tm = tm; + time_t built_time = rnp_mktime(&tm); + time_t check_time = mktime(&check_tm); + if (built_time != check_time) { + /* If date is beyond of yk2038 and we have 32-bit signed time_t, we need to reduce + * timestamp */ + RNP_LOG("Warning: date %s is beyond of 32-bit time_t, so timestamp was reduced to " + "maximum supported value.", + s.c_str()); + } + t = built_time; + return true; +} diff --git a/comm/third_party/rnp/src/rnp/rnpcfg.h b/comm/third_party/rnp/src/rnp/rnpcfg.h new file mode 100644 index 0000000000..c9478bbd88 --- /dev/null +++ b/comm/third_party/rnp/src/rnp/rnpcfg.h @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2017, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef RNP_CFG_H_ +#define RNP_CFG_H_ + +#include <stdbool.h> +#include <stdint.h> +#include <string> +#include <vector> +#include <unordered_map> + +/* cfg variables known by rnp */ +#define CFG_OVERWRITE "overwrite" /* overwrite output file if it is already exist or fail */ +#define CFG_ARMOR "armor" /* armor output data or not */ +#define CFG_ARMOR_DATA_TYPE "armor_type" /* armor data type, used with ``enarmor`` option */ +#define CFG_COMMAND "command" /* command to execute over input data */ +#define CFG_DETACHED "detached" /* produce the detached signature */ +#define CFG_CLEARTEXT "cleartext" /* cleartext signing should be used */ +#define CFG_SIGN_NEEDED "sign_needed" /* signing is needed during data protection */ +#define CFG_OUTFILE "outfile" /* name/path of the output file */ +#define CFG_NO_OUTPUT "no_output" /* do not output any data - just verify or process */ +#define CFG_INFILE "infile" /* name/path of the input file */ +#define CFG_SETFNAME "setfname" /* file name to embed into the literal data packet */ +#define CFG_RESULTS "results" /* name/path for results, not used right now */ +#define CFG_KEYSTOREFMT "keystorefmt" /* keyring format : GPG, G10, KBX */ +#define CFG_COREDUMPS "coredumps" /* enable/disable core dumps. 1 or 0. */ +#define CFG_NEEDSSECKEY "needsseckey" /* needs secret key for the ongoing operation */ +#define CFG_USERID "userid" /* userid for the ongoing operation */ +#define CFG_RECIPIENTS "recipients" /* list of encrypted data recipients */ +#define CFG_SIGNERS "signers" /* list of signers */ +#define CFG_HOMEDIR "homedir" /* home directory - folder with keyrings and so on */ +#define CFG_KEYFILE "keyfile" /* path to the file with key(s), used instead of keyring */ +#define CFG_PASSFD "pass-fd" /* password file descriptor */ +#define CFG_PASSWD "password" /* password as command-line constant */ +#define CFG_PASSWORDC "passwordc" /* number of passwords for symmetric encryption */ +#define CFG_USERINPUTFD "user-input-fd" /* user input file descriptor */ +#define CFG_NUMTRIES "numtries" /* number of password request tries, or 'unlimited' */ +#define CFG_EXPIRATION "expiration" /* signature expiration time */ +#define CFG_CREATION "creation" /* signature validity start */ +#define CFG_CIPHER "cipher" /* symmetric encryption algorithm as string */ +#define CFG_HASH "hash" /* hash algorithm used, string like 'SHA1'*/ +#define CFG_WEAK_HASH "weak-hash" /* allow weak algorithms */ +#define CFG_S2K_ITER "s2k-iter" /* number of S2K hash iterations to perform */ +#define CFG_S2K_MSEC "s2k-msec" /* number of milliseconds S2K should target */ +#define CFG_ENCRYPT_PK "encrypt_pk" /* public key should be used during encryption */ +#define CFG_ENCRYPT_SK "encrypt_sk" /* password encryption should be used */ +#define CFG_IO_RESS "ress" /* results stream */ +#define CFG_NUMBITS "numbits" /* number of bits in generated key */ +#define CFG_EXPERT "expert" /* expert key generation mode */ +#define CFG_ZLEVEL "zlevel" /* compression level: 0..9 (0 for no compression) */ +#define CFG_ZALG "zalg" /* compression algorithm: zip, zlib or bzip2 */ +#define CFG_AEAD "aead" /* if nonzero then AEAD enryption mode, int */ +#define CFG_AEAD_CHUNK "aead_chunk" /* AEAD chunk size bits, int from 0 to 56 */ +#define CFG_KEYSTORE_DISABLED \ + "disable_keystore" /* indicates whether keystore must be initialized */ +#define CFG_FORCE "force" /* force command to succeed operation */ +#define CFG_SECRET "secret" /* indicates operation on secret key */ +#define CFG_WITH_SIGS "with-sigs" /* list keys with signatures */ +#define CFG_JSON "json" /* list packets with JSON output */ +#define CFG_GRIPS "grips" /* dump grips when dumping key packets */ +#define CFG_MPIS "mpis" /* dump MPI values when dumping packets */ +#define CFG_RAW "raw" /* dump raw packet contents */ +#define CFG_REV_TYPE "rev-type" /* revocation reason code */ +#define CFG_REV_REASON "rev-reason" /* revocation reason human-readable string */ +#define CFG_PERMISSIVE "permissive" /* ignore bad packets during key import */ +#define CFG_NOTTY "notty" /* disable tty usage and do input/output via stdin/stdout */ +#define CFG_FIX_25519_BITS "fix-25519-bits" /* fix Cv25519 secret key via --edit-key */ +#define CFG_CHK_25519_BITS "check-25519-bits" /* check Cv25519 secret key bits */ +#define CFG_ADD_SUBKEY "add-subkey" /* add subkey to existing primary */ +#define CFG_SET_KEY_EXPIRE "key-expire" /* set/update key expiration time */ +#define CFG_SOURCE "source" /* source for the detached signature */ +#define CFG_NOWRAP "no-wrap" /* do not wrap the output in a literal data packet */ +#define CFG_CURTIME "curtime" /* date or timestamp to override the system's time */ +#define CFG_ALLOW_HIDDEN "allow-hidden" /* allow hidden recipients */ + +/* rnp keyring setup variables */ +#define CFG_KR_PUB_FORMAT "kr-pub-format" +#define CFG_KR_SEC_FORMAT "kr-sec-format" +#define CFG_KR_PUB_PATH "kr-pub-path" +#define CFG_KR_SEC_PATH "kr-sec-path" +#define CFG_KR_DEF_KEY "kr-def-key" + +/* key generation variables */ +#define CFG_KG_PRIMARY_ALG "kg-primary-alg" +#define CFG_KG_PRIMARY_BITS "kg-primary-bits" +#define CFG_KG_PRIMARY_CURVE "kg-primary-curve" +#define CFG_KG_PRIMARY_EXPIRATION "kg-primary-expiration" +#define CFG_KG_SUBKEY_ALG "kg-subkey-alg" +#define CFG_KG_SUBKEY_BITS "kg-subkey-bits" +#define CFG_KG_SUBKEY_CURVE "kg-subkey-curve" +#define CFG_KG_SUBKEY_EXPIRATION "kg-subkey-expiration" +#define CFG_KG_HASH "kg-hash" +#define CFG_KG_PROT_HASH "kg-prot-hash" +#define CFG_KG_PROT_ALG "kg-prot-alg" +#define CFG_KG_PROT_ITERATIONS "kg-prot-iterations" + +/* rnp CLI config : contains all the system-dependent and specified by the user configuration + * options */ +class rnp_cfg_val; + +class rnp_cfg { + private: + std::unordered_map<std::string, rnp_cfg_val *> vals_; + std::string empty_str_; + + /** @brief Parse date from the string in %Y-%m-%d format (using "-", "/", "." as a + * separator) + * + * @param s string with the date + * @param t UNIX timestamp of successfully parsed date + * @return true when parsed successfully or false otherwise + */ + bool parse_date(const std::string &s, uint64_t &t) const; + bool extract_timestamp(const std::string &st, uint64_t &t) const; + + public: + /** @brief load default settings */ + void load_defaults(); + /** @brief set string value for the key in config */ + void set_str(const std::string &key, const std::string &val); + void set_str(const std::string &key, const char *val); + /** @brief set int value for the key in config */ + void set_int(const std::string &key, int val); + /** @brief set bool value for the key in config */ + void set_bool(const std::string &key, bool val); + /** @brief remove key and corresponding value from the config */ + void unset(const std::string &key); + /** @brief add string item to the list value */ + void add_str(const std::string &key, const std::string &val); + /** @brief check whether config has value for the key */ + bool has(const std::string &key) const; + /** @brief get string value from the config. If it is absent then empty string will be + * returned */ + const std::string &get_str(const std::string &key) const; + /** @brief get C string value from the config. Will return 0 instead of empty string if + * value is absent. */ + const char *get_cstr(const std::string &key) const; + /** @brief get int value from the config. If it is absent then def will be returned */ + int get_int(const std::string &key, int def = 0) const; + /** @brief get bool value from the config. If it is absent then false will be returned */ + bool get_bool(const std::string &key) const; + /** @brief get number of items in the string list value. If it is absent then 0 will be + * returned. */ + size_t get_count(const std::string &key) const; + /** @brief get string from the list value at the corresponding position. If there is no + * corresponding value or index too large then empty string will be returned. */ + const std::string &get_str(const std::string &key, size_t idx) const; + /** @brief get all strings from the list value */ + std::vector<std::string> get_list(const std::string &key) const; + /** @brief get number of the password tries */ + int get_pswdtries() const; + /** @brief get hash algorithm */ + const std::string get_hashalg() const; + + /** @brief Get expiration time from the cfg variable, as value relative to the current + * time. As per OpenPGP standard it should fit in 32 bit value, otherwise error is + * returned. + * + * Expiration may be specified in different formats: + * - 10d : 10 days (you can use [h]ours, d[ays], [w]eeks, [m]onthes) + * - 2017-07-12 : as the exact date + * - 60000 : number of seconds + * + * @param seconds On successful return result will be placed here + * @return true on success or false otherwise + */ + bool get_expiration(const std::string &key, uint32_t &seconds) const; + + /** @brief Get signature creation time from the config. + * Creation time may be specified in different formats: + * - 2017-07-12 : as the exact date + * - 1499334073 : timestamp + * + * @return timestamp of the signature creation. + */ + uint64_t get_sig_creation() const; + + /** @brief Get current time from the config. + * + * @return timestamp which should be considered as current time. + */ + uint64_t time() const; + + /** @brief copy or override a configuration. + * @param src vals will be overridden (if key exist) or copied (if not) from this object + */ + void copy(const rnp_cfg &src); + void clear(); + /* delete unneeded operators */ + rnp_cfg &operator=(const rnp_cfg &src) = delete; + rnp_cfg &operator=(const rnp_cfg &&src) = delete; + /** @brief destructor */ + ~rnp_cfg(); +}; + +#endif diff --git a/comm/third_party/rnp/src/rnpkeys/CMakeLists.txt b/comm/third_party/rnp/src/rnpkeys/CMakeLists.txt new file mode 100644 index 0000000000..6302903a02 --- /dev/null +++ b/comm/third_party/rnp/src/rnpkeys/CMakeLists.txt @@ -0,0 +1,101 @@ +# Copyright (c) 2018-2020 Ribose Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS +# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +if(MSVC) + # remove extra ${Configuration} subfolder + set(ArchiveOutputDir ${CMAKE_BINARY_DIR}\\src\\rnpkeys) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${ArchiveOutputDir}) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_MINSIZEREL ${ArchiveOutputDir}) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE ${ArchiveOutputDir}) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELWITHDEBINFO ${ArchiveOutputDir}) + + set(RuntimeOutputDir ${CMAKE_BINARY_DIR}\\src\\rnpkeys) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${RuntimeOutputDir}) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL ${RuntimeOutputDir}) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${RuntimeOutputDir}) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO ${RuntimeOutputDir}) + + find_path(GETOPT_INCLUDE_DIR + NAMES getopt.h + ) + find_library(GETOPT_LIBRARY + NAMES getopt + ) + find_path(DIRENT_INCLUDE_DIR + NAMES dirent.h + ) +endif() + +# for the headers +find_package(JSON-C 0.11 REQUIRED) + +add_executable(rnpkeys + rnpkeys.cpp + tui.cpp + main.cpp + ../rnp/rnpcfg.cpp + ../rnp/fficli.cpp +) + +if(BUILD_SHARED_LIBS) + target_sources(rnpkeys PRIVATE ../lib/logging.cpp $<TARGET_OBJECTS:rnp-common>) +endif(BUILD_SHARED_LIBS) + +target_include_directories(rnpkeys + PRIVATE + "${PROJECT_SOURCE_DIR}/src" + "${PROJECT_SOURCE_DIR}/src/lib" + "${JSON-C_INCLUDE_DIRS}" +) +if(MSVC) + target_include_directories(rnpkeys + PRIVATE + "${GETOPT_INCLUDE_DIR}" + "${DIRENT_INCLUDE_DIR}" + ) +endif() + +target_link_libraries(rnpkeys + PRIVATE + librnp + JSON-C::JSON-C +) +if(MSVC) + target_link_libraries(rnpkeys + PRIVATE + "${GETOPT_LIBRARY}" + ) +endif() + +include(GNUInstallDirs) +install(TARGETS rnpkeys + RUNTIME + DESTINATION "${CMAKE_INSTALL_BINDIR}" + COMPONENT cli +) + +# Build and install man page +if (ENABLE_DOC) + add_adoc_man("${CMAKE_CURRENT_SOURCE_DIR}/rnpkeys.1.adoc" ${RNP_VERSION}) +endif() diff --git a/comm/third_party/rnp/src/rnpkeys/main.cpp b/comm/third_party/rnp/src/rnpkeys/main.cpp new file mode 100644 index 0000000000..8bcb7e1115 --- /dev/null +++ b/comm/third_party/rnp/src/rnpkeys/main.cpp @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2017, [Ribose Inc](https://www.ribose.com). + * Copyright (c) 2009 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is originally derived from software contributed to + * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and + * carried further by Ribose Inc (https://www.ribose.com). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/* Command line program to perform rnp operations */ +#ifdef _MSC_VER +#include "uniwin.h" +#else +#include <getopt.h> +#endif +#include <stdio.h> +#include <string.h> +#include "rnpkeys.h" + +extern struct option options[]; +extern const char * usage; + +optdefs_t +get_short_cmd(int ch) +{ + switch (ch) { + case 'V': + return CMD_VERSION; + case 'g': + return CMD_GENERATE_KEY; + case 'l': + return CMD_LIST_KEYS; + case 'h': + [[fallthrough]]; + default: + return CMD_HELP; + } +} + +#ifndef RNP_RUN_TESTS +int +main(int argc, char **argv) +#else +int rnpkeys_main(int argc, char **argv); +int +rnpkeys_main(int argc, char **argv) +#endif +{ + if (argc < 2) { + print_usage(usage); + return EXIT_FAILURE; + } + + cli_rnp_t rnp = {}; +#if !defined(RNP_RUN_TESTS) && defined(_WIN32) + try { + rnp.substitute_args(&argc, &argv); + } catch (std::exception &ex) { + RNP_LOG("Error converting arguments ('%s')", ex.what()); + return EXIT_FAILURE; + } +#endif + + rnp_cfg cfg; + optdefs_t cmd = CMD_NONE; + int optindex = 0; + int ch; + + while ((ch = getopt_long(argc, argv, "Vglh", options, &optindex)) != -1) { + /* Check for unsupported command/option */ + if (ch == '?') { + print_usage(usage); + return EXIT_FAILURE; + } + + optdefs_t newcmd = cmd; + if (ch >= CMD_LIST_KEYS) { + if (!setoption(cfg, &newcmd, options[optindex].val, optarg)) { + ERR_MSG("Failed to process argument --%s", options[optindex].name); + return EXIT_FAILURE; + } + } else { + newcmd = get_short_cmd(ch); + } + + if (cmd && newcmd != cmd) { + ERR_MSG("Conflicting commands!"); + return EXIT_FAILURE; + } + cmd = newcmd; + } + + /* No initialization required for these two commands. */ + if (cmd == CMD_HELP || cmd == CMD_VERSION) { + return cli_rnp_t::ret_code(rnp_cmd(&rnp, cmd, NULL)); + } + + if (!rnpkeys_init(rnp, cfg)) { + return EXIT_FAILURE; + } + + if (!cli_rnp_setup(&rnp)) { + return EXIT_FAILURE; + } + + /* now do the required action for each of the command line args */ + if (optind == argc) { + return cli_rnp_t::ret_code(rnp_cmd(&rnp, cmd, NULL)); + } + bool success = true; + for (int i = optind; i < argc; i++) { + success = success && rnp_cmd(&rnp, cmd, argv[i]); + } + return cli_rnp_t::ret_code(success); +} diff --git a/comm/third_party/rnp/src/rnpkeys/moz.build b/comm/third_party/rnp/src/rnpkeys/moz.build new file mode 100644 index 0000000000..e6cb1c503c --- /dev/null +++ b/comm/third_party/rnp/src/rnpkeys/moz.build @@ -0,0 +1,64 @@ +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +Program("rnpkeys") + +include("../../../rnpdefs.mozbuild") + +USE_LIBS += ["rnp"] + +LOCAL_INCLUDES = [ + "!../lib", + "..", + "../../include", + "../common", + "../lib", +] + +if CONFIG["MZLA_SYSTEM_JSONC"]: + CXXFLAGS += CONFIG["MZLA_JSONC_CFLAGS"] + LDFLAGS += CONFIG["MZLA_JSONC_LIBS"] + ["-Wl,-rpath,$ORIGIN"] +else: + USE_LIBS += ["json-c"] + LOCAL_INCLUDES += ["!/comm/third_party/json-c", "/comm/third_party/json-c"] + LDFLAGS += ["-Wl,-rpath,$ORIGIN"] + +SOURCES += [ + "../common/file-utils.cpp", + "../common/str-utils.cpp", + "../common/time-utils.cpp", + "../lib/logging.cpp", + "../rnp/fficli.cpp", + "../rnp/rnpcfg.cpp", + "main.cpp", + "rnpkeys.cpp", + "tui.cpp", +] + +if CONFIG["CC_TYPE"] == "clang-cl": + CXXFLAGS += [ + "/EHs", + "-Wno-deprecated-declarations", + ] + LOCAL_INCLUDES += [ + "/comm/third_party/niwcompat", + ] + + OS_LIBS += ["shell32"] + + if CONFIG["CPU_ARCH"] == "x86": + LDFLAGS += ["clang_rt.builtins-i386.lib"] + elif CONFIG["CPU_ARCH"] == "x86_64": + LDFLAGS += ["clang_rt.builtins-x86_64.lib"] + + SOURCES += [ + "/comm/third_party/niwcompat/getopt.c", + ] + SOURCES["../rnp/rnpcfg.cpp"].flags += [ + "-FI", + "%s/comm/third_party/niwcompat/extra_include.h" % TOPSRCDIR, + ] + DEFINES["MOZILLA_CONFIG_H"] = True + DEFINES["_CRT_SECURE_NO_WARNINGS"] = True diff --git a/comm/third_party/rnp/src/rnpkeys/rnpkeys.1.adoc b/comm/third_party/rnp/src/rnpkeys/rnpkeys.1.adoc new file mode 100644 index 0000000000..2b09d1794d --- /dev/null +++ b/comm/third_party/rnp/src/rnpkeys/rnpkeys.1.adoc @@ -0,0 +1,453 @@ += rnpkeys(1) +RNP +:doctype: manpage +:release-version: {component-version} +:man manual: RNP Manual +:man source: RNP {release-version} + +== NAME + +RNPKEYS - OpenPGP key management utility. + +== SYNOPSIS + +*rnpkeys* [_--homedir_ _dir_] [_OPTIONS_] _COMMAND_ + +== DESCRIPTION + +The _rnpkeys_ command-line utility is part of the _RNP_ suite and +provides OpenPGP key management functionality, including: + +* key listing; +* key generation; +* key import/export; and +* key editing. + + +=== BASICS + +By default, *rnp* will apply a _COMMAND_, additionally configured with _OPTIONS_, +to all _INPUT_FILE_(s) or _stdin_ if no _INPUT_FILE_ is given. +There are some special cases for _INPUT_FILE_ : + +* _-_ (dash) substitutes to _stdin_ +* env:VARIABLE_NAME substitutes to the contents of environment variable VARIABLE_NAME + +Depending on the input, output may be written: + +* to the specified file with a removed or added file extension (_.pgp_, _.asc_, _.sig_); or +* to _stdout_. + +Without the *--armor* option, output will be in binary. + +If _COMMAND_ requires public or private keys, *rnp* will look for the keyrings in *~/.rnp*. The options *--homedir* and *--keyfile* override this (see below). + +If _COMMAND_ needs a password, *rnp* will ask for it via *stdin* or *tty*, +unless the *--password* or *--pass-fd* option was specified. + + +By default, *rnpkeys* will use keyrings stored in the _~/.rnp_ directory. + +This behavior may be overridden with the _--homedir_ option. + +If _COMMAND_ needs a password, the command will prompt the caller +via _stdin_ or _tty_, unless the *--password* or *--pass-fd* +options were also used. + +=== SPECIFYING KEYS + +Most *rnpkeys* commands require a key locator or a filter, +representing one or more keys. + +It may be specified in one of the following ways: + +*userid*:: +Or just part of the *userid*. +For *"Alice <alice@rnpgp.com>"*, the following methods are considered identical: + +** _alice_ +** _alice@rnpgp_ +** _rnpgp.com_ + +*keyid*:: +Or its right-most 8 characters. With or without _0x_ at the beginning and spaces/tabs inside. Such as: + +** _0x725F6F2D6D5F6120_ +** _"725F6F2D 6D5F6120"_ +** _0x6D5F6120_ + +*key fingerprint*: The 40-character key fingerprint, such as: + +** _"0x416E746F 6E537669 72696465 6E6B6F20"_ + + + +== COMMANDS + +=== INFORMATIONAL + +*-h*, *--help*:: +Displays a short help message. No options are expected. + +*-V*, *--version*:: +Displays version information. No options are expected. + +*-l*, *--list-keys*:: +List out keys and some brief information about each. + ++ +Additional options: + +*--with-sigs*::: +Additionally display signatures of listed keys. + + +=== KEY GENERATION + +*-g*, *--generate-key*:: +Generate a new keypair. + ++ +Without additional options, an RSA primary key pair with an RSA sub-key pair will be generated, and prompting for the encryption password afterwards. ++ +Additional options: + +*--numbits*::: +Overrides the default RSA key size of *2048* bits. + +*--expiration* _TIME_::: +Set key and subkey expiration time, counting from the creation time. + ++ +By default generated keys do not expire. + ++ +Expiration time can be specified as: + +* expiration date in the ISO 8601:2019 date format (_yyyy-mm-dd_); or +* hours/days/months/years since creation time with the syntax of _20h_/_30d_/_1m_/_1y_; +* number of seconds. + +*--expert*::: +Select key algorithms interactively and override default settings. + +*--userid*::: +Specifies the _userid_ to be used in generation. + +*--hash*::: +Specify the hash algorithm used in generation. + +*--cipher*::: +Specify the encryption algorithm used in generation. + +*--s2k-iterations*::: +Specify the number of iterations for the S2K (string-to-key) process. + ++ +This is used during the derivation of the symmetric key, which +encrypts a secret key from the password. + + +*--s2k-msec*::: +Specify that *rnpkeys* should automatically pick a +*--s2k-iterations* value such that the single key derivation operation +would take _NUMBER_ of milliseconds on the current system. + ++ +For example, setting it to _2000_ would mean that each secret key +decryption operation would take around 2 seconds (on the current machine). + + +=== KEY/SIGNATURE IMPORT + +*--import*, *--import-keys*, *--import-sigs*:: +Import keys or signatures. + ++ +While *rnpkeys* automatically detects the input data format, +one may still wish to specify whether the input provides keys or signatures. + ++ +By default, the import process will stop on the first discovered +erroneous key or signature. + ++ +Additional options: + +*--permissive*::: +Skip errored or unsupported packets during the import process. + +=== KEY/SIGNATURE EXPORT + +*--export-key* [*--userid*=_FILTER_] [_FILTER_]:: +Export key(s). Only export keys that match _FILTER_ if _FILTER_ is given. + ++ +If filter matches a primary key, the subkeys of the primary key are also exported. ++ +By default, key data is written to _stdout_ in ASCII-armored format. ++ +Additional options: + +*--output* _PATH_::: +Specifies output to be written to a file name instead of _stdout_. + +*--secret*::: +Without this option specified, the command will only export public key(s). +This option must be provided to export secret key(s). + +*--export-rev* _KEY_:: +Export the revocation signature for a specified secret key. + ++ +The revocation signature can be used later in a case of key loss or compromise. ++ +Additional options: + +*--rev-type*::: +Specifies type of key revocation. + +*--rev-reason*::: +Specifies reason for key revocation. + + +=== KEY MANIPULATION + +*--revoke-key* _KEY_:: +Issue revocation signature for the secret key, and save it in the keyring. + ++ +Revoked keys cannot be used further. + ++ +Additional options: + +*--rev-type*::: +Specifies type of key revocation, see *options* section for the available values. + +*--rev-reason*::: +Specifies reason for key revocation. + + +*--remove-key* _KEY_:: +Remove the specified key. + ++ +If a primary key is specified, then all of its subkeys are also removed. + ++ +If the specified key is a secret key, then it will not be deleted without +confirmation. ++ +Additional options: + +*--force*::: +Forces removal of a secret key without prompting the user. + +*--edit-key* _KEY_:: +Edit or update information, associated with a key. Should be accompanied with editing option. + ++ +Currently the following options are available: + ++ +*--add-subkey*::: +Generate and add a new subkey to the existing primary key. All additional options for the +*--generate-key* command apply for subkey generation as well, except *--userid*. + +*--check-cv25519-bits*::: +Check whether least significant/most significant bits of Curve25519 ECDH subkey are correctly set. +RNP internally sets those bits to required values (3 least significant bits and most significant bit must be zero) during decryption, +however other implementations (GnuPG) may require those bits to be set in key material. +_KEY_ must specify the exact subkey via keyid or fingerprint. + +*--fix-cv25519-bits*::: +Set least significant/most significant bits of Curve25519 ECDH subkey to the correct values, and save a key. +So later export of the key would ensure compatibility with other implementations (like GnuPG). +This operation would require the password for your secret key. +Since version 0.16.0 of RNP generated secret key is stored with bits set to a needed value, +however, this may be needed to fix older keys or keys generated by other implementations. +_KEY_ must specify the exact subkey via keyid or fingerprint. + +*--set-expire* _TIME_::: +Set key expiration time. See the description of the *--expiration* option for possible time formats. +Setting argument to 0 removes key expiration, the key would never expire. It is not recommended +due to security reasons. + +=== OPTIONS + +*--homedir* _DIR_:: +Change homedir (where RNP looks for keyrings) to the specified value. + ++ +The default homedir is _~/.rnp_ . + +*--output* _PATH_:: +Write data processing related output to the file specified. + ++ +Combine it with *--overwrite* to overwrite file if it already exists. + +*--overwrite*:: +Overwrite output file if it already exists. + ++ + +*--userid* _USERID_:: +Use the specified _userid_ during key generation and in some +key-searching operations. + +*--numbits* _BITS_:: +Specify size in bits for the generated key and subkey. + ++ +_bits_ may be in range *1024*-*16384*, as long as the public key algorithm +does not place additional limits. + +*--cipher* _ALGORITHM_:: +Set the key encryption algorithm. This is only used in key generation. + ++ +The default value is _AES256_. + +*--hash* _ALGORITHM_:: +Use the specified hash algorithm for signatures and derivation of the encrypting key from password for secret key encryption. + ++ +The default value is _SHA256_. + +*--expert*:: +Use the *expert key generation* mode, allowing the selection of +key/subkey algorithms. + ++ +The following types of keys can be generated in this mode: + ++ +-- +** *DSA* key with *ElGamal* encryption subkey +** *DSA* key with *RSA* subkey +** *ECDSA* key with *ECDH* subkey +** *EdDSA* key with *x25519* subkey +** *SM2* key with subkey +-- ++ +Specifically, for *ECDSA* and *ECDH* the underlying curve can also be specified: + ++ +-- +** _NIST P-256_, _NIST P-384_, _NIST P-521_ +** _brainpoolP256r1_, _brainpoolP384r1_, _brainpoolP512r1_ +** _secp256k1_ +-- + +*--pass-fd* _FD_:: +Specify a file descriptor to read passwords from instead of from _stdin_/_tty_. + ++ +Useful for automated or non-interactive sessions. + +*--password* _PASSWORD_:: +Use the specified password when it is needed. + ++ +WARNING: Not recommended for production use due to potential security issues. +Use *--pass-fd* for batch operations instead. + +*--with-sigs*:: +Print signature information when listing keys via the *-l* command. + +*--force*:: +Force actions to happen without prompting the user. + ++ +This applies to cases such as secret key removal, revoking an already revoked key and so on. + +*--permissive*:: +Skip malformed or unknown keys/signatures during key import. + ++ +By default, *rnpkeys* will stop on the first erroring packet +and exit with an error. + +*--rev-type* _TYPE_:: +Use the specified type during revocation signature generation instead of the default _0_. + ++ +The following values are supported: + ++ +-- +** 0, or "no": no revocation type specified. +** 1, or "superseded": key was superseded with another key. +** 2, or "compromised": key was compromised and no longer valid. +** 3, or "retired": key is retired. +-- ++ +Please refer to *IETF RFC 4880* for details. + +*--rev-reason* _REASON_:: +Add the specified human-readable revocation _REASON_ to the +signature instead of an empty string. + +*--s2k-iterations* _NUMBER_:: +Specify the number of iterations for the S2K (string-to-key) process. + ++ +This is used during the derivation of the symmetric key, which +encrypts a secret key from the password. + ++ +Please refer to IETF RFC 4880 for further details. + +*--s2k-msec* _NUMBER_:: +Specify that *rnpkeys* should automatically pick a +*--s2k-iterations* value such that the single key derivation operation +would take _NUMBER_ of milliseconds on the current system. + ++ +For example, setting it to _2000_ would mean that each secret key +decryption operation would take around 2 seconds (on the current machine). + +*--notty*:: +Disable use of tty. + ++ +By default RNP would detect whether TTY is attached and use it for user prompts. + ++ +This option overrides default behaviour so user input may be passed in batch mode. + +*--current-time* _TIME_:: +Override system's time with a specified value. + ++ +By default RNP uses system's time in all signature/key checks, however in some scenarios it could be needed to override this. + ++ +*TIME* could be specified in the ISO 8601-1:2019 date format (_yyyy-mm-dd_), or in the UNIX timestamp format. + +== EXIT STATUS + +_0_:: + Success. + +_Non-zero_:: + Failure. + +== EXAMPLES + +The following examples demonstrate method of usage of the _rnpkeys_ command. + +=== EXAMPLE 1: IMPORT EXISTING KEYS FROM THE GNUPG + +Following oneliner may be used to import all public keys from the GnuPG: + +*gpg* *-a* *--export* | *rnpkeys* *--import* _-_ + +To import all secret keys the following command should be used (please note, that you'll be asked for secret key password(s)): + +*gpg* *-a* *--export-secret-keys* | *rnpkeys* *--import* _-_ + +=== EXAMPLE 2: GENERATE A NEW KEY + +This example generates a new key with specified userid and expiration. +Also it enables "expert" mode, allowing the selection of key/subkey algorithms. + +*rnpkeys* *--generate* *--userid* *"john@doe.com"* *--expert* *--expiration* *1y* + +== BUGS + +Please report _issues_ via the RNP public issue tracker at: +https://github.com/rnpgp/rnp/issues. + +_Security reports_ or _security-sensitive feedback_ should be reported +according to the instructions at: +https://www.rnpgp.org/feedback. + + +== AUTHORS + +*RNP* is an open source project led by Ribose and has +received contributions from numerous individuals and +organizations. + + +== RESOURCES + +*Web site*: https://www.rnpgp.org + +*Source repository*: https://github.com/rnpgp/rnp + + +== COPYING + +Copyright \(C) 2017-2021 Ribose. +The RNP software suite is _freely licensed_: +please refer to the *LICENSE* file for details. + + + +== SEE ALSO + +*rnp(1)*, *librnp(3)* diff --git a/comm/third_party/rnp/src/rnpkeys/rnpkeys.cpp b/comm/third_party/rnp/src/rnpkeys/rnpkeys.cpp new file mode 100644 index 0000000000..1a6997c288 --- /dev/null +++ b/comm/third_party/rnp/src/rnpkeys/rnpkeys.cpp @@ -0,0 +1,648 @@ +/* + * Copyright (c) 2017-2021, [Ribose Inc](https://www.ribose.com). + * Copyright (c) 2009 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is originally derived from software contributed to + * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and + * carried further by Ribose Inc (https://www.ribose.com). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/* Command line program to perform rnp operations */ + +#ifdef _MSC_VER +#include "uniwin.h" +#else +#include <getopt.h> +#endif +#include <string.h> +#include <stdarg.h> +#include "rnpkeys.h" +#include "str-utils.h" +#include <set> + +const char *usage = + "Manipulate OpenPGP keys and keyrings.\n" + "Usage: rnpkeys --command [options] [files]\n" + "Commands:\n" + " -h, --help This help message.\n" + " -V, --version Print RNP version information.\n" + " -g, --generate-key Generate a new keypair (default is RSA).\n" + " --userid Specify key's userid.\n" + " --expert Select key type, size, and additional parameters.\n" + " --numbits Override default key size (2048).\n" + " --expiration Set key and subkey expiration time.\n" + " --cipher Set cipher used to encrypt a secret key.\n" + " --hash Set hash which is used for key derivation.\n" + " --allow-weak-hash Allow usage of a weak hash algorithm.\n" + " -l, --list-keys List keys in the keyrings.\n" + " --secret List secret keys instead of public ones.\n" + " --with-sigs List signatures as well.\n" + " --import Import keys or signatures.\n" + " --import-keys Import keys.\n" + " --import-sigs Import signatures.\n" + " --permissive Skip erroring keys/sigs instead of failing.\n" + " --export-key Export a key.\n" + " --secret Export a secret key instead of a public.\n" + " --export-rev Export a key's revocation.\n" + " --rev-type Set revocation type.\n" + " --rev-reason Human-readable reason for revocation.\n" + " --revoke-key Revoke a key specified.\n" + " --remove-key Remove a key specified.\n" + " --edit-key Edit key properties.\n" + " --add-subkey Add new subkey.\n" + " --check-cv25519-bits Check whether Cv25519 subkey bits are correct.\n" + " --fix-cv25519-bits Fix Cv25519 subkey bits.\n" + " --set-expire Set key expiration time.\n" + "\n" + "Other options:\n" + " --homedir Override home directory (default is ~/.rnp/).\n" + " --password Password, which should be used during operation.\n" + " --pass-fd Read password(s) from the file descriptor.\n" + " --force Force operation (like secret key removal).\n" + " --output [file, -] Write data to the specified file or stdout.\n" + " --overwrite Overwrite output file without a prompt.\n" + " --notty Do not write anything to the TTY.\n" + " --current-time Override system's time.\n" + "\n" + "See man page for a detailed listing and explanation.\n" + "\n"; + +struct option options[] = { + /* key-management commands */ + {"list-keys", no_argument, NULL, CMD_LIST_KEYS}, + {"export", no_argument, NULL, CMD_EXPORT_KEY}, + {"export-key", optional_argument, NULL, CMD_EXPORT_KEY}, + {"import", no_argument, NULL, CMD_IMPORT}, + {"import-key", no_argument, NULL, CMD_IMPORT_KEYS}, + {"import-keys", no_argument, NULL, CMD_IMPORT_KEYS}, + {"import-sigs", no_argument, NULL, CMD_IMPORT_SIGS}, + {"gen", optional_argument, NULL, CMD_GENERATE_KEY}, + {"gen-key", optional_argument, NULL, CMD_GENERATE_KEY}, + {"generate", optional_argument, NULL, CMD_GENERATE_KEY}, + {"generate-key", optional_argument, NULL, CMD_GENERATE_KEY}, + {"export-rev", no_argument, NULL, CMD_EXPORT_REV}, + {"export-revocation", no_argument, NULL, CMD_EXPORT_REV}, + {"revoke-key", no_argument, NULL, CMD_REVOKE_KEY}, + {"remove-key", no_argument, NULL, CMD_REMOVE_KEY}, + {"edit-key", no_argument, NULL, CMD_EDIT_KEY}, + /* debugging commands */ + {"help", no_argument, NULL, CMD_HELP}, + {"version", no_argument, NULL, CMD_VERSION}, + {"debug", required_argument, NULL, OPT_DEBUG}, + /* options */ + {"coredumps", no_argument, NULL, OPT_COREDUMPS}, + {"keystore-format", required_argument, NULL, OPT_KEY_STORE_FORMAT}, + {"userid", required_argument, NULL, OPT_USERID}, + {"with-sigs", no_argument, NULL, OPT_WITH_SIGS}, + {"hash", required_argument, NULL, OPT_HASH_ALG}, + {"home", required_argument, NULL, OPT_HOMEDIR}, + {"homedir", required_argument, NULL, OPT_HOMEDIR}, + {"numbits", required_argument, NULL, OPT_NUMBITS}, + {"s2k-iterations", required_argument, NULL, OPT_S2K_ITER}, + {"s2k-msec", required_argument, NULL, OPT_S2K_MSEC}, + {"expiration", required_argument, NULL, OPT_EXPIRATION}, + {"pass-fd", required_argument, NULL, OPT_PASSWDFD}, + {"password", required_argument, NULL, OPT_PASSWD}, + {"results", required_argument, NULL, OPT_RESULTS}, + {"cipher", required_argument, NULL, OPT_CIPHER}, + {"expert", no_argument, NULL, OPT_EXPERT}, + {"output", required_argument, NULL, OPT_OUTPUT}, + {"overwrite", no_argument, NULL, OPT_OVERWRITE}, + {"force", no_argument, NULL, OPT_FORCE}, + {"secret", no_argument, NULL, OPT_SECRET}, + {"rev-type", required_argument, NULL, OPT_REV_TYPE}, + {"rev-reason", required_argument, NULL, OPT_REV_REASON}, + {"permissive", no_argument, NULL, OPT_PERMISSIVE}, + {"notty", no_argument, NULL, OPT_NOTTY}, + {"fix-cv25519-bits", no_argument, NULL, OPT_FIX_25519_BITS}, + {"check-cv25519-bits", no_argument, NULL, OPT_CHK_25519_BITS}, + {"add-subkey", no_argument, NULL, OPT_ADD_SUBKEY}, + {"set-expire", required_argument, NULL, OPT_SET_EXPIRE}, + {"current-time", required_argument, NULL, OPT_CURTIME}, + {"allow-weak-hash", no_argument, NULL, OPT_ALLOW_WEAK_HASH}, + {NULL, 0, NULL, 0}, +}; + +/* list keys */ +static bool +print_keys_info(cli_rnp_t *rnp, FILE *fp, const char *filter) +{ + bool psecret = rnp->cfg().get_bool(CFG_SECRET); + bool psigs = rnp->cfg().get_bool(CFG_WITH_SIGS); + int flags = CLI_SEARCH_SUBKEYS_AFTER | (psecret ? CLI_SEARCH_SECRET : 0); + std::vector<rnp_key_handle_t> keys; + + if (!cli_rnp_keys_matching_string(rnp, keys, filter ? filter : "", flags)) { + fprintf(fp, "Key(s) not found.\n"); + return false; + } + fprintf(fp, "%d key%s found\n", (int) keys.size(), (keys.size() == 1) ? "" : "s"); + for (auto key : keys) { + cli_rnp_print_key_info(fp, rnp->ffi, key, psecret, psigs); + } + + fprintf(fp, "\n"); + /* clean up */ + clear_key_handles(keys); + return true; +} + +static bool +import_keys(cli_rnp_t *rnp, rnp_input_t input, const std::string &inname) +{ + std::set<std::string> new_pub_keys; + std::set<std::string> new_sec_keys; + std::set<std::string> updated_keys; + bool res = false; + bool updated = false; + size_t unchanged_keys = 0; + size_t processed_keys = 0; + + uint32_t flags = RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS | + RNP_LOAD_SAVE_SINGLE | RNP_LOAD_SAVE_BASE64; + + bool permissive = rnp->cfg().get_bool(CFG_PERMISSIVE); + if (permissive) { + flags |= RNP_LOAD_SAVE_PERMISSIVE; + } + + do { + /* load keys one-by-one */ + char * results = NULL; + rnp_result_t ret = rnp_import_keys(rnp->ffi, input, flags, &results); + if (ret == RNP_ERROR_EOF) { + res = true; + break; + } + if (ret && updated) { + /* some keys were imported, but then error occurred */ + ERR_MSG("warning: not all data was processed."); + res = true; + break; + } + if (ret) { + ERR_MSG("failed to import key(s) from %s, stopping.", inname.c_str()); + break; + } + + // print information about imported key(s) + json_object *jso = json_tokener_parse(results); + rnp_buffer_destroy(results); + if (!jso) { + ERR_MSG("invalid key import resulting JSON"); + break; + } + json_object *keys = NULL; + if (!json_object_object_get_ex(jso, "keys", &keys)) { + ERR_MSG("invalid key import JSON contents"); + json_object_put(jso); + break; + } + processed_keys += json_object_array_length(keys); + for (size_t idx = 0; idx < (size_t) json_object_array_length(keys); idx++) { + json_object * keyinfo = json_object_array_get_idx(keys, idx); + rnp_key_handle_t key = NULL; + if (!keyinfo) { + continue; + } + std::string pub_status = json_obj_get_str(keyinfo, "public"); + std::string sec_status = json_obj_get_str(keyinfo, "secret"); + const char *fphex = json_obj_get_str(keyinfo, "fingerprint"); + + if (pub_status == "new") { + new_pub_keys.insert(fphex); + updated = true; + } + if (sec_status == "new") { + new_sec_keys.insert(fphex); + updated = true; + } + if (pub_status == "updated" || sec_status == "updated") { + updated_keys.insert(fphex); + updated = true; + } + if (pub_status == "unchanged" || sec_status == "unchanged") { + if (!new_pub_keys.count(fphex) && !new_sec_keys.count(fphex) && + !updated_keys.count(fphex)) { + unchanged_keys++; + continue; + } + } + if (rnp_locate_key(rnp->ffi, "fingerprint", fphex, &key) || !key) { + ERR_MSG("failed to locate key with fingerprint %s", fphex); + continue; + } + cli_rnp_print_key_info(stdout, rnp->ffi, key, true, false); + rnp_key_handle_destroy(key); + } + json_object_put(jso); + } while (1); + + // print statistics + ERR_MSG("Import finished: %lu key%s processed, %lu new public keys, %lu new secret keys, " + "%lu updated, %lu unchanged.", + processed_keys, + (processed_keys != 1) ? "s" : "", + new_pub_keys.size(), + new_sec_keys.size(), + updated_keys.size(), + unchanged_keys); + + if (updated) { + // set default key if we didn't have one + if (rnp->defkey().empty()) { + rnp->set_defkey(); + } + + // save public and secret keyrings + if (!cli_rnp_save_keyrings(rnp)) { + ERR_MSG("failed to save keyrings"); + } + } + return res; +} + +static bool +import_sigs(cli_rnp_t *rnp, rnp_input_t input, const std::string &inname) +{ + bool res = false; + char * results = NULL; + json_object *jso = NULL; + json_object *sigs = NULL; + int unknown_sigs = 0; + int new_sigs = 0; + int old_sigs = 0; + + if (rnp_import_signatures(rnp->ffi, input, 0, &results)) { + ERR_MSG("Failed to import signatures from %s", inname.c_str()); + goto done; + } + // print information about imported signature(s) + jso = json_tokener_parse(results); + if (!jso || !json_object_object_get_ex(jso, "sigs", &sigs)) { + ERR_MSG("Invalid signature import result"); + goto done; + } + + for (size_t idx = 0; idx < (size_t) json_object_array_length(sigs); idx++) { + json_object *siginfo = json_object_array_get_idx(sigs, idx); + if (!siginfo) { + continue; + } + const char *status = json_obj_get_str(siginfo, "public"); + std::string pub_status = status ? status : "unknown"; + status = json_obj_get_str(siginfo, "secret"); + std::string sec_status = status ? status : "unknown"; + + if ((pub_status == "new") || (sec_status == "new")) { + new_sigs++; + } else if ((pub_status == "unchanged") || (sec_status == "unchanged")) { + old_sigs++; + } else { + unknown_sigs++; + } + } + + // print status information + ERR_MSG("Import finished: %d new signature%s, %d unchanged, %d unknown.", + new_sigs, + (new_sigs != 1) ? "s" : "", + old_sigs, + unknown_sigs); + + // save public and secret keyrings + if ((new_sigs > 0) && !cli_rnp_save_keyrings(rnp)) { + ERR_MSG("Failed to save keyrings"); + goto done; + } + res = true; +done: + json_object_put(jso); + rnp_buffer_destroy(results); + return res; +} + +static bool +import(cli_rnp_t *rnp, const std::string &spec, int cmd) +{ + if (spec.empty()) { + ERR_MSG("Import path isn't specified"); + return false; + } + rnp_input_t input = cli_rnp_input_from_specifier(*rnp, spec, NULL); + if (!input) { + ERR_MSG("Failed to create input for %s", spec.c_str()); + return false; + } + if (cmd == CMD_IMPORT) { + char *contents = NULL; + if (rnp_guess_contents(input, &contents)) { + ERR_MSG("Warning! Failed to guess content type to import. Assuming keys."); + } + cmd = (contents && !strcmp(contents, "signature")) ? CMD_IMPORT_SIGS : CMD_IMPORT_KEYS; + rnp_buffer_destroy(contents); + } + + bool res = false; + switch (cmd) { + case CMD_IMPORT_KEYS: + res = import_keys(rnp, input, spec); + break; + case CMD_IMPORT_SIGS: + res = import_sigs(rnp, input, spec); + break; + default: + ERR_MSG("Unexpected command: %d", cmd); + } + rnp_input_destroy(input); + return res; +} + +/* print a usage message */ +void +print_usage(const char *usagemsg) +{ + cli_rnp_print_praise(); + puts(usagemsg); +} + +/* do a command once for a specified file 'f' */ +bool +rnp_cmd(cli_rnp_t *rnp, optdefs_t cmd, const char *f) +{ + std::string fs; + + switch (cmd) { + case CMD_LIST_KEYS: + if (!f && rnp->cfg().get_count(CFG_USERID)) { + fs = rnp->cfg().get_str(CFG_USERID, 0); + f = fs.c_str(); + } + return print_keys_info(rnp, stdout, f); + case CMD_EXPORT_KEY: { + if (!f && rnp->cfg().get_count(CFG_USERID)) { + fs = rnp->cfg().get_str(CFG_USERID, 0); + f = fs.c_str(); + } + if (!f) { + ERR_MSG("No key specified."); + return 0; + } + return cli_rnp_export_keys(rnp, f); + } + case CMD_IMPORT: + case CMD_IMPORT_KEYS: + case CMD_IMPORT_SIGS: + return import(rnp, f ? f : "", cmd); + case CMD_GENERATE_KEY: { + if (!f) { + size_t count = rnp->cfg().get_count(CFG_USERID); + if (count == 1) { + fs = rnp->cfg().get_str(CFG_USERID, 0); + f = fs.c_str(); + } else if (count > 1) { + ERR_MSG("Only single userid is supported for generated keys"); + return false; + } + } + return cli_rnp_generate_key(rnp, f); + } + case CMD_EXPORT_REV: { + if (!f) { + ERR_MSG("You need to specify key to generate revocation for."); + return false; + } + return cli_rnp_export_revocation(rnp, f); + } + case CMD_REVOKE_KEY: { + if (!f) { + ERR_MSG("You need to specify key or subkey to revoke."); + return false; + } + return cli_rnp_revoke_key(rnp, f); + } + case CMD_REMOVE_KEY: { + if (!f) { + ERR_MSG("You need to specify key or subkey to remove."); + return false; + } + return cli_rnp_remove_key(rnp, f); + } + case CMD_EDIT_KEY: { + if (!f) { + ERR_MSG("You need to specify a key or subkey to edit."); + return false; + } + return rnp->edit_key(f); + } + case CMD_VERSION: + cli_rnp_print_praise(); + return true; + case CMD_HELP: + default: + print_usage(usage); + return true; + } +} + +/* set the option */ +bool +setoption(rnp_cfg &cfg, optdefs_t *cmd, int val, const char *arg) +{ + switch (val) { + case OPT_COREDUMPS: +#ifdef _WIN32 + ERR_MSG("warning: --coredumps doesn't make sense on windows systems."); +#endif + cfg.set_bool(CFG_COREDUMPS, true); + return true; + case CMD_GENERATE_KEY: + cfg.set_bool(CFG_NEEDSSECKEY, true); + *cmd = (optdefs_t) val; + return true; + case OPT_EXPERT: + cfg.set_bool(CFG_EXPERT, true); + return true; + case CMD_LIST_KEYS: + case CMD_EXPORT_KEY: + case CMD_EXPORT_REV: + case CMD_REVOKE_KEY: + case CMD_REMOVE_KEY: + case CMD_EDIT_KEY: + case CMD_IMPORT: + case CMD_IMPORT_KEYS: + case CMD_IMPORT_SIGS: + case CMD_HELP: + case CMD_VERSION: + *cmd = (optdefs_t) val; + return true; + /* options */ + case OPT_KEY_STORE_FORMAT: + cfg.set_str(CFG_KEYSTOREFMT, arg); + return true; + case OPT_USERID: + cfg.add_str(CFG_USERID, arg); + return true; + case OPT_HOMEDIR: + cfg.set_str(CFG_HOMEDIR, arg); + return true; + case OPT_NUMBITS: { + int bits = 0; + if (!rnp::str_to_int(arg, bits) || (bits < 1024) || (bits > 16384)) { + ERR_MSG("wrong bits value: %s", arg); + return false; + } + cfg.set_int(CFG_NUMBITS, bits); + return true; + } + case OPT_ALLOW_WEAK_HASH: + cfg.set_bool(CFG_WEAK_HASH, true); + return true; + case OPT_HASH_ALG: + return cli_rnp_set_hash(cfg, arg); + case OPT_S2K_ITER: { + int iterations = atoi(arg); + if (!iterations) { + ERR_MSG("Wrong iterations value: %s", arg); + return false; + } + cfg.set_int(CFG_S2K_ITER, iterations); + return true; + } + case OPT_EXPIRATION: + cfg.set_str(CFG_KG_PRIMARY_EXPIRATION, arg); + cfg.set_str(CFG_KG_SUBKEY_EXPIRATION, arg); + return true; + case OPT_S2K_MSEC: { + int msec = 0; + if (!rnp::str_to_int(arg, msec) || !msec) { + ERR_MSG("Invalid s2k msec value: %s", arg); + return false; + } + cfg.set_int(CFG_S2K_MSEC, msec); + return true; + } + case OPT_PASSWDFD: + cfg.set_str(CFG_PASSFD, arg); + return true; + case OPT_PASSWD: + cfg.set_str(CFG_PASSWD, arg); + return true; + case OPT_RESULTS: + cfg.set_str(CFG_IO_RESS, arg); + return true; + case OPT_CIPHER: + return cli_rnp_set_cipher(cfg, arg); + case OPT_DEBUG: + ERR_MSG("Option --debug is deprecated, ignoring."); + return true; + case OPT_OUTPUT: + if (!arg) { + ERR_MSG("No output filename argument provided"); + return false; + } + cfg.set_str(CFG_OUTFILE, arg); + return true; + case OPT_OVERWRITE: + cfg.set_bool(CFG_OVERWRITE, true); + return true; + case OPT_FORCE: + cfg.set_bool(CFG_FORCE, true); + return true; + case OPT_SECRET: + cfg.set_bool(CFG_SECRET, true); + return true; + case OPT_WITH_SIGS: + cfg.set_bool(CFG_WITH_SIGS, true); + return true; + case OPT_REV_TYPE: { + std::string revtype = arg; + if (revtype == "0") { + revtype = "no"; + } else if (revtype == "1") { + revtype = "superseded"; + } else if (revtype == "2") { + revtype = "compromised"; + } else if (revtype == "3") { + revtype = "retired"; + } + cfg.set_str(CFG_REV_TYPE, revtype); + return true; + } + case OPT_REV_REASON: + cfg.set_str(CFG_REV_REASON, arg); + return true; + case OPT_PERMISSIVE: + cfg.set_bool(CFG_PERMISSIVE, true); + return true; + case OPT_NOTTY: + cfg.set_bool(CFG_NOTTY, true); + return true; + case OPT_FIX_25519_BITS: + cfg.set_bool(CFG_FIX_25519_BITS, true); + return true; + case OPT_CHK_25519_BITS: + cfg.set_bool(CFG_CHK_25519_BITS, true); + return true; + case OPT_CURTIME: + cfg.set_str(CFG_CURTIME, arg); + return true; + case OPT_ADD_SUBKEY: + cfg.set_bool(CFG_ADD_SUBKEY, true); + return true; + case OPT_SET_EXPIRE: + cfg.set_str(CFG_SET_KEY_EXPIRE, arg); + return true; + default: + *cmd = CMD_HELP; + return true; + } +} + +bool +rnpkeys_init(cli_rnp_t &rnp, const rnp_cfg &cfg) +{ + rnp_cfg rnpcfg; + rnpcfg.load_defaults(); + rnpcfg.set_int(CFG_NUMBITS, DEFAULT_RSA_NUMBITS); + rnpcfg.set_str(CFG_IO_RESS, "<stdout>"); + rnpcfg.copy(cfg); + + if (!cli_cfg_set_keystore_info(rnpcfg)) { + ERR_MSG("fatal: cannot set keystore info"); + return false; + } + if (!rnp.init(rnpcfg)) { + ERR_MSG("fatal: failed to initialize rnpkeys"); + return false; + } + if (!cli_rnp_check_weak_hash(&rnp)) { + ERR_MSG("Weak hash algorithm detected. Pass --allow-weak-hash option if you really " + "want to use it."); + return false; + } + /* TODO: at some point we should check for error here */ + (void) rnp.load_keyrings(true); + return true; +} diff --git a/comm/third_party/rnp/src/rnpkeys/rnpkeys.h b/comm/third_party/rnp/src/rnpkeys/rnpkeys.h new file mode 100644 index 0000000000..611fcc10e7 --- /dev/null +++ b/comm/third_party/rnp/src/rnpkeys/rnpkeys.h @@ -0,0 +1,80 @@ +#ifndef RNPKEYS_H_ +#define RNPKEYS_H_ + +#include <stdbool.h> +#ifdef HAVE_SYS_PARAM_H +#include <sys/param.h> +#else +#include "uniwin.h" +#endif +#include "../rnp/fficli.h" +#include "logging.h" + +#define DEFAULT_RSA_NUMBITS 2048 + +typedef enum { + /* commands */ + CMD_NONE = 0, + CMD_LIST_KEYS = 260, + CMD_EXPORT_KEY, + CMD_IMPORT, + CMD_IMPORT_KEYS, + CMD_IMPORT_SIGS, + CMD_GENERATE_KEY, + CMD_EXPORT_REV, + CMD_REVOKE_KEY, + CMD_REMOVE_KEY, + CMD_EDIT_KEY, + CMD_VERSION, + CMD_HELP, + + /* options */ + OPT_KEY_STORE_FORMAT, + OPT_USERID, + OPT_HOMEDIR, + OPT_NUMBITS, + OPT_ALLOW_WEAK_HASH, + OPT_HASH_ALG, + OPT_COREDUMPS, + OPT_PASSWDFD, + OPT_PASSWD, + OPT_RESULTS, + OPT_CIPHER, + OPT_EXPERT, + OPT_OUTPUT, + OPT_OVERWRITE, + OPT_FORCE, + OPT_SECRET, + OPT_S2K_ITER, + OPT_S2K_MSEC, + OPT_EXPIRATION, + OPT_WITH_SIGS, + OPT_REV_TYPE, + OPT_REV_REASON, + OPT_PERMISSIVE, + OPT_NOTTY, + OPT_FIX_25519_BITS, + OPT_CHK_25519_BITS, + OPT_CURTIME, + OPT_ADD_SUBKEY, + OPT_SET_EXPIRE, + + /* debug */ + OPT_DEBUG +} optdefs_t; + +bool rnp_cmd(cli_rnp_t *rnp, optdefs_t cmd, const char *f); +bool setoption(rnp_cfg &cfg, optdefs_t *cmd, int val, const char *arg); +void print_usage(const char *usagemsg); + +/** + * @brief Initializes rnpkeys. Function allocates memory dynamically for + * rnp argument, which must be freed by the caller. + * + * @param rnp initialized rnp context + * @param cfg configuration with settings from command line + * @return true on success, or false otherwise. + */ +bool rnpkeys_init(cli_rnp_t &rnp, const rnp_cfg &cfg); + +#endif /* _rnpkeys_ */ diff --git a/comm/third_party/rnp/src/rnpkeys/tui.cpp b/comm/third_party/rnp/src/rnpkeys/tui.cpp new file mode 100644 index 0000000000..73f26dc2d1 --- /dev/null +++ b/comm/third_party/rnp/src/rnpkeys/tui.cpp @@ -0,0 +1,413 @@ +/* + * Copyright (c) 2017-2020, [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <algorithm> +#ifdef _MSC_VER +#include "uniwin.h" +#else +#include <unistd.h> +#endif +#include <errno.h> +#include <iterator> +#include "rnp/rnpcfg.h" +#include "rnpkeys.h" +#include "defaults.h" +#include "file-utils.h" +#include "logging.h" + +/* ----------------------------------------------------------------------------- + * @brief Reads input from file pointer and converts it securelly to ints + * Partially based on ERR34-C from SEI CERT C Coding Standard + * + * @param fp pointer to opened pipe + * @param result[out] result read from file pointer and converted to int + * + * @returns true and value in result if integer was parsed correctly, + * otherwise false + * +-------------------------------------------------------------------------------- */ +static bool +rnp_secure_get_long_from_fd(FILE *fp, long &result, bool allow_empty = true) +{ + char buff[BUFSIZ]; + if (!fgets(buff, sizeof(buff), fp)) { + RNP_LOG("EOF or read error"); + return false; + } + + errno = 0; + char *end_ptr = NULL; + long num_long = strtol(buff, &end_ptr, 10); + if (ERANGE == errno) { + RNP_LOG("Number out of range"); + return false; + } + if (end_ptr == buff) { + return allow_empty; + } + if ('\n' != *end_ptr && '\0' != *end_ptr) { + RNP_LOG("Unexpected end of line"); + return false; + } + + result = num_long; + return true; +} + +static bool +is_rsa_keysize_supported(uint32_t keysize) +{ + return ((keysize >= 1024) && (keysize <= 4096) && !(keysize % 8)); +} + +static const char * +ask_curve_name(FILE *input_fp) +{ + std::vector<const char *> curves; + static const char *const known_curves[] = { + "NIST P-256", + "NIST P-384", + "NIST P-521", + "brainpoolP256r1", + "brainpoolP384r1", + "brainpoolP512r1", + "secp256k1", + }; + const size_t curvenum = sizeof(known_curves) / sizeof(*known_curves); + + try { + std::copy_if(known_curves, + known_curves + curvenum, + std::back_inserter(curves), + [](const char *curve) { + bool supported = false; + return !rnp_supports_feature(RNP_FEATURE_CURVE, curve, &supported) && + supported; + }); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return NULL; + } + const size_t ccount = curves.size(); + if (!ccount) { + return NULL; + } + bool ok = false; + const char *result = NULL; + int attempts = 0; + do { + if (attempts >= 10) { + printf("Too many attempts. Aborting.\n"); + return NULL; + } + printf("Please select which elliptic curve you want:\n"); + for (size_t i = 0; i < ccount; i++) { + printf("\t(%zu) %s\n", i + 1, curves[i]); + } + printf("(default %s)> ", DEFAULT_CURVE); + long val = 0; + ok = rnp_secure_get_long_from_fd(input_fp, val) && (val > 0) && (val <= (long) ccount); + if (ok) { + result = curves[val - 1]; + } + attempts++; + } while (!ok); + + return result; +} + +static long +ask_rsa_bitlen(FILE *input_fp) +{ + long result = 0; + do { + result = DEFAULT_RSA_NUMBITS; + printf("Please provide bit length of the key (between 1024 and 4096):\n(default %d)> ", + DEFAULT_RSA_NUMBITS); + } while (!rnp_secure_get_long_from_fd(input_fp, result) || + !is_rsa_keysize_supported(result)); + return result; +} + +static long +ask_elgamal_bitlen(FILE *input_fp) +{ + do { + printf( + "Please provide bit length of the ElGamal key (between %d and %d):\n(default %d) > ", + ELGAMAL_MIN_P_BITLEN, + ELGAMAL_MAX_P_BITLEN, + DEFAULT_ELGAMAL_NUMBITS); + long result = DEFAULT_ELGAMAL_NUMBITS; + if (!rnp_secure_get_long_from_fd(input_fp, result)) { + continue; + } + if ((result >= ELGAMAL_MIN_P_BITLEN) && (result <= ELGAMAL_MAX_P_BITLEN)) { + // round up to multiple of 32 + result = ((result + 31) / 32) * 32; + printf("Bitlen of the key will be %lu\n", result); + return result; + } + } while (1); +} + +static long +ask_dsa_bitlen(FILE *input_fp) +{ + do { + printf( + "Please provide bit length of the DSA key (between %d and %d):\n(default %d) > ", + DSA_MIN_P_BITLEN, + DSA_MAX_P_BITLEN, + DSA_DEFAULT_P_BITLEN); + long result = DSA_DEFAULT_P_BITLEN; + if (!rnp_secure_get_long_from_fd(input_fp, result)) { + continue; + } + if ((result >= DSA_MIN_P_BITLEN) && (result <= DSA_MAX_P_BITLEN)) { + // round up to multiple of 64 + result = ((result + 63) / 64) * 64; + printf("Bitlen of the key will be %lu\n", result); + return result; + } + } while (1); +} + +static bool +rnpkeys_ask_generate_params(rnp_cfg &cfg, FILE *input_fp) +{ + long option = 0; + do { + printf("Please select what kind of key you want:\n" + "\t(1) RSA (Encrypt or Sign)\n" + "\t(16) DSA + ElGamal\n" + "\t(17) DSA + RSA\n" // TODO: See #584 + "\t(19) ECDSA + ECDH\n" + "\t(22) EDDSA + X25519\n" + "\t(99) SM2\n" + "> "); + if (!rnp_secure_get_long_from_fd(input_fp, option, false)) { + option = 0; + continue; + } + switch (option) { + case 1: { + int bits = ask_rsa_bitlen(input_fp); + cfg.set_str(CFG_KG_PRIMARY_ALG, RNP_ALGNAME_RSA); + cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_RSA); + cfg.set_int(CFG_KG_PRIMARY_BITS, bits); + cfg.set_int(CFG_KG_SUBKEY_BITS, bits); + break; + } + case 16: { + int bits = ask_dsa_bitlen(input_fp); + cfg.set_str(CFG_KG_PRIMARY_ALG, RNP_ALGNAME_DSA); + cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_ELGAMAL); + cfg.set_int(CFG_KG_PRIMARY_BITS, bits); + cfg.set_int(CFG_KG_SUBKEY_BITS, bits); + break; + } + case 17: { + int bits = ask_dsa_bitlen(input_fp); + cfg.set_str(CFG_KG_PRIMARY_ALG, RNP_ALGNAME_DSA); + cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_RSA); + cfg.set_int(CFG_KG_PRIMARY_BITS, bits); + cfg.set_int(CFG_KG_SUBKEY_BITS, bits); + break; + } + case 19: { + const char *curve = ask_curve_name(input_fp); + if (!curve) { + return false; + } + cfg.set_str(CFG_KG_PRIMARY_ALG, RNP_ALGNAME_ECDSA); + cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_ECDH); + cfg.set_str(CFG_KG_PRIMARY_CURVE, curve); + cfg.set_str(CFG_KG_SUBKEY_CURVE, curve); + break; + } + case 22: { + cfg.set_str(CFG_KG_PRIMARY_ALG, RNP_ALGNAME_EDDSA); + cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_ECDH); + cfg.set_str(CFG_KG_SUBKEY_CURVE, "Curve25519"); + break; + } + case 99: { + cfg.set_str(CFG_KG_PRIMARY_ALG, RNP_ALGNAME_SM2); + cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_SM2); + if (!cfg.has(CFG_KG_HASH)) { + cfg.set_str(CFG_KG_HASH, RNP_ALGNAME_SM3); + } + break; + } + default: + option = 0; + break; + } + } while (!option); + + return true; +} + +static bool +rnpkeys_ask_generate_params_subkey(rnp_cfg &cfg, FILE *input_fp) +{ + long option = 0; + do { + printf("Please select subkey algorithm you want:\n" + "\t(1) RSA\n" + "\t(16) ElGamal\n" + "\t(17) DSA\n" + "\t(18) ECDH\n" + "\t(19) ECDSA\n" + "\t(22) EDDSA\n" + "\t(99) SM2" + "> "); + if (!rnp_secure_get_long_from_fd(input_fp, option, false)) { + option = 0; + continue; + } + switch (option) { + case 1: { + int bits = ask_rsa_bitlen(input_fp); + cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_RSA); + cfg.set_int(CFG_KG_SUBKEY_BITS, bits); + break; + } + case 16: { + int bits = ask_elgamal_bitlen(input_fp); + cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_ELGAMAL); + cfg.set_int(CFG_KG_SUBKEY_BITS, bits); + break; + } + case 17: { + int bits = ask_dsa_bitlen(input_fp); + cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_DSA); + cfg.set_int(CFG_KG_SUBKEY_BITS, bits); + break; + } + case 18: { + const char *curve = ask_curve_name(input_fp); + if (!curve) { + return false; + } + cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_ECDH); + cfg.set_str(CFG_KG_SUBKEY_CURVE, curve); + break; + } + case 19: { + const char *curve = ask_curve_name(input_fp); + if (!curve) { + return false; + } + cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_ECDSA); + cfg.set_str(CFG_KG_SUBKEY_CURVE, curve); + break; + } + case 22: { + cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_EDDSA); + break; + } + case 99: { + cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_SM2); + if (!cfg.has(CFG_KG_HASH)) { + cfg.set_str(CFG_KG_HASH, RNP_ALGNAME_SM3); + } + break; + } + default: + option = 0; + break; + } + } while (!option); + + return true; +} + +bool +cli_rnp_set_generate_params(rnp_cfg &cfg, bool subkey) +{ + bool res = true; + // hash algorithms for signing and protection + if (cfg.has(CFG_HASH)) { + cfg.set_str(CFG_KG_HASH, cfg.get_str(CFG_HASH)); + cfg.set_str(CFG_KG_PROT_HASH, cfg.get_str(CFG_HASH)); + } + + // key and subkey algorithms, bit length/curve + if (!cfg.get_bool(CFG_EXPERT)) { + cfg.set_str(CFG_KG_PRIMARY_ALG, RNP_ALGNAME_RSA); + cfg.set_int(CFG_KG_PRIMARY_BITS, cfg.get_int(CFG_NUMBITS)); + cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_RSA); + cfg.set_int(CFG_KG_SUBKEY_BITS, cfg.get_int(CFG_NUMBITS)); + } else { + FILE *input = stdin; + if (cfg.has(CFG_USERINPUTFD)) { + int inputfd = dup(cfg.get_int(CFG_USERINPUTFD)); + if (inputfd != -1) { + input = rnp_fdopen(inputfd, "r"); + if (!input) { + close(inputfd); + } + } + } + if (!input) { + return false; + } + if (subkey) { + res = rnpkeys_ask_generate_params_subkey(cfg, input); + } else { + res = rnpkeys_ask_generate_params(cfg, input); + } + if (input != stdin) { + fclose(input); + } + if (!res) { + return false; + } + } + + // make sure hash algorithms are set + if (!cfg.has(CFG_KG_HASH)) { + cfg.set_str(CFG_KG_HASH, DEFAULT_HASH_ALG); + } + if (!cfg.has(CFG_KG_PROT_HASH)) { + cfg.set_str(CFG_KG_PROT_HASH, DEFAULT_HASH_ALG); + } + + // protection symmetric algorithm + cfg.set_str(CFG_KG_PROT_ALG, + cfg.has(CFG_CIPHER) ? cfg.get_str(CFG_CIPHER) : DEFAULT_SYMM_ALG); + // protection iterations count + size_t iterations = cfg.get_int(CFG_S2K_ITER); + if (!iterations) { + res = res && !rnp_calculate_iterations(cfg.get_str(CFG_KG_PROT_HASH).c_str(), + cfg.get_int(CFG_S2K_MSEC), + &iterations); + } + cfg.set_int(CFG_KG_PROT_ITERATIONS, iterations); + return res; +} |