Add a script to generate the Sparkle signatures.
[macgdbp.git] / dev / openssl-sign-ed25519.cc
1 // clang++ -o openssl-sign-ed25519 openssl-sign-ed25519.cc -I/opt/local/include -L/opt/local/lib -lcrypto -std=c++14
2 /*
3 * MacGDBp
4 * Copyright (c) 2019, Blue Static <https://www.bluestatic.org>
5 *
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.
9 *
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.
13 *
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
16 */
17
18
19 /*
20 openssl-sign-ed25519 provides a sign/verify interface for ED25519 operations.
21
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.
25
26 Usage:
27
28 Get base64 signature:
29
30 ./openssl-sign-ed25519 --sign --key /path/to/key.pem --file file.zip | openssl enc -a -A
31
32 Verify signature:
33
34 cat sig-b64 | openssl dec -a | ./openssl-sign-ed25519 --verify - --key /path/to/key.pem --file file.zip
35
36 Usage Notes:
37
38 - Encrypted private keys are not supported as no password input is provided.
39
40 Implementation Notes:
41
42 - No resources are freed since this is a one-shot tool.
43 */
44
45 #include <fcntl.h>
46 #include <getopt.h>
47 #include <openssl/evp.h>
48 #include <openssl/err.h>
49 #include <openssl/pem.h>
50 #include <stdint.h>
51 #include <stdio.h>
52 #include <unistd.h>
53
54 #include <memory>
55 #include <vector>
56
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},
63 };
64
65 void Usage(const char* prog) {
66 fprintf(stderr, "%s: ", prog);
67 for (const auto& opt : kOptions) {
68 if (!opt.name)
69 continue;
70
71 if (opt.has_arg == optional_argument)
72 fprintf(stderr, "[");
73 fprintf(stderr, "--%s", opt.name);
74 if (opt.has_arg == optional_argument)
75 fprintf(stderr, "]");
76
77 if (opt.has_arg != no_argument)
78 fprintf(stderr, " <%s>", opt.name);
79
80 fprintf(stderr, " ");
81 }
82 fprintf(stderr, "\n");
83 }
84
85 void CryptoError(const char* msg) {
86 unsigned long error = ERR_get_error();
87 char buf[256];
88 ERR_error_string(error, buf);
89 fprintf(stderr, "%s: -%ld %s\n", msg, error, buf);
90 }
91
92 bool ReadEntireFile(const char* path, std::vector<uint8_t>* data) {
93 int fd = -1;
94 if (strncmp(path, "-", 1) == 0) {
95 fd = STDIN_FILENO;
96 } else {
97 fd = open(path, O_RDONLY);
98 if (fd < 0) {
99 perror("open");
100 return false;
101 }
102 }
103
104 const size_t buffer_size = getpagesize();
105 ssize_t bytes_read = 0;
106 do {
107 size_t current_size = data->size();
108 data->resize(current_size + buffer_size);
109
110 ssize_t bytes_read = read(fd, &(*data)[current_size], buffer_size);
111 if (bytes_read < 0) {
112 perror("read");
113 return false;
114 } else if (bytes_read >= 0) {
115 data->resize(current_size + bytes_read);
116 }
117 } while (bytes_read > 0);
118
119 return true;
120 }
121
122 bool LoadKeyAndCreateContext(const char* keyfile_path, EVP_PKEY** pkey, EVP_PKEY_CTX** pctx) {
123 FILE* keyfile = fopen(keyfile_path, "r");
124 if (!keyfile) {
125 perror("fopen keyfile");
126 return false;
127 }
128
129 *pkey = PEM_read_PrivateKey(keyfile, /*keyout=*/nullptr, /*password=*/nullptr, /*ucontext=*/nullptr);
130 if (!*pkey) {
131 CryptoError("Failed to read private key");
132 return false;
133 }
134
135 *pctx = EVP_PKEY_CTX_new(*pkey, nullptr);
136 if (!pctx) {
137 CryptoError("Failed to create pkey context");
138 return false;
139 }
140
141 return true;
142 }
143
144 bool SignFile(EVP_PKEY* pkey, EVP_PKEY_CTX* pctx, const char* infile_path) {
145 int rv;
146
147 std::vector<uint8_t> data;
148 if (!ReadEntireFile(infile_path, &data))
149 return false;
150
151 EVP_MD_CTX* ctx = EVP_MD_CTX_new();
152 rv = EVP_DigestSignInit(ctx, &pctx, /*type=*/nullptr, /*engine=*/nullptr, pkey);
153 if (rv != 1) {
154 CryptoError("Failed to initialize digest context");
155 return false;
156 }
157
158 size_t signature_length;
159 rv = EVP_DigestSign(ctx, nullptr, &signature_length, data.data(), data.size());
160 if (rv != 1) {
161 CryptoError("Failed to sign - get length");
162 return false;
163 }
164
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());
167 if (rv < 0) {
168 CryptoError("Failed to sign");
169 return false;
170 }
171
172 for (size_t i = 0; i < signature_length; ) {
173 ssize_t written = write(STDOUT_FILENO, &signature.get()[i], signature_length - i);
174 if (written <= 0) {
175 perror("write");
176 return false;
177 }
178 i += written;
179 }
180
181 return true;
182 }
183
184 bool VerifyFile(EVP_PKEY* pkey, EVP_PKEY_CTX* pctx, const char* sigfile_path, const char* infile_path) {
185 int rv;
186
187 std::vector<uint8_t> signature;
188 if (!ReadEntireFile(sigfile_path, &signature))
189 return false;
190
191 std::vector<uint8_t> data;
192 if (!ReadEntireFile(infile_path, &data))
193 return false;
194
195 EVP_MD_CTX* ctx = EVP_MD_CTX_new();
196
197 rv = EVP_DigestVerifyInit(ctx, &pctx, /*type=*/nullptr, /*engine=*/nullptr, pkey);
198 if (rv != 1) {
199 CryptoError("Failed to initialize verify context");
200 return false;
201 }
202
203 rv = EVP_DigestVerify(ctx, signature.data(), signature.size(), data.data(), data.size());
204 if (rv != 1) {
205 printf("Failed to verify data.\n");
206 return false;
207 }
208
209 printf("Verify OK.\n");
210
211 return true;
212 }
213
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;
219
220 int opt;
221 while ((opt = getopt_long(argc, argv, "ksvih", kOptions, nullptr)) != -1) {
222 switch (opt) {
223 case 's':
224 do_sign = true;
225 break;
226 case 'k':
227 keyfile = optarg;
228 break;
229 case 'v':
230 sigfile = optarg;
231 break;
232 case 'i':
233 infile = optarg;
234 break;
235 case 'h':
236 Usage(argv[0]);
237 return EXIT_SUCCESS;
238 default:
239 Usage(argv[0]);
240 return EXIT_FAILURE;
241 }
242 }
243
244 if (!infile) {
245 fprintf(stderr, "No input file specified.\n");
246 Usage(argv[0]);
247 return EXIT_FAILURE;
248 }
249
250 if (!keyfile) {
251 fprintf(stderr, "Key must be specified.\n");
252 Usage(argv[0]);
253 return EXIT_FAILURE;
254 }
255
256 if (!(do_sign ^ (sigfile != nullptr))) {
257 fprintf(stderr, "Must specify one of --sign or --verify.\n");
258 Usage(argv[0]);
259 return EXIT_FAILURE;
260 }
261
262 EVP_PKEY* pkey;
263 EVP_PKEY_CTX* pctx;
264 if (!LoadKeyAndCreateContext(keyfile, &pkey, &pctx))
265 return EXIT_FAILURE;
266
267 bool ok = false;
268
269 if (do_sign)
270 ok = SignFile(pkey, pctx, infile);
271 else
272 ok = VerifyFile(pkey, pctx, sigfile, infile);
273
274 return ok ? EXIT_SUCCESS : EXIT_FAILURE;
275 }