177 lines
5.4 KiB
C
177 lines
5.4 KiB
C
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
|
|
#include <openssl/pem.h>
|
|
|
|
#include "fd-util.h"
|
|
#include "user-record-sign.h"
|
|
#include "fileio.h"
|
|
|
|
static int user_record_signable_json(UserRecord *ur, char **ret) {
|
|
_cleanup_(user_record_unrefp) UserRecord *reduced = NULL;
|
|
_cleanup_(json_variant_unrefp) JsonVariant *j = NULL;
|
|
int r;
|
|
|
|
assert(ur);
|
|
assert(ret);
|
|
|
|
r = user_record_clone(ur, USER_RECORD_REQUIRE_REGULAR|USER_RECORD_ALLOW_PRIVILEGED|USER_RECORD_ALLOW_PER_MACHINE|USER_RECORD_STRIP_SECRET|USER_RECORD_STRIP_BINDING|USER_RECORD_STRIP_STATUS|USER_RECORD_STRIP_SIGNATURE, &reduced);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
j = json_variant_ref(reduced->json);
|
|
|
|
r = json_variant_normalize(&j);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
return json_variant_format(j, 0, ret);
|
|
}
|
|
|
|
DEFINE_TRIVIAL_CLEANUP_FUNC(EVP_MD_CTX*, EVP_MD_CTX_free);
|
|
|
|
int user_record_sign(UserRecord *ur, EVP_PKEY *private_key, UserRecord **ret) {
|
|
_cleanup_(json_variant_unrefp) JsonVariant *encoded = NULL, *v = NULL;
|
|
_cleanup_(user_record_unrefp) UserRecord *signed_ur = NULL;
|
|
_cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *md_ctx = NULL;
|
|
_cleanup_free_ char *text = NULL, *key = NULL;
|
|
size_t signature_size = 0, key_size = 0;
|
|
_cleanup_free_ void *signature = NULL;
|
|
_cleanup_fclose_ FILE *mf = NULL;
|
|
int r;
|
|
|
|
assert(ur);
|
|
assert(private_key);
|
|
assert(ret);
|
|
|
|
r = user_record_signable_json(ur, &text);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
md_ctx = EVP_MD_CTX_new();
|
|
if (!md_ctx)
|
|
return -ENOMEM;
|
|
|
|
if (EVP_DigestSignInit(md_ctx, NULL, NULL, NULL, private_key) <= 0)
|
|
return -EIO;
|
|
|
|
/* Request signature size */
|
|
if (EVP_DigestSign(md_ctx, NULL, &signature_size, (uint8_t*) text, strlen(text)) <= 0)
|
|
return -EIO;
|
|
|
|
signature = malloc(signature_size);
|
|
if (!signature)
|
|
return -ENOMEM;
|
|
|
|
if (EVP_DigestSign(md_ctx, signature, &signature_size, (uint8_t*) text, strlen(text)) <= 0)
|
|
return -EIO;
|
|
|
|
mf = open_memstream_unlocked(&key, &key_size);
|
|
if (!mf)
|
|
return -ENOMEM;
|
|
|
|
if (PEM_write_PUBKEY(mf, private_key) <= 0)
|
|
return -EIO;
|
|
|
|
r = fflush_and_check(mf);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = json_build(&encoded, JSON_BUILD_ARRAY(
|
|
JSON_BUILD_OBJECT(JSON_BUILD_PAIR("data", JSON_BUILD_BASE64(signature, signature_size)),
|
|
JSON_BUILD_PAIR("key", JSON_BUILD_STRING(key)))));
|
|
if (r < 0)
|
|
return r;
|
|
|
|
v = json_variant_ref(ur->json);
|
|
|
|
r = json_variant_set_field(&v, "signature", encoded);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
if (DEBUG_LOGGING)
|
|
json_variant_dump(v, JSON_FORMAT_PRETTY|JSON_FORMAT_COLOR_AUTO, NULL, NULL);
|
|
|
|
signed_ur = user_record_new();
|
|
if (!signed_ur)
|
|
return log_oom();
|
|
|
|
r = user_record_load(signed_ur, v, USER_RECORD_LOAD_FULL);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
*ret = TAKE_PTR(signed_ur);
|
|
return 0;
|
|
}
|
|
|
|
int user_record_verify(UserRecord *ur, EVP_PKEY *public_key) {
|
|
_cleanup_free_ char *text = NULL;
|
|
unsigned n_good = 0, n_bad = 0;
|
|
JsonVariant *array, *e;
|
|
int r;
|
|
|
|
assert(ur);
|
|
assert(public_key);
|
|
|
|
array = json_variant_by_key(ur->json, "signature");
|
|
if (!array)
|
|
return USER_RECORD_UNSIGNED;
|
|
|
|
if (!json_variant_is_array(array))
|
|
return -EINVAL;
|
|
|
|
if (json_variant_elements(array) == 0)
|
|
return USER_RECORD_UNSIGNED;
|
|
|
|
r = user_record_signable_json(ur, &text);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
JSON_VARIANT_ARRAY_FOREACH(e, array) {
|
|
_cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *md_ctx = NULL;
|
|
_cleanup_free_ void *signature = NULL;
|
|
size_t signature_size = 0;
|
|
JsonVariant *data;
|
|
|
|
if (!json_variant_is_object(e))
|
|
return -EINVAL;
|
|
|
|
data = json_variant_by_key(e, "data");
|
|
if (!data)
|
|
return -EINVAL;
|
|
|
|
r = json_variant_unbase64(data, &signature, &signature_size);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
md_ctx = EVP_MD_CTX_new();
|
|
if (!md_ctx)
|
|
return -ENOMEM;
|
|
|
|
if (EVP_DigestVerifyInit(md_ctx, NULL, NULL, NULL, public_key) <= 0)
|
|
return -EIO;
|
|
|
|
if (EVP_DigestVerify(md_ctx, signature, signature_size, (uint8_t*) text, strlen(text)) <= 0) {
|
|
n_bad ++;
|
|
continue;
|
|
}
|
|
|
|
n_good ++;
|
|
}
|
|
|
|
return n_good > 0 ? (n_bad == 0 ? USER_RECORD_SIGNED_EXCLUSIVE : USER_RECORD_SIGNED) :
|
|
(n_bad == 0 ? USER_RECORD_UNSIGNED : USER_RECORD_FOREIGN);
|
|
}
|
|
|
|
int user_record_has_signature(UserRecord *ur) {
|
|
JsonVariant *array;
|
|
|
|
array = json_variant_by_key(ur->json, "signature");
|
|
if (!array)
|
|
return false;
|
|
|
|
if (!json_variant_is_array(array))
|
|
return -EINVAL;
|
|
|
|
return json_variant_elements(array) > 0;
|
|
}
|