tpmdd updates for v5.19-rc1

- Strictened validation of key hashes for SYSTEM_BLACKLIST_HASH_LIST.  An
   invalid hash format causes a compilation error.  Previously, they got
   included to the kernel binary but were silently ignored at run-time.
 - Allow root user to append new hashes to the blacklist keyring.
 - Trusted keys backed with Cryptographic Acceleration and Assurance Module
   (CAAM), which part of some of the new NXP's SoC's.  Now there is total
   three hardware backends for trusted keys: TPM, ARM TEE and CAAM.
 - A scattered set of fixes and small improvements for the TPM driver.
 
 Signed-off-by: Jarkko Sakkinen <jarkko@kernel.org>
 -----BEGIN PGP SIGNATURE-----
 
 iIgEABYIADAWIQRE6pSOnaBC00OEHEIaerohdGur0gUCYoux6xIcamFya2tvQGtl
 cm5lbC5vcmcACgkQGnq6IXRrq9LTQgEA4zRrlmLPjhZ1iZpPZiyBBv5eOx20/c+y
 R7tCfJFB2+ABAOT1E885vt+GgKTY4mYloHJ+ZtnTIf1QRMP6EoSX+TwP
 =oBOO
 -----END PGP SIGNATURE-----

Merge tag 'tpmdd-next-v5.19-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/jarkko/linux-tpmdd

Pull tpm updates from Jarkko Sakkinen:

 - Tightened validation of key hashes for SYSTEM_BLACKLIST_HASH_LIST. An
   invalid hash format causes a compilation error. Previously, they got
   included to the kernel binary but were silently ignored at run-time.

 - Allow root user to append new hashes to the blacklist keyring.

 - Trusted keys backed with Cryptographic Acceleration and Assurance
   Module (CAAM), which part of some of the new NXP's SoC's. Now there
   is total three hardware backends for trusted keys: TPM, ARM TEE and
   CAAM.

 - A scattered set of fixes and small improvements for the TPM driver.

* tag 'tpmdd-next-v5.19-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/jarkko/linux-tpmdd:
  MAINTAINERS: add KEYS-TRUSTED-CAAM
  doc: trusted-encrypted: describe new CAAM trust source
  KEYS: trusted: Introduce support for NXP CAAM-based trusted keys
  crypto: caam - add in-kernel interface for blob generator
  crypto: caam - determine whether CAAM supports blob encap/decap
  KEYS: trusted: allow use of kernel RNG for key material
  KEYS: trusted: allow use of TEE as backend without TCG_TPM support
  tpm: Add field upgrade mode support for Infineon TPM2 modules
  tpm: Fix buffer access in tpm2_get_tpm_pt()
  char: tpm: cr50_i2c: Suppress duplicated error message in .remove()
  tpm: cr50: Add new device/vendor ID 0x504a6666
  tpm: Remove read16/read32/write32 calls from tpm_tis_phy_ops
  tpm: ibmvtpm: Correct the return value in tpm_ibmvtpm_probe()
  tpm/tpm_ftpm_tee: Return true/false (not 1/0) from bool functions
  certs: Explain the rationale to call panic()
  certs: Allow root user to append signed hashes to the blacklist keyring
  certs: Check that builtin blacklist hashes are valid
  certs: Make blacklist_vet_description() more strict
  certs: Factor out the blacklist hash creation
  tools/certs: Add print-cert-tbs-hash.sh
This commit is contained in:
Linus Torvalds 2022-05-24 13:16:50 -07:00
commit 7cf6a8a17f
36 changed files with 1054 additions and 279 deletions

View File

@ -6081,11 +6081,22 @@
sources:
- "tpm"
- "tee"
- "caam"
If not specified then it defaults to iterating through
the trust source list starting with TPM and assigns the
first trust source as a backend which is initialized
successfully during iteration.
trusted.rng= [KEYS]
Format: <string>
The RNG used to generate key material for trusted keys.
Can be one of:
- "kernel"
- the same value as trusted.source: "tpm" or "tee"
- "default"
If not specified, "default" is used. In this case,
the RNG's choice is left to each individual trust source.
tsc= Disable clocksource stability checks for TSC.
Format: <string>
[x86] reliable: mark tsc clocksource as reliable, this

View File

@ -35,6 +35,13 @@ safe.
Rooted to Hardware Unique Key (HUK) which is generally burnt in on-chip
fuses and is accessible to TEE only.
(3) CAAM (Cryptographic Acceleration and Assurance Module: IP on NXP SoCs)
When High Assurance Boot (HAB) is enabled and the CAAM is in secure
mode, trust is rooted to the OTPMK, a never-disclosed 256-bit key
randomly generated and fused into each SoC at manufacturing time.
Otherwise, a common fixed test key is used instead.
* Execution isolation
(1) TPM
@ -46,6 +53,10 @@ safe.
Customizable set of operations running in isolated execution
environment verified via Secure/Trusted boot process.
(3) CAAM
Fixed set of operations running in isolated execution environment.
* Optional binding to platform integrity state
(1) TPM
@ -63,6 +74,11 @@ safe.
Relies on Secure/Trusted boot process for platform integrity. It can
be extended with TEE based measured boot process.
(3) CAAM
Relies on the High Assurance Boot (HAB) mechanism of NXP SoCs
for platform integrity.
* Interfaces and APIs
(1) TPM
@ -74,10 +90,13 @@ safe.
TEEs have well-documented, standardized client interface and APIs. For
more details refer to ``Documentation/staging/tee.rst``.
(3) CAAM
Interface is specific to silicon vendor.
* Threat model
The strength and appropriateness of a particular TPM or TEE for a given
The strength and appropriateness of a particular trust source for a given
purpose must be assessed when using them to protect security-relevant data.
@ -87,22 +106,32 @@ Key Generation
Trusted Keys
------------
New keys are created from random numbers generated in the trust source. They
are encrypted/decrypted using a child key in the storage key hierarchy.
Encryption and decryption of the child key must be protected by a strong
access control policy within the trust source.
New keys are created from random numbers. They are encrypted/decrypted using
a child key in the storage key hierarchy. Encryption and decryption of the
child key must be protected by a strong access control policy within the
trust source. The random number generator in use differs according to the
selected trust source:
* TPM (hardware device) based RNG
* TPM: hardware device based RNG
Strength of random numbers may vary from one device manufacturer to
another.
Keys are generated within the TPM. Strength of random numbers may vary
from one device manufacturer to another.
* TEE (OP-TEE based on Arm TrustZone) based RNG
* TEE: OP-TEE based on Arm TrustZone based RNG
RNG is customizable as per platform needs. It can either be direct output
from platform specific hardware RNG or a software based Fortuna CSPRNG
which can be seeded via multiple entropy sources.
* CAAM: Kernel RNG
The normal kernel random number generator is used. To seed it from the
CAAM HWRNG, enable CRYPTO_DEV_FSL_CAAM_RNG_API and ensure the device
is probed.
Users may override this by specifying ``trusted.rng=kernel`` on the kernel
command-line to override the used RNG with the kernel's random number pool.
Encrypted Keys
--------------
@ -189,6 +218,19 @@ Usage::
specific to TEE device implementation. The key length for new keys is always
in bytes. Trusted Keys can be 32 - 128 bytes (256 - 1024 bits).
Trusted Keys usage: CAAM
------------------------
Usage::
keyctl add trusted name "new keylen" ring
keyctl add trusted name "load hex_blob" ring
keyctl print keyid
"keyctl print" returns an ASCII hex copy of the sealed key, which is in a
CAAM-specific format. The key length for new keys is always in bytes.
Trusted Keys can be 32 - 128 bytes (256 - 1024 bits).
Encrypted Keys usage
--------------------

View File

@ -4575,7 +4575,9 @@ L: keyrings@vger.kernel.org
S: Maintained
F: Documentation/admin-guide/module-signing.rst
F: certs/
F: scripts/check-blacklist-hashes.awk
F: scripts/sign-file.c
F: tools/certs/
CFAG12864B LCD DRIVER
M: Miguel Ojeda <ojeda@kernel.org>
@ -10865,6 +10867,15 @@ S: Supported
F: include/keys/trusted_tee.h
F: security/keys/trusted-keys/trusted_tee.c
KEYS-TRUSTED-CAAM
M: Ahmad Fatoum <a.fatoum@pengutronix.de>
R: Pengutronix Kernel Team <kernel@pengutronix.de>
L: linux-integrity@vger.kernel.org
L: keyrings@vger.kernel.org
S: Maintained
F: include/keys/trusted_caam.h
F: security/keys/trusted-keys/trusted_caam.c
KEYS/KEYRINGS
M: David Howells <dhowells@redhat.com>
M: Jarkko Sakkinen <jarkko@kernel.org>

1
certs/.gitignore vendored
View File

@ -1,4 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-only
/blacklist_hashes_checked
/extract-cert
/x509_certificate_list
/x509_revocation_list

View File

