// clang++ -o openssl-sign-ed25519 openssl-sign-ed25519.cc -I/opt/local/include -L/opt/local/lib -lcrypto -std=c++14 /* * MacGDBp * Copyright (c) 2019, Blue Static * * This program is free software; you can redistribute it and/or modify it under the terms of the GNU * General Public License as published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along with this program; if not, * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ /* openssl-sign-ed25519 provides a sign/verify interface for ED25519 operations. Until https://github.com/openssl/openssl/issues/6988 is fixed, OpenSSL cannot be used to generate and verify signatures of files using ED25519 keys. Sparkle only supports ED25519 keys, so this tool is used to bridge the gap. Usage: Get base64 signature: ./openssl-sign-ed25519 --sign --key /path/to/key.pem --file file.zip | openssl enc -a -A Verify signature: cat sig-b64 | openssl dec -a | ./openssl-sign-ed25519 --verify - --key /path/to/key.pem --file file.zip Usage Notes: - Encrypted private keys are not supported as no password input is provided. Implementation Notes: - No resources are freed since this is a one-shot tool. */ #include #include #include #include #include #include #include #include #include #include const option kOptions[] = { {"key", required_argument, nullptr, 'k'}, {"sign", no_argument, nullptr, 's'}, {"verify", required_argument, nullptr, 'v'}, {"file", required_argument, nullptr, 'i'}, {nullptr, 0, nullptr, 0}, }; void Usage(const char* prog) { fprintf(stderr, "%s: ", prog); for (const auto& opt : kOptions) { if (!opt.name) continue; if (opt.has_arg == optional_argument) fprintf(stderr, "["); fprintf(stderr, "--%s", opt.name); if (opt.has_arg == optional_argument) fprintf(stderr, "]"); if (opt.has_arg != no_argument) fprintf(stderr, " <%s>", opt.name); fprintf(stderr, " "); } fprintf(stderr, "\n"); } void CryptoError(const char* msg) { unsigned long error = ERR_get_error(); char buf[256]; ERR_error_string(error, buf); fprintf(stderr, "%s: -%ld %s\n", msg, error, buf); } bool ReadEntireFile(const char* path, std::vector* data) { int fd = -1; if (strncmp(path, "-", 1) == 0) { fd = STDIN_FILENO; } else { fd = open(path, O_RDONLY); if (fd < 0) { perror("open"); return false; } } const size_t buffer_size = getpagesize(); ssize_t bytes_read = 0; do { size_t current_size = data->size(); data->resize(current_size + buffer_size); ssize_t bytes_read = read(fd, &(*data)[current_size], buffer_size); if (bytes_read < 0) { perror("read"); return false; } else if (bytes_read >= 0) { data->resize(current_size + bytes_read); } } while (bytes_read > 0); return true; } bool LoadKeyAndCreateContext(const char* keyfile_path, EVP_PKEY** pkey, EVP_PKEY_CTX** pctx) { FILE* keyfile = fopen(keyfile_path, "r"); if (!keyfile) { perror("fopen keyfile"); return false; } *pkey = PEM_read_PrivateKey(keyfile, /*keyout=*/nullptr, /*password=*/nullptr, /*ucontext=*/nullptr); if (!*pkey) { CryptoError("Failed to read private key"); return false; } *pctx = EVP_PKEY_CTX_new(*pkey, nullptr); if (!pctx) { CryptoError("Failed to create pkey context"); return false; } return true; } bool SignFile(EVP_PKEY* pkey, EVP_PKEY_CTX* pctx, const char* infile_path) { int rv; std::vector data; if (!ReadEntireFile(infile_path, &data)) return false; EVP_MD_CTX* ctx = EVP_MD_CTX_new(); rv = EVP_DigestSignInit(ctx, &pctx, /*type=*/nullptr, /*engine=*/nullptr, pkey); if (rv != 1) { CryptoError("Failed to initialize digest context"); return false; } size_t signature_length; rv = EVP_DigestSign(ctx, nullptr, &signature_length, data.data(), data.size()); if (rv != 1) { CryptoError("Failed to sign - get length"); return false; } std::unique_ptr signature(new uint8_t[signature_length]); rv = EVP_DigestSign(ctx, signature.get(), &signature_length, data.data(), data.size()); if (rv < 0) { CryptoError("Failed to sign"); return false; } for (size_t i = 0; i < signature_length; ) { ssize_t written = write(STDOUT_FILENO, &signature.get()[i], signature_length - i); if (written <= 0) { perror("write"); return false; } i += written; } return true; } bool VerifyFile(EVP_PKEY* pkey, EVP_PKEY_CTX* pctx, const char* sigfile_path, const char* infile_path) { int rv; std::vector signature; if (!ReadEntireFile(sigfile_path, &signature)) return false; std::vector data; if (!ReadEntireFile(infile_path, &data)) return false; EVP_MD_CTX* ctx = EVP_MD_CTX_new(); rv = EVP_DigestVerifyInit(ctx, &pctx, /*type=*/nullptr, /*engine=*/nullptr, pkey); if (rv != 1) { CryptoError("Failed to initialize verify context"); return false; } rv = EVP_DigestVerify(ctx, signature.data(), signature.size(), data.data(), data.size()); if (rv != 1) { printf("Failed to verify data.\n"); return false; } printf("Verify OK.\n"); return true; } int main(int argc, char* const argv[]) { bool do_sign = false; const char* keyfile = nullptr; const char* sigfile = nullptr; const char* infile = nullptr; int opt; while ((opt = getopt_long(argc, argv, "ksvih", kOptions, nullptr)) != -1) { switch (opt) { case 's': do_sign = true; break; case 'k': keyfile = optarg; break; case 'v': sigfile = optarg; break; case 'i': infile = optarg; break; case 'h': Usage(argv[0]); return EXIT_SUCCESS; default: Usage(argv[0]); return EXIT_FAILURE; } } if (!infile) { fprintf(stderr, "No input file specified.\n"); Usage(argv[0]); return EXIT_FAILURE; } if (!keyfile) { fprintf(stderr, "Key must be specified.\n"); Usage(argv[0]); return EXIT_FAILURE; } if (!(do_sign ^ (sigfile != nullptr))) { fprintf(stderr, "Must specify one of --sign or --verify.\n"); Usage(argv[0]); return EXIT_FAILURE; } EVP_PKEY* pkey; EVP_PKEY_CTX* pctx; if (!LoadKeyAndCreateContext(keyfile, &pkey, &pctx)) return EXIT_FAILURE; bool ok = false; if (do_sign) ok = SignFile(pkey, pctx, infile); else ok = VerifyFile(pkey, pctx, sigfile, infile); return ok ? EXIT_SUCCESS : EXIT_FAILURE; }