]> pd.if.org Git - zpackage/blobdiff - crypto/libeddsa/sign.c
package signature work
[zpackage] / crypto / libeddsa / sign.c
index 723c1f06b45cb9811614a214d1fc304f955a6f18..593f993c76e0c17366606179ddd49eafe35513a7 100644 (file)
  * -h message is hex encoded
  */
 
+/* chacha20 encryption: 256 bit key = 32 bytes */
+
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
+#include <ctype.h>
 #include <unistd.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 
 #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; i<len; i+=2) {
-               sscanf((const char *)src+i, "%02x", &x);
-               dst[i/2] = x;
+       switch(type) {
+               case SECRET_KEY:
+                       bytea_seckey(obj, buf);
+                       memset(buf, 0, sizeof buf);
+                       break;
+               case PUBLIC_KEY:
+                       bytea_pubkey(obj, buf);
+                       break;
+               case SIGNATURE:
+                       bytea_signature(obj, buf);
+                       break;
+               default:
+                       type = UNKNOWN;
+                       break;
        }
+
+       return type;
 }
 
 void *map(char *file, size_t *size) {
@@ -100,379 +651,1051 @@ void *map(char *file, size_t *size) {
        return m;
 }
 
-ssize_t readbytes(char *dest, char *file, size_t n) {
-       char *m;
-       size_t fs;
+/* read 512 bytes from fd, returns -1 for error, 512 for full read,
+ * < 512 for end of file
+ */
+ssize_t read512(int fd, void *dst) {
+       ssize_t bytes = 0;
+       ssize_t need = 512;
+       while (need) {
+               bytes = read(fd, dst + 512 - need, need);
+               if (bytes == -1) {
+                       return -1;
+               }
+               if (bytes == 0) {
+                       return 512 - need;
+               }
+               need -= bytes;
+       }
+       return 512;
+}
+
+int read_item(int fd, void *item) {
+       char buffer[512];
+       ssize_t bytes;
+       int type;
+
+       bytes = read512(fd, buffer);
+       if (bytes != 512) {
+               return 0;
+       }
+       type = deserialize(item, buffer, sizeof buffer);
+       return type;
+}
+
+int write_secret_key(int fd, struct secret_key *sk, int outputflags) {
+       uint8_t skh[256];
+       char serialized[512];
+       size_t outlen;
 
-       m = map(file, &fs);
-       if (!m) {
-               return -1;
+       if (outputflags & RAW) {
+               hex(serialized, sk->key, 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 <sys/time.h>
+#include <sys/resource.h>
+
 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;
 }