blob: 38d9ba1a726fa42cad875d05f60ffcc1938fab67 [file] [log] [blame]
/*
* MNH Firmware for firmware authentication
*
* Copyright 2017 Google Inc.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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/types.h>
#include <crypto/pkcs7.h>
#include <crypto/public_key.h>
#include <keys/asymmetric-type.h>
#include <keys/system_keyring.h>
#include <linux/device.h>
#include <linux/firmware.h>
#include <linux/kernel.h>
#include <linux/key.h>
#include <linux/slab.h>
#include "mnh-crypto.h"
#include "mnh-sm.h"
#define PKEY_ID_PKCS7 2
#define ESL_SIG_STRING "~Module signature appended~\n"
/**
* Verify signature of a firmware blob in the filesystem
* @dev: Device node
* @path: path of the firmware file to be verified
* RETURNS:
* 0 success
* -EBADMSG PKCS#7 signature with non-detached data
* -ECANCELED invalid PKCS#7 message or signature block is missing
* -EKEYREJECTED key had a usage restriction, or signature failed to match
* -ENOPKG crypto module could not be found
* -ENOKEY PKCS#7 signature not signed with a trusted key
*/
int mnh_crypto_verify_fw(struct device *dev, const char *path)
{
struct cert_info *info;
const struct firmware *img;
int err = 0;
/* load firmware from filesystem */
dev_dbg(dev, "%s loading firmware: %s\n", __func__, path);
err = request_firmware(&img, path, dev);
if (err) {
dev_err(dev, "%s request firmware failed - %d\n",
__func__, err);
return err;
}
dev_dbg(dev, "%s firmware loaded\n", __func__);
info = kzalloc(sizeof(struct cert_info),
GFP_KERNEL);
if (info == NULL) {
release_firmware(img);
return -ENOMEM;
}
info->img = img->data;
info->img_len = img->size;
err = mnh_crypto_verify(dev, info);
kfree(info);
release_firmware(img);
return err;
}
EXPORT_SYMBOL_GPL(mnh_crypto_verify_fw);
/**
* Check proper framing of image, signature, header and end-marker
* and perform signature check.
* @dev: Device node
* @info: data structure including buffer address and size
* RETURNS:
* 0 success
* -EBADMSG PKCS#7 signature with non-detached data
* -ECANCELED invalid PKCS#7 message or signature block is missing
* -EKEYREJECTED key had a usage restriction, or signature failed to match
* -ENOPKG crypto module could not be found
* -ENOKEY PKCS#7 signature not signed with a trusted key
*/
int mnh_crypto_verify(struct device *dev, struct cert_info *info)
{
int err = -ENOKEY;
const unsigned long marker_len = sizeof(ESL_SIG_STRING) - 1;
const void *img = info->img;
dev_dbg(dev, "%s starting signature check\n", __func__);
/* check if signature string is present at the end of the image */
dev_dbg(dev, "%s - buffer addr: %pP | buffer size: %lu\n", __func__,
info->img, info->img_len);
if (memcmp(img + info->img_len - marker_len,
ESL_SIG_STRING, marker_len) == 0) {
dev_dbg(dev, "%s ESL_SIG_STRING found, proceeding\n",
__func__);
info->img_len -= marker_len;
/* verify the signature against the available keys */
err = mnh_crypto_verify_sig(dev, info);
info->cert_size += marker_len;
if (!err) {
info->cert = FW_IMAGE_CERT_OK;
dev_dbg(dev, "%s Image signature verified OK\n",
__func__);
} else {
dev_err(dev, "%s Image signature error: %d\n",
__func__, err);
info->cert = FW_IMAGE_TAINTED;
}
} else {
info->cert = FW_IMAGE_NO_CERT;
dev_err(dev, "%s ESL_SIG_STRING not found, aborting\n",
__func__);
err = -ECANCELED;
}
return err;
}
EXPORT_SYMBOL_GPL(mnh_crypto_verify);
/*
* mnh_crypto_verify_sig checks the validity of the image's signature type
* and validates the signature of the image against the available public
* keys in the system keychain. Function populates info->cert & ->cert_size.
* RETURNS:
* 0 success
* -EBADMSG PKCS#7 signature with non-detached data
* -ECANCELED invalid PKCS#7 message
* -EKEYREJECTED key had a usage restriction, or signature failed to match
* -ENOPKG crypto module could not be found
* -ENOKEY PKCS#7 signature not signed with a trusted key
*/
int mnh_crypto_verify_sig(struct device *dev, struct cert_info *info)
{
struct esl_img_signature esl_sig;
size_t sig_len;
size_t imglen = info->img_len;
const void *esl_img = info->img;
dev_dbg(dev, "mnh %s image size %zu\n", __func__, imglen);
if (imglen <= sizeof(esl_sig))
return -ECANCELED;
/* this structure is appended at the end of the file */
memcpy(&esl_sig, esl_img + (imglen - sizeof(esl_sig)),
sizeof(esl_sig));
/* strip out the esl_img_signature structure at the end */
imglen -= sizeof(esl_sig);
/* length of the actual signature block */
sig_len = be32_to_cpu(esl_sig.sig_len);
/* check signature type */
if (esl_sig.id_type != PKEY_ID_PKCS7) {
dev_err(dev, "mnh %s Image not signed with PKCS#7 message\n",
__func__);
return -ECANCELED;
}
/* strip out the signature, all that is left is the payload */
if (imglen <= sig_len)
return -ECANCELED;
imglen -= sig_len;
dev_dbg(dev, "mnh %s sizes: payload %zu | signature %zu | ident %zu\n",
__func__, imglen, sig_len, sizeof(esl_sig));
/* total size of signature and header information */
info->cert_size = info->img_len - imglen;
return verify_pkcs7_signature(esl_img, imglen,
esl_img + imglen, sig_len,
NULL, VERIFYING_MODULE_SIGNATURE,
NULL, NULL);
}