@ -104,8 +104,11 @@ config SYSTEM_BLACKLIST_HASH_LIST
help
If set, this option should be the filename of a list of hashes in the
form "<hash>", "<hash>", ... . This will be included into a C
wrapper to incorporate the list into the kernel. Each <hash> should
be a string of hex digits.
wrapper to incorporate the list into the kernel. Each <hash> must be a
string starting with a prefix ("tbs" or "bin"), then a colon (":"), and
finally an even number of hexadecimal lowercase characters (up to 128).
Certificate hashes can be generated with
tools/certs/print-cert-tbs-hash.sh .
config SYSTEM_REVOCATION_LIST
bool "Provide system-wide ring of revocation certificates"
@ -124,4 +127,14 @@ config SYSTEM_REVOCATION_KEYS
containing X.509 certificates to be included in the default blacklist
keyring.
config SYSTEM_BLACKLIST_AUTH_UPDATE
bool "Allow root to add signed blacklist keys"
depends on SYSTEM_BLACKLIST_KEYRING
depends on SYSTEM_DATA_VERIFICATION
help
If set, provide the ability to load new blacklist keys at run time if
they are signed and vouched by a certificate from the builtin trusted
keyring. The PKCS#7 signature of the description is set in the key
payload. Blacklist keys cannot be removed.
endmenu

View File

@ -7,6 +7,18 @@ obj-$(CONFIG_SYSTEM_TRUSTED_KEYRING) += system_keyring.o system_certificates.o c
obj-$(CONFIG_SYSTEM_BLACKLIST_KEYRING) += blacklist.o common.o
obj-$(CONFIG_SYSTEM_REVOCATION_LIST) += revocation_certificates.o
ifneq ($(CONFIG_SYSTEM_BLACKLIST_HASH_LIST),)
quiet_cmd_check_blacklist_hashes = CHECK $(patsubst "%",%,$(2))
cmd_check_blacklist_hashes = $(AWK) -f $(srctree)/scripts/check-blacklist-hashes.awk $(2); touch $@
$(eval $(call config_filename,SYSTEM_BLACKLIST_HASH_LIST))
$(obj)/blacklist_hashes.o: $(obj)/blacklist_hashes_checked
CFLAGS_blacklist_hashes.o += -I$(srctree)
targets += blacklist_hashes_checked
$(obj)/blacklist_hashes_checked: $(SYSTEM_BLACKLIST_HASH_LIST_SRCPREFIX)$(SYSTEM_BLACKLIST_HASH_LIST_FILENAME) scripts/check-blacklist-hashes.awk FORCE
$(call if_changed,check_blacklist_hashes,$(SYSTEM_BLACKLIST_HASH_LIST_SRCPREFIX)$(CONFIG_SYSTEM_BLACKLIST_HASH_LIST))
obj-$(CONFIG_SYSTEM_BLACKLIST_KEYRING) += blacklist_hashes.o
else
obj-$(CONFIG_SYSTEM_BLACKLIST_KEYRING) += blacklist_nohashes.o
@ -21,7 +33,7 @@ $(obj)/system_certificates.o: $(obj)/x509_certificate_list
$(obj)/x509_certificate_list: $(CONFIG_SYSTEM_TRUSTED_KEYS) $(obj)/extract-cert FORCE
$(call if_changed,extract_certs)
targets += x509_certificate_list
targets += x509_certificate_list blacklist_hashes_checked
# If module signing is requested, say by allyesconfig, but a key has not been
# supplied, then one will need to be generated to make sure the build does not

View File

