diff --git a/commandline.c b/commandline.c index 126923df..ddb48201 100644 --- a/commandline.c +++ b/commandline.c @@ -1971,6 +1971,17 @@ static void cli_output_identity(struct cli_context *context, const keyring_ident } } break; + case KEYTYPE_PUBLIC_TAG: + { + const char *name; + const unsigned char *value; + int length; + if (keyring_unpack_tag(kp, &name, &value, &length)==0){ + cli_field_name(context, name, ":"); + cli_put_string(context, alloca_toprint_quoted(-1, value, length, NULL), "\n"); + } + } + break; } } } @@ -2048,6 +2059,41 @@ int app_keyring_set_did(const struct cli_parsed *parsed, struct cli_context *con return r; } +static int app_keyring_set_tag(const struct cli_parsed *parsed, struct cli_context *context) +{ + const char *sidhex, *tag, *value; + cli_arg(parsed, "sid", &sidhex, str_is_subscriber_id, ""); + cli_arg(parsed, "tag", &tag, NULL, ""); + cli_arg(parsed, "value", &value, NULL, ""); + + if (!(keyring = keyring_open_instance_cli(parsed))) + return -1; + + sid_t sid; + if (str_to_sid_t(&sid, sidhex) == -1) + return WHY("str_to_sid_t() failed"); + + int cn=0,in=0,kp=0; + int r=0; + if (!keyring_find_sid(keyring, &cn, &in, &kp, &sid)) + r=WHY("No matching SID"); + else{ + int length = strlen(value); + if (keyring_set_public_tag(keyring->contexts[cn]->identities[in], tag, (const unsigned char*)value, length)) + r=WHY("Could not set tag value"); + else{ + if (keyring_commit(keyring)) + r=WHY("Could not write updated keyring record"); + else{ + cli_output_identity(context, keyring->contexts[cn]->identities[in]); + } + } + } + + keyring_free(keyring); + return r; +} + static int handle_pins(const struct cli_parsed *parsed, struct cli_context *context, int revoke) { const char *pin, *sid_hex; @@ -2687,6 +2733,8 @@ struct cli_schema command_line_options[]={ "Create a new identity in the keyring protected by the supplied PIN (empty PIN if not given)"}, {app_keyring_set_did,{"keyring", "set","did" KEYRING_PIN_OPTIONS,"","","",NULL}, 0, "Set the DID for the specified SID (must supply PIN to unlock the SID record in the keyring)"}, + {app_keyring_set_tag,{"keyring", "set","tag" KEYRING_PIN_OPTIONS,"","","",NULL}, 0, + "Set a named tag for the specified SID (must supply PIN to unlock the SID record in the keyring)"}, {app_id_self,{"id","self|peers|allpeers",NULL}, 0, "Return identity(s) as URIs of own node, or of known routable peers, or all known peers"}, {app_id_pin, {"id", "enter", "pin", "", NULL}, 0, diff --git a/keyring.c b/keyring.c index e242c402..7dcdffd4 100644 --- a/keyring.c +++ b/keyring.c @@ -430,6 +430,7 @@ static const char *keytype_str(unsigned ktype, const char *unknown) case KEYTYPE_CRYPTOSIGN: return "CRYPTOSIGN"; case KEYTYPE_RHIZOME: return "RHIZOME"; case KEYTYPE_DID: return "DID"; + case KEYTYPE_PUBLIC_TAG: return "PUBLIC_TAG"; default: return unknown; } } @@ -486,6 +487,12 @@ static int pack_private_only(const keypair *kp, struct rotbuf *rb) return 0; } +static int pack_public_only(const keypair *kp, struct rotbuf *rb) +{ + rotbuf_putbuf(rb, kp->public_key, kp->public_key_len); + return 0; +} + static int pack_private_public(const keypair *kp, struct rotbuf *rb) { rotbuf_putbuf(rb, kp->private_key, kp->private_key_len); @@ -638,6 +645,17 @@ static int unpack_private_only(keypair *kp, struct rotbuf *rb, int key_length) return 0; } +static int unpack_public_only(keypair *kp, struct rotbuf *rb, int key_length) +{ + if (!kp->public_key){ + kp->public_key_len = key_length; + if ((kp->public_key = emalloc(kp->public_key_len))==NULL) + return -1; + } + rotbuf_getbuf(rb, kp->public_key, kp->public_key_len); + return 0; +} + static int unpack_cryptobox(keypair *kp, struct rotbuf *rb, int key_length) { rotbuf_getbuf(rb, kp->private_key, kp->private_key_len); @@ -769,6 +787,16 @@ const struct keytype keytypes[] = { .unpacker = unpack_did_name, .dumper = dump_did_name, .loader = load_did_name + }, + [KEYTYPE_PUBLIC_TAG] = { + .private_key_size = 0, + .public_key_size = 0, // size is derived from the stored key length + .packed_size = 0, + .creator = NULL, // not included in a newly created identity + .packer = pack_public_only, + .unpacker = unpack_public_only, + .dumper = dump_private_public, + .loader = load_unknown } // ADD MORE KEY TYPES HERE }; @@ -843,13 +871,16 @@ static int keyring_pack_identity(const keyring_identity *id, unsigned char packe unsigned ktype = id->keypairs[kp]->type; const char *kts = keytype_str(ktype, "unknown"); int (*packer)(const keypair *, struct rotbuf *) = NULL; - size_t keypair_len; + size_t keypair_len=0; const struct keytype *kt = &keytypes[ktype]; if (ktype == 0x00) FATALF("ktype=0 in keypair kp=%u", kp); if (ktype < NELS(keytypes)) { packer = kt->packer; keypair_len = kt->packed_size; + if (keypair_len==0){ + keypair_len = id->keypairs[kp]->private_key_len + id->keypairs[kp]->public_key_len; + } } else { packer = pack_private_only; keypair_len = id->keypairs[kp]->private_key_len; @@ -922,16 +953,24 @@ static int cmp_keypair(const keypair *a, const keypair *b) { int c = a->type < b->type ? -1 : a->type > b->type ? 1 : 0; if (c == 0 && a->public_key_len) { - assert(a->public_key_len == b->public_key_len); assert(a->public_key != NULL); assert(b->public_key != NULL); - c = memcmp(a->public_key, b->public_key, a->public_key_len); + int len=a->public_key_len; + if (len>b->public_key_len) + len=b->public_key_len; + c = memcmp(a->public_key, b->public_key, len); + if (c==0 && a->public_key_len!=b->public_key_len) + c = a->public_key_len - b->public_key_len; } if (c == 0 && a->private_key_len) { - assert(a->private_key_len == b->private_key_len); assert(a->private_key != NULL); assert(b->private_key != NULL); - c = memcmp(a->private_key, b->private_key, a->private_key_len); + int len=a->private_key_len; + if (len>b->private_key_len) + len=b->private_key_len; + c = memcmp(a->private_key, b->private_key, len); + if (c==0 && a->private_key_len!=b->private_key_len) + c = a->private_key_len - b->private_key_len; } return c; } @@ -1454,6 +1493,88 @@ int keyring_find_did(const keyring_file *k, int *cn, int *in, int *kp, const cha return 0; } +int keyring_unpack_tag(keypair *key, const char **name, const unsigned char **value, int *length) +{ + int i; + for (i=0;ipublic_key_len-1;i++){ + if (key->public_key[i]==0){ + *name = (const char*)key->public_key; + *value = &key->public_key[i+1]; + *length = key->public_key_len - (i+1); + return 0; + } + } + return WHY("Did not find NULL values in tag"); +} + +int keyring_set_public_tag(keyring_identity *id, const char *name, const unsigned char *value, int length) +{ + int i; + for(i=0;ikeypair_count;i++){ + const char *tag_name; + const unsigned char *tag_value; + int tag_length; + if (id->keypairs[i]->type==KEYTYPE_PUBLIC_TAG && + keyring_unpack_tag(id->keypairs[i], &tag_name, &tag_value, &tag_length)==0 && + strcmp(tag_name, name)==0) { + if (config.debug.keyring) + DEBUG("Found existing public tag"); + break; + } + } + + if (i >= PKR_MAX_KEYPAIRS) + return WHY("Too many key pairs"); + + /* allocate if needed */ + if (i >= id->keypair_count) { + if (config.debug.keyring) + DEBUGF("Creating new public tag @%d", i); + if ((id->keypairs[i] = keyring_alloc_keypair(KEYTYPE_PUBLIC_TAG, 0)) == NULL) + return -1; + ++id->keypair_count; + } + + if (id->keypairs[i]->public_key) + free(id->keypairs[i]->public_key); + + int name_len=strlen(name)+1; + id->keypairs[i]->public_key_len = name_len+length; + id->keypairs[i]->public_key = emalloc(id->keypairs[i]->public_key_len); + if (!id->keypairs[i]->public_key) + return -1; + bcopy(name, id->keypairs[i]->public_key, name_len); + bcopy(value, &id->keypairs[i]->public_key[name_len], length); + if (config.debug.keyring) + dump("New tag", id->keypairs[i]->public_key, id->keypairs[i]->public_key_len); + return 0; +} + +int keyring_find_public_tag(const keyring_file *k, int *cn, int *in, int *kp, const char *name, const unsigned char **value, int *length) +{ + for(;keyring_next_keytype(k,cn,in,kp,KEYTYPE_PUBLIC_TAG);++(*kp)) { + keypair *keypair=k->contexts[*cn]->identities[*in]->keypairs[*kp]; + const char *tag_name; + if (!keyring_unpack_tag(keypair, &tag_name, value, length) && + strcmp(name, tag_name)==0){ + return 1; + } + } + *value=NULL; + return 0; +} + +int keyring_find_public_tag_value(const keyring_file *k, int *cn, int *in, int *kp, const char *name, const unsigned char *value, int length) +{ + const unsigned char *stored_value; + int stored_length; + for(;keyring_find_public_tag(k, cn, in, kp, name, &stored_value, &stored_length);++(*kp)) { + if (stored_length == length && memcmp(value, stored_value, length)==0) + return 1; + } + return 0; +} + int keyring_identity_find_keytype(const keyring_file *k, int cn, int in, int keytype) { int kp; diff --git a/keyring.h b/keyring.h index fbc7b3ab..86fe87fd 100644 --- a/keyring.h +++ b/keyring.h @@ -66,6 +66,9 @@ void keyring_release_identity(keyring_file *k, int cn, int id); and keep them private if people so desire */ #define KEYTYPE_DID 0x04 +/* Arbitrary name / value pairs */ +#define KEYTYPE_PUBLIC_TAG 0x05 + /* handle to keyring file for use in running instance */ extern keyring_file *keyring; @@ -97,4 +100,9 @@ int keyring_mapping_request(keyring_file *k, struct overlay_frame *frame, overla int keyring_send_unlock(struct subscriber *subscriber); void keyring_release_subscriber(keyring_file *k, const sid_t *sid); +int keyring_set_public_tag(keyring_identity *id, const char *name, const unsigned char *value, int length); +int keyring_find_public_tag(const keyring_file *k, int *cn, int *in, int *kp, const char *name, const unsigned char **value, int *length); +int keyring_find_public_tag_value(const keyring_file *k, int *cn, int *in, int *kp, const char *name, const unsigned char *value, int length); +int keyring_unpack_tag(keypair *key, const char **name, const unsigned char **value, int *length); + #endif // __SERVALD_KEYRING_H diff --git a/str.h b/str.h index 7f9827be..3d62a9cc 100644 --- a/str.h +++ b/str.h @@ -104,7 +104,9 @@ size_t toprint_len(const char *srcBuf, size_t srcBytes, const char quotes[2]); size_t toprint_str_len(const char *srcStr, const char quotes[2]); size_t strn_fromprint(unsigned char *dst, size_t dstsiz, const char *src, size_t srclen, char endquote, const char **afterp); -#define alloca_toprint(dstlen,buf,len) toprint((char *)alloca((dstlen) == -1 ? toprint_len((const char *)(buf),(len), "``") + 1 : (dstlen)), (dstlen), (const char *)(buf), (len), "``") +#define alloca_toprint_quoted(dstlen,buf,len,quotes) toprint((char *)alloca((dstlen) == -1 ? toprint_len((const char *)(buf),(len), (quotes)) + 1 : (dstlen)), (dstlen), (const char *)(buf), (len), (quotes)) +#define alloca_toprint(dstlen,buf,len) alloca_toprint_quoted(dstlen,buf,len,"``") + #define alloca_str_toprint_quoted(str, quotes) toprint_str((char *)alloca(toprint_str_len((str), (quotes)) + 1), -1, (str), (quotes)) #define alloca_str_toprint(str) alloca_str_toprint_quoted(str, "``") diff --git a/tests/keyring b/tests/keyring index f572bf27..7c6d438b 100755 --- a/tests/keyring +++ b/tests/keyring @@ -71,6 +71,28 @@ test_DidName() { assertStdoutGrep --stderr --matches=1 "^$SID:123456:Display Name\$" } +doc_SetTag="Set a named tag against an identity" +test_SetTag() { + executeOk_servald keyring add '' + assertStdoutGrep --matches=1 "^sid:" + assertStdoutLineCount '==' 1 + extract_stdout_keyvalue SID sid "$rexp_sid" + executeOk_servald keyring set tag "$SID" 'tag1' 'First Value' + assertStdoutGrep --matches=1 "^sid:$SID\$" + assertStdoutGrep --matches=1 "^tag1:First Value\$" + assertStdoutLineCount '==' 2 + executeOk_servald keyring set tag "$SID" 'tag2' 'Second Value' + assertStdoutGrep --matches=1 "^sid:$SID\$" + assertStdoutGrep --matches=1 "^tag1:First Value\$" + assertStdoutGrep --matches=1 "^tag2:Second Value\$" + assertStdoutLineCount '==' 3 + executeOk_servald keyring set tag "$SID" 'tag1' 'Third Value' + assertStdoutGrep --matches=1 "^sid:$SID\$" + assertStdoutGrep --matches=1 "^tag1:Third Value\$" + assertStdoutGrep --matches=1 "^tag2:Second Value\$" + assertStdoutLineCount '==' 3 +} + doc_Pinless="No keyring PIN with PIN-less identities" test_Pinless() { executeOk_servald keyring add ''