213 lines
5.7 KiB
C
213 lines
5.7 KiB
C
/*
|
|
* gsign: make signify-compatible Ed25519 keys and signatures
|
|
* (REPO_DESIGN.txt section 2). A build/maintainer tool, like
|
|
* gsha256sum.
|
|
*
|
|
* gsign -G -p KEY.pub -s KEY.sec [-c COMMENT]
|
|
* gsign -S -s KEY.sec -m FILE [-x SIGFILE]
|
|
* gsign -V -p KEY.pub -m FILE [-x SIGFILE]
|
|
*
|
|
* SIGFILE defaults to FILE.sig. Exit 0 on success, 1 otherwise.
|
|
*/
|
|
|
|
#define _POSIX_C_SOURCE 200809L
|
|
|
|
#include <getopt.h>
|
|
#include <limits.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
#include "log.h"
|
|
#include "sign.h"
|
|
|
|
#define USAGE \
|
|
"usage: gsign [-hv]\n" \
|
|
" gsign -G -p KEY.pub -s KEY.sec [-c COMMENT]\n" \
|
|
" gsign -S -s KEY.sec -m FILE [-x SIGFILE]\n" \
|
|
" gsign -V -p KEY.pub -m FILE [-x SIGFILE]\n"
|
|
|
|
static const struct option longopts[] = {
|
|
{ "help", no_argument, NULL, 'h' },
|
|
{ "version", no_argument, NULL, 'v' },
|
|
{ "generate", no_argument, NULL, 'G' },
|
|
{ "sign", no_argument, NULL, 'S' },
|
|
{ "verify", no_argument, NULL, 'V' },
|
|
{ "pubkey", required_argument, NULL, 'p' },
|
|
{ "seckey", required_argument, NULL, 's' },
|
|
{ "message", required_argument, NULL, 'm' },
|
|
{ "sig", required_argument, NULL, 'x' },
|
|
{ "comment", required_argument, NULL, 'c' },
|
|
{ NULL, 0, NULL, 0 }
|
|
};
|
|
|
|
static void
|
|
usage(void)
|
|
{
|
|
fprintf(stderr, USAGE);
|
|
}
|
|
|
|
static void
|
|
help(void)
|
|
{
|
|
printf("gsign: Make and check signify-compatible signatures\n");
|
|
printf(USAGE);
|
|
printf("\n");
|
|
printf("%-25s %s\n", "{-h --help}", "Show this message");
|
|
printf("%-25s %s\n", "{-v --version}", "Show the current version");
|
|
printf("%-25s %s\n", "{-G --generate}", "Generate a new key pair");
|
|
printf("%-25s %s\n", "{-S --sign}", "Sign a file");
|
|
printf("%-25s %s\n", "{-V --verify}", "Verify a file's signature");
|
|
printf("%-25s %s\n", "{-p --pubkey} KEY.pub", "Public key file");
|
|
printf("%-25s %s\n", "{-s --seckey} KEY.sec", "Secret key file");
|
|
printf("%-25s %s\n", "{-m --message} FILE", "File to sign or verify");
|
|
printf("%-25s %s\n", "{-x --sig} SIGFILE", "Signature file (default: FILE.sig)");
|
|
printf("%-25s %s\n", "{-c --comment} COMMENT", "Comment for a new key pair (default: gsign)");
|
|
printf("\n");
|
|
printf("This program is free software.\n");
|
|
printf("See the GNU GPL version 3 for details.\n");
|
|
}
|
|
|
|
/* "<what> <path> <problem>." about one file. */
|
|
static int
|
|
fail(const char *what, const char *path, int st)
|
|
{
|
|
const char *problem;
|
|
switch (st) {
|
|
case GL_SIGN_ERR_FORMAT: problem = "is malformed"; break;
|
|
case GL_SIGN_ERR_ENCRYPTED: problem = "is encrypted: sign with signify"; break;
|
|
case GL_SIGN_ERR_TOOBIG: problem = "is too large"; break;
|
|
case GL_SIGN_ERR_EXISTS: problem = "already exists"; break;
|
|
case GL_SIGN_ERR_WRITE: problem = "cannot be written"; break;
|
|
default: problem = "cannot be read"; break;
|
|
}
|
|
lg_printf(LG_ERR, "%s %s %s.", what, path, problem);
|
|
return 1;
|
|
}
|
|
|
|
int
|
|
main(int argc, char *argv[])
|
|
{
|
|
char mode = 0;
|
|
const char *pub = NULL, *sec = NULL, *msg = NULL, *sig = NULL;
|
|
const char *comment = "gsign";
|
|
int c;
|
|
|
|
while ((c = getopt_long(argc, argv, "hvGSVp:s:m:x:c:", longopts, NULL)) != -1) {
|
|
switch (c) {
|
|
case 'h':
|
|
help();
|
|
return 0;
|
|
case 'v':
|
|
printf("v4.0.0\n");
|
|
return 0;
|
|
case 'G': case 'S': case 'V':
|
|
if (mode && mode != c) {
|
|
usage();
|
|
return 1;
|
|
}
|
|
mode = (char)c;
|
|
break;
|
|
case 'p': pub = optarg; break;
|
|
case 's': sec = optarg; break;
|
|
case 'm': msg = optarg; break;
|
|
case 'x': sig = optarg; break;
|
|
case 'c': comment = optarg; break;
|
|
default:
|
|
usage();
|
|
return 1;
|
|
}
|
|
}
|
|
if (optind != argc) {
|
|
usage();
|
|
return 1;
|
|
}
|
|
|
|
char sigbuf[PATH_MAX];
|
|
if (msg && !sig) {
|
|
if ((size_t)snprintf(sigbuf, sizeof sigbuf, "%s.sig", msg) >= sizeof sigbuf) {
|
|
lg_printf(LG_ERR, "Path too long: %s", msg);
|
|
return 1;
|
|
}
|
|
sig = sigbuf;
|
|
}
|
|
|
|
int st;
|
|
switch (mode) {
|
|
case 'G':
|
|
if (!pub || !sec || msg) {
|
|
usage();
|
|
return 1;
|
|
}
|
|
/* The library refuses these too (O_EXCL); this names which. */
|
|
struct stat sb;
|
|
if (lstat(sec, &sb) == 0)
|
|
return fail("Secret key", sec, GL_SIGN_ERR_EXISTS);
|
|
if (lstat(pub, &sb) == 0)
|
|
return fail("Public key", pub, GL_SIGN_ERR_EXISTS);
|
|
st = gl_sign_generate(pub, sec, comment);
|
|
if (st == GL_SIGN_ERR_FORMAT) {
|
|
lg_printf(LG_ERR, "Comment must be one line, at most %d characters.",
|
|
GL_SIGN_MAX_COMMENT - 11);
|
|
return 1;
|
|
}
|
|
if (st == GL_SIGN_ERR_RANDOM) {
|
|
lg_printf(LG_ERR, "Cannot get random bytes for a new key.");
|
|
return 1;
|
|
}
|
|
if (st != GL_SIGN_OK) {
|
|
lg_printf(LG_ERR, "Cannot write %s and %s.", pub, sec);
|
|
return 1;
|
|
}
|
|
lg_printf(LG_SUCCESS, "Wrote %s and %s.", pub, sec);
|
|
return 0;
|
|
|
|
case 'S':
|
|
if (!sec || !msg || pub) {
|
|
usage();
|
|
return 1;
|
|
}
|
|
if ((st = gl_sign_check_seckey(sec)) != GL_SIGN_OK)
|
|
return fail("Secret key", sec, st);
|
|
st = gl_sign_file(sec, msg, sig);
|
|
if (st == GL_SIGN_ERR_WRITE)
|
|
return fail("Signature", sig, st);
|
|
if (st != GL_SIGN_OK)
|
|
return fail("File", msg, st);
|
|
lg_printf(LG_SUCCESS, "Signed %s: %s.", msg, sig);
|
|
return 0;
|
|
|
|
case 'V': {
|
|
if (!pub || !msg || sec) {
|
|
usage();
|
|
return 1;
|
|
}
|
|
gl_sign_pubkey_t pk;
|
|
gl_sign_sig_t sg;
|
|
if ((st = gl_sign_read_pubkey(pub, &pk)) != GL_SIGN_OK)
|
|
return fail("Public key", pub, st);
|
|
if ((st = gl_sign_read_sig(sig, &sg)) != GL_SIGN_OK)
|
|
return fail("Signature", sig, st);
|
|
st = gl_sign_verify_file(pub, msg, sig);
|
|
switch (st) {
|
|
case GL_SIGN_OK:
|
|
lg_printf(LG_SUCCESS, "Signature verified: %s.", msg);
|
|
return 0;
|
|
case GL_SIGN_ERR_KEYNUM:
|
|
lg_printf(LG_ERR, "%s was signed by a different key than %s.", msg, pub);
|
|
return 1;
|
|
case GL_SIGN_ERR_BAD:
|
|
lg_printf(LG_ERR, "Signature does not verify: %s.", msg);
|
|
return 1;
|
|
default:
|
|
return fail("File", msg, st);
|
|
}
|
|
}
|
|
|
|
default:
|
|
usage();
|
|
return 1;
|
|
}
|
|
}
|