s390: add support for user-defined certificates
authorAnastasia Eskova <anastasia.eskova@ibm.com>
Fri, 28 Apr 2023 10:42:42 +0000 (06:42 -0400)
committerHeiko Carstens <hca@linux.ibm.com>
Mon, 24 Jul 2023 10:12:21 +0000 (12:12 +0200)
Enable receiving the user-defined certificates from the s390x
hypervisor via new diagnose 0x320 calls, and make them available to the
Linux root user as 'cert_store_key' type keys in a so-called
'cert_store' keyring.

New user-space interfaces:

  /sys/firmware/cert_store/refresh

    Writing to this attribute re-fetches certificates via DIAG 0x320

  /sys/firmware/cert_store/cs_status

    Reading from this attribute returns either of:

  "uninitialized"
    If no certificate has been retrieved yet
  "ok"
    If certificates have been successfully retrieved
  "failed (<number>)"
    If certificate retrieval failed with reason code <number>

New debug trace areas:

  /sys/kernel/debug/s390dbf/cert_store_msg

  /sys/kernel/debug/s390dbf/cert_store_hexdump

Usage example:

To initiate request for certificates available to the system as root:

  $ echo 1 > /sys/firmware/cert_store/refresh

Upon success the '/sys/firmware/cert_store/cs_status' contains
the value 'ok'.

  $ cat /sys/firmware/cert_store/cs_status
  ok

Get the ID of the keyring 'cert_store':

  $ keyctl search @us keyring cert_store
OR
  $ keyctl link @us @s; keyctl request keyring cert_store

Obtain list of IDs of certificates:

  $ keyctl rlist <cert_store keyring ID>

Display certificate content as hex-dump:

  $ keyctl read <certificate ID>

Read certificate contents as binary data:

  $ keyctl pipe <certificate ID> >cert_data

Display certificate description:

  $ keyctl describe <certificate ID>

The certificate description has the following format:

  <64 bytes certificate name in EBCDIC> ':'
  <certificate index as obtained from hypervisor> ':'
  <certificate store token obtained from hypervisor>

The certificate description in /proc/keys has certificate name
represented in ASCII.

Users can read but cannot update the content of the certificate.

Signed-off-by: Anastasia Eskova <anastasia.eskova@ibm.com>
Reviewed-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Acked-by: Heiko Carstens <hca@linux.ibm.com>
Signed-off-by: Heiko Carstens <hca@linux.ibm.com>
arch/s390/Kconfig
arch/s390/include/asm/diag.h
arch/s390/include/asm/sclp.h
arch/s390/kernel/Makefile
arch/s390/kernel/cert_store.c [new file with mode: 0644]
arch/s390/kernel/diag.c
drivers/s390/char/sclp_early.c

index 5b39918b7042b071601becf673e2b9f05b81ec7c..c0afca69904e202866629a0426f5a5538524279f 100644 (file)
@@ -512,6 +512,16 @@ config KEXEC_SIG
          verification for the corresponding kernel image type being
          loaded in order for this to work.
 
+config CERT_STORE
+       bool "Get user certificates via DIAG320"
+       depends on KEYS
+       help
+         Enable this option if you want to access user-provided secure boot
+         certificates via DIAG 0x320.
+
+         These certificates will be made available via the keyring named
+         'cert_store'.
+
 config KERNEL_NOBP
        def_bool n
        prompt "Enable modified branch prediction for the kernel by default"
index 902e0330dd91782ba620aeac588156c06da442e0..fb5a886ff47ff51309b32f8b02fae37983b96a51 100644 (file)
@@ -36,6 +36,7 @@ enum diag_stat_enum {
        DIAG_STAT_X304,
        DIAG_STAT_X308,
        DIAG_STAT_X318,
+       DIAG_STAT_X320,
        DIAG_STAT_X500,
        NR_DIAG_STAT
 };
