mirror of
https://github.com/servalproject/serval-dna.git
synced 2024-12-20 05:37:57 +00:00
Redesign the Keyring REST API (fixes #132)
The new API follows REST rules by using the proper request verbs: POST, PUT, PATCH and DELETE, instead of just GET. The legacy GET-only API is still supported for backward compatibility, but not longer tested or documented. Add a new query-single-identity operation. Implement the lock-single-identity operation, which until now had been documented but not yet implemented. Whenever a single identity is locked (released), any other unlocked identities with the same PIN are flagged to indicate that the PIN is not "fully" unlocked, so that the next time the PIN is entered, the slot decryption is re-tried for non-loaded identities, and the locked identity will be unlocked again. Update the 'keyring' and 'keyringrestful' test scripts: - refactored to reduce curl command-line clutter in test cases - now tests the redesigned request verbs and paths - added a test for GET /restful/keyring/SID - added a test for PUT /restful/keyring/SID/lock
This commit is contained in:
parent
8242ca0a00
commit
98ec1c9608
@ -158,7 +158,8 @@ Keyring REST API operations
|
|||||||
Returns a list of all currently unlocked identities, one identity per row in
|
Returns a list of all currently unlocked identities, one identity per row in
|
||||||
[JSON table][] format. The following parameters are recognised:
|
[JSON table][] format. The following parameters are recognised:
|
||||||
|
|
||||||
* **pin**: see [identity unlocking](#identity-unlocking)
|
* **pin** (optional) = a passphrase to unlock identities prior to listing;
|
||||||
|
see [identity unlocking](#identity-unlocking)
|
||||||
|
|
||||||
The table columns are:
|
The table columns are:
|
||||||
|
|
||||||
@ -169,46 +170,67 @@ The table columns are:
|
|||||||
| `did` | the optional [DID](#did) (telephone number); `null` if none is assigned |
|
| `did` | the optional [DID](#did) (telephone number); `null` if none is assigned |
|
||||||
| `name` | the optional string [Name](#name); `null` if none is assigned |
|
| `name` | the optional string [Name](#name); `null` if none is assigned |
|
||||||
|
|
||||||
### GET /restful/keyring/add
|
### POST /restful/keyring/add
|
||||||
|
|
||||||
Creates a new identity with a random [SID](#serval-id). The following
|
Creates a new identity with a random [SID](#serval-id). This request does not
|
||||||
parameters are recognised:
|
accept parameters in the request body (eg, using a [Content-Type][] of
|
||||||
|
[multipart/form-data][]), but does accept the following [query parameters][] in
|
||||||
|
the *path*:
|
||||||
|
|
||||||
* **pin**: if present, then the new identity is protected by the given
|
* **pin** (optional) = the passphrase for the new identity; if present, then
|
||||||
passphrase; see [identity unlocking](#identity-unlocking) -- note that the
|
the new identity is protected by the given passphrase; see [identity
|
||||||
newly created identity is already unlocked when this request returns,
|
unlocking](#identity-unlocking) -- note that the newly created identity is
|
||||||
because the passphrase has been added to the PIN cache
|
already unlocked when this request returns, because the passphrase has been
|
||||||
* **did**: the DID (phone number); empty or absent to indicate no DID,
|
added to the PIN cache
|
||||||
otherwise must conform to the rules for [DID](#did)
|
|
||||||
* **name**: the name; empty or absent to specify no name, otherwise must
|
* **did** (optional) = the [DID](#did) of the new identity; empty or absent
|
||||||
conform to the rules for [Name](#name)
|
to indicate no DID, otherwise must be valid
|
||||||
|
|
||||||
|
* **name** (optional) = the [Name](#name) of the new identity; empty or
|
||||||
|
absent to specify no name, otherwise must be valid
|
||||||
|
|
||||||
If any parameter contains an invalid value then the request returns [400 Bad
|
If any parameter contains an invalid value then the request returns [400 Bad
|
||||||
Request][400]. Returns [201 Created][201] if an identity is created; the [JSON
|
Request][400]. Returns [201 Created][201] if an identity is created; the [JSON
|
||||||
result](#keyring-json-result) describes the identity that was created.
|
result](#keyring-json-result) describes the identity that was created.
|
||||||
|
|
||||||
### GET /restful/keyring/SID/remove
|
### GET /restful/keyring/SID
|
||||||
|
|
||||||
Removes an existing identity with a given [SID](#serval-id). The following
|
Return the details of an existing unlocked identity with a given
|
||||||
parameters are recognised:
|
[SID](#serval-id). The following [query parameters][] are recognised:
|
||||||
|
|
||||||
* **pin**: see [identity unlocking](#identity-unlocking)
|
* **pin** (optional) = a passphrase to unlock the identity prior to querying
|
||||||
|
it; see [identity unlocking](#identity-unlocking)
|
||||||
|
|
||||||
|
If there is no unlocked identity with the given SID, this request returns [404
|
||||||
|
Not Found][404]. Otherwise it returns [200 OK][200] and the [JSON
|
||||||
|
result](#keyring-json-result) describes the identity.
|
||||||
|
|
||||||
|
### DELETE /restful/keyring/SID
|
||||||
|
|
||||||
|
Removes an existing unlocked identity with a given [SID](#serval-id). The
|
||||||
|
following [query parameters][] are recognised:
|
||||||
|
|
||||||
|
* **pin** (optional) = a passphrase to unlock the identity prior to deleting
|
||||||
|
it; see [identity unlocking](#identity-unlocking)
|
||||||
|
|
||||||
If there is no unlocked identity with the given SID, this request returns [404
|
If there is no unlocked identity with the given SID, this request returns [404
|
||||||
Not Found][404]. Otherwise it returns [200 OK][200] and the [JSON
|
Not Found][404]. Otherwise it returns [200 OK][200] and the [JSON
|
||||||
result](#keyring-json-result) describes the identity that was removed.
|
result](#keyring-json-result) describes the identity that was removed.
|
||||||
|
|
||||||
### GET /restful/keyring/SID/set
|
### PATCH /restful/keyring/SID
|
||||||
|
|
||||||
Sets and/or clears the [DID](#did) and/or [Name](#name) of the unlocked
|
Sets and/or clears the [DID](#did) and/or [Name](#name) of the unlocked
|
||||||
identity that has the given [SID](#serval-id). The following parameters are
|
identity that has the given [SID](#serval-id). The following [query
|
||||||
recognised:
|
parameters][] are recognised:
|
||||||
|
|
||||||
* **pin**: see [identity unlocking](#identity-unlocking)
|
* **pin** (optional) = a passphrase to unlock the identity prior to deleting
|
||||||
* **did**: the DID (phone number); empty to clear the DID, otherwise must
|
it; see [identity unlocking](#identity-unlocking)
|
||||||
conform to the rules for [DID](#did)
|
|
||||||
* **name**: the name; empty to clear the name, otherwise must conform to the
|
* **did** (optional) = the [DID](#did) to assign to the identity; empty to
|
||||||
rules for [Name](#name)
|
clear the DID, otherwise must be valid
|
||||||
|
|
||||||
|
* **name** (optional) = the [Name](#name) to assign to the identity; empty to
|
||||||
|
clear the name, otherwise must be valid
|
||||||
|
|
||||||
If a parameter is missing, then the corresponding field of the identity is left
|
If a parameter is missing, then the corresponding field of the identity is left
|
||||||
unchanged. If a parameter is set to an empty string, then the corresponding
|
unchanged. If a parameter is set to an empty string, then the corresponding
|
||||||
@ -218,7 +240,7 @@ If any parameter contains an invalid value then the request returns [400 Bad
|
|||||||
Request][400]. If there is no unlocked identity with the given SID, this
|
Request][400]. If there is no unlocked identity with the given SID, this
|
||||||
request returns [404 Not Found][404].
|
request returns [404 Not Found][404].
|
||||||
|
|
||||||
### GET /restful/keyring/SID/lock
|
### PUT /restful/keyring/SID/lock
|
||||||
|
|
||||||
Locks an existing identity with a given [SID](#serval-id).
|
Locks an existing identity with a given [SID](#serval-id).
|
||||||
|
|
||||||
@ -229,7 +251,7 @@ result](#keyring-json-result) describes the identity that was locked.
|
|||||||
|
|
||||||
-----
|
-----
|
||||||
**Copyright 2015 Serval Project Inc.**
|
**Copyright 2015 Serval Project Inc.**
|
||||||
**Copyright 2016-2017 Flinders University**
|
**Copyright 2016-2018 Flinders University**
|
||||||
![CC-BY-4.0](./cc-by-4.0.png)
|
![CC-BY-4.0](./cc-by-4.0.png)
|
||||||
Available under the [Creative Commons Attribution 4.0 International licence][CC BY 4.0].
|
Available under the [Creative Commons Attribution 4.0 International licence][CC BY 4.0].
|
||||||
|
|
||||||
|
315
keyring.c
315
keyring.c
@ -131,7 +131,7 @@ static int keyring_load(keyring_file *k, const char *pin)
|
|||||||
keyring_bam **b=&k->bam;
|
keyring_bam **b=&k->bam;
|
||||||
size_t offset = 0;
|
size_t offset = 0;
|
||||||
while (offset < k->file_size) {
|
while (offset < k->file_size) {
|
||||||
/* Read bitmap from slab. If offset is zero, read the salt */
|
/* Read allocmap from slab. If offset is zero, read the salt */
|
||||||
if (fseeko(k->file, (off_t)offset, SEEK_SET)) {
|
if (fseeko(k->file, (off_t)offset, SEEK_SET)) {
|
||||||
WHYF_perror("fseeko(%d, %zd, SEEK_SET)", fileno(k->file), offset);
|
WHYF_perror("fseeko(%d, %zd, SEEK_SET)", fileno(k->file), offset);
|
||||||
return WHY("Could not seek to BAM in keyring file");
|
return WHY("Could not seek to BAM in keyring file");
|
||||||
@ -140,13 +140,13 @@ static int keyring_load(keyring_file *k, const char *pin)
|
|||||||
if (!*b)
|
if (!*b)
|
||||||
return WHYF("Could not allocate keyring_bam structure");
|
return WHYF("Could not allocate keyring_bam structure");
|
||||||
(*b)->file_offset = offset;
|
(*b)->file_offset = offset;
|
||||||
/* Read bitmap */
|
/* Read allocation bitmap */
|
||||||
int r = fread((*b)->bitmap, KEYRING_BAM_BYTES, 1, k->file);
|
int r = fread((*b)->allocmap, KEYRING_BAM_BYTES, 1, k->file);
|
||||||
if (r != 1) {
|
if (r != 1) {
|
||||||
WHYF_perror("fread(%p, %zd, 1, %d)", (*b)->bitmap, KEYRING_BAM_BYTES, fileno(k->file));
|
WHYF_perror("fread(%p, %zd, 1, %d)", (*b)->allocmap, KEYRING_BAM_BYTES, fileno(k->file));
|
||||||
return WHYF("Could not read BAM from keyring file");
|
return WHYF("Could not read BAM from keyring file");
|
||||||
}
|
}
|
||||||
/* Read salt if this is the first bitmap block.
|
/* Read salt if this is the first allocmap block.
|
||||||
We setup a context for this self-supplied key-ring salt.
|
We setup a context for this self-supplied key-ring salt.
|
||||||
(other keyring salts may be provided later on, resulting in
|
(other keyring salts may be provided later on, resulting in
|
||||||
multiple contexts being loaded) */
|
multiple contexts being loaded) */
|
||||||
@ -169,6 +169,52 @@ static int keyring_load(keyring_file *k, const char *pin)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static unsigned is_slot_allocated(const keyring_file *k, unsigned slot)
|
||||||
|
{
|
||||||
|
assert(slot != 0);
|
||||||
|
assert(slot < KEYRING_BAM_BITS);
|
||||||
|
unsigned position = slot & (KEYRING_BAM_BITS - 1);
|
||||||
|
unsigned byte = position >> 3;
|
||||||
|
unsigned bit = position & 7;
|
||||||
|
return (k->bam->allocmap[byte] & (1 << bit)) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned is_slot_loadable(const keyring_file *k, unsigned slot)
|
||||||
|
{
|
||||||
|
assert(slot != 0);
|
||||||
|
assert(slot < KEYRING_BAM_BITS);
|
||||||
|
unsigned position = slot & (KEYRING_BAM_BITS - 1);
|
||||||
|
unsigned byte = position >> 3;
|
||||||
|
unsigned bit = position & 7;
|
||||||
|
return (k->bam->allocmap[byte] & (1 << bit)) && !(k->bam->loadmap[byte] & (1 << bit));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mark_slot_allocated(keyring_file *k, unsigned slot, int allocated)
|
||||||
|
{
|
||||||
|
assert(slot != 0);
|
||||||
|
assert(slot < KEYRING_BAM_BITS);
|
||||||
|
unsigned position = slot & (KEYRING_BAM_BITS - 1);
|
||||||
|
unsigned byte = position >> 3;
|
||||||
|
unsigned bit = position & 7;
|
||||||
|
if (allocated)
|
||||||
|
k->bam->allocmap[byte] |= (1 << bit);
|
||||||
|
else
|
||||||
|
k->bam->allocmap[byte] &= ~(1 << bit);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mark_slot_loaded(keyring_file *k, unsigned slot, int loaded)
|
||||||
|
{
|
||||||
|
assert(slot != 0);
|
||||||
|
assert(slot < KEYRING_BAM_BITS);
|
||||||
|
unsigned position = slot & (KEYRING_BAM_BITS - 1);
|
||||||
|
unsigned byte = position >> 3;
|
||||||
|
unsigned bit = position & 7;
|
||||||
|
if (loaded)
|
||||||
|
k->bam->loadmap[byte] |= (1 << bit);
|
||||||
|
else
|
||||||
|
k->bam->loadmap[byte] &= ~(1 << bit);
|
||||||
|
}
|
||||||
|
|
||||||
void keyring_iterator_start(keyring_file *k, keyring_iterator *it)
|
void keyring_iterator_start(keyring_file *k, keyring_iterator *it)
|
||||||
{
|
{
|
||||||
assert(k);
|
assert(k);
|
||||||
@ -321,34 +367,72 @@ void keyring_free(keyring_file *k)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int keyring_release_identities_by_pin(keyring_file *f, const char *pin)
|
/* Release ("lock") all the identities that have a given PIN, by unlinking them from the in-memory
|
||||||
|
* cache list. DOES call keyring_free_identity(id) on each released identity. When this function
|
||||||
|
* returns, there are no unlocked identities with the given PIN.
|
||||||
|
*/
|
||||||
|
void keyring_release_identities_by_pin(keyring_file *k, const char *pin)
|
||||||
{
|
{
|
||||||
keyring_identity **i=&f->identities;
|
INFO("release identity by PIN");
|
||||||
|
DEBUGF(keyring, "release identity PIN=%s", alloca_str_toprint(pin));
|
||||||
|
keyring_identity **i=&k->identities;
|
||||||
while(*i){
|
while(*i){
|
||||||
keyring_identity *id = (*i);
|
keyring_identity *id = (*i);
|
||||||
if (id->PKRPin && strcmp(id->PKRPin, pin) == 0){
|
if (id->PKRPin && strcmp(id->PKRPin, pin) == 0) {
|
||||||
|
INFOF("release identity slot=%u SID=%s", id->slot, alloca_tohex_sid_t(*id->box_pk));
|
||||||
(*i) = id->next;
|
(*i) = id->next;
|
||||||
|
mark_slot_loaded(k, id->slot, 0);
|
||||||
keyring_free_identity(id);
|
keyring_free_identity(id);
|
||||||
}else{
|
}else{
|
||||||
i=&id->next;
|
i=&id->next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Release the given single identity by unlinking it from the in-memory cache list. To ensure that
|
||||||
|
* re-entering the identity's PIN will unlock it again, mark any other unlocked identities that have
|
||||||
|
* the same PIN as no longer "fully" unlocked, so that keyring_enter_pin() will re-try the
|
||||||
|
* decryption. Does NOT call keyring_free_identity(id), so the identity's in-memory structure
|
||||||
|
* remain intact; the caller is responsible for freeing the identity.
|
||||||
|
*/
|
||||||
|
void keyring_release_identity(keyring_file *k, keyring_identity *id)
|
||||||
|
{
|
||||||
|
INFOF("release identity slot=%u SID=%s", id->slot, alloca_tohex_sid_t(*id->box_pk));
|
||||||
|
keyring_identity **prev = NULL;
|
||||||
|
keyring_identity **i;
|
||||||
|
// find the identity in the keyring's linked list, so it can be unlinked
|
||||||
|
for (i = &k->identities; *i; i = &(*i)->next) {
|
||||||
|
keyring_identity *iid = *i;
|
||||||
|
if (iid == id)
|
||||||
|
prev = i;
|
||||||
|
// mark any other identities that have the same PIN as no longer fully unlocked
|
||||||
|
if (id->PKRPin && iid->PKRPin && strcmp(id->PKRPin, iid->PKRPin) == 0)
|
||||||
|
iid->is_fully_unlocked = 0;
|
||||||
|
}
|
||||||
|
assert(prev); // the identity being released must be in the keyring
|
||||||
|
(*prev) = id->next;
|
||||||
|
mark_slot_loaded(k, id->slot, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Release the single identity with the given SID, by unlinking it from the in-memory cache list.
|
||||||
|
* See the comment on keyring_release_identity() regarding PIN management. Returns zero if an
|
||||||
|
* identity was found and released, -1 if no such identity was found. Unlike
|
||||||
|
* keyring_release_identity(), this function DOES call keyring_free_identity(id) on any released
|
||||||
|
* identity before returning.
|
||||||
|
*/
|
||||||
int keyring_release_subscriber(keyring_file *k, const sid_t *sid)
|
int keyring_release_subscriber(keyring_file *k, const sid_t *sid)
|
||||||
{
|
{
|
||||||
keyring_identity **i=&k->identities;
|
INFOF("release identity SID=%s", alloca_tohex_sid_t(*sid));
|
||||||
while(*i){
|
keyring_identity **i;
|
||||||
keyring_identity *id = (*i);
|
for (i = &k->identities; *i; i = &(*i)->next) {
|
||||||
if (cmp_sid_t(id->box_pk,sid)==0){
|
keyring_identity *iid = *i;
|
||||||
(*i) = id->next;
|
if (cmp_sid_t(iid->box_pk, sid) == 0) {
|
||||||
keyring_free_identity(id);
|
keyring_release_identity(k, iid);
|
||||||
|
keyring_free_identity(iid);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
i=&id->next;
|
|
||||||
}
|
}
|
||||||
return WHYF("Keyring entry for %s not found", alloca_tohex_sid_t(*sid));
|
return WHYF("cannot release non-existent keyring entry SID=%s", alloca_tohex_sid_t(*sid));
|
||||||
}
|
}
|
||||||
|
|
||||||
void keyring_free_identity(keyring_identity *id)
|
void keyring_free_identity(keyring_identity *id)
|
||||||
@ -1064,7 +1148,7 @@ static int keyring_identity_add_keypair(keyring_identity *id, keypair *kp)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static keyring_identity *keyring_unpack_identity(unsigned char *slot, const char *pin)
|
static keyring_identity *keyring_unpack_identity(unsigned char *slot_data, const char *pin)
|
||||||
{
|
{
|
||||||
/* Skip salt and MAC */
|
/* Skip salt and MAC */
|
||||||
keyring_identity *id = emalloc_zero(sizeof(keyring_identity));
|
keyring_identity *id = emalloc_zero(sizeof(keyring_identity));
|
||||||
@ -1073,13 +1157,13 @@ static keyring_identity *keyring_unpack_identity(unsigned char *slot, const char
|
|||||||
if (pin && *pin)
|
if (pin && *pin)
|
||||||
id->PKRPin = str_edup(pin);
|
id->PKRPin = str_edup(pin);
|
||||||
// The two bytes immediately following the MAC describe the rotation offset.
|
// The two bytes immediately following the MAC describe the rotation offset.
|
||||||
uint16_t rotation = (slot[PKR_SALT_BYTES + PKR_MAC_BYTES] << 8) | slot[PKR_SALT_BYTES + PKR_MAC_BYTES + 1];
|
uint16_t rotation = (slot_data[PKR_SALT_BYTES + PKR_MAC_BYTES] << 8) | slot_data[PKR_SALT_BYTES + PKR_MAC_BYTES + 1];
|
||||||
/* Pack the key pairs into the rest of the slot as a rotated buffer. */
|
/* Pack the key pairs into the rest of the slot as a rotated buffer. */
|
||||||
struct rotbuf rbuf;
|
struct rotbuf rbuf;
|
||||||
rotbuf_init(&rbuf,
|
rotbuf_init(&rbuf,
|
||||||
slot + PKR_SALT_BYTES + PKR_MAC_BYTES + 2,
|
slot_data + PKR_SALT_BYTES + PKR_MAC_BYTES + 2,
|
||||||
KEYRING_PAGE_SIZE - (PKR_SALT_BYTES + PKR_MAC_BYTES + 2),
|
KEYRING_PAGE_SIZE - (PKR_SALT_BYTES + PKR_MAC_BYTES + 2),
|
||||||
rotation);
|
rotation);
|
||||||
while (!rbuf.wrap) {
|
while (!rbuf.wrap) {
|
||||||
struct rotbuf rbo = rbuf;
|
struct rotbuf rbo = rbuf;
|
||||||
unsigned char ktype = rotbuf_getc(&rbuf);
|
unsigned char ktype = rotbuf_getc(&rbuf);
|
||||||
@ -1231,130 +1315,117 @@ static int keyring_finalise_identity(uint8_t *dirty, keyring_identity *id)
|
|||||||
* munged, we then need to verify that the slot is valid, and if so unpack the details of the
|
* munged, we then need to verify that the slot is valid, and if so unpack the details of the
|
||||||
* identity.
|
* identity.
|
||||||
*/
|
*/
|
||||||
static int keyring_decrypt_pkr(keyring_file *k, const char *pin, int slot_number)
|
static int keyring_decrypt_pkr(keyring_file *k, const char *pin, int slot)
|
||||||
{
|
{
|
||||||
DEBUGF(keyring, "k=%p, pin=%s slot_number=%d", k, alloca_str_toprint(pin), slot_number);
|
DEBUGF(keyring, "k=%p pin=%s slot=%d", k, alloca_str_toprint(pin), slot);
|
||||||
unsigned char slot[KEYRING_PAGE_SIZE];
|
unsigned char slot_data[KEYRING_PAGE_SIZE];
|
||||||
keyring_identity *id=NULL;
|
keyring_identity *id=NULL;
|
||||||
|
|
||||||
/* 1. Read slot. */
|
/* 1. Read slot. */
|
||||||
if (fseeko(k->file,slot_number*KEYRING_PAGE_SIZE,SEEK_SET))
|
if (fseeko(k->file,slot*KEYRING_PAGE_SIZE,SEEK_SET))
|
||||||
return WHY_perror("fseeko");
|
return WHY_perror("fseeko");
|
||||||
if (fread(slot, KEYRING_PAGE_SIZE, 1, k->file) != 1)
|
if (fread(slot_data, KEYRING_PAGE_SIZE, 1, k->file) != 1)
|
||||||
return WHY_perror("fread");
|
return WHY_perror("fread");
|
||||||
/* 2. Decrypt data from slot. */
|
/* 2. Decrypt data from slot. */
|
||||||
if (keyring_munge_block(slot, KEYRING_PAGE_SIZE, k->KeyRingSalt, k->KeyRingSaltLen, k->KeyRingPin, pin)) {
|
if (keyring_munge_block(slot_data, KEYRING_PAGE_SIZE, k->KeyRingSalt, k->KeyRingSaltLen, k->KeyRingPin, pin)) {
|
||||||
WHYF("keyring_munge_block() failed, slot=%u", slot_number);
|
WHYF("keyring_munge_block() failed, slot=%u", slot);
|
||||||
goto kdp_safeexit;
|
goto kdp_safeexit;
|
||||||
}
|
}
|
||||||
/* 3. Unpack contents of slot into a new identity in the provided context. */
|
/* 3. Unpack contents of slot into a new identity in the provided context. */
|
||||||
DEBUGF(keyring, "unpack slot %u", slot_number);
|
DEBUGF(keyring, "unpack slot %u", slot);
|
||||||
if (((id = keyring_unpack_identity(slot, pin)) == NULL))
|
if (((id = keyring_unpack_identity(slot_data, pin)) == NULL))
|
||||||
goto kdp_safeexit; // Not a valid slot
|
goto kdp_safeexit; // Not a valid slot
|
||||||
id->slot = slot_number;
|
id->slot = slot;
|
||||||
/* 4. Verify that slot is self-consistent (check MAC) */
|
/* 4. Verify that slot is self-consistent (check MAC) */
|
||||||
unsigned char hash[crypto_hash_sha512_BYTES];
|
unsigned char hash[crypto_hash_sha512_BYTES];
|
||||||
if (keyring_identity_mac(id, slot, hash))
|
if (keyring_identity_mac(id, slot_data, hash))
|
||||||
goto kdp_safeexit;
|
goto kdp_safeexit;
|
||||||
/* compare hash to record */
|
/* compare hash to record */
|
||||||
if (memcmp(hash, &slot[PKR_SALT_BYTES], crypto_hash_sha512_BYTES)) {
|
if (memcmp(hash, &slot_data[PKR_SALT_BYTES], crypto_hash_sha512_BYTES)) {
|
||||||
DEBUGF(keyring, "slot %u is not valid (MAC mismatch)", slot_number);
|
DEBUGF(keyring, "slot %u is not valid (MAC mismatch)", slot);
|
||||||
DEBUG_dump(keyring, "computed",hash,crypto_hash_sha512_BYTES);
|
DEBUG_dump(keyring, "computed",hash,crypto_hash_sha512_BYTES);
|
||||||
DEBUG_dump(keyring, "stored",&slot[PKR_SALT_BYTES],crypto_hash_sha512_BYTES);
|
DEBUG_dump(keyring, "stored",&slot_data[PKR_SALT_BYTES],crypto_hash_sha512_BYTES);
|
||||||
goto kdp_safeexit;
|
goto kdp_safeexit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keyring_commit_identity(k, id)!=1)
|
if (!keyring_commit_identity(k, id))
|
||||||
goto kdp_safeexit;
|
goto kdp_safeexit;
|
||||||
|
|
||||||
|
INFOF("unlocked identity slot=%u SID=%s", id->slot, alloca_tohex_sid_t(*id->box_pk));
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
kdp_safeexit:
|
kdp_safeexit:
|
||||||
/* Clean up any potentially sensitive data before exiting */
|
/* Clean up any potentially sensitive data before exiting */
|
||||||
bzero(slot,KEYRING_PAGE_SIZE);
|
bzero(slot_data,KEYRING_PAGE_SIZE);
|
||||||
bzero(hash,crypto_hash_sha512_BYTES);
|
bzero(hash,crypto_hash_sha512_BYTES);
|
||||||
if (id)
|
if (id)
|
||||||
keyring_free_identity(id);
|
keyring_free_identity(id);
|
||||||
return 1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Try all valid slots with the PIN and see if we find any identities with that PIN.
|
/* Try all valid slots with the PIN and see if we find any identities with that PIN. We might find
|
||||||
We might find more than one. */
|
* none, or more than one. Returns the total number of unlocked identities with the given PIN,
|
||||||
int keyring_enter_pin(keyring_file *k, const char *pin)
|
* including any that were already unlocked before this function was called.
|
||||||
|
*/
|
||||||
|
unsigned keyring_enter_pin(keyring_file *k, const char *pin)
|
||||||
{
|
{
|
||||||
IN();
|
IN();
|
||||||
DEBUGF(keyring, "k=%p, pin=%s", k, alloca_str_toprint(pin));
|
DEBUGF(keyring, "k=%p, pin=%s", k, alloca_str_toprint(pin));
|
||||||
if (!pin) pin="";
|
if (!pin) pin="";
|
||||||
|
|
||||||
// Check if PIN is already entered.
|
unsigned identity_count = 0;
|
||||||
int identitiesFound=0;
|
bool_t is_fully_unlocked = 1;
|
||||||
keyring_identity *id = k->identities;
|
|
||||||
while(id){
|
|
||||||
if (pin && *pin){
|
|
||||||
if (id->PKRPin && strcmp(id->PKRPin, pin) == 0)
|
|
||||||
identitiesFound++;
|
|
||||||
}else if(!id->PKRPin)
|
|
||||||
identitiesFound++;
|
|
||||||
id=id->next;
|
|
||||||
}
|
|
||||||
if (identitiesFound)
|
|
||||||
RETURN(identitiesFound);
|
|
||||||
|
|
||||||
unsigned slot;
|
|
||||||
for(slot=0;slot<k->file_size/KEYRING_PAGE_SIZE;slot++) {
|
|
||||||
/* slot zero is the BAM and salt, so skip it */
|
|
||||||
if (slot&(KEYRING_BAM_BITS-1)) {
|
|
||||||
/* Not a BAM slot, so examine */
|
|
||||||
size_t file_offset = slot * KEYRING_PAGE_SIZE;
|
|
||||||
|
|
||||||
/* See if this part of the keyring file is organised */
|
// check if PIN is already entered and all identities with that PIN are currently
|
||||||
keyring_bam *b=k->bam;
|
{
|
||||||
while (b && (file_offset >= b->file_offset + KEYRING_SLAB_SIZE))
|
keyring_identity *id;
|
||||||
b=b->next;
|
for (id = k->identities; id; id = id->next) {
|
||||||
if (!b) continue;
|
if (*pin ? (id->PKRPin && strcmp(id->PKRPin, pin) == 0) : (!id->PKRPin)) {
|
||||||
|
++identity_count;
|
||||||
/* Now see if slot is marked in-use. No point checking unallocated slots,
|
if (!id->is_fully_unlocked)
|
||||||
especially since the cost can be upto a second of CPU time on a phone. */
|
is_fully_unlocked = 0;
|
||||||
int position=slot&(KEYRING_BAM_BITS-1);
|
|
||||||
int byte=position>>3;
|
|
||||||
int bit=position&7;
|
|
||||||
if (b->bitmap[byte]&(1<<bit)) {
|
|
||||||
/* Slot is occupied, so check it.
|
|
||||||
We have to check it for each keyring context (ie keyring pin) */
|
|
||||||
if (keyring_decrypt_pkr(k, pin, slot) == 0)
|
|
||||||
++identitiesFound;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (k->dirty)
|
// try to decrypt if there are no identities already open with the given PIN, or if the PIN is not
|
||||||
keyring_commit(k);
|
// fully unlocked
|
||||||
|
if (!identity_count || !is_fully_unlocked) {
|
||||||
RETURN(identitiesFound);
|
unsigned slot;
|
||||||
|
unsigned slots = k->file_size / KEYRING_PAGE_SIZE;
|
||||||
|
if (slots >= KEYRING_BAM_BITS)
|
||||||
|
slots = KEYRING_BAM_BITS - 1;
|
||||||
|
// slot zero is the BAM and salt, so skip it
|
||||||
|
for (slot = 1; slot < slots; ++slot) {
|
||||||
|
// only try to decrypt slots that are marked as allocated and not already loaded; the cost of
|
||||||
|
// decrypting can be up to a second of CPU time on a phone
|
||||||
|
if (is_slot_loadable(k, slot)) {
|
||||||
|
if (keyring_decrypt_pkr(k, pin, slot) == 0) {
|
||||||
|
mark_slot_loaded(k, slot, 1);
|
||||||
|
++identity_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// now all identities with the given PIN have been unlocked, so mark the PIN being fully
|
||||||
|
// unlocked
|
||||||
|
if (!is_fully_unlocked) {
|
||||||
|
keyring_identity *id;
|
||||||
|
for (id = k->identities; id; id = id->next) {
|
||||||
|
if (*pin ? (id->PKRPin && strcmp(id->PKRPin, pin) == 0) : (!id->PKRPin)) {
|
||||||
|
id->is_fully_unlocked = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (k->dirty)
|
||||||
|
keyring_commit(k);
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN(identity_count);
|
||||||
OUT();
|
OUT();
|
||||||
}
|
}
|
||||||
|
|
||||||
static unsigned test_slot(const keyring_file *k, unsigned slot)
|
|
||||||
{
|
|
||||||
assert(slot < KEYRING_BAM_BITS);
|
|
||||||
unsigned position = slot & (KEYRING_BAM_BITS - 1);
|
|
||||||
unsigned byte = position >> 3;
|
|
||||||
unsigned bit = position & 7;
|
|
||||||
return (k->bam->bitmap[byte] & (1 << bit)) ? 1 : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void set_slot(keyring_file *k, unsigned slot, int bitvalue)
|
|
||||||
{
|
|
||||||
assert(slot < KEYRING_BAM_BITS);
|
|
||||||
unsigned position = slot & (KEYRING_BAM_BITS - 1);
|
|
||||||
unsigned byte = position >> 3;
|
|
||||||
unsigned bit = position & 7;
|
|
||||||
if (bitvalue)
|
|
||||||
k->bam->bitmap[byte] |= (1 << bit);
|
|
||||||
else
|
|
||||||
k->bam->bitmap[byte] &= ~(1 << bit);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Find free slot in keyring. Slot 0 in any slab is the BAM and possible keyring salt, so only
|
/* Find free slot in keyring. Slot 0 in any slab is the BAM and possible keyring salt, so only
|
||||||
* search for space in slots 1 and above. TODO: Extend to handle more than one slab!
|
* search for space in slots 1 and above. TODO: Extend to handle more than one slab!
|
||||||
*/
|
*/
|
||||||
@ -1364,22 +1435,28 @@ static unsigned find_free_slot(const keyring_file *k)
|
|||||||
unsigned slot;
|
unsigned slot;
|
||||||
// walk the list of slots, randomising the low order bits of the index
|
// walk the list of slots, randomising the low order bits of the index
|
||||||
unsigned mask = randombytes_uniform(KEYRING_ALLOC_CHUNK);
|
unsigned mask = randombytes_uniform(KEYRING_ALLOC_CHUNK);
|
||||||
for (i = 0; i < KEYRING_BAM_BITS; ++i){
|
for (i = 0; i < KEYRING_BAM_BITS; ++i) {
|
||||||
slot = 1+(i ^ mask);
|
slot = 1 + (i ^ mask);
|
||||||
DEBUGF(keyring, "Check %u, is slot %u free?", i, slot);
|
DEBUGF(keyring, "Check %u, is slot %u free?", i, slot);
|
||||||
if (slot < KEYRING_BAM_BITS && !test_slot(k, slot))
|
if (slot < KEYRING_BAM_BITS && !is_slot_allocated(k, slot))
|
||||||
return slot;
|
return slot;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Return non-zero if the identity was successfully added, zero if the identity was not added
|
||||||
|
* because the SID is already used by an existng identity in the keyring.
|
||||||
|
*/
|
||||||
static int keyring_commit_identity(keyring_file *k, keyring_identity *id)
|
static int keyring_commit_identity(keyring_file *k, keyring_identity *id)
|
||||||
{
|
{
|
||||||
keyring_finalise_identity(&k->dirty, id);
|
keyring_finalise_identity(&k->dirty, id);
|
||||||
// Do nothing if an identity with this sid already exists
|
// Do nothing if an identity with this sid already exists
|
||||||
if (keyring_find_identity_sid(k, id->box_pk))
|
if (keyring_find_identity_sid(k, id->box_pk)) {
|
||||||
|
DEBUGF(keyring, "identity not committed, SID already in use: SID=%s", alloca_tohex_sid_t(*id->box_pk));
|
||||||
return 0;
|
return 0;
|
||||||
set_slot(k, id->slot, 1);
|
}
|
||||||
|
mark_slot_allocated(k, id->slot, 1);
|
||||||
|
mark_slot_loaded(k, id->slot, 1);
|
||||||
|
|
||||||
keyring_identity **i=&k->identities;
|
keyring_identity **i=&k->identities;
|
||||||
while(*i)
|
while(*i)
|
||||||
@ -1387,6 +1464,7 @@ static int keyring_commit_identity(keyring_file *k, keyring_identity *id)
|
|||||||
|
|
||||||
*i=id;
|
*i=id;
|
||||||
add_subscriber(id);
|
add_subscriber(id);
|
||||||
|
DEBUGF(keyring, "identity committed slot=%u SID=%s", id->slot, alloca_tohex_sid_t(id->subscriber->sid));
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1418,7 +1496,7 @@ keyring_identity *keyring_inmemory_identity(){
|
|||||||
keyring_finalise_identity(NULL, id);
|
keyring_finalise_identity(NULL, id);
|
||||||
if (id)
|
if (id)
|
||||||
add_subscriber(id);
|
add_subscriber(id);
|
||||||
INFOF("Created in-memory identity: %s", alloca_tohex_sid_t(id->subscriber->sid));
|
INFOF("created in-memory identity SID=%s", alloca_tohex_sid_t(id->subscriber->sid));
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1449,13 +1527,13 @@ keyring_identity *keyring_create_identity(keyring_file *k, const char *pin)
|
|||||||
WHY("no free slots in first slab (no support for more than one slab)");
|
WHY("no free slots in first slab (no support for more than one slab)");
|
||||||
goto kci_safeexit;
|
goto kci_safeexit;
|
||||||
}
|
}
|
||||||
DEBUGF(keyring, "Allocate identity into slot %u", id->slot);
|
|
||||||
|
|
||||||
/* Mark slot as occupied and internalise new identity. */
|
/* Mark slot as occupied and internalise new identity. */
|
||||||
if (keyring_commit_identity(k, id)!=1)
|
if (!keyring_commit_identity(k, id))
|
||||||
goto kci_safeexit;
|
goto kci_safeexit;
|
||||||
|
|
||||||
/* Everything went fine */
|
/* Everything went fine */
|
||||||
|
INFOF("created identity slot=%u SID=%s", id->slot, alloca_tohex_sid_t(id->subscriber->sid));
|
||||||
k->dirty = 1;
|
k->dirty = 1;
|
||||||
return id;
|
return id;
|
||||||
|
|
||||||
@ -1467,7 +1545,7 @@ keyring_identity *keyring_create_identity(keyring_file *k, const char *pin)
|
|||||||
|
|
||||||
static int write_random_slot(keyring_file *k, unsigned slot)
|
static int write_random_slot(keyring_file *k, unsigned slot)
|
||||||
{
|
{
|
||||||
if (test_slot(k, slot)!=0)
|
if (is_slot_allocated(k, slot))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
DEBUGF(keyring, "Fill slot %u with randomness", slot);
|
DEBUGF(keyring, "Fill slot %u with randomness", slot);
|
||||||
@ -1502,8 +1580,9 @@ void keyring_destroy_identity(keyring_file *k, keyring_identity *id)
|
|||||||
assert(id->slot != 0);
|
assert(id->slot != 0);
|
||||||
DEBUGF(keyring, "Destroy identity in slot %u", id->slot);
|
DEBUGF(keyring, "Destroy identity in slot %u", id->slot);
|
||||||
|
|
||||||
// Mark the slot as unused in the BAM.
|
// Mark the slot as unallocated in the BAM and un-loaded in memory.
|
||||||
set_slot(k, id->slot, 0);
|
mark_slot_allocated(k, id->slot, 0);
|
||||||
|
mark_slot_loaded(k, id->slot, 0);
|
||||||
|
|
||||||
// Fill the slot in the file with random bytes.
|
// Fill the slot in the file with random bytes.
|
||||||
write_random_slot(k, id->slot);
|
write_random_slot(k, id->slot);
|
||||||
@ -1526,8 +1605,8 @@ int keyring_commit(keyring_file *k)
|
|||||||
if (fseeko(k->file, b->file_offset, SEEK_SET) == -1) {
|
if (fseeko(k->file, b->file_offset, SEEK_SET) == -1) {
|
||||||
WHYF_perror("fseeko(%d, %ld, SEEK_SET)", fileno(k->file), (long)b->file_offset);
|
WHYF_perror("fseeko(%d, %ld, SEEK_SET)", fileno(k->file), (long)b->file_offset);
|
||||||
errorCount++;
|
errorCount++;
|
||||||
} else if (fwrite(b->bitmap, KEYRING_BAM_BYTES, 1, k->file) != 1) {
|
} else if (fwrite(b->allocmap, KEYRING_BAM_BYTES, 1, k->file) != 1) {
|
||||||
WHYF_perror("fwrite(%p, %ld, 1, %d)", b->bitmap, (long)KEYRING_BAM_BYTES, fileno(k->file));
|
WHYF_perror("fwrite(%p, %ld, 1, %d)", b->allocmap, (long)KEYRING_BAM_BYTES, fileno(k->file));
|
||||||
errorCount++;
|
errorCount++;
|
||||||
} else if (fwrite(k->KeyRingSalt, k->KeyRingSaltLen, 1, k->file)!=1) {
|
} else if (fwrite(k->KeyRingSalt, k->KeyRingSaltLen, 1, k->file)!=1) {
|
||||||
WHYF_perror("fwrite(%p, %ld, 1, %d)", k->KeyRingSalt, (long)k->KeyRingSaltLen, fileno(k->file));
|
WHYF_perror("fwrite(%p, %ld, 1, %d)", k->KeyRingSalt, (long)k->KeyRingSaltLen, fileno(k->file));
|
||||||
@ -2104,7 +2183,7 @@ int keyring_load_from_dump(keyring_file *k, unsigned entry_pinc, const char **en
|
|||||||
if (id == NULL || idn != last_idn) {
|
if (id == NULL || idn != last_idn) {
|
||||||
last_idn = idn;
|
last_idn = idn;
|
||||||
if (id){
|
if (id){
|
||||||
if (keyring_commit_identity(k, id)!=1)
|
if (!keyring_commit_identity(k, id))
|
||||||
keyring_free_identity(id);
|
keyring_free_identity(id);
|
||||||
else
|
else
|
||||||
k->dirty=1;
|
k->dirty=1;
|
||||||
@ -2129,7 +2208,7 @@ int keyring_load_from_dump(keyring_file *k, unsigned entry_pinc, const char **en
|
|||||||
keyring_free_keypair(kp);
|
keyring_free_keypair(kp);
|
||||||
}
|
}
|
||||||
if (id){
|
if (id){
|
||||||
if (keyring_commit_identity(k, id)!=1)
|
if (!keyring_commit_identity(k, id))
|
||||||
keyring_free_identity(id);
|
keyring_free_identity(id);
|
||||||
else
|
else
|
||||||
k->dirty=1;
|
k->dirty=1;
|
||||||
|
20
keyring.h
20
keyring.h
@ -21,6 +21,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|||||||
#ifndef __SERVAL_DNA__KEYRING_H
|
#ifndef __SERVAL_DNA__KEYRING_H
|
||||||
#define __SERVAL_DNA__KEYRING_H
|
#define __SERVAL_DNA__KEYRING_H
|
||||||
|
|
||||||
|
#include "lang.h" // for bool_t
|
||||||
#include "serval_types.h" // for sid_t
|
#include "serval_types.h" // for sid_t
|
||||||
#include "os.h" // for time_ms_t
|
#include "os.h" // for time_ms_t
|
||||||
|
|
||||||
@ -46,8 +47,19 @@ struct keyring_challenge{
|
|||||||
unsigned char challenge[24];
|
unsigned char challenge[24];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* An unlocked identity is represented by an instance of one of these structs
|
||||||
|
* in the linked list starting in the keyring_file structure.
|
||||||
|
*/
|
||||||
typedef struct keyring_identity {
|
typedef struct keyring_identity {
|
||||||
|
// A nul-terminated string containing the identity's PIN (passphrase); NULL
|
||||||
|
// if no PIN (empty passphrase). This string must be free()d before the
|
||||||
|
// struct is deallocated.
|
||||||
char *PKRPin;
|
char *PKRPin;
|
||||||
|
|
||||||
|
// Whether all other identities in the same keyring file that have the same
|
||||||
|
// PIN are also unlocked:
|
||||||
|
bool_t is_fully_unlocked : 1;
|
||||||
|
|
||||||
struct subscriber *subscriber;
|
struct subscriber *subscriber;
|
||||||
unsigned int slot;
|
unsigned int slot;
|
||||||
struct keyring_challenge *challenge;
|
struct keyring_challenge *challenge;
|
||||||
@ -68,7 +80,8 @@ typedef struct keyring_identity {
|
|||||||
|
|
||||||
typedef struct keyring_bam {
|
typedef struct keyring_bam {
|
||||||
size_t file_offset;
|
size_t file_offset;
|
||||||
unsigned char bitmap[KEYRING_BAM_BYTES];
|
unsigned char allocmap[KEYRING_BAM_BYTES];
|
||||||
|
unsigned char loadmap[KEYRING_BAM_BYTES];
|
||||||
struct keyring_bam *next;
|
struct keyring_bam *next;
|
||||||
} keyring_bam;
|
} keyring_bam;
|
||||||
|
|
||||||
@ -99,7 +112,8 @@ keyring_identity *keyring_find_identity_sid(keyring_file *k, const sid_t *sidp);
|
|||||||
keyring_identity *keyring_find_identity(keyring_file *k, const identity_t *sign);
|
keyring_identity *keyring_find_identity(keyring_file *k, const identity_t *sign);
|
||||||
|
|
||||||
void keyring_free(keyring_file *k);
|
void keyring_free(keyring_file *k);
|
||||||
int keyring_release_identities_by_pin(keyring_file *f, const char *pin);
|
void keyring_release_identities_by_pin(keyring_file *f, const char *pin);
|
||||||
|
void keyring_release_identity(keyring_file *k, keyring_identity *id);
|
||||||
int keyring_release_subscriber(keyring_file *k, const sid_t *sid);
|
int keyring_release_subscriber(keyring_file *k, const sid_t *sid);
|
||||||
|
|
||||||
#define KEYTYPE_CRYPTOBOX 0x01 // must be lowest
|
#define KEYTYPE_CRYPTOBOX 0x01 // must be lowest
|
||||||
@ -122,7 +136,7 @@ extern __thread keyring_file *keyring;
|
|||||||
keyring_file *keyring_create_instance();
|
keyring_file *keyring_create_instance();
|
||||||
keyring_file *keyring_open_instance(const char *pin);
|
keyring_file *keyring_open_instance(const char *pin);
|
||||||
keyring_file *keyring_open_instance_cli(const struct cli_parsed *parsed);
|
keyring_file *keyring_open_instance_cli(const struct cli_parsed *parsed);
|
||||||
int keyring_enter_pin(keyring_file *k, const char *pin);
|
unsigned keyring_enter_pin(keyring_file *k, const char *pin);
|
||||||
int keyring_set_did_name(keyring_identity *id, const char *did, const char *name);
|
int keyring_set_did_name(keyring_identity *id, const char *did, const char *name);
|
||||||
int keyring_set_pin(keyring_identity *id, const char *pin);
|
int keyring_set_pin(keyring_identity *id, const char *pin);
|
||||||
int keyring_sign_message(struct keyring_identity *identity, unsigned char *content, size_t buffer_len, size_t *content_len);
|
int keyring_sign_message(struct keyring_identity *identity, unsigned char *content, size_t buffer_len, size_t *content_len);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Serval DNA HTTP RESTful interface
|
Serval DNA Keyring HTTP RESTful interface
|
||||||
|
Copyright (C) 2018 Flinders University
|
||||||
Copyright (C) 2013-2015 Serval Project Inc.
|
Copyright (C) 2013-2015 Serval Project Inc.
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or
|
This program is free software; you can redistribute it and/or
|
||||||
@ -17,7 +18,7 @@ along with this program; if not, write to the Free Software
|
|||||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "lang.h" // for FALLTHROUGH
|
#include "lang.h" // for bool_t, FALLTHROUGH
|
||||||
#include "serval.h"
|
#include "serval.h"
|
||||||
#include "conf.h"
|
#include "conf.h"
|
||||||
#include "httpd.h"
|
#include "httpd.h"
|
||||||
@ -28,15 +29,14 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|||||||
|
|
||||||
DEFINE_FEATURE(http_rest_keyring);
|
DEFINE_FEATURE(http_rest_keyring);
|
||||||
|
|
||||||
#define keyring_TOKEN_STRLEN (BASE64_ENCODED_LEN(sizeof(rhizome_bid_t) + sizeof(uint64_t)))
|
|
||||||
#define alloca_keyring_token(bid, offset) keyring_ token_to_str(alloca(keyring_TOKEN_STRLEN + 1), (bid), (offset))
|
|
||||||
|
|
||||||
DECLARE_HANDLER("/restful/keyring/", restful_keyring_);
|
DECLARE_HANDLER("/restful/keyring/", restful_keyring_);
|
||||||
|
|
||||||
static HTTP_HANDLER restful_keyring_identitylist_json;
|
static HTTP_HANDLER restful_keyring_identitylist_json;
|
||||||
static HTTP_HANDLER restful_keyring_add;
|
static HTTP_HANDLER restful_keyring_add;
|
||||||
static HTTP_HANDLER restful_keyring_remove;
|
static HTTP_HANDLER restful_keyring_get;
|
||||||
static HTTP_HANDLER restful_keyring_set;
|
static HTTP_HANDLER restful_keyring_set;
|
||||||
|
static HTTP_HANDLER restful_keyring_remove;
|
||||||
|
static HTTP_HANDLER restful_keyring_lock;
|
||||||
|
|
||||||
static int restful_keyring_(httpd_request *r, const char *remainder)
|
static int restful_keyring_(httpd_request *r, const char *remainder)
|
||||||
{
|
{
|
||||||
@ -44,35 +44,40 @@ static int restful_keyring_(httpd_request *r, const char *remainder)
|
|||||||
int ret = authorize_restful(&r->http);
|
int ret = authorize_restful(&r->http);
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
const char *verb = HTTP_VERB_GET;
|
|
||||||
HTTP_HANDLER *handler = NULL;
|
|
||||||
const char *end;
|
const char *end;
|
||||||
if (strcmp(remainder, "identities.json") == 0) {
|
if (r->http.verb == HTTP_VERB_GET && strcmp(remainder, "identities.json") == 0)
|
||||||
handler = restful_keyring_identitylist_json;
|
return restful_keyring_identitylist_json(r, "");
|
||||||
verb = HTTP_VERB_GET;
|
if (r->http.verb == HTTP_VERB_POST && strcmp(remainder, "add") == 0)
|
||||||
remainder = "";
|
return restful_keyring_add(r, "");
|
||||||
}
|
if (parse_sid_t(&r->sid1, remainder, -1, &end) != -1) {
|
||||||
else if (strcmp(remainder, "add") == 0) {
|
if (strcmp(end, "") == 0) {
|
||||||
handler = restful_keyring_add;
|
if (r->http.verb == HTTP_VERB_GET)
|
||||||
verb = HTTP_VERB_GET;
|
return restful_keyring_get(r, "");
|
||||||
remainder = "";
|
if (r->http.verb == HTTP_VERB_PATCH)
|
||||||
}
|
return restful_keyring_set(r, "");
|
||||||
else if (parse_sid_t(&r->sid1, remainder, -1, &end) != -1) {
|
if (r->http.verb == HTTP_VERB_DELETE)
|
||||||
remainder = end;
|
return restful_keyring_remove(r, "");
|
||||||
if (strcmp(remainder, "/remove") == 0) {
|
|
||||||
handler = restful_keyring_remove;
|
|
||||||
remainder = "";
|
|
||||||
}
|
}
|
||||||
else if (strcmp(remainder, "/set") == 0) {
|
else if (r->http.verb == HTTP_VERB_PUT && strcmp(end, "/lock") == 0)
|
||||||
handler = restful_keyring_set;
|
return restful_keyring_lock(r, "");
|
||||||
remainder = "";
|
}
|
||||||
|
// Legacy API requests that use GET but should use another verb instead (see above). Eventually
|
||||||
|
// this support should be removed and some text in the response should refer the client to the
|
||||||
|
// correct request.
|
||||||
|
if (r->http.verb == HTTP_VERB_GET) {
|
||||||
|
if (strcmp(remainder, "add") == 0) {
|
||||||
|
return restful_keyring_add(r, "");
|
||||||
|
}
|
||||||
|
else if (parse_sid_t(&r->sid1, remainder, -1, &end) != -1) {
|
||||||
|
if (strcmp(end, "/remove") == 0)
|
||||||
|
return restful_keyring_remove(r, "");
|
||||||
|
else if (strcmp(end, "/set") == 0)
|
||||||
|
return restful_keyring_set(r, "");
|
||||||
|
else if (strcmp(end, "/lock") == 0)
|
||||||
|
return restful_keyring_lock(r, "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (handler == NULL)
|
return 404;
|
||||||
return 404;
|
|
||||||
if (r->http.verb != verb)
|
|
||||||
return 405;
|
|
||||||
return handler(r, remainder);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int http_request_keyring_response(struct httpd_request *r, uint16_t result, const char *message)
|
static int http_request_keyring_response(struct httpd_request *r, uint16_t result, const char *message)
|
||||||
@ -99,12 +104,10 @@ static int http_request_keyring_response_identity(struct httpd_request *r, uint1
|
|||||||
json_id_kv[0].value = &json_sid;
|
json_id_kv[0].value = &json_sid;
|
||||||
json_sid.type = JSON_STRING_NULTERM;
|
json_sid.type = JSON_STRING_NULTERM;
|
||||||
json_sid.u.string.content = alloca_tohex_sid_t(*id->box_pk);
|
json_sid.u.string.content = alloca_tohex_sid_t(*id->box_pk);
|
||||||
|
|
||||||
json_id_kv[1].key = "identity";
|
json_id_kv[1].key = "identity";
|
||||||
json_id_kv[1].value = &json_sas;
|
json_id_kv[1].value = &json_sas;
|
||||||
json_sas.type = JSON_STRING_NULTERM;
|
json_sas.type = JSON_STRING_NULTERM;
|
||||||
json_sas.u.string.content = alloca_tohex_identity_t(&id->sign_keypair->public_key);
|
json_sas.u.string.content = alloca_tohex_identity_t(&id->sign_keypair->public_key);
|
||||||
|
|
||||||
if (did) {
|
if (did) {
|
||||||
json_id_kv[json_id.u.object.itemc].key = "did";
|
json_id_kv[json_id.u.object.itemc].key = "did";
|
||||||
json_id_kv[json_id.u.object.itemc].value = &json_did;
|
json_id_kv[json_id.u.object.itemc].value = &json_did;
|
||||||
@ -238,6 +241,21 @@ static int restful_keyring_add(httpd_request *r, const char *remainder)
|
|||||||
return http_request_keyring_response_identity(r, 201, id);
|
return http_request_keyring_response_identity(r, 201, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int restful_keyring_get(httpd_request *r, const char *remainder)
|
||||||
|
{
|
||||||
|
if (*remainder)
|
||||||
|
return 404;
|
||||||
|
const char *pin = http_request_get_query_param(&r->http, "pin");
|
||||||
|
if (pin)
|
||||||
|
keyring_enter_pin(keyring, pin);
|
||||||
|
keyring_identity *id = keyring_find_identity_sid(keyring, &r->sid1);
|
||||||
|
if (!id)
|
||||||
|
return http_request_keyring_response(r, 404, "Identity not found");
|
||||||
|
int ret = http_request_keyring_response_identity(r, 200, id);
|
||||||
|
keyring_free_identity(id);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
static int restful_keyring_remove(httpd_request *r, const char *remainder)
|
static int restful_keyring_remove(httpd_request *r, const char *remainder)
|
||||||
{
|
{
|
||||||
if (*remainder)
|
if (*remainder)
|
||||||
@ -278,3 +296,15 @@ static int restful_keyring_set(httpd_request *r, const char *remainder)
|
|||||||
return http_request_keyring_response(r, 500, "Could not store new identity");
|
return http_request_keyring_response(r, 500, "Could not store new identity");
|
||||||
return http_request_keyring_response_identity(r, 200, id);
|
return http_request_keyring_response_identity(r, 200, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int restful_keyring_lock(httpd_request *r, const char *remainder)
|
||||||
|
{
|
||||||
|
if (*remainder)
|
||||||
|
return 404;
|
||||||
|
keyring_identity *id = keyring_find_identity_sid(keyring, &r->sid1);
|
||||||
|
if (!id)
|
||||||
|
return http_request_keyring_response(r, 404, "Identity not found");
|
||||||
|
keyring_release_identity(keyring, id);
|
||||||
|
keyring_free_identity(id);
|
||||||
|
return http_request_keyring_response(r, 200, "Identity locked");
|
||||||
|
}
|
||||||
|
@ -1282,8 +1282,6 @@ static int mdp_search_identities(struct socket_address *client, struct mdp_heade
|
|||||||
struct overlay_buffer *payload)
|
struct overlay_buffer *payload)
|
||||||
{
|
{
|
||||||
assert(keyring != NULL);
|
assert(keyring != NULL);
|
||||||
keyring_iterator it;
|
|
||||||
keyring_iterator_start(keyring, &it);
|
|
||||||
|
|
||||||
const char *tag=NULL;
|
const char *tag=NULL;
|
||||||
const unsigned char *value=NULL;
|
const unsigned char *value=NULL;
|
||||||
@ -1297,6 +1295,8 @@ static int mdp_search_identities(struct socket_address *client, struct mdp_heade
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
keyring_iterator it;
|
||||||
|
keyring_iterator_start(keyring, &it);
|
||||||
while(1){
|
while(1){
|
||||||
if (value_len){
|
if (value_len){
|
||||||
if (!keyring_find_public_tag_value(&it, tag, value, value_len))
|
if (!keyring_find_public_tag_value(&it, tag, value, value_len))
|
||||||
|
@ -376,6 +376,12 @@ test_ServerLockUnlock() {
|
|||||||
assertStdoutGrep --matches=1 --fixed-strings "$SIDA"
|
assertStdoutGrep --matches=1 --fixed-strings "$SIDA"
|
||||||
assertStdoutGrep --matches=1 --fixed-strings "$TWOA"
|
assertStdoutGrep --matches=1 --fixed-strings "$TWOA"
|
||||||
assertStdoutLineCount == 4
|
assertStdoutLineCount == 4
|
||||||
|
executeOk_servald id enter pin 'two'
|
||||||
|
executeOk_servald id list
|
||||||
|
assertStdoutGrep --matches=1 --fixed-strings "$SIDA"
|
||||||
|
assertStdoutGrep --matches=1 --fixed-strings "$TWOA"
|
||||||
|
assertStdoutGrep --matches=1 --fixed-strings "$TWOB"
|
||||||
|
assertStdoutLineCount == 5
|
||||||
}
|
}
|
||||||
teardown_ServerLockUnlock() {
|
teardown_ServerLockUnlock() {
|
||||||
teardown_servald
|
teardown_servald
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
# Tests for Serval DNA HTTP RESTful interface
|
# Tests for Serval DNA HTTP RESTful interface
|
||||||
#
|
#
|
||||||
# Copyright 2013-2015 Serval Project, Inc.
|
# Copyright 2013-2015 Serval Project, Inc.
|
||||||
# Copyright 2016 Flinders Univerity
|
# Copyright 2016-2018 Flinders Univerity
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or
|
# This program is free software; you can redistribute it and/or
|
||||||
# modify it under the terms of the GNU General Public License
|
# modify it under the terms of the GNU General Public License
|
||||||
@ -35,7 +35,6 @@ setup() {
|
|||||||
set api.restful.users.harry.password potter \
|
set api.restful.users.harry.password potter \
|
||||||
set api.restful.users.ron.password weasley \
|
set api.restful.users.ron.password weasley \
|
||||||
set api.restful.users.hermione.password grainger
|
set api.restful.users.hermione.password grainger
|
||||||
set_extra_config
|
|
||||||
if [ -z "$IDENTITY_COUNT" ]; then
|
if [ -z "$IDENTITY_COUNT" ]; then
|
||||||
create_single_identity
|
create_single_identity
|
||||||
else
|
else
|
||||||
@ -44,6 +43,7 @@ setup() {
|
|||||||
start_servald_instances +A
|
start_servald_instances +A
|
||||||
wait_until servald_restful_http_server_started +A
|
wait_until servald_restful_http_server_started +A
|
||||||
get_servald_restful_http_server_port PORTA +A
|
get_servald_restful_http_server_port PORTA +A
|
||||||
|
REQUEST_COUNT=0
|
||||||
}
|
}
|
||||||
|
|
||||||
finally() {
|
finally() {
|
||||||
@ -56,10 +56,6 @@ teardown() {
|
|||||||
report_all_servald_servers
|
report_all_servald_servers
|
||||||
}
|
}
|
||||||
|
|
||||||
set_extra_config() {
|
|
||||||
:
|
|
||||||
}
|
|
||||||
|
|
||||||
set_keyring_config() {
|
set_keyring_config() {
|
||||||
executeOk_servald config \
|
executeOk_servald config \
|
||||||
set debug.httpd on \
|
set debug.httpd on \
|
||||||
@ -68,24 +64,37 @@ set_keyring_config() {
|
|||||||
set log.console.level debug
|
set log.console.level debug
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rest_request() {
|
||||||
|
local oo
|
||||||
|
local request_verb="${1?}"
|
||||||
|
local path="${2?}"
|
||||||
|
local response_code="${3:-200}"
|
||||||
|
local preserve="request-$((++REQUEST_COUNT)).json"
|
||||||
|
executeOk curl \
|
||||||
|
--silent --show-error \
|
||||||
|
--write-out '%{http_code}' \
|
||||||
|
--output response.json \
|
||||||
|
--dump-header response.headers \
|
||||||
|
--basic --user harry:potter \
|
||||||
|
--request "$request_verb" \
|
||||||
|
"http://$addr_localhost:$PORTA$path"
|
||||||
|
tfw_cat response.headers response.json
|
||||||
|
cp response.json "$preserve"
|
||||||
|
tfw_preserve "$preserve"
|
||||||
|
assertStdoutIs "$response_code"
|
||||||
|
}
|
||||||
|
|
||||||
doc_keyringList="HTTP RESTful list keyring identities as JSON"
|
doc_keyringList="HTTP RESTful list keyring identities as JSON"
|
||||||
setup_keyringList() {
|
setup_keyringList() {
|
||||||
IDENTITY_COUNT=10
|
IDENTITY_COUNT=10
|
||||||
setup
|
setup
|
||||||
}
|
}
|
||||||
test_keyringList() {
|
test_keyringList() {
|
||||||
executeOk curl \
|
rest_request GET "/restful/keyring/identities.json"
|
||||||
--silent --fail --show-error \
|
assert [ "$(jq '.rows | length' response.json)" = $IDENTITY_COUNT ]
|
||||||
--output list.json \
|
|
||||||
--dump-header http.headers \
|
|
||||||
--basic --user harry:potter \
|
|
||||||
"http://$addr_localhost:$PORTA/restful/keyring/identities.json"
|
|
||||||
tfw_cat http.headers list.json
|
|
||||||
tfw_preserve list.json
|
|
||||||
assert [ "$(jq '.rows | length' list.json)" = $IDENTITY_COUNT ]
|
|
||||||
# All SIDs are present in the list.
|
# All SIDs are present in the list.
|
||||||
for SID in ${SIDA[*]}; do
|
for SID in ${SIDA[*]}; do
|
||||||
assert [ "$(jq -r '.rows | contains([["'"$SID"'"]])' list.json)" = true ]
|
assert [ "$(jq -r '.rows | contains([["'"$SID"'"]])' response.json)" = true ]
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,52 +106,36 @@ setup_keyringListPin() {
|
|||||||
}
|
}
|
||||||
test_keyringListPin() {
|
test_keyringListPin() {
|
||||||
# First, list without supplying the PIN
|
# First, list without supplying the PIN
|
||||||
executeOk curl \
|
rest_request GET "/restful/keyring/identities.json"
|
||||||
--silent --fail --show-error \
|
transform_list_json response.json ids1.json
|
||||||
--output list1.json \
|
|
||||||
--dump-header http.headers \
|
|
||||||
--basic --user harry:potter \
|
|
||||||
"http://$addr_localhost:$PORTA/restful/keyring/identities.json"
|
|
||||||
tfw_cat http.headers list1.json
|
|
||||||
tfw_preserve list1.json
|
|
||||||
transform_list_json list1.json ids1.json
|
|
||||||
assert [ "$(jq 'length' ids1.json)" = $((IDENTITY_COUNT-1)) ]
|
assert [ "$(jq 'length' ids1.json)" = $((IDENTITY_COUNT-1)) ]
|
||||||
assertJq ids1.json 'contains([{"sid": "'$SIDA1'"}]) | not'
|
assertJq ids1.json 'contains([{"sid": "'$SIDA1'"}]) | not'
|
||||||
assertJq ids1.json 'contains([{"sid": "'$SIDA2'"}])'
|
assertJq ids1.json 'contains([{"sid": "'$SIDA2'"}])'
|
||||||
assertJq ids1.json 'contains([{"sid": "'$SIDA3'"}])'
|
assertJq ids1.json 'contains([{"sid": "'$SIDA3'"}])'
|
||||||
# Then, list supplying the PIN
|
# Then, list supplying the PIN
|
||||||
executeOk curl \
|
rest_request GET "/restful/keyring/identities.json?pin=wif+waf"
|
||||||
--silent --fail --show-error \
|
transform_list_json response.json ids2.json
|
||||||
--output list2.json \
|
|
||||||
--dump-header http.headers \
|
|
||||||
--basic --user harry:potter \
|
|
||||||
"http://$addr_localhost:$PORTA/restful/keyring/identities.json?pin=wif+waf"
|
|
||||||
tfw_cat http.headers list2.json
|
|
||||||
tfw_preserve list2.json
|
|
||||||
transform_list_json list2.json ids2.json
|
|
||||||
assert [ "$(jq 'length' ids2.json)" = $IDENTITY_COUNT ]
|
assert [ "$(jq 'length' ids2.json)" = $IDENTITY_COUNT ]
|
||||||
assertJq ids2.json 'contains([{"sid": "'$SIDA1'"}])'
|
assertJq ids2.json 'contains([{"sid": "'$SIDA1'"}])'
|
||||||
assertJq ids2.json 'contains([{"sid": "'$SIDA2'"}])'
|
assertJq ids2.json 'contains([{"sid": "'$SIDA2'"}])'
|
||||||
assertJq ids2.json 'contains([{"sid": "'$SIDA3'"}])'
|
assertJq ids2.json 'contains([{"sid": "'$SIDA3'"}])'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
doc_keyringGet="HTTP RESTful get single keyring identity as JSON"
|
||||||
|
test_keyringGet() {
|
||||||
|
rest_request GET "/restful/keyring/$SIDA1"
|
||||||
|
assertJq response.json 'contains({"identity": {"sid": "'$SIDA1'", "identity": "'$IDA1'", "did": "'"$DIDA1"'", "name": "'"$NAMEA1"'"}})'
|
||||||
|
}
|
||||||
|
|
||||||
doc_keyringAdd="HTTP RESTful add keyring identity"
|
doc_keyringAdd="HTTP RESTful add keyring identity"
|
||||||
setup_keyringAdd() {
|
setup_keyringAdd() {
|
||||||
IDENTITY_COUNT=2
|
IDENTITY_COUNT=2
|
||||||
setup
|
setup
|
||||||
}
|
}
|
||||||
test_keyringAdd() {
|
test_keyringAdd() {
|
||||||
executeOk curl \
|
rest_request POST "/restful/keyring/add?did=" 201
|
||||||
--silent --show-error --write-out '%{http_code}' \
|
SID="$(jq -r '.identity.sid' response.json)"
|
||||||
--output add.json \
|
ID="$(jq -r '.identity.identity' response.json)"
|
||||||
--dump-header http.headers \
|
|
||||||
--basic --user harry:potter \
|
|
||||||
"http://$addr_localhost:$PORTA/restful/keyring/add?did="
|
|
||||||
tfw_cat http.headers add.json
|
|
||||||
tfw_preserve add.json
|
|
||||||
assertStdoutIs '201'
|
|
||||||
SID="$(jq -r '.identity.sid' add.json)"
|
|
||||||
ID="$(jq -r '.identity.identity' add.json)"
|
|
||||||
assert matches_rexp "^${rexp_sid}$" "$SID"
|
assert matches_rexp "^${rexp_sid}$" "$SID"
|
||||||
assert matches_rexp "^${rexp_id}$" "$ID"
|
assert matches_rexp "^${rexp_id}$" "$ID"
|
||||||
executeOk_servald keyring list
|
executeOk_servald keyring list
|
||||||
@ -156,17 +149,9 @@ setup_keyringAddPin() {
|
|||||||
setup
|
setup
|
||||||
}
|
}
|
||||||
test_keyringAddPin() {
|
test_keyringAddPin() {
|
||||||
executeOk curl \
|
rest_request POST "/restful/keyring/add?pin=1234" 201
|
||||||
--silent --show-error --write-out '%{http_code}' \
|
SID="$(jq -r '.identity.sid' response.json)"
|
||||||
--output add.json \
|
ID="$(jq -r '.identity.identity' response.json)"
|
||||||
--dump-header http.headers \
|
|
||||||
--basic --user harry:potter \
|
|
||||||
"http://$addr_localhost:$PORTA/restful/keyring/add?pin=1234"
|
|
||||||
tfw_cat http.headers add.json
|
|
||||||
tfw_preserve add.json
|
|
||||||
assertStdoutIs '201'
|
|
||||||
SID="$(jq -r '.identity.sid' add.json)"
|
|
||||||
ID="$(jq -r '.identity.identity' add.json)"
|
|
||||||
executeOk_servald keyring list
|
executeOk_servald keyring list
|
||||||
assert_keyring_list 2
|
assert_keyring_list 2
|
||||||
assertStdoutGrep --stderr --matches=0 "^$SID:$ID::\$"
|
assertStdoutGrep --stderr --matches=0 "^$SID:$ID::\$"
|
||||||
@ -175,15 +160,8 @@ test_keyringAddPin() {
|
|||||||
assertStdoutGrep --stderr --matches=1 "^$SID:$ID::\$"
|
assertStdoutGrep --stderr --matches=1 "^$SID:$ID::\$"
|
||||||
# Now the server has internalised the PIN, so the new identity appears in the
|
# Now the server has internalised the PIN, so the new identity appears in the
|
||||||
# list
|
# list
|
||||||
executeOk curl \
|
rest_request GET "/restful/keyring/identities.json"
|
||||||
--silent --fail --show-error \
|
transform_list_json response.json ids.json
|
||||||
--output list.json \
|
|
||||||
--dump-header http.headers \
|
|
||||||
--basic --user harry:potter \
|
|
||||||
"http://$addr_localhost:$PORTA/restful/keyring/identities.json"
|
|
||||||
tfw_cat http.headers list.json
|
|
||||||
tfw_preserve list.json
|
|
||||||
transform_list_json list.json ids.json
|
|
||||||
assertJq ids.json 'contains([{"sid": "'$SIDA1'"}])'
|
assertJq ids.json 'contains([{"sid": "'$SIDA1'"}])'
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,16 +171,8 @@ setup_keyringRemove() {
|
|||||||
setup
|
setup
|
||||||
}
|
}
|
||||||
test_keyringRemove() {
|
test_keyringRemove() {
|
||||||
executeOk curl \
|
rest_request DELETE "/restful/keyring/$SIDA1"
|
||||||
--silent --show-error --write-out '%{http_code}' \
|
SID="$(jq -r '.identity.sid' response.json)"
|
||||||
--output add.json \
|
|
||||||
--dump-header http.headers \
|
|
||||||
--basic --user harry:potter \
|
|
||||||
"http://$addr_localhost:$PORTA/restful/keyring/$SIDA1/remove"
|
|
||||||
tfw_cat http.headers add.json
|
|
||||||
tfw_preserve add.json
|
|
||||||
assertStdoutIs '200'
|
|
||||||
SID="$(jq -r '.identity.sid' add.json)"
|
|
||||||
assert [ "$SID" = "$SIDA1" ]
|
assert [ "$SID" = "$SIDA1" ]
|
||||||
executeOk_servald keyring list
|
executeOk_servald keyring list
|
||||||
assert_keyring_list 1
|
assert_keyring_list 1
|
||||||
@ -215,16 +185,8 @@ setup_keyringSetDidName() {
|
|||||||
setup
|
setup
|
||||||
}
|
}
|
||||||
test_keyringSetDidName() {
|
test_keyringSetDidName() {
|
||||||
executeOk curl \
|
rest_request PATCH "/restful/keyring/$SIDA1?did=987654321&name=Joe%20Bloggs"
|
||||||
--silent --show-error --write-out '%{http_code}' \
|
assertJq response.json 'contains({"identity": {"sid": "'$SIDA1'", "did": "987654321", "name": "Joe Bloggs"}})'
|
||||||
--output set.json \
|
|
||||||
--dump-header http.headers \
|
|
||||||
--basic --user harry:potter \
|
|
||||||
"http://$addr_localhost:$PORTA/restful/keyring/$SIDA1/set?did=987654321&name=Joe%20Bloggs"
|
|
||||||
tfw_cat http.headers set.json
|
|
||||||
tfw_preserve set.json
|
|
||||||
assertStdoutIs '200'
|
|
||||||
assertJq set.json 'contains({"identity": {"sid": "'$SIDA1'", "did": "987654321", "name": "Joe Bloggs"}})'
|
|
||||||
executeOk_servald keyring list
|
executeOk_servald keyring list
|
||||||
assert_keyring_list 2
|
assert_keyring_list 2
|
||||||
assertStdoutGrep --stderr --matches=1 "^$SIDA1:$IDA1:987654321:Joe Bloggs\$"
|
assertStdoutGrep --stderr --matches=1 "^$SIDA1:$IDA1:987654321:Joe Bloggs\$"
|
||||||
@ -238,54 +200,75 @@ setup_keyringSetDidNamePin() {
|
|||||||
}
|
}
|
||||||
test_keyringSetDidNamePin() {
|
test_keyringSetDidNamePin() {
|
||||||
# First try with no PIN, and make sure it fails
|
# First try with no PIN, and make sure it fails
|
||||||
executeOk curl \
|
rest_request GET "/restful/keyring/$SIDA1/set?did=111222333&name=Nobody" 404
|
||||||
--silent --show-error --write-out '%{http_code}' \
|
|
||||||
--output set1.json \
|
|
||||||
--dump-header http.headers \
|
|
||||||
--basic --user harry:potter \
|
|
||||||
"http://$addr_localhost:$PORTA/restful/keyring/$SIDA1/set?did=111222333&name=Nobody"
|
|
||||||
tfw_cat http.headers set1.json
|
|
||||||
tfw_preserve set1.json
|
|
||||||
assertStdoutIs '404'
|
|
||||||
# Enter incorrect PIN, and make sure it fails
|
# Enter incorrect PIN, and make sure it fails
|
||||||
executeOk curl \
|
rest_request GET "/restful/keyring/$SIDA1/set?did=444555666&name=Anybody&pin=wrong" 404
|
||||||
--silent --show-error --write-out '%{http_code}' \
|
|
||||||
--output set2.json \
|
|
||||||
--dump-header http.headers \
|
|
||||||
--basic --user harry:potter \
|
|
||||||
"http://$addr_localhost:$PORTA/restful/keyring/$SIDA1/set?did=444555666&name=Anybody"
|
|
||||||
tfw_cat http.headers set2.json
|
|
||||||
tfw_preserve set2.json
|
|
||||||
assertStdoutIs '404'
|
|
||||||
# Then try with correct PIN, and make sure it succeeds
|
# Then try with correct PIN, and make sure it succeeds
|
||||||
executeOk curl \
|
rest_request GET "/restful/keyring/$SIDA1/set?did=987654321&name=Joe%20Bloggs&pin=$PINA1" 200
|
||||||
--silent --show-error --write-out '%{http_code}' \
|
assertJq response.json 'contains({"identity": {"sid": "'$SIDA1'", "did": "987654321", "name": "Joe Bloggs"}})'
|
||||||
--output set3.json \
|
executeOk_servald keyring list --entry-pin="$PINA1"
|
||||||
--dump-header http.headers \
|
|
||||||
--basic --user harry:potter \
|
|
||||||
"http://$addr_localhost:$PORTA/restful/keyring/$SIDA1/set?did=987654321&name=Joe%20Bloggs&pin=xyzabc"
|
|
||||||
tfw_cat http.headers set3.json
|
|
||||||
tfw_preserve set3.json
|
|
||||||
assertStdoutIs '200'
|
|
||||||
assertJq set3.json 'contains({"identity": {"sid": "'$SIDA1'", "did": "987654321", "name": "Joe Bloggs"}})'
|
|
||||||
executeOk_servald keyring list --entry-pin=xyzabc
|
|
||||||
assert_keyring_list 2
|
assert_keyring_list 2
|
||||||
assertStdoutGrep --stderr --matches=1 "^$SIDA1:$IDA1:987654321:Joe Bloggs\$"
|
assertStdoutGrep --stderr --matches=1 "^$SIDA1:$IDA1:987654321:Joe Bloggs\$"
|
||||||
# Finally, try again with no PIN, and make sure it succeeds (server has
|
# Finally, try again with no PIN, and make sure it succeeds (server has
|
||||||
# internalised the PIN supplied in the last request)
|
# internalised the PIN supplied in the last request)
|
||||||
executeOk curl \
|
rest_request GET "/restful/keyring/$SIDA1/set?did=321321321&name=Fred+Nurks" 200
|
||||||
--silent --show-error --write-out '%{http_code}' \
|
assertJq response.json 'contains({"identity": {"sid": "'$SIDA1'", "did": "321321321", "name": "Fred Nurks"}})'
|
||||||
--output set4.json \
|
executeOk_servald keyring list --entry-pin="$PINA1"
|
||||||
--dump-header http.headers \
|
|
||||||
--basic --user harry:potter \
|
|
||||||
"http://$addr_localhost:$PORTA/restful/keyring/$SIDA1/set?did=321321321&name=Fred+Nurks"
|
|
||||||
tfw_cat http.headers set4.json
|
|
||||||
tfw_preserve set4.json
|
|
||||||
assertStdoutIs '200'
|
|
||||||
assertJq set4.json 'contains({"identity": {"sid": "'$SIDA1'", "did": "321321321", "name": "Fred Nurks"}})'
|
|
||||||
executeOk_servald keyring list --entry-pin=xyzabc
|
|
||||||
assert_keyring_list 2
|
assert_keyring_list 2
|
||||||
assertStdoutGrep --stderr --matches=1 "^$SIDA1:$IDA1:321321321:Fred Nurks\$"
|
assertStdoutGrep --stderr --matches=1 "^$SIDA1:$IDA1:321321321:Fred Nurks\$"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
doc_keyringLockUnlock="HTTP RESTful lock and unlock PIN-protected identities"
|
||||||
|
setup_keyringLockUnlock() {
|
||||||
|
IDENTITY_COUNT=5
|
||||||
|
PIN1=abc
|
||||||
|
PIN2=xyz
|
||||||
|
PINA2="$PIN1"
|
||||||
|
PINA3="$PIN2"
|
||||||
|
PINA4="$PIN2"
|
||||||
|
PINA5="$PIN2"
|
||||||
|
setup
|
||||||
|
# The initial list of unlocked identities should only be the single non-PIN identity.
|
||||||
|
rest_request GET "/restful/keyring/identities.json"
|
||||||
|
# Unlock PIN1.
|
||||||
|
rest_request GET "/restful/keyring/identities.json?pin=$PIN1"
|
||||||
|
# Unlock PIN2.
|
||||||
|
rest_request GET "/restful/keyring/identities.json?pin=$PIN2"
|
||||||
|
transform_list_json response.json ids_setup.json
|
||||||
|
assert [ "$(jq 'length' ids_setup.json)" = $IDENTITY_COUNT ]
|
||||||
|
assertJq ids_setup.json 'contains([{"sid": "'$SIDA1'"}])'
|
||||||
|
assertJq ids_setup.json 'contains([{"sid": "'$SIDA2'"}])'
|
||||||
|
assertJq ids_setup.json 'contains([{"sid": "'$SIDA3'"}])'
|
||||||
|
assertJq ids_setup.json 'contains([{"sid": "'$SIDA4'"}])'
|
||||||
|
assertJq ids_setup.json 'contains([{"sid": "'$SIDA5'"}])'
|
||||||
|
}
|
||||||
|
test_keyringLockUnlock() {
|
||||||
|
# Lock the only PIN1 identity and assert that it no longer appears in the
|
||||||
|
# identity list.
|
||||||
|
rest_request PUT "/restful/keyring/$SIDA2/lock"
|
||||||
|
rest_request GET "/restful/keyring/identities.json"
|
||||||
|
transform_list_json response.json ids1.json
|
||||||
|
assert [ "$(jq 'length' ids1.json)" = $((IDENTITY_COUNT - 1)) ]
|
||||||
|
assertJq ids1.json 'contains([{"sid": "'$SIDA2'"}]) | not'
|
||||||
|
# Lock the second PIN2 identity and assert that it no longer appears in the
|
||||||
|
# identity list.
|
||||||
|
rest_request PUT "/restful/keyring/$SIDA4/lock"
|
||||||
|
rest_request GET "/restful/keyring/identities.json"
|
||||||
|
transform_list_json response.json ids2.json
|
||||||
|
assert [ "$(jq 'length' ids2.json)" = $((IDENTITY_COUNT - 2)) ]
|
||||||
|
assertJq ids2.json 'contains([{"sid": "'$SIDA4'"}]) | not'
|
||||||
|
# Unlock PIN1 and assert that all PIN1 identities now appear in the identity
|
||||||
|
# list.
|
||||||
|
rest_request GET "/restful/keyring/identities.json?pin=$PIN1"
|
||||||
|
transform_list_json response.json ids3.json
|
||||||
|
assert [ "$(jq 'length' ids3.json)" = $((IDENTITY_COUNT - 1)) ]
|
||||||
|
assertJq ids3.json 'contains([{"sid": "'$SIDA2'"}])'
|
||||||
|
# Unlock PIN2 and assert that all PIN2 identities now appear in the identity
|
||||||
|
# list.
|
||||||
|
rest_request GET "/restful/keyring/identities.json?pin=$PIN2"
|
||||||
|
transform_list_json response.json ids4.json
|
||||||
|
assert [ "$(jq 'length' ids4.json)" = $IDENTITY_COUNT ]
|
||||||
|
assertJq ids4.json 'contains([{"sid": "'$SIDA4'"}])'
|
||||||
|
}
|
||||||
|
|
||||||
runTests "$@"
|
runTests "$@"
|
||||||
|
Loading…
Reference in New Issue
Block a user