@ -15,10 +15,24 @@
#include <linux/err.h>
#include <linux/seq_file.h>
#include <linux/uidgid.h>
#include <linux/verification.h>
#include <keys/system_keyring.h>
#include "blacklist.h"
#include "common.h"
/*
* According to crypto/asymmetric_keys/x509_cert_parser.c:x509_note_pkey_algo(),
* the size of the currently longest supported hash algorithm is 512 bits,
* which translates into 128 hex characters.
*/
#define MAX_HASH_LEN 128
#define BLACKLIST_KEY_PERM (KEY_POS_SEARCH | KEY_POS_VIEW | \
KEY_USR_SEARCH | KEY_USR_VIEW)
static const char tbs_prefix[] = "tbs";
static const char bin_prefix[] = "bin";
static struct key *blacklist_keyring;
#ifdef CONFIG_SYSTEM_REVOCATION_LIST
@ -32,41 +46,89 @@ extern __initconst const unsigned long revocation_certificate_list_size;
*/
static int blacklist_vet_description(const char *desc)
{
int n = 0;
int i, prefix_len, tbs_step = 0, bin_step = 0;
if (*desc == ':')
return -EINVAL;
for (; *desc; desc++)
if (*desc == ':')
goto found_colon;
/* The following algorithm only works if prefix lengths match. */
BUILD_BUG_ON(sizeof(tbs_prefix) != sizeof(bin_prefix));
prefix_len = sizeof(tbs_prefix) - 1;
for (i = 0; *desc; desc++, i++) {
if (*desc == ':') {
if (tbs_step == prefix_len)
goto found_colon;
if (bin_step == prefix_len)
goto found_colon;
return -EINVAL;
}
if (i >= prefix_len)
return -EINVAL;
if (*desc == tbs_prefix[i])
tbs_step++;
if (*desc == bin_prefix[i])
bin_step++;
}
return -EINVAL;
found_colon:
desc++;
for (; *desc; desc++) {
for (i = 0; *desc && i < MAX_HASH_LEN; desc++, i++) {
if (!isxdigit(*desc) || isupper(*desc))
return -EINVAL;
n++;
}
if (*desc)
/* The hash is greater than MAX_HASH_LEN. */
return -ENOPKG;
if (n == 0 || n & 1)
/* Checks for an even number of hexadecimal characters. */
if (i == 0 || i & 1)
return -EINVAL;
return 0;
}
/*
* The hash to be blacklisted is expected to be in the description. There will
* be no payload.
*/
static int blacklist_preparse(struct key_preparsed_payload *prep)
static int blacklist_key_instantiate(struct key *key,
struct key_preparsed_payload *prep)
{
if (prep->datalen > 0)
return -EINVAL;
return 0;
#ifdef CONFIG_SYSTEM_BLACKLIST_AUTH_UPDATE
int err;
#endif
/* Sets safe default permissions for keys loaded by user space. */
key->perm = BLACKLIST_KEY_PERM;
/*
* Skips the authentication step for builtin hashes, they are not
* signed but still trusted.
*/
if (key->flags & (1 << KEY_FLAG_BUILTIN))
goto out;
#ifdef CONFIG_SYSTEM_BLACKLIST_AUTH_UPDATE
/*
* Verifies the description's PKCS#7 signature against the builtin
* trusted keyring.
*/
err = verify_pkcs7_signature(key->description,
strlen(key->description), prep->data, prep->datalen,
NULL, VERIFYING_UNSPECIFIED_SIGNATURE, NULL, NULL);
if (err)
return err;
#else
/*
* It should not be possible to come here because the keyring doesn't
* have KEY_USR_WRITE and the only other way to call this function is
* for builtin hashes.
*/
WARN_ON_ONCE(1);
return -EPERM;
#endif
out:
return generic_key_instantiate(key, prep);
}
static void blacklist_free_preparse(struct key_preparsed_payload *prep)
static int blacklist_key_update(struct key *key,
struct key_preparsed_payload *prep)
{
return -EPERM;
}
static void blacklist_describe(const struct key *key, struct seq_file *m)
@ -77,17 +139,48 @@ static void blacklist_describe(const struct key *key, struct seq_file *m)
static struct key_type key_type_blacklist = {
.name = "blacklist",
.vet_description = blacklist_vet_description,
.preparse = blacklist_preparse,
.free_preparse = blacklist_free_preparse,
.instantiate = generic_key_instantiate,
.instantiate = blacklist_key_instantiate,
.update = blacklist_key_update,
.describe = blacklist_describe,
};
static char *get_raw_hash(const u8 *hash, size_t hash_len,
enum blacklist_hash_type hash_type)
{
size_t type_len;
const char *type_prefix;
char *buffer, *p;
switch (hash_type) {
case BLACKLIST_HASH_X509_TBS:
type_len = sizeof(tbs_prefix) - 1;
type_prefix = tbs_prefix;
break;
case BLACKLIST_HASH_BINARY:
type_len = sizeof(bin_prefix) - 1;
type_prefix = bin_prefix;
break;
default:
WARN_ON_ONCE(1);
return ERR_PTR(-EINVAL);
}
buffer = kmalloc(type_len + 1 + hash_len * 2 + 1, GFP_KERNEL);
if (!buffer)
return ERR_PTR(-ENOMEM);
p = memcpy(buffer, type_prefix, type_len);
p += type_len;
*p++ = ':';
bin2hex(p, hash, hash_len);
p += hash_len * 2;
*p = '\0';
return buffer;
}
/**
* mark_hash_blacklisted - Add a hash to the system blacklist
* mark_raw_hash_blacklisted - Add a hash to the system blacklist
* @hash: The hash as a hex string with a type prefix (eg. "tbs:23aa429783")
*/
int mark_hash_blacklisted(const char *hash)
static int mark_raw_hash_blacklisted(const char *hash)
{
key_ref_t key;
@ -96,8 +189,7 @@ int mark_hash_blacklisted(const char *hash)
hash,
NULL,
0,
((KEY_POS_ALL & ~KEY_POS_SETATTR) |
KEY_USR_VIEW),
BLACKLIST_KEY_PERM,
KEY_ALLOC_NOT_IN_QUOTA |
KEY_ALLOC_BUILT_IN);
if (IS_ERR(key)) {
@ -107,29 +199,36 @@ int mark_hash_blacklisted(const char *hash)
return 0;
}
int mark_hash_blacklisted(const u8 *hash, size_t hash_len,
enum blacklist_hash_type hash_type)
{
const char *buffer;
int err;
buffer = get_raw_hash(hash, hash_len, hash_type);
if (IS_ERR(buffer))
return PTR_ERR(buffer);
err = mark_raw_hash_blacklisted(buffer);
kfree(buffer);
return err;
}
/**
* is_hash_blacklisted - Determine if a hash is blacklisted
* @hash: The hash to be checked as a binary blob
* @hash_len: The length of the binary hash
* @type: Type of hash
* @hash_type: Type of hash
*/
int is_hash_blacklisted(const u8 *hash, size_t hash_len, const char *type)
int is_hash_blacklisted(const u8 *hash, size_t hash_len,
enum blacklist_hash_type hash_type)
{
key_ref_t kref;
size_t type_len = strlen(type);
char *buffer, *p;
const char *buffer;
int ret = 0;
buffer = kmalloc(type_len + 1 + hash_len * 2 + 1, GFP_KERNEL);
if (!buffer)
return -ENOMEM;
p = memcpy(buffer, type, type_len);
p += type_len;
*p++ = ':';
bin2hex(p, hash, hash_len);
p += hash_len * 2;
*p = 0;
buffer = get_raw_hash(hash, hash_len, hash_type);
if (IS_ERR(buffer))
return PTR_ERR(buffer);
kref = keyring_search(make_key_ref(blacklist_keyring, true),
&key_type_blacklist, buffer, false);
if (!IS_ERR(kref)) {
@ -144,7 +243,8 @@ EXPORT_SYMBOL_GPL(is_hash_blacklisted);
int is_binary_blacklisted(const u8 *hash, size_t hash_len)
{
if (is_hash_blacklisted(hash, hash_len, "bin") == -EKEYREJECTED)
if (is_hash_blacklisted(hash, hash_len, BLACKLIST_HASH_BINARY) ==
-EKEYREJECTED)
return -EPERM;
return 0;
@ -166,8 +266,10 @@ int add_key_to_revocation_list(const char *data, size_t size)
NULL,
data,
size,
((KEY_POS_ALL & ~KEY_POS_SETATTR) | KEY_USR_VIEW),
KEY_ALLOC_NOT_IN_QUOTA | KEY_ALLOC_BUILT_IN);
KEY_POS_VIEW | KEY_POS_READ | KEY_POS_SEARCH
| KEY_USR_VIEW,
KEY_ALLOC_NOT_IN_QUOTA | KEY_ALLOC_BUILT_IN
| KEY_ALLOC_BYPASS_RESTRICTION);
if (IS_ERR(key)) {
pr_err("Problem with revocation key (%ld)\n", PTR_ERR(key));
@ -194,30 +296,57 @@ int is_key_on_revocation_list(struct pkcs7_message *pkcs7)
}
#endif
static int restrict_link_for_blacklist(struct key *dest_keyring,
const struct key_type *type, const union key_payload *payload,
struct key *restrict_key)
{
if (type == &key_type_blacklist)
return 0;
return -EOPNOTSUPP;
}
/*
* Initialise the blacklist
*
* The blacklist_init() function is registered as an initcall via
* device_initcall(). As a result if the blacklist_init() function fails for
* any reason the kernel continues to execute. While cleanly returning -ENODEV
* could be acceptable for some non-critical kernel parts, if the blacklist
* keyring fails to load it defeats the certificate/key based deny list for
* signed modules. If a critical piece of security functionality that users
* expect to be present fails to initialize, panic()ing is likely the right
* thing to do.
*/
static int __init blacklist_init(void)
{
const char *const *bl;
struct key_restriction *restriction;
if (register_key_type(&key_type_blacklist) < 0)
panic("Can't allocate system blacklist key type\n");
restriction = kzalloc(sizeof(*restriction), GFP_KERNEL);
if (!restriction)
panic("Can't allocate blacklist keyring restriction\n");
restriction->check = restrict_link_for_blacklist;
blacklist_keyring =
keyring_alloc(".blacklist",
GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, current_cred(),
(KEY_POS_ALL & ~KEY_POS_SETATTR) |
KEY_USR_VIEW | KEY_USR_READ |
KEY_USR_SEARCH,
KEY_ALLOC_NOT_IN_QUOTA |
KEY_POS_VIEW | KEY_POS_READ | KEY_POS_SEARCH |
KEY_POS_WRITE |
KEY_USR_VIEW | KEY_USR_READ | KEY_USR_SEARCH
#ifdef CONFIG_SYSTEM_BLACKLIST_AUTH_UPDATE
| KEY_USR_WRITE
#endif
, KEY_ALLOC_NOT_IN_QUOTA |
KEY_ALLOC_SET_KEEP,
NULL, NULL);
restriction, NULL);
if (IS_ERR(blacklist_keyring))
panic("Can't allocate system blacklist keyring\n");
for (bl = blacklist_hashes; *bl; bl++)
if (mark_hash_blacklisted(*bl) < 0)
if (mark_raw_hash_blacklisted(*bl) < 0)
pr_err("- blacklisting failed\n");
return 0;
}

View File

@ -69,7 +69,8 @@ int x509_get_sig_params(struct x509_certificate *cert)
if (ret < 0)
goto error_2;
ret = is_hash_blacklisted(sig->digest, sig->digest_size, "tbs");
ret = is_hash_blacklisted(sig->digest, sig->digest_size,
BLACKLIST_HASH_X509_TBS);
if (ret == -EKEYREJECTED) {
pr_err("Cert %*phN is blacklisted\n",
sig->digest_size, sig->digest);

View File

@ -400,7 +400,16 @@ ssize_t tpm2_get_tpm_pt(struct tpm_chip *chip, u32 property_id, u32 *value,
if (!rc) {
out = (struct tpm2_get_cap_out *)
&buf.data[TPM_HEADER_SIZE];
*value = be32_to_cpu(out->value);
/*
* To prevent failing boot up of some systems, Infineon TPM2.0
* returns SUCCESS on TPM2_Startup in field upgrade mode. Also
* the TPM2_Getcapability command returns a zero length list
* in field upgrade mode.
*/
if (be32_to_cpu(out->property_cnt) > 0)
*value = be32_to_cpu(out->value);
else
rc = -ENODATA;
}
tpm_buf_destroy(&buf);
return rc;
@ -745,7 +754,11 @@ int tpm2_auto_startup(struct tpm_chip *chip)
rc = tpm2_get_cc_attrs_tbl(chip);
out:
if (rc == TPM2_RC_UPGRADE) {
/*
* Infineon TPM in field upgrade mode will return no data for the number
* of supported commands.
*/
if (rc == TPM2_RC_UPGRADE || rc == -ENODATA) {
dev_info(&chip->dev, "TPM in field upgrade mode, requires firmware upgrade\n");
chip->flags |= TPM_CHIP_FLAG_FIRMWARE_UPGRADE;
rc = 0;

View File

@ -177,7 +177,7 @@ static u8 ftpm_tee_tpm_op_status(struct tpm_chip *chip)
static bool ftpm_tee_tpm_req_canceled(struct tpm_chip *chip, u8 status)
{
return 0;
return false;
}
static const struct tpm_class_ops ftpm_tee_tpm_ops = {

View File

@ -681,6 +681,7 @@ static int tpm_ibmvtpm_probe(struct vio_dev *vio_dev,
if (!wait_event_timeout(ibmvtpm->crq_queue.wq,
ibmvtpm->rtce_buf != NULL,
HZ)) {
rc = -ENODEV;
dev_err(dev, "CRQ response timed out\n");
goto init_irq_cleanup;
}

View File

@ -153,50 +153,46 @@ static int check_acpi_tpm2(struct device *dev)
#endif
static int tpm_tcg_read_bytes(struct tpm_tis_data *data, u32 addr, u16 len,
u8 *result)
u8 *result, enum tpm_tis_io_mode io_mode)
{
struct tpm_tis_tcg_phy *phy = to_tpm_tis_tcg_phy(data);
__le16 result_le16;
__le32 result_le32;
while (len--)
*result++ = ioread8(phy->iobase + addr);
switch (io_mode) {
case TPM_TIS_PHYS_8:
while (len--)
*result++ = ioread8(phy->iobase + addr);
break;
case TPM_TIS_PHYS_16:
result_le16 = cpu_to_le16(ioread16(phy->iobase + addr));
memcpy(result, &result_le16, sizeof(u16));
break;
case TPM_TIS_PHYS_32:
result_le32 = cpu_to_le32(ioread32(phy->iobase + addr));
memcpy(result, &result_le32, sizeof(u32));
break;
}
return 0;
}
static int tpm_tcg_write_bytes(struct tpm_tis_data *data, u32 addr, u16 len,
const u8 *value)
const u8 *value, enum tpm_tis_io_mode io_mode)
{
struct tpm_tis_tcg_phy *phy = to_tpm_tis_tcg_phy(data);
while (len--)
iowrite8(*value++, phy->iobase + addr);
return 0;
}
static int tpm_tcg_read16(struct tpm_tis_data *data, u32 addr, u16 *result)
{
struct tpm_tis_tcg_phy *phy = to_tpm_tis_tcg_phy(data);
*result = ioread16(phy->iobase + addr);
return 0;
}
static int tpm_tcg_read32(struct tpm_tis_data *data, u32 addr, u32 *result)
{
struct tpm_tis_tcg_phy *phy = to_tpm_tis_tcg_phy(data);
*result = ioread32(phy->iobase + addr);
return 0;
}
static int tpm_tcg_write32(struct tpm_tis_data *data, u32 addr, u32 value)
{
struct tpm_tis_tcg_phy *phy = to_tpm_tis_tcg_phy(data);
iowrite32(value, phy->iobase + addr);
switch (io_mode) {
case TPM_TIS_PHYS_8:
while (len--)
iowrite8(*value++, phy->iobase + addr);
break;
case TPM_TIS_PHYS_16:
return -EINVAL;
case TPM_TIS_PHYS_32:
iowrite32(le32_to_cpu(*((__le32 *)value)), phy->iobase + addr);
break;
}
return 0;
}
@ -204,9 +200,6 @@ static int tpm_tcg_write32(struct tpm_tis_data *data, u32 addr, u32 value)
static const struct tpm_tis_phy_ops tpm_tcg = {
.read_bytes = tpm_tcg_read_bytes,
.write_bytes = tpm_tcg_write_bytes,
.read16 = tpm_tcg_read16,
.read32 = tpm_tcg_read32,
.write32 = tpm_tcg_write32,
};
static int tpm_tis_init(struct device *dev, struct tpm_info *tpm_info)

View File

@ -104,54 +104,88 @@ struct tpm_tis_data {
unsigned int timeout_max; /* usecs */
};
/*
* IO modes to indicate how many bytes should be read/written at once in the
* tpm_tis_phy_ops read_bytes/write_bytes calls. Use TPM_TIS_PHYS_8 to
* receive/transmit byte-wise, TPM_TIS_PHYS_16 for two bytes etc.
*/
enum tpm_tis_io_mode {
TPM_TIS_PHYS_8,
TPM_TIS_PHYS_16,
TPM_TIS_PHYS_32,
};
struct tpm_tis_phy_ops {
/* data is passed in little endian */
int (*read_bytes)(struct tpm_tis_data *data, u32 addr, u16 len,
u8 *result);
u8 *result, enum tpm_tis_io_mode mode);
int (*write_bytes)(struct tpm_tis_data *data, u32 addr, u16 len,
const u8 *value);
int (*read16)(struct tpm_tis_data *data, u32 addr, u16 *result);
int (*read32)(struct tpm_tis_data *data, u32 addr, u32 *result);
int (*write32)(struct tpm_tis_data *data, u32 addr, u32 src);
const u8 *value, enum tpm_tis_io_mode mode);
};
static inline int tpm_tis_read_bytes(struct tpm_tis_data *data, u32 addr,
u16 len, u8 *result)
{
return data->phy_ops->read_bytes(data, addr, len, result);
return data->phy_ops->read_bytes(data, addr, len, result,
TPM_TIS_PHYS_8);
}
static inline int tpm_tis_read8(struct tpm_tis_data *data, u32 addr, u8 *result)
{
return data->phy_ops->read_bytes(data, addr, 1, result);
return data->phy_ops->read_bytes(data, addr, 1, result, TPM_TIS_PHYS_8);
}
static inline int tpm_tis_read16(struct tpm_tis_data *data, u32 addr,
u16 *result)
{
return data->phy_ops->read16(data, addr, result);
__le16 result_le;
int rc;
rc = data->phy_ops->read_bytes(data, addr, sizeof(u16),
(u8 *)&result_le, TPM_TIS_PHYS_16);
if (!rc)
*result = le16_to_cpu(result_le);
return rc;
}
static inline int tpm_tis_read32(struct tpm_tis_data *data, u32 addr,
u32 *result)
{
return data->phy_ops->read32(data, addr, result);
__le32 result_le;
int rc;
rc = data->phy_ops->read_bytes(data, addr, sizeof(u32),
(u8 *)&result_le, TPM_TIS_PHYS_32);
if (!rc)
*result = le32_to_cpu(result_le);
return rc;
}
static inline int tpm_tis_write_bytes(struct tpm_tis_data *data, u32 addr,
u16 len, const u8 *value)
{
return data->phy_ops->write_bytes(data, addr, len, value);
return data->phy_ops->write_bytes(data, addr, len, value,
TPM_TIS_PHYS_8);
}
static inline int tpm_tis_write8(struct tpm_tis_data *data, u32 addr, u8 value)
{
return data->phy_ops->write_bytes(data, addr, 1, &value);
return data->phy_ops->write_bytes(data, addr, 1, &value,
TPM_TIS_PHYS_8);
}
static inline int tpm_tis_write32(struct tpm_tis_data *data, u32 addr,
u32 value)
{
return data->phy_ops->write32(data, addr, value);
__le32 value_le;
int rc;
value_le = cpu_to_le32(value);
rc = data->phy_ops->write_bytes(data, addr, sizeof(u32),
(u8 *)&value_le, TPM_TIS_PHYS_32);
return rc;
}
static inline bool is_bsw(void)

View File

@ -31,6 +31,7 @@
#define TPM_CR50_TIMEOUT_SHORT_MS 2 /* Short timeout during transactions */
#define TPM_CR50_TIMEOUT_NOIRQ_MS 20 /* Timeout for TPM ready without IRQ */
#define TPM_CR50_I2C_DID_VID 0x00281ae0L /* Device and vendor ID reg value */
#define TPM_TI50_I2C_DID_VID 0x504a6666L /* Device and vendor ID reg value */
#define TPM_CR50_I2C_MAX_RETRIES 3 /* Max retries due to I2C errors */
#define TPM_CR50_I2C_RETRY_DELAY_LO 55 /* Min usecs between retries on I2C */
#define TPM_CR50_I2C_RETRY_DELAY_HI 65 /* Max usecs between retries on I2C */
@ -742,15 +743,15 @@ static int tpm_cr50_i2c_probe(struct i2c_client *client)
}
vendor = le32_to_cpup((__le32 *)buf);
if (vendor != TPM_CR50_I2C_DID_VID) {
if (vendor != TPM_CR50_I2C_DID_VID && vendor != TPM_TI50_I2C_DID_VID) {
dev_err(dev, "Vendor ID did not match! ID was %08x\n", vendor);
tpm_cr50_release_locality(chip, true);
return -ENODEV;
}
dev_info(dev, "cr50 TPM 2.0 (i2c 0x%02x irq %d id 0x%x)\n",
dev_info(dev, "%s TPM 2.0 (i2c 0x%02x irq %d id 0x%x)\n",
vendor == TPM_TI50_I2C_DID_VID ? "ti50" : "cr50",
client->addr, client->irq, vendor >> 16);
return tpm_chip_register(chip);
}
@ -768,8 +769,8 @@ static int tpm_cr50_i2c_remove(struct i2c_client *client)
struct device *dev = &client->dev;
if (!chip) {
dev_err(dev, "Could not get client data at remove\n");
return -ENODEV;
dev_crit(dev, "Could not get client data at remove, memory corruption ahead\n");
return 0;
}
tpm_chip_unregister(chip);

View File

@ -31,10 +31,6 @@ extern int tpm_tis_spi_init(struct spi_device *spi, struct tpm_tis_spi_phy *phy,
extern int tpm_tis_spi_transfer(struct tpm_tis_data *data, u32 addr, u16 len,
u8 *in, const u8 *out);
extern int tpm_tis_spi_read16(struct tpm_tis_data *data, u32 addr, u16 *result);
extern int tpm_tis_spi_read32(struct tpm_tis_data *data, u32 addr, u32 *result);
extern int tpm_tis_spi_write32(struct tpm_tis_data *data, u32 addr, u32 value);
#ifdef CONFIG_TCG_TIS_SPI_CR50
extern int cr50_spi_probe(struct spi_device *spi);
#else

View File

@ -222,13 +222,13 @@ static int tpm_tis_spi_cr50_transfer(struct tpm_tis_data *data, u32 addr, u16 le
}
static int tpm_tis_spi_cr50_read_bytes(struct tpm_tis_data *data, u32 addr,
u16 len, u8 *result)
u16 len, u8 *result, enum tpm_tis_io_mode io_mode)
{
return tpm_tis_spi_cr50_transfer(data, addr, len, result, NULL);
}
static int tpm_tis_spi_cr50_write_bytes(struct tpm_tis_data *data, u32 addr,
u16 len, const u8 *value)
u16 len, const u8 *value, enum tpm_tis_io_mode io_mode)
{
return tpm_tis_spi_cr50_transfer(data, addr, len, NULL, value);
}
@ -236,9 +236,6 @@ static int tpm_tis_spi_cr50_write_bytes(struct tpm_tis_data *data, u32 addr,
static const struct tpm_tis_phy_ops tpm_spi_cr50_phy_ops = {
.read_bytes = tpm_tis_spi_cr50_read_bytes,
.write_bytes = tpm_tis_spi_cr50_write_bytes,
.read16 = tpm_tis_spi_read16,
.read32 = tpm_tis_spi_read32,
.write32 = tpm_tis_spi_write32,
};
static void cr50_print_fw_version(struct tpm_tis_data *data)

View File

@ -141,55 +141,17 @@ exit:
}
static int tpm_tis_spi_read_bytes(struct tpm_tis_data *data, u32 addr,
u16 len, u8 *result)
u16 len, u8 *result, enum tpm_tis_io_mode io_mode)
{
return tpm_tis_spi_transfer(data, addr, len, result, NULL);
}
static int tpm_tis_spi_write_bytes(struct tpm_tis_data *data, u32 addr,
u16 len, const u8 *value)
u16 len, const u8 *value, enum tpm_tis_io_mode io_mode)
{
return tpm_tis_spi_transfer(data, addr, len, NULL, value);
}
int tpm_tis_spi_read16(struct tpm_tis_data *data, u32 addr, u16 *result)
{
__le16 result_le;
int rc;
rc = data->phy_ops->read_bytes(data, addr, sizeof(u16),
(u8 *)&result_le);
if (!rc)
*result = le16_to_cpu(result_le);
return rc;
}
int tpm_tis_spi_read32(struct tpm_tis_data *data, u32 addr, u32 *result)
{
__le32 result_le;
int rc;
rc = data->phy_ops->read_bytes(data, addr, sizeof(u32),
(u8 *)&result_le);
if (!rc)
*result = le32_to_cpu(result_le);
return rc;
}
int tpm_tis_spi_write32(struct tpm_tis_data *data, u32 addr, u32 value)
{
__le32 value_le;
int rc;
value_le = cpu_to_le32(value);
rc = data->phy_ops->write_bytes(data, addr, sizeof(u32),
(u8 *)&value_le);
return rc;
}
int tpm_tis_spi_init(struct spi_device *spi, struct tpm_tis_spi_phy *phy,
int irq, const struct tpm_tis_phy_ops *phy_ops)
{
@ -205,9 +167,6 @@ int tpm_tis_spi_init(struct spi_device *spi, struct tpm_tis_spi_phy *phy,
static const struct tpm_tis_phy_ops tpm_spi_phy_ops = {
.read_bytes = tpm_tis_spi_read_bytes,
.write_bytes = tpm_tis_spi_write_bytes,
.read16 = tpm_tis_spi_read16,
.read32 = tpm_tis_spi_read32,
.write32 = tpm_tis_spi_write32,
};
static int tpm_tis_spi_probe(struct spi_device *dev)

View File

@ -35,72 +35,53 @@ static inline struct tpm_tis_synquacer_phy *to_tpm_tis_tcg_phy(struct tpm_tis_da
}
static int tpm_tis_synquacer_read_bytes(struct tpm_tis_data *data, u32 addr,
u16 len, u8 *result)
u16 len, u8 *result,
enum tpm_tis_io_mode io_mode)
{
struct tpm_tis_synquacer_phy *phy = to_tpm_tis_tcg_phy(data);
while (len--)
*result++ = ioread8(phy->iobase + addr);
switch (io_mode) {
case TPM_TIS_PHYS_8:
while (len--)
*result++ = ioread8(phy->iobase + addr);
break;
case TPM_TIS_PHYS_16:
result[1] = ioread8(phy->iobase + addr + 1);
result[0] = ioread8(phy->iobase + addr);
break;
case TPM_TIS_PHYS_32:
result[3] = ioread8(phy->iobase + addr + 3);
result[2] = ioread8(phy->iobase + addr + 2);
result[1] = ioread8(phy->iobase + addr + 1);
result[0] = ioread8(phy->iobase + addr);
break;
}
return 0;
}
static int tpm_tis_synquacer_write_bytes(struct tpm_tis_data *data, u32 addr,
u16 len, const u8 *value)
u16 len, const u8 *value,
enum tpm_tis_io_mode io_mode)
{
struct tpm_tis_synquacer_phy *phy = to_tpm_tis_tcg_phy(data);
while (len--)
iowrite8(*value++, phy->iobase + addr);
return 0;
}
static int tpm_tis_synquacer_read16_bw(struct tpm_tis_data *data,
u32 addr, u16 *result)
{
struct tpm_tis_synquacer_phy *phy = to_tpm_tis_tcg_phy(data);
/*
* Due to the limitation of SPI controller on SynQuacer,
* 16/32 bits access must be done in byte-wise and descending order.
*/
*result = (ioread8(phy->iobase + addr + 1) << 8) |
(ioread8(phy->iobase + addr));
return 0;
}
static int tpm_tis_synquacer_read32_bw(struct tpm_tis_data *data,
u32 addr, u32 *result)
{
struct tpm_tis_synquacer_phy *phy = to_tpm_tis_tcg_phy(data);
/*
* Due to the limitation of SPI controller on SynQuacer,
* 16/32 bits access must be done in byte-wise and descending order.
*/
*result = (ioread8(phy->iobase + addr + 3) << 24) |
(ioread8(phy->iobase + addr + 2) << 16) |
(ioread8(phy->iobase + addr + 1) << 8) |
(ioread8(phy->iobase + addr));
return 0;
}
static int tpm_tis_synquacer_write32_bw(struct tpm_tis_data *data,
u32 addr, u32 value)
{
struct tpm_tis_synquacer_phy *phy = to_tpm_tis_tcg_phy(data);
/*
* Due to the limitation of SPI controller on SynQuacer,
* 16/32 bits access must be done in byte-wise and descending order.
*/
iowrite8(value >> 24, phy->iobase + addr + 3);
iowrite8(value >> 16, phy->iobase + addr + 2);
iowrite8(value >> 8, phy->iobase + addr + 1);
iowrite8(value, phy->iobase + addr);
switch (io_mode) {
case TPM_TIS_PHYS_8:
while (len--)
iowrite8(*value++, phy->iobase + addr);
break;
case TPM_TIS_PHYS_16:
return -EINVAL;
case TPM_TIS_PHYS_32:
/*
* Due to the limitation of SPI controller on SynQuacer,
* 16/32 bits access must be done in byte-wise and descending order.
*/
iowrite8(value[3], phy->iobase + addr + 3);
iowrite8(value[2], phy->iobase + addr + 2);
iowrite8(value[1], phy->iobase + addr + 1);
iowrite8(value[0], phy->iobase + addr);
break;
}
return 0;
}
@ -108,9 +89,6 @@ static int tpm_tis_synquacer_write32_bw(struct tpm_tis_data *data,
static const struct tpm_tis_phy_ops tpm_tcg_bw = {
.read_bytes = tpm_tis_synquacer_read_bytes,
.write_bytes = tpm_tis_synquacer_write_bytes,
.read16 = tpm_tis_synquacer_read16_bw,
.read32 = tpm_tis_synquacer_read32_bw,
.write32 = tpm_tis_synquacer_write32_bw,
};
static int tpm_tis_synquacer_init(struct device *dev,

View File

@ -151,6 +151,9 @@ config CRYPTO_DEV_FSL_CAAM_RNG_API
Selecting this will register the SEC4 hardware rng to
the hw_random API for supplying the kernel entropy pool.
config CRYPTO_DEV_FSL_CAAM_BLOB_GEN
bool
endif # CRYPTO_DEV_FSL_CAAM_JR
endif # CRYPTO_DEV_FSL_CAAM

View File

@ -21,6 +21,7 @@ caam_jr-$(CONFIG_CRYPTO_DEV_FSL_CAAM_CRYPTO_API_QI) += caamalg_qi.o
caam_jr-$(CONFIG_CRYPTO_DEV_FSL_CAAM_AHASH_API) += caamhash.o
caam_jr-$(CONFIG_CRYPTO_DEV_FSL_CAAM_RNG_API) += caamrng.o
caam_jr-$(CONFIG_CRYPTO_DEV_FSL_CAAM_PKC_API) += caampkc.o pkc_desc.o
caam_jr-$(CONFIG_CRYPTO_DEV_FSL_CAAM_BLOB_GEN) += blob_gen.o
caam-$(CONFIG_CRYPTO_DEV_FSL_CAAM_CRYPTO_API_QI) += qi.o
ifneq ($(CONFIG_CRYPTO_DEV_FSL_CAAM_CRYPTO_API_QI),)

View File

@ -0,0 +1,182 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2015 Pengutronix, Steffen Trumtrar <kernel@pengutronix.de>
* Copyright (C) 2021 Pengutronix, Ahmad Fatoum <kernel@pengutronix.de>
*/
#define pr_fmt(fmt) "caam blob_gen: " fmt
#include <linux/device.h>
#include <soc/fsl/caam-blob.h>
#include "compat.h"
#include "desc_constr.h"
#include "desc.h"
#include "error.h"
#include "intern.h"
#include "jr.h"
#include "regs.h"
#define CAAM_BLOB_DESC_BYTES_MAX \
/* Command to initialize & stating length of descriptor */ \
(CAAM_CMD_SZ + \
/* Command to append the key-modifier + key-modifier data */ \
CAAM_CMD_SZ + CAAM_BLOB_KEYMOD_LENGTH + \
/* Command to include input key + pointer to the input key */ \
CAAM_CMD_SZ + CAAM_PTR_SZ_MAX + \
/* Command to include output key + pointer to the output key */ \
CAAM_CMD_SZ + CAAM_PTR_SZ_MAX + \
/* Command describing the operation to perform */ \
CAAM_CMD_SZ)
struct caam_blob_priv {
struct device jrdev;
};
struct caam_blob_job_result {
int err;
struct completion completion;
};
static void caam_blob_job_done(struct device *dev, u32 *desc, u32 err, void *context)
{
struct caam_blob_job_result *res = context;
int ecode = 0;
dev_dbg(dev, "%s %d: err 0x%x\n", __func__, __LINE__, err);
if (err)
ecode = caam_jr_strstatus(dev, err);
res->err = ecode;
/*
* Upon completion, desc points to a buffer containing a CAAM job
* descriptor which encapsulates data into an externally-storable
* blob.
*/
complete(&res->completion);
}
int caam_process_blob(struct caam_blob_priv *priv,
struct caam_blob_info *info, bool encap)
{
struct caam_blob_job_result testres;
struct device *jrdev = &priv->jrdev;
dma_addr_t dma_in, dma_out;
int op = OP_PCLID_BLOB;
size_t output_len;
u32 *desc;
int ret;
if (info->key_mod_len > CAAM_BLOB_KEYMOD_LENGTH)
return -EINVAL;
if (encap) {
op |= OP_TYPE_ENCAP_PROTOCOL;
output_len = info->input_len + CAAM_BLOB_OVERHEAD;
} else {
op |= OP_TYPE_DECAP_PROTOCOL;
output_len = info->input_len - CAAM_BLOB_OVERHEAD;
}
desc = kzalloc(CAAM_BLOB_DESC_BYTES_MAX, GFP_KERNEL | GFP_DMA);
if (!desc)
return -ENOMEM;
dma_in = dma_map_single(jrdev, info->input, info->input_len,
DMA_TO_DEVICE);
if (dma_mapping_error(jrdev, dma_in)) {
dev_err(jrdev, "unable to map input DMA buffer\n");
ret = -ENOMEM;
goto out_free;
}
dma_out = dma_map_single(jrdev, info->output, output_len,
DMA_FROM_DEVICE);
if (dma_mapping_error(jrdev, dma_out)) {
dev_err(jrdev, "unable to map output DMA buffer\n");
ret = -ENOMEM;
goto out_unmap_in;
}
/*
* A data blob is encrypted using a blob key (BK); a random number.
* The BK is used as an AES-CCM key. The initial block (B0) and the
* initial counter (Ctr0) are generated automatically and stored in
* Class 1 Context DWords 0+1+2+3. The random BK is stored in the
* Class 1 Key Register. Operation Mode is set to AES-CCM.
*/
init_job_desc(desc, 0);
append_key_as_imm(desc, info->key_mod, info->key_mod_len,
info->key_mod_len, CLASS_2 | KEY_DEST_CLASS_REG);
append_seq_in_ptr_intlen(desc, dma_in, info->input_len, 0);
append_seq_out_ptr_intlen(desc, dma_out, output_len, 0);
append_operation(desc, op);
print_hex_dump_debug("data@"__stringify(__LINE__)": ",
DUMP_PREFIX_ADDRESS, 16, 1, info->input,
info->input_len, false);
print_hex_dump_debug("jobdesc@"__stringify(__LINE__)": ",
DUMP_PREFIX_ADDRESS, 16, 1, desc,
desc_bytes(desc), false);
testres.err = 0;
init_completion(&testres.completion);
ret = caam_jr_enqueue(jrdev, desc, caam_blob_job_done, &testres);
if (ret == -EINPROGRESS) {
wait_for_completion(&testres.completion);
ret = testres.err;
print_hex_dump_debug("output@"__stringify(__LINE__)": ",
DUMP_PREFIX_ADDRESS, 16, 1, info->output,
output_len, false);
}
if (ret == 0)
info->output_len = output_len;
dma_unmap_single(jrdev, dma_out, output_len, DMA_FROM_DEVICE);
out_unmap_in:
dma_unmap_single(jrdev, dma_in, info->input_len, DMA_TO_DEVICE);
out_free:
kfree(desc);
return ret;
}
EXPORT_SYMBOL(caam_process_blob);
struct caam_blob_priv *caam_blob_gen_init(void)
{
struct caam_drv_private *ctrlpriv;
struct device *jrdev;
/*
* caam_blob_gen_init() may expectedly fail with -ENODEV, e.g. when
* CAAM driver didn't probe or when SoC lacks BLOB support. An
* error would be harsh in this case, so we stick to info level.
*/
jrdev = caam_jr_alloc();
if (IS_ERR(jrdev)) {
pr_info("job ring requested, but none currently available\n");
return ERR_PTR(-ENODEV);
}
ctrlpriv = dev_get_drvdata(jrdev->parent);
if (!ctrlpriv->blob_present) {
dev_info(jrdev, "no hardware blob generation support\n");
caam_jr_free(jrdev);
return ERR_PTR(-ENODEV);
}
return container_of(jrdev, struct caam_blob_priv, jrdev);
}
EXPORT_SYMBOL(caam_blob_gen_init);
void caam_blob_gen_exit(struct caam_blob_priv *priv)
{
caam_jr_free(&priv->jrdev);
}
EXPORT_SYMBOL(caam_blob_gen_exit);

View File

@ -820,12 +820,25 @@ static int caam_probe(struct platform_device *pdev)
return -ENOMEM;
}
if (ctrlpriv->era < 10)
comp_params = rd_reg32(&ctrl->perfmon.comp_parms_ls);
ctrlpriv->blob_present = !!(comp_params & CTPR_LS_BLOB);
/*
* Some SoCs like the LS1028A (non-E) indicate CTPR_LS_BLOB support,
* but fail when actually using it due to missing AES support, so
* check both here.
*/
if (ctrlpriv->era < 10) {
rng_vid = (rd_reg32(&ctrl->perfmon.cha_id_ls) &
CHA_ID_LS_RNG_MASK) >> CHA_ID_LS_RNG_SHIFT;
else
ctrlpriv->blob_present = ctrlpriv->blob_present &&
(rd_reg32(&ctrl->perfmon.cha_num_ls) & CHA_ID_LS_AES_MASK);
} else {
rng_vid = (rd_reg32(&ctrl->vreg.rng) & CHA_VER_VID_MASK) >>
CHA_VER_VID_SHIFT;
ctrlpriv->blob_present = ctrlpriv->blob_present &&
(rd_reg32(&ctrl->vreg.aesa) & CHA_VER_MISC_AES_NUM_MASK);
}
/*
* If SEC has RNG version >= 4 and RNG state handle has not been

View File

@ -92,6 +92,7 @@ struct caam_drv_private {
*/
u8 total_jobrs; /* Total Job Rings in device */
u8 qi_present; /* Nonzero if QI present in device */
u8 blob_present; /* Nonzero if BLOB support present in device */
u8 mc_en; /* Nonzero if MC f/w is active */
int secvio_irq; /* Security violation interrupt number */
int virt_en; /* Virtualization enabled in CAAM */

View File

@ -320,7 +320,8 @@ struct version_regs {
#define CHA_VER_VID_MASK (0xffull << CHA_VER_VID_SHIFT)
/* CHA Miscellaneous Information - AESA_MISC specific */
#define CHA_VER_MISC_AES_GCM BIT(1 + CHA_VER_MISC_SHIFT)
#define CHA_VER_MISC_AES_NUM_MASK GENMASK(7, 0)
#define CHA_VER_MISC_AES_GCM BIT(1 + CHA_VER_MISC_SHIFT)
/* CHA Miscellaneous Information - PKHA_MISC specific */
#define CHA_VER_MISC_PKHA_NO_CRYPT BIT(7 + CHA_VER_MISC_SHIFT)
@ -414,6 +415,7 @@ struct caam_perfmon {
#define CTPR_MS_PG_SZ_MASK 0x10
#define CTPR_MS_PG_SZ_SHIFT 4
u32 comp_parms_ms; /* CTPR - Compile Parameters Register */
#define CTPR_LS_BLOB BIT(1)
u32 comp_parms_ls; /* CTPR - Compile Parameters Register */
u64 rsvd1[2];

View File

@ -10,6 +10,13 @@
#include <linux/key.h>
enum blacklist_hash_type {
/* TBSCertificate hash */
BLACKLIST_HASH_X509_TBS = 1,
/* Raw data hash */
BLACKLIST_HASH_BINARY = 2,
};
#ifdef CONFIG_SYSTEM_TRUSTED_KEYRING
extern int restrict_link_by_builtin_trusted(struct key *keyring,
@ -54,13 +61,14 @@ static inline void __init set_machine_trusted_keys(struct key *keyring)
extern struct pkcs7_message *pkcs7;
#ifdef CONFIG_SYSTEM_BLACKLIST_KEYRING
extern int mark_hash_blacklisted(const char *hash);
extern int mark_hash_blacklisted(const u8 *hash, size_t hash_len,
enum blacklist_hash_type hash_type);
extern int is_hash_blacklisted(const u8 *hash, size_t hash_len,
const char *type);
enum blacklist_hash_type hash_type);
extern int is_binary_blacklisted(const u8 *hash, size_t hash_len);
#else
static inline int is_hash_blacklisted(const u8 *hash, size_t hash_len,
const char *type)
enum blacklist_hash_type hash_type)
{
return 0;
}

View File

@ -64,7 +64,7 @@ struct trusted_key_ops {
/* Unseal a key. */
int (*unseal)(struct trusted_key_payload *p, char *datablob);
/* Get a randomized key. */
/* Optional: Get a randomized key. */
int (*get_random)(unsigned char *key, size_t key_len);
/* Exit key interface. */

View File

@ -0,0 +1,11 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) 2021 Pengutronix, Ahmad Fatoum <kernel@pengutronix.de>
*/
#ifndef __CAAM_TRUSTED_KEY_H
#define __CAAM_TRUSTED_KEY_H
extern struct trusted_key_ops trusted_key_caam_ops;
#endif

103
include/soc/fsl/caam-blob.h Normal file
View File

@ -0,0 +1,103 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) 2020 Pengutronix, Ahmad Fatoum <kernel@pengutronix.de>
*/
#ifndef __CAAM_BLOB_GEN
#define __CAAM_BLOB_GEN
#include <linux/types.h>
#include <linux/errno.h>
#define CAAM_BLOB_KEYMOD_LENGTH 16
#define CAAM_BLOB_OVERHEAD (32 + 16)
#define CAAM_BLOB_MAX_LEN 4096
struct caam_blob_priv;
/**
* struct caam_blob_info - information for CAAM blobbing
* @input: pointer to input buffer (must be DMAable)
* @input_len: length of @input buffer in bytes.
* @output: pointer to output buffer (must be DMAable)
* @output_len: length of @output buffer in bytes.
* @key_mod: key modifier
* @key_mod_len: length of @key_mod in bytes.
* May not exceed %CAAM_BLOB_KEYMOD_LENGTH
*/
struct caam_blob_info {
void *input;
size_t input_len;
void *output;
size_t output_len;
const void *key_mod;
size_t key_mod_len;
};
/**
* caam_blob_gen_init - initialize blob generation
* Return: pointer to new &struct caam_blob_priv instance on success
* and ``ERR_PTR(-ENODEV)`` if CAAM has no hardware blobbing support
* or no job ring could be allocated.
*/
struct caam_blob_priv *caam_blob_gen_init(void);
/**
* caam_blob_gen_exit - free blob generation resources
* @priv: instance returned by caam_blob_gen_init()
*/
void caam_blob_gen_exit(struct caam_blob_priv *priv);
/**
* caam_process_blob - encapsulate or decapsulate blob
* @priv: instance returned by caam_blob_gen_init()
* @info: pointer to blobbing info describing key, blob and
* key modifier buffers.
* @encap: true for encapsulation, false for decapsulation
*
* Return: %0 and sets ``info->output_len`` on success and a negative
* error code otherwise.
*/
int caam_process_blob(struct caam_blob_priv *priv,
struct caam_blob_info *info, bool encap);
/**
* caam_encap_blob - encapsulate blob
* @priv: instance returned by caam_blob_gen_init()
* @info: pointer to blobbing info describing input key,
* output blob and key modifier buffers.
*
* Return: %0 and sets ``info->output_len`` on success and
* a negative error code otherwise.
*/
static inline int caam_encap_blob(struct caam_blob_priv *priv,
struct caam_blob_info *info)
{
if (info->output_len < info->input_len + CAAM_BLOB_OVERHEAD)
return -EINVAL;
return caam_process_blob(priv, info, true);
}
/**
* caam_decap_blob - decapsulate blob
* @priv: instance returned by caam_blob_gen_init()
* @info: pointer to blobbing info describing output key,
* input blob and key modifier buffers.
*
* Return: %0 and sets ``info->output_len`` on success and
* a negative error code otherwise.
*/
static inline int caam_decap_blob(struct caam_blob_priv *priv,
struct caam_blob_info *info)
{
if (info->input_len < CAAM_BLOB_OVERHEAD ||
info->output_len < info->input_len - CAAM_BLOB_OVERHEAD)
return -EINVAL;
return caam_process_blob(priv, info, false);
}
#endif

View File

@ -0,0 +1,37 @@
#!/usr/bin/awk -f
# SPDX-License-Identifier: GPL-2.0
#
# Copyright © 2020, Microsoft Corporation. All rights reserved.
#
# Author: Mickaël Salaün <mic@linux.microsoft.com>
#
# Check that a CONFIG_SYSTEM_BLACKLIST_HASH_LIST file contains a valid array of
# hash strings. Such string must start with a prefix ("tbs" or "bin"), then a
# colon (":"), and finally an even number of hexadecimal lowercase characters
# (up to 128).
BEGIN {
RS = ","
}
{
if (!match($0, "^[ \t\n\r]*\"([^\"]*)\"[ \t\n\r]*$", part1)) {
print "Not a string (item " NR "):", $0;
exit 1;
}
if (!match(part1[1], "^(tbs|bin):(.*)$", part2)) {
print "Unknown prefix (item " NR "):", part1[1];
exit 1;
}
if (!match(part2[2], "^([0-9a-f]+)$", part3)) {
print "Not a lowercase hexadecimal string (item " NR "):", part2[2];
exit 1;
}
if (length(part3[1]) > 128) {
print "Hash string too long (item " NR "):", part3[1];
exit 1;
}
if (length(part3[1]) % 2 == 1) {
print "Not an even number of hexadecimal characters (item " NR "):", part3[1];
exit 1;
}
}

View File

@ -16,35 +16,13 @@ static efi_guid_t efi_cert_x509_sha256_guid __initdata =
EFI_CERT_X509_SHA256_GUID;
static efi_guid_t efi_cert_sha256_guid __initdata = EFI_CERT_SHA256_GUID;
/*
* Blacklist a hash.
*/
static __init void uefi_blacklist_hash(const char *source, const void *data,
size_t len, const char *type,
size_t type_len)
{
char *hash, *p;
hash = kmalloc(type_len + len * 2 + 1, GFP_KERNEL);
if (!hash)
return;
p = memcpy(hash, type, type_len);
p += type_len;
bin2hex(p, data, len);
p += len * 2;
*p = 0;
mark_hash_blacklisted(hash);
kfree(hash);
}
/*
* Blacklist an X509 TBS hash.
*/
static __init void uefi_blacklist_x509_tbs(const char *source,
const void *data, size_t len)
{
uefi_blacklist_hash(source, data, len, "tbs:", 4);
mark_hash_blacklisted(data, len, BLACKLIST_HASH_X509_TBS);
}
/*
@ -53,7 +31,7 @@ static __init void uefi_blacklist_x509_tbs(const char *source,
static __init void uefi_blacklist_binary(const char *source,
const void *data, size_t len)
{
uefi_blacklist_hash(source, data, len, "bin:", 4);
mark_hash_blacklisted(data, len, BLACKLIST_HASH_BINARY);
}
/*

View File

@ -70,23 +70,19 @@ config BIG_KEYS
config TRUSTED_KEYS
tristate "TRUSTED KEYS"
depends on KEYS && TCG_TPM
select CRYPTO
select CRYPTO_HMAC
select CRYPTO_SHA1
select CRYPTO_HASH_INFO
select ASN1_ENCODER
select OID_REGISTRY
select ASN1
depends on KEYS
help
This option provides support for creating, sealing, and unsealing
keys in the kernel. Trusted keys are random number symmetric keys,
generated and RSA-sealed by the TPM. The TPM only unseals the keys,
if the boot PCRs and other criteria match. Userspace will only ever
see encrypted blobs.
generated and sealed by a trust source selected at kernel boot-time.
Userspace will only ever see encrypted blobs.
If you are unsure as to whether this is required, answer N.
if TRUSTED_KEYS
source "security/keys/trusted-keys/Kconfig"
endif
config ENCRYPTED_KEYS
tristate "ENCRYPTED KEYS"
depends on KEYS

View File

@ -0,0 +1,38 @@
config TRUSTED_KEYS_TPM
bool "TPM-based trusted keys"
depends on TCG_TPM >= TRUSTED_KEYS
default y
select CRYPTO
select CRYPTO_HMAC
select CRYPTO_SHA1
select CRYPTO_HASH_INFO
select ASN1_ENCODER
select OID_REGISTRY
select ASN1
help
Enable use of the Trusted Platform Module (TPM) as trusted key
backend. Trusted keys are random number symmetric keys,
which will be generated and RSA-sealed by the TPM.
The TPM only unseals the keys, if the boot PCRs and other
criteria match.
config TRUSTED_KEYS_TEE
bool "TEE-based trusted keys"
depends on TEE >= TRUSTED_KEYS
default y
help
Enable use of the Trusted Execution Environment (TEE) as trusted
key backend.
config TRUSTED_KEYS_CAAM
bool "CAAM-based trusted keys"
depends on CRYPTO_DEV_FSL_CAAM_JR >= TRUSTED_KEYS
select CRYPTO_DEV_FSL_CAAM_BLOB_GEN
default y
help
Enable use of NXP's Cryptographic Accelerator and Assurance Module
(CAAM) as trusted key backend.
if !TRUSTED_KEYS_TPM && !TRUSTED_KEYS_TEE && !TRUSTED_KEYS_CAAM
comment "No trust source selected!"
endif

View File

@ -5,10 +5,12 @@
obj-$(CONFIG_TRUSTED_KEYS) += trusted.o
trusted-y += trusted_core.o
trusted-y += trusted_tpm1.o
trusted-$(CONFIG_TRUSTED_KEYS_TPM) += trusted_tpm1.o
$(obj)/trusted_tpm2.o: $(obj)/tpm2key.asn1.h
trusted-y += trusted_tpm2.o
trusted-y += tpm2key.asn1.o
trusted-$(CONFIG_TRUSTED_KEYS_TPM) += trusted_tpm2.o
trusted-$(CONFIG_TRUSTED_KEYS_TPM) += tpm2key.asn1.o
trusted-$(CONFIG_TEE) += trusted_tee.o
trusted-$(CONFIG_TRUSTED_KEYS_TEE) += trusted_tee.o
trusted-$(CONFIG_TRUSTED_KEYS_CAAM) += trusted_caam.o

View File

@ -0,0 +1,80 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2021 Pengutronix, Ahmad Fatoum <kernel@pengutronix.de>
*/
#include <keys/trusted_caam.h>
#include <keys/trusted-type.h>
#include <linux/build_bug.h>
#include <linux/key-type.h>
#include <soc/fsl/caam-blob.h>
static struct caam_blob_priv *blobifier;
#define KEYMOD "SECURE_KEY"
static_assert(MAX_KEY_SIZE + CAAM_BLOB_OVERHEAD <= CAAM_BLOB_MAX_LEN);
static_assert(MAX_BLOB_SIZE <= CAAM_BLOB_MAX_LEN);
static int trusted_caam_seal(struct trusted_key_payload *p, char *datablob)
{
int ret;
struct caam_blob_info info = {
.input = p->key, .input_len = p->key_len,
.output = p->blob, .output_len = MAX_BLOB_SIZE,
.key_mod = KEYMOD, .key_mod_len = sizeof(KEYMOD) - 1,
};
ret = caam_encap_blob(blobifier, &info);
if (ret)
return ret;
p->blob_len = info.output_len;
return 0;
}
static int trusted_caam_unseal(struct trusted_key_payload *p, char *datablob)
{
int ret;
struct caam_blob_info info = {
.input = p->blob, .input_len = p->blob_len,
.output = p->key, .output_len = MAX_KEY_SIZE,
.key_mod = KEYMOD, .key_mod_len = sizeof(KEYMOD) - 1,
};
ret = caam_decap_blob(blobifier, &info);
if (ret)
return ret;
p->key_len = info.output_len;
return 0;
}
static int trusted_caam_init(void)
{
int ret;
blobifier = caam_blob_gen_init();
if (IS_ERR(blobifier))
return PTR_ERR(blobifier);
ret = register_key_type(&key_type_trusted);
if (ret)
caam_blob_gen_exit(blobifier);
return ret;
}
static void trusted_caam_exit(void)
{
unregister_key_type(&key_type_trusted);
caam_blob_gen_exit(blobifier);
}
struct trusted_key_ops trusted_key_caam_ops = {
.migratable = 0, /* non-migratable */
.init = trusted_caam_init,
.seal = trusted_caam_seal,
.unseal = trusted_caam_unseal,
.exit = trusted_caam_exit,
};

View File

@ -9,6 +9,7 @@
#include <keys/user-type.h>
#include <keys/trusted-type.h>
#include <keys/trusted_tee.h>
#include <keys/trusted_caam.h>
#include <keys/trusted_tpm.h>
#include <linux/capability.h>
#include <linux/err.h>
@ -16,23 +17,31 @@
#include <linux/key-type.h>
#include <linux/module.h>
#include <linux/parser.h>
#include <linux/random.h>
#include <linux/rcupdate.h>
#include <linux/slab.h>
#include <linux/static_call.h>
#include <linux/string.h>
#include <linux/uaccess.h>
static char *trusted_rng = "default";
module_param_named(rng, trusted_rng, charp, 0);
MODULE_PARM_DESC(rng, "Select trusted key RNG");
static char *trusted_key_source;
module_param_named(source, trusted_key_source, charp, 0);
MODULE_PARM_DESC(source, "Select trusted keys source (tpm or tee)");
MODULE_PARM_DESC(source, "Select trusted keys source (tpm, tee or caam)");
static const struct trusted_key_source trusted_key_sources[] = {
#if IS_REACHABLE(CONFIG_TCG_TPM)
#if defined(CONFIG_TRUSTED_KEYS_TPM)
{ "tpm", &trusted_key_tpm_ops },
#endif
#if IS_REACHABLE(CONFIG_TEE)
#if defined(CONFIG_TRUSTED_KEYS_TEE)
{ "tee", &trusted_key_tee_ops },
#endif
#if defined(CONFIG_TRUSTED_KEYS_CAAM)
{ "caam", &trusted_key_caam_ops },
#endif
};
DEFINE_STATIC_CALL_NULL(trusted_key_init, *trusted_key_sources[0].ops->init);
@ -312,8 +321,14 @@ struct key_type key_type_trusted = {
};
EXPORT_SYMBOL_GPL(key_type_trusted);
static int kernel_get_random(unsigned char *key, size_t key_len)
{
return get_random_bytes_wait(key, key_len) ?: key_len;
}
static int __init init_trusted(void)
{
int (*get_random)(unsigned char *key, size_t key_len);
int i, ret = 0;
for (i = 0; i < ARRAY_SIZE(trusted_key_sources); i++) {
@ -322,6 +337,28 @@ static int __init init_trusted(void)
strlen(trusted_key_sources[i].name)))
continue;
/*
* We always support trusted.rng="kernel" and "default" as
* well as trusted.rng=$trusted.source if the trust source
* defines its own get_random callback.
*/
get_random = trusted_key_sources[i].ops->get_random;
if (trusted_rng && strcmp(trusted_rng, "default")) {
if (!strcmp(trusted_rng, "kernel")) {
get_random = kernel_get_random;
} else if (strcmp(trusted_rng, trusted_key_sources[i].name) ||
!get_random) {
pr_warn("Unsupported RNG. Supported: kernel");
if (get_random)
pr_cont(", %s", trusted_key_sources[i].name);
pr_cont(", default\n");
return -EINVAL;
}
}
if (!get_random)
get_random = kernel_get_random;
static_call_update(trusted_key_init,
trusted_key_sources[i].ops->init);
static_call_update(trusted_key_seal,
@ -329,7 +366,7 @@ static int __init init_trusted(void)
static_call_update(trusted_key_unseal,
trusted_key_sources[i].ops->unseal);
static_call_update(trusted_key_get_random,
trusted_key_sources[i].ops->get_random);
get_random);
static_call_update(trusted_key_exit,
trusted_key_sources[i].ops->exit);
migratable = trusted_key_sources[i].ops->migratable;

View File

@ -0,0 +1,91 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
#
# Copyright © 2020, Microsoft Corporation. All rights reserved.
#
# Author: Mickaël Salaün <mic@linux.microsoft.com>
#
# Compute and print the To Be Signed (TBS) hash of a certificate. This is used
# as description of keys in the blacklist keyring to identify certificates.
# This output should be redirected, without newline, in a file (hash0.txt) and
# signed to create a PKCS#7 file (hash0.p7s). Both of these files can then be
# loaded in the kernel with.
#
# Exemple on a workstation:
# ./print-cert-tbs-hash.sh certificate-to-invalidate.pem > hash0.txt
# openssl smime -sign -in hash0.txt -inkey builtin-private-key.pem \
# -signer builtin-certificate.pem -certfile certificate-chain.pem \
# -noattr -binary -outform DER -out hash0.p7s
#
# Exemple on a managed system:
# keyctl padd blacklist "$(< hash0.txt)" %:.blacklist < hash0.p7s
set -u -e -o pipefail
CERT="${1:-}"
BASENAME="$(basename -- "${BASH_SOURCE[0]}")"
if [ $# -ne 1 ] || [ ! -f "${CERT}" ]; then
echo "usage: ${BASENAME} <certificate>" >&2
exit 1
fi
# Checks that it is indeed a certificate (PEM or DER encoded) and exclude the
# optional PEM text header.
if ! PEM="$(openssl x509 -inform DER -in "${CERT}" 2>/dev/null || openssl x509 -in "${CERT}")"; then
echo "ERROR: Failed to parse certificate" >&2
exit 1
fi
# TBSCertificate starts at the second entry.
# Cf. https://tools.ietf.org/html/rfc3280#section-4.1
#
# Exemple of first lines printed by openssl asn1parse:
# 0:d=0 hl=4 l= 763 cons: SEQUENCE
# 4:d=1 hl=4 l= 483 cons: SEQUENCE
# 8:d=2 hl=2 l= 3 cons: cont [ 0 ]
# 10:d=3 hl=2 l= 1 prim: INTEGER :02
# 13:d=2 hl=2 l= 20 prim: INTEGER :3CEB2CB8818D968AC00EEFE195F0DF9665328B7B
# 35:d=2 hl=2 l= 13 cons: SEQUENCE
# 37:d=3 hl=2 l= 9 prim: OBJECT :sha256WithRSAEncryption
RANGE_AND_DIGEST_RE='
2s/^\s*\([0-9]\+\):d=\s*[0-9]\+\s\+hl=\s*[0-9]\+\s\+l=\s*\([0-9]\+\)\s\+cons:\s*SEQUENCE\s*$/\1 \2/p;
7s/^\s*[0-9]\+:d=\s*[0-9]\+\s\+hl=\s*[0-9]\+\s\+l=\s*[0-9]\+\s\+prim:\s*OBJECT\s*:\(.*\)$/\1/p;
'
RANGE_AND_DIGEST=($(echo "${PEM}" | \
openssl asn1parse -in - | \
sed -n -e "${RANGE_AND_DIGEST_RE}"))
if [ "${#RANGE_AND_DIGEST[@]}" != 3 ]; then
echo "ERROR: Failed to parse TBSCertificate." >&2
exit 1
fi
OFFSET="${RANGE_AND_DIGEST[0]}"
END="$(( OFFSET + RANGE_AND_DIGEST[1] ))"
DIGEST="${RANGE_AND_DIGEST[2]}"
# The signature hash algorithm is used by Linux to blacklist certificates.
# Cf. crypto/asymmetric_keys/x509_cert_parser.c:x509_note_pkey_algo()
DIGEST_MATCH=""
while read -r DIGEST_ITEM; do
if [ -z "${DIGEST_ITEM}" ]; then
break
fi
if echo "${DIGEST}" | grep -qiF "${DIGEST_ITEM}"; then
DIGEST_MATCH="${DIGEST_ITEM}"
break
fi
done < <(openssl list -digest-commands | tr ' ' '\n' | sort -ur)
if [ -z "${DIGEST_MATCH}" ]; then
echo "ERROR: Unknown digest algorithm: ${DIGEST}" >&2
exit 1
fi
echo "${PEM}" | \
openssl x509 -in - -outform DER | \
dd "bs=1" "skip=${OFFSET}" "count=${END}" "status=none" | \
openssl dgst "-${DIGEST_MATCH}" - | \
awk '{printf "tbs:" $2}'