From 0c2216d1e0dc8565a6bf61c9572e47bb1ae1c1fb Mon Sep 17 00:00:00 2001 From: Nathan Wagner Date: Sat, 6 Jul 2019 01:54:11 +0000 Subject: [PATCH] package signature work --- Makefile | 7 +- crypto/libeddsa/sign.c | 1835 +++++++++++++++++++++++++++++++++------- doc/zpm-sign.8 | 104 ++- t/sign.t | 21 +- 4 files changed, 1638 insertions(+), 329 deletions(-) diff --git a/Makefile b/Makefile index d2acd75..cd2ce62 100644 --- a/Makefile +++ b/Makefile @@ -802,10 +802,13 @@ crypto/libeddsa/lib/sc.c \ crypto/libeddsa/lib/sha512.c \ crypto/libeddsa/lib/x25519.c -SIGNOBJ=$(SIGNSRC:.c=.o) +SIGNOBJ=$(SIGNSRC:.c=.o) lib/readpass.o lib/blake2/ref/blake2b-ref.o \ + crypto/chacha.o $(SIGNOBJ): CFLAGS=-Wall -Wextra -W -Werror -Wno-pointer-sign \ - -Icrypto/libeddsa/lib -Wno-unused-command-line-argument + -Wno-missing-braces \ + -Icrypto/libeddsa/lib -Wno-unused-command-line-argument \ + -I. zpm-sign: $(SIGNOBJ) $(CC) -Wall -Wextra -W -Werror -Wno-unused-command-line-argument -static -o $@ $+ diff --git a/crypto/libeddsa/sign.c b/crypto/libeddsa/sign.c index 723c1f0..593f993 100644 --- a/crypto/libeddsa/sign.c +++ b/crypto/libeddsa/sign.c @@ -22,9 +22,12 @@ * -h message is hex encoded */ +/* chacha20 encryption: 256 bit key = 32 bytes */ + #include #include #include +#include #include #include #include @@ -37,22 +40,468 @@ #include "eddsa.h" #include "sha512.h" +#include "crypto/chacha.h" +#include "lib/blake2/ref/blake2.h" + +char *readpass(char *prompt); #define VERIFY 1 #define SIGN 2 -#define GENKEY 3 +#define KEYGEN 3 #define EXTRACT 4 +#define KEYSIGN 5 +#define LIST 6 +#define ENCRYPT 7 +#define DECRYPT 8 +#define SIGNKEY 9 +#define IMPORT 10 -//#define MARK do { fprintf(stderr, "%s %s:%d\n", __FILE__, __func__, __LINE__); } while (0) +/* output flags */ +#define RAW 0x1 +#define HUMANREADABLE 0x2 -static void hexdump(void *src, size_t len) { - unsigned char *b = src; - while (len--) { - printf("%02x", *b++); +#define SECRET_KEY 1 +#define PUBLIC_KEY 2 +#define SIGNATURE 3 +#define UNKNOWN 4 + +#define VERSION 1 + +/* possible additional types: + * designated revoker + * revocation certificate + */ + +/* timestamps are 8 byte little endian integers of seconds since + * the epoch. or unix time, or some such. Times are a mess. + * Unix time is leap second unaware, and ambiguous during leap seconds. + * UTC requires a database of leap seconds for past time, and the + * specified second in the future possibly changes. + * TAI is good, but currently 37 seconds behind UTC, which is just weird + */ + +/* size of structs would have to be increased for 57/64 byte keys + * and key*2 size signatures + */ + +/* need 25 more bytes for ed448 */ +struct secret_key { + char magic[4]; /* "ZPMS" */ + uint8_t version; /* 0x01 */ + uint8_t type; /* 0x01 */ + uint8_t encryption; /* 0x01 == chacha */ + uint8_t reserved1; /* MBZ */ + char key[32]; /* private key data */ + int64_t created; /* time in unix epoch seconds, little endian */ + int64_t expires; /* time in unix epoch seconds, little endian, = is none, could do uint64max */ + char salt[8]; /* password salt, nist wants 16, but we're not using nist approved hash algorithms anyway. */ + char reserved2[64]; /* could put the pubkey here could have 16 bytes of IV for chacha to encrypt. */ + char id[120]; /* 1 byte len, id bytes, zero bytes remainder */ + char trailer[8]; /* room for a pointer */ +}; + +struct public_key { + char magic[4]; /* "ZPMS" */ + uint8_t version; /* 0x01 */ + uint8_t type; /* 0x02 */ + uint8_t reserved0; /* MBZ */ + uint8_t reserved1; /* MBZ */ + char key[32]; /* public key data */ + int64_t created; /* time in unix epoch seconds, little endian */ + int64_t expires; /* time in unix epoch seconds, little endian */ + char reserved2[8]; /* MBZ */ + char signature[64]; /* self sig data */ + char id[120]; /* 1 byte len, id bytes, zero bytes remainder */ + char trailer[8]; /* room for a pointer */ +}; + +struct signature { + char magic[4]; /* "ZPMS" */ + uint8_t version; /* 0x01 */ + uint8_t type; /* 0x03 */ + uint8_t reserved0; /* MBZ */ /* trust level? revokable? */ + uint8_t reserved1; /* MBZ */ + char key[32]; /* signing public key data?, can be zero */ + int64_t signtime; /* time in unix epoch seconds, little endian */ + int64_t expires; /* time in unix epoch seconds, little endian */ + char reserved2[8]; /* MBZ */ + char signature[64]; /* sig data */ + char reserved3[120]; /* some indication of what you're signing ? */ + /* 512 bits is 64 bytes, could put the hash of the data signed */ + char trailer[8]; /* room for a pointer */ +}; + +struct keysig { + char magic[4]; /* "ZPMS" */ + uint8_t version; /* 0x01 */ + uint8_t type; + uint8_t encryption; /* trust level for pk? revokable for sig? */ + uint8_t reserved1; /* MBZ */ + char key[32]; /* signing public key data?, can be zero */ + /* should these be signed? */ + int64_t created; /* time in unix epoch seconds */ + int64_t expires; /* time in unix epoch seconds */ + char salt[8]; /* reserved for pk and sig, could be IV for sig */ + char signature[64]; /* sig data, reserved for sk */ + char id[120]; /* signing hash for sig? otherwise reserved for sig */ + /* 512 bits is 64 bytes, could put the hash of the data signed */ + char trailer[8]; /* room for a pointer */ +}; + +struct signing_context { + char *message; + size_t mlen; + + char *keyfile, *keystring, *keyid, *keyprefix, *passphrase; + unsigned char passkey[32]; +}; + +void putsle8(unsigned char *dst, int64_t inval) { + int i; + union { uint64_t out; int64_t in; } uv; + uint64_t val; + + uv.in = inval; + val = uv.out; + + for (i=0; i<8; i++) { + dst[i] = val & 0xff; + val >>= 8; + } +} + +int64_t getsle8(unsigned char *dst) { + uint64_t val = 0; + int i; + union { int64_t out; uint64_t in; } uv; + + for (i=0; i<8; i++) { + val += (uint64_t)dst[i] << (8*i); + } + + uv.in = val; + + return uv.out; +} + +void seckey_bytea(unsigned char *bytes, struct secret_key *sk) { + memset(bytes, 0, 256); + memcpy(bytes, "ZPMS", 4); + bytes[4] = sk->version; + bytes[5] = sk->type; + bytes[6] = sk->encryption; + /* 7 reserved */ + memcpy(bytes + 8, sk->key, 32); + putsle8(bytes+40, sk->created); + putsle8(bytes+48, sk->expires); + /* @56 8 salt, not implemented */ + /* memcpy(bytes + 64, sk->reserved2, 64); should probably put the pk */ + memcpy(bytes + 128, sk->id, 120); + /* 8 reserved */ +} + +void bytea_seckey(struct secret_key *sk, unsigned char *bytes) { + memcpy(sk->magic, bytes, 4); + sk->version = bytes[4]; + sk->type = bytes[5]; + sk->encryption = bytes[6]; + sk->reserved1 = 0; + memcpy(sk->key, bytes + 8, 32); + sk->created = getsle8(bytes+40); + sk->expires = getsle8(bytes+48); + memset(sk->salt, 0, 8); /* salt not implemented */ + memset(sk->reserved2, 0, 64); /* salt not implemented */ + memcpy(sk->id, bytes + 128, 120); + memset(sk->trailer, 0, 8); +} + +int create_key(struct secret_key *sk, int generate) { + memcpy(sk->magic, "ZPMS", 4); + sk->version = VERSION; + sk->type = SECRET_KEY; + sk->encryption = 0; + sk->reserved1 = 0; + + if (generate) { + /* a private key is just 32 random bytes */ + getrandom(sk->key, sizeof sk->key, 0); } - printf("\n"); + + sk->created = time(NULL); + sk->expires = INT64_MAX; + memset(sk->salt, 0, 8); + memset(sk->reserved2, 0, 64); + memset(sk->id, 0, 120); + memset(sk->trailer, 0, 8); + + ed25519_genpub(sk->reserved2, sk->key); + return 1; } +/* hash password into 32 bytes for chacha encryption */ +int collect_key(unsigned char *key, const char *pass) { + struct blake2b_state__ h; + if (!pass) { + pass = readpass("Passphrase: "); + } + + if (!pass) { + return 0; + } + + blake2b_init(&h, 32); + blake2b_update(&h, pass, strlen(pass)); + blake2b_final(&h, key, 32); + return 1; +} + +int encrypt_key(struct secret_key *sk, unsigned char *key) { + int i; + + if (sk->encryption) { + return 1; + } + + /* TODO chacha encrypt sk->key */ + for (i=0; i < 32; i++) { + sk->key[i] ^= key[i]; + } + + sk->encryption = 1; + return 1; +} + +int encrypt_sk(struct secret_key *sk, unsigned char *pass) { + unsigned char key[32]; + + if (sk->encryption == 1) { + return 1; + } + + collect_key(key, pass); + encrypt_key(sk, key); + + memset(key, 0, 32); + memset(pass, 0, strlen(pass)); + + return 1; +} + +int decrypt_sk(struct secret_key *sk, unsigned char *pass) { + unsigned char key[32]; + int i; + + if (sk->encryption == 0) { + return 1; + } + + collect_key(key, pass); + + for (i=0; i < 32; i++) { + sk->key[i] ^= key[i]; + } + + memset(key, 0, 32); + memset(pass, 0, strlen(pass)); + + sk->encryption = 0; + + return 1; +} + +int decrypt_key(struct secret_key *sk, unsigned char *key) { + int i; + + if (sk->encryption == 0) { + return 1; + } + + /* TODO chacha decrypt sk->key */ + for (i=0; i < 32; i++) { + sk->key[i] ^= key[i]; + } + + sk->encryption = 0; + return 1; +} + +void pubkey_init(struct public_key *pk) { + memcpy(pk->magic, "ZPMS", 4); + pk->version = VERSION; + pk->type = PUBLIC_KEY; + pk->reserved0 = 0; + pk->reserved1 = 0; + memset(pk->key, 0, sizeof pk->key); + pk->created = 0; + pk->expires = 0; + memset(pk->reserved2, 0, 8); + memset(pk->signature, 0, 64); + memset(pk->id, 0, 120); + memset(pk->trailer, 0, 8); +} + +void pubkey_bytea(unsigned char *bytes, struct public_key *pk) { + memset(bytes, 0, 256); + memcpy(bytes, "ZPMS", 4); + bytes[4] = pk->version; + bytes[5] = pk->type; + /* 6 7 reserved */ + memcpy(bytes + 8, pk->key, 32); + putsle8(bytes+40, pk->created); + putsle8(bytes+48, pk->expires); + /* @56 8 reserved */ + memcpy(bytes + 64, pk->signature, 64); + memcpy(bytes + 128, pk->id, 120); + /* 8 reserved */ +} + +void bytea_pubkey(struct public_key *pk, unsigned char *bytes) { + memcpy(pk->magic, bytes, 4); + pk->version = bytes[4]; + pk->type = bytes[5]; + pk->reserved0 = 0; + pk->reserved1 = 0; + memcpy(pk->key, bytes + 8, 32); + pk->created = getsle8(bytes+40); + pk->expires = getsle8(bytes+48); + memset(pk->reserved2, 0, 8); + memcpy(pk->signature, bytes + 64, 64); + memcpy(pk->id, bytes + 128, 120); + memset(pk->trailer, 0, 8); +} + +int derive_pubkey(struct public_key *pk, struct secret_key *sk) { + char bytes[256]; + + memcpy(pk->magic, "ZPMS", 4); + pk->version = VERSION; + pk->type = PUBLIC_KEY; + pk->reserved0 = 0; + pk->reserved1 = 0; + ed25519_genpub(pk->key, sk->key); + pk->created = sk->created; + pk->expires = sk->expires; + memset(pk->reserved2, 0, 8); + memset(pk->signature, 0, 64); + memset(pk->id, 0, 120); + strncpy(pk->id, sk->id, 119); + memset(pk->trailer, 0, 8); + + /* serialize and sign */ + pubkey_bytea(bytes, pk); + ed25519_sign(pk->signature, sk->key, pk->key, bytes, 256); + + return 1; +} + +void bytea_signature(struct signature *sig, unsigned char *bytes) { + memcpy(sig->magic, bytes, 4); + sig->version = bytes[4]; + sig->type = bytes[5]; + sig->reserved0 = 0; + sig->reserved1 = 0; + memcpy(sig->key, bytes + 8, 32); + sig->signtime = getsle8(bytes+40); + sig->expires = getsle8(bytes+48); + memset(sig->reserved2, 0, 8); + memcpy(sig->signature, bytes + 64, 64); + memcpy(sig->reserved3, bytes + 128, 120); + memset(sig->trailer, 0, 8); +} + +void signature_bytea(unsigned char *bytes, struct signature *sig) { + memset(bytes, 0, 256); + memcpy(bytes, "ZPMS", 4); + bytes[4] = sig->version; + bytes[5] = sig->type; + /* 6 7 reserved */ + memcpy(bytes + 8, sig->key, 32); + putsle8(bytes+40, sig->signtime); + putsle8(bytes+48, sig->expires); + /* @56 8 reserved */ + memcpy(bytes + 64, sig->signature, 64); + /* 120 reserved */ + /* 8 reserved */ +} + +union signobject { + struct signature sig; + struct public_key pk; + struct secret_key sk; + char bytes[256]; +}; + +int object_type(union signobject *obj) { + return obj ? obj->sk.type : 0; +} + +void sighash(unsigned char *dst, struct signature *sig, unsigned char *data, + size_t len) { + unsigned char signtime[8], expires[8]; + struct sha512 hash; + + /* need to put these in as little endian */ + putsle8(signtime, sig->signtime); + putsle8(expires, sig->expires); + + sha512_init(&hash); + sha512_add(&hash, signtime, sizeof signtime); + sha512_add(&hash, expires, sizeof expires); + sha512_add(&hash, data, len); + sha512_final(&hash, dst); +} + +void init_signature(struct signature *sig) { + memcpy(sig->magic, "ZPMS", 4); + sig->version = VERSION; + sig->type = SIGNATURE; + sig->reserved0 = 0; + sig->reserved1 = 0; + memset(sig->key, 0, sizeof sig->key); + sig->signtime = 0; + sig->expires = 0; + memset(sig->reserved2, 0, 8); + memset(sig->signature, 0, 64); + memset(sig->reserved3, 0, 120); + memset(sig->trailer, 0, 8); +} + +int create_signature(struct signature *sig, struct secret_key *sk, + int baresign, unsigned char *data, size_t len) { + unsigned char messagehash[64]; + + if (!sk || !data || !sig) { + return 0; + } + + memcpy(sig->magic, "ZPMS", 4); + sig->version = VERSION; + sig->type = SIGNATURE; + sig->reserved0 = 0; + sig->reserved1 = 0; + ed25519_genpub(sig->key, sk->key); +#if 0 + sig->signtime = 0; + sig->expires = 0; +#endif + memset(sig->reserved2, 0, 8); + memset(sig->signature, 0, 64); + memset(sig->reserved3, 0, 120); + memset(sig->trailer, 0, 8); + + if (!baresign) { + sighash(messagehash, sig, data, len); + data = messagehash; + len = sizeof messagehash; + } + + ed25519_sign(sig->signature, sk->key, sig->key, data, len); + + return 1; +} + +//#define MARK do { fprintf(stderr, "%s %s:%d\n", __FILE__, __func__, __LINE__); } while (0) + static char hexchars[] = "0123456789abcdefABCDEF"; static void hex(char *dst, uint8_t *src, size_t len) { @@ -64,14 +513,116 @@ static void hex(char *dst, uint8_t *src, size_t len) { } } -static void hexbin(uint8_t *dst, unsigned char *src, size_t len) { - size_t i; - int x; +/* always 512 bytes dst, 248 bytes src, last 8 bytes + * are ignored and set to zero, last 4 bytes not output + */ +void serialize(uint8_t *dst, unsigned char *src) { + int i; + uint8_t byte, hi, lo; + + for (i = 0; i < 248; i++) { + byte = src[i]; + hi = byte >> 4; + lo = byte & 0xf; + if (i % 32 == 0 && i > 0) { + *dst++ = '\n'; + } + + *dst++ = hexchars[hi]; + *dst++ = hexchars[lo]; + } + for (; i < 252; i++) { + *dst++ = '0'; + *dst++ = '0'; + } + *dst++ = '\n'; +} + +int hexval(int digit) { + if (digit >= '0' && digit <= '9') { + return digit - '0'; + } else if (digit == 'A' || digit == 'a') { + return 10; + } else if (digit == 'B' || digit == 'b') { + return 11; + } else if (digit == 'C' || digit == 'c') { + return 12; + } else if (digit == 'D' || digit == 'd') { + return 13; + } else if (digit == 'E' || digit == 'e') { + return 14; + } else if (digit == 'F' || digit == 'f') { + return 15; + } + + return 0; +} + +/* convert at most dlen hex bytes from srclen, returns + * number of bytes read, src may contain zero bytes, + * so use strlen in the caller if needed + */ +size_t hex2bin(uint8_t *dst, size_t dlen, char *src, size_t slen) { + size_t i, n = 0; + uint8_t val = 0; + + for (i = 0; i < slen && n < dlen; i++) { + if (!isxdigit(src[i])) { + continue; + } + + val = hexval(src[i++]) << 4; + /* look for next hex digit */ + while (i < slen && !isxdigit(src[i])) { + i++; + } + if (i == slen || !isxdigit(src[i])) { + break; /* tests redundant, but make code clearer */ + } + val += hexval(src[i]); /* low nibble */ + dst[n++] = val; + } + + return n; +} + +/* read in up to 248 bytes, set remaining to zero + * returns byte 5 if the bytes begin with "ZPMS" and 248 bytes were converted + * src must be at least 512 bytes + */ +int deserialize(void *obj, uint8_t *src, size_t slen) { + int bytes, i, type; + char buf[256]; + + bytes = hex2bin(buf, 248, src, slen); + + for (i = bytes; i < 256; i++) { + buf[i] = 0; + } + + if (bytes == 248 && memcmp(buf, "ZPMS", 4) == 0) { + type = buf[5]; + } else { + return 0; + } - for (i=0; ikey, 32); + outlen = 64; + } else { + seckey_bytea(skh, sk); + serialize(serialized, skh); + outlen = sizeof serialized; } - if (n > fs) { - n = fs; + + write(fd, serialized, outlen); + /* clear memory */ + memset(skh, 0, sizeof skh); + memset(serialized, 0, sizeof serialized); + + return 1; +} + +int write_public_key(int fd, struct public_key *pk, int outputflags) { + uint8_t skh[256]; + char serialized[512]; + size_t outlen; + + if (outputflags & RAW) { + hex(serialized, pk->key, 32); + outlen = 64; + } else { + pubkey_bytea(skh, pk); + serialize(serialized, skh); + outlen = sizeof serialized; } - size_t pk = strspn(m, hexchars); - pk /= 2; - if (n > pk) { - n = pk; + write(fd, serialized, outlen); + /* clear memory */ + memset(skh, 0, sizeof skh); + memset(serialized, 0, sizeof serialized); + + return 1; +} + +int write_signature(int fd, struct signature *sig, int outputflags) { + uint8_t skh[256]; + char serialized[512]; + size_t outlen; + + if (outputflags & RAW) { + hex(serialized, sig->signature, 64); + outlen = 128; + } else { + signature_bytea(skh, sig); + serialize(serialized, skh); + outlen = sizeof serialized; } - hexbin(dest, m, n*2); - munmap(m, fs); - return n; + write(fd, serialized, outlen); + /* clear memory */ + memset(skh, 0, sizeof skh); + memset(serialized, 0, sizeof serialized); + + return 1; } +int write_object(int fd, void *o, int outputflags) { + uint8_t bytes[256]; + char serialized[512]; + size_t outlen; + int type; + union signobject *obj = o; -/* private key format is: - * "ZPMS" 4 byte magic "ZPMS" - * byte 0x01 = version 1 - * byte 0x01 = private key - * byte 0x01 = chacha encrypted, 0x00 = plain/raw key bytes - * byte 0x00 reserved, probably "key usage" - * 32 bytes private key - * 24 bytes reserved - * sub keys, public keys? No point to the public key, since it's - * always derivable. could have 16 bytes of IV for chacha to encrypt. - * - * entire thing is then hexencoded, but - */ + type = ((struct secret_key *)obj)->type; -/* public key format is: - * "ZPMS" 4 byte magic "ZPMS" - * 0x01 1 byte version - * 0x02 1 byte "public key packet" - * 0x00 1 byte reserved - * 0x00 1 byte reserved - * 0x.. 32 bytes public key - * 0x.. 24 bytes reserved - * 0x.. revocation signatures - * 0x.. user id packets - * 0x.. certification signatures - * 0x.. sub key packets - */ + if (outputflags & RAW) { + switch (type) { + case 0x01: hex(serialized, obj->sk.key, 32); + outlen = 64; + break; + case 0x02: hex(serialized, obj->pk.key, 32); + outlen = 64; + break; + case 0x03: hex(serialized, obj->sig.signature, 64); + outlen = 128; + break; + default: + return 0; + } + } else { + outlen = 512; + switch (type) { + case 0x01: seckey_bytea(bytes, &obj->sk); break; + case 0x02: pubkey_bytea(bytes, &obj->pk); break; + case 0x03: signature_bytea(bytes, &obj->sig); break; + memset(bytes, 0, sizeof bytes); + default: return 0; + } + serialize(serialized, bytes); + } -/* timestamps are 8 byte little endian integers of seconds since - * the epoch. or unix time, or some such. Times are a mess. - * Unix time is leap second unaware, and ambiguous during leap seconds. - * UTC requires a database of leap seconds for past time, and the - * specified second in the future possibly changes. - * TAI is good, but currently 37 seconds behind UTC, which is just weird - */ + write(fd, serialized, outlen); + memset(serialized, 0, outlen); -/* signature items from openpgp */ -/* revokable flag - * 0-255 trust level - * - */ + return 1; +} -/* signature format: - * "ZPMS" 4 byte magic "ZPMS" - * 0x01 1 byte version - * 0x03 1 byte "signature packet" - * 0x00 1 byte reserved - * 0x00 1 byte reserved - * 0x.. 8 bytes timestamp of signature time - * 0x.. 8 bytes timestamp of signature expiration, 0 if none - * 0x.. 64 bytes of actual signature - * 0x.. 32 bytes of public key making the signature - * 0x.. 8 bytes reserved - * Total is 128 bytes. - */ +/* TODO work on strings, not file output */ +void write_to_512(int fd, int output) { + char trailer[65]; + memset(trailer, '-', 64); + trailer[64] = '\n'; -/* expires time could probably be 4 bytes as an offset from created. - * That would free up 4 bytes. Sign time could probably be four - * bytes with an epoch of say 2019-01-01, since negative times don't - * really make any sense, that give more than 100 years, and frees - * up another 4 bytes - * - * version is zero until this proof of concept is better validated - */ -struct signature { - char magic[4]; /* magic ZPMS */ - char info[4]; /* version = 0, type=3, 1 byte trust, 1 byte reserved */ - uint64_t signtime; /* unix time */ - uint64_t expires; /* unix time, 0 = none */ - char sig[64]; - char pub[32]; - char reserved[8]; -}; + while (output < 512) { + int shortfall = 512 - output; + if (shortfall >= 65) { + write(fd, trailer, 65); + output += 65; + continue; + } + write(fd, trailer + (65-shortfall), shortfall); + output += shortfall; + } +} -struct key { - char magic[4]; /* "ZPMS" */ - char info[4]; /* version, type = 0x02, reserved, reserved */ - char key[32]; - uint64_t created; - uint64_t expires; - char reserved[8]; - char sig[64]; /* zeroes for a private key, or fill in, doesn't matter*/ - /* could fill in the first 32 bytes of sig with the public key - * if this is a private key, then you don't need another structure */ -}; +int dump_signature(int fd, struct signature *sig) { + char as_str[1024], key[64], sigstr[128]; + int output; + int64_t curtime = time(0); + + hex(key, sig->key, 32); + hex(sigstr, sig->signature, 64); + + output = sprintf(as_str, "Type: %s\nSigned: %ld\n%s: %ld\nPublic Key: %s\nSignature:\n%.64s\n%.64s\n", + "signature", + sig->signtime, + curtime > sig->expires ? "Expired" : "Expires", + sig->expires, key, + sigstr, sigstr+64); + write(fd, as_str, strlen(as_str)); + write_to_512(fd, output); -int create_key(struct key *sk) { - char info[] = { 0, 1, 0, 0 }; - memcpy(sk->magic, "ZPMS", 4); - memcpy(sk->info, info, 4); - getrandom(sk->key, sizeof sk->key, 0); - ed25519_genpub(sk->sig, sk->key); - sk->created = time(NULL); - memset(sk->reserved, 0, 8); - memset(sk->sig+32, 0, 32); return 1; } -int derive_pubkey(struct key *pk, struct key *sk) { - char info[] = { 0, 2, 0, 0 }; +int dump_public_key(int fd, struct public_key *pk) { + char as_str[1024], key[64], sig[128]; + int output; + int64_t curtime = time(0); - memcpy(sk->magic, "ZPMS", 4); - memcpy(sk->info, info, 4); - ed25519_genpub(pk->key, sk->key); - pk->created = sk->created; - pk->expires = sk->expires; - memset(sk->reserved, 0, 8); + hex(key, pk->key, 32); + hex(sig, pk->signature, 64); + + output = sprintf(as_str, "Type: %s\nId: \"%s\"\nCreated: %ld\n%s: %ld\nKey: %.64s\nSignature:\n%.64s\n%.64s\n", + "public key", + pk->id, pk->created, + curtime > pk->expires ? "Expired" : "Expires", + pk->expires, key, + sig, sig+64); + write(fd, as_str, strlen(as_str)); + write_to_512(fd, output); + + return 1; +} + +int dump_secret_key(int fd, struct secret_key *sk) { + char as_str[1024]; + char key[64]; + int output; + int64_t curtime = time(0); + char exprep[64]; + + hex(key, sk->key, 32); + if (sk->expires == INT64_MAX) { + sprintf(exprep, "never"); + } else { + sprintf(exprep, "%ld", sk->expires); + } + + output = sprintf(as_str, "Type: %s\nEncrypted: %s\nId: \"%s\"\nCreated: %ld\n%s: %s\nKey: %.64s\n", + "secret key", + sk->encryption ? "yes" : "no", + sk->id, sk->created, + curtime > sk->expires ? "Expired" : "Expires", + exprep, key); + write(fd, as_str, strlen(as_str)); + write_to_512(fd, output); - ed25519_sign(pk->sig, sk->key, pk->sig, sk->info, 52); return 1; +} +int dump_object(int fd, void *obj) { + int rv; + switch (((struct secret_key *)obj)->type) { + case 0x01: rv = dump_secret_key(fd, obj); break; + case 0x02: rv = dump_public_key(fd, obj); break; + case 0x03: rv = dump_signature(fd, obj); break; + default: rv = 0; break; + } + return rv; } -/* if reserved used 16 bytes for signtime and expires, and we added - * 64 bytes as a signature, a public key could include it's own self - * signature, and a key would be 128 bytes, as would a signature. This - * would make all structures 128 bytes. +/* returns a malloced keyfile name. no check is made to see if the + * file actually exists */ +char *find_keyfile(char *file) { + char *f = 0; + + if (file) { + f = strdup(file); + } else if (getenv("ZPM_KEYFILE")) { + f = strdup(getenv("ZPM_KEYFILE")); + } else { + char *home = getenv("HOME"); + if (home) { + size_t need; + need = snprintf(0, 0, "%s/.zpm/key", home); + char *freefile = malloc(need + 1); + if (freefile) { + snprintf(freefile, need+1, "%s/.zpm/key", home); + } + f = freefile; + } + } + + return f; +} + +int obj_id_match(union signobject *obj, char *id) { + if (!obj || !id) { + return 1; + } + + switch(object_type(obj)) { + case SECRET_KEY: + return !strcmp(obj->sk.id, id); + break; + case PUBLIC_KEY: + return !strcmp(obj->pk.id, id); + break; + default: + return 1; + break; + } -int read_secret_key(uint8_t *sec, uint8_t *pub, char *file) { - uint8_t sk[64]; + return 1; +} + +int key_id_match(struct secret_key *sk, char *id) { + if (!sk && !id) { + return 1; + } + /* TODO look for substring */ + return !strcmp(sk->id, id); +} + +/* try to populate a secret key structure. + * preference order: + * direct specified string, won't have any additional info added + * string from ZPM_KEY + * file, if id, try to match + * file from ZPM_KEYFILE + * file at ~/.zpm/key + * otherwise fail + * if direct from string, encrypted? assume no. need option then. + */ + +/* can probably just call read_item */ +int read_object(int fd, union signobject *obj) { + char buf[512]; + ssize_t bytes; - readbytes(sk, file, sizeof sk); - if (sk[4] != 1 || sk[5] != 1) { - printf("magic: "); - hexdump(sk, 16); + bytes = read512(fd, buf); + if (bytes < 512) { return 0; } - memcpy(sec, sk+8, 32); - //printf("sec: "); - //hexdump(sec, 32); - if (pub) { - ed25519_genpub(pub, sec); - // printf("pub: "); - // hexdump(pub, 32); + return deserialize(obj, buf, bytes); +} + +int find_secret_key(struct secret_key *sk, char *id, char *file, char *direct) { + char *path = 0; + int fd, bytes; + size_t len; + + if (direct || (direct = getenv("ZPM_KEY"))) { + /* TODO check strlen == 64 */ + len = strlen(direct); + if (len < 2 * sizeof sk->key) { + return 2; + } + memset(sk->key, 0, sizeof sk->key); + bytes = hex2bin(sk->key, sizeof sk->key, direct, len); + if (bytes != sizeof sk->key) { + return 0; + } + create_key(sk, 0); + return 1; + } else if (file) { + path = find_keyfile(file); + if (!path) { + return 0; + } + + fd = open(path, O_RDONLY); + free(path); + if (fd == -1) { + return 0; + } + + int64_t curtime = time(0); + int type; + do { + type = read_object(fd, (union signobject *)sk); + if (type != SECRET_KEY) { + continue; + } + if (curtime > sk->expires) { + continue; + } + if (!id || key_id_match(sk, id)) { + close(fd); + return 1; + } + } while (type != 0); + close(fd); + } + + return 0; +} + +int open_output(char *path) { + int fd = 1; + + if (path && strcmp(path, "-") != 0) { + fd = open(path, O_WRONLY|O_CREAT, 0600); + } + + return fd; +} + +void set_key_id(struct secret_key *sk, char *id) { + memset(sk->id, 0, 120); + if (id) { + strncpy(sk->id, id, 119); + } +} + +void set_pub_id(struct public_key *sk, char *id) { + memset(sk->id, 0, 120); + if (id) { + strncpy(sk->id, id, 119); + } +} + +int object_filter(union signobject *obj, int type, char *id, char *prefix) { + int otype; + size_t len; + + otype = object_type(obj); + + if (otype == 0) { + return 0; + } + + if (type && otype != type) { + return 0; + } + + if (otype != SECRET_KEY) { + return 0; + } + + if (id) { + /* TODO memmem */ + if (!key_id_match(&obj->sk, id)) { + return 0; + } + } + + if (prefix) { + char buf[32]; + len = strlen(prefix); + len = hex2bin(buf, sizeof buf, prefix, len); + if (memcmp(obj->sk.key, buf, len) != 0) { + return 0; + } } return 1; } -/* private key format is: - * "ZPMS" 4 byte magic "ZPMS" - * byte 0x01 = version 1 - * byte 0x01 = private key - * byte 0x01 = chacha encrypted, 0x00 = plain/raw key bytes - * byte 0x00 reserved, probably "key usage" - * 32 bytes private key - * 24 bytes reserved - * sub keys, public keys? No point to the public key, since it's - * always derivable. could have 16 bytes of IV for chacha to encrypt. - * - * entire thing is then hexencoded, but +/* read or construct a secret key */ +int find_key(struct secret_key *sk, struct signing_context *ctx) { + char *keystring, *keyfile, *idstring, *keyprefix; + size_t len; + + keystring = ctx->keystring; + keyfile = ctx->keyfile; + idstring = ctx->keyid; + keyprefix = ctx->keyprefix; + + //fprintf(stderr, "looking for key, str = \"%s\", file = \"%s\", id = \"%s\", prefix = \"%s\"\n", keystring, keyfile, idstring, keyprefix); + + if (keystring) { + len = strlen(keystring); + if (len < 64) { + return 0; + } + hex2bin(sk->key, sizeof sk->key, keystring, len); + create_key(sk, 0); + set_key_id(sk, idstring); + return 1; + } else { + char *keyringfile = find_keyfile(keyfile); + if (!keyringfile) { + return 0; + } + int keyring = open(keyringfile, O_RDONLY); + union signobject obj; + int rv; + while ((rv = read_object(keyring, &obj))) { + if (rv != SECRET_KEY) { + continue; + } + if (object_filter(&obj, SECRET_KEY, idstring, keyprefix)) { + break; + } + } + close(keyring); + if (rv == 1) { + return 1; + } + } + + if (sk->encryption && ctx->passphrase) { + collect_key(ctx->passkey, ctx->passphrase); + decrypt_key(sk, ctx->passkey); + } + + return 0; +} + +/* flags: 1 = hex encoded, 2 = input is path */ +char *create_message(char *in, size_t *len, int flags) { + char *msg = 0; + if (in) { + if (flags == 2) { + msg = map(in, len); + } else if (flags == 1) { + *len = strlen(in); + msg = malloc(*len/2 + 2); + *len = hex2bin(msg, *len, in, *len); + } else { + *len = strlen(in); + msg = in; + } + } else { + /* would need to read from stdin and buffer */ + } + + + return msg; +} + +char *construct_message(char *ms, char *path, size_t *mlen, int flags) { + char *msg = 0, *mapped = 0; + *mlen = 0; + + if (ms) { + *mlen = strlen(ms); + msg = ms; + } else if (path) { + mapped = msg = map(path, mlen); + } + + if (flags) { + /* input is hex encoded */ + char *new; + new = malloc(*mlen/2+1); + *mlen = hex2bin(new, *mlen/2+1, msg, *mlen); + msg = new; + } + + return msg; +} + + /* TODO should use mlock()ed memory for the secret key */ +#if 0 +#if _POSIX_ADVISORY_INFO > 0 + long pagesize = sysconf(_SC_PAGESIZE); + int rv = posix_memalign(&sk, pagesize, sizeof *sk); + if (rv) { + mlock(sk, sizeof sk); + /* not able to lock, use stack memory */ + } else { + /* not able to allocate, use stack memory */ + } + /* TODO if the allocate or lock fails, try mlockall(), + * if that fails, abort or perhaps continue if insecure mode */ + /* TODO also mlock buffers */ +#endif +#endif + +/* if flags == 0, then need to hash the message and the signature info + * before verifying, otherwise, bare sign, so just verify */ +int verify(struct signature *sig, char *message, size_t mlen, int flags) { + char hash[64]; + + if (flags == 0) { + sighash(hash, sig, message, mlen); + message = hash; + mlen = sizeof hash; + } -int write_secret_key(uint8_t *sec, int flags, int fd) { - uint8_t skh[128] = "5A504D5301010000"; /* ZPMS 1 1 0 0 */ + return ed25519_verify(sig->signature, sig->key, message, mlen); +} - if (flags & 0x01) { - skh[13] = '1'; +int generate_key(struct secret_key *sk, char *keystring, char *idstring) { + size_t len; + + if (!sk) { + return 0; } - //hexdump(sec, 32); - hex(skh + 16, sec, 32); - memset(skh + 80, '0', 48); - write(fd, skh, sizeof skh); - memset(skh, 0, sizeof skh); + if (keystring) { + len = strlen(keystring); + if (len < 64) { + return 0; + } + hex2bin(sk->key, sizeof sk->key, keystring, len); + create_key(sk, 0); + } else { + create_key(sk, 1); + } + + if (idstring) { + strncpy(sk->id, idstring, 119); + } + + return 1; +} + +int list_object(int fd, union signobject *obj) { + if (fd >= 0 && obj) { + dump_object(fd, obj); + } return 1; } +/* TODO can be just a find_key and take an arg */ +int find_public_key(struct public_key *pk, char *id, char *pstr, char *pfile) { + char *path = 0, buf[32]; + int fd, bytes, type = 0; + size_t len; + + if (!pstr && !pfile) { + pstr = getenv("ZPM_PUBLIC_KEY"); + } + + if (pstr) { + /* TODO check strlen == 64 */ + len = strlen(pstr); + if (len >= 512) { + type = deserialize(pk, pstr, len); + } else if (len >= 64) { + bytes = hex2bin(buf, sizeof buf, pstr, len); + if (bytes == 32) { + pubkey_init(pk); + memcpy(pk->key, buf, 32); + if (id) { + set_pub_id(pk, id); + } + type = PUBLIC_KEY; + } + } + } else if (pfile) { + path = find_keyfile(pfile); + if (!path) { + return 0; + } + + fd = open(path, O_RDONLY); + free(path); + if (fd == -1) { + return 0; + } + + int64_t curtime = time(0); + int type; + do { + type = read_object(fd, (union signobject *)pk); + if (type != PUBLIC_KEY) { + continue; + } + if (curtime > pk->expires) { + /* TODO warn? */ + continue; + } + if (!id || obj_id_match((union signobject *)pk, id)) { + close(fd); + return type; + } + } while (type != 0); + close(fd); + } + + return type; +} + +int find_signature(struct signature *sig, char *sigstring, char *sigfile) { + size_t len; + int type = 0; + char *mapped = 0; + char buf[64]; + + if (sigstring) { + len = strlen(sigstring); + if (len >= 512) { + type = deserialize(sig, sigstring, len); + } else if (len >= 128) { + len = hex2bin(buf, sizeof buf, sigstring, len); + if (len == 64) { + init_signature(sig); + memcpy(sig->signature, buf, 64); + type = SIGNATURE; + } + } + } else if (sigfile) { + mapped = map(sigfile, &len); + if (mapped && len >= 512) { + type = deserialize(sig, mapped, len); + } + } + + if (mapped) { + munmap(mapped, len); + } + return type; +} + +int object_match(union signobject *a, union signobject *b) { + if (!a && !b) { + return 1; + } + if (!a || !b) { + return 0; + } + + if (object_type(a) != object_type(b)) { + return 0; + } + + if (object_type(a) == PUBLIC_KEY) { + return memcmp(a->pk.key, b->pk.key, 32) == 0; + } else if (object_type(a) == SECRET_KEY) { + return memcmp(a->sk.key, b->sk.key, 32) == 0; + } else if (object_type(a) == SIGNATURE) { + if (memcmp(a->sig.key, b->sig.key, 32) != 0) { + return 0; + } + return memcmp(a->sig.signature, b->sig.signature, 64) == 0; + } + + return 0; +} + +/* return true if we find something that matches obj */ +int find_object(int fd, union signobject *lookfor) { + union signobject obj; + while (read_object(fd, &obj) > 0) { + if (object_match(lookfor, &obj)) { + return 1; + } + } + + return 0; +} +#include +#include + int main(int ac, char *av[]) { - int option; - int mode = 0; - char *outfile = 0; - uint8_t pub[ED25519_KEY_LEN]; - /* TODO should use mlock()ed memory for the secret key */ - uint8_t sec[ED25519_KEY_LEN]; - uint8_t sig[ED25519_SIG_LEN]; - void *message = 0; + /* + * M = message, P = public key, K = secret key, S = signature + * T = timestamp, E = expires, I = identity, H = message hash + * C = ciphertext, W = password + */ + struct signing_context ctx = { 0 }; + + unsigned char *message = 0; + size_t mlen; char *messagefile = 0; char *messagestring = 0; - char messagehash[SHA512_HASH_LENGTH]; - char *keystring = 0, *keyfile = 0; - unsigned char *sigstring = 0, *sigfile = 0; + + char *timestring = 0, *expirestring = 0; + int64_t timestamp = 0, expires = INT64_MAX; + char *passphrase = 0; - size_t mlen; - int rawsig = 0, hexencoded = 0; - int debug = 0; + unsigned char passkey[32]; + + char *keyid = 0, *keyprefix = 0; + struct secret_key sk = { 0 }; + char *keyfile = 0, *keystring = 0; + + struct public_key pk = { 0 }; + char *pubfile = 0, *pubstring = 0; + + struct signature sig = { 0 }; + char *sigfile = 0, *sigstring = 0; + + char *encfile = 0, *encstring = 0; + char *outfile = 0; + int output = 1; /* file descriptor */ + + int option, argn; + int mode = 0; + + int hexencoded = 0, outputflags = 0; + int debug = 0, quiet = 0, bareinput = 0; + int outputmsghash = 0; + int rv; + +#if 0 +#if _POSIX_ADVISORY_INFO > 0 + fprintf(stderr, "locking memory\n"); + struct rlimit lim; + getrlimit(RLIMIT_MEMLOCK, &lim); + fprintf(stderr, "memlock limit %llu/%llu\n", lim.rlim_cur, lim.rlim_max); + struct public_key *pkp; + posix_memalign(&pkp, + rv = mlockall(MCL_CURRENT|MCL_FUTURE); /* can't happen, have to just + lock sk and maybe ctx */ + if (rv == -1) { + perror("unable to lock memory"); + } +#endif +#endif - while ((option = getopt(ac, av, "o:vS:f:sgek:K:rm:hd")) != -1) { + while ((option = getopt(ac, av, "vsgelxyirdHo:hbT:E:n:w:m:N:k:K:p:P:f:F:c:C:")) != -1) { switch (option) { - case 'o': outfile = optarg; break; + /* modes */ case 'v': mode = VERIFY; break; - case 'S': sigstring = optarg; break; - case 'f': sigfile = optarg; break; case 's': mode = SIGN; break; - case 'g': mode = GENKEY; break; + case 'g': mode = KEYGEN; break; case 'e': mode = EXTRACT; break; - case 'k': keystring = optarg; break; - case 'K': keyfile = optarg; break; - case 'p': passphrase = optarg; break; - case 'm': messagestring = optarg; break; - case 'h': hexencoded = 1; break; - case 'r': rawsig = 1; break; + case 'l': mode = LIST; break; + case 'x': mode = ENCRYPT; break; + case 'y': mode = DECRYPT; break; + case 'i': mode = IMPORT; break; + /* more modes: + * remove pw, add pw, export, delete + * need a manage mode to delete + * or update keys + */ +#if 0 + /* trust level */ + case 't': trust = strtoul(optarg, 0, 10); break; +#endif + /* outputs */ + case 'r': outputflags |= RAW; break; case 'd': debug++; break; + case 'H': outputmsghash = 1; break; + case 'o': outfile = optarg; break; + case 'q': quiet++; break; + + /* inputs */ + case 'h': hexencoded = 1; break; + case 'b': bareinput = 1; break; + case 'T': timestring = optarg; break; /* T */ + case 'E': expirestring = optarg; break; /* E */ + case 'n': keyid = optarg; break; /* I */ + case 'w': passphrase = optarg; break; /* W */ + case 'm': messagestring = optarg; break; /* M */ + case 'N': keyprefix = optarg; break; /* K ish */ + case 'k': keyfile = optarg; break; /* K */ + case 'K': keystring = optarg; break; /* K */ + case 'p': pubfile = optarg; break; /* P */ + case 'P': pubstring = optarg; break; /* P */ + case 'f': sigfile = optarg; break; /* S */ + case 'F': sigstring = optarg; break; /* S */ + case 'c': encfile = optarg; break; /* C */ + case 'C': encstring = optarg; break; /* C */ + default: exit(EXIT_FAILURE); break; } } - if (sigfile) { - ssize_t r; - memset(sig, 0, sizeof sig); - r = readbytes(sig, sigfile, sizeof sig); - if (r == -1) { - return 8; - } - if ((size_t)r < sizeof sig) { - return 7; - } - } else if (sigstring) { - memset(sig, 0, sizeof sig); - hexbin(sig, sigstring, sizeof sig); - } + argn = optind; - if (keystring) { - memset(sec, 0, sizeof sec); - hexbin(sec, keystring, strlen(keystring)); - ed25519_genpub(pub, sec); - if (debug) { - printf("sec:\n"); hexdump(sec, sizeof sec); - } - } else if (keyfile) { - read_secret_key(sec, pub, keyfile); - //hexdump(sec, sizeof sec); - //hexdump(pub, sizeof pub); + /* + * M = message, P = public key, K = secret key, S = signature + * T = timestamp, E = expires, I = identity, H = message hash + * C = ciphertext, W = password + * + * -sb bare data sign (M, K) -> S + * -s full data sign ( (M,T,E) -> H, K) -> S + * -s key sign ( (P,T,E) -> H, K) -> S + * -e extract (K) -> P + * -g generate (T, E, I) -> K (default to keyring append) + * -g generate (K, T, E, I) -> K (default to keyring append) + * self sign ( (K->P,T,E,I) -> H, M) -> S + * import (K/P/S) -> keyring file append, stdout daft, unless redir + * show (K/P/S) -> output input + * -l list keys (K/P/S) -> key list, get from -k + * -v verify (M, S) -> boolean, via exit value, no output + * -x encrypt (M, K, T, E, P...) -> C + * -y decrypt (C, K) -> M + * -H generate signing hash for message or key + * + * output style: serialized, -d human readable, -r raw + * input style: serialized, -b bare, -h hexencoded + * + * possible inputs: M K T E P I S C + * M -> from -m, non-option arg, stdin + * K -> from -K, ZPM_KEY, -k, ZPM_KEYFILE, ~/.zpm/key + * P -> from -P, -p + * S -> from -F, -f, second arg? + * C -> from -C, -c, non-option arg, stdin + * T -> from -T, time(NULL) + * E -> from -E, never/-1 + * I -> from -n, ZPM_KEYID, K, S, null + * W -> from -w, ZPM_PASS, ~/.zpm/pass, terminal + */ + + if (timestring) { + timestamp = strtoull(timestring, 0, 10); + } else { + timestamp = time(NULL); } - if (mode == EXTRACT) { - /* want to print the pubkey only */ - hexdump(pub, sizeof pub); - exit(0); + if (expirestring) { + expires = strtoull(expirestring, 0, 10); } - /* need to be able to encrypt the private keys */ - /* use chacha */ - if (mode == GENKEY) { - /* a private key is just 32 random bytes */ - getrandom(sec, sizeof sec, 0); - ed25519_genpub(pub, sec); - ed25519_sign(sig, sec, pub, sec, sizeof sec); - //int rv = ed25519_verify(sig, pub, sec, sizeof sec); - if (outfile) { - int fd; - fd = open(outfile, O_WRONLY|O_CREAT, 0600); - if (fd == -1) { - perror("can't open outfile"); - exit(EXIT_FAILURE); + ctx.keystring = keystring; + ctx.keyfile = keyfile; + ctx.keyid = keyid; + ctx.keyprefix = keyprefix; + ctx.passphrase = passphrase; + + if (mode == KEYGEN) { + rv = generate_key(&sk, keystring, keyid); + if (!rv) { + exit(EXIT_FAILURE); + } + sk.created = timestamp; + sk.expires = expires; + } else if (mode == SIGN) { + char *firstarg = av[argn]; + rv = find_key(&sk, &ctx); + if (!rv) { + fprintf(stderr, "unable to find signing key\n"); + exit(EXIT_FAILURE); + } + char *message = construct_message(messagestring, firstarg, &mlen, hexencoded); + // dump_secret_key(2, &sk); + sig.signtime = timestamp; + sig.expires = expires; + rv = create_signature(&sig, &sk, bareinput, message, mlen); + if (!rv) { + exit(EXIT_FAILURE); + } + // dump_signature(2, &sig); + } else if (mode == SIGNKEY) { + find_key(&sk, &ctx); + find_public_key(&pk, keyid, pubstring, pubfile); + /* TODO construct a message out of a public key */ + mlen = 0; + sig.signtime = timestamp; + sig.expires = expires; + rv = create_signature(&sig, &sk, 0, message, mlen); + if (!rv) { + exit(EXIT_FAILURE); + } + } else if (mode == EXTRACT) { + rv = find_key(&sk, &ctx); + if (passphrase) { + decrypt_sk(&sk, passphrase); + } + if (!rv) { + exit(EXIT_FAILURE); + } + derive_pubkey(&pk, &sk); + } else if (mode == VERIFY) { + rv = find_signature(&sig, sigstring, sigfile); + if (rv == SIGNATURE) { + /* if you've specified the public key in a verify op, + * you probably know what you're doing + */ + if (pubstring || pubfile) { + rv = find_public_key(&pk, keyid, pubstring, pubfile); + if (rv == PUBLIC_KEY) { + memcpy(sig.key, pk.key, 32); + } } - write_secret_key(sec, 0, fd); - close(fd); + //dump_signature(2, &sig); + + message = construct_message(messagestring, av[argn], &mlen, hexencoded); + rv = verify(&sig, message, mlen, bareinput); } else { - write_secret_key(sec, 0, 1); + rv = 0; } - } - - /* set up the message data */ - if (mode == SIGN || mode == VERIFY) { - messagefile = av[optind]; - if (messagefile) { - message = map(messagefile, &mlen); - if (!message) { - return 9; + } else if (mode == LIST) { + union signobject obj; + char *keyringfile = find_keyfile(keyfile); + if (!keyringfile) { + fprintf(stderr, "can't find keyring file\n"); + exit(1); + } + int keyring = open(keyringfile, O_RDWR|O_APPEND); + if (keyring == -1) { + fprintf(stderr, "can't open keyring file %s\n", keyringfile); + perror(""); + exit(EXIT_FAILURE); + } + while ((rv = read_object(keyring, &obj))) { + list_object(output, &obj); + } + close(keyring); + } else if (mode == IMPORT) { + int fd, i, rv; + union signobject obj; + char *keyringfile = find_keyfile(keyfile); + int keyring = open(keyringfile, O_RDWR|O_APPEND); + if (keyring == -1) { + fprintf(stderr, "%s ", keyringfile); + perror(""); + exit(EXIT_FAILURE); + } + for (i = argn; i < ac; i++) { + fd = open(av[i], O_RDONLY); + if (fd == -1) { + fprintf(stderr, "%s ", av[i]); + perror(""); + rv = 0; + close(keyring); + exit(EXIT_FAILURE); } - } else if (messagestring) { - message = messagestring; - if (hexencoded) { - mlen = strlen(messagestring)/2; - message = malloc(mlen); - if (!message) { - return 10; + while ((rv = read_object(fd, &obj))) { + fprintf(stderr, "importing type %d\n", object_type(&obj)); + /* TODO probably a more efficient way to do + * this. Perhaps read in the keyring to + * memory. */ + lseek(keyring, 0, SEEK_SET); + if (!find_object(keyring, &obj)) { + write_object(keyring, &obj, outputflags); } - hexbin(message, messagestring, mlen*2); - } else { - mlen = strlen(messagestring); } - } else { - return 2; - } - if (!message) { - return 3; - } - } - - if (mode == SIGN) { - struct signature s; - s.magic[0] = 'Z'; - s.magic[1] = 'P'; - s.magic[2] = 'M'; - s.magic[3] = 'S'; - s.info[0] = 1; - s.info[1] = 1; - s.info[2] = 0; - s.info[3] = 0; - s.signtime = time(NULL); - s.expires = 0; - memset(s.reserved, 0, 8); - if (!rawsig) { - struct sha512 hash; - sha512_init(&hash); - sha512_add(&hash, message, mlen); - sha512_add(&hash, (uint8_t *)&s.signtime, sizeof s.signtime); - sha512_add(&hash, (uint8_t *)&s.expires, sizeof s.expires); - sha512_add(&hash, s.info, sizeof s.info); - sha512_final(&hash, messagehash); - message = messagehash; - } - - if (message == 0) { - return 5; - } - - ed25519_sign(sig, sec, pub, message, mlen); - memcpy(s.sig, sig, sizeof sig); - memcpy(s.pub, pub, sizeof pub); - if (rawsig) { - hexdump(sig, sizeof sig); - } else { - hexdump(&s, sizeof s); + close(fd); } + close(keyring); + } else if (mode == ENCRYPT) { + fprintf(stderr, "encryption not supported\n"); + exit(EXIT_FAILURE); + } else if (mode == DECRYPT) { + fprintf(stderr, "decryption not supported\n"); + exit(EXIT_FAILURE); } - if (mode == VERIFY) { - fprintf(stderr, "sig: "); - hexdump(sig, sizeof sig); - int rv = ed25519_verify(sig, pub, message, mlen); - if (rv) { - fprintf(stderr, "verified\n"); - return 0; - } else { - fprintf(stderr, "not verified\n"); - return 6; + /* + * now, do the action + */ + + if (mode == KEYGEN) { + if (!outfile) { + outfile = find_keyfile(keyfile); + } + if (outfile && strcmp(outfile, "-") != 0) { + output = open(outfile, O_RDWR|O_CREAT|O_APPEND, 0600); + } + /* TODO encrypt key */ + if (passphrase) { + fprintf(stderr, "encrypting key\n"); + collect_key(passkey, passphrase); + encrypt_key(&sk, passkey); + //memset(passphrase, 0, strlen(passphrase)); + memset(passkey, 0, 32); + } + // dump_secret_key(2, &sk); + write_secret_key(output, &sk, outputflags); + } else if (mode == SIGN || mode == SIGNKEY) { + if (outfile && strcmp(outfile, "-") != 0) { + output = open(outfile, O_RDWR|O_CREAT|O_APPEND, 0600); + } + //dump_signature(2, &sig); + write_signature(output, &sig, outputflags); + } else if (mode == EXTRACT) { + if (outfile && strcmp(outfile, "-") != 0) { + output = open(outfile, O_RDWR|O_CREAT|O_APPEND, 0600); } + // dump_public_key(2, &pk); + write_public_key(output, &pk, outputflags); + } else if (mode == VERIFY) { + exit(rv ? 0 : 1); + } else if (mode == LIST) { + /* handled above */ + } else if (mode == IMPORT) { + /* handled above */ + } else if (mode == ENCRYPT) { + fprintf(stderr, "encryption not supported\n"); + exit(EXIT_FAILURE); + } else if (mode == DECRYPT) { + fprintf(stderr, "decryption not supported\n"); + exit(EXIT_FAILURE); } if (message && messagefile) { munmap(message, mlen); } + memset(sk.key, 0, sizeof sk.key); return 0; } diff --git a/doc/zpm-sign.8 b/doc/zpm-sign.8 index 1682eec..d89388f 100644 --- a/doc/zpm-sign.8 +++ b/doc/zpm-sign.8 @@ -1,25 +1,25 @@ -.TH zpm-sign 8 2019-03-02 "ZPM 0.6.2" +.TH zpm-sign 8 2019-03-09 "ZPM 0.7.0" .SH NAME zpm-sign \- manage package signatures .SH SYNOPSIS .B zpm sign [ -.B -hrdsgev +.B \-vsge ] [ -.BI -f " sigfile" +.B \-Hhrd ] [ .BI -o " outfile" ] [ -.BI -S " sigstring" +.BI -i " input" ] [ -.BI -k " keystring" +.BI -k " keyid" ] [ -.BI -K " keyfile" +.BI -I " inputhexstring" ] [ .BI -p " passphrase" @@ -33,8 +33,7 @@ zpm-sign \- manage package signatures manages signatures on zpm packages. It can generate signing keys, sign files, and verify signatures. The ed25519 algorithms are used exclusively, and all the signature code is taken from the -ref10 implementation. Signatures themselves are hex encoded -representions of the signature metadata and the actual signature value. +ref10 implementation. .PP Private keys are potentially encrypted with chacha20 before storing them on disk. @@ -44,6 +43,20 @@ Signatures are always detached signatures. The \-k option is used to specify a key. Keys are looked for in the keyring file (or taken literally from the command line if the \-h option is given). +The \-i option can be used to specify a +key file to look for a key in. If the \-k option is given, or the ZPM_KEY +environment variable is set, a key which matches that string in the id portion +of the key is looked for, otherwise the first secret key found is used. If the +\-i option is not given, a key will be searched for in a file given with the +ZPM_KEYFILE environment variable or in ~/.zpm/key Alternatively, a hex encoded +private key can be specified directly with the \-I option. If several of these +options apply, The \-I option takes precedence. I.e. command line beats +environment variables beats defaults. +.PP +If the private key needs a passphrase, it can be specified via the \-p +option or the ZPM_KEYPASS environment variable, neither of which +is secure. Otherwise, if /dev/tty can be opened, zpm-sign will +attempt to ask for a passphrase. .PP For signing or generating a public key, a private key is looked for, and if not specified, the first one found in the keyring file is used. @@ -52,6 +65,27 @@ For signature verification, no key needs to be specified as the public key is contained in the signature. .SS Signing Mode To sign data, use \-s +.PP +Inputs to a regular signature are: +.TP the secret key used to sign the message, +See above for methods to specify a secret key. +.TP +Signature timestamp +Defaults to the current time read from the system clock. +Can be specified with the \-T option. +.TP +Expiration timestamp +Defaults to never. Can be specified with the \-E optino. +.TP +The message to be signed. +Is read from stdin, a command line argument, or the \-m option. +If both an argument and a \-m option are given the \-m option wins. +.PP +When a raw signature is created (with the \-r option), the +.PP +The signature is output to stdout, or the output file given with the +\-o option. A signature packet is output unless the \-b option is given, +in which case only the hex encoded signature is output. .SS Key Generation Mode A new private key is generated with \-g .SS Verification Mode @@ -118,9 +152,15 @@ Must be specified as a decimal representation of the unix timestamp to be used. .SH PACKET AND FILE FORMAT .PP -Signatures and keys are output as hexencoded packet bytestrings. All are 128 -bytes, (256 bytes as hex encoded). Reserved bytes in packet formats must be -zero. +Signatures and keys are output as hexencoded packet bytestrings. All are 256 +bytes, (512 bytes as hex encoded). Reserved bytes in packet formats must be +zero. The last 8 bytes are reserved. +.PP +On disk format is a series of 32 bytes, hex encoded, followed by +a newline. This takes one line per 32 bytes, thus 4 lines for 128, +8 lines for 256. If we waste 8 bytes at the end, we have 8 lines +and 512 bytes encoded, the last line will be short by 16 characters, +so that the actual size on disk is 512 bytes. .PP Timestamps are 8 byte little endian integers of seconds since the epoch. or unix time, or some such. Times are a mess. Unix time is leap second unaware, @@ -149,14 +189,16 @@ byte 0x01 = private key byte 0x01 = chacha encrypted, 0x00 = plain/raw key bytes byte 0x00 reserved, probably "key usage" 32 bytes private key -8 bytes creation time -8 bytes expiration time +8 bytes creation time, little endian +8 bytes expiration time, little endian 8 bytes password salt, note, nist wants 16, but we're not using nist approved hash algorithms anyway. 64 bytes reserved could have 16 bytes of IV for chacha to encrypt. Can probably use those, hashed with the password as the salt, but not very much salt then. +120 bytes id +8 bytes reserved trailer .EE .SS Public Keys .EX @@ -169,6 +211,8 @@ public key format is: 0x.. 32 bytes public key 0x.. 24 bytes reserved : create, expire, 8 reserved 0x.. 64 bytes reserved : self sig + 120 bytes reserved : id = 1 byte id length, n bytes id, zeros + 8 bytes reserved trailer 0x.. revocation signatures 0x.. user id packets 0x.. certification signatures @@ -191,11 +235,39 @@ a revocation signature, used to revoke signatures. 0x.. 64 bytes of actual signature 0x.. 32 bytes of public key making the signature 0x.. 8 bytes reserved + 120 bytes reserved + 8 bytes reserved trailer .EE .SH EXAMPLES .TP -.B zpm sign -lists all files in the local database +.B zpm sign -s \fIpath\fR +Sign the contents of the file found at \fIpath\fR using the default secret key +with a signing timestamp of the current time and no expiration date. The +signature packet will be written to stdout. +.TP +.B zpm sign -s -m 'foo' +Sign the string 'foo'. +.TP +.B printf '%s' foo | zpm sign -s +Sign the string 'foo' as in the above example, the message being read +from stdin. +.TP +.B zpm sign -s -r -m 'foo' +Sign the string 'foo' directly using the default private key, no +additional information is signed, and the output is a hex encoded +raw signature with no additional newlines, making up 128 characters +of output. There is no way to output just the raw signature while +still adding the timestamps and such, but the raw signature can be +extracted from a signature packet. +.TP +.B zpm sign -s -r \\ +-I 'c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7' \\ +-h -m 'af82' +Sign the two byte hex encoded message af82 with the raw hex encoded +secret key given by the -I argument. The result will be +6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac +18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a +all on a single line with no trailing newline. .SH EXIT STATUS 0 on success non zero on failure .SH BUGS @@ -214,6 +286,8 @@ used for this, but the semantics then need to be designed. The general methods described by SEC 1 Ver 2.0 were followed, but this is not an implementation of that scheme, because different algorithms are used. +.PP +Chacha20 is used to encrypt private keys. .SH FILES ~/.zpm/keys /var/lib/zpm/keys diff --git a/t/sign.t b/t/sign.t index 60c1698..8581326 100755 --- a/t/sign.t +++ b/t/sign.t @@ -4,20 +4,29 @@ . tap.sh -plan 10 +ts= +plan 15 checksig() { sig=$(printf '%s' "$sig" | tr -d '[[:space:]]') pk=$(printf '%s' "$pk" | tr -d '[[:space:]]') sk=$(printf '%s' "$sk" | tr -d '[[:space:]]') msg=$(printf '%s' "$msg" | tr -d '[[:space:]]') + msgname=$(echo "$msg" | cut -c1-8) - have=$(zpm sign -rhe -k $sk) - okstreq "$have" "$pk" - extract pk $(echo "$pk" | cut -c1-8) + have=$(zpm sign -re -K $sk) + okstreq "$have" "$pk" extract pk $(echo "$pk" | cut -c1-8) - have=$(zpm sign -rhs -k $sk -m "$msg") - - okstreq "$have" "$sig" signed msg $(echo "$msg" | cut -c1-8) + gensig=$(zpm sign -rbhs -K $sk -m "$msg") + okstreq "$gensig" "$sig" signed msg $msgname + + #diag "sig = $sig" + #zpm sign -v -P "$pk" -F "$sig" -bh -m "$msg" + #okexit verified $msgname with given + + #diag "sig = $gensig" + zpm sign -v -P "$pk" -F "$gensig" -bh -m "$msg" + okexit verified $msgname } sk=" -- 2.40.0