You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
286 lines
6.2 KiB
286 lines
6.2 KiB
/*
|
|
* FIVE keyring
|
|
*
|
|
* Copyright (C) 2017 Samsung Electronics, Inc.
|
|
* Yevgen Kopylov, <y.kopylov@samsung.com>
|
|
*
|
|
* This software is licensed under the terms of the GNU General Public
|
|
* License version 2, as published by the Free Software Foundation, and
|
|
* may be copied, distributed, and modified under those terms.
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#include <linux/key-type.h>
|
|
#include <crypto/public_key.h>
|
|
#include <keys/asymmetric-type.h>
|
|
#include <crypto/hash_info.h>
|
|
#include "five.h"
|
|
#include "five_crypto_comp.h"
|
|
#include "five_porting.h"
|
|
#include "five_testing.h"
|
|
|
|
__visible_for_testing
|
|
struct key *five_keyring;
|
|
|
|
#ifndef CONFIG_FIVE_TRUSTED_KEYRING
|
|
__visible_for_testing
|
|
const char *five_keyring_name = "_five";
|
|
#else
|
|
__visible_for_testing
|
|
const char *five_keyring_name = ".five";
|
|
#endif
|
|
|
|
__visible_for_testing __mockable
|
|
key_ref_t call_keyring_search(
|
|
key_ref_t keyring, struct key_type *type,
|
|
const char *description, bool recurse)
|
|
{
|
|
return keyring_search(keyring, type, description, recurse);
|
|
}
|
|
|
|
__visible_for_testing __mockable
|
|
struct key *call_request_key(struct key_type *type,
|
|
const char *description,
|
|
const char *callout_info)
|
|
{
|
|
return request_key(type, description, callout_info);
|
|
}
|
|
|
|
__visible_for_testing __mockable
|
|
key_ref_t call_key_create_or_update(
|
|
key_ref_t keyring_ref,
|
|
const char *type,
|
|
const char *description,
|
|
const void *payload,
|
|
size_t plen,
|
|
key_perm_t perm,
|
|
unsigned long flags)
|
|
{
|
|
return key_create_or_update(
|
|
keyring_ref, type, description,
|
|
payload, plen, perm, flags);
|
|
}
|
|
|
|
__visible_for_testing __mockable
|
|
void call_key_ref_put(key_ref_t key_ref)
|
|
{
|
|
key_ref_put(key_ref);
|
|
}
|
|
|
|
__visible_for_testing __mockable
|
|
void call_key_put(struct key *key)
|
|
{
|
|
key_put(key);
|
|
}
|
|
|
|
__visible_for_testing __mockable
|
|
int call_five_verify_signature(struct key *key,
|
|
struct public_key_signature *pks,
|
|
struct five_cert *cert,
|
|
struct five_cert_header *header)
|
|
{
|
|
return five_verify_signature(key, pks, cert, header);
|
|
}
|
|
|
|
__visible_for_testing __mockable
|
|
struct key *call_five_keyring_alloc(
|
|
const char *description,
|
|
kuid_t uid, kgid_t gid, const struct cred *cred,
|
|
key_perm_t perm, unsigned long flags)
|
|
{
|
|
return five_keyring_alloc(description, uid, gid, cred, perm, flags);
|
|
}
|
|
|
|
/*
|
|
* Request an asymmetric key.
|
|
*/
|
|
__visible_for_testing
|
|
struct key *five_request_asymmetric_key(uint32_t keyid)
|
|
{
|
|
struct key *key;
|
|
char name[12];
|
|
|
|
snprintf(name, sizeof(name), "id:%08x", keyid);
|
|
|
|
pr_debug("key search: \"%s\"\n", name);
|
|
|
|
if (five_keyring) {
|
|
/* search in specific keyring */
|
|
key_ref_t kref;
|
|
|
|
kref = call_keyring_search(make_key_ref(five_keyring, 1),
|
|
&key_type_asymmetric, name, true);
|
|
if (IS_ERR(kref))
|
|
key = ERR_CAST(kref);
|
|
else
|
|
key = key_ref_to_ptr(kref);
|
|
} else {
|
|
return ERR_PTR(-ENOKEY);
|
|
}
|
|
|
|
if (IS_ERR(key)) {
|
|
switch (PTR_ERR(key)) {
|
|
/* Hide some search errors */
|
|
case -EACCES:
|
|
case -ENOTDIR:
|
|
case -EAGAIN:
|
|
return ERR_PTR(-ENOKEY);
|
|
default:
|
|
return key;
|
|
}
|
|
}
|
|
|
|
pr_debug("%s() = 0 [%x]\n", __func__, key_serial(key));
|
|
|
|
return key;
|
|
}
|
|
|
|
static int five_asymmetric_verify(struct five_cert *cert,
|
|
const char *data, int datalen)
|
|
{
|
|
struct public_key_signature pks;
|
|
struct five_cert_header *header;
|
|
struct key *key;
|
|
int ret = -ENOMEM;
|
|
|
|
header = (struct five_cert_header *)cert->body.header->value;
|
|
if (header->hash_algo >= HASH_ALGO__LAST)
|
|
return -ENOPKG;
|
|
|
|
key = five_request_asymmetric_key(__be32_to_cpu(header->key_id));
|
|
if (IS_ERR(key))
|
|
return PTR_ERR(key);
|
|
|
|
memset(&pks, 0, sizeof(pks));
|
|
|
|
pks.digest = (u8 *)data;
|
|
pks.digest_size = datalen;
|
|
ret = call_five_verify_signature(key, &pks, cert, header);
|
|
|
|
call_key_put(key);
|
|
pr_debug("%s() = %d\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
int five_digsig_verify(struct five_cert *cert,
|
|
const char *digest, int digestlen)
|
|
{
|
|
if (!five_keyring) {
|
|
five_keyring = call_request_key(
|
|
&key_type_keyring, five_keyring_name, NULL);
|
|
if (IS_ERR(five_keyring)) {
|
|
int err = PTR_ERR(five_keyring);
|
|
|
|
pr_err("no %s keyring: %d\n", five_keyring_name, err);
|
|
five_keyring = NULL;
|
|
return err;
|
|
}
|
|
}
|
|
|
|
return five_asymmetric_verify(cert, digest, digestlen);
|
|
}
|
|
|
|
__visible_for_testing
|
|
int __init five_load_x509_from_mem(const char *data, size_t size)
|
|
{
|
|
key_ref_t key;
|
|
int rc = 0;
|
|
|
|
if (!five_keyring || size == 0)
|
|
return -EINVAL;
|
|
|
|
key = call_key_create_or_update(make_key_ref(five_keyring, 1),
|
|
"asymmetric",
|
|
NULL,
|
|
data,
|
|
size,
|
|
((KEY_POS_ALL & ~KEY_POS_SETATTR) |
|
|
KEY_USR_VIEW | KEY_USR_READ),
|
|
KEY_ALLOC_NOT_IN_QUOTA);
|
|
if (IS_ERR(key)) {
|
|
rc = PTR_ERR(key);
|
|
pr_err("Problem loading X.509 certificate (%d): %s\n",
|
|
rc, "built-in");
|
|
} else {
|
|
pr_notice("Loaded X.509 cert '%s': %s\n",
|
|
key_ref_to_ptr(key)->description, "built-in");
|
|
call_key_ref_put(key);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
#ifdef CONFIG_FIVE_CERT_ENG
|
|
extern char five_local_ca_start_eng[];
|
|
extern char five_local_ca_end_eng[];
|
|
|
|
__visible_for_testing
|
|
int __init five_import_eng_key(void)
|
|
{
|
|
size_t size = five_local_ca_end_eng - five_local_ca_start_eng;
|
|
|
|
return five_load_x509_from_mem(five_local_ca_start_eng, size);
|
|
}
|
|
#else
|
|
__visible_for_testing
|
|
int __init five_import_eng_key(void)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_FIVE_CERT_USER
|
|
extern char five_local_ca_start_user[];
|
|
extern char five_local_ca_end_user[];
|
|
|
|
__visible_for_testing
|
|
int __init five_import_user_key(void)
|
|
{
|
|
size_t size = five_local_ca_end_user - five_local_ca_start_user;
|
|
|
|
return five_load_x509_from_mem(five_local_ca_start_user, size);
|
|
}
|
|
#else
|
|
__visible_for_testing
|
|
int __init five_import_user_key(void)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
int __init five_load_built_x509(void)
|
|
{
|
|
int rc;
|
|
|
|
rc = five_import_eng_key();
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = five_import_user_key();
|
|
|
|
return rc;
|
|
}
|
|
|
|
int __init five_keyring_init(void)
|
|
{
|
|
const struct cred *cred = current_cred();
|
|
int err = 0;
|
|
|
|
five_keyring = call_five_keyring_alloc(five_keyring_name, KUIDT_INIT(0),
|
|
KGIDT_INIT(0), cred,
|
|
((KEY_POS_ALL & ~KEY_POS_SETATTR) |
|
|
KEY_USR_VIEW | KEY_USR_READ |
|
|
KEY_USR_SEARCH),
|
|
KEY_ALLOC_NOT_IN_QUOTA);
|
|
if (IS_ERR(five_keyring)) {
|
|
err = PTR_ERR(five_keyring);
|
|
pr_info("Can't allocate %s keyring (%d)\n",
|
|
five_keyring_name, err);
|
|
five_keyring = NULL;
|
|
}
|
|
return err;
|
|
}
|
|
|