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:
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