supercop

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

commit 68eb46b7fb3289bfc552e26f3567321acfb8d6d1
parent 0cd54e2ee4f7fc860defffcbc7cd93652051bb12
Author: Robin Bron <robin.bron@yourhosting.nl>
Date:   Sun, 13 Sep 2026 22:27:29 +0200

Add hdr and asc key formats

Diffstat:
Mencoding.md | 53+++++++++++++++++++++++++++++++++++++++++++++++++----
Mman/supercop.1 | 15++++++++++++---
Msrc/cli/command/generate.c | 28+++++++++++++++++++---------
Msrc/cli/command/printkey.c | 59+++++++++++++++++++++++++++++++++++++++++++++++------------
Msrc/cli/common.c | 10++++++++++
Msrc/cli/common.h | 3+++
Asrc/fmt/asc/armor.c | 44++++++++++++++++++++++++++++++++++++++++++++
Asrc/fmt/asc/armor.h | 36++++++++++++++++++++++++++++++++++++
Asrc/fmt/asc/b64.c | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/fmt/asc/b64.h | 25+++++++++++++++++++++++++
Asrc/fmt/asc/common.h | 11+++++++++++
Asrc/fmt/asc/decode.c | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/fmt/asc/detect.c | 22++++++++++++++++++++++
Asrc/fmt/asc/encode.c | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/fmt/asc/register.c | 30++++++++++++++++++++++++++++++
Asrc/fmt/hdr/common.h | 11+++++++++++
Asrc/fmt/hdr/decode.c | 102+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/fmt/hdr/detect.c | 38++++++++++++++++++++++++++++++++++++++
Asrc/fmt/hdr/encode.c | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/fmt/hdr/register.c | 30++++++++++++++++++++++++++++++
Asrc/fmt/hdr/scan.c | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/fmt/hdr/scan.h | 34++++++++++++++++++++++++++++++++++
22 files changed, 933 insertions(+), 28 deletions(-)

