mirror of
https://github.com/servalproject/serval-dna.git
synced 2024-12-18 20:57:56 +00:00
98ec1c9608
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
311 lines
11 KiB
C
311 lines
11 KiB
C
/*
|
|
Serval DNA Keyring HTTP RESTful interface
|
|
Copyright (C) 2018 Flinders University
|
|
Copyright (C) 2013-2015 Serval Project Inc.
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "lang.h" // for bool_t, FALLTHROUGH
|
|
#include "serval.h"
|
|
#include "conf.h"
|
|
#include "httpd.h"
|
|
#include "server.h"
|
|
#include "keyring.h"
|
|
#include "strbuf_helpers.h"
|
|
#include "dataformats.h"
|
|
|
|
DEFINE_FEATURE(http_rest_keyring);
|
|
|
|
DECLARE_HANDLER("/restful/keyring/", restful_keyring_);
|
|
|
|
static HTTP_HANDLER restful_keyring_identitylist_json;
|
|
static HTTP_HANDLER restful_keyring_add;
|
|
static HTTP_HANDLER restful_keyring_get;
|
|
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)
|
|
{
|
|
r->http.response.header.content_type = &CONTENT_TYPE_JSON;
|
|
int ret = authorize_restful(&r->http);
|
|
if (ret)
|
|
return ret;
|
|
const char *end;
|
|
if (r->http.verb == HTTP_VERB_GET && strcmp(remainder, "identities.json") == 0)
|
|
return restful_keyring_identitylist_json(r, "");
|
|
if (r->http.verb == HTTP_VERB_POST && strcmp(remainder, "add") == 0)
|
|
return restful_keyring_add(r, "");
|
|
if (parse_sid_t(&r->sid1, remainder, -1, &end) != -1) {
|
|
if (strcmp(end, "") == 0) {
|
|
if (r->http.verb == HTTP_VERB_GET)
|
|
return restful_keyring_get(r, "");
|
|
if (r->http.verb == HTTP_VERB_PATCH)
|
|
return restful_keyring_set(r, "");
|
|
if (r->http.verb == HTTP_VERB_DELETE)
|
|
return restful_keyring_remove(r, "");
|
|
}
|
|
else if (r->http.verb == HTTP_VERB_PUT && strcmp(end, "/lock") == 0)
|
|
return restful_keyring_lock(r, "");
|
|
}
|
|
// 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, "");
|
|
}
|
|
}
|
|
return 404;
|
|
}
|
|
|
|
static int http_request_keyring_response(struct httpd_request *r, uint16_t result, const char *message)
|
|
{
|
|
http_request_simple_response(&r->http, result, message);
|
|
return result;
|
|
}
|
|
|
|
static int http_request_keyring_response_identity(struct httpd_request *r, uint16_t result, const keyring_identity *id)
|
|
{
|
|
const char *did = NULL;
|
|
const char *name = NULL;
|
|
keyring_identity_extract(id, &did, &name);
|
|
struct json_atom json_id;
|
|
struct json_key_value json_id_kv[4];
|
|
struct json_atom json_sid;
|
|
struct json_atom json_sas;
|
|
struct json_atom json_did;
|
|
struct json_atom json_name;
|
|
json_id.type = JSON_OBJECT;
|
|
json_id.u.object.itemc = 2;
|
|
json_id.u.object.itemv = json_id_kv;
|
|
json_id_kv[0].key = "sid";
|
|
json_id_kv[0].value = &json_sid;
|
|
json_sid.type = JSON_STRING_NULTERM;
|
|
json_sid.u.string.content = alloca_tohex_sid_t(*id->box_pk);
|
|
json_id_kv[1].key = "identity";
|
|
json_id_kv[1].value = &json_sas;
|
|
json_sas.type = JSON_STRING_NULTERM;
|
|
json_sas.u.string.content = alloca_tohex_identity_t(&id->sign_keypair->public_key);
|
|
if (did) {
|
|
json_id_kv[json_id.u.object.itemc].key = "did";
|
|
json_id_kv[json_id.u.object.itemc].value = &json_did;
|
|
++json_id.u.object.itemc;
|
|
json_did.type = JSON_STRING_NULTERM;
|
|
json_did.u.string.content = did;
|
|
}
|
|
if (name) {
|
|
json_id_kv[json_id.u.object.itemc].key = "name";
|
|
json_id_kv[json_id.u.object.itemc].value = &json_name;
|
|
++json_id.u.object.itemc;
|
|
json_name.type = JSON_STRING_NULTERM;
|
|
json_name.u.string.content = name;
|
|
}
|
|
r->http.response.result_extra[0].label = "identity";
|
|
r->http.response.result_extra[0].value = json_id;
|
|
return http_request_keyring_response(r, result, NULL);
|
|
}
|
|
|
|
static HTTP_CONTENT_GENERATOR restful_keyring_identitylist_json_content;
|
|
|
|
static int restful_keyring_identitylist_json(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);
|
|
r->u.sidlist.phase = LIST_HEADER;
|
|
keyring_iterator_start(keyring, &r->u.sidlist.it);
|
|
http_request_response_generated(&r->http, 200, &CONTENT_TYPE_JSON, restful_keyring_identitylist_json_content);
|
|
return 1;
|
|
}
|
|
|
|
static HTTP_CONTENT_GENERATOR_STRBUF_CHUNKER restful_keyring_identitylist_json_content_chunk;
|
|
|
|
static int restful_keyring_identitylist_json_content(struct http_request *hr, unsigned char *buf, size_t bufsz, struct http_content_generator_result *result)
|
|
{
|
|
return generate_http_content_from_strbuf_chunks(hr, (char *)buf, bufsz, result, restful_keyring_identitylist_json_content_chunk);
|
|
}
|
|
|
|
static int restful_keyring_identitylist_json_content_chunk(struct http_request *hr, strbuf b)
|
|
{
|
|
httpd_request *r = (httpd_request *) hr;
|
|
// The "my_sid" and "their_sid" per-conversation fields allow the same JSON structure to be used
|
|
// in a future, non-SID-specific request, eg, to list all conversations for all currently open
|
|
// identities.
|
|
const char *headers[] = {
|
|
"sid",
|
|
"identity",
|
|
"did",
|
|
"name"
|
|
};
|
|
switch (r->u.sidlist.phase) {
|
|
case LIST_HEADER:
|
|
strbuf_puts(b, "{\n\"header\":[");
|
|
unsigned i;
|
|
for (i = 0; i != NELS(headers); ++i) {
|
|
if (i)
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_string(b, headers[i]);
|
|
}
|
|
strbuf_puts(b, "],\n\"rows\":[");
|
|
if (!strbuf_overrun(b)){
|
|
r->u.sidlist.phase = LIST_FIRST;
|
|
if (!keyring_next_identity(&r->u.sidlist.it))
|
|
r->u.sidlist.phase = LIST_END;
|
|
}
|
|
return 1;
|
|
|
|
case LIST_ROWS:
|
|
strbuf_putc(b, ',');
|
|
FALLTHROUGH;
|
|
case LIST_FIRST:
|
|
r->u.sidlist.phase = LIST_ROWS;
|
|
const char *did = NULL;
|
|
const char *name = NULL;
|
|
keyring_identity_extract(r->u.sidlist.it.identity, &did, &name);
|
|
strbuf_puts(b, "\n[");
|
|
strbuf_json_string(b, alloca_tohex_sid_t(*r->u.sidlist.it.identity->box_pk));
|
|
strbuf_puts(b, ",");
|
|
strbuf_json_string(b, alloca_tohex_identity_t(&r->u.sidlist.it.identity->sign_keypair->public_key));
|
|
strbuf_puts(b, ",");
|
|
strbuf_json_string(b, did);
|
|
strbuf_puts(b, ",");
|
|
strbuf_json_string(b, name);
|
|
strbuf_puts(b, "]");
|
|
|
|
if (!strbuf_overrun(b)) {
|
|
if (!keyring_next_identity(&r->u.sidlist.it))
|
|
r->u.sidlist.phase = LIST_END;
|
|
}
|
|
return 1;
|
|
|
|
case LIST_END:
|
|
strbuf_puts(b, "\n]\n}\n");
|
|
if (strbuf_overrun(b))
|
|
return 1;
|
|
|
|
r->u.sidlist.phase = LIST_DONE;
|
|
// fall through...
|
|
case LIST_DONE:
|
|
return 0;
|
|
}
|
|
abort();
|
|
return 0;
|
|
}
|
|
|
|
static int restful_keyring_add(httpd_request *r, const char *remainder)
|
|
{
|
|
if (*remainder)
|
|
return 404;
|
|
const char *pin = http_request_get_query_param(&r->http, "pin");
|
|
const char *did = http_request_get_query_param(&r->http, "did");
|
|
const char *name = http_request_get_query_param(&r->http, "name");
|
|
if (did && did[0] && !str_is_did(did))
|
|
return http_request_keyring_response(r, 400, "Invalid DID");
|
|
if (name && name[0] && !str_is_identity_name(name))
|
|
return http_request_keyring_response(r, 400, "Invalid Name");
|
|
keyring_identity *id = keyring_create_identity(keyring, pin ? pin : "");
|
|
if (id == NULL)
|
|
return http_request_keyring_response(r, 500, "Could not create identity");
|
|
if ((did || name) && keyring_set_did_name(id, did ? did : "", name ? name : "") == -1) {
|
|
keyring_free_identity(id);
|
|
return http_request_keyring_response(r, 500, "Could not set identity DID/Name");
|
|
}
|
|
if (keyring_commit(keyring) == -1) {
|
|
keyring_free_identity(id);
|
|
return http_request_keyring_response(r, 500, "Could not store new identity");
|
|
}
|
|
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)
|
|
{
|
|
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");
|
|
keyring_destroy_identity(keyring, id);
|
|
if (keyring_commit(keyring) == -1)
|
|
return http_request_keyring_response(r, 500, "Could not erase removed identity");
|
|
int ret = http_request_keyring_response_identity(r, 200, id);
|
|
keyring_free_identity(id);
|
|
return ret;
|
|
}
|
|
|
|
static int restful_keyring_set(httpd_request *r, const char *remainder)
|
|
{
|
|
if (*remainder)
|
|
return 404;
|
|
const char *pin = http_request_get_query_param(&r->http, "pin");
|
|
const char *did = http_request_get_query_param(&r->http, "did");
|
|
const char *name = http_request_get_query_param(&r->http, "name");
|
|
if (did && did[0] && !str_is_did(did))
|
|
return http_request_keyring_response(r, 400, "Invalid DID");
|
|
if (name && name[0] && !str_is_identity_name(name))
|
|
return http_request_keyring_response(r, 400, "Invalid Name");
|
|
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");
|
|
if (keyring_set_did_name(id, did, name) == -1)
|
|
return http_request_keyring_response(r, 500, "Could not set identity DID/Name");
|
|
if (keyring_commit(keyring) == -1)
|
|
return http_request_keyring_response(r, 500, "Could not store new identity");
|
|
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");
|
|
}
|