This may or may not be useful. We can embed the message after the signature and reduce the number of files in half. It's not a lot of code. (This is how the crypto_sign API originally worked, but we've since added some metadata, so we still need to destruct and reconstruct the crypto_sign inputs and outputs.)
The -e option is added. It works a lot like gzip/gunzip by default. You sign a message, creating message.txt.sig: signify -e -S message.txt You verify message.txt.sig, creating message.txt: signify -e -V message.txt Note you always specify the message name. I've cleaned up the source a bit to disambiguate between "input" and "message". And of course, -o works as before. The signature is prepended to the data (much easier to find than appended). It can be stripped off manually by deleting the first two lines. One other thing to note is that this now requires the base64 data to stay on one line, unless we go whole hog with fancy --- BEGIN HERE COMES THE DATA --- --- END THERE GOES THE DATA --- lines. We keep accumulating features, so maybe we should. Index: signify.1 =================================================================== RCS file: /cvs/src/usr.bin/signify/signify.1,v retrieving revision 1.7 diff -u -p -r1.7 signify.1 --- signify.1 3 Jan 2014 17:10:27 -0000 1.7 +++ signify.1 8 Jan 2014 20:40:52 -0000 @@ -27,21 +27,23 @@ .Fl s Ar seckey .Fl G .Nm signify +.Op Fl e .Op Fl o Ar output .Fl s Ar seckey .Fl S -.Ar input +.Ar message .Nm signify +.Op Fl e .Op Fl o Ar output .Fl p Ar pubkey .Fl V -.Ar input +.Ar message .Sh DESCRIPTION The .Nm utility creates and verifies cryptographic signatures for an input file -.Ar input . +.Ar message . The mode of operation is selected by the .Fl G , .Fl S , @@ -51,6 +53,9 @@ options. .Pp The options are as follows: .Bl -tag -width Dssoutput +.It Fl e +Embed the message after the signature when signing. +For verification, extract the message from the signature. .It Fl G Generate a new keypair. .It Fl n @@ -58,7 +63,7 @@ Do not ask for a passphrase during key g Otherwise, .Nm will prompt the user for a passphrase on the terminal. -.It Fl o Ar output +.It Fl o Ar sigfile The signature file to create or verify. The default is .Ar input Ns .sig . Index: signify.c =================================================================== RCS file: /cvs/src/usr.bin/signify/signify.c,v retrieving revision 1.15 diff -u -p -r1.15 signify.c --- signify.c 8 Jan 2014 07:04:29 -0000 1.15 +++ signify.c 8 Jan 2014 20:33:06 -0000 @@ -64,6 +64,8 @@ struct sig { uint8_t sig[SIGBYTES]; }; +static int embedded; + extern char *__progname; static void @@ -72,9 +74,9 @@ usage(void) fprintf(stderr, "usage:" #ifndef VERIFYONLY "\t%s [-n] -p pubkey -s seckey -G\n" - "\t%s [-o output] -s seckey -S input\n" + "\t%s [-e] [-o output] -s seckey -S input\n" #endif - "\t%s [-o output] -p pubkey -V input\n", + "\t%s [-e] [-o output] -p pubkey -V input\n", #ifndef VERIFYONLY __progname, __progname, #endif @@ -117,30 +119,43 @@ readall(int fd, void *buf, size_t len, c } } +static size_t +parseb64file(const char *filename, char *b64, void *buf, size_t len) +{ + int rv; + char *commentend, *b64end; + + commentend = strchr(b64, '\n'); + if (!commentend || commentend - b64 <= COMMENTHDRLEN || + memcmp(b64, COMMENTHDR, COMMENTHDRLEN)) + errx(1, "invalid comment in %s; must start with '%s'", + filename, COMMENTHDR); + b64end = strchr(commentend + 1, '\n'); + if (!b64end) + errx(1, "missing new line after b64 in %s", filename); + *b64end = 0; + rv = b64_pton(commentend + 1, buf, len); + if (rv != len) + errx(1, "invalid b64 encoding in %s", filename); + if (memcmp(buf, PKALG, 2)) + errx(1, "unsupported file %s", filename); + return b64end - b64 + 1; +} + static void readb64file(const char *filename, void *buf, size_t len) { char b64[2048]; int rv, fd; - char *commentend; fd = xopen(filename, O_RDONLY | O_NOFOLLOW, 0); memset(b64, 0, sizeof(b64)); rv = read(fd, b64, sizeof(b64) - 1); if (rv == -1) err(1, "read from %s", filename); - commentend = strchr(b64, '\n'); - if (!commentend || commentend - b64 <= COMMENTHDRLEN || - memcmp(b64, COMMENTHDR, COMMENTHDRLEN)) - errx(1, "invalid comment in %s; must start with '%s'", - filename, COMMENTHDR); - rv = b64_pton(commentend + 1, buf, len); - if (rv != len) - errx(1, "invalid b64 encoding in %s", filename); + parseb64file(filename, b64, buf, len); memset(b64, 0, sizeof(b64)); close(fd); - if (memcmp(buf, PKALG, 2)) - errx(1, "unsupported file %s", filename); } uint8_t * @@ -179,6 +194,16 @@ writeall(int fd, const void *buf, size_t } static void +appendall(const char *filename, const void *buf, size_t len) +{ + int fd; + + fd = xopen(filename, O_NOFOLLOW | O_RDWR | O_APPEND, 0); + writeall(fd, buf, len, filename); + close(fd); +} + +static void writeb64file(const char *filename, const char *comment, const void *buf, size_t len, mode_t mode) { @@ -270,7 +295,7 @@ generate(const char *pubkeyfile, const c } static void -sign(const char *seckeyfile, const char *inputfile, const char *sigfile) +sign(const char *seckeyfile, const char *msgfile, const char *sigfile) { struct sig sig; uint8_t digest[SHA512_DIGEST_LENGTH]; @@ -297,7 +322,7 @@ sign(const char *seckeyfile, const char errx(1, "incorrect passphrase"); memset(digest, 0, sizeof(digest)); - msg = readmsg(inputfile, &msglen); + msg = readmsg(msgfile, &msglen); signmsg(enckey.seckey, msg, msglen, sig.sig); memcpy(sig.fingerprint, enckey.fingerprint, FPLEN); @@ -305,6 +330,8 @@ sign(const char *seckeyfile, const char memcpy(sig.pkalg, PKALG, 2); writeb64file(sigfile, "signature", &sig, sizeof(sig), 0666); + if (embedded) + appendall(sigfile, msg, msglen); free(msg); } @@ -331,31 +358,44 @@ verifymsg(uint8_t *pubkey, uint8_t *msg, static void -verify(const char *pubkeyfile, const char *inputfile, const char *sigfile) +verify(const char *pubkeyfile, const char *msgfile, const char *sigfile) { struct sig sig; struct pubkey pubkey; - unsigned long long msglen; + unsigned long long msglen, siglen = 0; uint8_t *msg; + int fd; + + msg = readmsg(embedded ? sigfile : msgfile, &msglen); readb64file(pubkeyfile, &pubkey, sizeof(pubkey)); - readb64file(sigfile, &sig, sizeof(sig)); + if (embedded) { + siglen = parseb64file(sigfile, msg, &sig, sizeof(sig)); + msg += siglen; + msglen -= siglen; + } else { + readb64file(sigfile, &sig, sizeof(sig)); + } if (memcmp(pubkey.fingerprint, sig.fingerprint, FPLEN)) errx(1, "verification failed: checked against wrong key"); - msg = readmsg(inputfile, &msglen); - verifymsg(pubkey.pubkey, msg, msglen, sig.sig); + if (embedded) { + fd = xopen(msgfile, O_CREAT|O_EXCL|O_NOFOLLOW|O_RDWR, 0666); + writeall(fd, msg, msglen, msgfile); + close(fd); + } + printf("verified\n"); - free(msg); + free(msg - siglen); } int main(int argc, char **argv) { - const char *pubkeyfile = NULL, *seckeyfile = NULL, *inputfile = NULL, + const char *pubkeyfile = NULL, *seckeyfile = NULL, *msgfile = NULL, *sigfile = NULL; char sigfilebuf[1024]; int ch, rounds; @@ -369,7 +409,7 @@ main(int argc, char **argv) rounds = 42; - while ((ch = getopt(argc, argv, "GSVno:p:s:")) != -1) { + while ((ch = getopt(argc, argv, "GSVeno:p:s:")) != -1) { switch (ch) { #ifndef VERIFYONLY case 'G': @@ -388,6 +428,9 @@ main(int argc, char **argv) usage(); verb = VERIFY; break; + case 'e': + embedded = 1; + break; case 'n': rounds = 0; break; @@ -426,11 +469,11 @@ main(int argc, char **argv) if (argc != 1) usage(); - inputfile = argv[0]; + msgfile = argv[0]; if (!sigfile) { if (snprintf(sigfilebuf, sizeof(sigfilebuf), "%s.sig", - inputfile) >= sizeof(sigfilebuf)) + msgfile) >= sizeof(sigfilebuf)) errx(1, "path too long"); sigfile = sigfilebuf; } @@ -438,13 +481,13 @@ main(int argc, char **argv) if (verb == SIGN) { if (!seckeyfile) usage(); - sign(seckeyfile, inputfile, sigfile); + sign(seckeyfile, msgfile, sigfile); } else #endif if (verb == VERIFY) { if (!pubkeyfile) usage(); - verify(pubkeyfile, inputfile, sigfile); + verify(pubkeyfile, msgfile, sigfile); } }