1/* somewhat derived from OpenSSH */
2#include "defs"
3
4#include <ctype.h>
5#include <stdlib.h>
6#include <string.h>
7
8#include <alloc.h>
9#include <byte.h>
10#include <fmt.h>
11#include <str.h>
12#include <stralloc.h>
13#include <subgetopt.h>
14#include <buffer.h>
15#include <strerr.h>
16#include <openreadclose.h>
17
18#include "base64.h"
19#include "bsd-sha2.h"
20#include "md5.h"
21
22#define FLDBASE 8
23#define FLDSIZE_Y (FLDBASE + 1)
24#define FLDSIZE_X (FLDBASE * 2 + 1)
25
26struct options {
27 int hashalg;
28};
29
30static void
31usage(void)
32{
33 buffer_puts(buffer_2, "usage: randomart [-E md5|sha256] [file ...]\n");
34 buffer_puts(buffer_2,
35 " randomart [-E md5|sha256] -k public_key_line\n");
36 buffer_flush(buffer_2);
37 exit(1);
38}
39
40static void
41warnx1(const char *a, const char *b)
42{
43 strerr_warn3(a, b, "", 0);
44}
45
46static void
47warnx2(const char *a, const char *b, const char *c)
48{
49 strerr_warn4(a, b, c, "", 0);
50}
51
52static void
53warnsys1(const char *a, const char *b)
54{
55 strerr_warn3(a, b, "", &strerr_sys);
56}
57
58static char *
59alloc_nz(unsigned int size)
60{
61 char *ret;
62
63 ret = alloc(size ? size : 1);
64 if (ret == NULL) {
65 strerr_die2sys(111, "randomart: ", "alloc");
66 exit(1);
67 }
68 return ret;
69}
70
71static char *
72dup_string(const char *s)
73{
74 char *ret;
75 unsigned int len;
76
77 len = str_len(s) + 1;
78 ret = alloc_nz(len);
79 byte_copy(ret, len, (char *)s);
80 return ret;
81}
82
83static char *
84trim(char *s)
85{
86 char *end;
87
88 while (*s != '\0' && isspace((unsigned char)*s))
89 s++;
90 if (*s == '\0')
91 return s;
92 end = s + str_len(s) - 1;
93 while (end > s && isspace((unsigned char)*end))
94 *end-- = '\0';
95 return s;
96}
97
98static unsigned int
99format_box_label(char *buf, unsigned int buf_len, const char *text,
100 const char *fallback)
101{
102 int r;
103 unsigned int n;
104 const char *src;
105
106 if (buf_len < 3)
107 return 0;
108 src = text;
109 n = str_len(text);
110 if (n + 2 >= buf_len) {
111 src = fallback;
112 n = str_len(fallback);
113 if (n + 2 >= buf_len)
114 n = buf_len - 3;
115 }
116 buf[0] = '[';
117 n = fmt_str(buf + 1, (char *)src);
118 if (n + 2 >= buf_len)
119 n = buf_len - 3;
120 buf[n + 1] = ']';
121 buf[n + 2] = '\0';
122 return n + 2;
123}
124
125static int
126compute_digest(const u_char *data, size_t len, int hashalg,
127 u_char *digest, size_t *digest_len, const char **label)
128{
129 MD5_CTX md5;
130 SHA2_CTX sha256;
131
132 switch (hashalg) {
133 case 0:
134 MD5Init(&md5);
135 MD5Update(&md5, data, len);
136 MD5Final(digest, &md5);
137 *digest_len = MD5_DIGEST_LENGTH;
138 *label = "MD5";
139 return 0;
140 case 1:
141 SHA256Init(&sha256);
142 SHA256Update(&sha256, data, len);
143 SHA256Final(digest, &sha256);
144 *digest_len = SHA256_DIGEST_LENGTH;
145 *label = "SHA256";
146 return 0;
147 default:
148 return -1;
149 }
150}
151
152static int
153sa_catc(stralloc *sa, char c)
154{
155 return stralloc_catb(sa, &c, 1);
156}
157
158static int
159sa_catn(stralloc *sa, char c, unsigned int n)
160{
161 while (n-- > 0) {
162 if (!sa_catc(sa, c))
163 return 0;
164 }
165 return 1;
166}
167
168static char *
169render_randomart(const char *title, const char *subtitle,
170 const u_char *dgst_raw, size_t dgst_raw_len)
171{
172 static const char augmentation_string[] = " .o+=*BOX@%&#/^SE";
173 char title_buf[FLDSIZE_X];
174 char subtitle_buf[FLDSIZE_X];
175 u_char field[FLDSIZE_X][FLDSIZE_Y];
176 stralloc out = { 0 };
177 unsigned int i, title_len, subtitle_len;
178 u_int b;
179 int x, y;
180 unsigned int len = str_len(augmentation_string) - 1;
181
182 byte_zero(field, sizeof(field));
183 x = FLDSIZE_X / 2;
184 y = FLDSIZE_Y / 2;
185
186 for (i = 0; i < dgst_raw_len; i++) {
187 int input = dgst_raw[i];
188
189 for (b = 0; b < 4; b++) {
190 x += (input & 0x1) ? 1 : -1;
191 y += (input & 0x2) ? 1 : -1;
192 if (x < 0)
193 x = 0;
194 if (y < 0)
195 y = 0;
196 if (x >= FLDSIZE_X)
197 x = FLDSIZE_X - 1;
198 if (y >= FLDSIZE_Y)
199 y = FLDSIZE_Y - 1;
200 if (field[x][y] < len - 2)
201 field[x][y]++;
202 input >>= 2;
203 }
204 }
205
206 field[FLDSIZE_X / 2][FLDSIZE_Y / 2] = len - 1;
207 field[x][y] = len;
208
209 title_len = format_box_label(title_buf, sizeof(title_buf), title, "key");
210 subtitle_len = format_box_label(subtitle_buf, sizeof(subtitle_buf),
211 subtitle, "hash");
212
213 if (!sa_catc(&out, '+') ||
214 !sa_catn(&out, '-', (FLDSIZE_X - title_len) / 2) ||
215 !stralloc_catb(&out, title_buf, title_len) ||
216 !sa_catn(&out, '-', FLDSIZE_X - ((FLDSIZE_X - title_len) / 2 + title_len)) ||
217 !stralloc_catb(&out, "+\n", 2))
218 goto fail;
219
220 for (y = 0; y < FLDSIZE_Y; y++) {
221 if (!sa_catc(&out, '|'))
222 goto fail;
223 for (x = 0; x < FLDSIZE_X; x++) {
224 char ch = augmentation_string[field[x][y] > len ?
225 len : field[x][y]];
226 if (!sa_catc(&out, ch))
227 goto fail;
228 }
229 if (!stralloc_catb(&out, "|\n", 2))
230 goto fail;
231 }
232
233 if (!sa_catc(&out, '+') ||
234 !sa_catn(&out, '-', (FLDSIZE_X - subtitle_len) / 2) ||
235 !stralloc_catb(&out, subtitle_buf, subtitle_len) ||
236 !sa_catn(&out, '-', FLDSIZE_X - ((FLDSIZE_X - subtitle_len) / 2 + subtitle_len)) ||
237 !sa_catc(&out, '+') ||
238 !stralloc_0(&out))
239 goto fail;
240
241 return out.s;
242
243fail:
244 alloc_free(out.s);
245 return NULL;
246}
247
248static int
249parse_public_key_line(char *line, char **typep, u_char **blobp,
250 size_t *blob_lenp)
251{
252 char *type, *blob_text, *rest;
253 int decoded_len;
254 unsigned int blob_cap;
255 u_char *blob;
256
257 line = trim(line);
258 if (*line == '\0' || *line == '#')
259 return 1;
260
261 type = strtok(line, " \t");
262 blob_text = strtok(NULL, " \t");
263 rest = strtok(NULL, "");
264 (void)rest;
265 if (type == NULL || blob_text == NULL)
266 return -1;
267
268 blob_cap = (str_len(blob_text) / 4) * 3 + 4;
269 blob = (u_char *)alloc_nz(blob_cap);
270 decoded_len = b64_pton(blob_text, blob, blob_cap);
271 if (decoded_len < 0) {
272 alloc_free(blob);
273 return -1;
274 }
275
276 *typep = dup_string(type);
277 *blobp = blob;
278 *blob_lenp = (size_t)decoded_len;
279 return 0;
280}
281
282static int
283print_art(const char *label, const char *art)
284{
285 if (buffer_puts(buffer_1, label) == -1 ||
286 buffer_puts(buffer_1, "\n") == -1 ||
287 buffer_puts(buffer_1, art) == -1 ||
288 buffer_puts(buffer_1, "\n") == -1 ||
289 buffer_flush(buffer_1) == -1)
290 return -1;
291 return 0;
292}
293
294static int
295process_key_line(char *line, const char *label, const struct options *opts)
296{
297 char *type = NULL, *art = NULL;
298 u_char *blob = NULL;
299 u_char digest[SHA256_DIGEST_LENGTH];
300 size_t blob_len = 0, digest_len = 0;
301 const char *hash_label = NULL;
302 int ret = 1;
303
304 ret = parse_public_key_line(line, &type, &blob, &blob_len);
305 if (ret == 1) {
306 ret = 2;
307 goto out;
308 }
309 if (ret != 0) {
310 warnx2(label, ": ", "unable to parse OpenSSH public key line");
311 goto out;
312 }
313 if (compute_digest(blob, blob_len, opts->hashalg, digest,
314 &digest_len, &hash_label) != 0) {
315 warnx1(label, ": unsupported digest");
316 goto out;
317 }
318 art = render_randomart(type, hash_label, digest, digest_len);
319 if (art == NULL) {
320 warnx1(label, ": unable to render randomart");
321 goto out;
322 }
323 ret = print_art(label, art);
324out:
325 alloc_free(type);
326 alloc_free(blob);
327 alloc_free(art);
328 return ret;
329}
330
331static int
332process_blob(char *blob, const char *label, const struct options *opts)
333{
334 char *line = blob;
335 char *next;
336 int ret = 1;
337 int line_ret;
338
339 while (*line) {
340 next = line + str_chr(line, '\n');
341 if (*next != '\0')
342 *next = '\0';
343 else
344 next = 0;
345 line_ret = process_key_line(line, label, opts);
346 if (line_ret == 0) {
347 ret = 0;
348 break;
349 }
350 if (next == NULL)
351 break;
352 line = next + 1;
353 }
354 if (ret != 0)
355 warnx1(label, ": no usable public key line found");
356 return ret;
357}
358
359static int
360process_path(const char *path, const struct options *opts)
361{
362 stralloc sa = { 0 };
363 int ret;
364
365 ret = openreadclose(path, &sa, 8192);
366 if (ret == 0) {
367 warnsys1(path, ": ");
368 alloc_free(sa.s);
369 return 1;
370 }
371 if (ret == -1) {
372 warnsys1(path, ": ");
373 alloc_free(sa.s);
374 return 1;
375 }
376 if (!stralloc_0(&sa))
377 strerr_die2sys(111, "randomart: ", "stralloc_0");
378 ret = process_blob(sa.s, path, opts);
379 alloc_free(sa.s);
380 return ret;
381}
382
383static int
384process_stdin(const struct options *opts)
385{
386 stralloc sa = { 0 };
387 char ch;
388 int r;
389 int ret = 1;
390
391 for (;;) {
392 r = buffer_get(buffer_0, &ch, 1);
393 if (r < 0) {
394 warnsys1("<stdin>", ": ");
395 break;
396 }
397 if (r == 0) {
398 if (sa.len != 0) {
399 if (!stralloc_0(&sa))
400 strerr_die2sys(111, "randomart: ",
401 "stralloc_0");
402 ret = process_key_line(sa.s, "<stdin>", opts);
403 }
404 break;
405 }
406 if (ch == '\n') {
407 if (!stralloc_0(&sa))
408 strerr_die2sys(111, "randomart: ", "stralloc_0");
409 ret = process_key_line(sa.s, "<stdin>", opts);
410 if (ret == 0)
411 break;
412 sa.len = 0;
413 } else if (!stralloc_append(&sa, &ch)) {
414 strerr_die2sys(111, "randomart: ", "stralloc_append");
415 }
416 }
417 if (ret != 0)
418 warnx1("<stdin>", ": no usable public key line found");
419 alloc_free(sa.s);
420 return ret;
421}
422
423int
424main(int argc, char **argv)
425{
426 struct options opts;
427 char *inline_key = NULL;
428 int ch;
429 int ret = 0;
430
431 opts.hashalg = 1;
432
433 while ((ch = subgetopt(argc, (const char * const *)argv, "E:k:h")) !=
434 SUBGETOPTDONE) {
435 switch (ch) {
436 case 'E':
437 if (str_equal(subgetoptarg, "md5"))
438 opts.hashalg = 0;
439 else if (str_equal(subgetoptarg, "sha256"))
440 opts.hashalg = 1;
441 else
442 usage();
443 break;
444 case 'k':
445 inline_key = (char *)subgetoptarg;
446 break;
447 case 'h':
448 default:
449 usage();
450 }
451 }
452
453 argc -= subgetoptind;
454 argv += subgetoptind;
455
456 if (inline_key != NULL) {
457 char *buf = dup_string(inline_key);
458
459 ret = process_key_line(buf, "<inline>", &opts);
460 alloc_free(buf);
461 return ret;
462 }
463 if (argc == 0)
464 return process_stdin(&opts);
465 for (; *argv != NULL; argv++)
466 ret |= process_path(*argv, &opts);
467 return ret;
468}