diff --git a/encoding.md b/encoding.md @@ -3,16 +3,61 @@ Keys can (or will) be encoded in multiple formats. This file shows a list of encodings supported by this program. -The first byte of a key-file must ALWAYS start with the version identifier -(byte sequence prefix), allowing a different prefix to identify a different type -of key encoding. +Each key-file must start with a distinct prefix (byte sequence), allowing a +different prefix to identify a different type of key encoding. + +A decoded key is either full (public + private half) or public-only. A +`NULL` private half means public-only; commands requiring the private half +(`sign`) refuse such keys while `verify` accepts them. A lone private half +additionally yields its public half via scalar multiplication. ## 0x00 Plain, binary, ed25519. Not text-safe. Starts with a 0x00 byte, followed by 32 bytes of the raw public -key, then followed by 64 bytes of the raw private key. +key. Full keys additionally carry 64 bytes of the raw private key after that, +so valid sizes are 33 (public-only) or 97 (full) bytes. Intended as the first format supported and to be deprecated later. Meant to be simple to code, not to be safe or flexible. + +## asc + +Ascii-armored raw key bytes, no prefix. A full key emits both a PRIVATE +block (64 private bytes) and a PUBLIC block (32 public bytes): + +``` +-----BEGIN SUPERCOP PRIVATE KEY----- +<base64, 64 columns> +-----END SUPERCOP PRIVATE KEY----- +-----BEGIN SUPERCOP PUBLIC KEY----- +<base64, 64 columns> +-----END SUPERCOP PUBLIC KEY----- +``` + +Public-only keys emit just the PUBLIC block. Labels are namespaced so +openssl never mistakes the raw payload for DER/PKCS#8. When both blocks +are present, the private block is authoritative and the public block must +agree with the derived public half; a corrupt private block fails closed. + +## hdr + +General-purpose text-protocol header encoding. Byte-identical to what +`printkey` outputs: + +``` +public-key: <64 hex> +private-key: <128 hex | (no private key)> +``` + +Parsing rules, meant to make integration in other programs easy: + +- Key lines may appear on any line, in any order, surrounded by any other + content; unknown labels are ignored. +- Labels are case-insensitive, with 0 or more spaces/tabs around the `:`. +- A parenthesized value (e.g. `(no private key)`) marks the field absent; a + missing `private-key:` line means the same. First valid occurrence wins, + malformed hex runs are skipped. +- A lone `private-key:` line derives its public half; a file with neither + key line is invalid. diff --git a/man/supercop.1 b/man/supercop.1 @@ -25,11 +25,11 @@ Show version number and exit (same as \fBsupercop version\fR) .SH COMMANDS .TP -.B generate\fR [\fB-k\fR \fIkeyfile\fR] +.B generate\fR [\fIoptions\fR] Generate a new key, writing to keyfile or stdout .TP -.B printkey\fR \fB-k\fR \fIkeyfile\fR -Print contents of key file +.B printkey\fR [\fIoptions\fR] +Print contents of key file, optionally converting formats .TP .B sign\fR \fB-k\fR \fIkeyfile\fR [\fB-m\fR \fImessage\fR | \fB-M\fR \fImessage-file\fR] Sign a message @@ -56,6 +56,15 @@ Message file to sign or verify (defaults to stdin) .TP .B \-s, \-\-signature\fR \fIsignature_string\fR Signature to verify the message against +.TP +.B \-f, \-\-format\fR \fIname\fR +Key format for generate output or printkey conversion: 0x00, asc, hdr +.TP +.B \-o, \-\-out\fR \fIpath\fR +Write printkey output to file instead of stdout +.TP +.B \-\-public\-only +Output only the public half of the key (printkey) .SH RETURN VALUE Here are the possible return values: diff --git a/src/cli/command/generate.c b/src/cli/command/generate.c @@ -1,5 +1,6 @@ #include <stdio.h> #include <stdlib.h> +#include <string.h> #include "cli/command.h" #include "cli/common.h" @@ -8,16 +9,20 @@ static int cmd_generate(int argc, const char **argv) { const char *keyFile = NULL; + const char *format = "0x00"; FILE *fout = stdout; + struct Format *fmt; + struct KeyPair out; int result; static const char *const usages[] = { - "supercop generate [-k keyfile]", + "supercop generate [options]", NULL, }; struct argparse_option options[] = { OPT_HELP(), OPT_STRING('k', "key-file", &keyFile, "Key file to write (defaults to stdout)", NULL, 0, 0), + OPT_STRING('f', "format", &format, "Key format to write (0x00, asc, hdr)", NULL, 0, 0), OPT_END(), }; @@ -45,14 +50,18 @@ static int cmd_generate(int argc, const char **argv) { } ed25519_create_keypair(public_key, private_key, seed); - struct KeyPair kp; - kp.public_key = public_key; - kp.private_key = private_key; + out.public_key = public_key; + out.private_key = private_key; - // Encode in the last-registered format - // TODO: allow format selection + // Encode in the requested format (default keeps historical behavior) + fmt = supercop_find_format(format); + if (!fmt) { + fprintf(stderr, "Unknown format: %s\n", format); + if (keyFile) fclose(fout); + return 1; + } size_t encoded_length; - char *encoded = supercop_formats->encode(&kp, &encoded_length); + char *encoded = fmt->encode(&out, &encoded_length); if (!encoded) { fprintf(stderr, "Error while encoding key\n"); if (keyFile) fclose(fout); @@ -85,9 +94,10 @@ void __attribute__((constructor)) cmd_generate_setup(void) { "supercop generate - Generate new key\n" "\n" "Usage:\n" - " supercop generate [-k keyfile]\n" + " supercop generate [options]\n" "\n" "Options:\n" - " -k, --key-file <path> Key file to write (defaults to stdout)\n"; + " -k, --key-file <path> Key file to write (defaults to stdout)\n" + " -f, --format <name> Key format to write: 0x00, asc, hdr (default 0x00)\n"; commands = cmd; } diff --git a/src/cli/command/printkey.c b/src/cli/command/printkey.c @@ -7,16 +7,27 @@ static int cmd_printkey(int argc, const char **argv) { const char *keyFile = NULL; + const char *format = "hdr"; + const char *outFile = NULL; + int pubOnly = 0; struct KeyPair *kp; - int i, result; + struct Format *fmt; + struct KeyPair out; + FILE *fout = stdout; + size_t encoded_length; + char *encoded; + int result; static const char *const usages[] = { - "supercop printkey -k keyfile", + "supercop printkey [options]", NULL, }; struct argparse_option options[] = { OPT_HELP(), OPT_STRING('k', "key-file", &keyFile, "Select key file to use for the operation", NULL, 0, 0), + OPT_STRING('f', "format", &format, "Output format (0x00, asc, hdr, defaults to hdr)", NULL, 0, 0), + OPT_STRING('o', "out", &outFile, "Write output to file instead of stdout", NULL, 0, 0), + OPT_BOOLEAN(0, "public-only", &pubOnly, "Output only the public half of the key", NULL, 0, 0), OPT_END(), }; @@ -38,17 +49,38 @@ static int cmd_printkey(int argc, const char **argv) { return 1; } - fprintf(stdout, "public-key: "); - for(i=0;i<32;i++) fprintf(stdout, "%02x", kp->public_key[i]); - if (kp->private_key) { - fprintf(stdout, "\nprivate-key: "); - for(i=0;i<64;i++) fprintf(stdout, "%02x", kp->private_key[i]); - } else { - fprintf(stdout, "\nprivate-key: (no private key in file)"); + fmt = supercop_find_format(format); + if (!fmt) { + fprintf(stderr, "Unknown format: %s\n", format); + keypair_free(kp); + return 1; + } + + if (outFile) { + fout = fopen(outFile, "w+"); + if (!fout) { + fprintf(stderr, "Could not open output file\n"); + keypair_free(kp); + return 1; + } + } + + // Shallow copy: encoders never take ownership, priv stripped on request + out = *kp; + if (pubOnly) out.private_key = NULL; + + encoded = fmt->encode(&out, &encoded_length); + if (!encoded) { + fprintf(stderr, "Error while encoding key\n"); + if (outFile && fout) fclose(fout); + keypair_free(kp); + return 1; } - fprintf(stdout, "\n"); + fwrite(encoded, 1, encoded_length, fout); + if (encoded) free(encoded); result = 0; + if (outFile && fout) fclose(fout); keypair_free(kp); return result; } @@ -69,9 +101,12 @@ void __attribute__((constructor)) cmd_printkey_setup(void) { "supercop printkey - Print contents of key file\n" "\n" "Usage:\n" - " supercop printkey -k keyfile\n" + " supercop printkey [options]\n" "\n" "Options:\n" - " -k, --key-file <path> Select key file to use for the operation\n"; + " -k, --key-file <path> Select key file to use for the operation\n" + " -f, --format <name> Output format: 0x00, asc, hdr (default hdr)\n" + " -o, --out <path> Write output to file instead of stdout\n" + " --public-only Output only the public half of the key\n"; commands = cmd; } diff --git a/src/cli/common.c b/src/cli/common.c @@ -16,6 +16,16 @@ long fremaining(FILE *fd) { return end - current; } +struct Format *supercop_find_format(const char *name) { + struct Format *fmt = supercop_formats; + if (!name) return NULL; + while (fmt) { + if (fmt->name && strcmp(fmt->name, name) == 0) return fmt; + fmt = fmt->next; + } + return NULL; +} + struct KeyPair *readKeyFile(const char *filename) { struct Format *fmt = supercop_formats; FILE *fd = fopen(filename, "r"); diff --git a/src/cli/common.h b/src/cli/common.h @@ -20,6 +20,9 @@ extern struct Format *supercop_formats; long fremaining(FILE *fd); struct KeyPair *readKeyFile(const char *filename); +// Find a registered key format by name (0x00, asc, hdr); NULL if unknown +struct Format *supercop_find_format(const char *name); + // Message source: -m string wins, then -M file, then stdin FILE *supercop_open_message(const char *message, const char *messageFile); diff --git a/src/fmt/asc/armor.c b/src/fmt/asc/armor.c @@ -0,0 +1,44 @@ +#include <stdio.h> +#include <string.h> + +#include "armor.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int asc_find_block(const unsigned char *buf, size_t len, const char *label, + const unsigned char **payload, size_t *payloadlen) { + char begin[64], end[64]; + size_t blen, elen, i, pend; + int n; + + if (!buf || !label) return 0; + n = snprintf(begin, sizeof(begin), "-----BEGIN %s-----", label); + if (n < 0 || (size_t)n >= sizeof(begin)) return 0; + n = snprintf(end, sizeof(end), "-----END %s-----", label); + if (n < 0 || (size_t)n >= sizeof(end)) return 0; + blen = strlen(begin); + elen = strlen(end); + if (len < blen) return 0; + + for (i = 0; i + blen <= len; i++) { + size_t pstart; + if (memcmp(buf + i, begin, blen) != 0) continue; + pstart = i + blen; + for (pend = pstart; pend + elen <= len; pend++) { + if (memcmp(buf + pend, end, elen) == 0) { + if (payload) *payload = buf + pstart; + if (payloadlen) *payloadlen = pend - pstart; + return 1; + } + } + // Begin marker without an end: no valid block + return 0; + } + return 0; +} + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/src/fmt/asc/armor.h b/src/fmt/asc/armor.h @@ -0,0 +1,36 @@ +// Shared ascii-armor block locating for the asc format. +// +// A block looks like: +// +// -----BEGIN <label>----- +// <base64 payload, 64-col wrapped> +// -----END <label>----- +// +// All scanning is bounded by the buffer length, never assuming +// NUL-termination. + +#ifndef __SUPERCOP_FMT_ASC_ARMOR_H__ +#define __SUPERCOP_FMT_ASC_ARMOR_H__ + +#include <stddef.h> + +#ifdef __cplusplus +extern "C" { +#endif + +// Namespaced labels: bare "PRIVATE KEY"/"PUBLIC KEY" would invite openssl +// to misparse our raw payload as DER/PKCS#8. +#define ASC_LABEL_PRIVATE "SUPERCOP PRIVATE KEY" +#define ASC_LABEL_PUBLIC "SUPERCOP PUBLIC KEY" + +// Locate the first block with the given label. On success sets +// *payload/*payloadlen to the bytes between the markers and returns 1, +// otherwise returns 0. payload/payloadlen may be NULL to just test presence. +int asc_find_block(const unsigned char *buf, size_t len, const char *label, + const unsigned char **payload, size_t *payloadlen); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // __SUPERCOP_FMT_ASC_ARMOR_H__ diff --git a/src/fmt/asc/b64.c b/src/fmt/asc/b64.c @@ -0,0 +1,113 @@ +#include <stdlib.h> + +#include "b64.h" + +#ifdef __cplusplus +extern "C" { +#endif + +static const char asc_b64_alphabet[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +char *asc_b64_encode(const unsigned char *in, size_t len, size_t *outlen) { + size_t rawlen, full_lines, i, o, raw; + char *out; + unsigned int n; + + if (!in || !outlen || len == 0) return NULL; + // 4 chars per 3 bytes, plus '\n' per 64-char line, plus trailing '\n' + rawlen = ((len + 2) / 3) * 4; + full_lines = (rawlen + 63) / 64; + out = malloc(rawlen + full_lines + 1); + if (!out) return NULL; + + o = 0; + raw = 0; + for (i = 0; i < len; i += 3) { + size_t rem = len - i; + n = (unsigned int)in[i] << 16; + if (rem > 1) n |= (unsigned int)in[i + 1] << 8; + if (rem > 2) n |= in[i + 2]; + out[o++] = asc_b64_alphabet[(n >> 18) & 0x3F]; + out[o++] = asc_b64_alphabet[(n >> 12) & 0x3F]; + out[o++] = (rem > 1) ? asc_b64_alphabet[(n >> 6) & 0x3F] : '='; + out[o++] = (rem > 2) ? asc_b64_alphabet[n & 0x3F] : '='; + raw += 4; + if (raw % 64 == 0) out[o++] = '\n'; + } + if (out[o - 1] != '\n') out[o++] = '\n'; + out[o] = '\0'; + *outlen = o; + return out; +} + +static int asc_b64_value(unsigned char c) { + if (c >= 'A' && c <= 'Z') return c - 'A'; + if (c >= 'a' && c <= 'z') return c - 'a' + 26; + if (c >= '0' && c <= '9') return c - '0' + 52; + if (c == '+') return 62; + if (c == '/') return 63; + return -1; +} + +static int asc_b64_is_ws(unsigned char c) { + return c == ' ' || c == '\t' || c == '\r' || c == '\n'; +} + +unsigned char *asc_b64_decode(const unsigned char *in, size_t len, size_t *outlen) { + size_t cleanlen = 0, i, o; + unsigned char *clean, *out; + unsigned int n; + int a, b, c, d; + + if (!in || !outlen || len == 0) return NULL; + clean = malloc(len); + if (!clean) return NULL; + for (i = 0; i < len; i++) { + if (asc_b64_is_ws(in[i])) continue; + if (in[i] == '=' || asc_b64_value(in[i]) >= 0) { + clean[cleanlen++] = in[i]; + } else { + free(clean); + return NULL; + } + } + // Input must arrive in complete 4-char quanta + if (cleanlen == 0 || cleanlen % 4 != 0) { + free(clean); + return NULL; + } + out = malloc((cleanlen / 4) * 3); + if (!out) { + free(clean); + return NULL; + } + o = 0; + for (i = 0; i < cleanlen; i += 4) { + a = asc_b64_value(clean[i]); + b = asc_b64_value(clean[i + 1]); + c = (clean[i + 2] == '=') ? 0 : asc_b64_value(clean[i + 2]); + d = (clean[i + 3] == '=') ? 0 : asc_b64_value(clean[i + 3]); + // Padding only valid as the last quantum's tail + if (a < 0 || b < 0 || c < 0 || d < 0 || + clean[i] == '=' || clean[i + 1] == '=' || + (clean[i + 2] == '=' && clean[i + 3] != '=') || + ((clean[i + 2] == '=' || clean[i + 3] == '=') && i + 4 != cleanlen)) { + free(clean); + free(out); + return NULL; + } + n = ((unsigned int)a << 18) | ((unsigned int)b << 12) | + ((unsigned int)c << 6) | (unsigned int)d; + out[o++] = (n >> 16) & 0xFF; + if (clean[i + 2] != '=') out[o++] = (n >> 8) & 0xFF; + if (clean[i + 3] != '=') out[o++] = n & 0xFF; + } + free(clean); + *outlen = o; + return out; +} + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/src/fmt/asc/b64.h b/src/fmt/asc/b64.h @@ -0,0 +1,25 @@ +// Minimal internal base64 (RFC 4648 alphabet), just enough for the asc armor. +// No external dependency; operates on length-bounded buffers. + +#ifndef __SUPERCOP_FMT_ASC_B64_H__ +#define __SUPERCOP_FMT_ASC_B64_H__ + +#include <stddef.h> + +#ifdef __cplusplus +extern "C" { +#endif + +// Encodes in, wrapping lines at 64 columns with '\n', trailing '\n' included. +// Returns malloc'd buffer with *outlen set, or NULL on failure/empty input. +char *asc_b64_encode(const unsigned char *in, size_t len, size_t *outlen); + +// Decodes in, skipping whitespace (space/tab/CR/LF). Expects correct '=' +// padding. Returns malloc'd buffer with *outlen set, or NULL on failure. +unsigned char *asc_b64_decode(const unsigned char *in, size_t len, size_t *outlen); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // __SUPERCOP_FMT_ASC_B64_H__ diff --git a/src/fmt/asc/common.h b/src/fmt/asc/common.h @@ -0,0 +1,11 @@ +#include "../common.h" +#include "../../keypair/keypair.h" + +#ifndef __SUPERCOP_FMT_ASC_COMMON_H__ +#define __SUPERCOP_FMT_ASC_COMMON_H__ + +char fmt_asc_detect(unsigned char *cipherdata, size_t len); +char * fmt_asc_encode(struct KeyPair *keypair, size_t *len); +struct KeyPair * fmt_asc_decode(unsigned char *cipherdata, size_t len); + +#endif // __SUPERCOP_FMT_ASC_COMMON_H__ diff --git a/src/fmt/asc/decode.c b/src/fmt/asc/decode.c @@ -0,0 +1,94 @@ +#include <stdlib.h> +#include <string.h> + +#include "common.h" +#include "armor.h" +#include "b64.h" +#include "orlp/ed25519.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Decode one armor block into malloc'd raw bytes; NULL unless the block +// exists, is valid base64, and decodes to exactly wantlen bytes. +static unsigned char *asc_block_bytes(unsigned char *cipherdata, size_t len, + const char *label, size_t wantlen) { + const unsigned char *payload; + size_t payloadlen; + unsigned char *raw; + size_t rawlen; + if (!asc_find_block(cipherdata, len, label, &payload, &payloadlen)) return NULL; + raw = asc_b64_decode(payload, payloadlen, &rawlen); + if (!raw || rawlen != wantlen) { + if (raw) free(raw); + return NULL; + } + return raw; +} + +static struct KeyPair *asc_pair_alloc(void) { + struct KeyPair *kp = calloc(1, sizeof(struct KeyPair)); + if (!kp) return NULL; + kp->public_key = calloc(1, 32); + if (!kp->public_key) { + free(kp); + return NULL; + } + return kp; +} + +struct KeyPair * fmt_asc_decode(unsigned char *cipherdata, size_t len) { + unsigned char *privraw; + unsigned char *pubraw; + struct KeyPair *kp; + + // A PRIVATE block is authoritative for full keys: present-but-corrupt + // fails closed instead of silently downgrading to pub-only. + if (asc_find_block(cipherdata, len, ASC_LABEL_PRIVATE, NULL, NULL)) { + privraw = asc_block_bytes(cipherdata, len, ASC_LABEL_PRIVATE, 64); + if (!privraw) return NULL; + kp = asc_pair_alloc(); + if (!kp) { + free(privraw); + return NULL; + } + kp->private_key = calloc(1, 64); + if (!kp->private_key) { + free(privraw); + free(kp->public_key); + free(kp); + return NULL; + } + memcpy(kp->private_key, privraw, 64); + if (privraw) free(privraw); + ed25519_derive_pubkey(kp->public_key, kp->private_key); + // An accompanying PUBLIC block must agree when present + pubraw = asc_block_bytes(cipherdata, len, ASC_LABEL_PUBLIC, 32); + if (pubraw) { + int ok = memcmp(pubraw, kp->public_key, 32) == 0; + free(pubraw); + if (!ok) { + keypair_free(kp); + return NULL; + } + } + return kp; + } + + // Lone PUBLIC block (32 public bytes) decodes as a pub-only pair + pubraw = asc_block_bytes(cipherdata, len, ASC_LABEL_PUBLIC, 32); + if (!pubraw) return NULL; + kp = asc_pair_alloc(); + if (!kp) { + free(pubraw); + return NULL; + } + memcpy(kp->public_key, pubraw, 32); + if (pubraw) free(pubraw); + return kp; +} + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/src/fmt/asc/detect.c b/src/fmt/asc/detect.c @@ -0,0 +1,22 @@ +#include <stddef.h> +#include <string.h> + +#include "common.h" +#include "armor.h" + +#ifdef __cplusplus +extern "C" { +#endif + +char fmt_asc_detect(unsigned char *cipherdata, size_t len) { + // Stream-friendly: either armor block may start on any line. + // Claiming is cheap; decode validates and returns NULL on garbage, + // letting readKeyFile move on to the next format. + if (asc_find_block(cipherdata, len, ASC_LABEL_PRIVATE, NULL, NULL)) return 1; + if (asc_find_block(cipherdata, len, ASC_LABEL_PUBLIC, NULL, NULL)) return 1; + return 0; +} + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/src/fmt/asc/encode.c b/src/fmt/asc/encode.c @@ -0,0 +1,74 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "common.h" +#include "armor.h" +#include "b64.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Wrap raw bytes in an ascii-armor block; returns malloc'd NUL-terminated text. +static char *asc_wrap(const char *label, const unsigned char *raw, size_t rawlen, + size_t *outlen) { + char *b64, *out; + size_t b64len, need; + int n; + + b64 = asc_b64_encode(raw, rawlen, &b64len); + if (!b64) return NULL; + // "-----BEGIN %s-----\n" + b64 + "-----END %s-----\n" + NUL + need = strlen("-----BEGIN -----\n-----END -----\n") + 2 * strlen(label) + b64len + 1; + out = malloc(need); + if (!out) { + free(b64); + return NULL; + } + n = snprintf(out, need, "-----BEGIN %s-----\n%s-----END %s-----\n", + label, b64, label); + free(b64); + if (n < 0 || (size_t)n >= need) { + free(out); + return NULL; + } + *outlen = (size_t)n; + return out; +} + +char * fmt_asc_encode(struct KeyPair *kp, size_t *len) { + char *priv = NULL, *pub = NULL, *out; + size_t privlen = 0, publen = 0; + + if (!kp || !kp->public_key || !len) return NULL; + // Armor carries just the key bytes, no prefix. A full pair emits both a + // PRIVATE block (64 private bytes) and a PUBLIC block (32 public bytes); + // a pub-only pair emits just the PUBLIC block. + if (kp->private_key) { + priv = asc_wrap(ASC_LABEL_PRIVATE, kp->private_key, 64, &privlen); + if (!priv) return NULL; + } + pub = asc_wrap(ASC_LABEL_PUBLIC, kp->public_key, 32, &publen); + if (!pub) { + if (priv) free(priv); + return NULL; + } + out = malloc(privlen + publen + 1); + if (!out) { + if (priv) free(priv); + if (pub) free(pub); + return NULL; + } + memcpy(out, priv, privlen); + memcpy(out + privlen, pub, publen); + out[privlen + publen] = '\0'; + free(priv); + free(pub); + *len = privlen + publen; + return out; +} + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/src/fmt/asc/register.c b/src/fmt/asc/register.c @@ -0,0 +1,30 @@ +#include <stdlib.h> +#include <string.h> + +#include "common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern struct Format *supercop_formats; + +void __attribute__ ((constructor)) fmt_asc_register() { + struct Format *fmt = calloc(1, sizeof(struct Format)); + if (!fmt) return; + fmt->next = supercop_formats; + fmt->name = calloc(1, 4); + if (!fmt->name) { + free(fmt); + return; + } + fmt->detect = fmt_asc_detect; + fmt->encode = fmt_asc_encode; + fmt->decode = fmt_asc_decode; + supercop_formats = fmt; + strcpy(fmt->name, "asc"); +} + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/src/fmt/hdr/common.h b/src/fmt/hdr/common.h @@ -0,0 +1,11 @@ +#include "../common.h" +#include "../../keypair/keypair.h" + +#ifndef __SUPERCOP_FMT_HDR_COMMON_H__ +#define __SUPERCOP_FMT_HDR_COMMON_H__ + +char fmt_hdr_detect(unsigned char *cipherdata, size_t len); +char * fmt_hdr_encode(struct KeyPair *keypair, size_t *len); +struct KeyPair * fmt_hdr_decode(unsigned char *cipherdata, size_t len); + +#endif // __SUPERCOP_FMT_HDR_COMMON_H__ diff --git a/src/fmt/hdr/decode.c b/src/fmt/hdr/decode.c @@ -0,0 +1,102 @@ +#include <stdlib.h> +#include <string.h> + +#include "common.h" +#include "scan.h" +#include "orlp/ed25519.h" + +#ifdef __cplusplus +extern "C" { +#endif + +static int hdr_hexval(unsigned char c) { + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + return -1; +} + +// Parse exactly hexlen hex chars into out; returns 1 on success. +static int hdr_parse_hex(const unsigned char *val, size_t vallen, + unsigned char *out, size_t hexlen) { + size_t i; + int hi, lo; + if (vallen != hexlen * 2) return 0; + for (i = 0; i < hexlen; i++) { + hi = hdr_hexval(val[i * 2]); + lo = hdr_hexval(val[i * 2 + 1]); + if (hi < 0 || lo < 0) return 0; + out[i] = (unsigned char)((hi << 4) | lo); + } + return 1; +} + +struct KeyPair * fmt_hdr_decode(unsigned char *cipherdata, size_t len) { + const unsigned char *pos = cipherdata; + const unsigned char *end = cipherdata + len; + const unsigned char *line; + size_t linelen; + int which; + const unsigned char *val; + size_t vallen; + unsigned char pub[32]; + unsigned char priv[64]; + int seen_pub = 0, have_pub = 0; + int seen_priv = 0, have_priv = 0; + struct KeyPair *kp; + + // First valid occurrence of each label wins; malformed hex runs are + // ignored so a later valid block in the stream can still win. + while (hdr_next_line(&pos, end, &line, &linelen)) { + if (!hdr_match_label(line, linelen, &which, &val, &vallen)) continue; + if (which == 1 && seen_pub) continue; + if (which == 2 && seen_priv) continue; + if (vallen == 0) continue; + // Parenthesized value marks the field absent (e.g. "(no private key)") + if (val[0] == '(') { + if (which == 1) seen_pub = 1; + else seen_priv = 1; + continue; + } + if (which == 1) { + if (!hdr_parse_hex(val, vallen, pub, 32)) continue; + seen_pub = 1; + have_pub = 1; + } else { + if (!hdr_parse_hex(val, vallen, priv, 64)) continue; + seen_priv = 1; + have_priv = 1; + } + } + + if (!have_pub && !have_priv) return NULL; + + // A lone private key still yields its public half via scalar mult + if (!have_pub) { + ed25519_derive_pubkey(pub, priv); + have_pub = 1; + } + + kp = calloc(1, sizeof(struct KeyPair)); + if (!kp) return NULL; + kp->public_key = calloc(1, 32); + if (!kp->public_key) { + free(kp); + return NULL; + } + memcpy(kp->public_key, pub, 32); + if (have_priv) { + kp->private_key = calloc(1, 64); + if (!kp->private_key) { + free(kp->public_key); + free(kp); + return NULL; + } + memcpy(kp->private_key, priv, 64); + } + return kp; +} + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/src/fmt/hdr/detect.c b/src/fmt/hdr/detect.c @@ -0,0 +1,38 @@ +#include <stddef.h> + +#include "common.h" +#include "scan.h" + +#ifdef __cplusplus +extern "C" { +#endif + +static int hdr_is_hex(unsigned char c) { + return (c >= '0' && c <= '9') || + (c >= 'a' && c <= 'f') || + (c >= 'A' && c <= 'F'); +} + +char fmt_hdr_detect(unsigned char *cipherdata, size_t len) { + const unsigned char *pos = cipherdata; + const unsigned char *end = cipherdata + len; + const unsigned char *line; + size_t linelen; + int which; + const unsigned char *val; + size_t vallen; + + // Claim the buffer when any key-labeled line with a non-empty value + // appears anywhere in it; surrounding stream content is irrelevant. + while (hdr_next_line(&pos, end, &line, &linelen)) { + if (!hdr_match_label(line, linelen, &which, &val, &vallen)) continue; + if (vallen == 0) continue; + // A hex run or a parenthesized absent-marker both count as values + if (val[0] == '(' || hdr_is_hex(val[0])) return 1; + } + return 0; +} + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/src/fmt/hdr/encode.c b/src/fmt/hdr/encode.c @@ -0,0 +1,52 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +static void hdr_hex_encode(char *dst, const unsigned char *src, size_t n) { + static const char digits[] = "0123456789abcdef"; + size_t i; + for (i = 0; i < n; i++) { + dst[i * 2] = digits[(src[i] >> 4) & 0xF]; + dst[i * 2 + 1] = digits[src[i] & 0xF]; + } + dst[n * 2] = '\0'; +} + +char * fmt_hdr_encode(struct KeyPair *kp, size_t *len) { + char pubhex[65]; + char privhex[129]; + const char *privstr; + char *out; + size_t need; + int n; + + if (!kp || !kp->public_key || !len) return NULL; + hdr_hex_encode(pubhex, kp->public_key, 32); + if (kp->private_key) { + hdr_hex_encode(privhex, kp->private_key, 64); + privstr = privhex; + } else { + privstr = "(no private key)"; + } + + need = strlen("public-key: \nprivate-key: \n") + 64 + strlen(privstr) + 1; + out = malloc(need); + if (!out) return NULL; + n = snprintf(out, need, "public-key: %s\nprivate-key: %s\n", pubhex, privstr); + if (n < 0 || (size_t)n >= need) { + free(out); + return NULL; + } + *len = (size_t)n; + return out; +} + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/src/fmt/hdr/register.c b/src/fmt/hdr/register.c @@ -0,0 +1,30 @@ +#include <stdlib.h> +#include <string.h> + +#include "common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern struct Format *supercop_formats; + +void __attribute__ ((constructor)) fmt_hdr_register() { + struct Format *fmt = calloc(1, sizeof(struct Format)); + if (!fmt) return; + fmt->next = supercop_formats; + fmt->name = calloc(1, 4); + if (!fmt->name) { + free(fmt); + return; + } + fmt->detect = fmt_hdr_detect; + fmt->encode = fmt_hdr_encode; + fmt->decode = fmt_hdr_decode; + supercop_formats = fmt; + strcpy(fmt->name, "hdr"); +} + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/src/fmt/hdr/scan.c b/src/fmt/hdr/scan.c @@ -0,0 +1,77 @@ +#include <stddef.h> + +#include "scan.h" + +#ifdef __cplusplus +extern "C" { +#endif + +static int hdr_is_ws(unsigned char c) { + return c == ' ' || c == '\t' || c == '\r'; +} + +static int hdr_lower(unsigned char c) { + if (c >= 'A' && c <= 'Z') return c + ('a' - 'A'); + return c; +} + +// Case-insensitive literal match, bounded by remaining length. +static int hdr_match_word(const unsigned char *p, size_t left, const char *word) { + size_t i = 0; + while (word[i]) { + if (i >= left) return 0; + if (hdr_lower(p[i]) != (unsigned char)word[i]) return 0; + i++; + } + return 1; +} + +int hdr_match_label(const unsigned char *line, size_t linelen, int *which, + const unsigned char **val, size_t *vallen) { + size_t i = 0; + int w = 0; + size_t wlen = 0; + + while (i < linelen && hdr_is_ws(line[i])) i++; + + if (hdr_match_word(line + i, linelen - i, "public-key")) { + w = 1; + wlen = 10; + } else if (hdr_match_word(line + i, linelen - i, "private-key")) { + w = 2; + wlen = 11; + } else { + return 0; + } + i += wlen; + + while (i < linelen && hdr_is_ws(line[i])) i++; + if (i >= linelen || line[i] != ':') return 0; + i++; + + while (i < linelen && hdr_is_ws(line[i])) i++; + + *which = w; + *val = line + i; + *vallen = linelen - i; + while (*vallen > 0 && hdr_is_ws((*val)[*vallen - 1])) (*vallen)--; + return 1; +} + +int hdr_next_line(const unsigned char **pos, const unsigned char *end, + const unsigned char **line, size_t *linelen) { + const unsigned char *nl; + if (*pos >= end) return 0; + *line = *pos; + nl = *pos; + while (nl < end && *nl != '\n') nl++; + *linelen = (size_t)(nl - *pos); + // Strip a trailing carriage return so CRLF streams parse cleanly + if (*linelen > 0 && (*pos)[*linelen - 1] == '\r') (*linelen)--; + *pos = (nl < end) ? nl + 1 : end; + return 1; +} + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/src/fmt/hdr/scan.h b/src/fmt/hdr/scan.h @@ -0,0 +1,34 @@ +// Internal line scanner shared by the hdr detect/decode implementations. +// +// A key line looks like: +// +// [spaces/tabs] label [spaces/tabs] : [spaces/tabs] value [spaces/tabs] +// +// where label is public-key or private-key (case-insensitive). Matching is +// done against the length-bounded buffer, never assuming NUL-termination. + +#ifndef __SUPERCOP_FMT_HDR_SCAN_H__ +#define __SUPERCOP_FMT_HDR_SCAN_H__ + +#include <stddef.h> + +#ifdef __cplusplus +extern "C" { +#endif + +// which: 1 = public-key, 2 = private-key +// Returns 1 and fills which/val/vallen when line holds a key label, +// 0 otherwise. val/vallen may point at an empty value; callers decide. +int hdr_match_label(const unsigned char *line, size_t linelen, int *which, + const unsigned char **val, size_t *vallen); + +// Advance helpers for walking a length-bounded buffer line by line. +// Returns 1 while a line (without its '\n') is available. +int hdr_next_line(const unsigned char **pos, const unsigned char *end, + const unsigned char **line, size_t *linelen); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // __SUPERCOP_FMT_HDR_SCAN_H__