supercop

Basic CLI for supercop
git clone git://git.finwo.net/app/supercop
Log | Files | Refs | README | LICENSE

commit 6b0190f92bd30fa256cd8502976091281d5a6846
parent 2c7a5489c0ba02718d50767215175ee40099ac89
Author: Robin Bron <robin.bron@yourhosting.nl>
Date:   Sun, 13 Sep 2026 05:44:01 +0200

Update to current standards

Diffstat:
M.gitignore | 1+
MMakefile | 73++++++++++++++++++++++++++++++++++---------------------------------------
MREADME.md | 46++++++++++++++++++++++++++++++++--------------
Dconfig.mk | 4----
Mman/supercop.1 | 47++++++++++++++++++++++++++++++++---------------
Dpackage.ini | 6------
Asrc/cli/command.h | 23+++++++++++++++++++++++
Asrc/cli/command/generate.c | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/cli/command/list_commands.c | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/cli/command/printkey.c | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/cli/command/sign.c | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/cli/command/verify.c | 108+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/cli/command/version.c | 43+++++++++++++++++++++++++++++++++++++++++++
Asrc/cli/common.c | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/cli/common.h | 36++++++++++++++++++++++++++++++++++++
Asrc/cli/usage.c | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/cli/usage.h | 19+++++++++++++++++++
Msrc/main.c | 347+++++++------------------------------------------------------------------------
Atarget/common/.dep | 2++
Atarget/common/Makefile | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atarget/linux-glibc-amd64/.gitkeep | 0
21 files changed, 872 insertions(+), 394 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -1,4 +1,5 @@ *.o *.asc +/build/ /lib/ /supercop diff --git a/Makefile b/Makefile @@ -1,51 +1,46 @@ -include config.mk +TARGET:=linux-glibc-amd64 -SRC:= -SRC+=$(wildcard src/*.c) -SRC+=$(wildcard src/*/*.c) -SRC+=$(wildcard src/*/*/*.c) +BIN:=supercop -# Ourselves -MTUNE?=native -MARCH?=native -CFLAGS?= -CFLAGS+=-D VERSION=\"$(VERSION)\" -CFLAGS+=-mtune=$(MTUNE) -march=$(MARCH) -CFLAGS+=-O2 -pipe -CFLAGS+=-Wall - -INCLUDES?= -INCLUDES+=-Isrc - -include lib/.dep/config.mk - -# Map SRC into OBJ -OBJ:=$(patsubst %.c,%.o,$(SRC)) +VERSION?=v1.0.0 .PHONY: default -default: supercop +default: build/${TARGET}/${BIN} -README.md: ./man/supercop.1 - pandoc $< --output=$@ +FIND:=$(shell command -v gfind find) -supercop: $(OBJ) - $(CC) $(CFLAGS) $(INCLUDES) -o $@ $(OBJ) +WATCH?= +WATCH+=$(shell $(FIND) src -type f -name '*.c') +WATCH+=$(shell $(FIND) src -type f -name '*.h') -.PHONY: small -static: - CFLAGS="-static -Os -s" $(MAKE) "MTUNE=$(MTUNE)" "MARCH=$(MARCH)" supercop - strip -S --strip-unneeded --remove-section=.note.gnu.gold-version --remove-section=.comment --remove-section=.note --remove-section=.note.gnu.build-id --remove-section=.note.ABI-tag supercop +build/${TARGET}/${BIN}: build/${TARGET} $(WATCH) + cd build/${TARGET} && dep install + $(MAKE) --directory build/${TARGET} TARGET=${TARGET} VERSION=${VERSION} -%.o: %.c - $(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@ +build/${TARGET}: $(WATCH) + mkdir -p build/${TARGET} + cp -rT src/ build/${TARGET}/src + cp -rT man/ build/${TARGET}/man + cp -rT target/common/ build/${TARGET} + cp -rT target/${TARGET}/ build/${TARGET} -.PHONY: install -install: supercop - install -d ${DESTDIR}${PREFIX}/bin - install -m 755 supercop ${DESTDIR}${PREFIX}/bin - install -d ${DESTDIR}${MANPREFIX}/man1 - install -m 644 $(wildcard man/*.1) ${DESTDIR}${MANPREFIX}/man1 +.PHONY: targets +targets: + @ls -1 target | grep -v '^common$$' + +README.md: ./man/supercop.1 + pandoc $< --output=$@ .PHONY: clean clean: - rm -rf $(OBJ) + rm -rf build + +# Never let the catch-all try to rebuild the makefiles themselves +Makefile: ; + +# Forward any other goal verbatim into the assembled target tree +.PHONY: FORCE +FORCE: +%: build/${TARGET} FORCE + cd build/${TARGET} && dep install + $(MAKE) --directory build/${TARGET} TARGET=${TARGET} VERSION=${VERSION} $@ diff --git a/README.md b/README.md @@ -4,41 +4,55 @@ supercop - Simplified CLI for supercop-ref10 # SYNOPSIS -**supercop** *\<operation\>* \[**-k** *keyfile*\] \[**-m** *message*\] -\[**-M** *message-file*\] \[**-s** *signature*\] +**supercop** \[*global options*\] *command* \[*command options*\]\ +**supercop** **list-commands**\ +**supercop** **\--version** # DESCRIPTION This program allows easy signing and signature verification for the supercop-ref10 implementation of the ed25519 signature scheme. -# OPERATIONS +Global options must appear before the command name. Run **supercop** +*command* **\--help** for the details on a single command. -**\--printkey** +# GLOBAL OPTIONS -: Print contents of key file +**-h, \--help** + +: Show usage and exit + +**-V, \--version** + +: Show version number and exit (same as **supercop version**) + +# COMMANDS -**\--generate** +**generate \[**-k** *keyfile*\]** -: Generate a new key +: Generate a new key, writing to keyfile or stdout -**\--sign** +**printkey **-k** *keyfile*** + +: Print contents of key file + +**sign **-k** *keyfile* \[**-m** *message* \| **-M** *message-file*\]** : Sign a message -**\--verify** +**verify **-k** *keyfile* **-s** *signature* \[**-m** *message* \| **-M** *message-file*\]** : Verify a message signature -**\--version** +**version** -: Show version number and exit +: Show version number and exit (same as **supercop \--version**) -# OPTIONS +**list-commands** -**-h, \--help** +: List available commands -: Show usage and exit +# COMMAND OPTIONS **-k, \--key-file *path*** @@ -69,6 +83,10 @@ Here are the possible return values: supercop doesn\'t follow any environment variables +# FILES + +supercop uses no configuration files + # AUTHOR finwo (https://finwo.nl) diff --git a/config.mk b/config.mk @@ -1,4 +0,0 @@ -VERSION=v1.0.0 - -PREFIX?=/usr/local -MANPREFIX?=${PREFIX}/share/man diff --git a/man/supercop.1 b/man/supercop.1 @@ -1,35 +1,49 @@ -.TH supercop 1 "Sep 16, 2021" "" "FINWO" +.TH supercop 1 "" "" "FINWO" .SH NAME supercop - Simplified CLI for supercop-ref10 .SH SYNOPSIS -\fBsupercop\fR \fI<operation>\fR [\fB-k\fR \fIkeyfile\fR] [\fB-m\fR \fImessage\fR] [\fB-M\fR \fImessage-file\fR] [\fB-s\fR \fIsignature\fR] +\fBsupercop\fR [\fIglobal options\fR] \fIcommand\fR [\fIcommand options\fR] +.br +\fBsupercop\fR \fBlist-commands\fR +.br +\fBsupercop\fR \fB--version\fR .SH DESCRIPTION This program allows easy signing and signature verification for the supercop-ref10 implementation of the ed25519 signature scheme. -.SH OPERATIONS +Global options must appear before the command name. Run \fBsupercop\fR \fIcommand\fR \fB--help\fR for the details on a single command. + +.SH GLOBAL OPTIONS .TP -.B \-\-printkey -Print contents of key file +.B \-h, \-\-help +Show usage and exit .TP -.B \-\-generate -Generate a new key +.B \-V, \-\-version +Show version number and exit (same as \fBsupercop version\fR) + +.SH COMMANDS +.TP +.B generate\fR [\fB-k\fR \fIkeyfile\fR] +Generate a new key, writing to keyfile or stdout .TP -.B \-\-sign +.B printkey\fR \fB-k\fR \fIkeyfile\fR +Print contents of key file +.TP +.B sign\fR \fB-k\fR \fIkeyfile\fR [\fB-m\fR \fImessage\fR | \fB-M\fR \fImessage-file\fR] Sign a message .TP -.B \-\-verify +.B verify\fR \fB-k\fR \fIkeyfile\fR \fB-s\fR \fIsignature\fR [\fB-m\fR \fImessage\fR | \fB-M\fR \fImessage-file\fR] Verify a message signature .TP -.B \-\-version -Show version number and exit - -.SH OPTIONS +.B version +Show version number and exit (same as \fBsupercop --version\fR) .TP -.B \-h, \-\-help -Show usage and exit +.B list-commands +List available commands + +.SH COMMAND OPTIONS .TP .B \-k, \-\-key\-file\fR \fIpath\fR Select key file to use for the operation @@ -55,5 +69,8 @@ Here are the possible return values: .SH ENVIRONMENT supercop doesn't follow any environment variables +.SH FILES +supercop uses no configuration files + .SH AUTHOR finwo (https://finwo.nl) diff --git a/package.ini b/package.ini @@ -1,6 +0,0 @@ -[dependencies] -cofyc/argparse=https://raw.githubusercontent.com/finwo/dep-repository/main/cofyc/argparse/package.ini -orlp/ed25519=https://raw.githubusercontent.com/finwo/dep-repository/main/orlp/ed25519/package.ini -[package] -deps=lib -name=finwo/supercop-cli diff --git a/src/cli/command.h b/src/cli/command.h @@ -0,0 +1,23 @@ +#ifndef __SUPERCOP_CLI_COMMAND_H__ +#define __SUPERCOP_CLI_COMMAND_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +struct cmd_struct { + void *next; + const char **name; + const char *display; + const char *description; + const char *help_text; + int (*fn)(int, const char **); +}; + +extern struct cmd_struct *commands; + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // __SUPERCOP_CLI_COMMAND_H__ diff --git a/src/cli/command/generate.c b/src/cli/command/generate.c @@ -0,0 +1,88 @@ +#include <stdio.h> +#include <stdlib.h> + +#include "cli/command.h" +#include "cli/common.h" +#include "cofyc/argparse.h" +#include "orlp/ed25519.h" + +static int cmd_generate(int argc, const char **argv) { + const char *keyFile = NULL; + FILE *fout = stdout; + int result; + + static const char *const usages[] = { + "supercop generate [-k keyfile]", + NULL, + }; + struct argparse_option options[] = { + OPT_HELP(), + OPT_STRING('k', "key-file", &keyFile, "Key file to write (defaults to stdout)"), + OPT_END(), + }; + + struct argparse argparse; + argparse_init(&argparse, options, usages, ARGPARSE_STOP_AT_NON_OPTION); + argparse_describe(&argparse, "\nGenerate a new ed25519 key", NULL); + argparse_parse(&argparse, argc, argv); + + if (keyFile) { + fout = fopen(keyFile, "w+"); + if (!fout) { + fprintf(stderr, "Could not open output file\n"); + return 1; + } + } + + unsigned char seed[32]; + unsigned char public_key[32]; + unsigned char private_key[64]; + + if (ed25519_create_seed(seed)) { + fprintf(stderr, "Error while generating seed\n"); + if (keyFile) fclose(fout); + return 1; + } + + ed25519_create_keypair(public_key, private_key, seed); + struct KeyPair kp; + kp.public_key = public_key; + kp.private_key = private_key; + + // Encode in the last-registered format + // TODO: allow format selection + int encoded_length; + char *encoded = supercop_formats->encode(&kp, &encoded_length); + fwrite(encoded, 1, encoded_length, fout); + free(encoded); + + result = 0; + if (keyFile) { + fclose(fout); + } + + return result; +} + +void __attribute__((constructor)) cmd_generate_setup(void) { + struct cmd_struct *cmd = calloc(1, sizeof(struct cmd_struct)); + if (!cmd) { + fprintf(stderr, "Failed to allocate memory for generate command\n"); + return; + } + cmd->next = commands; + cmd->fn = cmd_generate; + static const char *generate_names[] = {"generate", NULL}; + cmd->name = generate_names; + cmd->display = "generate"; + cmd->description = "Generate new key"; + cmd->help_text = + "supercop generate - Generate new key\n" + "\n" + "Usage:\n" + " supercop generate [-k keyfile]\n" + "\n" + "Options:\n" + " -k, --key-file <path> Key file to write (defaults to stdout)\n"; + commands = cmd; +} diff --git a/src/cli/command/list_commands.c b/src/cli/command/list_commands.c @@ -0,0 +1,49 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "cli/command.h" + +static int cmd_list_commands(int argc, const char **argv) { + (void)argc; + (void)argv; + + // Find the longest command name for aligned output + size_t width = 0; + struct cmd_struct *cmd = commands; + while (cmd) { + size_t len = strlen(cmd->display ? cmd->display : cmd->name[0]); + if (len > width) width = len; + cmd = cmd->next; + } + + cmd = commands; + while (cmd) { + printf(" %-*s %s\n", (int)width, + cmd->display ? cmd->display : cmd->name[0], + cmd->description ? cmd->description : ""); + cmd = cmd->next; + } + + return 0; +} + +void __attribute__((constructor)) cmd_list_commands_setup(void) { + struct cmd_struct *cmd = calloc(1, sizeof(struct cmd_struct)); + if (!cmd) { + fprintf(stderr, "Failed to allocate memory for list-commands command\n"); + return; + } + cmd->next = commands; + cmd->fn = cmd_list_commands; + static const char *list_commands_names[] = {"list-commands", NULL}; + cmd->name = list_commands_names; + cmd->display = "list-commands"; + cmd->description = "List available commands"; + cmd->help_text = + "supercop list-commands - List available commands\n" + "\n" + "Usage:\n" + " supercop list-commands\n"; + commands = cmd; +} diff --git a/src/cli/command/printkey.c b/src/cli/command/printkey.c @@ -0,0 +1,68 @@ +#include <stdio.h> +#include <stdlib.h> + +#include "cli/command.h" +#include "cli/common.h" +#include "cofyc/argparse.h" + +static int cmd_printkey(int argc, const char **argv) { + const char *keyFile = NULL; + struct KeyPair *kp; + int i, result; + + static const char *const usages[] = { + "supercop printkey -k keyfile", + NULL, + }; + struct argparse_option options[] = { + OPT_HELP(), + OPT_STRING('k', "key-file", &keyFile, "Select key file to use for the operation"), + OPT_END(), + }; + + struct argparse argparse; + argparse_init(&argparse, options, usages, ARGPARSE_STOP_AT_NON_OPTION); + argparse_describe(&argparse, "\nPrint contents of key file", NULL); + argparse_parse(&argparse, argc, argv); + + if (!keyFile) { + fprintf(stderr, "Missing required argument: key-file\n\n"); + argparse_usage(&argparse); + return 1; + } + + kp = readKeyFile(keyFile); + + fprintf(stdout, "public-key: "); + for(i=0;i<32;i++) fprintf(stdout, "%02x", kp->public_key[i]); + fprintf(stdout, "\nprivate-key: "); + for(i=0;i<64;i++) fprintf(stdout, "%02x", kp->private_key[i]); + fprintf(stdout, "\n"); + + result = 0; + keypair_free(kp); + return result; +} + +void __attribute__((constructor)) cmd_printkey_setup(void) { + struct cmd_struct *cmd = calloc(1, sizeof(struct cmd_struct)); + if (!cmd) { + fprintf(stderr, "Failed to allocate memory for printkey command\n"); + return; + } + cmd->next = commands; + cmd->fn = cmd_printkey; + static const char *printkey_names[] = {"printkey", NULL}; + cmd->name = printkey_names; + cmd->display = "printkey"; + cmd->description = "Print contents of key file"; + cmd->help_text = + "supercop printkey - Print contents of key file\n" + "\n" + "Usage:\n" + " supercop printkey -k keyfile\n" + "\n" + "Options:\n" + " -k, --key-file <path> Select key file to use for the operation\n"; + commands = cmd; +} diff --git a/src/cli/command/sign.c b/src/cli/command/sign.c @@ -0,0 +1,83 @@ +#include <stdio.h> +#include <stdlib.h> + +#include "cli/command.h" +#include "cli/common.h" +#include "cofyc/argparse.h" +#include "orlp/ed25519.h" + +static int cmd_sign(int argc, const char **argv) { + const char *keyFile = NULL; + const char *message = NULL; + const char *messageFile = NULL; + struct KeyPair *kp; + FILE *fmessage; + int i, result; + + static const char *const usages[] = { + "supercop sign -k keyfile [-m message | -M message-file]", + NULL, + }; + struct argparse_option options[] = { + OPT_HELP(), + OPT_STRING('k', "key-file", &keyFile, "Select key file to use for the operation"), + OPT_STRING('m', "message", &message, "Message to sign (defaults to stdin)"), + OPT_STRING('M', "message-file", &messageFile, "Message file to sign (defaults to stdin)"), + OPT_END(), + }; + + struct argparse argparse; + argparse_init(&argparse, options, usages, ARGPARSE_STOP_AT_NON_OPTION); + argparse_describe(&argparse, "\nSign a message", NULL); + argparse_parse(&argparse, argc, argv); + + if (!keyFile) { + fprintf(stderr, "Missing required argument: key-file\n\n"); + argparse_usage(&argparse); + return 1; + } + + fmessage = supercop_open_message(message, messageFile); + kp = readKeyFile(keyFile); + + long message_len = fremaining(fmessage); + const unsigned char *msg = calloc(1, message_len); + fread((void *)msg, 1, message_len, fmessage); + + unsigned char *signature = calloc(1, 64); + ed25519_sign(signature, msg, message_len, kp->public_key, kp->private_key); + + for(i=0;i<64;i++) fprintf(stdout, "%02x", signature[i]); + fprintf(stdout, "\n"); + + free(signature); + free((void *)msg); + keypair_free(kp); + result = 0; + return result; +} + +void __attribute__((constructor)) cmd_sign_setup(void) { + struct cmd_struct *cmd = calloc(1, sizeof(struct cmd_struct)); + if (!cmd) { + fprintf(stderr, "Failed to allocate memory for sign command\n"); + return; + } + cmd->next = commands; + cmd->fn = cmd_sign; + static const char *sign_names[] = {"sign", NULL}; + cmd->name = sign_names; + cmd->display = "sign"; + cmd->description = "Sign a message"; + cmd->help_text = + "supercop sign - Sign a message\n" + "\n" + "Usage:\n" + " supercop sign -k keyfile [-m message | -M message-file]\n" + "\n" + "Options:\n" + " -k, --key-file <path> Select key file to use for the operation\n" + " -m, --message <message> Message to sign (defaults to stdin)\n" + " -M, --message-file <path> Message file to sign (defaults to stdin)\n"; + commands = cmd; +} diff --git a/src/cli/command/verify.c b/src/cli/command/verify.c @@ -0,0 +1,108 @@ +#include <stdio.h> +#include <stdlib.h> + +#include "cli/command.h" +#include "cli/common.h" +#include "cofyc/argparse.h" +#include "orlp/ed25519.h" + +static int cmd_verify(int argc, const char **argv) { + const char *keyFile = NULL; + const char *message = NULL; + const char *messageFile = NULL; + const char *verifySignature = NULL; + struct KeyPair *kp; + FILE *fmessage; + FILE *fsignature; + int isValid; + + static const char *const usages[] = { + "supercop verify -k keyfile -s signature [-m message | -M message-file]", + NULL, + }; + struct argparse_option options[] = { + OPT_HELP(), + OPT_STRING('k', "key-file", &keyFile, "Select key file to use for the operation"), + OPT_STRING('m', "message", &message, "Message to verify (defaults to stdin)"), + OPT_STRING('M', "message-file", &messageFile, "Message file to verify (defaults to stdin)"), + OPT_STRING('s', "signature", &verifySignature, "Signature to verify"), + OPT_END(), + }; + + struct argparse argparse; + argparse_init(&argparse, options, usages, ARGPARSE_STOP_AT_NON_OPTION); + argparse_describe(&argparse, "\nVerify a message signature", NULL); + argparse_parse(&argparse, argc, argv); + + if (!keyFile) { + fprintf(stderr, "Missing required argument: key-file\n\n"); + argparse_usage(&argparse); + return 1; + } + + if (!verifySignature) { + fprintf(stderr, "Missing required argument: signature\n\n"); + argparse_usage(&argparse); + return 1; + } + + fmessage = supercop_open_message(message, messageFile); + fsignature = supercop_parse_signature(verifySignature); + + long message_len = fremaining(fmessage); + const unsigned char *msg = calloc(1, message_len); + fread((void *)msg, 1, message_len, fmessage); + + long signature_len = fremaining(fsignature); + const unsigned char *sig = calloc(1, signature_len); + fread((void *)sig, 1, signature_len, fsignature); + if (signature_len != 64) { + fprintf(stderr, "Invalid signature!!\n"); + free((void *)sig); + free((void *)msg); + fclose(fsignature); + return 1; + } + + kp = readKeyFile(keyFile); + isValid = ed25519_verify(sig, msg, message_len, kp->public_key); + + free((void *)sig); + free((void *)msg); + fclose(fsignature); + keypair_free(kp); + + if (isValid) { + fprintf(stdout, "OK\n"); + return 0; + } else { + fprintf(stdout, "FAIL\n"); + return 1; + } +} + +void __attribute__((constructor)) cmd_verify_setup(void) { + struct cmd_struct *cmd = calloc(1, sizeof(struct cmd_struct)); + if (!cmd) { + fprintf(stderr, "Failed to allocate memory for verify command\n"); + return; + } + cmd->next = commands; + cmd->fn = cmd_verify; + static const char *verify_names[] = {"verify", NULL}; + cmd->name = verify_names; + cmd->display = "verify"; + cmd->description = "Verify a message signature"; + cmd->help_text = + "supercop verify - Verify a message signature\n" + "\n" + "Usage:\n" + " supercop verify -k keyfile -s signature [-m message | -M message-file]\n" + "\n" + "Options:\n" + " -k, --key-file <path> Select key file to use for the operation\n" + " -m, --message <message> Message to verify (defaults to stdin)\n" + " -M, --message-file <path> Message file to verify (defaults to stdin)\n" + " -s, --signature <hex> Signature to verify the message against\n"; + commands = cmd; +} diff --git a/src/cli/command/version.c b/src/cli/command/version.c @@ -0,0 +1,43 @@ +#include <stdio.h> +#include <stdlib.h> + +#include "cli/command.h" +#include "cli/common.h" + +static int cmd_version(int argc, const char **argv) { + (void)argc; + (void)argv; + print_version(); + return 0; +} + +void __attribute__((constructor)) cmd_version_setup(void) { + struct cmd_struct *cmd = calloc(1, sizeof(struct cmd_struct)); + if (!cmd) { + fprintf(stderr, "Failed to allocate memory for version command\n"); + return; + } + cmd->next = commands; + cmd->fn = cmd_version; + static const char *version_names[] = {"version", NULL}; + cmd->name = version_names; + cmd->display = "version"; + cmd->description = "Show version number and exit"; + cmd->help_text = + "supercop version - Show version number and exit\n" + "\n" + "Usage:\n" + " supercop version\n" + "\n" + "Description:\n" + " Print the version of supercop and exit.\n" + "\n" + " The global --version flag is dispatched to this command, so both\n" + " produce the exact same output.\n" + "\n" + "Examples:\n" + " supercop version # Show the version\n" + " supercop --version # Same, as a global flag\n" + " supercop -V # Same, short form\n"; + commands = cmd; +} diff --git a/src/cli/common.c b/src/cli/common.c @@ -0,0 +1,88 @@ +#include "cli/common.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#ifndef VERSION +#define VERSION "n/a" +#endif + +long fremaining(FILE *fd) { + long current = ftell(fd); + fseek(fd, 0, SEEK_END); + long end = ftell(fd); + fseek(fd, current, SEEK_SET); + return end - current; +} + +struct KeyPair *readKeyFile(const char *filename) { + struct Format *fmt = supercop_formats; + FILE *fd = fopen(filename, "r"); + unsigned char *buf; + struct KeyPair *kp; + + if (!fd) { + fprintf(stderr, "Could not open key file\n"); + exit(1); + } + + // Read whole file + long fsize = fremaining(fd); + buf = calloc(1, fsize + 1); + fread(buf, 1, fsize, fd); + + // Auto-detect format + while(fmt) { + if (!fmt->detect(buf)) { + fmt = fmt->next; + continue; + } + kp = fmt->decode(buf); + free(buf); + fclose(fd); + return kp; + } + + return NULL; +} + +FILE *supercop_open_message(const char *message, const char *messageFile) { + FILE *fmessage = stdin; + + if (message) { + fmessage = tmpfile(); + fwrite(message, 1, strlen(message), fmessage); + fseek(fmessage, 0, SEEK_SET); + } + + if (messageFile) { + if (message) fclose(fmessage); + fmessage = fopen(messageFile, "r"); + if (!fmessage) { + fprintf(stderr, "Could not open message file\n"); + exit(1); + } + } + + return fmessage; +} + +FILE *supercop_parse_signature(const char *hex) { + FILE *fsignature = tmpfile(); + const char *pos = hex; + unsigned char c; + + while(*pos) { + sscanf(pos, "%2hhx", &c); + fputc(c, fsignature); + pos += 2; + } + fseek(fsignature, 0, SEEK_SET); + + return fsignature; +} + +void print_version(void) { + fprintf(stdout, "%s\n", VERSION); +} diff --git a/src/cli/common.h b/src/cli/common.h @@ -0,0 +1,36 @@ +#ifndef __SUPERCOP_CLI_COMMON_H__ +#define __SUPERCOP_CLI_COMMON_H__ + +#include <stdio.h> + +#include "fmt/common.h" +#include "keypair/keypair.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Intentionally minimal: this is a short-lived CLI, so it keeps the +// fprintf(stdout/stderr) + exit-code contract instead of adopting the +// rxi/log verbosity model used by the daemon projects (unosd, udphole). +// No config file, no environment variables: flags and stdin/files only. + +extern struct Format *supercop_formats; + +long fremaining(FILE *fd); +struct KeyPair *readKeyFile(const char *filename); + +// Message source: -m string wins, then -M file, then stdin +FILE *supercop_open_message(const char *message, const char *messageFile); + +// Parse a hex signature string into a rewinded tmpfile (must be 64 bytes) +FILE *supercop_parse_signature(const char *hex); + +// Single implementation shared by the --version flag and version subcommand +void print_version(void); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // __SUPERCOP_CLI_COMMON_H__ diff --git a/src/cli/usage.c b/src/cli/usage.c @@ -0,0 +1,79 @@ +#include "usage.h" + +#include <stdio.h> +#include <stdlib.h> + +#include "cofyc/argparse.h" +#include "cli/command.h" + +static int opt_version = 0; + +static const char *const usages[] = { + "supercop [global options] <command> [command options]", + "supercop list-commands", + NULL, +}; + +static struct argparse_option options[] = { + OPT_GROUP("Global options:"), + OPT_BOOLEAN('h', "help", NULL, "Show this help message and exit", argparse_help_cb, 0, OPT_NONEG), + OPT_BOOLEAN('V', "version", &opt_version, "Show version information and exit", NULL, 0, OPT_NONEG), + OPT_END(), +}; + +// argparse prints the epilog at the end of its usage output, which is the only +// way to get our command listing into the built-in --help handler +static char *build_epilog(void) { + char *buffer = NULL; + size_t size = 0; + FILE *out = open_memstream(&buffer, &size); + if (!out) { + return NULL; + } + + struct cmd_struct *cmd = commands; + + // The %-17s aligns our descriptions with the ones argparse prints + fprintf(out, "\nCommands:\n"); + while (cmd) { + fprintf(out, " %-17s %s\n", cmd->display ? cmd->display : cmd->name[0], + cmd->description ? cmd->description : ""); + cmd = cmd->next; + } + + fclose(out); + + // argparse adds a newline of its own + if (size && buffer[size - 1] == '\n') { + buffer[size - 1] = '\0'; + } + + return buffer; +} + +int supercop_global_options(int argc, const char **argv, int *show_version) { + struct argparse argparse; + char *epilog = build_epilog(); + + opt_version = 0; + argparse_init(&argparse, options, usages, ARGPARSE_STOP_AT_NON_OPTION); + argparse_describe(&argparse, "\nMinimalistic program to generate ed25519 keys and verify/sign messages", epilog); + argc = argparse_parse(&argparse, argc, argv); + free(epilog); + + if (show_version) { + *show_version = opt_version; + } + + return argc; +} + +void supercop_print_global_usage(void) { + struct argparse argparse; + char *epilog = build_epilog(); + + argparse_init(&argparse, options, usages, ARGPARSE_STOP_AT_NON_OPTION); + argparse_describe(&argparse, "\nMinimalistic program to generate ed25519 keys and verify/sign messages", epilog); + argparse_usage(&argparse); + free(epilog); +} diff --git a/src/cli/usage.h b/src/cli/usage.h @@ -0,0 +1,19 @@ +#ifndef __SUPERCOP_CLI_USAGE_H__ +#define __SUPERCOP_CLI_USAGE_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +// Parse the global options, returns the remaining argument count +// Sets show_version when -V/--version was given, handles --help by itself +int supercop_global_options(int argc, const char **argv, int *show_version); + +// Print the global usage: options, commands and help topics +void supercop_print_global_usage(void); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // __SUPERCOP_CLI_USAGE_H__ diff --git a/src/main.c b/src/main.c @@ -1,343 +1,58 @@ -#include <stddef.h> #include <stdio.h> #include <stdlib.h> #include <string.h> -#define COMMAND_GENERATE 1 -#define COMMAND_PRINTKEY 2 -#define COMMAND_SIGN 4 -#define COMMAND_VERIFY 8 -#define COMMAND_VERSION 16 - -#ifndef VERSION -#define VERSION "n/a" -#endif - #ifdef __cplusplus extern "C" { #endif -#include "cofyc/argparse.h" -#include "orlp/ed25519.h" +#include "cli/command.h" +#include "cli/common.h" +#include "cli/usage.h" #include "fmt/common.h" -#include "keypair/keypair.h" struct Format *supercop_formats = NULL; +struct cmd_struct *commands = NULL; -static const char *const usage[] = { - "supercop <command> [options]", - NULL, -}; - -long fremaining(FILE *fd) { - long current = ftell(fd); - fseek(fd, 0, SEEK_END); - long end = ftell(fd); - fseek(fd, current, SEEK_SET); - return end - current; -} - -struct KeyPair * readKeyFile(const char *filename) { - struct Format *fmt = supercop_formats; - FILE *fd = fopen(filename, "r"); - unsigned char *buf; - struct KeyPair *kp; - - if (!fd) { - fprintf(stderr, "Could not open key file\n"); - exit(1); - } +int main(int argc, const char **argv) { + int show_version = 0; - // Read whole file - long fsize = fremaining(fd); - buf = calloc(1, fsize + 1); - fread(buf, 1, fsize, fd); + argc = supercop_global_options(argc, argv, &show_version); - // Auto-detect format - while(fmt) { - if (!fmt->detect(buf)) { - fmt = fmt->next; - continue; - } - kp = fmt->decode(buf); - free(buf); - fclose(fd); - return kp; + /* --version is nothing but another way of calling the version command */ + if (show_version) { + static const char *version_argv[] = {"version", NULL}; + argv = version_argv; + argc = 1; } - return NULL; -} - -int cmd_generate(FILE *fd) { - unsigned char version = 0; - unsigned char seed[32]; - unsigned char public_key[32]; - unsigned char private_key[64]; - - // Generate random seed - if (ed25519_create_seed(seed)) { - fprintf(stderr, "Error while generating seed\n"); - exit(1); + if (argc < 1) { + supercop_print_global_usage(); + return 1; } - // Generate the actual key - ed25519_create_keypair(public_key, private_key, seed); - struct KeyPair kp; - kp.public_key = public_key; - kp.private_key = private_key; - - // Encode in the last-registered format - // TODO: allow format selection - int encoded_length; - char *encoded = supercop_formats->encode(&kp, &encoded_length); - fwrite(encoded, 1, encoded_length, fd); - - // Clean up - free(encoded); - - return 0; -} - -// Prints human-readable version of the keypair -// Should be easy in high-level languages as well -int cmd_printkey(struct KeyPair *kp, FILE *fout) { - int i; - fprintf(fout, "public-key: "); - for(i=0;i<32;i++) fprintf(fout, "%02x", kp->public_key[i]); - fprintf(fout, "\nprivate-key: "); - for(i=0;i<64;i++) fprintf(fout, "%02x", kp->private_key[i]); - fprintf(fout, "\n"); - return 0; -} - -int cmd_sign(struct KeyPair *kp, FILE *fmessage, FILE *fout) { - int i; - - // Read message - // TODO: make this stdin-compatible - long message_len = fremaining(fmessage); - const unsigned char *message = calloc(1, message_len); - fread(message, 1, message_len, fmessage); - - // Create signature - unsigned char *signature = calloc(1, 64); - ed25519_sign(signature, message, message_len, kp->public_key, kp->private_key); - - // Output signature - for(i=0;i<64;i++) fprintf(fout, "%02x", signature[i]); - fprintf(fout, "\n"); - - // Clean up - free(signature); - free(message); - return 0; -} - -int cmd_verify(struct KeyPair *kp, FILE *fmessage, FILE *fsignature, FILE *fout) { - int isValid; - - // Read message - // TODO: make this stdin-compatible - long message_len = fremaining(fmessage); - const unsigned char *message = calloc(1, message_len); - fread(message, 1, message_len, fmessage); - - // Read signature - long signature_len = fremaining(fsignature); - const unsigned char *signature = calloc(1, signature_len); - fread(signature, 1, signature_len, fsignature); - if (signature_len != 64) { - fprintf(stderr, "Invalid signature!!\n"); - exit(1); + /* Try to run command with args provided. */ + struct cmd_struct *cmd = commands; + while (cmd) { + const char **name = cmd->name; + while (*name) { + if (!strcmp(*name, argv[0])) { + goto found; + } + name++; + } + cmd = cmd->next; } +found: - // Verify the signature - isValid = ed25519_verify(signature, message, message_len, kp->public_key); - - // Clean up - free(signature); - free(message); - - // Handle response - if (isValid) { - fprintf(fout, "OK\n"); - return 0; + if (cmd) { + return cmd->fn(argc, argv); } else { - fprintf(fout, "FAIL\n"); + fprintf(stderr, "Unknown command: %s\n", argv[0]); return 1; } -} - -int main(int argc, const char **argv) { - struct KeyPair *kp; - const char *keyFile = NULL; - const char *verifySignature = NULL; - const char *message = NULL; - const char *messageFile = NULL; - unsigned char c; - int command = 0; - int result = 0; - int i; - const char *pos; - - FILE *fmessage = NULL; - FILE *fsignature = NULL; - FILE *fout = NULL; - - // Setup help & argument parsing - struct argparse_option options[] = { - OPT_GROUP("Operations"), - OPT_BIT(0, "printkey", &command, "Print contents of key file" , NULL, COMMAND_PRINTKEY), - OPT_BIT(0, "generate", &command, "Generate new key" , NULL, COMMAND_GENERATE), - OPT_BIT(0, "sign" , &command, "Sign a message" , NULL, COMMAND_SIGN ), - OPT_BIT(0, "verify" , &command, "Verify a message signature" , NULL, COMMAND_VERIFY ), - OPT_BIT(0, "version" , &command, "Show version number and exit", NULL, COMMAND_VERSION ), - // TODO: key-exchange (would be cool, questionable usability though - OPT_GROUP("Basic options"), - OPT_HELP(), - OPT_STRING('k', "key-file" , &keyFile , "Select key file to use for the operation"), - OPT_STRING('m', "message" , &message , "Message to sign or verify (defaults to stdin)"), - OPT_STRING('M', "message-file", &messageFile , "Message file to sign or verify (defaults to stdin)"), - OPT_STRING('s', "signature" , &verifySignature, "Signature to verify"), - // TODO: signature-file (for both sign & verify) - OPT_END(), - }; - - // Setup basic information - struct argparse argparse; - argparse_init(&argparse, options, usage, 0); - argparse_describe(&argparse, "\nMinimalistic program to generate ed25519 keys and verify/sign messages", NULL); - - // Actually parse the arguments - argc = argparse_parse(&argparse, argc, argv); - - // Our paths separate here - switch(command) { - - case COMMAND_VERSION: - fprintf(stdout, "%s\n", VERSION); - return 0; - - case COMMAND_GENERATE: - - // Build output fd - fout = stdout; - if (keyFile) { - fout = fopen(keyFile, "w+"); - if (!fout) { - fprintf(stderr, "Could not open output file\n"); - exit(1); - } - } - - // Actually generate the key file - result = cmd_generate(fout); - - // Close output fd if file - if (keyFile) { - fclose(fout); - } - - // Done - return result; - - case COMMAND_PRINTKEY: - - // No key file = error - if (!keyFile) { - fprintf(stderr, "Missing required argument: key-file\n\n"); - argparse_usage(&argparse); - exit(1); - } - - kp = readKeyFile(keyFile); - result = cmd_printkey(kp, stdout); - keypair_free(kp); - return result; - - case COMMAND_SIGN: - - // No key file = error - if (!keyFile) { - fprintf(stderr, "Missing required argument: key-file\n\n"); - argparse_usage(&argparse); - exit(1); - } - - // Fetch message as FILE* - fmessage = stdin; - if (message) { - fmessage = tmpfile(); - fwrite(message, 1, strlen(message), fmessage); - fseek(fmessage, 0, SEEK_SET); - } - if (messageFile) { - if (message) fclose(fmessage); - fmessage = fopen(messageFile, "r"); - if (!fmessage) { - fprintf(stderr, "Could not open message file\n"); - exit(1); - } - } - - // Read the key file we're using - kp = readKeyFile(keyFile); - result = cmd_sign(kp, fmessage, stdout); - keypair_free(kp); - return result; - - case COMMAND_VERIFY: - - // No key file = error - if (!keyFile) { - fprintf(stderr, "Missing required argument: key-file\n\n"); - argparse_usage(&argparse); - exit(1); - } - - // Fetch message as FILE* - fmessage = stdin; - if (message) { - fmessage = tmpfile(); - fwrite(message, 1, strlen(message), fmessage); - fseek(fmessage, 0, SEEK_SET); - } - if (messageFile) { - if (message) fclose(fmessage); - fmessage = fopen(messageFile, "r"); - if (!fmessage) { - fprintf(stderr, "Could not open message file\n"); - exit(1); - } - } - - // Signature = required during verify - if (!verifySignature) { - fprintf(stderr, "Missing required argument: signature\n\n"); - argparse_usage(&argparse); - exit(1); - } - - // Parse signature as hex & keep as FILE* - fsignature = tmpfile(); - pos = verifySignature; - while(*pos) { - sscanf(pos, "%2hhx", &c); - fputc(c, fsignature); - pos += 2; - } - fseek(fsignature, 0, SEEK_SET); - - // Read key & verify signature - kp = readKeyFile(keyFile); - result = cmd_verify(kp, fmessage, fsignature, stdout); - fclose(fsignature); - keypair_free(kp); - return result; - } - argparse_usage(&argparse); - return 1; + return 0; } #ifdef __cplusplus diff --git a/target/common/.dep b/target/common/.dep @@ -0,0 +1,2 @@ +cofyc/argparse https://git.finwo.net/misc/dep-repository/archives/heads/pkg/cofyc/argparse.tar.gz +orlp/ed25519 https://git.finwo.net/misc/dep-repository/archives/heads/pkg/orlp/ed25519.tar.gz diff --git a/target/common/Makefile b/target/common/Makefile @@ -0,0 +1,56 @@ +TARGET?= + +BIN=supercop + +VERSION?=n/a + +SRC:= +SRC+=$(wildcard src/*.c) +SRC+=$(wildcard src/*/*.c) +SRC+=$(wildcard src/*/*/*.c) +SRC+=$(wildcard src/*/*/*/*.c) + +# Ourselves +MTUNE?=native +MARCH?=native +CFLAGS?= +CFLAGS+=-D VERSION=\"$(VERSION)\" +CFLAGS+=-mtune=$(MTUNE) -march=$(MARCH) +CFLAGS+=-O2 -pipe +CFLAGS+=-Wall -Wextra + +INCLUDES:= +INCLUDES+=-I src + +include lib/.dep/config.mk + +# Target-specific overrides, optional. +# Supplied by target/<target>/Makefile.target +-include Makefile.target + +CFLAGS+=$(INCLUDES) + +OBJ:=$(SRC:.c=.o) + +.PHONY: default +default: $(BIN) + +%.o: %.c $(LIBS) + $(CC) $(CFLAGS) $(@:.o=.c) -D__NAME=\"$(BIN)\" -D__TARGET=\"$(TARGET)\" -c -o $@ + +$(BIN): $(OBJ) + $(CC) $(CFLAGS) $(OBJ) -o $@ + +.PHONY: static +static: + CFLAGS="-static -Os -s" $(MAKE) "MTUNE=$(MTUNE)" "MARCH=$(MARCH)" $(BIN) + strip -S --strip-unneeded --remove-section=.note.gnu.gold-version --remove-section=.comment --remove-section=.note --remove-section=.note.gnu.build-id --remove-section=.note.ABI-tag $(BIN) + +.PHONY: clean +clean: + rm -f $(OBJ) $(BIN) + +.PHONY: install +install: $(BIN) + install -Dm0755 $(BIN) $(DESTDIR)/bin/$(BIN) + install -Dm0644 man/supercop.1 $(DESTDIR)/share/man/man1/supercop.1 diff --git a/target/linux-glibc-amd64/.gitkeep b/target/linux-glibc-amd64/.gitkeep