1 // clang++ -o openssl-sign-ed25519 openssl-sign-ed25519.cc -I/opt/local/include -L/opt/local/lib -lcrypto -std=c++14
4 * Copyright (c) 2019, Blue Static <https://www.bluestatic.org>
6 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
7 * General Public License as published by the Free Software Foundation; either version 2 of the
8 * License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
11 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
14 * You should have received a copy of the GNU General Public License along with this program; if not,
15 * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
20 openssl-sign-ed25519 provides a sign/verify interface for ED25519 operations.
22 Until https://github.com/openssl/openssl/issues/6988 is fixed, OpenSSL cannot
23 be used to generate and verify signatures of files using ED25519 keys. Sparkle
24 only supports ED25519 keys, so this tool is used to bridge the gap.
30 ./openssl-sign-ed25519 --sign --key /path/to/key.pem --file file.zip | openssl enc -a -A
34 cat sig-b64 | openssl dec -a | ./openssl-sign-ed25519 --verify - --key /path/to/key.pem --file file.zip
38 - Encrypted private keys are not supported as no password input is provided.
42 - No resources are freed since this is a one-shot tool.
47 #include <openssl/evp.h>
48 #include <openssl/err.h>
49 #include <openssl/pem.h>
57 const option kOptions
[] = {
58 {"key", required_argument
, nullptr, 'k'},
59 {"sign", no_argument
, nullptr, 's'},
60 {"verify", required_argument
, nullptr, 'v'},
61 {"file", required_argument
, nullptr, 'i'},
62 {nullptr, 0, nullptr, 0},
65 void Usage(const char* prog
) {
66 fprintf(stderr
, "%s: ", prog
);
67 for (const auto& opt
: kOptions
) {
71 if (opt
.has_arg
== optional_argument
)
73 fprintf(stderr
, "--%s", opt
.name
);
74 if (opt
.has_arg
== optional_argument
)
77 if (opt
.has_arg
!= no_argument
)
78 fprintf(stderr
, " <%s>", opt
.name
);
82 fprintf(stderr
, "\n");
85 void CryptoError(const char* msg
) {
86 unsigned long error
= ERR_get_error();
88 ERR_error_string(error
, buf
);
89 fprintf(stderr
, "%s: -%ld %s\n", msg
, error
, buf
);
92 bool ReadEntireFile(const char* path
, std
::vector
<uint8_t>* data
) {
94 if (strncmp(path
, "-", 1) == 0) {
97 fd
= open(path
, O_RDONLY
);
104 const size_t buffer_size
= getpagesize();
105 ssize_t bytes_read
= 0;
107 size_t current_size
= data
->size();
108 data
->resize(current_size
+ buffer_size
);
110 ssize_t bytes_read
= read(fd
, &(*data
)[current_size
], buffer_size
);
111 if (bytes_read
< 0) {
114 } else if (bytes_read
>= 0) {
115 data
->resize(current_size
+ bytes_read
);
117 } while (bytes_read
> 0);
122 bool LoadKeyAndCreateContext(const char* keyfile_path
, EVP_PKEY
** pkey
, EVP_PKEY_CTX
** pctx
) {
123 FILE* keyfile
= fopen(keyfile_path
, "r");
125 perror("fopen keyfile");
129 *pkey
= PEM_read_PrivateKey(keyfile
, /*keyout=*/nullptr, /*password=*/nullptr, /*ucontext=*/nullptr);
131 CryptoError("Failed to read private key");
135 *pctx
= EVP_PKEY_CTX_new(*pkey
, nullptr);
137 CryptoError("Failed to create pkey context");
144 bool SignFile(EVP_PKEY
* pkey
, EVP_PKEY_CTX
* pctx
, const char* infile_path
) {
147 std
::vector
<uint8_t> data
;
148 if (!ReadEntireFile(infile_path
, &data
))
151 EVP_MD_CTX
* ctx
= EVP_MD_CTX_new();
152 rv
= EVP_DigestSignInit(ctx
, &pctx
, /*type=*/nullptr, /*engine=*/nullptr, pkey
);
154 CryptoError("Failed to initialize digest context");
158 size_t signature_length
;
159 rv
= EVP_DigestSign(ctx
, nullptr, &signature_length
, data
.data(), data
.size());
161 CryptoError("Failed to sign - get length");
165 std
::unique_ptr
<uint8_t[]> signature(new uint8_t[signature_length
]);
166 rv
= EVP_DigestSign(ctx
, signature
.get(), &signature_length
, data
.data(), data
.size());
168 CryptoError("Failed to sign");
172 for (size_t i
= 0; i
< signature_length
; ) {
173 ssize_t written
= write(STDOUT_FILENO
, &signature
.get()[i
], signature_length
- i
);
184 bool VerifyFile(EVP_PKEY
* pkey
, EVP_PKEY_CTX
* pctx
, const char* sigfile_path
, const char* infile_path
) {
187 std
::vector
<uint8_t> signature
;
188 if (!ReadEntireFile(sigfile_path
, &signature
))
191 std
::vector
<uint8_t> data
;
192 if (!ReadEntireFile(infile_path
, &data
))
195 EVP_MD_CTX
* ctx
= EVP_MD_CTX_new();
197 rv
= EVP_DigestVerifyInit(ctx
, &pctx
, /*type=*/nullptr, /*engine=*/nullptr, pkey
);
199 CryptoError("Failed to initialize verify context");
203 rv
= EVP_DigestVerify(ctx
, signature
.data(), signature
.size(), data
.data(), data
.size());
205 printf("Failed to verify data.\n");
209 printf("Verify OK.\n");
214 int main(int argc
, char* const argv
[]) {
215 bool do_sign
= false;
216 const char* keyfile
= nullptr;
217 const char* sigfile
= nullptr;
218 const char* infile
= nullptr;
221 while ((opt
= getopt_long(argc
, argv
, "ksvih", kOptions
, nullptr)) != -1) {
245 fprintf(stderr
, "No input file specified.\n");
251 fprintf(stderr
, "Key must be specified.\n");
256 if (!(do_sign
^ (sigfile
!= nullptr))) {
257 fprintf(stderr
, "Must specify one of --sign or --verify.\n");
264 if (!LoadKeyAndCreateContext(keyfile
, &pkey
, &pctx
))
270 ok
= SignFile(pkey
, pctx
, infile
);
272 ok
= VerifyFile(pkey
, pctx
, sigfile
, infile
);
274 return ok ? EXIT_SUCCESS
: EXIT_FAILURE
;