Files
libglacier-ng/src/gsign.c
T
2026-10-14 00:50:56 -04:00

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;
}
}