index dac7da88f61fa543677f16b94eb209be146531af..5742d23bba137453e01b280685d1d8178f4c14a0 100644 (file)
@@ -86,6 +86,7 @@ struct sclp_info {
        unsigned char has_kss : 1;
        unsigned char has_gisaf : 1;
        unsigned char has_diag318 : 1;
+       unsigned char has_diag320 : 1;
        unsigned char has_sipl : 1;
        unsigned char has_sipl_eckd : 1;
        unsigned char has_dirq : 1;
index 6b2a051e1f8a2a7fec822ab5b287a9b25fd33a84..8d7514c72bb8e9279725f480267d69d9eda812f8 100644 (file)
@@ -68,7 +68,7 @@ obj-$(CONFIG_JUMP_LABEL)      += jump_label.o
 
 obj-$(CONFIG_KEXEC_FILE)       += machine_kexec_file.o kexec_image.o
 obj-$(CONFIG_KEXEC_FILE)       += kexec_elf.o
-
+obj-$(CONFIG_CERT_STORE)       += cert_store.o
 obj-$(CONFIG_IMA_SECURE_AND_OR_TRUSTED_BOOT)   += ima_arch.o
 
 obj-$(CONFIG_PERF_EVENTS)      += perf_event.o
diff --git a/arch/s390/kernel/cert_store.c b/arch/s390/kernel/cert_store.c
new file mode 100644 (file)
index 0000000..1cbeb9c
--- /dev/null
@@ -0,0 +1,810 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * DIAG 0x320 support and certificate store handling
+ *
+ * Copyright IBM Corp. 2023
+ * Author(s):  Anastasia Eskova <anastasia.eskova@ibm.com>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/key-type.h>
+#include <linux/key.h>
+#include <linux/keyctl.h>
+#include <linux/kobject.h>
+#include <linux/module.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <crypto/sha2.h>
+#include <keys/user-type.h>
+#include <asm/debug.h>
+#include <asm/diag.h>
+#include <asm/ebcdic.h>
+#include <asm/sclp.h>
+
+#define DIAG_MAX_RETRIES               10
+
+#define VCE_FLAGS_VALID_MASK           0x80
+
+#define ISM_LEN_DWORDS                 4
+#define VCSSB_LEN_BYTES                        128
+#define VCSSB_LEN_NO_CERTS             4
+#define VCB_LEN_NO_CERTS               64
+#define VC_NAME_LEN_BYTES              64
+
+#define CERT_STORE_KEY_TYPE_NAME       "cert_store_key"
+#define CERT_STORE_KEYRING_NAME                "cert_store"
+
+static debug_info_t *cert_store_dbf;
+static debug_info_t *cert_store_hexdump;
+
+#define pr_dbf_msg(fmt, ...) \
+       debug_sprintf_event(cert_store_dbf, 3, fmt "\n", ## __VA_ARGS__)
+
+enum diag320_subcode {
+       DIAG320_SUBCODES        = 0,
+       DIAG320_STORAGE         = 1,
+       DIAG320_CERT_BLOCK      = 2,
+};
+
+enum diag320_rc {
+       DIAG320_RC_OK           = 0x0001,
+       DIAG320_RC_CS_NOMATCH   = 0x0306,
+};
+
+/* Verification Certificates Store Support Block (VCSSB). */
+struct vcssb {
+       u32 vcssb_length;
+       u8  pad_0x04[3];
+       u8  version;
+       u8  pad_0x08[8];
+       u32 cs_token;
+       u8  pad_0x14[12];
+       u16 total_vc_index_count;
+       u16 max_vc_index_count;
+       u8  pad_0x24[28];
+       u32 max_vce_length;
+       u32 max_vcxe_length;
+       u8  pad_0x48[8];
+       u32 max_single_vcb_length;
+       u32 total_vcb_length;
+       u32 max_single_vcxb_length;
+       u32 total_vcxb_length;
+       u8  pad_0x60[32];
+} __packed __aligned(8);
+
+/* Verification Certificate Entry (VCE) Header. */
+struct vce_header {
+       u32 vce_length;
+       u8  flags;
+       u8  key_type;
+       u16 vc_index;
+       u8  vc_name[VC_NAME_LEN_BYTES]; /* EBCDIC */
+       u8  vc_format;
+       u8  pad_0x49;
+       u16 key_id_length;
+       u8  pad_0x4c;
+       u8  vc_hash_type;
+       u16 vc_hash_length;
+       u8  pad_0x50[4];
+       u32 vc_length;
+       u8  pad_0x58[8];
+       u16 vc_hash_offset;
+       u16 vc_offset;
+       u8  pad_0x64[28];
+} __packed __aligned(4);
+
+/* Verification Certificate Block (VCB) Header. */
+struct vcb_header {
+       u32 vcb_input_length;
+       u8  pad_0x04[4];
+       u16 first_vc_index;
+       u16 last_vc_index;
+       u32 pad_0x0c;
+       u32 cs_token;
+       u8  pad_0x14[12];
+       u32 vcb_output_length;
+       u8  pad_0x24[3];
+       u8  version;
+       u16 stored_vc_count;
+       u16 remaining_vc_count;
+       u8  pad_0x2c[20];
+} __packed __aligned(4);
+
+/* Verification Certificate Block (VCB). */
+struct vcb {
+       struct vcb_header vcb_hdr;
+       u8 vcb_buf[];
+} __packed __aligned(4);
+
+/* Verification Certificate Entry (VCE). */
+struct vce {
+       struct vce_header vce_hdr;
+       u8 cert_data_buf[];
+} __packed __aligned(4);
+
+static void cert_store_key_describe(const struct key *key, struct seq_file *m)
+{
+       char ascii[VC_NAME_LEN_BYTES + 1];
+
+       /*
+        * First 64 bytes of the key description is key name in EBCDIC CP 500.
+        * Convert it to ASCII for displaying in /proc/keys.
+        */
+       strscpy(ascii, key->description, sizeof(ascii));
+       EBCASC_500(ascii, VC_NAME_LEN_BYTES);
+       seq_puts(m, ascii);
+
+       seq_puts(m, &key->description[VC_NAME_LEN_BYTES]);
+       if (key_is_positive(key))
+               seq_printf(m, ": %u", key->datalen);
+}
+
+/*
+ * Certificate store key type takes over properties of
+ * user key but cannot be updated.
+ */
+static struct key_type key_type_cert_store_key = {
+       .name           = CERT_STORE_KEY_TYPE_NAME,
+       .preparse       = user_preparse,
+       .free_preparse  = user_free_preparse,
+       .instantiate    = generic_key_instantiate,
+       .revoke         = user_revoke,
+       .destroy        = user_destroy,
+       .describe       = cert_store_key_describe,
+       .read           = user_read,
+};
+
+/* Logging functions. */
+static void pr_dbf_vcb(const struct vcb *b)
+{
+       pr_dbf_msg("VCB Header:");
+       pr_dbf_msg("vcb_input_length: %d", b->vcb_hdr.vcb_input_length);
+       pr_dbf_msg("first_vc_index: %d", b->vcb_hdr.first_vc_index);
+       pr_dbf_msg("last_vc_index: %d", b->vcb_hdr.last_vc_index);
+       pr_dbf_msg("cs_token: %d", b->vcb_hdr.cs_token);
+       pr_dbf_msg("vcb_output_length: %d", b->vcb_hdr.vcb_output_length);
+       pr_dbf_msg("version: %d", b->vcb_hdr.version);
+       pr_dbf_msg("stored_vc_count: %d", b->vcb_hdr.stored_vc_count);
+       pr_dbf_msg("remaining_vc_count: %d", b->vcb_hdr.remaining_vc_count);
+}
+
+static void pr_dbf_vce(const struct vce *e)
+{
+       unsigned char vc_name[VC_NAME_LEN_BYTES + 1];
+       char log_string[VC_NAME_LEN_BYTES + 40];
+
+       pr_dbf_msg("VCE Header:");
+       pr_dbf_msg("vce_hdr.vce_length: %d", e->vce_hdr.vce_length);
+       pr_dbf_msg("vce_hdr.flags: %d", e->vce_hdr.flags);
+       pr_dbf_msg("vce_hdr.key_type: %d", e->vce_hdr.key_type);
+       pr_dbf_msg("vce_hdr.vc_index: %d", e->vce_hdr.vc_index);
+       pr_dbf_msg("vce_hdr.vc_format: %d", e->vce_hdr.vc_format);
+       pr_dbf_msg("vce_hdr.key_id_length: %d", e->vce_hdr.key_id_length);
+       pr_dbf_msg("vce_hdr.vc_hash_type: %d", e->vce_hdr.vc_hash_type);
+       pr_dbf_msg("vce_hdr.vc_hash_length: %d", e->vce_hdr.vc_hash_length);
+       pr_dbf_msg("vce_hdr.vc_hash_offset: %d", e->vce_hdr.vc_hash_offset);
+       pr_dbf_msg("vce_hdr.vc_length: %d", e->vce_hdr.vc_length);
+       pr_dbf_msg("vce_hdr.vc_offset: %d", e->vce_hdr.vc_offset);
+
+       /* Certificate name in ASCII. */
+       memcpy(vc_name, e->vce_hdr.vc_name, VC_NAME_LEN_BYTES);
+       EBCASC_500(vc_name, VC_NAME_LEN_BYTES);
+       vc_name[VC_NAME_LEN_BYTES] = '\0';
+
+       snprintf(log_string, sizeof(log_string),
+                "index: %d vce_hdr.vc_name (ASCII): %s",
+                e->vce_hdr.vc_index, vc_name);
+       debug_text_event(cert_store_hexdump, 3, log_string);
+
+       /* Certificate data. */
+       debug_text_event(cert_store_hexdump, 3, "VCE: Certificate data start");
+       debug_event(cert_store_hexdump, 3, (u8 *)e->cert_data_buf, 128);
+       debug_text_event(cert_store_hexdump, 3, "VCE: Certificate data end");
+       debug_event(cert_store_hexdump, 3,
+                   (u8 *)e->cert_data_buf + e->vce_hdr.vce_length - 128, 128);
+}
+
+static void pr_dbf_vcssb(const struct vcssb *s)
+{
+       debug_text_event(cert_store_hexdump, 3, "DIAG320 Subcode1");
+       debug_event(cert_store_hexdump, 3, (u8 *)s, VCSSB_LEN_BYTES);
+
+       pr_dbf_msg("VCSSB:");
+       pr_dbf_msg("vcssb_length: %u", s->vcssb_length);
+       pr_dbf_msg("version: %u", s->version);
+       pr_dbf_msg("cs_token: %u", s->cs_token);
+       pr_dbf_msg("total_vc_index_count: %u", s->total_vc_index_count);
+       pr_dbf_msg("max_vc_index_count: %u", s->max_vc_index_count);
+       pr_dbf_msg("max_vce_length: %u", s->max_vce_length);
+       pr_dbf_msg("max_vcxe_length: %u", s->max_vce_length);
+       pr_dbf_msg("max_single_vcb_length: %u", s->max_single_vcb_length);
+       pr_dbf_msg("total_vcb_length: %u", s->total_vcb_length);
+       pr_dbf_msg("max_single_vcxb_length: %u", s->max_single_vcxb_length);
+       pr_dbf_msg("total_vcxb_length: %u", s->total_vcxb_length);
+}
+
+static int __diag320(unsigned long subcode, void *addr)
+{
+       union register_pair rp = { .even = (unsigned long)addr, };
+
+       asm volatile(
+               "       diag    %[rp],%[subcode],0x320\n"
+               "0:     nopr    %%r7\n"
+               EX_TABLE(0b, 0b)
+               : [rp] "+d" (rp.pair)
+               : [subcode] "d" (subcode)
+               : "cc", "memory");
+
+       return rp.odd;
+}
+
+static int diag320(unsigned long subcode, void *addr)
+{
+       diag_stat_inc(DIAG_STAT_X320);
+
+       return __diag320(subcode, addr);
+}
+
+/*
+ * Calculate SHA256 hash of the VCE certificate and compare it to hash stored in
+ * VCE. Return -EINVAL if hashes don't match.
+ */
+static int check_certificate_hash(const struct vce *vce)
+{
+       u8 hash[SHA256_DIGEST_SIZE];
+       u16 vc_hash_length;
+       u8 *vce_hash;
+
+       vce_hash = (u8 *)vce + vce->vce_hdr.vc_hash_offset;
+       vc_hash_length = vce->vce_hdr.vc_hash_length;
+       sha256((u8 *)vce + vce->vce_hdr.vc_offset, vce->vce_hdr.vc_length, hash);
+       if (memcmp(vce_hash, hash, vc_hash_length) == 0)
+               return 0;
+
+       pr_dbf_msg("SHA256 hash of received certificate does not match");
+       debug_text_event(cert_store_hexdump, 3, "VCE hash:");
+       debug_event(cert_store_hexdump, 3, vce_hash, SHA256_DIGEST_SIZE);
+       debug_text_event(cert_store_hexdump, 3, "Calculated hash:");
+       debug_event(cert_store_hexdump, 3, hash, SHA256_DIGEST_SIZE);
+
+       return -EINVAL;
+}
+
+static int check_certificate_valid(const struct vce *vce)
+{
+       if (!(vce->vce_hdr.flags & VCE_FLAGS_VALID_MASK)) {
+               pr_dbf_msg("Certificate entry is invalid");
+               return -EINVAL;
+       }
+       if (vce->vce_hdr.vc_format != 1) {
+               pr_dbf_msg("Certificate format is not supported");
+               return -EINVAL;
+       }
+       if (vce->vce_hdr.vc_hash_type != 1) {
+               pr_dbf_msg("Hash type is not supported");
+               return -EINVAL;
+       }
+
+       return check_certificate_hash(vce);
+}
+
+static struct key *get_user_session_keyring(void)
+{
+       key_ref_t us_keyring_ref;
+
+       us_keyring_ref = lookup_user_key(KEY_SPEC_USER_SESSION_KEYRING,
+                                        KEY_LOOKUP_CREATE, KEY_NEED_LINK);
+       if (IS_ERR(us_keyring_ref)) {
+               pr_dbf_msg("Couldn't get user session keyring: %ld",
+                          PTR_ERR(us_keyring_ref));
+               return ERR_PTR(-ENOKEY);
+       }
+       key_ref_put(us_keyring_ref);
+       return key_ref_to_ptr(us_keyring_ref);
+}
+
+/* Invalidate all keys from cert_store keyring. */
+static int invalidate_keyring_keys(struct key *keyring)
+{
+       unsigned long num_keys, key_index;
+       size_t keyring_payload_len;
+       key_serial_t *key_array;
+       struct key *current_key;
+       int rc;
+
+       keyring_payload_len = key_type_keyring.read(keyring, NULL, 0);
+       num_keys = keyring_payload_len / sizeof(key_serial_t);
+       key_array = kcalloc(num_keys, sizeof(key_serial_t), GFP_KERNEL);
+       if (!key_array)
+               return -ENOMEM;
+
+       rc = key_type_keyring.read(keyring, (char *)key_array, keyring_payload_len);
+       if (rc != keyring_payload_len) {
+               pr_dbf_msg("Couldn't read keyring payload");
+               goto out;
+       }
+
+       for (key_index = 0; key_index < num_keys; key_index++) {
+               current_key = key_lookup(key_array[key_index]);
+               pr_dbf_msg("Invalidating key %08x", current_key->serial);
+
+               key_invalidate(current_key);
+               key_put(current_key);
+               rc = key_unlink(keyring, current_key);
+               if (rc) {
+                       pr_dbf_msg("Couldn't unlink key %08x: %d", current_key->serial, rc);
+                       break;
+               }
+       }
+out:
+       kfree(key_array);
+       return rc;
+}
+
+static struct key *find_cs_keyring(void)
+{
+       key_ref_t cs_keyring_ref;
+       struct key *cs_keyring;
+
+       cs_keyring_ref = keyring_search(make_key_ref(get_user_session_keyring(), true),
+                                       &key_type_keyring, CERT_STORE_KEYRING_NAME,
+                                       false);
+       if (!IS_ERR(cs_keyring_ref)) {
+               cs_keyring = key_ref_to_ptr(cs_keyring_ref);
+               key_ref_put(cs_keyring_ref);
+               goto found;
+       }
+       /* Search default locations: thread, process, session keyrings */
+       cs_keyring = request_key(&key_type_keyring, CERT_STORE_KEYRING_NAME, NULL);
+       if (IS_ERR(cs_keyring))
+               return NULL;
+       key_put(cs_keyring);
+found:
+       return cs_keyring;
+}
+
+static void cleanup_cs_keys(void)
+{
+       struct key *cs_keyring;
+
+       cs_keyring = find_cs_keyring();
+       if (!cs_keyring)
+               return;
+
+       pr_dbf_msg("Found cert_store keyring. Purging...");
+       /*
+        * Remove cert_store_key_type in case invalidation
+        * of old cert_store keys failed (= severe error).
+        */
+       if (invalidate_keyring_keys(cs_keyring))
+               unregister_key_type(&key_type_cert_store_key);
+
+       keyring_clear(cs_keyring);
+       key_invalidate(cs_keyring);
+       key_put(cs_keyring);
+       key_unlink(get_user_session_keyring(), cs_keyring);
+}
+
+static struct key *create_cs_keyring(void)
+{
+       static struct key *cs_keyring;
+
+       /* Cleanup previous cs_keyring and all associated keys if any. */
+       cleanup_cs_keys();
+       cs_keyring = keyring_alloc(CERT_STORE_KEYRING_NAME, GLOBAL_ROOT_UID,
+                                  GLOBAL_ROOT_GID, current_cred(),
+                                  (KEY_POS_ALL & ~KEY_POS_SETATTR) | KEY_USR_VIEW | KEY_USR_READ,
+                                  KEY_ALLOC_NOT_IN_QUOTA | KEY_ALLOC_SET_KEEP,
+                                  NULL, get_user_session_keyring());
+       if (IS_ERR(cs_keyring)) {
+               pr_dbf_msg("Can't allocate cert_store keyring");
+               return NULL;
+       }
+
+       pr_dbf_msg("Successfully allocated cert_store keyring: %08x", cs_keyring->serial);
+
+       /*
+        * In case a previous clean-up ran into an
+        * error and unregistered key type.
+        */
+       register_key_type(&key_type_cert_store_key);
+
+       return cs_keyring;
+}
+
+/*
+ * Allocate memory and create key description in format
+ * [key name in EBCDIC]:[VCE index]:[CS token].
+ * Return a pointer to key description or NULL if memory
+ * allocation failed. Memory should be freed by caller.
+ */
+static char *get_key_description(struct vcssb *vcssb, const struct vce *vce)
+{
+       size_t len, name_len;
+       u32 cs_token;
+       char *desc;
+
+       cs_token = vcssb->cs_token;
+       /* Description string contains "%64s:%04u:%08u\0". */
+       name_len = sizeof(vce->vce_hdr.vc_name);
+       len = name_len + 1 + 4 + 1 + 8 + 1;
+       desc = kmalloc(len, GFP_KERNEL);
+       if (!desc)
+               return NULL;
+
+       memcpy(desc, vce->vce_hdr.vc_name, name_len);
+       sprintf(desc + name_len, ":%04u:%08u", vce->vce_hdr.vc_index, cs_token);
+
+       return desc;
+}
+
+/*
+ * Create a key of type "cert_store_key" using the data from VCE for key
+ * payload and key description. Link the key to "cert_store" keyring.
+ */
+static int create_key_from_vce(struct vcssb *vcssb, struct vce *vce,
+                              struct key *keyring)
+{
+       key_ref_t newkey;
+       char *desc;
+       int rc;
+
+       desc = get_key_description(vcssb, vce);
+       if (!desc)
+               return -ENOMEM;
+
+       newkey = key_create_or_update(
+               make_key_ref(keyring, true), CERT_STORE_KEY_TYPE_NAME,
+               desc, (u8 *)vce + vce->vce_hdr.vc_offset,
+               vce->vce_hdr.vc_length,
+               (KEY_POS_ALL & ~KEY_POS_SETATTR)  | KEY_USR_VIEW | KEY_USR_READ,
+               KEY_ALLOC_NOT_IN_QUOTA);
+
+       rc = PTR_ERR_OR_ZERO(newkey);
+       if (rc) {
+               pr_dbf_msg("Couldn't create a key from Certificate Entry (%d)", rc);
+               rc = -ENOKEY;
+               goto out;
+       }
+
+       key_ref_put(newkey);
+out:
+       kfree(desc);
+       return rc;
+}
+
+/* Get Verification Certificate Storage Size block with DIAG320 subcode2. */
+static int get_vcssb(struct vcssb *vcssb)
+{
+       int diag320_rc;
+
+       memset(vcssb, 0, sizeof(*vcssb));
+       vcssb->vcssb_length = VCSSB_LEN_BYTES;
+       diag320_rc = diag320(DIAG320_STORAGE, vcssb);
+       pr_dbf_vcssb(vcssb);
+
+       if (diag320_rc != DIAG320_RC_OK) {
+               pr_dbf_msg("Diag 320 Subcode 1 returned bad RC: %04x", diag320_rc);
+               return -EIO;
+       }
+       if (vcssb->vcssb_length == VCSSB_LEN_NO_CERTS) {
+               pr_dbf_msg("No certificates available for current configuration");
+               return -ENOKEY;
+       }
+
+       return 0;
+}
+
+static u32 get_4k_mult_vcb_size(struct vcssb *vcssb)
+{
+       return round_up(vcssb->max_single_vcb_length, PAGE_SIZE);
+}
+
+/* Fill input fields of single-entry VCB that will be read by LPAR. */
+static void fill_vcb_input(struct vcssb *vcssb, struct vcb *vcb, u16 index)
+{
+       memset(vcb, 0, sizeof(*vcb));
+       vcb->vcb_hdr.vcb_input_length = get_4k_mult_vcb_size(vcssb);
+       vcb->vcb_hdr.cs_token = vcssb->cs_token;
+
+       /* Request single entry. */
+       vcb->vcb_hdr.first_vc_index = index;
+       vcb->vcb_hdr.last_vc_index = index;
+}
+
+static void extract_vce_from_sevcb(struct vcb *vcb, struct vce *vce)
+{
+       struct vce *extracted_vce;
+
+       extracted_vce = (struct vce *)vcb->vcb_buf;
+       memcpy(vce, vcb->vcb_buf, extracted_vce->vce_hdr.vce_length);
+       pr_dbf_vce(vce);
+}
+
+static int get_sevcb(struct vcssb *vcssb, u16 index, struct vcb *vcb)
+{
+       int rc, diag320_rc;
+
+       fill_vcb_input(vcssb, vcb, index);
+
+       diag320_rc = diag320(DIAG320_CERT_BLOCK, vcb);
+       pr_dbf_msg("Diag 320 Subcode2 RC %2x", diag320_rc);
+       pr_dbf_vcb(vcb);
+
+       switch (diag320_rc) {
+       case DIAG320_RC_OK:
+               rc = 0;
+               if (vcb->vcb_hdr.vcb_output_length == VCB_LEN_NO_CERTS) {
+                       pr_dbf_msg("No certificate entry for index %u", index);
+                       rc = -ENOKEY;
+               } else if (vcb->vcb_hdr.remaining_vc_count != 0) {
+                       /* Retry on insufficient space. */
+                       pr_dbf_msg("Couldn't get all requested certificates");
+                       rc = -EAGAIN;
+               }
+               break;
+       case DIAG320_RC_CS_NOMATCH:
+               pr_dbf_msg("Certificate Store token mismatch");
+               rc = -EAGAIN;
+               break;
+       default:
+               pr_dbf_msg("Diag 320 Subcode2 returned bad rc (0x%4x)", diag320_rc);
+               rc = -EINVAL;
+               break;
+       }
+
+       return rc;
+}
+
+/*
+ * Allocate memory for single-entry VCB, get VCB via DIAG320 subcode 2 call,
+ * extract VCE and create a key from its' certificate.
+ */
+static int create_key_from_sevcb(struct vcssb *vcssb, u16 index,
+                                struct key *keyring)
+{
+       struct vcb *vcb;
+       struct vce *vce;
+       int rc;
+
+       rc = -ENOMEM;
+       vcb = vmalloc(get_4k_mult_vcb_size(vcssb));
+       vce = vmalloc(vcssb->max_single_vcb_length - sizeof(vcb->vcb_hdr));
+       if (!vcb || !vce)
+               goto out;
+
+       rc = get_sevcb(vcssb, index, vcb);
+       if (rc)
+               goto out;
+
+       extract_vce_from_sevcb(vcb, vce);
+       rc = check_certificate_valid(vce);
+       if (rc)
+               goto out;
+
+       rc = create_key_from_vce(vcssb, vce, keyring);
+       if (rc)
+               goto out;
+
+       pr_dbf_msg("Successfully created key from Certificate Entry %d", index);
+out:
+       vfree(vce);
+       vfree(vcb);
+       return rc;
+}
+
+/*
+ * Request a single-entry VCB for each VCE available for the partition.
+ * Create a key from it and link it to cert_store keyring. If no keys
+ * could be created (i.e. VCEs were invalid) return -ENOKEY.
+ */
+static int add_certificates_to_keyring(struct vcssb *vcssb, struct key *keyring)
+{
+       int rc, index, count, added;
+
+       count = 0;
+       added = 0;
+       /* Certificate Store entries indices start with 1 and have no gaps. */
+       for (index = 1; index < vcssb->total_vc_index_count + 1; index++) {
+               pr_dbf_msg("Creating key from VCE %u", index);
+               rc = create_key_from_sevcb(vcssb, index, keyring);
+               count++;
+
+               if (rc == -EAGAIN)
+                       return rc;
+
+               if (rc)
+                       pr_dbf_msg("Creating key from VCE %u failed (%d)", index, rc);
+               else
+                       added++;
+       }
+
+       if (added == 0) {
+               pr_dbf_msg("Processed %d entries. No keys created", count);
+               return -ENOKEY;
+       }
+
+       pr_info("Added %d of %d keys to cert_store keyring", added, count);
+
+       /*
+        * Do not allow to link more keys to certificate store keyring after all
+        * the VCEs were processed.
+        */
+       rc = keyring_restrict(make_key_ref(keyring, true), NULL, NULL);
+       if (rc)
+               pr_dbf_msg("Failed to set restriction to cert_store keyring (%d)", rc);
+
+       return 0;
+}
+
+/*
+ * Check which DIAG320 subcodes are installed.
+ * Return -ENOENT if subcodes 1 or 2 are not available.
+ */
+static int query_diag320_subcodes(void)
+{
+       unsigned long ism[ISM_LEN_DWORDS];
+       int rc;
+
+       rc = diag320(0, ism);
+       if (rc != DIAG320_RC_OK) {
+               pr_dbf_msg("DIAG320 subcode query returned %04x", rc);
+               return -ENOENT;
+       }
+
+       debug_text_event(cert_store_hexdump, 3, "DIAG320 Subcode 0");
+       debug_event(cert_store_hexdump, 3, ism, sizeof(ism));
+
+       if (!test_bit_inv(1, ism) || !test_bit_inv(2, ism)) {
+               pr_dbf_msg("Not all required DIAG320 subcodes are installed");
+               return -ENOENT;
+       }
+
+       return 0;
+}
+
+/*
+ * Check if Certificate Store is supported by the firmware and DIAG320 subcodes
+ * 1 and 2 are installed. Create cert_store keyring and link all certificates
+ * available for the current partition to it as "cert_store_key" type
+ * keys. On refresh or error invalidate cert_store keyring and destroy
+ * all keys of "cert_store_key" type.
+ */
+static int fill_cs_keyring(void)
+{
+       struct key *cs_keyring;
+       struct vcssb *vcssb;
+       int rc;
+
+       rc = -ENOMEM;
+       vcssb = kmalloc(VCSSB_LEN_BYTES, GFP_KERNEL);
+       if (!vcssb)
+               goto cleanup_keys;
+
+       rc = -ENOENT;
+       if (!sclp.has_diag320) {
+               pr_dbf_msg("Certificate Store is not supported");
+               goto cleanup_keys;
+       }
+
+       rc = query_diag320_subcodes();
+       if (rc)
+               goto cleanup_keys;
+
+       rc = get_vcssb(vcssb);
+       if (rc)
+               goto cleanup_keys;
+
+       cs_keyring = create_cs_keyring();
+       if (!cs_keyring)
+               goto cleanup_keys;
+
+       rc = add_certificates_to_keyring(vcssb, cs_keyring);
+       if (rc)
+               goto cleanup_cs_keyring;
+
+       goto out;
+
+cleanup_cs_keyring:
+       key_put(cs_keyring);
+cleanup_keys:
+       cleanup_cs_keys();
+out:
+       kfree(vcssb);
+       return rc;
+}
+
+static DEFINE_MUTEX(cs_refresh_lock);
+static int cs_status_val = -1;
+
+static ssize_t cs_status_show(struct kobject *kobj,
+                             struct kobj_attribute *attr, char *buf)
+{
+       if (cs_status_val == -1)
+               return sysfs_emit(buf, "uninitialized\n");
+       else if (cs_status_val == 0)
+               return sysfs_emit(buf, "ok\n");
+
+       return sysfs_emit(buf, "failed (%d)\n", cs_status_val);
+}
+
+static struct kobj_attribute cs_status_attr = __ATTR_RO(cs_status);
+
+static ssize_t refresh_store(struct kobject *kobj, struct kobj_attribute *attr,
+                            const char *buf, size_t count)
+{
+       int rc, retries;
+
+       pr_dbf_msg("Refresh certificate store information requested");
+       rc = mutex_lock_interruptible(&cs_refresh_lock);
+       if (rc)
+               return rc;
+
+       for (retries = 0; retries < DIAG_MAX_RETRIES; retries++) {
+               /* Request certificates from certificate store. */
+               rc = fill_cs_keyring();
+               if (rc)
+                       pr_dbf_msg("Failed to refresh certificate store information (%d)", rc);
+               if (rc != -EAGAIN)
+                       break;
+       }
+       cs_status_val = rc;
+       mutex_unlock(&cs_refresh_lock);
+
+       return rc ?: count;
+}
+
+static struct kobj_attribute refresh_attr = __ATTR_WO(refresh);
+
+static const struct attribute *cert_store_attrs[] __initconst = {
+       &cs_status_attr.attr,
+       &refresh_attr.attr,
+       NULL,
+};
+
+static struct kobject *cert_store_kobj;
+
+static int __init cert_store_init(void)
+{
+       int rc = -ENOMEM;
+
+       cert_store_dbf = debug_register("cert_store_msg", 10, 1, 64);
+       if (!cert_store_dbf)
+               goto cleanup_dbf;
+
+       cert_store_hexdump = debug_register("cert_store_hexdump", 3, 1, 128);
+       if (!cert_store_hexdump)
+               goto cleanup_dbf;
+
+       debug_register_view(cert_store_hexdump, &debug_hex_ascii_view);
+       debug_register_view(cert_store_dbf, &debug_sprintf_view);
+
+       /* Create directory /sys/firmware/cert_store. */
+       cert_store_kobj = kobject_create_and_add("cert_store", firmware_kobj);
+       if (!cert_store_kobj)
+               goto cleanup_dbf;
+
+       rc = sysfs_create_files(cert_store_kobj, cert_store_attrs);
+       if (rc)
+               goto cleanup_kobj;
+
+       register_key_type(&key_type_cert_store_key);
+
+       return rc;
+
+cleanup_kobj:
+       kobject_put(cert_store_kobj);
+cleanup_dbf:
+       debug_unregister(cert_store_dbf);
+       debug_unregister(cert_store_hexdump);
+
+       return rc;
+}
+device_initcall(cert_store_init);
index 82079f2d8583baa3440f4deea6a5b4fb93dcaa8c..cca56b649ca3ba85c906b631fb3fe9d57074829c 100644 (file)
@@ -50,6 +50,7 @@ static const struct diag_desc diag_map[NR_DIAG_STAT] = {
        [DIAG_STAT_X304] = { .code = 0x304, .name = "Partition-Resource Service" },
        [DIAG_STAT_X308] = { .code = 0x308, .name = "List-Directed IPL" },
        [DIAG_STAT_X318] = { .code = 0x318, .name = "CP Name and Version Codes" },
+       [DIAG_STAT_X320] = { .code = 0x320, .name = "Certificate Store" },
        [DIAG_STAT_X500] = { .code = 0x500, .name = "Virtio Service" },
 };
 
index f480d6c7fd399b6b8ccb2bbd4e0b611c5719f5bf..fdc8668f3fba719995beff9a8fa9db7fa49a5645 100644 (file)
@@ -55,6 +55,7 @@ static void __init sclp_early_facilities_detect(void)
                S390_lowcore.machine_flags |= MACHINE_FLAG_TLB_GUEST;
        if (sccb->cpuoff > 134) {
                sclp.has_diag318 = !!(sccb->byte_134 & 0x80);
+               sclp.has_diag320 = !!(sccb->byte_134 & 0x04);
                sclp.has_iplcc = !!(sccb->byte_134 & 0x02);
        }
        if (sccb->cpuoff > 137) {