* [PATCH v3 01/28] Add boot-certificates to s390-ccw-virtio machine type option
2025-06-04 21:56 [PATCH v3 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
@ 2025-06-04 21:56 ` Zhuoying Cai
2025-06-06 14:00 ` Daniel P. Berrangé
2025-06-04 21:56 ` [PATCH v3 02/28] crypto/x509-utils: Add helper functions for certificate store Zhuoying Cai
` (26 subsequent siblings)
27 siblings, 1 reply; 51+ messages in thread
From: Zhuoying Cai @ 2025-06-04 21:56 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, pbonzini
Cc: walling, jjherne, jrossi, pasic, borntraeger, farman, iii, eblake,
armbru, qemu-s390x, qemu-devel, zycai
Add boot-certificates as a parameter of s390-ccw-virtio machine type option.
The `boot-certificates=/path/dir:/path/file` parameter is implemented
to provide path to either a directory or a single certificate.
Multiple paths can be delineated using a colon.
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
hw/s390x/s390-virtio-ccw.c | 22 ++++++++++++++++++++++
include/hw/s390x/s390-virtio-ccw.h | 1 +
qemu-options.hx | 7 ++++++-
3 files changed, 29 insertions(+), 1 deletion(-)
diff --git a/hw/s390x/s390-virtio-ccw.c b/hw/s390x/s390-virtio-ccw.c
index f20e02de9f..144ef52f34 100644
--- a/hw/s390x/s390-virtio-ccw.c
+++ b/hw/s390x/s390-virtio-ccw.c
@@ -798,6 +798,22 @@ static void machine_set_loadparm(Object *obj, Visitor *v,
g_free(val);
}
+static inline char *machine_get_boot_certificates(Object *obj, Error **errp)
+{
+ S390CcwMachineState *ms = S390_CCW_MACHINE(obj);
+
+ return g_strdup(ms->boot_certificates);
+}
+
+static void machine_set_boot_certificates(Object *obj, const char *str,
+ Error **errp)
+{
+ S390CcwMachineState *ms = S390_CCW_MACHINE(obj);
+
+ g_free(ms->boot_certificates);
+ ms->boot_certificates = g_strdup(str);
+}
+
static void ccw_machine_class_init(ObjectClass *oc, const void *data)
{
MachineClass *mc = MACHINE_CLASS(oc);
@@ -851,6 +867,12 @@ static void ccw_machine_class_init(ObjectClass *oc, const void *data)
"Up to 8 chars in set of [A-Za-z0-9. ] (lower case chars converted"
" to upper case) to pass to machine loader, boot manager,"
" and guest kernel");
+
+ object_class_property_add_str(oc, "boot-certificates",
+ machine_get_boot_certificates,
+ machine_set_boot_certificates);
+ object_class_property_set_description(oc, "boot-certificates",
+ "provide path to a directory or a single certificate for secure boot");
}
static inline void s390_machine_initfn(Object *obj)
diff --git a/include/hw/s390x/s390-virtio-ccw.h b/include/hw/s390x/s390-virtio-ccw.h
index 526078a4e2..45adc8bce6 100644
--- a/include/hw/s390x/s390-virtio-ccw.h
+++ b/include/hw/s390x/s390-virtio-ccw.h
@@ -31,6 +31,7 @@ struct S390CcwMachineState {
uint8_t loadparm[8];
uint64_t memory_limit;
uint64_t max_pagesize;
+ char *boot_certificates;
SCLPDevice *sclp;
};
diff --git a/qemu-options.hx b/qemu-options.hx
index 7eb8e02b4b..6d01f8c4b2 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -43,7 +43,8 @@ DEF("machine", HAS_ARG, QEMU_OPTION_machine, \
#endif
" memory-backend='backend-id' specifies explicitly provided backend for main RAM (default=none)\n"
" cxl-fmw.0.targets.0=firsttarget,cxl-fmw.0.targets.1=secondtarget,cxl-fmw.0.size=size[,cxl-fmw.0.interleave-granularity=granularity]\n"
- " smp-cache.0.cache=cachename,smp-cache.0.topology=topologylevel\n",
+ " smp-cache.0.cache=cachename,smp-cache.0.topology=topologylevel\n"
+ " boot-certificates='/path/directory:/path/file' provide a path to a directory or a boot certificate\n",
QEMU_ARCH_ALL)
SRST
``-machine [type=]name[,prop=value[,...]]``
@@ -200,6 +201,10 @@ SRST
::
-machine smp-cache.0.cache=l1d,smp-cache.0.topology=core,smp-cache.1.cache=l1i,smp-cache.1.topology=core
+
+ ``boot-certificates='/path/directory:/path/file'``
+ Provide a path to a directory or a boot certificate on the host [s390x only].
+ A colon may be used to delineate multiple paths.
ERST
DEF("M", HAS_ARG, QEMU_OPTION_M,
--
2.49.0
^ permalink raw reply related [flat|nested] 51+ messages in thread* Re: [PATCH v3 01/28] Add boot-certificates to s390-ccw-virtio machine type option
2025-06-04 21:56 ` [PATCH v3 01/28] Add boot-certificates to s390-ccw-virtio machine type option Zhuoying Cai
@ 2025-06-06 14:00 ` Daniel P. Berrangé
2025-06-20 15:45 ` Zhuoying Cai
0 siblings, 1 reply; 51+ messages in thread
From: Daniel P. Berrangé @ 2025-06-06 14:00 UTC (permalink / raw)
To: Zhuoying Cai
Cc: thuth, richard.henderson, david, pbonzini, walling, jjherne,
jrossi, pasic, borntraeger, farman, iii, eblake, armbru,
qemu-s390x, qemu-devel
On Wed, Jun 04, 2025 at 05:56:29PM -0400, Zhuoying Cai wrote:
> Add boot-certificates as a parameter of s390-ccw-virtio machine type option.
>
> The `boot-certificates=/path/dir:/path/file` parameter is implemented
> to provide path to either a directory or a single certificate.
>
> Multiple paths can be delineated using a colon.
How do users specify paths which contain a colon as a valid
character ?
Ideally we should be using array properties when we need
a list of parameters.
>
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
> hw/s390x/s390-virtio-ccw.c | 22 ++++++++++++++++++++++
> include/hw/s390x/s390-virtio-ccw.h | 1 +
> qemu-options.hx | 7 ++++++-
> 3 files changed, 29 insertions(+), 1 deletion(-)
>
> diff --git a/hw/s390x/s390-virtio-ccw.c b/hw/s390x/s390-virtio-ccw.c
> index f20e02de9f..144ef52f34 100644
> --- a/hw/s390x/s390-virtio-ccw.c
> +++ b/hw/s390x/s390-virtio-ccw.c
> @@ -798,6 +798,22 @@ static void machine_set_loadparm(Object *obj, Visitor *v,
> g_free(val);
> }
>
> +static inline char *machine_get_boot_certificates(Object *obj, Error **errp)
> +{
> + S390CcwMachineState *ms = S390_CCW_MACHINE(obj);
> +
> + return g_strdup(ms->boot_certificates);
> +}
> +
> +static void machine_set_boot_certificates(Object *obj, const char *str,
> + Error **errp)
> +{
> + S390CcwMachineState *ms = S390_CCW_MACHINE(obj);
> +
> + g_free(ms->boot_certificates);
> + ms->boot_certificates = g_strdup(str);
> +}
> +
> static void ccw_machine_class_init(ObjectClass *oc, const void *data)
> {
> MachineClass *mc = MACHINE_CLASS(oc);
> @@ -851,6 +867,12 @@ static void ccw_machine_class_init(ObjectClass *oc, const void *data)
> "Up to 8 chars in set of [A-Za-z0-9. ] (lower case chars converted"
> " to upper case) to pass to machine loader, boot manager,"
> " and guest kernel");
> +
> + object_class_property_add_str(oc, "boot-certificates",
> + machine_get_boot_certificates,
> + machine_set_boot_certificates);
> + object_class_property_set_description(oc, "boot-certificates",
> + "provide path to a directory or a single certificate for secure boot");
> }
>
> static inline void s390_machine_initfn(Object *obj)
> diff --git a/include/hw/s390x/s390-virtio-ccw.h b/include/hw/s390x/s390-virtio-ccw.h
> index 526078a4e2..45adc8bce6 100644
> --- a/include/hw/s390x/s390-virtio-ccw.h
> +++ b/include/hw/s390x/s390-virtio-ccw.h
> @@ -31,6 +31,7 @@ struct S390CcwMachineState {
> uint8_t loadparm[8];
> uint64_t memory_limit;
> uint64_t max_pagesize;
> + char *boot_certificates;
>
> SCLPDevice *sclp;
> };
> diff --git a/qemu-options.hx b/qemu-options.hx
> index 7eb8e02b4b..6d01f8c4b2 100644
> --- a/qemu-options.hx
> +++ b/qemu-options.hx
> @@ -43,7 +43,8 @@ DEF("machine", HAS_ARG, QEMU_OPTION_machine, \
> #endif
> " memory-backend='backend-id' specifies explicitly provided backend for main RAM (default=none)\n"
> " cxl-fmw.0.targets.0=firsttarget,cxl-fmw.0.targets.1=secondtarget,cxl-fmw.0.size=size[,cxl-fmw.0.interleave-granularity=granularity]\n"
> - " smp-cache.0.cache=cachename,smp-cache.0.topology=topologylevel\n",
> + " smp-cache.0.cache=cachename,smp-cache.0.topology=topologylevel\n"
> + " boot-certificates='/path/directory:/path/file' provide a path to a directory or a boot certificate\n",
> QEMU_ARCH_ALL)
> SRST
> ``-machine [type=]name[,prop=value[,...]]``
> @@ -200,6 +201,10 @@ SRST
> ::
>
> -machine smp-cache.0.cache=l1d,smp-cache.0.topology=core,smp-cache.1.cache=l1i,smp-cache.1.topology=core
> +
> + ``boot-certificates='/path/directory:/path/file'``
> + Provide a path to a directory or a boot certificate on the host [s390x only].
> + A colon may be used to delineate multiple paths.
> ERST
>
> DEF("M", HAS_ARG, QEMU_OPTION_M,
> --
> 2.49.0
>
With regards,
Daniel
--
|: https://berrange.com -o- https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org -o- https://fstop138.berrange.com :|
|: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|
^ permalink raw reply [flat|nested] 51+ messages in thread* Re: [PATCH v3 01/28] Add boot-certificates to s390-ccw-virtio machine type option
2025-06-06 14:00 ` Daniel P. Berrangé
@ 2025-06-20 15:45 ` Zhuoying Cai
2025-06-24 15:03 ` Jared Rossi
0 siblings, 1 reply; 51+ messages in thread
From: Zhuoying Cai @ 2025-06-20 15:45 UTC (permalink / raw)
To: Daniel P. Berrangé
Cc: thuth, richard.henderson, david, pbonzini, walling, jjherne,
jrossi, pasic, borntraeger, farman, iii, eblake, armbru,
qemu-s390x, qemu-devel
On 6/6/25 10:00 AM, Daniel P. Berrangé wrote:
> On Wed, Jun 04, 2025 at 05:56:29PM -0400, Zhuoying Cai wrote:
>> Add boot-certificates as a parameter of s390-ccw-virtio machine type option.
>>
>> The `boot-certificates=/path/dir:/path/file` parameter is implemented
>> to provide path to either a directory or a single certificate.
>>
>> Multiple paths can be delineated using a colon.
>
> How do users specify paths which contain a colon as a valid
> character ?
>
It was suggested to separate lists of directories and files with a
colon, following the convention used by the shell PATH variable. As the
colon serves as a delimiter, it’s expected that individual paths do not
contain any colon characters.
> Ideally we should be using array properties when we need
> a list of parameters.
>
Could you provide an example of specifying the boot-certificate
parameter with the -machine option using array properties?
>>
>> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
>> ---
>> hw/s390x/s390-virtio-ccw.c | 22 ++++++++++++++++++++++
>> include/hw/s390x/s390-virtio-ccw.h | 1 +
>> qemu-options.hx | 7 ++++++-
>> 3 files changed, 29 insertions(+), 1 deletion(-)
>>
>> diff --git a/hw/s390x/s390-virtio-ccw.c b/hw/s390x/s390-virtio-ccw.c
>> index f20e02de9f..144ef52f34 100644
>> --- a/hw/s390x/s390-virtio-ccw.c
>> +++ b/hw/s390x/s390-virtio-ccw.c
>> @@ -798,6 +798,22 @@ static void machine_set_loadparm(Object *obj, Visitor *v,
>> g_free(val);
>> }
>>
>> +static inline char *machine_get_boot_certificates(Object *obj, Error **errp)
>> +{
>> + S390CcwMachineState *ms = S390_CCW_MACHINE(obj);
>> +
>> + return g_strdup(ms->boot_certificates);
>> +}
>> +
>> +static void machine_set_boot_certificates(Object *obj, const char *str,
>> + Error **errp)
>> +{
>> + S390CcwMachineState *ms = S390_CCW_MACHINE(obj);
>> +
>> + g_free(ms->boot_certificates);
>> + ms->boot_certificates = g_strdup(str);
>> +}
>> +
>> static void ccw_machine_class_init(ObjectClass *oc, const void *data)
>> {
>> MachineClass *mc = MACHINE_CLASS(oc);
>> @@ -851,6 +867,12 @@ static void ccw_machine_class_init(ObjectClass *oc, const void *data)
>> "Up to 8 chars in set of [A-Za-z0-9. ] (lower case chars converted"
>> " to upper case) to pass to machine loader, boot manager,"
>> " and guest kernel");
>> +
>> + object_class_property_add_str(oc, "boot-certificates",
>> + machine_get_boot_certificates,
>> + machine_set_boot_certificates);
>> + object_class_property_set_description(oc, "boot-certificates",
>> + "provide path to a directory or a single certificate for secure boot");
>> }
>>
>> static inline void s390_machine_initfn(Object *obj)
>> diff --git a/include/hw/s390x/s390-virtio-ccw.h b/include/hw/s390x/s390-virtio-ccw.h
>> index 526078a4e2..45adc8bce6 100644
>> --- a/include/hw/s390x/s390-virtio-ccw.h
>> +++ b/include/hw/s390x/s390-virtio-ccw.h
>> @@ -31,6 +31,7 @@ struct S390CcwMachineState {
>> uint8_t loadparm[8];
>> uint64_t memory_limit;
>> uint64_t max_pagesize;
>> + char *boot_certificates;
>>
>> SCLPDevice *sclp;
>> };
>> diff --git a/qemu-options.hx b/qemu-options.hx
>> index 7eb8e02b4b..6d01f8c4b2 100644
>> --- a/qemu-options.hx
>> +++ b/qemu-options.hx
>> @@ -43,7 +43,8 @@ DEF("machine", HAS_ARG, QEMU_OPTION_machine, \
>> #endif
>> " memory-backend='backend-id' specifies explicitly provided backend for main RAM (default=none)\n"
>> " cxl-fmw.0.targets.0=firsttarget,cxl-fmw.0.targets.1=secondtarget,cxl-fmw.0.size=size[,cxl-fmw.0.interleave-granularity=granularity]\n"
>> - " smp-cache.0.cache=cachename,smp-cache.0.topology=topologylevel\n",
>> + " smp-cache.0.cache=cachename,smp-cache.0.topology=topologylevel\n"
>> + " boot-certificates='/path/directory:/path/file' provide a path to a directory or a boot certificate\n",
>> QEMU_ARCH_ALL)
>> SRST
>> ``-machine [type=]name[,prop=value[,...]]``
>> @@ -200,6 +201,10 @@ SRST
>> ::
>>
>> -machine smp-cache.0.cache=l1d,smp-cache.0.topology=core,smp-cache.1.cache=l1i,smp-cache.1.topology=core
>> +
>> + ``boot-certificates='/path/directory:/path/file'``
>> + Provide a path to a directory or a boot certificate on the host [s390x only].
>> + A colon may be used to delineate multiple paths.
>> ERST
>>
>> DEF("M", HAS_ARG, QEMU_OPTION_M,
>> --
>> 2.49.0
>>
>
> With regards,
> Daniel
^ permalink raw reply [flat|nested] 51+ messages in thread* Re: [PATCH v3 01/28] Add boot-certificates to s390-ccw-virtio machine type option
2025-06-20 15:45 ` Zhuoying Cai
@ 2025-06-24 15:03 ` Jared Rossi
2025-06-30 19:31 ` Zhuoying Cai
0 siblings, 1 reply; 51+ messages in thread
From: Jared Rossi @ 2025-06-24 15:03 UTC (permalink / raw)
To: Zhuoying Cai, Daniel P. Berrangé
Cc: thuth, richard.henderson, david, pbonzini, walling, jjherne,
pasic, borntraeger, farman, iii, eblake, armbru, qemu-s390x,
qemu-devel
On 6/20/25 11:45 AM, Zhuoying Cai wrote:
> On 6/6/25 10:00 AM, Daniel P. Berrangé wrote:
>> On Wed, Jun 04, 2025 at 05:56:29PM -0400, Zhuoying Cai wrote:
>>> Add boot-certificates as a parameter of s390-ccw-virtio machine type option.
>>>
>>> The `boot-certificates=/path/dir:/path/file` parameter is implemented
>>> to provide path to either a directory or a single certificate.
>>>
>>> Multiple paths can be delineated using a colon.
>> How do users specify paths which contain a colon as a valid
>> character ?
>>
> It was suggested to separate lists of directories and files with a
> colon, following the convention used by the shell PATH variable. As the
> colon serves as a delimiter, it’s expected that individual paths do not
> contain any colon characters.
I'm not sure if I understand why this is needed. Why would someone want
to have the certificates in two distinct locations, as opposed to all in
one directory or in sub-directories of a single main certificate
directory? Supporting only one path would simplify both the
implementation and the usage. Could we just not allow multiple paths,
or is there a use case that requires it?
> [snip..]
>
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH v3 01/28] Add boot-certificates to s390-ccw-virtio machine type option
2025-06-24 15:03 ` Jared Rossi
@ 2025-06-30 19:31 ` Zhuoying Cai
0 siblings, 0 replies; 51+ messages in thread
From: Zhuoying Cai @ 2025-06-30 19:31 UTC (permalink / raw)
To: Jared Rossi, Daniel P. Berrangé
Cc: thuth, richard.henderson, david, pbonzini, walling, jjherne,
pasic, borntraeger, farman, iii, eblake, armbru, qemu-s390x,
qemu-devel
On 6/24/25 11:03 AM, Jared Rossi wrote:
>
>
> On 6/20/25 11:45 AM, Zhuoying Cai wrote:
>> On 6/6/25 10:00 AM, Daniel P. Berrangé wrote:
>>> On Wed, Jun 04, 2025 at 05:56:29PM -0400, Zhuoying Cai wrote:
>>>> Add boot-certificates as a parameter of s390-ccw-virtio machine type option.
>>>>
>>>> The `boot-certificates=/path/dir:/path/file` parameter is implemented
>>>> to provide path to either a directory or a single certificate.
>>>>
>>>> Multiple paths can be delineated using a colon.
>>> How do users specify paths which contain a colon as a valid
>>> character ?
>>>
>> It was suggested to separate lists of directories and files with a
>> colon, following the convention used by the shell PATH variable. As the
>> colon serves as a delimiter, it’s expected that individual paths do not
>> contain any colon characters.
>
> I'm not sure if I understand why this is needed. Why would someone want
> to have the certificates in two distinct locations, as opposed to all in
> one directory or in sub-directories of a single main certificate
> directory? Supporting only one path would simplify both the
> implementation and the usage. Could we just not allow multiple paths,
> or is there a use case that requires it?
>
We chose to support lists of directories and files to provide greater
flexibility in the CLI. One use case involves scenarios where some
certificates are shipped with the distro (e.g., /usr/path/cert/dir),
while additional certificates may be managed by the local admin in a
separate location, such as /etc/path/cert/dir.
If supporting multiple of directories and files is a concern, the design
could be simplified to allow a single directory containing all the
certificates required for secure boot, which should still cover typical
usage scenarios.
I'd appreciate any additional feedback on this design.
>> [snip..]
>>
^ permalink raw reply [flat|nested] 51+ messages in thread
* [PATCH v3 02/28] crypto/x509-utils: Add helper functions for certificate store
2025-06-04 21:56 [PATCH v3 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
2025-06-04 21:56 ` [PATCH v3 01/28] Add boot-certificates to s390-ccw-virtio machine type option Zhuoying Cai
@ 2025-06-04 21:56 ` Zhuoying Cai
2025-06-06 10:03 ` Daniel P. Berrangé
` (2 more replies)
2025-06-04 21:56 ` [PATCH v3 03/28] hw/s390x/ipl: Create " Zhuoying Cai
` (25 subsequent siblings)
27 siblings, 3 replies; 51+ messages in thread
From: Zhuoying Cai @ 2025-06-04 21:56 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, pbonzini
Cc: walling, jjherne, jrossi, pasic, borntraeger, farman, iii, eblake,
armbru, qemu-s390x, qemu-devel, zycai
Add helper functions for x509 certificate which will be used in the next
patch for the certificate store.
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
crypto/meson.build | 5 +-
crypto/x509-utils.c | 166 ++++++++++++++++++++++++++++++++++++
include/crypto/x509-utils.h | 54 ++++++++++++
qapi/crypto.json | 80 +++++++++++++++++
4 files changed, 301 insertions(+), 4 deletions(-)
diff --git a/crypto/meson.build b/crypto/meson.build
index 735635de1f..0614bfa914 100644
--- a/crypto/meson.build
+++ b/crypto/meson.build
@@ -22,12 +22,9 @@ crypto_ss.add(files(
'tlscredsx509.c',
'tlssession.c',
'rsakey.c',
+ 'x509-utils.c',
))
-if gnutls.found()
- crypto_ss.add(files('x509-utils.c'))
-endif
-
if nettle.found()
crypto_ss.add(nettle, files('hash-nettle.c', 'hmac-nettle.c', 'pbkdf-nettle.c'))
if hogweed.found()
diff --git a/crypto/x509-utils.c b/crypto/x509-utils.c
index 8bad00a51b..7a7f12c111 100644
--- a/crypto/x509-utils.c
+++ b/crypto/x509-utils.c
@@ -11,6 +11,8 @@
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "crypto/x509-utils.h"
+
+#ifdef CONFIG_GNUTLS
#include <gnutls/gnutls.h>
#include <gnutls/crypto.h>
#include <gnutls/x509.h>
@@ -25,6 +27,109 @@ static const int qcrypto_to_gnutls_hash_alg_map[QCRYPTO_HASH_ALGO__MAX] = {
[QCRYPTO_HASH_ALGO_RIPEMD160] = GNUTLS_DIG_RMD160,
};
+static const int qcrypto_to_gnutls_keyid_flags_map[QCRYPTO_KEYID_FLAGS__MAX] = {
+ [QCRYPTO_KEYID_FLAGS_SHA1] = GNUTLS_KEYID_USE_SHA1,
+ [QCRYPTO_KEYID_FLAGS_SHA256] = GNUTLS_KEYID_USE_SHA256,
+ [QCRYPTO_KEYID_FLAGS_SHA512] = GNUTLS_KEYID_USE_SHA512,
+ [QCRYPTO_KEYID_FLAGS_BEST_KNOWN] = GNUTLS_KEYID_USE_BEST_KNOWN,
+};
+
+static const int qcrypto_to_gnutls_cert_fmt_map[QCRYPTO_CERT_FMT__MAX] = {
+ [QCRYPTO_CERT_FMT_DER] = GNUTLS_X509_FMT_DER,
+ [QCRYPTO_CERT_FMT_PEM] = GNUTLS_X509_FMT_PEM,
+};
+
+static const int gnutls_to_qcrypto_sig_alg_map[QCRYPTO_SIG_ALGO__MAX] = {
+ [GNUTLS_SIGN_UNKNOWN] = QCRYPTO_SIG_ALGO_UNKNOWN,
+ [GNUTLS_SIGN_RSA_SHA1] = QCRYPTO_SIG_ALGO_RSA_SHA1,
+ [GNUTLS_SIGN_RSA_SHA] = QCRYPTO_SIG_ALGO_RSA_SHA1,
+ [GNUTLS_SIGN_DSA_SHA1] = QCRYPTO_SIG_ALGO_DSA_SHA1,
+ [GNUTLS_SIGN_RSA_MD5] = QCRYPTO_SIG_ALGO_RSA_MD5,
+ [GNUTLS_SIGN_RSA_MD2] = QCRYPTO_SIG_ALGO_RSA_MD2,
+ [GNUTLS_SIGN_RSA_RMD160] = QCRYPTO_SIG_ALGO_RSA_RMD160,
+ [GNUTLS_SIGN_RSA_SHA256] = QCRYPTO_SIG_ALGO_RSA_SHA256,
+ [GNUTLS_SIGN_RSA_SHA384] = QCRYPTO_SIG_ALGO_RSA_SHA384,
+ [GNUTLS_SIGN_RSA_SHA512] = QCRYPTO_SIG_ALGO_RSA_SHA512,
+ [GNUTLS_SIGN_RSA_SHA224] = QCRYPTO_SIG_ALGO_RSA_SHA224,
+ [GNUTLS_SIGN_DSA_SHA224] = QCRYPTO_SIG_ALGO_DSA_SHA224,
+ [GNUTLS_SIGN_DSA_SHA256] = QCRYPTO_SIG_ALGO_DSA_SHA256,
+ [GNUTLS_SIGN_ECDSA_SHA1] = QCRYPTO_SIG_ALGO_ECDSA_SHA1,
+ [GNUTLS_SIGN_ECDSA_SHA224] = QCRYPTO_SIG_ALGO_ECDSA_SHA224,
+ [GNUTLS_SIGN_ECDSA_SHA256] = QCRYPTO_SIG_ALGO_ECDSA_SHA256,
+ [GNUTLS_SIGN_ECDSA_SHA384] = QCRYPTO_SIG_ALGO_ECDSA_SHA384,
+ [GNUTLS_SIGN_ECDSA_SHA512] = QCRYPTO_SIG_ALGO_ECDSA_SHA512,
+};
+
+int qcrypto_check_x509_cert_fmt(uint8_t *cert, size_t size,
+ QCryptoCertFmt fmt, Error **errp)
+{
+ int rc;
+ int ret = -1;
+ gnutls_x509_crt_t crt;
+ gnutls_datum_t datum = {.data = cert, .size = size};
+
+ if (fmt >= G_N_ELEMENTS(qcrypto_to_gnutls_cert_fmt_map)) {
+ error_setg(errp, "Unknown certificate format");
+ return ret;
+ }
+
+ if (gnutls_x509_crt_init(&crt) < 0) {
+ error_setg(errp, "Failed to initialize certificate");
+ return ret;
+ }
+
+ rc = gnutls_x509_crt_import(crt, &datum, qcrypto_to_gnutls_cert_fmt_map[fmt]);
+ if (rc == GNUTLS_E_ASN1_TAG_ERROR) {
+ goto cleanup;
+ }
+
+ ret = 0;
+
+cleanup:
+ gnutls_x509_crt_deinit(crt);
+ return ret;
+}
+
+int qcrypto_get_x509_hash_len(QCryptoHashAlgo alg)
+{
+ if (alg >= G_N_ELEMENTS(qcrypto_to_gnutls_hash_alg_map)) {
+ return 0;
+ }
+
+ return gnutls_hash_get_len(qcrypto_to_gnutls_hash_alg_map[alg]);
+}
+
+int qcrypto_get_x509_keyid_len(QCryptoKeyidFlags flag)
+{
+ QCryptoHashAlgo alg;
+
+ if (flag >= G_N_ELEMENTS(qcrypto_to_gnutls_keyid_flags_map)) {
+ return 0;
+ }
+
+ alg = QCRYPTO_HASH_ALGO_SHA1;
+ if ((flag & qcrypto_to_gnutls_keyid_flags_map[QCRYPTO_KEYID_FLAGS_SHA512]) ||
+ (flag & qcrypto_to_gnutls_keyid_flags_map[QCRYPTO_KEYID_FLAGS_BEST_KNOWN])) {
+ alg = QCRYPTO_HASH_ALGO_SHA512;
+ } else if (flag & qcrypto_to_gnutls_keyid_flags_map[QCRYPTO_KEYID_FLAGS_SHA256]) {
+ alg = QCRYPTO_HASH_ALGO_SHA256;
+ }
+
+ return qcrypto_get_x509_hash_len(alg);
+}
+
+static int qcrypto_import_x509_cert(gnutls_x509_crt_t crt, gnutls_datum_t *datum)
+{
+ int rc;
+
+ rc = gnutls_x509_crt_import(crt, datum, GNUTLS_X509_FMT_PEM);
+ if (rc) {
+ rc = gnutls_x509_crt_import(crt, datum, GNUTLS_X509_FMT_DER);
+ }
+
+ return rc;
+}
+
int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
QCryptoHashAlgo alg,
uint8_t *result,
@@ -74,3 +179,64 @@ int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
gnutls_x509_crt_deinit(crt);
return ret;
}
+
+int qcrypto_get_x509_signature_algorithm(uint8_t *cert, size_t size, Error **errp)
+{
+ int rc = -1;
+ gnutls_x509_crt_t crt;
+ gnutls_datum_t datum = {.data = cert, .size = size};
+
+ if (gnutls_x509_crt_init(&crt) < 0) {
+ error_setg(errp, "Failed to initialize certificate");
+ return rc;
+ }
+
+ if (qcrypto_import_x509_cert(crt, &datum) != 0) {
+ error_setg(errp, "Failed to import certificate");
+ goto cleanup;
+ }
+
+ rc = gnutls_x509_crt_get_signature_algorithm(crt);
+ rc = gnutls_to_qcrypto_sig_alg_map[rc];
+
+cleanup:
+ gnutls_x509_crt_deinit(crt);
+ return rc;
+}
+
+#else /* ! CONFIG_GNUTLS */
+
+int qcrypto_check_x509_cert_fmt(uint8_t *cert, size_t size,
+ QCryptoCertFmt fmt, Error **errp)
+{
+ error_setg(errp, "GNUTLS is required to get certificate format");
+ return -ENOTSUP;
+}
+
+int qcrypto_get_x509_hash_len(QCryptoHashAlgo alg)
+{
+ return -ENOTSUP;
+}
+
+int qcrypto_get_x509_keyid_len(QCryptoKeyidFlags flag)
+{
+ return -ENOTSUP;
+}
+
+int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
+ QCryptoHashAlgo hash,
+ uint8_t *result,
+ size_t *resultlen,
+ Error **errp)
+{
+ error_setg(errp, "GNUTLS is required to get fingerprint");
+ return -ENOTSUP;
+}
+
+int qcrypto_get_x509_signature_algorithm(uint8_t *cert, size_t size, Error **errp)
+{
+ error_setg(errp, "GNUTLS is required to get signature algorithm");
+ return -ENOTSUP;
+}
+
+#endif /* ! CONFIG_GNUTLS */
diff --git a/include/crypto/x509-utils.h b/include/crypto/x509-utils.h
index 1e99661a71..d7be57c8ce 100644
--- a/include/crypto/x509-utils.h
+++ b/include/crypto/x509-utils.h
@@ -19,4 +19,58 @@ int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
size_t *resultlen,
Error **errp);
+/**
+ * qcrypto_check_x509_cert_fmt
+ * @cert: pointer to the raw certiricate data
+ * @size: size of the certificate
+ * @fmt: expected certificate format
+ * @errp: error pointer
+ *
+ * Check whether the format of @cert matches @fmt.
+ *
+ * Returns: 0 if the format of @cert matches @fmt,
+ -1 if the format does not match,
+ * -ENOTSUP if GNUTLS is not enabled.
+ */
+int qcrypto_check_x509_cert_fmt(uint8_t *cert, size_t size,
+ QCryptoCertFmt fmt, Error **errp);
+
+/**
+ * qcrypto_get_x509_hash_len
+ * @alg: the hash algorithm
+ *
+ * Determine the length of the hash of the given @alg.
+ *
+ * Returns: the length on success,
+ 0 on error,
+ -ENOTSUP if GNUTLS is not enabled.
+ */
+int qcrypto_get_x509_hash_len(QCryptoHashAlgo alg);
+
+/**
+ * qcrypto_get_x509_keyid_len
+ * @flag: the key ID flag
+ *
+ * Determine the length of the key ID of the given @flag.
+ *
+ * Returns: the length on success,
+ 0 on error,
+ -ENOTSUP if GNUTLS is not enabled.
+ */
+int qcrypto_get_x509_keyid_len(QCryptoKeyidFlags flag);
+
+/**
+ * qcrypto_get_x509_signature_algorithm
+ * @cert: pointer to the raw certiricate data
+ * @size: size of the certificate
+ * @errp: error pointer
+ *
+ * Determine the signature algorithm used to sign the @cert.
+ *
+ * Returns: a value from the QCryptoSigAlgo enum on success,
+ * -1 on error,
+ * -ENOTSUP if GNUTLS is not enabled.
+ */
+int qcrypto_get_x509_signature_algorithm(uint8_t *cert, size_t size, Error **errp);
+
#endif
diff --git a/qapi/crypto.json b/qapi/crypto.json
index c9d967d782..af487dcecd 100644
--- a/qapi/crypto.json
+++ b/qapi/crypto.json
@@ -612,3 +612,83 @@
'base': { 'alg': 'QCryptoAkCipherAlgo' },
'discriminator': 'alg',
'data': { 'rsa': 'QCryptoAkCipherOptionsRSA' }}
+
+##
+# @QCryptoKeyidFlags:
+#
+# The supported flags for the key ID
+#
+# @sha1: SHA-1
+#
+# @sha256: SHA-256
+#
+# @sha512: SHA-512
+#
+# @best-known: BEST-KNOWN
+#
+# Since: 10.1
+##
+{ 'enum': 'QCryptoKeyidFlags',
+ 'data': ['sha1', 'sha256', 'sha512', 'best-known']}
+
+##
+# @QCryptoCertFmt:
+#
+# The supported certificate encoding formats
+#
+# @der: DER
+#
+# @pem: PEM
+#
+# Since: 10.1
+##
+{ 'enum': 'QCryptoCertFmt',
+ 'data': ['der', 'pem']}
+
+##
+# @QCryptoSigAlgo:
+#
+# Algorithms for digital signature
+#
+# @unknown: UNKNOWN
+#
+# @rsa-sha1: RSA-SHA1 or RSA-SHA
+#
+# @dsa-sha1: DSA-SHA1 or DSA-SHA
+#
+# @rsa-md5: RSA-MD5
+#
+# @rsa-md2: RSA-MD2
+#
+# @rsa-rmd160: RSA-RMD160
+#
+# @rsa-sha256: RSA-SHA256
+#
+# @rsa-sha384: RSA-SHA384
+#
+# @rsa-sha512: RSA-SHA512
+#
+# @rsa-sha224: RSA-SHA224
+#
+# @dsa-sha224: DSA-SHA224
+#
+# @dsa-sha256: DSA-SHA256
+#
+# @ecdsa-sha1: ECDSA-SHA1
+#
+# @ecdsa-sha224: ECDSA-SHA224
+#
+# @ecdsa-sha256: ECDSA-SHA256
+#
+# @ecdsa-sha384: ECDSA-SHA384
+#
+# @ecdsa-sha512: ECDSA-SHA512
+#
+# Since: 10.1
+##
+{ 'enum': 'QCryptoSigAlgo',
+ 'data': ['unknown', 'rsa-sha1', 'dsa-sha1',
+ 'rsa-md5', 'rsa-md2', 'rsa-rmd160',
+ 'rsa-sha256', 'rsa-sha384', 'rsa-sha512', 'rsa-sha224',
+ 'dsa-sha224', 'dsa-sha256',
+ 'ecdsa-sha1', 'ecdsa-sha224', 'ecdsa-sha256', 'ecdsa-sha384', 'ecdsa-sha512']}
--
2.49.0
^ permalink raw reply related [flat|nested] 51+ messages in thread* Re: [PATCH v3 02/28] crypto/x509-utils: Add helper functions for certificate store
2025-06-04 21:56 ` [PATCH v3 02/28] crypto/x509-utils: Add helper functions for certificate store Zhuoying Cai
@ 2025-06-06 10:03 ` Daniel P. Berrangé
2025-06-06 10:14 ` Daniel P. Berrangé
2025-06-17 10:58 ` Markus Armbruster
2 siblings, 0 replies; 51+ messages in thread
From: Daniel P. Berrangé @ 2025-06-06 10:03 UTC (permalink / raw)
To: Zhuoying Cai
Cc: thuth, richard.henderson, david, pbonzini, walling, jjherne,
jrossi, pasic, borntraeger, farman, iii, eblake, armbru,
qemu-s390x, qemu-devel
On Wed, Jun 04, 2025 at 05:56:30PM -0400, Zhuoying Cai wrote:
> Add helper functions for x509 certificate which will be used in the next
> patch for the certificate store.
>
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
> crypto/meson.build | 5 +-
> crypto/x509-utils.c | 166 ++++++++++++++++++++++++++++++++++++
> include/crypto/x509-utils.h | 54 ++++++++++++
> qapi/crypto.json | 80 +++++++++++++++++
> 4 files changed, 301 insertions(+), 4 deletions(-)
>
> diff --git a/crypto/meson.build b/crypto/meson.build
> index 735635de1f..0614bfa914 100644
> --- a/crypto/meson.build
> +++ b/crypto/meson.build
> @@ -22,12 +22,9 @@ crypto_ss.add(files(
> 'tlscredsx509.c',
> 'tlssession.c',
> 'rsakey.c',
> + 'x509-utils.c',
> ))
>
> -if gnutls.found()
> - crypto_ss.add(files('x509-utils.c'))
> -endif
> -
> if nettle.found()
> crypto_ss.add(nettle, files('hash-nettle.c', 'hmac-nettle.c', 'pbkdf-nettle.c'))
> if hogweed.found()
> diff --git a/crypto/x509-utils.c b/crypto/x509-utils.c
> index 8bad00a51b..7a7f12c111 100644
> --- a/crypto/x509-utils.c
> +++ b/crypto/x509-utils.c
> @@ -11,6 +11,8 @@
> #include "qemu/osdep.h"
> #include "qapi/error.h"
> #include "crypto/x509-utils.h"
> +
> +#ifdef CONFIG_GNUTLS
> #include <gnutls/gnutls.h>
> #include <gnutls/crypto.h>
> #include <gnutls/x509.h>
> @@ -25,6 +27,109 @@ static const int qcrypto_to_gnutls_hash_alg_map[QCRYPTO_HASH_ALGO__MAX] = {
> [QCRYPTO_HASH_ALGO_RIPEMD160] = GNUTLS_DIG_RMD160,
> };
>
> +static const int qcrypto_to_gnutls_keyid_flags_map[QCRYPTO_KEYID_FLAGS__MAX] = {
> + [QCRYPTO_KEYID_FLAGS_SHA1] = GNUTLS_KEYID_USE_SHA1,
> + [QCRYPTO_KEYID_FLAGS_SHA256] = GNUTLS_KEYID_USE_SHA256,
> + [QCRYPTO_KEYID_FLAGS_SHA512] = GNUTLS_KEYID_USE_SHA512,
> + [QCRYPTO_KEYID_FLAGS_BEST_KNOWN] = GNUTLS_KEYID_USE_BEST_KNOWN,
> +};
> +
> +static const int qcrypto_to_gnutls_cert_fmt_map[QCRYPTO_CERT_FMT__MAX] = {
> + [QCRYPTO_CERT_FMT_DER] = GNUTLS_X509_FMT_DER,
> + [QCRYPTO_CERT_FMT_PEM] = GNUTLS_X509_FMT_PEM,
> +};
> +
> +static const int gnutls_to_qcrypto_sig_alg_map[QCRYPTO_SIG_ALGO__MAX] = {
> + [GNUTLS_SIGN_UNKNOWN] = QCRYPTO_SIG_ALGO_UNKNOWN,
> + [GNUTLS_SIGN_RSA_SHA1] = QCRYPTO_SIG_ALGO_RSA_SHA1,
> + [GNUTLS_SIGN_RSA_SHA] = QCRYPTO_SIG_ALGO_RSA_SHA1,
> + [GNUTLS_SIGN_DSA_SHA1] = QCRYPTO_SIG_ALGO_DSA_SHA1,
> + [GNUTLS_SIGN_RSA_MD5] = QCRYPTO_SIG_ALGO_RSA_MD5,
> + [GNUTLS_SIGN_RSA_MD2] = QCRYPTO_SIG_ALGO_RSA_MD2,
> + [GNUTLS_SIGN_RSA_RMD160] = QCRYPTO_SIG_ALGO_RSA_RMD160,
> + [GNUTLS_SIGN_RSA_SHA256] = QCRYPTO_SIG_ALGO_RSA_SHA256,
> + [GNUTLS_SIGN_RSA_SHA384] = QCRYPTO_SIG_ALGO_RSA_SHA384,
> + [GNUTLS_SIGN_RSA_SHA512] = QCRYPTO_SIG_ALGO_RSA_SHA512,
> + [GNUTLS_SIGN_RSA_SHA224] = QCRYPTO_SIG_ALGO_RSA_SHA224,
> + [GNUTLS_SIGN_DSA_SHA224] = QCRYPTO_SIG_ALGO_DSA_SHA224,
> + [GNUTLS_SIGN_DSA_SHA256] = QCRYPTO_SIG_ALGO_DSA_SHA256,
> + [GNUTLS_SIGN_ECDSA_SHA1] = QCRYPTO_SIG_ALGO_ECDSA_SHA1,
> + [GNUTLS_SIGN_ECDSA_SHA224] = QCRYPTO_SIG_ALGO_ECDSA_SHA224,
> + [GNUTLS_SIGN_ECDSA_SHA256] = QCRYPTO_SIG_ALGO_ECDSA_SHA256,
> + [GNUTLS_SIGN_ECDSA_SHA384] = QCRYPTO_SIG_ALGO_ECDSA_SHA384,
> + [GNUTLS_SIGN_ECDSA_SHA512] = QCRYPTO_SIG_ALGO_ECDSA_SHA512,
> +};
> +
> +int qcrypto_check_x509_cert_fmt(uint8_t *cert, size_t size,
> + QCryptoCertFmt fmt, Error **errp)
> +{
> + int rc;
> + int ret = -1;
> + gnutls_x509_crt_t crt;
> + gnutls_datum_t datum = {.data = cert, .size = size};
> +
> + if (fmt >= G_N_ELEMENTS(qcrypto_to_gnutls_cert_fmt_map)) {
> + error_setg(errp, "Unknown certificate format");
Always include the actual invalid value in messages like this
error_setg(errp, "Unknown certificate format %d", fmt);
> + return ret;
> + }
> +
> + if (gnutls_x509_crt_init(&crt) < 0) {
> + error_setg(errp, "Failed to initialize certificate");
Include the real error message please
rc = gnutls_x509_crt_init(&crt);
if (rc < 0) {
error_setg(errp, "Failed to initialize certificate: %s",
gnutls_strerror(rc));
}
> + return ret;
> + }
> +
> + rc = gnutls_x509_crt_import(crt, &datum, qcrypto_to_gnutls_cert_fmt_map[fmt]);
> + if (rc == GNUTLS_E_ASN1_TAG_ERROR) {
> + goto cleanup;
This is jumping to the error cleanup path without filling 'errp'.
> + }
> +
> + ret = 0;
> +
> +cleanup:
> + gnutls_x509_crt_deinit(crt);
> + return ret;
> +}
> +
> +int qcrypto_get_x509_hash_len(QCryptoHashAlgo alg)
> +{
> + if (alg >= G_N_ELEMENTS(qcrypto_to_gnutls_hash_alg_map)) {
> + return 0;
> + }
> +
> + return gnutls_hash_get_len(qcrypto_to_gnutls_hash_alg_map[alg]);
> +}
This is just reinventing the qcrypto_hash_digest_len method,
drop it please.
> +int qcrypto_get_x509_keyid_len(QCryptoKeyidFlags flag)
> +{
> + QCryptoHashAlgo alg;
> +
> + if (flag >= G_N_ELEMENTS(qcrypto_to_gnutls_keyid_flags_map)) {
> + return 0;
> + }
> +
> + alg = QCRYPTO_HASH_ALGO_SHA1;
> + if ((flag & qcrypto_to_gnutls_keyid_flags_map[QCRYPTO_KEYID_FLAGS_SHA512]) ||
> + (flag & qcrypto_to_gnutls_keyid_flags_map[QCRYPTO_KEYID_FLAGS_BEST_KNOWN])) {
What is this comparison wanting to do ?
'flag' is declared as being a member of the QCryptoKeyidFlags enum
The code is checking 'flag' is in range for the bounds
of the qcrypto_to_gnutls_keyid_flags_map array, but then
'flag' is never used as an index for that array.
then the code takes the result of "qcrypto_to_gnutls_keyid_flags_map[QCRYPTO_KEYID_FLAGS_SHA512]"
which is a GNUTLS keyid constant, and compares it to 'flag' which is
a QCryptoKeyidFlags value.
This makes no sense at all.
> + alg = QCRYPTO_HASH_ALGO_SHA512;
> + } else if (flag & qcrypto_to_gnutls_keyid_flags_map[QCRYPTO_KEYID_FLAGS_SHA256]) {
> + alg = QCRYPTO_HASH_ALGO_SHA256;
> + }
> +
> + return qcrypto_get_x509_hash_len(alg);
> +}
This indirection via 'alg' is pointless when we have constants
for hash sizes already
if ((flag & qcrypto_to_gnutls_keyid_flags_map[QCRYPTO_KEYID_FLAGS_SHA512]) ||
(flag & qcrypto_to_gnutls_keyid_flags_map[QCRYPTO_KEYID_FLAGS_BEST_KNOWN])) {
return QCRYPTO_HASH_DIGEST_LEN_SHA512;
} else if (flag & qcrypto_to_gnutls_keyid_flags_map[QCRYPTO_KEYID_FLAGS_SHA256]) {
return QCRYPTO_HASH_DIGEST_LEN_SHA256;
} else {
return QCRYPTO_HASH_DIGEST_LEN_SHA1;
}
> +
> +static int qcrypto_import_x509_cert(gnutls_x509_crt_t crt, gnutls_datum_t *datum)
> +{
> + int rc;
> +
> + rc = gnutls_x509_crt_import(crt, datum, GNUTLS_X509_FMT_PEM);
> + if (rc) {
> + rc = gnutls_x509_crt_import(crt, datum, GNUTLS_X509_FMT_DER);
> + }
Must report the gnutls_strerror into an "errp" parameter.
> +
> + return rc;
> +}
> +
> int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
> QCryptoHashAlgo alg,
> uint8_t *result,
> @@ -74,3 +179,64 @@ int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
> gnutls_x509_crt_deinit(crt);
> return ret;
> }
> +
> +int qcrypto_get_x509_signature_algorithm(uint8_t *cert, size_t size, Error **errp)
> +{
> + int rc = -1;
> + gnutls_x509_crt_t crt;
> + gnutls_datum_t datum = {.data = cert, .size = size};
> +
> + if (gnutls_x509_crt_init(&crt) < 0) {
> + error_setg(errp, "Failed to initialize certificate");
This must include the gnutls_sterrror result
> + return rc;
> + }
> +
> + if (qcrypto_import_x509_cert(crt, &datum) != 0) {
'errp' must be passed into this so it can report an accurate message...
> + error_setg(errp, "Failed to import certificate");
..as this is useless.
> + goto cleanup;
> + }
> +
> + rc = gnutls_x509_crt_get_signature_algorithm(crt);
This is not handling errors.
> + rc = gnutls_to_qcrypto_sig_alg_map[rc];
This is not bounds checking the value.
Also it is bad practice to re-use the same variable for
two different purposes. Use 'ret' for tracking the overall
return status of this functino, and use 'rc' for things
this function calls.
> +
> +cleanup:
> + gnutls_x509_crt_deinit(crt);
> + return rc;
> +}
> +
> +#else /* ! CONFIG_GNUTLS */
> +
> +int qcrypto_check_x509_cert_fmt(uint8_t *cert, size_t size,
> + QCryptoCertFmt fmt, Error **errp)
> +{
> + error_setg(errp, "GNUTLS is required to get certificate format");
> + return -ENOTSUP;
No returning of errnos in crypto APIs please, only
the 'errp' parameter and 'return -1'.
> +}
> +
> +int qcrypto_get_x509_hash_len(QCryptoHashAlgo alg)
> +{
> + return -ENOTSUP;
> +}
> +
> +int qcrypto_get_x509_keyid_len(QCryptoKeyidFlags flag)
> +{
> + return -ENOTSUP;
> +}
> +
> +int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
> + QCryptoHashAlgo hash,
> + uint8_t *result,
> + size_t *resultlen,
> + Error **errp)
> +{
> + error_setg(errp, "GNUTLS is required to get fingerprint");
> + return -ENOTSUP;
> +}
> +
> +int qcrypto_get_x509_signature_algorithm(uint8_t *cert, size_t size, Error **errp)
> +{
> + error_setg(errp, "GNUTLS is required to get signature algorithm");
> + return -ENOTSUP;
> +}
> +
> +#endif /* ! CONFIG_GNUTLS */
With regards,
Daniel
--
|: https://berrange.com -o- https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org -o- https://fstop138.berrange.com :|
|: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|
^ permalink raw reply [flat|nested] 51+ messages in thread* Re: [PATCH v3 02/28] crypto/x509-utils: Add helper functions for certificate store
2025-06-04 21:56 ` [PATCH v3 02/28] crypto/x509-utils: Add helper functions for certificate store Zhuoying Cai
2025-06-06 10:03 ` Daniel P. Berrangé
@ 2025-06-06 10:14 ` Daniel P. Berrangé
2025-06-17 10:58 ` Markus Armbruster
2 siblings, 0 replies; 51+ messages in thread
From: Daniel P. Berrangé @ 2025-06-06 10:14 UTC (permalink / raw)
To: Zhuoying Cai
Cc: thuth, richard.henderson, david, pbonzini, walling, jjherne,
jrossi, pasic, borntraeger, farman, iii, eblake, armbru,
qemu-s390x, qemu-devel
On Wed, Jun 04, 2025 at 05:56:30PM -0400, Zhuoying Cai wrote:
> Add helper functions for x509 certificate which will be used in the next
> patch for the certificate store.
>
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
> crypto/meson.build | 5 +-
> crypto/x509-utils.c | 166 ++++++++++++++++++++++++++++++++++++
> include/crypto/x509-utils.h | 54 ++++++++++++
> qapi/crypto.json | 80 +++++++++++++++++
> 4 files changed, 301 insertions(+), 4 deletions(-)
> +int qcrypto_check_x509_cert_fmt(uint8_t *cert, size_t size,
> + QCryptoCertFmt fmt, Error **errp)
> +{
> + int rc;
> + int ret = -1;
> + gnutls_x509_crt_t crt;
> + gnutls_datum_t datum = {.data = cert, .size = size};
> +
> + if (fmt >= G_N_ELEMENTS(qcrypto_to_gnutls_cert_fmt_map)) {
> + error_setg(errp, "Unknown certificate format");
> + return ret;
> + }
> +
> + if (gnutls_x509_crt_init(&crt) < 0) {
> + error_setg(errp, "Failed to initialize certificate");
> + return ret;
> + }
> +
> + rc = gnutls_x509_crt_import(crt, &datum, qcrypto_to_gnutls_cert_fmt_map[fmt]);
> + if (rc == GNUTLS_E_ASN1_TAG_ERROR) {
> + goto cleanup;
> + }
> +
> + ret = 0;
> +
> +cleanup:
> + gnutls_x509_crt_deinit(crt);
> + return ret;
> +}
On reflection I think this method should be removed entirely.
In terms of QEMU command line we should exclusively allow
certs in PEM format only. If we need DER format internally,
we can use gnutls to convert from PEM to DER.
> +
> +int qcrypto_get_x509_hash_len(QCryptoHashAlgo alg)
> +{
> + if (alg >= G_N_ELEMENTS(qcrypto_to_gnutls_hash_alg_map)) {
> + return 0;
> + }
> +
> + return gnutls_hash_get_len(qcrypto_to_gnutls_hash_alg_map[alg]);
> +}
> +
> +int qcrypto_get_x509_keyid_len(QCryptoKeyidFlags flag)
> +{
> + QCryptoHashAlgo alg;
> +
> + if (flag >= G_N_ELEMENTS(qcrypto_to_gnutls_keyid_flags_map)) {
> + return 0;
> + }
> +
> + alg = QCRYPTO_HASH_ALGO_SHA1;
> + if ((flag & qcrypto_to_gnutls_keyid_flags_map[QCRYPTO_KEYID_FLAGS_SHA512]) ||
> + (flag & qcrypto_to_gnutls_keyid_flags_map[QCRYPTO_KEYID_FLAGS_BEST_KNOWN])) {
> + alg = QCRYPTO_HASH_ALGO_SHA512;
> + } else if (flag & qcrypto_to_gnutls_keyid_flags_map[QCRYPTO_KEYID_FLAGS_SHA256]) {
> + alg = QCRYPTO_HASH_ALGO_SHA256;
> + }
> +
> + return qcrypto_get_x509_hash_len(alg);
> +}
> +
> +static int qcrypto_import_x509_cert(gnutls_x509_crt_t crt, gnutls_datum_t *datum)
> +{
> + int rc;
> +
> + rc = gnutls_x509_crt_import(crt, datum, GNUTLS_X509_FMT_PEM);
> + if (rc) {
> + rc = gnutls_x509_crt_import(crt, datum, GNUTLS_X509_FMT_DER);
> + }
> +
> + return rc;
> +}
This method can go away too if we declare the public interface
is exclusively PEM.
> +
> +##
> +# @QCryptoCertFmt:
> +#
> +# The supported certificate encoding formats
> +#
> +# @der: DER
> +#
> +# @pem: PEM
> +#
> +# Since: 10.1
> +##
> +{ 'enum': 'QCryptoCertFmt',
> + 'data': ['der', 'pem']}
This can go away too if we declare we only use PEM.
With regards,
Daniel
--
|: https://berrange.com -o- https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org -o- https://fstop138.berrange.com :|
|: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|
^ permalink raw reply [flat|nested] 51+ messages in thread* Re: [PATCH v3 02/28] crypto/x509-utils: Add helper functions for certificate store
2025-06-04 21:56 ` [PATCH v3 02/28] crypto/x509-utils: Add helper functions for certificate store Zhuoying Cai
2025-06-06 10:03 ` Daniel P. Berrangé
2025-06-06 10:14 ` Daniel P. Berrangé
@ 2025-06-17 10:58 ` Markus Armbruster
2025-06-17 14:57 ` Zhuoying Cai
2 siblings, 1 reply; 51+ messages in thread
From: Markus Armbruster @ 2025-06-17 10:58 UTC (permalink / raw)
To: Zhuoying Cai
Cc: thuth, berrange, richard.henderson, david, pbonzini, walling,
jjherne, jrossi, pasic, borntraeger, farman, iii, eblake,
qemu-s390x, qemu-devel
Zhuoying Cai <zycai@linux.ibm.com> writes:
> Add helper functions for x509 certificate which will be used in the next
> patch for the certificate store.
>
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
[...]
> diff --git a/qapi/crypto.json b/qapi/crypto.json
> index c9d967d782..af487dcecd 100644
> --- a/qapi/crypto.json
> +++ b/qapi/crypto.json
> @@ -612,3 +612,83 @@
> 'base': { 'alg': 'QCryptoAkCipherAlgo' },
> 'discriminator': 'alg',
> 'data': { 'rsa': 'QCryptoAkCipherOptionsRSA' }}
> +
> +##
> +# @QCryptoKeyidFlags:
> +#
> +# The supported flags for the key ID
> +#
> +# @sha1: SHA-1
> +#
> +# @sha256: SHA-256
> +#
> +# @sha512: SHA-512
> +#
> +# @best-known: BEST-KNOWN
> +#
> +# Since: 10.1
> +##
> +{ 'enum': 'QCryptoKeyidFlags',
> + 'data': ['sha1', 'sha256', 'sha512', 'best-known']}
> +
> +##
> +# @QCryptoCertFmt:
For better or worse, we tend not to abbreviate things in QAPI schema
names: QCryptoCertFormat.
> +#
> +# The supported certificate encoding formats
> +#
> +# @der: DER
> +#
> +# @pem: PEM
> +#
> +# Since: 10.1
> +##
> +{ 'enum': 'QCryptoCertFmt',
> + 'data': ['der', 'pem']}
> +
> +##
> +# @QCryptoSigAlgo:
> +#
> +# Algorithms for digital signature
> +#
> +# @unknown: UNKNOWN
> +#
> +# @rsa-sha1: RSA-SHA1 or RSA-SHA
> +#
> +# @dsa-sha1: DSA-SHA1 or DSA-SHA
> +#
> +# @rsa-md5: RSA-MD5
> +#
> +# @rsa-md2: RSA-MD2
> +#
> +# @rsa-rmd160: RSA-RMD160
> +#
> +# @rsa-sha256: RSA-SHA256
> +#
> +# @rsa-sha384: RSA-SHA384
> +#
> +# @rsa-sha512: RSA-SHA512
> +#
> +# @rsa-sha224: RSA-SHA224
> +#
> +# @dsa-sha224: DSA-SHA224
> +#
> +# @dsa-sha256: DSA-SHA256
> +#
> +# @ecdsa-sha1: ECDSA-SHA1
> +#
> +# @ecdsa-sha224: ECDSA-SHA224
> +#
> +# @ecdsa-sha256: ECDSA-SHA256
> +#
> +# @ecdsa-sha384: ECDSA-SHA384
> +#
> +# @ecdsa-sha512: ECDSA-SHA512
> +#
> +# Since: 10.1
> +##
> +{ 'enum': 'QCryptoSigAlgo',
> + 'data': ['unknown', 'rsa-sha1', 'dsa-sha1',
> + 'rsa-md5', 'rsa-md2', 'rsa-rmd160',
> + 'rsa-sha256', 'rsa-sha384', 'rsa-sha512', 'rsa-sha224',
> + 'dsa-sha224', 'dsa-sha256',
> + 'ecdsa-sha1', 'ecdsa-sha224', 'ecdsa-sha256', 'ecdsa-sha384', 'ecdsa-sha512']}
Ignorant question: why are these QAPI enums?
If they need to be QAPI enums, then I'll have some requests on the doc
comments.
^ permalink raw reply [flat|nested] 51+ messages in thread* Re: [PATCH v3 02/28] crypto/x509-utils: Add helper functions for certificate store
2025-06-17 10:58 ` Markus Armbruster
@ 2025-06-17 14:57 ` Zhuoying Cai
2025-06-18 5:57 ` Markus Armbruster
0 siblings, 1 reply; 51+ messages in thread
From: Zhuoying Cai @ 2025-06-17 14:57 UTC (permalink / raw)
To: Markus Armbruster
Cc: thuth, berrange, richard.henderson, david, pbonzini, walling,
jjherne, jrossi, pasic, borntraeger, farman, iii, eblake,
qemu-s390x, qemu-devel
On 6/17/25 6:58 AM, Markus Armbruster wrote:
> Zhuoying Cai <zycai@linux.ibm.com> writes:
>
>> Add helper functions for x509 certificate which will be used in the next
>> patch for the certificate store.
>>
>> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
>
> [...]
>
>> diff --git a/qapi/crypto.json b/qapi/crypto.json
>> index c9d967d782..af487dcecd 100644
>> --- a/qapi/crypto.json
>> +++ b/qapi/crypto.json
>> @@ -612,3 +612,83 @@
>> 'base': { 'alg': 'QCryptoAkCipherAlgo' },
>> 'discriminator': 'alg',
>> 'data': { 'rsa': 'QCryptoAkCipherOptionsRSA' }}
>> +
>> +##
>> +# @QCryptoKeyidFlags:
>> +#
>> +# The supported flags for the key ID
>> +#
>> +# @sha1: SHA-1
>> +#
>> +# @sha256: SHA-256
>> +#
>> +# @sha512: SHA-512
>> +#
>> +# @best-known: BEST-KNOWN
>> +#
>> +# Since: 10.1
>> +##
>> +{ 'enum': 'QCryptoKeyidFlags',
>> + 'data': ['sha1', 'sha256', 'sha512', 'best-known']}
>> +
>> +##
>> +# @QCryptoCertFmt:
>
> For better or worse, we tend not to abbreviate things in QAPI schema
> names: QCryptoCertFormat.
>
>> +#
>> +# The supported certificate encoding formats
>> +#
>> +# @der: DER
>> +#
>> +# @pem: PEM
>> +#
>> +# Since: 10.1
>> +##
>> +{ 'enum': 'QCryptoCertFmt',
>> + 'data': ['der', 'pem']}
>> +
>> +##
>> +# @QCryptoSigAlgo:
>> +#
>> +# Algorithms for digital signature
>> +#
>> +# @unknown: UNKNOWN
>> +#
>> +# @rsa-sha1: RSA-SHA1 or RSA-SHA
>> +#
>> +# @dsa-sha1: DSA-SHA1 or DSA-SHA
>> +#
>> +# @rsa-md5: RSA-MD5
>> +#
>> +# @rsa-md2: RSA-MD2
>> +#
>> +# @rsa-rmd160: RSA-RMD160
>> +#
>> +# @rsa-sha256: RSA-SHA256
>> +#
>> +# @rsa-sha384: RSA-SHA384
>> +#
>> +# @rsa-sha512: RSA-SHA512
>> +#
>> +# @rsa-sha224: RSA-SHA224
>> +#
>> +# @dsa-sha224: DSA-SHA224
>> +#
>> +# @dsa-sha256: DSA-SHA256
>> +#
>> +# @ecdsa-sha1: ECDSA-SHA1
>> +#
>> +# @ecdsa-sha224: ECDSA-SHA224
>> +#
>> +# @ecdsa-sha256: ECDSA-SHA256
>> +#
>> +# @ecdsa-sha384: ECDSA-SHA384
>> +#
>> +# @ecdsa-sha512: ECDSA-SHA512
>> +#
>> +# Since: 10.1
>> +##
>> +{ 'enum': 'QCryptoSigAlgo',
>> + 'data': ['unknown', 'rsa-sha1', 'dsa-sha1',
>> + 'rsa-md5', 'rsa-md2', 'rsa-rmd160',
>> + 'rsa-sha256', 'rsa-sha384', 'rsa-sha512', 'rsa-sha224',
>> + 'dsa-sha224', 'dsa-sha256',
>> + 'ecdsa-sha1', 'ecdsa-sha224', 'ecdsa-sha256', 'ecdsa-sha384', 'ecdsa-sha512']}
>
> Ignorant question: why are these QAPI enums?
>
> If they need to be QAPI enums, then I'll have some requests on the doc
> comments.
>
Hi, thanks for the feedback.
The helper functions in x509-utils.c either take QAPI enum values as
parameters or return them. These enums are used later within QEMU.
^ permalink raw reply [flat|nested] 51+ messages in thread* Re: [PATCH v3 02/28] crypto/x509-utils: Add helper functions for certificate store
2025-06-17 14:57 ` Zhuoying Cai
@ 2025-06-18 5:57 ` Markus Armbruster
2025-06-18 15:34 ` Zhuoying Cai
0 siblings, 1 reply; 51+ messages in thread
From: Markus Armbruster @ 2025-06-18 5:57 UTC (permalink / raw)
To: Zhuoying Cai
Cc: thuth, berrange, richard.henderson, david, pbonzini, walling,
jjherne, jrossi, pasic, borntraeger, farman, iii, eblake,
qemu-s390x, qemu-devel
Zhuoying Cai <zycai@linux.ibm.com> writes:
> On 6/17/25 6:58 AM, Markus Armbruster wrote:
>> Zhuoying Cai <zycai@linux.ibm.com> writes:
>>
>>> Add helper functions for x509 certificate which will be used in the next
>>> patch for the certificate store.
>>>
>>> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
[...]
>> Ignorant question: why are these QAPI enums?
>>
>> If they need to be QAPI enums, then I'll have some requests on the doc
>> comments.
>>
>
> Hi, thanks for the feedback.
>
> The helper functions in x509-utils.c either take QAPI enum values as
> parameters or return them. These enums are used later within QEMU.
Let's look at the first one I found:
int qcrypto_check_x509_cert_fmt(uint8_t *cert, size_t size,
QCryptoCertFmt fmt, Error **errp)
{
int rc;
int ret = -1;
gnutls_x509_crt_t crt;
gnutls_datum_t datum = {.data = cert, .size = size};
if (fmt >= G_N_ELEMENTS(qcrypto_to_gnutls_cert_fmt_map)) {
error_setg(errp, "Unknown certificate format");
return ret;
}
if (gnutls_x509_crt_init(&crt) < 0) {
error_setg(errp, "Failed to initialize certificate");
return ret;
}
rc = gnutls_x509_crt_import(crt, &datum, qcrypto_to_gnutls_cert_fmt_map[fmt]);
if (rc == GNUTLS_E_ASN1_TAG_ERROR) {
goto cleanup;
}
ret = 0;
cleanup:
gnutls_x509_crt_deinit(crt);
return ret;
}
All it does with its @fmt argument is map it to the matching
GNUTLS_X509_FMT_*.
There's just one caller, init_cert_x509_der() in hw/s390x/cert-store.c:
is_der = qcrypto_check_x509_cert_fmt((uint8_t *)raw, size,
QCRYPTO_CERT_FMT_DER, &err);
QCRYPTO_CERT_FMT_DER gets mapped to GNUTLS_X509_FMT_DER. Why not pass
that directly? We don't need enum QCryptoCertFmt then.
If we need enum QCryptoCertFmt for some reason I can't see, why does it
have to be a QAPI type? Why not a plain C enum?
Similar questions for the other QAPI enums added in this series.
^ permalink raw reply [flat|nested] 51+ messages in thread* Re: [PATCH v3 02/28] crypto/x509-utils: Add helper functions for certificate store
2025-06-18 5:57 ` Markus Armbruster
@ 2025-06-18 15:34 ` Zhuoying Cai
2025-06-23 6:15 ` Markus Armbruster
0 siblings, 1 reply; 51+ messages in thread
From: Zhuoying Cai @ 2025-06-18 15:34 UTC (permalink / raw)
To: Markus Armbruster
Cc: thuth, berrange, richard.henderson, david, pbonzini, walling,
jjherne, jrossi, pasic, borntraeger, farman, iii, eblake,
qemu-s390x, qemu-devel
On 6/18/25 1:57 AM, Markus Armbruster wrote:
> Zhuoying Cai <zycai@linux.ibm.com> writes:
>
>> On 6/17/25 6:58 AM, Markus Armbruster wrote:
>>> Zhuoying Cai <zycai@linux.ibm.com> writes:
>>>
>>>> Add helper functions for x509 certificate which will be used in the next
>>>> patch for the certificate store.
>>>>
>>>> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
>
> [...]
>
>>> Ignorant question: why are these QAPI enums?
>>>
>>> If they need to be QAPI enums, then I'll have some requests on the doc
>>> comments.
>>>
>>
>> Hi, thanks for the feedback.
>>
>> The helper functions in x509-utils.c either take QAPI enum values as
>> parameters or return them. These enums are used later within QEMU.
>
> Let's look at the first one I found:
>
> int qcrypto_check_x509_cert_fmt(uint8_t *cert, size_t size,
> QCryptoCertFmt fmt, Error **errp)
> {
> int rc;
> int ret = -1;
> gnutls_x509_crt_t crt;
> gnutls_datum_t datum = {.data = cert, .size = size};
>
> if (fmt >= G_N_ELEMENTS(qcrypto_to_gnutls_cert_fmt_map)) {
> error_setg(errp, "Unknown certificate format");
> return ret;
> }
>
> if (gnutls_x509_crt_init(&crt) < 0) {
> error_setg(errp, "Failed to initialize certificate");
> return ret;
> }
>
> rc = gnutls_x509_crt_import(crt, &datum, qcrypto_to_gnutls_cert_fmt_map[fmt]);
> if (rc == GNUTLS_E_ASN1_TAG_ERROR) {
> goto cleanup;
> }
>
> ret = 0;
>
> cleanup:
> gnutls_x509_crt_deinit(crt);
> return ret;
> }
>
> All it does with its @fmt argument is map it to the matching
> GNUTLS_X509_FMT_*.
>
> There's just one caller, init_cert_x509_der() in hw/s390x/cert-store.c:
>
> is_der = qcrypto_check_x509_cert_fmt((uint8_t *)raw, size,
> QCRYPTO_CERT_FMT_DER, &err);
>
> QCRYPTO_CERT_FMT_DER gets mapped to GNUTLS_X509_FMT_DER. Why not pass
> that directly? We don't need enum QCryptoCertFmt then.
>
I received feedback on a previous patch series that directly using
GNUTLS in QEMU code is discouraged, except for under the crypto/
directory. Internal APIs should be defined to access GNUTLS
functionality instead.
> If we need enum QCryptoCertFmt for some reason I can't see, why does it
> have to be a QAPI type? Why not a plain C enum?
>
While implementing the new helper functions, I referred to
qcrypto_get_x509_cert_fingerprint() in crypto/x509-utils.c, which takes
QCryptoHashAlgo as a parameter. Following this, I added corresponding
QCRYPTO enums to map to GNUTLS enums.
If using plain C enums is preferred, I can update the code accordingly
in the next version.
> Similar questions for the other QAPI enums added in this series.
>
^ permalink raw reply [flat|nested] 51+ messages in thread* Re: [PATCH v3 02/28] crypto/x509-utils: Add helper functions for certificate store
2025-06-18 15:34 ` Zhuoying Cai
@ 2025-06-23 6:15 ` Markus Armbruster
2025-06-23 8:00 ` Daniel P. Berrangé
0 siblings, 1 reply; 51+ messages in thread
From: Markus Armbruster @ 2025-06-23 6:15 UTC (permalink / raw)
To: Zhuoying Cai
Cc: Markus Armbruster, thuth, berrange, richard.henderson, david,
pbonzini, walling, jjherne, jrossi, pasic, borntraeger, farman,
iii, eblake, qemu-s390x, qemu-devel
Zhuoying Cai <zycai@linux.ibm.com> writes:
> On 6/18/25 1:57 AM, Markus Armbruster wrote:
>> Zhuoying Cai <zycai@linux.ibm.com> writes:
>>
>>> On 6/17/25 6:58 AM, Markus Armbruster wrote:
>>>> Zhuoying Cai <zycai@linux.ibm.com> writes:
>>>>
>>>>> Add helper functions for x509 certificate which will be used in the next
>>>>> patch for the certificate store.
>>>>>
>>>>> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
>>
>> [...]
>>
>>>> Ignorant question: why are these QAPI enums?
>>>>
>>>> If they need to be QAPI enums, then I'll have some requests on the doc
>>>> comments.
>>>>
>>>
>>> Hi, thanks for the feedback.
>>>
>>> The helper functions in x509-utils.c either take QAPI enum values as
>>> parameters or return them. These enums are used later within QEMU.
>>
>> Let's look at the first one I found:
>>
>> int qcrypto_check_x509_cert_fmt(uint8_t *cert, size_t size,
>> QCryptoCertFmt fmt, Error **errp)
>> {
>> int rc;
>> int ret = -1;
>> gnutls_x509_crt_t crt;
>> gnutls_datum_t datum = {.data = cert, .size = size};
>>
>> if (fmt >= G_N_ELEMENTS(qcrypto_to_gnutls_cert_fmt_map)) {
>> error_setg(errp, "Unknown certificate format");
>> return ret;
>> }
>>
>> if (gnutls_x509_crt_init(&crt) < 0) {
>> error_setg(errp, "Failed to initialize certificate");
>> return ret;
>> }
>>
>> rc = gnutls_x509_crt_import(crt, &datum, qcrypto_to_gnutls_cert_fmt_map[fmt]);
>> if (rc == GNUTLS_E_ASN1_TAG_ERROR) {
>> goto cleanup;
>> }
>>
>> ret = 0;
>>
>> cleanup:
>> gnutls_x509_crt_deinit(crt);
>> return ret;
>> }
>>
>> All it does with its @fmt argument is map it to the matching
>> GNUTLS_X509_FMT_*.
>>
>> There's just one caller, init_cert_x509_der() in hw/s390x/cert-store.c:
>>
>> is_der = qcrypto_check_x509_cert_fmt((uint8_t *)raw, size,
>> QCRYPTO_CERT_FMT_DER, &err);
>>
>> QCRYPTO_CERT_FMT_DER gets mapped to GNUTLS_X509_FMT_DER. Why not pass
>> that directly? We don't need enum QCryptoCertFmt then.
>>
>
> I received feedback on a previous patch series that directly using
> GNUTLS in QEMU code is discouraged, except for under the crypto/
> directory. Internal APIs should be defined to access GNUTLS
> functionality instead.
>
>> If we need enum QCryptoCertFmt for some reason I can't see, why does it
>> have to be a QAPI type? Why not a plain C enum?
>
> While implementing the new helper functions, I referred to
> qcrypto_get_x509_cert_fingerprint() in crypto/x509-utils.c, which takes
> QCryptoHashAlgo as a parameter. Following this, I added corresponding
> QCRYPTO enums to map to GNUTLS enums.
>
> If using plain C enums is preferred, I can update the code accordingly
> in the next version.
Use plain C enums when practical.
Reasons for making a type a QAPI type include:
* Some QAPI command or event needs it.
* Something (typically QOM property accessors) needs the generated
visitor.
* For enums: something could use the generated QEnumLookup / ENUM_str()
macro.
>> Similar questions for the other QAPI enums added in this series.
^ permalink raw reply [flat|nested] 51+ messages in thread* Re: [PATCH v3 02/28] crypto/x509-utils: Add helper functions for certificate store
2025-06-23 6:15 ` Markus Armbruster
@ 2025-06-23 8:00 ` Daniel P. Berrangé
2025-06-23 9:22 ` Markus Armbruster
0 siblings, 1 reply; 51+ messages in thread
From: Daniel P. Berrangé @ 2025-06-23 8:00 UTC (permalink / raw)
To: Markus Armbruster
Cc: Zhuoying Cai, thuth, richard.henderson, david, pbonzini, walling,
jjherne, jrossi, pasic, borntraeger, farman, iii, eblake,
qemu-s390x, qemu-devel
On Mon, Jun 23, 2025 at 08:15:16AM +0200, Markus Armbruster wrote:
> Zhuoying Cai <zycai@linux.ibm.com> writes:
>
> > On 6/18/25 1:57 AM, Markus Armbruster wrote:
> >> Zhuoying Cai <zycai@linux.ibm.com> writes:
> >>
> >>> On 6/17/25 6:58 AM, Markus Armbruster wrote:
> >>>> Zhuoying Cai <zycai@linux.ibm.com> writes:
> >>>>
> >>>>> Add helper functions for x509 certificate which will be used in the next
> >>>>> patch for the certificate store.
> >>>>>
> >>>>> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> >>
> >> [...]
> >>
> >>>> Ignorant question: why are these QAPI enums?
> >>>>
> >>>> If they need to be QAPI enums, then I'll have some requests on the doc
> >>>> comments.
> >>>>
> >>>
> >>> Hi, thanks for the feedback.
> >>>
> >>> The helper functions in x509-utils.c either take QAPI enum values as
> >>> parameters or return them. These enums are used later within QEMU.
> >>
> >> Let's look at the first one I found:
> >>
> >> int qcrypto_check_x509_cert_fmt(uint8_t *cert, size_t size,
> >> QCryptoCertFmt fmt, Error **errp)
> >> {
> >> int rc;
> >> int ret = -1;
> >> gnutls_x509_crt_t crt;
> >> gnutls_datum_t datum = {.data = cert, .size = size};
> >>
> >> if (fmt >= G_N_ELEMENTS(qcrypto_to_gnutls_cert_fmt_map)) {
> >> error_setg(errp, "Unknown certificate format");
> >> return ret;
> >> }
> >>
> >> if (gnutls_x509_crt_init(&crt) < 0) {
> >> error_setg(errp, "Failed to initialize certificate");
> >> return ret;
> >> }
> >>
> >> rc = gnutls_x509_crt_import(crt, &datum, qcrypto_to_gnutls_cert_fmt_map[fmt]);
> >> if (rc == GNUTLS_E_ASN1_TAG_ERROR) {
> >> goto cleanup;
> >> }
> >>
> >> ret = 0;
> >>
> >> cleanup:
> >> gnutls_x509_crt_deinit(crt);
> >> return ret;
> >> }
> >>
> >> All it does with its @fmt argument is map it to the matching
> >> GNUTLS_X509_FMT_*.
> >>
> >> There's just one caller, init_cert_x509_der() in hw/s390x/cert-store.c:
> >>
> >> is_der = qcrypto_check_x509_cert_fmt((uint8_t *)raw, size,
> >> QCRYPTO_CERT_FMT_DER, &err);
> >>
> >> QCRYPTO_CERT_FMT_DER gets mapped to GNUTLS_X509_FMT_DER. Why not pass
> >> that directly? We don't need enum QCryptoCertFmt then.
> >>
> >
> > I received feedback on a previous patch series that directly using
> > GNUTLS in QEMU code is discouraged, except for under the crypto/
> > directory. Internal APIs should be defined to access GNUTLS
> > functionality instead.
> >
> >> If we need enum QCryptoCertFmt for some reason I can't see, why does it
> >> have to be a QAPI type? Why not a plain C enum?
> >
> > While implementing the new helper functions, I referred to
> > qcrypto_get_x509_cert_fingerprint() in crypto/x509-utils.c, which takes
> > QCryptoHashAlgo as a parameter. Following this, I added corresponding
> > QCRYPTO enums to map to GNUTLS enums.
> >
> > If using plain C enums is preferred, I can update the code accordingly
> > in the next version.
>
> Use plain C enums when practical.
>
> Reasons for making a type a QAPI type include:
>
> * Some QAPI command or event needs it.
>
> * Something (typically QOM property accessors) needs the generated
> visitor.
>
> * For enums: something could use the generated QEnumLookup / ENUM_str()
> macro.
Any time a method only accepts a subset of enum values, and needs to report
an error message, it should always use ENUM_str in the error message, rather
than the raw int value, to make the error message human friendly and invariant
to enum ordering. IOW, ENUM_str should be relatively frequently used/needed.
With regards,
Daniel
--
|: https://berrange.com -o- https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org -o- https://fstop138.berrange.com :|
|: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|
^ permalink raw reply [flat|nested] 51+ messages in thread* Re: [PATCH v3 02/28] crypto/x509-utils: Add helper functions for certificate store
2025-06-23 8:00 ` Daniel P. Berrangé
@ 2025-06-23 9:22 ` Markus Armbruster
0 siblings, 0 replies; 51+ messages in thread
From: Markus Armbruster @ 2025-06-23 9:22 UTC (permalink / raw)
To: Daniel P. Berrangé
Cc: Zhuoying Cai, thuth, richard.henderson, david, pbonzini, walling,
jjherne, jrossi, pasic, borntraeger, farman, iii, eblake,
qemu-s390x, qemu-devel
Daniel P. Berrangé <berrange@redhat.com> writes:
> On Mon, Jun 23, 2025 at 08:15:16AM +0200, Markus Armbruster wrote:
[...]
>> Use plain C enums when practical.
>>
>> Reasons for making a type a QAPI type include:
>>
>> * Some QAPI command or event needs it.
>>
>> * Something (typically QOM property accessors) needs the generated
>> visitor.
>>
>> * For enums: something could use the generated QEnumLookup / ENUM_str()
>> macro.
>
> Any time a method only accepts a subset of enum values, and needs to report
> an error message, it should always use ENUM_str in the error message, rather
> than the raw int value, to make the error message human friendly and invariant
> to enum ordering. IOW, ENUM_str should be relatively frequently used/needed.
Unless the enum exists at the user interface, such an error message is
almost certainly sub-par.
^ permalink raw reply [flat|nested] 51+ messages in thread
* [PATCH v3 03/28] hw/s390x/ipl: Create certificate store
2025-06-04 21:56 [PATCH v3 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
2025-06-04 21:56 ` [PATCH v3 01/28] Add boot-certificates to s390-ccw-virtio machine type option Zhuoying Cai
2025-06-04 21:56 ` [PATCH v3 02/28] crypto/x509-utils: Add helper functions for certificate store Zhuoying Cai
@ 2025-06-04 21:56 ` Zhuoying Cai
2025-06-06 10:31 ` Daniel P. Berrangé
2025-06-04 21:56 ` [PATCH v3 04/28] s390x: Guest support for Certificate Store Facility (CS) Zhuoying Cai
` (24 subsequent siblings)
27 siblings, 1 reply; 51+ messages in thread
From: Zhuoying Cai @ 2025-06-04 21:56 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, pbonzini
Cc: walling, jjherne, jrossi, pasic, borntraeger, farman, iii, eblake,
armbru, qemu-s390x, qemu-devel, zycai
Create a certificate store for boot certificates used for secure IPL.
Load certificates from the boot-certificate parameter of s390-ccw-virtio
machine type option into the cert store.
Currently, only x509 certificates in DER format and uses SHA-256 hashing
algorithm are supported, as these are the types required for secure boot
on s390.
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
hw/s390x/cert-store.c | 247 ++++++++++++++++++++++++++++++++++++
hw/s390x/cert-store.h | 39 ++++++
hw/s390x/ipl.c | 9 ++
hw/s390x/ipl.h | 3 +
hw/s390x/meson.build | 1 +
include/hw/s390x/ipl/qipl.h | 3 +
6 files changed, 302 insertions(+)
create mode 100644 hw/s390x/cert-store.c
create mode 100644 hw/s390x/cert-store.h
diff --git a/hw/s390x/cert-store.c b/hw/s390x/cert-store.c
new file mode 100644
index 0000000000..562fa22241
--- /dev/null
+++ b/hw/s390x/cert-store.c
@@ -0,0 +1,247 @@
+/*
+ * S390 certificate store implementation
+ *
+ * Copyright 2025 IBM Corp.
+ * Author(s): Zhuoying Cai <zycai@linux.ibm.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "cert-store.h"
+#include "qemu/error-report.h"
+#include "qemu/option.h"
+#include "qemu/config-file.h"
+#include "hw/s390x/ebcdic.h"
+#include "hw/s390x/s390-virtio-ccw.h"
+#include "qemu/cutils.h"
+#include "crypto/x509-utils.h"
+
+static const char *s390_get_boot_certificates(void)
+{
+ return S390_CCW_MACHINE(qdev_get_machine())->boot_certificates;
+}
+
+static size_t cert2buf(char *path, size_t max_size, char **cert_buf)
+{
+ size_t size;
+
+ /*
+ * maximum allowed size of the certificate file to avoid consuming excessive memory
+ * (malformed or maliciously large files)
+ */
+ if (!g_file_get_contents(path, cert_buf, &size, NULL) ||
+ size == 0 || size > max_size) {
+ return 0;
+ }
+
+ return size;
+}
+
+static S390IPLCertificate *init_cert_x509_der(size_t size, char *raw)
+{
+ S390IPLCertificate *q_cert = NULL;
+ int key_id_size;
+ int hash_size;
+ int is_der;
+ int hash_type;
+ Error *err = NULL;
+
+ is_der = qcrypto_check_x509_cert_fmt((uint8_t *)raw, size,
+ QCRYPTO_CERT_FMT_DER, &err);
+ /* return early if GNUTLS is not enabled */
+ if (is_der == -ENOTSUP) {
+ error_report("GNUTLS is not enabled");
+ return NULL;
+ }
+ if (is_der != 0) {
+ error_report("The certificate is not in DER format");
+ return NULL;
+ }
+
+ hash_type = qcrypto_get_x509_signature_algorithm((uint8_t *)raw, size, &err);
+ if (hash_type != QCRYPTO_SIG_ALGO_RSA_SHA256) {
+ error_report("The certificate does not use SHA-256 hashing");
+ return NULL;
+ }
+
+ key_id_size = qcrypto_get_x509_keyid_len(QCRYPTO_KEYID_FLAGS_SHA256);
+ if (key_id_size == 0) {
+ error_report("Failed to get certificate key ID size");
+ return NULL;
+ }
+
+ hash_size = qcrypto_get_x509_hash_len(QCRYPTO_HASH_ALGO_SHA256);
+ if (hash_size == 0) {
+ error_report("Failed to get certificate hash size");
+ return NULL;
+ }
+
+ q_cert = g_new(S390IPLCertificate, 1);
+ q_cert->size = size;
+ q_cert->key_id_size = key_id_size;
+ q_cert->hash_size = hash_size;
+ q_cert->raw = raw;
+ q_cert->format = QCRYPTO_CERT_FMT_DER;
+ q_cert->hash_type = QCRYPTO_SIG_ALGO_RSA_SHA256;
+
+ return q_cert;
+}
+
+static int check_path_type(const char *path)
+{
+ struct stat path_stat;
+
+ if (stat(path, &path_stat) != 0) {
+ perror("stat");
+ return -1;
+ }
+
+ if (S_ISDIR(path_stat.st_mode)) {
+ return S_IFDIR;
+ } else if (S_ISREG(path_stat.st_mode)) {
+ return S_IFREG;
+ } else {
+ return -1;
+ }
+}
+
+static S390IPLCertificate *init_cert(char *paths)
+{
+ char *buf;
+ char vc_name[VC_NAME_LEN_BYTES];
+ g_autofree gchar *filename;
+ size_t size;
+ S390IPLCertificate *qcert = NULL;
+
+ filename = g_path_get_basename(paths);
+
+ size = cert2buf(paths, CERT_MAX_SIZE, &buf);
+ if (size == 0) {
+ error_report("Failed to load certificate: %s", paths);
+ g_free(buf);
+ return NULL;
+ }
+
+ qcert = init_cert_x509_der(size, buf);
+ if (qcert == NULL) {
+ error_report("Failed to initialize certificate: %s", paths);
+ g_free(buf);
+ return NULL;
+ }
+
+ /*
+ * Left justified certificate name with padding on the right with blanks.
+ * Convert certificate name to EBCDIC.
+ */
+ strpadcpy(vc_name, VC_NAME_LEN_BYTES, filename, ' ');
+ ebcdic_put(qcert->vc_name, vc_name, VC_NAME_LEN_BYTES);
+
+ return qcert;
+}
+
+static void update_cert_store(S390IPLCertificateStore *cert_store,
+ S390IPLCertificate *qcert)
+{
+ size_t data_buf_size;
+ size_t keyid_buf_size;
+ size_t hash_buf_size;
+ size_t cert_buf_size;
+
+ /* length field is word aligned for later DIAG use */
+ keyid_buf_size = ROUND_UP(qcert->key_id_size, 4);
+ hash_buf_size = ROUND_UP(qcert->hash_size, 4);
+ cert_buf_size = ROUND_UP(qcert->size, 4);
+ data_buf_size = keyid_buf_size + hash_buf_size + cert_buf_size;
+
+ if (cert_store->max_cert_size < data_buf_size) {
+ cert_store->max_cert_size = data_buf_size;
+ }
+
+ cert_store->certs[cert_store->count] = *qcert;
+ cert_store->total_bytes += data_buf_size;
+ cert_store->count++;
+}
+
+static GPtrArray *get_cert_paths(void)
+{
+ const char *path;
+ gchar **paths;
+ gchar **paths_copy;
+ int path_type;
+ GDir *dir = NULL;
+ gchar *cert_path;
+ const gchar *filename;
+ GPtrArray *cert_path_builder;
+
+ cert_path_builder = g_ptr_array_new();
+
+ path = s390_get_boot_certificates();
+ if (path == NULL) {
+ return cert_path_builder;
+ }
+
+ paths = g_strsplit(path, ":", -1);
+ /* save the original pointer for freeing later */
+ paths_copy = paths;
+ while (*paths) {
+ /* skip empty certificate path */
+ if (!strcmp(*paths, "")) {
+ paths += 1;
+ continue;
+ }
+
+ cert_path = NULL;
+ path_type = check_path_type(*paths);
+ if (path_type == S_IFREG) {
+ cert_path = g_strdup(*paths);
+ g_ptr_array_add(cert_path_builder, cert_path);
+ } else if (path_type == S_IFDIR) {
+ dir = g_dir_open(*paths, 0, NULL);
+
+ if (dir) {
+ while ((filename = g_dir_read_name(dir))) {
+ cert_path = g_build_filename(*paths, filename, NULL);
+ g_ptr_array_add(cert_path_builder, (gpointer) cert_path);
+ }
+
+ g_dir_close(dir);
+ }
+ }
+
+ paths += 1;
+ }
+
+ g_strfreev(paths_copy);
+ return cert_path_builder;
+}
+
+void s390_ipl_create_cert_store(S390IPLCertificateStore *cert_store)
+{
+ GPtrArray *cert_path_builder;
+
+ cert_path_builder = get_cert_paths();
+ if (cert_path_builder->len == 0) {
+ g_ptr_array_free(cert_path_builder, true);
+ return;
+ }
+
+ cert_store->max_cert_size = 0;
+ cert_store->total_bytes = 0;
+
+ for (int i = 0; i < cert_path_builder->len; i++) {
+ if (i > MAX_CERTIFICATES - 1) {
+ printf("Warning: Maximum %d certificates are allowed,"
+ " ignoring certificate #%d and beyond\n",
+ MAX_CERTIFICATES, i + 1);
+ break;
+ }
+
+ S390IPLCertificate *qcert = init_cert((char *) cert_path_builder->pdata[i]);
+ if (qcert) {
+ update_cert_store(cert_store, qcert);
+ }
+ }
+
+ g_ptr_array_free(cert_path_builder, true);
+}
diff --git a/hw/s390x/cert-store.h b/hw/s390x/cert-store.h
new file mode 100644
index 0000000000..04acc6c2e0
--- /dev/null
+++ b/hw/s390x/cert-store.h
@@ -0,0 +1,39 @@
+/*
+ * S390 certificate store
+ *
+ * Copyright 2025 IBM Corp.
+ * Author(s): Zhuoying Cai <zycai@linux.ibm.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_S390_CERT_STORE_H
+#define HW_S390_CERT_STORE_H
+
+#include "hw/s390x/ipl/qipl.h"
+#include "crypto/x509-utils.h"
+
+#define VC_NAME_LEN_BYTES 64
+
+struct S390IPLCertificate {
+ uint8_t vc_name[VC_NAME_LEN_BYTES];
+ size_t size;
+ size_t key_id_size;
+ size_t hash_size;
+ char *raw;
+ QCryptoCertFmt format;
+ QCryptoSigAlgo hash_type;
+};
+typedef struct S390IPLCertificate S390IPLCertificate;
+
+struct S390IPLCertificateStore {
+ uint16_t count;
+ size_t max_cert_size;
+ size_t total_bytes;
+ S390IPLCertificate certs[MAX_CERTIFICATES];
+} QEMU_PACKED;
+typedef struct S390IPLCertificateStore S390IPLCertificateStore;
+
+void s390_ipl_create_cert_store(S390IPLCertificateStore *cert_store);
+
+#endif
diff --git a/hw/s390x/ipl.c b/hw/s390x/ipl.c
index 2f082396c7..186be923d7 100644
--- a/hw/s390x/ipl.c
+++ b/hw/s390x/ipl.c
@@ -35,6 +35,7 @@
#include "qemu/option.h"
#include "qemu/ctype.h"
#include "standard-headers/linux/virtio_ids.h"
+#include "cert-store.h"
#define KERN_IMAGE_START 0x010000UL
#define LINUX_MAGIC_ADDR 0x010008UL
@@ -422,6 +423,13 @@ void s390_ipl_convert_loadparm(char *ascii_lp, uint8_t *ebcdic_lp)
}
}
+S390IPLCertificateStore *s390_ipl_get_certificate_store(void)
+{
+ S390IPLState *ipl = get_ipl_device();
+
+ return &ipl->cert_store;
+}
+
static bool s390_build_iplb(DeviceState *dev_st, IplParameterBlock *iplb)
{
CcwDevice *ccw_dev = NULL;
@@ -717,6 +725,7 @@ void s390_ipl_prepare_cpu(S390CPU *cpu)
if (!ipl->kernel || ipl->iplb_valid) {
cpu->env.psw.addr = ipl->bios_start_addr;
+ s390_ipl_create_cert_store(&ipl->cert_store);
if (!ipl->iplb_valid) {
ipl->iplb_valid = s390_init_all_iplbs(ipl);
} else {
diff --git a/hw/s390x/ipl.h b/hw/s390x/ipl.h
index 8f83c7da29..bee72dfbb3 100644
--- a/hw/s390x/ipl.h
+++ b/hw/s390x/ipl.h
@@ -13,6 +13,7 @@
#ifndef HW_S390_IPL_H
#define HW_S390_IPL_H
+#include "cert-store.h"
#include "cpu.h"
#include "exec/target_page.h"
#include "system/address-spaces.h"
@@ -35,6 +36,7 @@ int s390_ipl_pv_unpack(struct S390PVResponse *pv_resp);
void s390_ipl_prepare_cpu(S390CPU *cpu);
IplParameterBlock *s390_ipl_get_iplb(void);
IplParameterBlock *s390_ipl_get_iplb_pv(void);
+S390IPLCertificateStore *s390_ipl_get_certificate_store(void);
enum s390_reset {
/* default is a reset not triggered by a CPU e.g. issued by QMP */
@@ -64,6 +66,7 @@ struct S390IPLState {
IplParameterBlock iplb;
IplParameterBlock iplb_pv;
QemuIplParameters qipl;
+ S390IPLCertificateStore cert_store;
uint64_t start_addr;
uint64_t compat_start_addr;
uint64_t bios_start_addr;
diff --git a/hw/s390x/meson.build b/hw/s390x/meson.build
index 11e4e78b85..5b02f47155 100644
--- a/hw/s390x/meson.build
+++ b/hw/s390x/meson.build
@@ -17,6 +17,7 @@ s390x_ss.add(files(
'sclpcpu.c',
'sclpquiesce.c',
'tod.c',
+ 'cert-store.c',
))
s390x_ss.add(when: 'CONFIG_KVM', if_true: files(
'tod-kvm.c',
diff --git a/include/hw/s390x/ipl/qipl.h b/include/hw/s390x/ipl/qipl.h
index 6824391111..2232b88049 100644
--- a/include/hw/s390x/ipl/qipl.h
+++ b/include/hw/s390x/ipl/qipl.h
@@ -20,6 +20,9 @@
#define LOADPARM_LEN 8
#define NO_LOADPARM "\0\0\0\0\0\0\0\0"
+#define MAX_CERTIFICATES 64
+#define CERT_MAX_SIZE (1024 * 8)
+
/*
* The QEMU IPL Parameters will be stored at absolute address
* 204 (0xcc) which means it is 32-bit word aligned but not
--
2.49.0
^ permalink raw reply related [flat|nested] 51+ messages in thread* Re: [PATCH v3 03/28] hw/s390x/ipl: Create certificate store
2025-06-04 21:56 ` [PATCH v3 03/28] hw/s390x/ipl: Create " Zhuoying Cai
@ 2025-06-06 10:31 ` Daniel P. Berrangé
2025-06-30 19:56 ` Zhuoying Cai
0 siblings, 1 reply; 51+ messages in thread
From: Daniel P. Berrangé @ 2025-06-06 10:31 UTC (permalink / raw)
To: Zhuoying Cai
Cc: thuth, richard.henderson, david, pbonzini, walling, jjherne,
jrossi, pasic, borntraeger, farman, iii, eblake, armbru,
qemu-s390x, qemu-devel
On Wed, Jun 04, 2025 at 05:56:31PM -0400, Zhuoying Cai wrote:
> Create a certificate store for boot certificates used for secure IPL.
>
> Load certificates from the boot-certificate parameter of s390-ccw-virtio
> machine type option into the cert store.
>
> Currently, only x509 certificates in DER format and uses SHA-256 hashing
> algorithm are supported, as these are the types required for secure boot
> on s390.
>
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
> hw/s390x/cert-store.c | 247 ++++++++++++++++++++++++++++++++++++
> hw/s390x/cert-store.h | 39 ++++++
> hw/s390x/ipl.c | 9 ++
> hw/s390x/ipl.h | 3 +
> hw/s390x/meson.build | 1 +
> include/hw/s390x/ipl/qipl.h | 3 +
> 6 files changed, 302 insertions(+)
> create mode 100644 hw/s390x/cert-store.c
> create mode 100644 hw/s390x/cert-store.h
>
> diff --git a/hw/s390x/cert-store.c b/hw/s390x/cert-store.c
> new file mode 100644
> index 0000000000..562fa22241
> --- /dev/null
> +++ b/hw/s390x/cert-store.c
> @@ -0,0 +1,247 @@
> +/*
> + * S390 certificate store implementation
> + *
> + * Copyright 2025 IBM Corp.
> + * Author(s): Zhuoying Cai <zycai@linux.ibm.com>
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "cert-store.h"
> +#include "qemu/error-report.h"
> +#include "qemu/option.h"
> +#include "qemu/config-file.h"
> +#include "hw/s390x/ebcdic.h"
> +#include "hw/s390x/s390-virtio-ccw.h"
> +#include "qemu/cutils.h"
> +#include "crypto/x509-utils.h"
> +
> +static const char *s390_get_boot_certificates(void)
> +{
> + return S390_CCW_MACHINE(qdev_get_machine())->boot_certificates;
> +}
> +
> +static size_t cert2buf(char *path, size_t max_size, char **cert_buf)
> +{
> + size_t size;
> +
> + /*
> + * maximum allowed size of the certificate file to avoid consuming excessive memory
> + * (malformed or maliciously large files)
> + */
> + if (!g_file_get_contents(path, cert_buf, &size, NULL) ||
> + size == 0 || size > max_size) {
By the time this 'size > max_size' check is performed, the file
is already loaded into memory, which is fairly pointless. In
existing code loading certs we don't enforce any max size. This
data comes from the host admin who QEMU has to assume is trusted,
so the size check is not required.
> + return 0;
> + }
> +
> + return size;
> +}
> +
> +static S390IPLCertificate *init_cert_x509_der(size_t size, char *raw)
> +{
> + S390IPLCertificate *q_cert = NULL;
> + int key_id_size;
> + int hash_size;
> + int is_der;
> + int hash_type;
> + Error *err = NULL;
> +
> + is_der = qcrypto_check_x509_cert_fmt((uint8_t *)raw, size,
> + QCRYPTO_CERT_FMT_DER, &err);
> + /* return early if GNUTLS is not enabled */
> + if (is_der == -ENOTSUP) {
> + error_report("GNUTLS is not enabled");
> + return NULL;
> + }
> + if (is_der != 0) {
> + error_report("The certificate is not in DER format");
> + return NULL;
> + }
As mentioned in the other patch, we should exclusively accept PEM
format as the public interface. If we need DER format internllay,
gnutls_x509_cert_export can convert it into DER for us.
> +
> + hash_type = qcrypto_get_x509_signature_algorithm((uint8_t *)raw, size, &err);
> + if (hash_type != QCRYPTO_SIG_ALGO_RSA_SHA256) {
> + error_report("The certificate does not use SHA-256 hashing");
> + return NULL;
> + }
> +
> + key_id_size = qcrypto_get_x509_keyid_len(QCRYPTO_KEYID_FLAGS_SHA256);
> + if (key_id_size == 0) {
> + error_report("Failed to get certificate key ID size");
> + return NULL;
> + }
> +
> + hash_size = qcrypto_get_x509_hash_len(QCRYPTO_HASH_ALGO_SHA256);
> + if (hash_size == 0) {
> + error_report("Failed to get certificate hash size");
> + return NULL;
> + }
Pointless method call when we have a QCRYPTO_HASH_DIGEST_LEN_SHA256
constant.
> +
> + q_cert = g_new(S390IPLCertificate, 1);
g_new0 to guarantee zero initialization of all fields.
> + q_cert->size = size;
> + q_cert->key_id_size = key_id_size;
> + q_cert->hash_size = hash_size;
> + q_cert->raw = raw;
> + q_cert->format = QCRYPTO_CERT_FMT_DER;
> + q_cert->hash_type = QCRYPTO_SIG_ALGO_RSA_SHA256;
> +
> + return q_cert;
> +}
> +
> +static int check_path_type(const char *path)
> +{
> + struct stat path_stat;
> +
> + if (stat(path, &path_stat) != 0) {
> + perror("stat");
> + return -1;
> + }
> +
> + if (S_ISDIR(path_stat.st_mode)) {
> + return S_IFDIR;
> + } else if (S_ISREG(path_stat.st_mode)) {
> + return S_IFREG;
> + } else {
> + return -1;
> + }
> +}
This helper isn't making the code any simpler - use the
stat() & S_ISXXX checks inline where needed.
> +static S390IPLCertificate *init_cert(char *paths)
s/paths/path/ as this is a single file being used.
> +{
> + char *buf;
> + char vc_name[VC_NAME_LEN_BYTES];
> + g_autofree gchar *filename;
All g_autofree variables *must* be initialized at time
of declaration.
> + size_t size;
> + S390IPLCertificate *qcert = NULL;
> +
> + filename = g_path_get_basename(paths);
> +
> + size = cert2buf(paths, CERT_MAX_SIZE, &buf);
> + if (size == 0) {
> + error_report("Failed to load certificate: %s", paths);
> + g_free(buf);
> + return NULL;
> + }
> +
> + qcert = init_cert_x509_der(size, buf);
> + if (qcert == NULL) {
> + error_report("Failed to initialize certificate: %s", paths);
> + g_free(buf);
> + return NULL;
> + }
> +
> + /*
> + * Left justified certificate name with padding on the right with blanks.
> + * Convert certificate name to EBCDIC.
> + */
> + strpadcpy(vc_name, VC_NAME_LEN_BYTES, filename, ' ');
What purpose does the 'vc_name' serve ? Are there expectations
on the user for naming of the cert fiels ?
> + ebcdic_put(qcert->vc_name, vc_name, VC_NAME_LEN_BYTES);
> +
> + return qcert;
> +}
> +
> +static void update_cert_store(S390IPLCertificateStore *cert_store,
> + S390IPLCertificate *qcert)
> +{
> + size_t data_buf_size;
> + size_t keyid_buf_size;
> + size_t hash_buf_size;
> + size_t cert_buf_size;
> +
> + /* length field is word aligned for later DIAG use */
> + keyid_buf_size = ROUND_UP(qcert->key_id_size, 4);
> + hash_buf_size = ROUND_UP(qcert->hash_size, 4);
> + cert_buf_size = ROUND_UP(qcert->size, 4);
> + data_buf_size = keyid_buf_size + hash_buf_size + cert_buf_size;
> +
> + if (cert_store->max_cert_size < data_buf_size) {
> + cert_store->max_cert_size = data_buf_size;
> + }
> +
> + cert_store->certs[cert_store->count] = *qcert;
> + cert_store->total_bytes += data_buf_size;
> + cert_store->count++;
> +}
> +
> +static GPtrArray *get_cert_paths(void)
> +{
> + const char *path;
> + gchar **paths;
g_auto(GStrv) paths = NULL;
to automatically free this
> + gchar **paths_copy;
and use this as the working variable
> + int path_type;
> + GDir *dir = NULL;
> + gchar *cert_path;
> + const gchar *filename;
> + GPtrArray *cert_path_builder;
> +
> + cert_path_builder = g_ptr_array_new();
g_autoptr(GPtrArray) cert_path_builder = g_ptr_array_new_fill(0, g_free)
so that this get freed automatically in error paths, along
with any paths stored within it.
> +
> + path = s390_get_boot_certificates();
> + if (path == NULL) {
> + return cert_path_builder;
> + }
Calling this variable 'path' is misleading given it is
a list of paths.
> +
> + paths = g_strsplit(path, ":", -1);
> + /* save the original pointer for freeing later */
> + paths_copy = paths;
> + while (*paths) {
> + /* skip empty certificate path */
> + if (!strcmp(*paths, "")) {
IMHO this should be reported as a fatal user configuration
error.
> + paths += 1;
> + continue;
> + }
> +
> + cert_path = NULL;
> + path_type = check_path_type(*paths);
Just call stat() directly here, and report a fatal error
if returns non-zero instead of ignoring the error
> + if (path_type == S_IFREG) {
> + cert_path = g_strdup(*paths);
> + g_ptr_array_add(cert_path_builder, cert_path);
Drop the intermediate cert_path variable as it
serves no purpose
> + } else if (path_type == S_IFDIR) {
> + dir = g_dir_open(*paths, 0, NULL);
Pass an "GError" object not NULL and treat failure as
fatal.
> +
> + if (dir) {
> + while ((filename = g_dir_read_name(dir))) {
> + cert_path = g_build_filename(*paths, filename, NULL);
> + g_ptr_array_add(cert_path_builder, (gpointer) cert_path);
> + }
The (gpointer) cast serves no purpose.
> +
> + g_dir_close(dir);
> + }
> + }
> +
> + paths += 1;
> + }
> +
> + g_strfreev(paths_copy);
Redundant when using g_auto
> + return cert_path_builder;
return g_steal_pointer(&cert_path_builder)
> +}
> +
> +void s390_ipl_create_cert_store(S390IPLCertificateStore *cert_store)
> +{
> + GPtrArray *cert_path_builder;
> +
> + cert_path_builder = get_cert_paths();
> + if (cert_path_builder->len == 0) {
> + g_ptr_array_free(cert_path_builder, true);
s/true/TRUE/ - GLib does not accept stdbool constants
> + return;
> + }
> +
> + cert_store->max_cert_size = 0;
> + cert_store->total_bytes = 0;
> +
> + for (int i = 0; i < cert_path_builder->len; i++) {
> + if (i > MAX_CERTIFICATES - 1) {
> + printf("Warning: Maximum %d certificates are allowed,"
> + " ignoring certificate #%d and beyond\n",
> + MAX_CERTIFICATES, i + 1);
Is this limit mandated by some s390x hardware design spec ?
If so, this should be a fatal error, not a warning.
If not, then it would be better to accept all the user
provided certs, no matter how many.
> + break;
> + }
> +
> + S390IPLCertificate *qcert = init_cert((char *) cert_path_builder->pdata[i]);
> + if (qcert) {
> + update_cert_store(cert_store, qcert);
> + }
> + }
> +
> + g_ptr_array_free(cert_path_builder, true);
> +}
With regards,
Daniel
--
|: https://berrange.com -o- https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org -o- https://fstop138.berrange.com :|
|: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|
^ permalink raw reply [flat|nested] 51+ messages in thread* Re: [PATCH v3 03/28] hw/s390x/ipl: Create certificate store
2025-06-06 10:31 ` Daniel P. Berrangé
@ 2025-06-30 19:56 ` Zhuoying Cai
0 siblings, 0 replies; 51+ messages in thread
From: Zhuoying Cai @ 2025-06-30 19:56 UTC (permalink / raw)
To: Daniel P. Berrangé
Cc: thuth, richard.henderson, david, pbonzini, walling, jjherne,
jrossi, pasic, borntraeger, farman, iii, eblake, armbru,
qemu-s390x, qemu-devel
On 6/6/25 6:31 AM, Daniel P. Berrangé wrote:
> On Wed, Jun 04, 2025 at 05:56:31PM -0400, Zhuoying Cai wrote:
>> Create a certificate store for boot certificates used for secure IPL.
>>
>> Load certificates from the boot-certificate parameter of s390-ccw-virtio
>> machine type option into the cert store.
>>
>> Currently, only x509 certificates in DER format and uses SHA-256 hashing
>> algorithm are supported, as these are the types required for secure boot
>> on s390.
>>
>> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
>> ---
>> hw/s390x/cert-store.c | 247 ++++++++++++++++++++++++++++++++++++
>> hw/s390x/cert-store.h | 39 ++++++
>> hw/s390x/ipl.c | 9 ++
>> hw/s390x/ipl.h | 3 +
>> hw/s390x/meson.build | 1 +
>> include/hw/s390x/ipl/qipl.h | 3 +
>> 6 files changed, 302 insertions(+)
>> create mode 100644 hw/s390x/cert-store.c
>> create mode 100644 hw/s390x/cert-store.h
>>
>> diff --git a/hw/s390x/cert-store.c b/hw/s390x/cert-store.c
>> new file mode 100644
>> index 0000000000..562fa22241
>> --- /dev/null
>> +++ b/hw/s390x/cert-store.c
>> @@ -0,0 +1,247 @@
>> +/*
>> + * S390 certificate store implementation
>> + *
>> + * Copyright 2025 IBM Corp.
>> + * Author(s): Zhuoying Cai <zycai@linux.ibm.com>
>> + *
>> + * SPDX-License-Identifier: GPL-2.0-or-later
>> + */
>> +
>> +#include "qemu/osdep.h"
>> +#include "cert-store.h"
>> +#include "qemu/error-report.h"
>> +#include "qemu/option.h"
>> +#include "qemu/config-file.h"
>> +#include "hw/s390x/ebcdic.h"
>> +#include "hw/s390x/s390-virtio-ccw.h"
>> +#include "qemu/cutils.h"
>> +#include "crypto/x509-utils.h"
>> +
>> +static const char *s390_get_boot_certificates(void)
>> +{
>> + return S390_CCW_MACHINE(qdev_get_machine())->boot_certificates;
>> +}
>> +
>> +static size_t cert2buf(char *path, size_t max_size, char **cert_buf)
>> +{
>> + size_t size;
>> +
>> + /*
>> + * maximum allowed size of the certificate file to avoid consuming excessive memory
>> + * (malformed or maliciously large files)
>> + */
>> + if (!g_file_get_contents(path, cert_buf, &size, NULL) ||
>> + size == 0 || size > max_size) {
>
> By the time this 'size > max_size' check is performed, the file
> is already loaded into memory, which is fairly pointless. In
> existing code loading certs we don't enforce any max size. This
> data comes from the host admin who QEMU has to assume is trusted,
> so the size check is not required.
>
>> + return 0;
>> + }
>> +
>> + return size;
>> +}
>> +
>> +static S390IPLCertificate *init_cert_x509_der(size_t size, char *raw)
>> +{
>> + S390IPLCertificate *q_cert = NULL;
>> + int key_id_size;
>> + int hash_size;
>> + int is_der;
>> + int hash_type;
>> + Error *err = NULL;
>> +
>> + is_der = qcrypto_check_x509_cert_fmt((uint8_t *)raw, size,
>> + QCRYPTO_CERT_FMT_DER, &err);
>> + /* return early if GNUTLS is not enabled */
>> + if (is_der == -ENOTSUP) {
>> + error_report("GNUTLS is not enabled");
>> + return NULL;
>> + }
>> + if (is_der != 0) {
>> + error_report("The certificate is not in DER format");
>> + return NULL;
>> + }
>
> As mentioned in the other patch, we should exclusively accept PEM
> format as the public interface. If we need DER format internllay,
> gnutls_x509_cert_export can convert it into DER for us.
>
>> +
>> + hash_type = qcrypto_get_x509_signature_algorithm((uint8_t *)raw, size, &err);
>> + if (hash_type != QCRYPTO_SIG_ALGO_RSA_SHA256) {
>> + error_report("The certificate does not use SHA-256 hashing");
>> + return NULL;
>> + }
>> +
>> + key_id_size = qcrypto_get_x509_keyid_len(QCRYPTO_KEYID_FLAGS_SHA256);
>> + if (key_id_size == 0) {
>> + error_report("Failed to get certificate key ID size");
>> + return NULL;
>> + }
>> +
>> + hash_size = qcrypto_get_x509_hash_len(QCRYPTO_HASH_ALGO_SHA256);
>> + if (hash_size == 0) {
>> + error_report("Failed to get certificate hash size");
>> + return NULL;
>> + }
>
> Pointless method call when we have a QCRYPTO_HASH_DIGEST_LEN_SHA256
> constant.
>
>> +
>> + q_cert = g_new(S390IPLCertificate, 1);
>
> g_new0 to guarantee zero initialization of all fields.
>
>> + q_cert->size = size;
>> + q_cert->key_id_size = key_id_size;
>> + q_cert->hash_size = hash_size;
>> + q_cert->raw = raw;
>> + q_cert->format = QCRYPTO_CERT_FMT_DER;
>> + q_cert->hash_type = QCRYPTO_SIG_ALGO_RSA_SHA256;
>> +
>> + return q_cert;
>> +}
>> +
>> +static int check_path_type(const char *path)
>> +{
>> + struct stat path_stat;
>> +
>> + if (stat(path, &path_stat) != 0) {
>> + perror("stat");
>> + return -1;
>> + }
>> +
>> + if (S_ISDIR(path_stat.st_mode)) {
>> + return S_IFDIR;
>> + } else if (S_ISREG(path_stat.st_mode)) {
>> + return S_IFREG;
>> + } else {
>> + return -1;
>> + }
>> +}
>
> This helper isn't making the code any simpler - use the
> stat() & S_ISXXX checks inline where needed.
>
>> +static S390IPLCertificate *init_cert(char *paths)
>
> s/paths/path/ as this is a single file being used.
>
>> +{
>> + char *buf;
>> + char vc_name[VC_NAME_LEN_BYTES];
>> + g_autofree gchar *filename;
>
> All g_autofree variables *must* be initialized at time
> of declaration.
>
>> + size_t size;
>> + S390IPLCertificate *qcert = NULL;
>> +
>> + filename = g_path_get_basename(paths);
>> +
>> + size = cert2buf(paths, CERT_MAX_SIZE, &buf);
>> + if (size == 0) {
>> + error_report("Failed to load certificate: %s", paths);
>> + g_free(buf);
>> + return NULL;
>> + }
>> +
>> + qcert = init_cert_x509_der(size, buf);
>> + if (qcert == NULL) {
>> + error_report("Failed to initialize certificate: %s", paths);
>> + g_free(buf);
>> + return NULL;
>> + }
>> +
>> + /*
>> + * Left justified certificate name with padding on the right with blanks.
>> + * Convert certificate name to EBCDIC.
>> + */
>> + strpadcpy(vc_name, VC_NAME_LEN_BYTES, filename, ' ');
>
> What purpose does the 'vc_name' serve ? Are there expectations
> on the user for naming of the cert fiels ?
>
vc_name refers to the name of the certificate, along with other
certificate metadata, which could be retrieved and displayed via the
DIAG320 invocation. This information is available to the Linux root user
through the cert_store keyring.
The certificate name is expected to consist of ASCII characters.
>> [snip..]
^ permalink raw reply [flat|nested] 51+ messages in thread
* [PATCH v3 04/28] s390x: Guest support for Certificate Store Facility (CS)
2025-06-04 21:56 [PATCH v3 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (2 preceding siblings ...)
2025-06-04 21:56 ` [PATCH v3 03/28] hw/s390x/ipl: Create " Zhuoying Cai
@ 2025-06-04 21:56 ` Zhuoying Cai
2025-06-04 21:56 ` [PATCH v3 05/28] s390x/diag: Introduce DIAG 320 for certificate store facility Zhuoying Cai
` (23 subsequent siblings)
27 siblings, 0 replies; 51+ messages in thread
From: Zhuoying Cai @ 2025-06-04 21:56 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, pbonzini
Cc: walling, jjherne, jrossi, pasic, borntraeger, farman, iii, eblake,
armbru, qemu-s390x, qemu-devel, zycai
DIAG 320 is supported when the certificate-store (CS) facility
is installed.
Availability of CS facility is determined by byte 134 bit 5 of the
SCLP Read Info block. Byte 134's facilities cannot be represented
without the availability of the extended-length-SCCB, so add it as
a check for consistency.
Note: secure IPL is not available for Secure Execution (SE) guests,
as their images are already integrity protected, and an additional
protection of the kernel by secure IPL is not necessary.
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
target/s390x/cpu_features.c | 1 +
target/s390x/cpu_features_def.h.inc | 1 +
target/s390x/cpu_models.c | 2 ++
target/s390x/gen-features.c | 3 +++
target/s390x/kvm/kvm.c | 2 ++
5 files changed, 9 insertions(+)
diff --git a/target/s390x/cpu_features.c b/target/s390x/cpu_features.c
index 4b5be6798e..99089ab3f5 100644
--- a/target/s390x/cpu_features.c
+++ b/target/s390x/cpu_features.c
@@ -147,6 +147,7 @@ void s390_fill_feat_block(const S390FeatBitmap features, S390FeatType type,
break;
case S390_FEAT_TYPE_SCLP_FAC134:
clear_be_bit(s390_feat_def(S390_FEAT_DIAG_318)->bit, data);
+ clear_be_bit(s390_feat_def(S390_FEAT_DIAG_320)->bit, data);
break;
default:
return;
diff --git a/target/s390x/cpu_features_def.h.inc b/target/s390x/cpu_features_def.h.inc
index e23e603a79..7b13a95d98 100644
--- a/target/s390x/cpu_features_def.h.inc
+++ b/target/s390x/cpu_features_def.h.inc
@@ -138,6 +138,7 @@ DEF_FEAT(SIE_IBS, "ibs", SCLP_CONF_CHAR_EXT, 10, "SIE: Interlock-and-broadcast-s
/* Features exposed via SCLP SCCB Facilities byte 134 (bit numbers relative to byte-134) */
DEF_FEAT(DIAG_318, "diag318", SCLP_FAC134, 0, "Control program name and version codes")
+DEF_FEAT(DIAG_320, "cstore", SCLP_FAC134, 5, "Provide Certificate Store functions")
/* Features exposed via SCLP CPU info. */
DEF_FEAT(SIE_F2, "sief2", SCLP_CPU, 4, "SIE: interception format 2 (Virtual SIE)")
diff --git a/target/s390x/cpu_models.c b/target/s390x/cpu_models.c
index 954a7a99a9..33ef5c190c 100644
--- a/target/s390x/cpu_models.c
+++ b/target/s390x/cpu_models.c
@@ -248,6 +248,7 @@ bool s390_has_feat(S390Feat feat)
if (s390_is_pv()) {
switch (feat) {
case S390_FEAT_DIAG_318:
+ case S390_FEAT_DIAG_320:
case S390_FEAT_HPMA2:
case S390_FEAT_SIE_F2:
case S390_FEAT_SIE_SKEY:
@@ -505,6 +506,7 @@ static void check_consistency(const S390CPUModel *model)
{ S390_FEAT_PTFF_STOUE, S390_FEAT_MULTIPLE_EPOCH },
{ S390_FEAT_AP_QUEUE_INTERRUPT_CONTROL, S390_FEAT_AP },
{ S390_FEAT_DIAG_318, S390_FEAT_EXTENDED_LENGTH_SCCB },
+ { S390_FEAT_DIAG_320, S390_FEAT_EXTENDED_LENGTH_SCCB },
{ S390_FEAT_NNPA, S390_FEAT_VECTOR },
{ S390_FEAT_RDP, S390_FEAT_LOCAL_TLB_CLEARING },
{ S390_FEAT_UV_FEAT_AP, S390_FEAT_AP },
diff --git a/target/s390x/gen-features.c b/target/s390x/gen-features.c
index a814ece82f..982d6122ac 100644
--- a/target/s390x/gen-features.c
+++ b/target/s390x/gen-features.c
@@ -720,6 +720,7 @@ static uint16_t full_GEN16_GA1[] = {
S390_FEAT_PAIE,
S390_FEAT_UV_FEAT_AP,
S390_FEAT_UV_FEAT_AP_INTR,
+ S390_FEAT_DIAG_320,
};
static uint16_t full_GEN17_GA1[] = {
@@ -922,6 +923,8 @@ static uint16_t qemu_MAX[] = {
S390_FEAT_KIMD_SHA_512,
S390_FEAT_KLMD_SHA_512,
S390_FEAT_PRNO_TRNG,
+ S390_FEAT_EXTENDED_LENGTH_SCCB,
+ S390_FEAT_DIAG_320,
};
/****** END FEATURE DEFS ******/
diff --git a/target/s390x/kvm/kvm.c b/target/s390x/kvm/kvm.c
index 2e02d2c4de..8f655a4b7f 100644
--- a/target/s390x/kvm/kvm.c
+++ b/target/s390x/kvm/kvm.c
@@ -2490,6 +2490,8 @@ bool kvm_s390_get_host_cpu_model(S390CPUModel *model, Error **errp)
set_bit(S390_FEAT_DIAG_318, model->features);
}
+ set_bit(S390_FEAT_DIAG_320, model->features);
+
/* Test for Ultravisor features that influence secure guest behavior */
query_uv_feat_guest(model->features);
--
2.49.0
^ permalink raw reply related [flat|nested] 51+ messages in thread* [PATCH v3 05/28] s390x/diag: Introduce DIAG 320 for certificate store facility
2025-06-04 21:56 [PATCH v3 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (3 preceding siblings ...)
2025-06-04 21:56 ` [PATCH v3 04/28] s390x: Guest support for Certificate Store Facility (CS) Zhuoying Cai
@ 2025-06-04 21:56 ` Zhuoying Cai
2025-06-04 21:56 ` [PATCH v3 06/28] s390x/diag: Refactor address validation check from diag308_parm_check Zhuoying Cai
` (22 subsequent siblings)
27 siblings, 0 replies; 51+ messages in thread
From: Zhuoying Cai @ 2025-06-04 21:56 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, pbonzini
Cc: walling, jjherne, jrossi, pasic, borntraeger, farman, iii, eblake,
armbru, qemu-s390x, qemu-devel, zycai
DIAGNOSE 320 is introduced to support certificate store facility,
which includes operations such as query certificate storage
information and provide certificates in the certificate store.
Currently, only subcode 0 is supported with this patch, which is
used to query a bitmap of which subcodes are supported.
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
include/hw/s390x/ipl/diag320.h | 17 ++++++++++++++
target/s390x/diag.c | 41 ++++++++++++++++++++++++++++++++++
target/s390x/kvm/kvm.c | 14 ++++++++++++
target/s390x/s390x-internal.h | 2 ++
target/s390x/tcg/misc_helper.c | 7 ++++++
5 files changed, 81 insertions(+)
create mode 100644 include/hw/s390x/ipl/diag320.h
diff --git a/include/hw/s390x/ipl/diag320.h b/include/hw/s390x/ipl/diag320.h
new file mode 100644
index 0000000000..713570545d
--- /dev/null
+++ b/include/hw/s390x/ipl/diag320.h
@@ -0,0 +1,17 @@
+/*
+ * S/390 DIAGNOSE 320 definitions and structures
+ *
+ * Copyright 2025 IBM Corp.
+ * Author(s): Zhuoying Cai <zycai@linux.ibm.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef S390X_DIAG320_H
+#define S390X_DIAG320_H
+
+#define DIAG_320_SUBC_QUERY_ISM 0
+
+#define DIAG_320_RC_OK 0x0001
+
+#endif
diff --git a/target/s390x/diag.c b/target/s390x/diag.c
index cff9fbc4b0..d33c5daf38 100644
--- a/target/s390x/diag.c
+++ b/target/s390x/diag.c
@@ -18,6 +18,7 @@
#include "hw/watchdog/wdt_diag288.h"
#include "system/cpus.h"
#include "hw/s390x/ipl.h"
+#include "hw/s390x/ipl/diag320.h"
#include "hw/s390x/s390-virtio-ccw.h"
#include "system/kvm.h"
#include "kvm/kvm_s390x.h"
@@ -191,3 +192,43 @@ out:
break;
}
}
+
+void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
+{
+ S390CPU *cpu = env_archcpu(env);
+ uint64_t subcode = env->regs[r3];
+ uint64_t addr = env->regs[r1];
+ int rc;
+
+ if (env->psw.mask & PSW_MASK_PSTATE) {
+ s390_program_interrupt(env, PGM_PRIVILEGED, ra);
+ return;
+ }
+
+ if (!s390_has_feat(S390_FEAT_DIAG_320)) {
+ s390_program_interrupt(env, PGM_SPECIFICATION, ra);
+ return;
+ }
+
+ if (r1 & 1) {
+ s390_program_interrupt(env, PGM_SPECIFICATION, ra);
+ return;
+ }
+
+ switch (subcode) {
+ case DIAG_320_SUBC_QUERY_ISM:
+ uint64_t ism = 0;
+
+ if (s390_cpu_virt_mem_write(cpu, addr, r1, &ism, sizeof(ism))) {
+ s390_cpu_virt_mem_handle_exc(cpu, ra);
+ return;
+ }
+
+ rc = DIAG_320_RC_OK;
+ break;
+ default:
+ s390_program_interrupt(env, PGM_SPECIFICATION, ra);
+ return;
+ }
+ env->regs[r1 + 1] = rc;
+}
diff --git a/target/s390x/kvm/kvm.c b/target/s390x/kvm/kvm.c
index 8f655a4b7f..d5b3694600 100644
--- a/target/s390x/kvm/kvm.c
+++ b/target/s390x/kvm/kvm.c
@@ -98,6 +98,7 @@
#define DIAG_TIMEREVENT 0x288
#define DIAG_IPL 0x308
#define DIAG_SET_CONTROL_PROGRAM_CODES 0x318
+#define DIAG_CERT_STORE 0x320
#define DIAG_KVM_HYPERCALL 0x500
#define DIAG_KVM_BREAKPOINT 0x501
@@ -1560,6 +1561,16 @@ static void handle_diag_318(S390CPU *cpu, struct kvm_run *run)
}
}
+static void kvm_handle_diag_320(S390CPU *cpu, struct kvm_run *run)
+{
+ uint64_t r1, r3;
+
+ r1 = (run->s390_sieic.ipa & 0x00f0) >> 4;
+ r3 = run->s390_sieic.ipa & 0x000f;
+
+ handle_diag_320(&cpu->env, r1, r3, RA_IGNORED);
+}
+
#define DIAG_KVM_CODE_MASK 0x000000000000ffff
static int handle_diag(S390CPU *cpu, struct kvm_run *run, uint32_t ipb)
@@ -1590,6 +1601,9 @@ static int handle_diag(S390CPU *cpu, struct kvm_run *run, uint32_t ipb)
case DIAG_KVM_BREAKPOINT:
r = handle_sw_breakpoint(cpu, run);
break;
+ case DIAG_CERT_STORE:
+ kvm_handle_diag_320(cpu, run);
+ break;
default:
trace_kvm_insn_diag(func_code);
kvm_s390_program_interrupt(cpu, PGM_SPECIFICATION);
diff --git a/target/s390x/s390x-internal.h b/target/s390x/s390x-internal.h
index a4ba6227ab..86a652f833 100644
--- a/target/s390x/s390x-internal.h
+++ b/target/s390x/s390x-internal.h
@@ -400,6 +400,8 @@ int mmu_translate_real(CPUS390XState *env, target_ulong raddr, int rw,
int handle_diag_288(CPUS390XState *env, uint64_t r1, uint64_t r3);
void handle_diag_308(CPUS390XState *env, uint64_t r1, uint64_t r3,
uintptr_t ra);
+void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3,
+ uintptr_t ra);
/* translate.c */
diff --git a/target/s390x/tcg/misc_helper.c b/target/s390x/tcg/misc_helper.c
index f7101be574..412c34ed93 100644
--- a/target/s390x/tcg/misc_helper.c
+++ b/target/s390x/tcg/misc_helper.c
@@ -142,6 +142,13 @@ void HELPER(diag)(CPUS390XState *env, uint32_t r1, uint32_t r3, uint32_t num)
/* time bomb (watchdog) */
r = handle_diag_288(env, r1, r3);
break;
+ case 0x320:
+ /* cert store */
+ bql_lock();
+ handle_diag_320(env, r1, r3, GETPC());
+ bql_unlock();
+ r = 0;
+ break;
default:
r = -1;
break;
--
2.49.0
^ permalink raw reply related [flat|nested] 51+ messages in thread* [PATCH v3 06/28] s390x/diag: Refactor address validation check from diag308_parm_check
2025-06-04 21:56 [PATCH v3 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (4 preceding siblings ...)
2025-06-04 21:56 ` [PATCH v3 05/28] s390x/diag: Introduce DIAG 320 for certificate store facility Zhuoying Cai
@ 2025-06-04 21:56 ` Zhuoying Cai
2025-06-04 21:56 ` [PATCH v3 07/28] s390x/diag: Implement DIAG 320 subcode 1 Zhuoying Cai
` (21 subsequent siblings)
27 siblings, 0 replies; 51+ messages in thread
From: Zhuoying Cai @ 2025-06-04 21:56 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, pbonzini
Cc: walling, jjherne, jrossi, pasic, borntraeger, farman, iii, eblake,
armbru, qemu-s390x, qemu-devel, zycai
Create a function to validate the address parameter of DIAGNOSE.
Refactor the function for reuse in the next patch, which allows address
validation in read or write operation of DIAGNOSE.
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
hw/s390x/ipl.h | 6 ++++++
target/s390x/diag.c | 4 +---
2 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/hw/s390x/ipl.h b/hw/s390x/ipl.h
index bee72dfbb3..e26fc1cd6a 100644
--- a/hw/s390x/ipl.h
+++ b/hw/s390x/ipl.h
@@ -118,6 +118,12 @@ QEMU_BUILD_BUG_MSG(offsetof(S390IPLState, iplb) & 3, "alignment of iplb wrong");
#define S390_IPLB_MIN_FCP_LEN 384
#define S390_IPLB_MIN_QEMU_SCSI_LEN 200
+static inline bool diag_parm_addr_valid(uint64_t addr, size_t size, bool write)
+{
+ return address_space_access_valid(&address_space_memory, addr,
+ size, write, MEMTXATTRS_UNSPECIFIED);
+}
+
static inline bool iplb_valid_len(IplParameterBlock *iplb)
{
return be32_to_cpu(iplb->len) <= sizeof(IplParameterBlock);
diff --git a/target/s390x/diag.c b/target/s390x/diag.c
index d33c5daf38..7b9b47a171 100644
--- a/target/s390x/diag.c
+++ b/target/s390x/diag.c
@@ -65,9 +65,7 @@ static int diag308_parm_check(CPUS390XState *env, uint64_t r1, uint64_t addr,
s390_program_interrupt(env, PGM_SPECIFICATION, ra);
return -1;
}
- if (!address_space_access_valid(&address_space_memory, addr,
- sizeof(IplParameterBlock), write,
- MEMTXATTRS_UNSPECIFIED)) {
+ if (!diag_parm_addr_valid(addr, sizeof(IplParameterBlock), write)) {
s390_program_interrupt(env, PGM_ADDRESSING, ra);
return -1;
}
--
2.49.0
^ permalink raw reply related [flat|nested] 51+ messages in thread* [PATCH v3 07/28] s390x/diag: Implement DIAG 320 subcode 1
2025-06-04 21:56 [PATCH v3 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (5 preceding siblings ...)
2025-06-04 21:56 ` [PATCH v3 06/28] s390x/diag: Refactor address validation check from diag308_parm_check Zhuoying Cai
@ 2025-06-04 21:56 ` Zhuoying Cai
2025-06-04 21:56 ` [PATCH v3 08/28] crypto/x509-utils: Add helper functions for DIAG 320 subcode 2 Zhuoying Cai
` (20 subsequent siblings)
27 siblings, 0 replies; 51+ messages in thread
From: Zhuoying Cai @ 2025-06-04 21:56 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, pbonzini
Cc: walling, jjherne, jrossi, pasic, borntraeger, farman, iii, eblake,
armbru, qemu-s390x, qemu-devel, zycai
DIAG 320 subcode 1 provides information needed to determine
the amount of storage to store one or more certificates.
The subcode value is denoted by setting the left-most bit
of an 8-byte field.
The verification-certificate-storage-size block (VCSSB) contains
the output data when the operation completes successfully.
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
include/hw/s390x/ipl/diag320.h | 23 ++++++++++++++++++++++
target/s390x/diag.c | 36 +++++++++++++++++++++++++++++++++-
2 files changed, 58 insertions(+), 1 deletion(-)
diff --git a/include/hw/s390x/ipl/diag320.h b/include/hw/s390x/ipl/diag320.h
index 713570545d..3916a2915e 100644
--- a/include/hw/s390x/ipl/diag320.h
+++ b/include/hw/s390x/ipl/diag320.h
@@ -11,7 +11,30 @@
#define S390X_DIAG320_H
#define DIAG_320_SUBC_QUERY_ISM 0
+#define DIAG_320_SUBC_QUERY_VCSI 1
#define DIAG_320_RC_OK 0x0001
+#define VCSSB_MAX_LEN 128
+#define VCE_HEADER_LEN 128
+#define VCB_HEADER_LEN 64
+
+#define DIAG_320_ISM_QUERY_VCSI 0x4000000000000000
+
+struct VCStorageSizeBlock {
+ uint32_t length;
+ uint8_t reserved0[3];
+ uint8_t version;
+ uint32_t reserved1[6];
+ uint16_t total_vc_ct;
+ uint16_t max_vc_ct;
+ uint32_t reserved3[7];
+ uint32_t max_vce_len;
+ uint32_t reserved4[3];
+ uint32_t max_single_vcb_len;
+ uint32_t total_vcb_len;
+ uint32_t reserved5[10];
+};
+typedef struct VCStorageSizeBlock VCStorageSizeBlock;
+
#endif
diff --git a/target/s390x/diag.c b/target/s390x/diag.c
index 7b9b47a171..1f7d0cb2f6 100644
--- a/target/s390x/diag.c
+++ b/target/s390x/diag.c
@@ -191,9 +191,13 @@ out:
}
}
+QEMU_BUILD_BUG_MSG(sizeof(VCStorageSizeBlock) != 128,
+ "size of VCStorageSizeBlock is wrong");
+
void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
{
S390CPU *cpu = env_archcpu(env);
+ S390IPLCertificateStore *qcs = s390_ipl_get_certificate_store();
uint64_t subcode = env->regs[r3];
uint64_t addr = env->regs[r1];
int rc;
@@ -215,13 +219,43 @@ void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
switch (subcode) {
case DIAG_320_SUBC_QUERY_ISM:
- uint64_t ism = 0;
+ uint64_t ism = cpu_to_be64(DIAG_320_ISM_QUERY_VCSI);
if (s390_cpu_virt_mem_write(cpu, addr, r1, &ism, sizeof(ism))) {
s390_cpu_virt_mem_handle_exc(cpu, ra);
return;
}
+ rc = DIAG_320_RC_OK;
+ break;
+ case DIAG_320_SUBC_QUERY_VCSI:
+ VCStorageSizeBlock vcssb;
+
+ if (!diag_parm_addr_valid(addr, sizeof(VCStorageSizeBlock),
+ true)) {
+ s390_program_interrupt(env, PGM_ADDRESSING, ra);
+ return;
+ }
+
+ if (!qcs || !qcs->count) {
+ vcssb.length = cpu_to_be32(4);
+ } else {
+ vcssb.length = cpu_to_be32(VCSSB_MAX_LEN);
+ vcssb.version = 0;
+ vcssb.total_vc_ct = cpu_to_be16(qcs->count);
+ vcssb.max_vc_ct = cpu_to_be16(MAX_CERTIFICATES);
+ vcssb.max_vce_len = cpu_to_be32(VCE_HEADER_LEN + qcs->max_cert_size);
+ vcssb.max_single_vcb_len = cpu_to_be32(VCB_HEADER_LEN + VCE_HEADER_LEN +
+ qcs->max_cert_size);
+ vcssb.total_vcb_len = cpu_to_be32(VCB_HEADER_LEN +
+ qcs->count * VCE_HEADER_LEN +
+ qcs->total_bytes);
+ }
+
+ if (s390_cpu_virt_mem_write(cpu, addr, r1, &vcssb, sizeof(VCStorageSizeBlock))) {
+ s390_cpu_virt_mem_handle_exc(cpu, ra);
+ return;
+ }
rc = DIAG_320_RC_OK;
break;
default:
--
2.49.0
^ permalink raw reply related [flat|nested] 51+ messages in thread* [PATCH v3 08/28] crypto/x509-utils: Add helper functions for DIAG 320 subcode 2
2025-06-04 21:56 [PATCH v3 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (6 preceding siblings ...)
2025-06-04 21:56 ` [PATCH v3 07/28] s390x/diag: Implement DIAG 320 subcode 1 Zhuoying Cai
@ 2025-06-04 21:56 ` Zhuoying Cai
2025-06-06 10:40 ` Daniel P. Berrangé
2025-06-06 10:43 ` Daniel P. Berrangé
2025-06-04 21:56 ` [PATCH v3 09/28] s390x/diag: Implement " Zhuoying Cai
` (19 subsequent siblings)
27 siblings, 2 replies; 51+ messages in thread
From: Zhuoying Cai @ 2025-06-04 21:56 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, pbonzini
Cc: walling, jjherne, jrossi, pasic, borntraeger, farman, iii, eblake,
armbru, qemu-s390x, qemu-devel, zycai
Add helper functions for x509 certificate which will be used in the next
patch for DIAG 320 subcode 2.
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
crypto/x509-utils.c | 190 +++++++++++++++++++++++++++++++++++-
include/crypto/x509-utils.h | 63 ++++++++++++
qapi/crypto.json | 20 ++++
3 files changed, 272 insertions(+), 1 deletion(-)
diff --git a/crypto/x509-utils.c b/crypto/x509-utils.c
index 7a7f12c111..dd8137210c 100644
--- a/crypto/x509-utils.c
+++ b/crypto/x509-utils.c
@@ -60,6 +60,14 @@ static const int gnutls_to_qcrypto_sig_alg_map[QCRYPTO_SIG_ALGO__MAX] = {
[GNUTLS_SIGN_ECDSA_SHA512] = QCRYPTO_SIG_ALGO_ECDSA_SHA512,
};
+static const int gnutls_to_qcrypto_pk_alg_map[QCRYPTO_PK_ALGO__MAX] = {
+ [GNUTLS_PK_UNKNOWN] = QCRYPTO_PK_ALGO_UNKNOWN,
+ [GNUTLS_PK_RSA] = QCRYPTO_PK_ALGO_RSA,
+ [GNUTLS_PK_DSA] = QCRYPTO_PK_ALGO_DSA,
+ [GNUTLS_PK_DH] = QCRYPTO_PK_ALGO_DH,
+ [GNUTLS_PK_ECDSA] = QCRYPTO_PK_ALGO_ECDSA,
+};
+
int qcrypto_check_x509_cert_fmt(uint8_t *cert, size_t size,
QCryptoCertFmt fmt, Error **errp)
{
@@ -153,7 +161,7 @@ int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
gnutls_x509_crt_init(&crt);
- if (gnutls_x509_crt_import(crt, &datum, GNUTLS_X509_FMT_PEM) != 0) {
+ if (qcrypto_import_x509_cert(crt, &datum) != 0) {
error_setg(errp, "Failed to import certificate");
goto cleanup;
}
@@ -204,6 +212,158 @@ cleanup:
return rc;
}
+int qcrypto_get_x509_cert_version(uint8_t *cert, size_t size, Error **errp)
+{
+ int rc = -1;
+ gnutls_x509_crt_t crt;
+ gnutls_datum_t datum = {.data = cert, .size = size};
+
+ if (gnutls_x509_crt_init(&crt) < 0) {
+ error_setg(errp, "Failed to initialize certificate");
+ return rc;
+ }
+
+ if (qcrypto_import_x509_cert(crt, &datum) != 0) {
+ error_setg(errp, "Failed to import certificate");
+ goto cleanup;
+ }
+
+ rc = gnutls_x509_crt_get_version(crt);
+
+cleanup:
+ gnutls_x509_crt_deinit(crt);
+ return rc;
+}
+
+int qcrypto_check_x509_cert_times(uint8_t *cert, size_t size, Error **errp)
+{
+ int rc = -1;
+ gnutls_x509_crt_t crt;
+ gnutls_datum_t datum = {.data = cert, .size = size};
+ time_t now = time(0);
+ time_t exp_time;
+ time_t act_time;
+
+ if (now == ((time_t)-1)) {
+ error_setg_errno(errp, errno, "Cannot get current time");
+ return rc;
+ }
+
+ if (gnutls_x509_crt_init(&crt) < 0) {
+ error_setg(errp, "Failed to initialize certificate");
+ return rc;
+ }
+
+ if (qcrypto_import_x509_cert(crt, &datum) != 0) {
+ error_setg(errp, "Failed to import certificate");
+ goto cleanup;
+ }
+
+ exp_time = gnutls_x509_crt_get_expiration_time(crt);
+ if (exp_time == ((time_t)-1)) {
+ error_setg(errp, "Failed to get certificate expiration time");
+ goto cleanup;
+ }
+ if (exp_time < now) {
+ error_setg(errp, "The certificate has expired");
+ goto cleanup;
+ }
+
+ act_time = gnutls_x509_crt_get_activation_time(crt);
+ if (act_time == ((time_t)-1)) {
+ error_setg(errp, "Failed to get certificate activation time");
+ goto cleanup;
+ }
+ if (act_time > now) {
+ error_setg(errp, "The certificate is not yet active");
+ goto cleanup;
+ }
+
+ rc = 0;
+
+cleanup:
+ gnutls_x509_crt_deinit(crt);
+ return rc;
+}
+
+int qcrypto_get_x509_pk_algorithm(uint8_t *cert, size_t size, Error **errp)
+{
+ int rc = -1;
+ unsigned int bits;
+ gnutls_x509_crt_t crt;
+ gnutls_datum_t datum = {.data = cert, .size = size};
+
+ if (gnutls_x509_crt_init(&crt) < 0) {
+ error_setg(errp, "Failed to initialize certificate");
+ return rc;
+ }
+
+ if (qcrypto_import_x509_cert(crt, &datum) != 0) {
+ error_setg(errp, "Failed to import certificate");
+ goto cleanup;
+ }
+
+ rc = gnutls_x509_crt_get_pk_algorithm(crt, &bits);
+ rc = gnutls_to_qcrypto_pk_alg_map[rc];
+
+cleanup:
+ gnutls_x509_crt_deinit(crt);
+ return rc;
+}
+
+int qcrypto_get_x509_cert_key_id(uint8_t *cert, size_t size,
+ QCryptoKeyidFlags flag,
+ uint8_t *result,
+ size_t *resultlen,
+ Error **errp)
+{
+ int ret = -1;
+ int keyid_len;
+ gnutls_x509_crt_t crt;
+ gnutls_datum_t datum = {.data = cert, .size = size};
+
+ if (flag >= G_N_ELEMENTS(qcrypto_to_gnutls_keyid_flags_map)) {
+ error_setg(errp, "Unknown key id flag");
+ return -1;
+ }
+
+ if (result == NULL) {
+ error_setg(errp, "No valid buffer given");
+ return -1;
+ }
+
+ if (gnutls_x509_crt_init(&crt)) {
+ error_setg(errp, "Failed to initialize certificate");
+ return -1;
+ }
+
+ if (qcrypto_import_x509_cert(crt, &datum) != 0) {
+ error_setg(errp, "Failed to import certificate");
+ goto cleanup;
+ }
+
+ keyid_len = qcrypto_get_x509_keyid_len(qcrypto_to_gnutls_keyid_flags_map[flag]);
+ if (*resultlen < keyid_len) {
+ error_setg(errp,
+ "Result buffer size %zu is smaller than key id %d",
+ *resultlen, keyid_len);
+ goto cleanup;
+ }
+
+ if (gnutls_x509_crt_get_key_id(crt,
+ qcrypto_to_gnutls_keyid_flags_map[flag],
+ result, resultlen) != 0) {
+ error_setg(errp, "Failed to get fingerprint from certificate");
+ goto cleanup;
+ }
+
+ ret = 0;
+
+cleanup:
+ gnutls_x509_crt_deinit(crt);
+ return ret;
+}
+
#else /* ! CONFIG_GNUTLS */
int qcrypto_check_x509_cert_fmt(uint8_t *cert, size_t size,
@@ -239,4 +399,32 @@ int qcrypto_get_x509_signature_algorithm(uint8_t *cert, size_t size, Error **err
return -ENOTSUP;
}
+int qcrypto_get_x509_cert_version(uint8_t *cert, size_t size, Error **errp)
+{
+ error_setg(errp, "GNUTLS is required to get certificate version");
+ return -ENOTSUP;
+}
+
+int qcrypto_check_x509_cert_times(uint8_t *cert, size_t size, Error **errp)
+{
+ error_setg(errp, "GNUTLS is required to get certificate times")
+ return -ENOTSUP;
+}
+
+int qcrypto_get_x509_pk_algorithm(uint8_t *cert, size_t size, Error **errp)
+{
+ error_setg(errp, "GNUTLS is required to get public key algorithm");
+ return -ENOTSUP;
+}
+
+int qcrypto_get_x509_cert_key_id(uint8_t *cert, size_t size,
+ QCryptoKeyidFlags flag,
+ uint8_t *result,
+ size_t *resultlen,
+ Error **errp)
+{
+ error_setg(errp, "GNUTLS is required to get key ID");
+ return -ENOTSUP;
+}
+
#endif /* ! CONFIG_GNUTLS */
diff --git a/include/crypto/x509-utils.h b/include/crypto/x509-utils.h
index d7be57c8ce..4a9941b33d 100644
--- a/include/crypto/x509-utils.h
+++ b/include/crypto/x509-utils.h
@@ -73,4 +73,67 @@ int qcrypto_get_x509_keyid_len(QCryptoKeyidFlags flag);
*/
int qcrypto_get_x509_signature_algorithm(uint8_t *cert, size_t size, Error **errp);
+/**
+ * qcrypto_get_x509_cert_version
+ * @cert: pointer to the raw certiricate data
+ * @size: size of the certificate
+ * @errp: error pointer
+ *
+ * Determine the version of the @cert.
+ *
+ * Returns: version of certificate on success,
+ * negative error code on error,
+ * -ENOTSUP if GNUTLS is not enabled.
+ */
+int qcrypto_get_x509_cert_version(uint8_t *cert, size_t size, Error **errp);
+
+/**
+ * qcrypto_check_x509_cert_times
+ * @cert: pointer to the raw certiricate data
+ * @size: size of the certificate
+ * @errp: error pointer
+ *
+ * Check whether the @cert activation and expiration times are valid at the current time.
+ *
+ * Returns: 0 if the certificate times are valid,
+ * -1 on error,
+ * -ENOTSUP if GNUTLS is not enabled.
+ */
+int qcrypto_check_x509_cert_times(uint8_t *cert, size_t size, Error **errp);
+
+/**
+ * qcrypto_get_x509_pk_algorithm
+ * @cert: pointer to the raw certiricate data
+ * @size: size of the certificate
+ * @errp: error pointer
+ *
+ * Determine the public key algorithm of the @cert.
+ *
+ * Returns: a value from the QCryptoPkAlgo enum on success,
+ * -1 on error,
+ * -ENOTSUP if GNUTLS is not enabled.
+ */
+int qcrypto_get_x509_pk_algorithm(uint8_t *cert, size_t size, Error **errp);
+
+/**
+ * qcrypto_get_x509_cert_key_id
+ * @cert: pointer to the raw certiricate data
+ * @size: size of the certificate
+ * @flag: the key ID flag
+ * @result: pointer to a buffer to store output key ID (may not be null)
+ * @resultlen: pointer to the size of the buffer
+ * @errp: error pointer
+ *
+ * Retrieve the key ID from the @cert based on the specified @flag.
+ *
+ * Returns: 0 if key ID was successfully stored in @result,
+ * -1 on error,
+ * -ENOTSUP if GNUTLS is not enabled.
+ */
+int qcrypto_get_x509_cert_key_id(uint8_t *cert, size_t size,
+ QCryptoKeyidFlags flag,
+ uint8_t *result,
+ size_t *resultlen,
+ Error **errp);
+
#endif
diff --git a/qapi/crypto.json b/qapi/crypto.json
index af487dcecd..0262ccee34 100644
--- a/qapi/crypto.json
+++ b/qapi/crypto.json
@@ -692,3 +692,23 @@
'rsa-sha256', 'rsa-sha384', 'rsa-sha512', 'rsa-sha224',
'dsa-sha224', 'dsa-sha256',
'ecdsa-sha1', 'ecdsa-sha224', 'ecdsa-sha256', 'ecdsa-sha384', 'ecdsa-sha512']}
+
+##
+# @QCryptoPkAlgo:
+#
+# Algorithms for public-key
+#
+# @unknown: UNKNOWN
+#
+# @rsa: RSA
+#
+# @dsa: DSA
+#
+# @dh: DH
+#
+# @ecdsa: ECDSA
+#
+# Since: 10.1
+##
+{ 'enum': 'QCryptoPkAlgo',
+ 'data': ['unknown', 'rsa', 'dsa', 'dh', 'ecdsa']}
--
2.49.0
^ permalink raw reply related [flat|nested] 51+ messages in thread* Re: [PATCH v3 08/28] crypto/x509-utils: Add helper functions for DIAG 320 subcode 2
2025-06-04 21:56 ` [PATCH v3 08/28] crypto/x509-utils: Add helper functions for DIAG 320 subcode 2 Zhuoying Cai
@ 2025-06-06 10:40 ` Daniel P. Berrangé
2025-06-06 10:43 ` Daniel P. Berrangé
1 sibling, 0 replies; 51+ messages in thread
From: Daniel P. Berrangé @ 2025-06-06 10:40 UTC (permalink / raw)
To: Zhuoying Cai
Cc: thuth, richard.henderson, david, pbonzini, walling, jjherne,
jrossi, pasic, borntraeger, farman, iii, eblake, armbru,
qemu-s390x, qemu-devel
On Wed, Jun 04, 2025 at 05:56:36PM -0400, Zhuoying Cai wrote:
> Add helper functions for x509 certificate which will be used in the next
> patch for DIAG 320 subcode 2.
>
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
> crypto/x509-utils.c | 190 +++++++++++++++++++++++++++++++++++-
> include/crypto/x509-utils.h | 63 ++++++++++++
> qapi/crypto.json | 20 ++++
> 3 files changed, 272 insertions(+), 1 deletion(-)
>
> diff --git a/crypto/x509-utils.c b/crypto/x509-utils.c
> index 7a7f12c111..dd8137210c 100644
> --- a/crypto/x509-utils.c
> +++ b/crypto/x509-utils.c
> @@ -60,6 +60,14 @@ static const int gnutls_to_qcrypto_sig_alg_map[QCRYPTO_SIG_ALGO__MAX] = {
> [GNUTLS_SIGN_ECDSA_SHA512] = QCRYPTO_SIG_ALGO_ECDSA_SHA512,
> };
>
> +static const int gnutls_to_qcrypto_pk_alg_map[QCRYPTO_PK_ALGO__MAX] = {
> + [GNUTLS_PK_UNKNOWN] = QCRYPTO_PK_ALGO_UNKNOWN,
> + [GNUTLS_PK_RSA] = QCRYPTO_PK_ALGO_RSA,
> + [GNUTLS_PK_DSA] = QCRYPTO_PK_ALGO_DSA,
> + [GNUTLS_PK_DH] = QCRYPTO_PK_ALGO_DH,
> + [GNUTLS_PK_ECDSA] = QCRYPTO_PK_ALGO_ECDSA,
> +};
> +
> int qcrypto_check_x509_cert_fmt(uint8_t *cert, size_t size,
> QCryptoCertFmt fmt, Error **errp)
> {
> @@ -153,7 +161,7 @@ int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
>
> gnutls_x509_crt_init(&crt);
>
> - if (gnutls_x509_crt_import(crt, &datum, GNUTLS_X509_FMT_PEM) != 0) {
> + if (qcrypto_import_x509_cert(crt, &datum) != 0) {
Don't change this - we want PEM format exclusively as our input.
> error_setg(errp, "Failed to import certificate");
> goto cleanup;
> }
> @@ -204,6 +212,158 @@ cleanup:
> return rc;
> }
>
> +int qcrypto_get_x509_cert_version(uint8_t *cert, size_t size, Error **errp)
> +{
> + int rc = -1;
> + gnutls_x509_crt_t crt;
> + gnutls_datum_t datum = {.data = cert, .size = size};
> +
> + if (gnutls_x509_crt_init(&crt) < 0) {
> + error_setg(errp, "Failed to initialize certificate");
> + return rc;
> + }
> +
> + if (qcrypto_import_x509_cert(crt, &datum) != 0) {
> + error_setg(errp, "Failed to import certificate");
> + goto cleanup;
> + }
> +
> + rc = gnutls_x509_crt_get_version(crt);
> +
> +cleanup:
> + gnutls_x509_crt_deinit(crt);
> + return rc;
> +}
> +
> +int qcrypto_check_x509_cert_times(uint8_t *cert, size_t size, Error **errp)
> +{
> + int rc = -1;
> + gnutls_x509_crt_t crt;
> + gnutls_datum_t datum = {.data = cert, .size = size};
> + time_t now = time(0);
> + time_t exp_time;
> + time_t act_time;
> +
> + if (now == ((time_t)-1)) {
> + error_setg_errno(errp, errno, "Cannot get current time");
> + return rc;
> + }
> +
> + if (gnutls_x509_crt_init(&crt) < 0) {
> + error_setg(errp, "Failed to initialize certificate");
Missing gnutls_strerror info
> + return rc;
> + }
> +
> + if (qcrypto_import_x509_cert(crt, &datum) != 0) {
> + error_setg(errp, "Failed to import certificate");
> + goto cleanup;
> + }
> +
> + exp_time = gnutls_x509_crt_get_expiration_time(crt);
> + if (exp_time == ((time_t)-1)) {
> + error_setg(errp, "Failed to get certificate expiration time");
> + goto cleanup;
> + }
> + if (exp_time < now) {
> + error_setg(errp, "The certificate has expired");
> + goto cleanup;
> + }
> +
> + act_time = gnutls_x509_crt_get_activation_time(crt);
> + if (act_time == ((time_t)-1)) {
> + error_setg(errp, "Failed to get certificate activation time");
> + goto cleanup;
> + }
> + if (act_time > now) {
> + error_setg(errp, "The certificate is not yet active");
> + goto cleanup;
> + }
> +
> + rc = 0;
> +
> +cleanup:
> + gnutls_x509_crt_deinit(crt);
> + return rc;
> +}
> +
> +int qcrypto_get_x509_pk_algorithm(uint8_t *cert, size_t size, Error **errp)
> +{
> + int rc = -1;
> + unsigned int bits;
> + gnutls_x509_crt_t crt;
> + gnutls_datum_t datum = {.data = cert, .size = size};
> +
> + if (gnutls_x509_crt_init(&crt) < 0) {
> + error_setg(errp, "Failed to initialize certificate");
Missing gnutls_strerror info
> + return rc;
> + }
> +
> + if (qcrypto_import_x509_cert(crt, &datum) != 0) {
> + error_setg(errp, "Failed to import certificate");
> + goto cleanup;
> + }
> +
> + rc = gnutls_x509_crt_get_pk_algorithm(crt, &bits);
Missing error reporting
> + rc = gnutls_to_qcrypto_pk_alg_map[rc];
Missing bounds checking
Also use separate 'ret' vs 'rc' variables for the overall
function return status vs intermediate calls
> +
> +cleanup:
> + gnutls_x509_crt_deinit(crt);
> + return rc;
> +}
> +
> +int qcrypto_get_x509_cert_key_id(uint8_t *cert, size_t size,
> + QCryptoKeyidFlags flag,
> + uint8_t *result,
> + size_t *resultlen,
> + Error **errp)
> +{
> + int ret = -1;
> + int keyid_len;
> + gnutls_x509_crt_t crt;
> + gnutls_datum_t datum = {.data = cert, .size = size};
> +
> + if (flag >= G_N_ELEMENTS(qcrypto_to_gnutls_keyid_flags_map)) {
> + error_setg(errp, "Unknown key id flag");
> + return -1;
> + }
> +
> + if (result == NULL) {
> + error_setg(errp, "No valid buffer given");
> + return -1;
> + }
> +
> + if (gnutls_x509_crt_init(&crt)) {
> + error_setg(errp, "Failed to initialize certificate");
> + return -1;
> + }
> +
> + if (qcrypto_import_x509_cert(crt, &datum) != 0) {
> + error_setg(errp, "Failed to import certificate");
> + goto cleanup;
> + }
> +
> + keyid_len = qcrypto_get_x509_keyid_len(qcrypto_to_gnutls_keyid_flags_map[flag]);
> + if (*resultlen < keyid_len) {
> + error_setg(errp,
> + "Result buffer size %zu is smaller than key id %d",
> + *resultlen, keyid_len);
> + goto cleanup;
> + }
This is avoided if this function is responsible for allocating
'result' to be the right size to begin with.
> +
> + if (gnutls_x509_crt_get_key_id(crt,
> + qcrypto_to_gnutls_keyid_flags_map[flag],
> + result, resultlen) != 0) {
> + error_setg(errp, "Failed to get fingerprint from certificate");
> + goto cleanup;
> + }
> +
> + ret = 0;
> +
> +cleanup:
> + gnutls_x509_crt_deinit(crt);
> + return ret;
> +}
> +
> #else /* ! CONFIG_GNUTLS */
>
> int qcrypto_check_x509_cert_fmt(uint8_t *cert, size_t size,
> @@ -239,4 +399,32 @@ int qcrypto_get_x509_signature_algorithm(uint8_t *cert, size_t size, Error **err
> return -ENOTSUP;
> }
>
> +int qcrypto_get_x509_cert_version(uint8_t *cert, size_t size, Error **errp)
> +{
> + error_setg(errp, "GNUTLS is required to get certificate version");
> + return -ENOTSUP;
No returning errnors please.
> +}
> +
> +int qcrypto_check_x509_cert_times(uint8_t *cert, size_t size, Error **errp)
> +{
> + error_setg(errp, "GNUTLS is required to get certificate times")
> + return -ENOTSUP;
> +}
> +
> +int qcrypto_get_x509_pk_algorithm(uint8_t *cert, size_t size, Error **errp)
> +{
> + error_setg(errp, "GNUTLS is required to get public key algorithm");
> + return -ENOTSUP;
> +}
> +
> +int qcrypto_get_x509_cert_key_id(uint8_t *cert, size_t size,
> + QCryptoKeyidFlags flag,
> + uint8_t *result,
> + size_t *resultlen,
> + Error **errp)
> +{
> + error_setg(errp, "GNUTLS is required to get key ID");
> + return -ENOTSUP;
> +}
> +
> #endif /* ! CONFIG_GNUTLS */
> diff --git a/include/crypto/x509-utils.h b/include/crypto/x509-utils.h
> index d7be57c8ce..4a9941b33d 100644
> --- a/include/crypto/x509-utils.h
> +++ b/include/crypto/x509-utils.h
> @@ -73,4 +73,67 @@ int qcrypto_get_x509_keyid_len(QCryptoKeyidFlags flag);
> */
> int qcrypto_get_x509_signature_algorithm(uint8_t *cert, size_t size, Error **errp);
>
> +/**
> + * qcrypto_get_x509_cert_version
> + * @cert: pointer to the raw certiricate data
^^^^^^ typo..again below
> + * @size: size of the certificate
> + * @errp: error pointer
> + *
> + * Determine the version of the @cert.
> + *
> + * Returns: version of certificate on success,
> + * negative error code on error,
> + * -ENOTSUP if GNUTLS is not enabled.
Nope, only return '-1' on error - all details belong in 'errp
> + */
> +int qcrypto_get_x509_cert_version(uint8_t *cert, size_t size, Error **errp);
> +
> +/**
> + * qcrypto_check_x509_cert_times
> + * @cert: pointer to the raw certiricate data
> + * @size: size of the certificate
> + * @errp: error pointer
> + *
> + * Check whether the @cert activation and expiration times are valid at the current time.
> + *
> + * Returns: 0 if the certificate times are valid,
> + * -1 on error,
> + * -ENOTSUP if GNUTLS is not enabled.
> + */
> +int qcrypto_check_x509_cert_times(uint8_t *cert, size_t size, Error **errp);
> +
> +/**
> + * qcrypto_get_x509_pk_algorithm
> + * @cert: pointer to the raw certiricate data
> + * @size: size of the certificate
> + * @errp: error pointer
> + *
> + * Determine the public key algorithm of the @cert.
> + *
> + * Returns: a value from the QCryptoPkAlgo enum on success,
> + * -1 on error,
> + * -ENOTSUP if GNUTLS is not enabled.
> + */
> +int qcrypto_get_x509_pk_algorithm(uint8_t *cert, size_t size, Error **errp);
> +
> +/**
> + * qcrypto_get_x509_cert_key_id
> + * @cert: pointer to the raw certiricate data
> + * @size: size of the certificate
> + * @flag: the key ID flag
> + * @result: pointer to a buffer to store output key ID (may not be null)
> + * @resultlen: pointer to the size of the buffer
> + * @errp: error pointer
> + *
> + * Retrieve the key ID from the @cert based on the specified @flag.
> + *
> + * Returns: 0 if key ID was successfully stored in @result,
> + * -1 on error,
> + * -ENOTSUP if GNUTLS is not enabled.
> + */
> +int qcrypto_get_x509_cert_key_id(uint8_t *cert, size_t size,
> + QCryptoKeyidFlags flag,
> + uint8_t *result,
> + size_t *resultlen,
Better calling convention is to use 'uint8_t **result' and have
this method allocate a correct size buffer & return its size
in 'resultlen'.
> + Error **errp);
> +
> #endif
With regards,
Daniel
--
|: https://berrange.com -o- https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org -o- https://fstop138.berrange.com :|
|: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|
^ permalink raw reply [flat|nested] 51+ messages in thread* Re: [PATCH v3 08/28] crypto/x509-utils: Add helper functions for DIAG 320 subcode 2
2025-06-04 21:56 ` [PATCH v3 08/28] crypto/x509-utils: Add helper functions for DIAG 320 subcode 2 Zhuoying Cai
2025-06-06 10:40 ` Daniel P. Berrangé
@ 2025-06-06 10:43 ` Daniel P. Berrangé
1 sibling, 0 replies; 51+ messages in thread
From: Daniel P. Berrangé @ 2025-06-06 10:43 UTC (permalink / raw)
To: Zhuoying Cai
Cc: thuth, richard.henderson, david, pbonzini, walling, jjherne,
jrossi, pasic, borntraeger, farman, iii, eblake, armbru,
qemu-s390x, qemu-devel
On Wed, Jun 04, 2025 at 05:56:36PM -0400, Zhuoying Cai wrote:
> Add helper functions for x509 certificate which will be used in the next
> patch for DIAG 320 subcode 2.
Please describe what this is adding from the POV of the crypto
subsystem - references to "the next patch" won't make any
sense when looking at crypto changes in isolation.
With regards,
Daniel
--
|: https://berrange.com -o- https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org -o- https://fstop138.berrange.com :|
|: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|
^ permalink raw reply [flat|nested] 51+ messages in thread
* [PATCH v3 09/28] s390x/diag: Implement DIAG 320 subcode 2
2025-06-04 21:56 [PATCH v3 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (7 preceding siblings ...)
2025-06-04 21:56 ` [PATCH v3 08/28] crypto/x509-utils: Add helper functions for DIAG 320 subcode 2 Zhuoying Cai
@ 2025-06-04 21:56 ` Zhuoying Cai
2025-06-06 10:51 ` Daniel P. Berrangé
2025-06-04 21:56 ` [PATCH v3 10/28] s390x/diag: Introduce DIAG 508 for secure IPL operations Zhuoying Cai
` (18 subsequent siblings)
27 siblings, 1 reply; 51+ messages in thread
From: Zhuoying Cai @ 2025-06-04 21:56 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, pbonzini
Cc: walling, jjherne, jrossi, pasic, borntraeger, farman, iii, eblake,
armbru, qemu-s390x, qemu-devel, zycai
DIAG 320 subcode 2 provides verification-certificates (VCs) that are in the
certificate store. Only X509 certificates in DER format and SHA-256 hash
type are recognized.
The subcode value is denoted by setting the second-left-most bit
of an 8-byte field.
The Verification Certificate Block (VCB) contains the output data
when the operation completes successfully. It includes a common
header followed by zero or more Verification Certificate Entries (VCEs),
depending on the VCB input length and the VC range (from the first VC
index to the last VC index) in the certificate store.
Each VCE contains information about a certificate retrieved from
the S390IPLCertificateStore, such as the certificate name, key type,
key ID length, hash length, and the raw certificate data.
The key ID and hash are extracted from the raw certificate by the crypto API.
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
include/hw/s390x/ipl/diag320.h | 47 ++++++
target/s390x/diag.c | 262 ++++++++++++++++++++++++++++++++-
2 files changed, 308 insertions(+), 1 deletion(-)
diff --git a/include/hw/s390x/ipl/diag320.h b/include/hw/s390x/ipl/diag320.h
index 3916a2915e..a926cf7d25 100644
--- a/include/hw/s390x/ipl/diag320.h
+++ b/include/hw/s390x/ipl/diag320.h
@@ -12,14 +12,23 @@
#define DIAG_320_SUBC_QUERY_ISM 0
#define DIAG_320_SUBC_QUERY_VCSI 1
+#define DIAG_320_SUBC_STORE_VC 2
#define DIAG_320_RC_OK 0x0001
+#define DIAG_320_RC_INVAL_VCB_LEN 0x0204
+#define DIAG_320_RC_BAD_RANGE 0x0302
#define VCSSB_MAX_LEN 128
#define VCE_HEADER_LEN 128
#define VCB_HEADER_LEN 64
#define DIAG_320_ISM_QUERY_VCSI 0x4000000000000000
+#define DIAG_320_ISM_STORE_VC 0x2000000000000000
+
+#define DIAG_320_VCE_FLAGS_VALID 0x80
+#define DIAG_320_VCE_KEYTYPE_SELF_DESCRIBING 0
+#define DIAG_320_VCE_FORMAT_X509_DER 1
+#define DIAG_320_VCE_HASHTYPE_SHA2_256 1
struct VCStorageSizeBlock {
uint32_t length;
@@ -37,4 +46,42 @@ struct VCStorageSizeBlock {
};
typedef struct VCStorageSizeBlock VCStorageSizeBlock;
+struct VCBlock {
+ uint32_t in_len;
+ uint32_t reserved0;
+ uint16_t first_vc_index;
+ uint16_t last_vc_index;
+ uint32_t reserved1[5];
+ uint32_t out_len;
+ uint8_t reserved2[3];
+ uint8_t version;
+ uint16_t stored_ct;
+ uint16_t remain_ct;
+ uint32_t reserved3[5];
+ uint8_t vce_buf[];
+};
+typedef struct VCBlock VCBlock;
+
+struct VCEntry {
+ uint32_t len;
+ uint8_t flags;
+ uint8_t key_type;
+ uint16_t cert_idx;
+ uint32_t name[16];
+ uint8_t format;
+ uint8_t reserved0;
+ uint16_t keyid_len;
+ uint8_t reserved1;
+ uint8_t hash_type;
+ uint16_t hash_len;
+ uint32_t reserved2;
+ uint32_t cert_len;
+ uint32_t reserved3[2];
+ uint16_t hash_offset;
+ uint16_t cert_offset;
+ uint32_t reserved4[7];
+ uint8_t cert_buf[];
+};
+typedef struct VCEntry VCEntry;
+
#endif
diff --git a/target/s390x/diag.c b/target/s390x/diag.c
index 1f7d0cb2f6..c8518dc5be 100644
--- a/target/s390x/diag.c
+++ b/target/s390x/diag.c
@@ -17,6 +17,7 @@
#include "s390x-internal.h"
#include "hw/watchdog/wdt_diag288.h"
#include "system/cpus.h"
+#include "hw/s390x/cert-store.h"
#include "hw/s390x/ipl.h"
#include "hw/s390x/ipl/diag320.h"
#include "hw/s390x/s390-virtio-ccw.h"
@@ -24,6 +25,7 @@
#include "kvm/kvm_s390x.h"
#include "target/s390x/kvm/pv.h"
#include "qemu/error-report.h"
+#include "crypto/x509-utils.h"
int handle_diag_288(CPUS390XState *env, uint64_t r1, uint64_t r3)
@@ -191,8 +193,260 @@ out:
}
}
+static int diag_320_is_cert_valid(S390IPLCertificate qcert, Error **errp)
+{
+ int version;
+ int rc;
+
+ version = qcrypto_get_x509_cert_version((uint8_t *)qcert.raw, qcert.size, errp);
+ if (version < 0) {
+ return version == -ENOTSUP ? -ENOTSUP : -1;
+ }
+
+ rc = qcrypto_check_x509_cert_times((uint8_t *)qcert.raw, qcert.size, errp);
+ if (rc) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static int diag_320_get_cert_info(VCEntry *vce, S390IPLCertificate qcert, int *is_valid,
+ unsigned char **key_id_data, void **hash_data)
+{
+ int algo;
+ int rc;
+ Error *err = NULL;
+
+ /* VCE flag (validity) */
+ *is_valid = diag_320_is_cert_valid(qcert, &err);
+ /* return early if GNUTLS is not enabled */
+ if (*is_valid == -ENOTSUP) {
+ error_report("GNUTLS is not supported");
+ return -1;
+ }
+ /* reset err for the next caller to avoid assert failure */
+ err = NULL;
+
+ /* key-type */
+ algo = qcrypto_get_x509_pk_algorithm((uint8_t *)qcert.raw, qcert.size, &err);
+ if (algo == QCRYPTO_PK_ALGO_RSA) {
+ vce->key_type = DIAG_320_VCE_KEYTYPE_SELF_DESCRIBING;
+ }
+ err = NULL;
+
+ /* VC format */
+ if (qcert.format == QCRYPTO_CERT_FMT_DER) {
+ vce->format = DIAG_320_VCE_FORMAT_X509_DER;
+ }
+
+ /* key id and key id len */
+ *key_id_data = g_malloc0(qcert.key_id_size);
+ rc = qcrypto_get_x509_cert_key_id((uint8_t *)qcert.raw, qcert.size,
+ QCRYPTO_KEYID_FLAGS_SHA256,
+ *key_id_data, &qcert.key_id_size, &err);
+ if (rc < 0) {
+ error_report_err(err);
+ error_report("Fail to retrieve certificate key ID");
+ goto out;
+ }
+ vce->keyid_len = cpu_to_be16(qcert.key_id_size);
+
+ /* hash type */
+ if (qcert.hash_type == QCRYPTO_SIG_ALGO_RSA_SHA256) {
+ vce->hash_type = DIAG_320_VCE_HASHTYPE_SHA2_256;
+ }
+
+ /* hash and hash len */
+ *hash_data = g_malloc0(qcert.hash_size);
+ rc = qcrypto_get_x509_cert_fingerprint((uint8_t *)qcert.raw, qcert.size,
+ QCRYPTO_HASH_ALGO_SHA256,
+ *hash_data, &qcert.hash_size, &err);
+ if (rc < 0) {
+ error_report_err(err);
+ error_report("Fail to retrieve certificate hash");
+ goto out;
+ }
+ vce->hash_len = cpu_to_be16(qcert.hash_size);
+
+ return 0;
+
+out:
+ g_free(*key_id_data);
+ g_free(*hash_data);
+
+ *key_id_data = NULL;
+ *hash_data = NULL;
+
+ return -1;
+}
+
+static VCEntry *build_vce(S390IPLCertificate qcert, uint32_t vce_len, int idx,
+ size_t keyid_buf_size, size_t hash_buf_size)
+{
+ VCEntry *vce = NULL;
+ unsigned char *key_id_data = NULL;
+ void *hash_data = NULL;
+ int is_valid = -1;
+ int rc;
+
+ /*
+ * Construct VCE
+ * Unused area following the VCE field contains zeros.
+ */
+ vce = g_malloc0(vce_len);
+
+ rc = diag_320_get_cert_info(vce, qcert, &is_valid, &key_id_data, &hash_data);
+ if (rc) {
+ g_free(vce);
+ return NULL;
+ }
+
+ vce->len = cpu_to_be32(vce_len);
+ vce->cert_idx = cpu_to_be16(idx + 1);
+ vce->cert_len = cpu_to_be32(qcert.size);
+
+ strncpy((char *)vce->name, (char *)qcert.vc_name, VC_NAME_LEN_BYTES);
+
+ /* VCE field offset is also word aligned */
+ vce->hash_offset = cpu_to_be16(VCE_HEADER_LEN + keyid_buf_size);
+ vce->cert_offset = cpu_to_be16(VCE_HEADER_LEN + keyid_buf_size + hash_buf_size);
+
+ /* Write Key ID */
+ memcpy(vce->cert_buf, key_id_data, qcert.key_id_size);
+ /* Write Hash key */
+ memcpy(vce->cert_buf + keyid_buf_size, hash_data, qcert.hash_size);
+ /* Write VCE cert data */
+ memcpy(vce->cert_buf + keyid_buf_size + hash_buf_size, qcert.raw, qcert.size);
+
+ /* The certificate is valid and VCE contains the certificate */
+ if (is_valid == 0) {
+ vce->flags |= DIAG_320_VCE_FLAGS_VALID;
+ }
+
+ g_free(key_id_data);
+ g_free(hash_data);
+
+ key_id_data = NULL;
+ hash_data = NULL;
+
+ return vce;
+}
+
+static int handle_diag320_store_vc(S390CPU *cpu, uint64_t addr, uint64_t r1, uintptr_t ra,
+ S390IPLCertificateStore *qcs)
+{
+ VCBlock *vcb;
+ size_t vce_offset;
+ size_t remaining_space;
+ size_t keyid_buf_size;
+ size_t hash_buf_size;
+ size_t cert_buf_size;
+ uint32_t vce_len;
+ uint16_t first_vc_index;
+ uint16_t last_vc_index;
+ uint32_t in_len;
+
+ vcb = g_new0(VCBlock, 1);
+ if (s390_cpu_virt_mem_read(cpu, addr, r1, vcb, sizeof(*vcb))) {
+ s390_cpu_virt_mem_handle_exc(cpu, ra);
+ return -1;
+ }
+
+ in_len = be32_to_cpu(vcb->in_len);
+ first_vc_index = be16_to_cpu(vcb->first_vc_index);
+ last_vc_index = be16_to_cpu(vcb->last_vc_index);
+
+ if (in_len % TARGET_PAGE_SIZE != 0) {
+ g_free(vcb);
+ return DIAG_320_RC_INVAL_VCB_LEN;
+ }
+
+ if (first_vc_index > last_vc_index) {
+ g_free(vcb);
+ return DIAG_320_RC_BAD_RANGE;
+ }
+
+ if (first_vc_index == 0) {
+ /*
+ * Zero is a valid index for the first and last VC index.
+ * Zero index results in the VCB header and zero certificates returned.
+ */
+ if (last_vc_index == 0) {
+ goto out;
+ }
+
+ /* DIAG320 certificate store remains a one origin for cert entries */
+ vcb->first_vc_index = 1;
+ }
+
+ vce_offset = VCB_HEADER_LEN;
+ vcb->out_len = VCB_HEADER_LEN;
+ remaining_space = in_len - VCB_HEADER_LEN;
+
+ for (int i = first_vc_index - 1; i < last_vc_index && i < qcs->count; i++) {
+ VCEntry *vce;
+ S390IPLCertificate qcert = qcs->certs[i];
+ /*
+ * Each VCE is word aligned.
+ * Each variable length field within the VCE is also word aligned.
+ */
+ keyid_buf_size = ROUND_UP(qcert.key_id_size, 4);
+ hash_buf_size = ROUND_UP(qcert.hash_size, 4);
+ cert_buf_size = ROUND_UP(qcert.size, 4);
+ vce_len = VCE_HEADER_LEN + cert_buf_size + keyid_buf_size + hash_buf_size;
+
+ /*
+ * If there is no more space to store the cert,
+ * set the remaining verification cert count and
+ * break early.
+ */
+ if (remaining_space < vce_len) {
+ vcb->remain_ct = cpu_to_be16(last_vc_index - i);
+ break;
+ }
+
+ vce = build_vce(qcert, vce_len, i, keyid_buf_size, hash_buf_size);
+ if (vce == NULL) {
+ continue;
+ }
+
+ /* Write VCE */
+ if (s390_cpu_virt_mem_write(cpu, addr + vce_offset, r1, vce, vce_len)) {
+ s390_cpu_virt_mem_handle_exc(cpu, ra);
+ return -1;
+ }
+
+ vce_offset += vce_len;
+ vcb->out_len += vce_len;
+ remaining_space -= vce_len;
+ vcb->stored_ct++;
+
+ g_free(vce);
+ }
+
+ vcb->out_len = cpu_to_be32(vcb->out_len);
+ vcb->stored_ct = cpu_to_be16(vcb->stored_ct);
+
+out:
+ /*
+ * Write VCB header
+ * All VCEs have been populated with the latest information
+ * and write VCB header last.
+ */
+ if (s390_cpu_virt_mem_write(cpu, addr, r1, vcb, VCB_HEADER_LEN)) {
+ s390_cpu_virt_mem_handle_exc(cpu, ra);
+ return -1;
+ }
+
+ g_free(vcb);
+ return DIAG_320_RC_OK;
+}
+
QEMU_BUILD_BUG_MSG(sizeof(VCStorageSizeBlock) != 128,
"size of VCStorageSizeBlock is wrong");
+QEMU_BUILD_BUG_MSG(sizeof(VCBlock) != 64, "size of VCBlock is wrong");
+QEMU_BUILD_BUG_MSG(sizeof(VCEntry) != 128, "size of VCEntry is wrong");
void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
{
@@ -219,7 +473,7 @@ void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
switch (subcode) {
case DIAG_320_SUBC_QUERY_ISM:
- uint64_t ism = cpu_to_be64(DIAG_320_ISM_QUERY_VCSI);
+ uint64_t ism = cpu_to_be64(DIAG_320_ISM_QUERY_VCSI | DIAG_320_ISM_STORE_VC);
if (s390_cpu_virt_mem_write(cpu, addr, r1, &ism, sizeof(ism))) {
s390_cpu_virt_mem_handle_exc(cpu, ra);
@@ -258,6 +512,12 @@ void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
}
rc = DIAG_320_RC_OK;
break;
+ case DIAG_320_SUBC_STORE_VC:
+ rc = handle_diag320_store_vc(cpu, addr, r1, ra, qcs);
+ if (rc == -1) {
+ return;
+ }
+ break;
default:
s390_program_interrupt(env, PGM_SPECIFICATION, ra);
return;
--
2.49.0
^ permalink raw reply related [flat|nested] 51+ messages in thread* Re: [PATCH v3 09/28] s390x/diag: Implement DIAG 320 subcode 2
2025-06-04 21:56 ` [PATCH v3 09/28] s390x/diag: Implement " Zhuoying Cai
@ 2025-06-06 10:51 ` Daniel P. Berrangé
0 siblings, 0 replies; 51+ messages in thread
From: Daniel P. Berrangé @ 2025-06-06 10:51 UTC (permalink / raw)
To: Zhuoying Cai
Cc: thuth, richard.henderson, david, pbonzini, walling, jjherne,
jrossi, pasic, borntraeger, farman, iii, eblake, armbru,
qemu-s390x, qemu-devel
On Wed, Jun 04, 2025 at 05:56:37PM -0400, Zhuoying Cai wrote:
> DIAG 320 subcode 2 provides verification-certificates (VCs) that are in the
> certificate store. Only X509 certificates in DER format and SHA-256 hash
> type are recognized.
>
> The subcode value is denoted by setting the second-left-most bit
> of an 8-byte field.
>
> The Verification Certificate Block (VCB) contains the output data
> when the operation completes successfully. It includes a common
> header followed by zero or more Verification Certificate Entries (VCEs),
> depending on the VCB input length and the VC range (from the first VC
> index to the last VC index) in the certificate store.
>
> Each VCE contains information about a certificate retrieved from
> the S390IPLCertificateStore, such as the certificate name, key type,
> key ID length, hash length, and the raw certificate data.
> The key ID and hash are extracted from the raw certificate by the crypto API.
>
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
> include/hw/s390x/ipl/diag320.h | 47 ++++++
> target/s390x/diag.c | 262 ++++++++++++++++++++++++++++++++-
> 2 files changed, 308 insertions(+), 1 deletion(-)
>
> diff --git a/target/s390x/diag.c b/target/s390x/diag.c
> index 1f7d0cb2f6..c8518dc5be 100644
> --- a/target/s390x/diag.c
> +++ b/target/s390x/diag.c
> @@ -17,6 +17,7 @@
> #include "s390x-internal.h"
> #include "hw/watchdog/wdt_diag288.h"
> #include "system/cpus.h"
> +#include "hw/s390x/cert-store.h"
> #include "hw/s390x/ipl.h"
> #include "hw/s390x/ipl/diag320.h"
> #include "hw/s390x/s390-virtio-ccw.h"
> @@ -24,6 +25,7 @@
> #include "kvm/kvm_s390x.h"
> #include "target/s390x/kvm/pv.h"
> #include "qemu/error-report.h"
> +#include "crypto/x509-utils.h"
>
>
> int handle_diag_288(CPUS390XState *env, uint64_t r1, uint64_t r3)
> @@ -191,8 +193,260 @@ out:
> }
> }
>
> +static int diag_320_is_cert_valid(S390IPLCertificate qcert, Error **errp)
> +{
> + int version;
> + int rc;
> +
> + version = qcrypto_get_x509_cert_version((uint8_t *)qcert.raw, qcert.size, errp);
Why not change 'qcert.raw' to be 'uint8_t *' to begin with if
all the APIs it is used with expect that ?
> + if (version < 0) {
> + return version == -ENOTSUP ? -ENOTSUP : -1;
> + }
This method shouldn't be returning errnos, only '-1'
> +
> + rc = qcrypto_check_x509_cert_times((uint8_t *)qcert.raw, qcert.size, errp);
> + if (rc) {
> + return -1;
> + }
> +
> + return 0;
> +}
> +
> +static int diag_320_get_cert_info(VCEntry *vce, S390IPLCertificate qcert, int *is_valid,
> + unsigned char **key_id_data, void **hash_data)
> +{
> + int algo;
> + int rc;
> + Error *err = NULL;
> +
> + /* VCE flag (validity) */
> + *is_valid = diag_320_is_cert_valid(qcert, &err);
> + /* return early if GNUTLS is not enabled */
> + if (*is_valid == -ENOTSUP) {
> + error_report("GNUTLS is not supported");
> + return -1;
> + }
> + /* reset err for the next caller to avoid assert failure */
> + err = NULL;
This leaks memory. There is also no reason why this should
treat missing GNUTLS as different from any other errors.
All fatal errors must be turned into failures of this
method.
> +
> + /* key-type */
> + algo = qcrypto_get_x509_pk_algorithm((uint8_t *)qcert.raw, qcert.size, &err);
> + if (algo == QCRYPTO_PK_ALGO_RSA) {
> + vce->key_type = DIAG_320_VCE_KEYTYPE_SELF_DESCRIBING;
> + }
> + err = NULL;
Again leaking memory and failing at error propagation.
> +
> + /* VC format */
> + if (qcert.format == QCRYPTO_CERT_FMT_DER) {
> + vce->format = DIAG_320_VCE_FORMAT_X509_DER;
> + }
> +
> + /* key id and key id len */
> + *key_id_data = g_malloc0(qcert.key_id_size);
This can be removed if qcrypto_get_x509_cert_key_id is changed
to allocate the correct size itself.
> + rc = qcrypto_get_x509_cert_key_id((uint8_t *)qcert.raw, qcert.size,
> + QCRYPTO_KEYID_FLAGS_SHA256,
> + *key_id_data, &qcert.key_id_size, &err);
> + if (rc < 0) {
> + error_report_err(err);
> + error_report("Fail to retrieve certificate key ID");
The second error_report() is redundant
> + goto out;
> + }
> + vce->keyid_len = cpu_to_be16(qcert.key_id_size);
> +
> + /* hash type */
> + if (qcert.hash_type == QCRYPTO_SIG_ALGO_RSA_SHA256) {
> + vce->hash_type = DIAG_320_VCE_HASHTYPE_SHA2_256;
> + }
> +
> + /* hash and hash len */
> + *hash_data = g_malloc0(qcert.hash_size);
> + rc = qcrypto_get_x509_cert_fingerprint((uint8_t *)qcert.raw, qcert.size,
> + QCRYPTO_HASH_ALGO_SHA256,
> + *hash_data, &qcert.hash_size, &err);
> + if (rc < 0) {
> + error_report_err(err);
> + error_report("Fail to retrieve certificate hash");
Also redundant.
> + goto out;
> + }
> + vce->hash_len = cpu_to_be16(qcert.hash_size);
> +
> + return 0;
> +
> +out:
> + g_free(*key_id_data);
> + g_free(*hash_data);
> +
> + *key_id_data = NULL;
> + *hash_data = NULL;
g_clear_pointer(key_id_data, g_free) does free & clear in one operation
> +
> + return -1;
> +}
> +
> +static VCEntry *build_vce(S390IPLCertificate qcert, uint32_t vce_len, int idx,
> + size_t keyid_buf_size, size_t hash_buf_size)
> +{
> + VCEntry *vce = NULL;
g_autofree
> + unsigned char *key_id_data = NULL;
g_autofree
> + void *hash_data = NULL;
g_autofree
> + int is_valid = -1;
> + int rc;
> +
> + /*
> + * Construct VCE
> + * Unused area following the VCE field contains zeros.
> + */
> + vce = g_malloc0(vce_len);
> +
> + rc = diag_320_get_cert_info(vce, qcert, &is_valid, &key_id_data, &hash_data);
> + if (rc) {
> + g_free(vce);
Redundant if using g_autofree
> + return NULL;
> + }
> +
> + vce->len = cpu_to_be32(vce_len);
> + vce->cert_idx = cpu_to_be16(idx + 1);
> + vce->cert_len = cpu_to_be32(qcert.size);
> +
> + strncpy((char *)vce->name, (char *)qcert.vc_name, VC_NAME_LEN_BYTES);
> +
> + /* VCE field offset is also word aligned */
> + vce->hash_offset = cpu_to_be16(VCE_HEADER_LEN + keyid_buf_size);
> + vce->cert_offset = cpu_to_be16(VCE_HEADER_LEN + keyid_buf_size + hash_buf_size);
> +
> + /* Write Key ID */
> + memcpy(vce->cert_buf, key_id_data, qcert.key_id_size);
> + /* Write Hash key */
> + memcpy(vce->cert_buf + keyid_buf_size, hash_data, qcert.hash_size);
> + /* Write VCE cert data */
> + memcpy(vce->cert_buf + keyid_buf_size + hash_buf_size, qcert.raw, qcert.size);
> +
> + /* The certificate is valid and VCE contains the certificate */
> + if (is_valid == 0) {
> + vce->flags |= DIAG_320_VCE_FLAGS_VALID;
> + }
> +
> + g_free(key_id_data);
> + g_free(hash_data);
> +
> + key_id_data = NULL;
> + hash_data = NULL;
Redundant with g_autofree
> +
> + return vce;
Use g_steal_pointer(&vce) with g_autofree.
> +}
> +
> +static int handle_diag320_store_vc(S390CPU *cpu, uint64_t addr, uint64_t r1, uintptr_t ra,
> + S390IPLCertificateStore *qcs)
> +{
> + VCBlock *vcb;
g_autofree VCBlock *vcb = NULL;
> + size_t vce_offset;
> + size_t remaining_space;
> + size_t keyid_buf_size;
> + size_t hash_buf_size;
> + size_t cert_buf_size;
> + uint32_t vce_len;
> + uint16_t first_vc_index;
> + uint16_t last_vc_index;
> + uint32_t in_len;
> +
> + vcb = g_new0(VCBlock, 1);
> + if (s390_cpu_virt_mem_read(cpu, addr, r1, vcb, sizeof(*vcb))) {
> + s390_cpu_virt_mem_handle_exc(cpu, ra);
Fails to free 'vcb', hence the best practice use of g_autofree
> + return -1;
> + }
> +
> + in_len = be32_to_cpu(vcb->in_len);
> + first_vc_index = be16_to_cpu(vcb->first_vc_index);
> + last_vc_index = be16_to_cpu(vcb->last_vc_index);
> +
> + if (in_len % TARGET_PAGE_SIZE != 0) {
> + g_free(vcb);
> + return DIAG_320_RC_INVAL_VCB_LEN;
> + }
> +
> + if (first_vc_index > last_vc_index) {
> + g_free(vcb);
> + return DIAG_320_RC_BAD_RANGE;
> + }
> +
> + if (first_vc_index == 0) {
> + /*
> + * Zero is a valid index for the first and last VC index.
> + * Zero index results in the VCB header and zero certificates returned.
> + */
> + if (last_vc_index == 0) {
> + goto out;
> + }
> +
> + /* DIAG320 certificate store remains a one origin for cert entries */
> + vcb->first_vc_index = 1;
> + }
> +
> + vce_offset = VCB_HEADER_LEN;
> + vcb->out_len = VCB_HEADER_LEN;
> + remaining_space = in_len - VCB_HEADER_LEN;
> +
> + for (int i = first_vc_index - 1; i < last_vc_index && i < qcs->count; i++) {
> + VCEntry *vce;
> + S390IPLCertificate qcert = qcs->certs[i];
> + /*
> + * Each VCE is word aligned.
> + * Each variable length field within the VCE is also word aligned.
> + */
> + keyid_buf_size = ROUND_UP(qcert.key_id_size, 4);
> + hash_buf_size = ROUND_UP(qcert.hash_size, 4);
> + cert_buf_size = ROUND_UP(qcert.size, 4);
> + vce_len = VCE_HEADER_LEN + cert_buf_size + keyid_buf_size + hash_buf_size;
> +
> + /*
> + * If there is no more space to store the cert,
> + * set the remaining verification cert count and
> + * break early.
> + */
> + if (remaining_space < vce_len) {
> + vcb->remain_ct = cpu_to_be16(last_vc_index - i);
> + break;
> + }
> +
> + vce = build_vce(qcert, vce_len, i, keyid_buf_size, hash_buf_size);
> + if (vce == NULL) {
> + continue;
> + }
> +
> + /* Write VCE */
> + if (s390_cpu_virt_mem_write(cpu, addr + vce_offset, r1, vce, vce_len)) {
> + s390_cpu_virt_mem_handle_exc(cpu, ra);
> + return -1;
> + }
> +
> + vce_offset += vce_len;
> + vcb->out_len += vce_len;
> + remaining_space -= vce_len;
> + vcb->stored_ct++;
> +
> + g_free(vce);
> + }
> +
> + vcb->out_len = cpu_to_be32(vcb->out_len);
> + vcb->stored_ct = cpu_to_be16(vcb->stored_ct);
> +
> +out:
> + /*
> + * Write VCB header
> + * All VCEs have been populated with the latest information
> + * and write VCB header last.
> + */
> + if (s390_cpu_virt_mem_write(cpu, addr, r1, vcb, VCB_HEADER_LEN)) {
> + s390_cpu_virt_mem_handle_exc(cpu, ra);
Fails to free 'vcb'
> + return -1;
> + }
> +
> + g_free(vcb);
> + return DIAG_320_RC_OK;
> +}
> +
With regards,
Daniel
--
|: https://berrange.com -o- https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org -o- https://fstop138.berrange.com :|
|: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|
^ permalink raw reply [flat|nested] 51+ messages in thread
* [PATCH v3 10/28] s390x/diag: Introduce DIAG 508 for secure IPL operations
2025-06-04 21:56 [PATCH v3 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (8 preceding siblings ...)
2025-06-04 21:56 ` [PATCH v3 09/28] s390x/diag: Implement " Zhuoying Cai
@ 2025-06-04 21:56 ` Zhuoying Cai
2025-06-04 21:56 ` [PATCH v3 11/28] crypto: Add helper functions for DIAG 508 subcode 1 Zhuoying Cai
` (17 subsequent siblings)
27 siblings, 0 replies; 51+ messages in thread
From: Zhuoying Cai @ 2025-06-04 21:56 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, pbonzini
Cc: walling, jjherne, jrossi, pasic, borntraeger, farman, iii, eblake,
armbru, qemu-s390x, qemu-devel, zycai
From: Collin Walling <walling@linux.ibm.com>
In order to support secure IPL (aka secure boot) for the s390-ccw BIOS,
a new s390 DIAGNOSE instruction is introduced to leverage QEMU for
handling operations such as signature verification and certificate
retrieval.
Currently, only subcode 0 is supported with this patch, which is used to
query a bitmap of which subcodes are supported.
Signed-off-by: Collin Walling <walling@linux.ibm.com>
---
include/hw/s390x/ipl/diag508.h | 15 +++++++++++++++
target/s390x/diag.c | 27 +++++++++++++++++++++++++++
target/s390x/kvm/kvm.c | 14 ++++++++++++++
target/s390x/s390x-internal.h | 2 ++
target/s390x/tcg/misc_helper.c | 7 +++++++
5 files changed, 65 insertions(+)
create mode 100644 include/hw/s390x/ipl/diag508.h
diff --git a/include/hw/s390x/ipl/diag508.h b/include/hw/s390x/ipl/diag508.h
new file mode 100644
index 0000000000..6281ad8299
--- /dev/null
+++ b/include/hw/s390x/ipl/diag508.h
@@ -0,0 +1,15 @@
+/*
+ * S/390 DIAGNOSE 508 definitions and structures
+ *
+ * Copyright 2025 IBM Corp.
+ * Author(s): Collin Walling <walling@linux.ibm.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef S390X_DIAG508_H
+#define S390X_DIAG508_H
+
+#define DIAG_508_SUBC_QUERY_SUBC 0x0000
+
+#endif
diff --git a/target/s390x/diag.c b/target/s390x/diag.c
index c8518dc5be..1ef1eb5299 100644
--- a/target/s390x/diag.c
+++ b/target/s390x/diag.c
@@ -20,6 +20,7 @@
#include "hw/s390x/cert-store.h"
#include "hw/s390x/ipl.h"
#include "hw/s390x/ipl/diag320.h"
+#include "hw/s390x/ipl/diag508.h"
#include "hw/s390x/s390-virtio-ccw.h"
#include "system/kvm.h"
#include "kvm/kvm_s390x.h"
@@ -524,3 +525,29 @@ void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
}
env->regs[r1 + 1] = rc;
}
+
+void handle_diag_508(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
+{
+ uint64_t subcode = env->regs[r3];
+ int rc;
+
+ if (env->psw.mask & PSW_MASK_PSTATE) {
+ s390_program_interrupt(env, PGM_PRIVILEGED, ra);
+ return;
+ }
+
+ if ((subcode & ~0x0ffffULL) || (r1 & 1)) {
+ s390_program_interrupt(env, PGM_SPECIFICATION, ra);
+ return;
+ }
+
+ switch (subcode) {
+ case DIAG_508_SUBC_QUERY_SUBC:
+ rc = 0;
+ break;
+ default:
+ s390_program_interrupt(env, PGM_SPECIFICATION, ra);
+ return;
+ }
+ env->regs[r1 + 1] = rc;
+}
diff --git a/target/s390x/kvm/kvm.c b/target/s390x/kvm/kvm.c
index d5b3694600..840330709b 100644
--- a/target/s390x/kvm/kvm.c
+++ b/target/s390x/kvm/kvm.c
@@ -101,6 +101,7 @@
#define DIAG_CERT_STORE 0x320
#define DIAG_KVM_HYPERCALL 0x500
#define DIAG_KVM_BREAKPOINT 0x501
+#define DIAG_SECURE_IPL 0x508
#define ICPT_INSTRUCTION 0x04
#define ICPT_PROGRAM 0x08
@@ -1571,6 +1572,16 @@ static void kvm_handle_diag_320(S390CPU *cpu, struct kvm_run *run)
handle_diag_320(&cpu->env, r1, r3, RA_IGNORED);
}
+static void kvm_handle_diag_508(S390CPU *cpu, struct kvm_run *run)
+{
+ uint64_t r1, r3;
+
+ r1 = (run->s390_sieic.ipa & 0x00f0) >> 4;
+ r3 = run->s390_sieic.ipa & 0x000f;
+
+ handle_diag_508(&cpu->env, r1, r3, RA_IGNORED);
+}
+
#define DIAG_KVM_CODE_MASK 0x000000000000ffff
static int handle_diag(S390CPU *cpu, struct kvm_run *run, uint32_t ipb)
@@ -1604,6 +1615,9 @@ static int handle_diag(S390CPU *cpu, struct kvm_run *run, uint32_t ipb)
case DIAG_CERT_STORE:
kvm_handle_diag_320(cpu, run);
break;
+ case DIAG_SECURE_IPL:
+ kvm_handle_diag_508(cpu, run);
+ break;
default:
trace_kvm_insn_diag(func_code);
kvm_s390_program_interrupt(cpu, PGM_SPECIFICATION);
diff --git a/target/s390x/s390x-internal.h b/target/s390x/s390x-internal.h
index 86a652f833..df0973266a 100644
--- a/target/s390x/s390x-internal.h
+++ b/target/s390x/s390x-internal.h
@@ -402,6 +402,8 @@ void handle_diag_308(CPUS390XState *env, uint64_t r1, uint64_t r3,
uintptr_t ra);
void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3,
uintptr_t ra);
+void handle_diag_508(CPUS390XState *env, uint64_t r1, uint64_t r3,
+ uintptr_t ra);
/* translate.c */
diff --git a/target/s390x/tcg/misc_helper.c b/target/s390x/tcg/misc_helper.c
index 412c34ed93..ddbf495118 100644
--- a/target/s390x/tcg/misc_helper.c
+++ b/target/s390x/tcg/misc_helper.c
@@ -149,6 +149,13 @@ void HELPER(diag)(CPUS390XState *env, uint32_t r1, uint32_t r3, uint32_t num)
bql_unlock();
r = 0;
break;
+ case 0x508:
+ /* secure ipl operations */
+ bql_lock();
+ handle_diag_508(env, r1, r3, GETPC());
+ bql_unlock();
+ r = 0;
+ break;
default:
r = -1;
break;
--
2.49.0
^ permalink raw reply related [flat|nested] 51+ messages in thread* [PATCH v3 11/28] crypto: Add helper functions for DIAG 508 subcode 1
2025-06-04 21:56 [PATCH v3 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (9 preceding siblings ...)
2025-06-04 21:56 ` [PATCH v3 10/28] s390x/diag: Introduce DIAG 508 for secure IPL operations Zhuoying Cai
@ 2025-06-04 21:56 ` Zhuoying Cai
2025-06-06 10:56 ` Daniel P. Berrangé
2025-06-04 21:56 ` [PATCH v3 12/28] s390x/diag: Implement DIAG 508 subcode 1 for signature verification Zhuoying Cai
` (16 subsequent siblings)
27 siblings, 1 reply; 51+ messages in thread
From: Zhuoying Cai @ 2025-06-04 21:56 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, pbonzini
Cc: walling, jjherne, jrossi, pasic, borntraeger, farman, iii, eblake,
armbru, qemu-s390x, qemu-devel, zycai
Add helper functions for x509 certificate which will be used in the next
patch for DIAG 508 subcode 1.
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
crypto/x509-utils.c | 61 +++++++++++++++++++++++++++++++++++++
include/crypto/x509-utils.h | 20 ++++++++++++
2 files changed, 81 insertions(+)
diff --git a/crypto/x509-utils.c b/crypto/x509-utils.c
index dd8137210c..4c997cf5d4 100644
--- a/crypto/x509-utils.c
+++ b/crypto/x509-utils.c
@@ -16,6 +16,7 @@
#include <gnutls/gnutls.h>
#include <gnutls/crypto.h>
#include <gnutls/x509.h>
+#include <gnutls/pkcs7.h>
static const int qcrypto_to_gnutls_hash_alg_map[QCRYPTO_HASH_ALGO__MAX] = {
[QCRYPTO_HASH_ALGO_MD5] = GNUTLS_DIG_MD5,
@@ -364,6 +365,58 @@ cleanup:
return ret;
}
+static int qcrypto_import_pkcs7(gnutls_pkcs7_t pkcs7, gnutls_datum_t *datum)
+{
+ int rc;
+
+ rc = gnutls_pkcs7_import(pkcs7, datum , GNUTLS_X509_FMT_PEM);
+ if (rc) {
+ rc = gnutls_pkcs7_import(pkcs7, datum , GNUTLS_X509_FMT_DER);
+ }
+
+ return rc;
+}
+
+int qcrypto_verify_x509_cert(uint8_t *cert, size_t cert_size,
+ uint8_t *comp, size_t comp_size,
+ uint8_t *sig, size_t sig_size, Error **errp)
+{
+ int rc = -1;
+ gnutls_x509_crt_t crt;
+ gnutls_pkcs7_t signature;
+ gnutls_datum_t cert_datum = {.data = cert, .size = cert_size};
+ gnutls_datum_t data_datum = {.data = comp, .size = comp_size};
+ gnutls_datum_t sig_datum = {.data = sig, .size = sig_size};
+
+ if (gnutls_x509_crt_init(&crt) < 0) {
+ error_setg(errp, "Failed to initialize certificate");
+ return rc;
+ }
+
+ if (qcrypto_import_x509_cert(crt, &cert_datum) != 0) {
+ error_setg(errp, "Failed to import certificate");
+ gnutls_x509_crt_deinit(crt);
+ return rc;
+ }
+
+ if (gnutls_pkcs7_init(&signature) < 0) {
+ error_setg(errp, "Failed to initalize pkcs7 data.");
+ return rc;
+ }
+
+ if (qcrypto_import_pkcs7(signature, &sig_datum) != 0) {
+ error_setg(errp, "Failed to import signature");
+ goto cleanup;
+ }
+
+ rc = gnutls_pkcs7_verify_direct(signature, crt, 0, &data_datum, 0);
+
+cleanup:
+ gnutls_x509_crt_deinit(crt);
+ gnutls_pkcs7_deinit(signature);
+ return rc;
+}
+
#else /* ! CONFIG_GNUTLS */
int qcrypto_check_x509_cert_fmt(uint8_t *cert, size_t size,
@@ -427,4 +480,12 @@ int qcrypto_get_x509_cert_key_id(uint8_t *cert, size_t size,
return -ENOTSUP;
}
+int qcrypto_verify_x509_cert(uint8_t *cert, size_t cert_size,
+ uint8_t *comp, size_t comp_size,
+ uint8_t *sig, size_t sig_size, Error **errp)
+{
+ error_setg(errp, "GNUTLS is required for signature-verification support");
+ return -ENOTSUP;
+}
+
#endif /* ! CONFIG_GNUTLS */
diff --git a/include/crypto/x509-utils.h b/include/crypto/x509-utils.h
index 4a9941b33d..0b698b1b08 100644
--- a/include/crypto/x509-utils.h
+++ b/include/crypto/x509-utils.h
@@ -136,4 +136,24 @@ int qcrypto_get_x509_cert_key_id(uint8_t *cert, size_t size,
size_t *resultlen,
Error **errp);
+/**
+ * qcrypto_verify_x509_cert
+ * @cert: pointer to the raw certiricate data
+ * @cert_size: size of the certificate
+ * @comp: pointer to the component to be verified
+ * @comp_size: size of the component
+ * @sig: pointer to the signature
+ * @sig_size: size of the signature
+ * @errp: error pointer
+ *
+ * Verify the provided @comp against the @sig and @cert.
+ *
+ * Returns: 0 on success,
+ * negative error code on error,
+ * -ENOTSUP if GNUTLS is not enabled.
+ */
+int qcrypto_verify_x509_cert(uint8_t *cert, size_t cert_size,
+ uint8_t *comp, size_t comp_size,
+ uint8_t *sig, size_t sig_size, Error **errp);
+
#endif
--
2.49.0
^ permalink raw reply related [flat|nested] 51+ messages in thread* Re: [PATCH v3 11/28] crypto: Add helper functions for DIAG 508 subcode 1
2025-06-04 21:56 ` [PATCH v3 11/28] crypto: Add helper functions for DIAG 508 subcode 1 Zhuoying Cai
@ 2025-06-06 10:56 ` Daniel P. Berrangé
0 siblings, 0 replies; 51+ messages in thread
From: Daniel P. Berrangé @ 2025-06-06 10:56 UTC (permalink / raw)
To: Zhuoying Cai
Cc: thuth, richard.henderson, david, pbonzini, walling, jjherne,
jrossi, pasic, borntraeger, farman, iii, eblake, armbru,
qemu-s390x, qemu-devel
On Wed, Jun 04, 2025 at 05:56:39PM -0400, Zhuoying Cai wrote:
> Add helper functions for x509 certificate which will be used in the next
> patch for DIAG 508 subcode 1.
>
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
> crypto/x509-utils.c | 61 +++++++++++++++++++++++++++++++++++++
> include/crypto/x509-utils.h | 20 ++++++++++++
> 2 files changed, 81 insertions(+)
>
> diff --git a/crypto/x509-utils.c b/crypto/x509-utils.c
> index dd8137210c..4c997cf5d4 100644
> --- a/crypto/x509-utils.c
> +++ b/crypto/x509-utils.c
> @@ -16,6 +16,7 @@
> #include <gnutls/gnutls.h>
> #include <gnutls/crypto.h>
> #include <gnutls/x509.h>
> +#include <gnutls/pkcs7.h>
>
> static const int qcrypto_to_gnutls_hash_alg_map[QCRYPTO_HASH_ALGO__MAX] = {
> [QCRYPTO_HASH_ALGO_MD5] = GNUTLS_DIG_MD5,
> @@ -364,6 +365,58 @@ cleanup:
> return ret;
> }
>
> +static int qcrypto_import_pkcs7(gnutls_pkcs7_t pkcs7, gnutls_datum_t *datum)
> +{
> + int rc;
> +
> + rc = gnutls_pkcs7_import(pkcs7, datum , GNUTLS_X509_FMT_PEM);
> + if (rc) {
> + rc = gnutls_pkcs7_import(pkcs7, datum , GNUTLS_X509_FMT_DER);
> + }
> +
> + return rc;
> +}
Again use PEM exclusively which avoids this helper.
> +
> +int qcrypto_verify_x509_cert(uint8_t *cert, size_t cert_size,
> + uint8_t *comp, size_t comp_size,
> + uint8_t *sig, size_t sig_size, Error **errp)
This is a sub-optimal name as it is not verifying the cert,
it is using the cert to verify a signature.
Incidentally I've just remembered that crypto all methods introduced
by this patch series should use 'qcrypto_x509_' as the prefix.
IOW, call this qcrypto_x509_verify_signature, and similar pattern
in earlier patches.
> +{
> + int rc = -1;
> + gnutls_x509_crt_t crt;
> + gnutls_pkcs7_t signature;
> + gnutls_datum_t cert_datum = {.data = cert, .size = cert_size};
> + gnutls_datum_t data_datum = {.data = comp, .size = comp_size};
> + gnutls_datum_t sig_datum = {.data = sig, .size = sig_size};
> +
> + if (gnutls_x509_crt_init(&crt) < 0) {
> + error_setg(errp, "Failed to initialize certificate");
> + return rc;
> + }
> +
> + if (qcrypto_import_x509_cert(crt, &cert_datum) != 0) {
> + error_setg(errp, "Failed to import certificate");
> + gnutls_x509_crt_deinit(crt);
> + return rc;
> + }
> +
> + if (gnutls_pkcs7_init(&signature) < 0) {
> + error_setg(errp, "Failed to initalize pkcs7 data.");
gnutls_strerror details please, and no full stop required.
> + return rc;
> + }
> +
> + if (qcrypto_import_pkcs7(signature, &sig_datum) != 0) {
> + error_setg(errp, "Failed to import signature");
> + goto cleanup;
> + }
> +
> + rc = gnutls_pkcs7_verify_direct(signature, crt, 0, &data_datum, 0);
This is failing to set 'errp', and also should not be returning
the raw gnutls value, only '-1' on failure, '0' on success
> +
> +cleanup:
> + gnutls_x509_crt_deinit(crt);
> + gnutls_pkcs7_deinit(signature);
> + return rc;
> +}
> +
> #else /* ! CONFIG_GNUTLS */
>
> +/**
> + * qcrypto_verify_x509_cert
> + * @cert: pointer to the raw certiricate data
> + * @cert_size: size of the certificate
> + * @comp: pointer to the component to be verified
> + * @comp_size: size of the component
> + * @sig: pointer to the signature
> + * @sig_size: size of the signature
> + * @errp: error pointer
> + *
> + * Verify the provided @comp against the @sig and @cert.
> + *
> + * Returns: 0 on success,
> + * negative error code on error,
> + * -ENOTSUP if GNUTLS is not enabled.
Only -1 on error, no gnutls error codes nor errnos
> + */
> +int qcrypto_verify_x509_cert(uint8_t *cert, size_t cert_size,
> + uint8_t *comp, size_t comp_size,
> + uint8_t *sig, size_t sig_size, Error **errp);
> +
> #endif
> --
> 2.49.0
>
With regards,
Daniel
--
|: https://berrange.com -o- https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org -o- https://fstop138.berrange.com :|
|: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|
^ permalink raw reply [flat|nested] 51+ messages in thread
* [PATCH v3 12/28] s390x/diag: Implement DIAG 508 subcode 1 for signature verification
2025-06-04 21:56 [PATCH v3 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (10 preceding siblings ...)
2025-06-04 21:56 ` [PATCH v3 11/28] crypto: Add helper functions for DIAG 508 subcode 1 Zhuoying Cai
@ 2025-06-04 21:56 ` Zhuoying Cai
2025-06-06 10:58 ` Daniel P. Berrangé
2025-06-04 21:56 ` [PATCH v3 13/28] pc-bios/s390-ccw: Introduce IPL Information Report Block (IIRB) Zhuoying Cai
` (15 subsequent siblings)
27 siblings, 1 reply; 51+ messages in thread
From: Zhuoying Cai @ 2025-06-04 21:56 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, pbonzini
Cc: walling, jjherne, jrossi, pasic, borntraeger, farman, iii, eblake,
armbru, qemu-s390x, qemu-devel, zycai
From: Collin Walling <walling@linux.ibm.com>
DIAG 508 subcode 1 performs signature-verification on signed components.
A signed component may be a Linux kernel image, or any other signed
binary. **Verification of initrd is not supported.**
The instruction call expects two item-pairs: an address of a device
component, an address of the analogous signature file (in PKCS#7 format),
and their respective lengths. All of this data should be encapsulated
within a Diag508SignatureVerificationBlock, with the CertificateStoreInfo
fields ignored. The DIAG handler will read from the provided addresses
to retrieve the necessary data, parse the signature file, then
perform the signature-verification. Because there is no way to
correlate a specific certificate to a component, each certificate
in the store is tried until either verification succeeds, or all
certs have been exhausted.
The subcode value is denoted by setting the second-to-left-most bit of
a 2-byte field.
A return code of 1 indicates success, and the index and length of the
corresponding certificate will be set in the CertificateStoreInfo
portion of the SigVerifBlock. The following values indicate failure:
0x0202: component data is invalid
0x0302: signature is not in PKCS#7 format
0x0402: signature-verification failed
Signed-off-by: Collin Walling <walling@linux.ibm.com>
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
include/hw/s390x/ipl/diag508.h | 23 +++++++++
target/s390x/diag.c | 86 +++++++++++++++++++++++++++++++++-
2 files changed, 108 insertions(+), 1 deletion(-)
diff --git a/include/hw/s390x/ipl/diag508.h b/include/hw/s390x/ipl/diag508.h
index 6281ad8299..c99c6705c0 100644
--- a/include/hw/s390x/ipl/diag508.h
+++ b/include/hw/s390x/ipl/diag508.h
@@ -11,5 +11,28 @@
#define S390X_DIAG508_H
#define DIAG_508_SUBC_QUERY_SUBC 0x0000
+#define DIAG_508_SUBC_SIG_VERIF 0x8000
+
+#define DIAG_508_RC_OK 0x0001
+#define DIAG_508_RC_NO_CERTS 0x0102
+#define DIAG_508_RC_INVAL_COMP_DATA 0x0202
+#define DIAG_508_RC_INVAL_PKCS7_SIG 0x0302
+#define DIAG_508_RC_FAIL_VERIF 0x0402
+
+struct Diag508CertificateStoreInfo {
+ uint8_t idx;
+ uint8_t reserved[7];
+ uint64_t len;
+};
+typedef struct Diag508CertificateStoreInfo Diag508CertificateStoreInfo;
+
+struct Diag508SignatureVerificationBlock {
+ Diag508CertificateStoreInfo csi;
+ uint64_t comp_len;
+ uint64_t comp_addr;
+ uint64_t sig_len;
+ uint64_t sig_addr;
+};
+typedef struct Diag508SignatureVerificationBlock Diag508SignatureVerificationBlock;
#endif
diff --git a/target/s390x/diag.c b/target/s390x/diag.c
index 1ef1eb5299..ca7dd0f2e6 100644
--- a/target/s390x/diag.c
+++ b/target/s390x/diag.c
@@ -526,9 +526,81 @@ void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
env->regs[r1 + 1] = rc;
}
+static int handle_diag508_sig_verif(uint64_t addr, size_t csi_size, size_t svb_size,
+ S390IPLCertificateStore *qcs)
+{
+ int rc;
+ int verified;
+ Error *err;
+ uint64_t comp_len, comp_addr;
+ uint64_t sig_len, sig_addr;
+ g_autofree uint8_t *svb_comp;
+ g_autofree uint8_t *svb_sig;
+ g_autofree Diag508SignatureVerificationBlock *svb;
+
+ if (!qcs || !qcs->count) {
+ return DIAG_508_RC_NO_CERTS;
+ }
+
+ svb = g_new0(Diag508SignatureVerificationBlock, 1);
+ cpu_physical_memory_read(addr, svb, svb_size);
+
+ comp_len = be64_to_cpu(svb->comp_len);
+ comp_addr = be64_to_cpu(svb->comp_addr);
+ sig_len = be64_to_cpu(svb->sig_len);
+ sig_addr = be64_to_cpu(svb->sig_addr);
+
+ if (!comp_len || !comp_addr) {
+ return DIAG_508_RC_INVAL_COMP_DATA;
+ }
+
+ if (!sig_len || !sig_addr) {
+ return DIAG_508_RC_INVAL_PKCS7_SIG;
+ }
+
+ svb_comp = g_malloc0(comp_len);
+ cpu_physical_memory_read(comp_addr, svb_comp, comp_len);
+
+ svb_sig = g_malloc0(sig_len);
+ cpu_physical_memory_read(sig_addr, svb_sig, sig_len);
+
+ rc = DIAG_508_RC_FAIL_VERIF;
+ /*
+ * It is uncertain which certificate contains
+ * the analogous key to verify the signed data
+ */
+ for (int i = 0; i < qcs->count; i++) {
+ err = NULL;
+ verified = qcrypto_verify_x509_cert((uint8_t *)qcs->certs[i].raw,
+ qcs->certs[i].size,
+ svb_comp, comp_len,
+ svb_sig, sig_len, &err);
+
+ /* return early if GNUTLS is not enabled */
+ if (verified == -ENOTSUP) {
+ break;
+ }
+
+ if (verified == 0) {
+ svb->csi.idx = i;
+ svb->csi.len = cpu_to_be64(qcs->certs[i].size);
+ cpu_physical_memory_write(addr, &svb->csi, be32_to_cpu(csi_size));
+ rc = DIAG_508_RC_OK;
+ break;
+ }
+ }
+
+ return rc;
+}
+
+QEMU_BUILD_BUG_MSG(sizeof(Diag508SignatureVerificationBlock) != 48,
+ "size of Diag508SignatureVerificationBlock is wrong");
+
void handle_diag_508(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
{
+ S390IPLCertificateStore *qcs = s390_ipl_get_certificate_store();
uint64_t subcode = env->regs[r3];
+ uint64_t addr = env->regs[r1];
int rc;
if (env->psw.mask & PSW_MASK_PSTATE) {
@@ -543,7 +615,19 @@ void handle_diag_508(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
switch (subcode) {
case DIAG_508_SUBC_QUERY_SUBC:
- rc = 0;
+ rc = DIAG_508_SUBC_SIG_VERIF;
+ break;
+ case DIAG_508_SUBC_SIG_VERIF:
+ size_t csi_size = sizeof(Diag508CertificateStoreInfo);
+ size_t svb_size = sizeof(Diag508SignatureVerificationBlock);
+
+ if (!diag_parm_addr_valid(addr, svb_size, false) ||
+ !diag_parm_addr_valid(addr, csi_size, true)) {
+ s390_program_interrupt(env, PGM_ADDRESSING, ra);
+ return;
+ }
+
+ rc = handle_diag508_sig_verif(addr, csi_size, svb_size, qcs);
break;
default:
s390_program_interrupt(env, PGM_SPECIFICATION, ra);
--
2.49.0
^ permalink raw reply related [flat|nested] 51+ messages in thread* Re: [PATCH v3 12/28] s390x/diag: Implement DIAG 508 subcode 1 for signature verification
2025-06-04 21:56 ` [PATCH v3 12/28] s390x/diag: Implement DIAG 508 subcode 1 for signature verification Zhuoying Cai
@ 2025-06-06 10:58 ` Daniel P. Berrangé
0 siblings, 0 replies; 51+ messages in thread
From: Daniel P. Berrangé @ 2025-06-06 10:58 UTC (permalink / raw)
To: Zhuoying Cai
Cc: thuth, richard.henderson, david, pbonzini, walling, jjherne,
jrossi, pasic, borntraeger, farman, iii, eblake, armbru,
qemu-s390x, qemu-devel
On Wed, Jun 04, 2025 at 05:56:40PM -0400, Zhuoying Cai wrote:
> From: Collin Walling <walling@linux.ibm.com>
>
> DIAG 508 subcode 1 performs signature-verification on signed components.
> A signed component may be a Linux kernel image, or any other signed
> binary. **Verification of initrd is not supported.**
>
> The instruction call expects two item-pairs: an address of a device
> component, an address of the analogous signature file (in PKCS#7 format),
> and their respective lengths. All of this data should be encapsulated
> within a Diag508SignatureVerificationBlock, with the CertificateStoreInfo
> fields ignored. The DIAG handler will read from the provided addresses
> to retrieve the necessary data, parse the signature file, then
> perform the signature-verification. Because there is no way to
> correlate a specific certificate to a component, each certificate
> in the store is tried until either verification succeeds, or all
> certs have been exhausted.
>
> The subcode value is denoted by setting the second-to-left-most bit of
> a 2-byte field.
>
> A return code of 1 indicates success, and the index and length of the
> corresponding certificate will be set in the CertificateStoreInfo
> portion of the SigVerifBlock. The following values indicate failure:
>
> 0x0202: component data is invalid
> 0x0302: signature is not in PKCS#7 format
> 0x0402: signature-verification failed
>
> Signed-off-by: Collin Walling <walling@linux.ibm.com>
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> diff --git a/target/s390x/diag.c b/target/s390x/diag.c
> index 1ef1eb5299..ca7dd0f2e6 100644
> --- a/target/s390x/diag.c
> +++ b/target/s390x/diag.c
> @@ -526,9 +526,81 @@ void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
> env->regs[r1 + 1] = rc;
> }
>
> +static int handle_diag508_sig_verif(uint64_t addr, size_t csi_size, size_t svb_size,
> + S390IPLCertificateStore *qcs)
> +{
> + int rc;
> + int verified;
> + Error *err;
> + uint64_t comp_len, comp_addr;
> + uint64_t sig_len, sig_addr;
> + g_autofree uint8_t *svb_comp;
> + g_autofree uint8_t *svb_sig;
> + g_autofree Diag508SignatureVerificationBlock *svb;
All g_auto* variables must be initialized to NULL at time
of declaration. As written this results in freeing of
unitialized data if a 'return' is hit before these are
initialized.
> +
> + if (!qcs || !qcs->count) {
> + return DIAG_508_RC_NO_CERTS;
> + }
> +
> + svb = g_new0(Diag508SignatureVerificationBlock, 1);
> + cpu_physical_memory_read(addr, svb, svb_size);
> +
> + comp_len = be64_to_cpu(svb->comp_len);
> + comp_addr = be64_to_cpu(svb->comp_addr);
> + sig_len = be64_to_cpu(svb->sig_len);
> + sig_addr = be64_to_cpu(svb->sig_addr);
> +
> + if (!comp_len || !comp_addr) {
> + return DIAG_508_RC_INVAL_COMP_DATA;
> + }
> +
> + if (!sig_len || !sig_addr) {
> + return DIAG_508_RC_INVAL_PKCS7_SIG;
> + }
> +
> + svb_comp = g_malloc0(comp_len);
> + cpu_physical_memory_read(comp_addr, svb_comp, comp_len);
> +
> + svb_sig = g_malloc0(sig_len);
> + cpu_physical_memory_read(sig_addr, svb_sig, sig_len);
> +
> + rc = DIAG_508_RC_FAIL_VERIF;
> + /*
> + * It is uncertain which certificate contains
> + * the analogous key to verify the signed data
> + */
> + for (int i = 0; i < qcs->count; i++) {
> + err = NULL;
> + verified = qcrypto_verify_x509_cert((uint8_t *)qcs->certs[i].raw,
> + qcs->certs[i].size,
> + svb_comp, comp_len,
> + svb_sig, sig_len, &err);
> +
> + /* return early if GNUTLS is not enabled */
> + if (verified == -ENOTSUP) {
> + break;
> + }
All errors must be honoured not merely missing gnutls, and this code
leaks 'err' too
> +
> + if (verified == 0) {
> + svb->csi.idx = i;
> + svb->csi.len = cpu_to_be64(qcs->certs[i].size);
> + cpu_physical_memory_write(addr, &svb->csi, be32_to_cpu(csi_size));
> + rc = DIAG_508_RC_OK;
> + break;
> + }
> + }
> +
> + return rc;
> +}
With regards,
Daniel
--
|: https://berrange.com -o- https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org -o- https://fstop138.berrange.com :|
|: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|
^ permalink raw reply [flat|nested] 51+ messages in thread
* [PATCH v3 13/28] pc-bios/s390-ccw: Introduce IPL Information Report Block (IIRB)
2025-06-04 21:56 [PATCH v3 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (11 preceding siblings ...)
2025-06-04 21:56 ` [PATCH v3 12/28] s390x/diag: Implement DIAG 508 subcode 1 for signature verification Zhuoying Cai
@ 2025-06-04 21:56 ` Zhuoying Cai
2025-06-04 21:56 ` [PATCH v3 14/28] pc-bios/s390-ccw: Define memory for IPLB and convert IPLB to pointers Zhuoying Cai
` (14 subsequent siblings)
27 siblings, 0 replies; 51+ messages in thread
From: Zhuoying Cai @ 2025-06-04 21:56 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, pbonzini
Cc: walling, jjherne, jrossi, pasic, borntraeger, farman, iii, eblake,
armbru, qemu-s390x, qemu-devel, zycai
The IPL information report block (IIRB) contains information used
to locate IPL records and to report the results of signature verification
of one or more secure components of the load device.
IIRB is stored immediately following the IPL Parameter Block. Results on
component verification in any case (failure or success) are stored.
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
pc-bios/s390-ccw/iplb.h | 62 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 62 insertions(+)
diff --git a/pc-bios/s390-ccw/iplb.h b/pc-bios/s390-ccw/iplb.h
index 08f259ff31..bdbc733e16 100644
--- a/pc-bios/s390-ccw/iplb.h
+++ b/pc-bios/s390-ccw/iplb.h
@@ -23,6 +23,68 @@ extern QemuIplParameters qipl;
extern IplParameterBlock iplb __attribute__((__aligned__(PAGE_SIZE)));
extern bool have_iplb;
+struct IplInfoReportBlockHeader {
+ uint32_t len;
+ uint8_t iirb_flags;
+ uint8_t reserved1[2];
+ uint8_t version;
+ uint8_t reserved2[8];
+} __attribute__ ((packed));
+typedef struct IplInfoReportBlockHeader IplInfoReportBlockHeader;
+
+struct IplInfoBlockHeader {
+ uint32_t len;
+ uint8_t ibt;
+ uint8_t reserved1[3];
+ uint8_t reserved2[8];
+} __attribute__ ((packed));
+typedef struct IplInfoBlockHeader IplInfoBlockHeader;
+
+enum IplIbt {
+ IPL_IBT_CERTIFICATES = 1,
+ IPL_IBT_COMPONENTS = 2,
+};
+
+struct IplSignatureCertificateEntry {
+ uint64_t addr;
+ uint64_t len;
+} __attribute__ ((packed));
+typedef struct IplSignatureCertificateEntry IplSignatureCertificateEntry;
+
+struct IplSignatureCertificateList {
+ IplInfoBlockHeader ipl_info_header;
+ IplSignatureCertificateEntry cert_entries[MAX_CERTIFICATES];
+} __attribute__ ((packed));
+typedef struct IplSignatureCertificateList IplSignatureCertificateList;
+
+#define S390_IPL_COMPONENT_FLAG_SC 0x80
+#define S390_IPL_COMPONENT_FLAG_CSV 0x40
+
+struct IplDeviceComponentEntry {
+ uint64_t addr;
+ uint64_t len;
+ uint8_t flags;
+ uint8_t reserved1[5];
+ uint16_t cert_index;
+ uint8_t reserved2[8];
+} __attribute__ ((packed));
+typedef struct IplDeviceComponentEntry IplDeviceComponentEntry;
+
+struct IplDeviceComponentList {
+ IplInfoBlockHeader ipl_info_header;
+ IplDeviceComponentEntry device_entries[MAX_CERTIFICATES];
+} __attribute__ ((packed));
+typedef struct IplDeviceComponentList IplDeviceComponentList;
+
+#define COMP_LIST_MAX sizeof(IplDeviceComponentList)
+#define CERT_LIST_MAX sizeof(IplSignatureCertificateList)
+
+struct IplInfoReportBlock {
+ IplInfoReportBlockHeader hdr;
+ uint8_t info_blks[COMP_LIST_MAX + CERT_LIST_MAX];
+} __attribute__ ((packed));
+typedef struct IplInfoReportBlock IplInfoReportBlock;
+
#define S390_IPL_TYPE_FCP 0x00
#define S390_IPL_TYPE_CCW 0x02
#define S390_IPL_TYPE_QEMU_SCSI 0xff
--
2.49.0
^ permalink raw reply related [flat|nested] 51+ messages in thread* [PATCH v3 14/28] pc-bios/s390-ccw: Define memory for IPLB and convert IPLB to pointers
2025-06-04 21:56 [PATCH v3 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (12 preceding siblings ...)
2025-06-04 21:56 ` [PATCH v3 13/28] pc-bios/s390-ccw: Introduce IPL Information Report Block (IIRB) Zhuoying Cai
@ 2025-06-04 21:56 ` Zhuoying Cai
2025-06-04 21:56 ` [PATCH v3 15/28] hw/s390x/ipl: Add IPIB flags to IPL Parameter Block Zhuoying Cai
` (13 subsequent siblings)
27 siblings, 0 replies; 51+ messages in thread
From: Zhuoying Cai @ 2025-06-04 21:56 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, pbonzini
Cc: walling, jjherne, jrossi, pasic, borntraeger, farman, iii, eblake,
armbru, qemu-s390x, qemu-devel, zycai
Define a memory space for both IPL Parameter Block (IPLB) and
IPL Information Report Block (IIRB) since IIRB is stored immediately
following IPLB.
Convert IPLB to pointer and it points to the start of the defined memory space.
IIRB points to the end of IPLB.
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
pc-bios/s390-ccw/iplb.h | 12 ++++++++++--
pc-bios/s390-ccw/jump2ipl.c | 6 +++---
pc-bios/s390-ccw/main.c | 34 +++++++++++++++++++---------------
pc-bios/s390-ccw/netmain.c | 8 ++++----
4 files changed, 36 insertions(+), 24 deletions(-)
diff --git a/pc-bios/s390-ccw/iplb.h b/pc-bios/s390-ccw/iplb.h
index bdbc733e16..11302e004d 100644
--- a/pc-bios/s390-ccw/iplb.h
+++ b/pc-bios/s390-ccw/iplb.h
@@ -20,7 +20,7 @@
#include <string.h>
extern QemuIplParameters qipl;
-extern IplParameterBlock iplb __attribute__((__aligned__(PAGE_SIZE)));
+extern IplParameterBlock *iplb;
extern bool have_iplb;
struct IplInfoReportBlockHeader {
@@ -85,6 +85,14 @@ struct IplInfoReportBlock {
} __attribute__ ((packed));
typedef struct IplInfoReportBlock IplInfoReportBlock;
+struct IplBlocks {
+ IplParameterBlock iplb;
+ IplInfoReportBlock iirb;
+} __attribute__ ((packed));
+typedef struct IplBlocks IplBlocks;
+
+extern IplBlocks ipl_data __attribute__((__aligned__(PAGE_SIZE)));
+
#define S390_IPL_TYPE_FCP 0x00
#define S390_IPL_TYPE_CCW 0x02
#define S390_IPL_TYPE_QEMU_SCSI 0xff
@@ -127,7 +135,7 @@ static inline bool load_next_iplb(void)
qipl.index++;
next_iplb = (IplParameterBlock *) qipl.next_iplb;
- memcpy(&iplb, next_iplb, sizeof(IplParameterBlock));
+ memcpy(iplb, next_iplb, sizeof(IplParameterBlock));
qipl.chain_len--;
qipl.next_iplb = qipl.next_iplb + sizeof(IplParameterBlock);
diff --git a/pc-bios/s390-ccw/jump2ipl.c b/pc-bios/s390-ccw/jump2ipl.c
index 86321d0f46..fa2ca5cbe1 100644
--- a/pc-bios/s390-ccw/jump2ipl.c
+++ b/pc-bios/s390-ccw/jump2ipl.c
@@ -43,11 +43,11 @@ int jump_to_IPL_code(uint64_t address)
* The IPLB for QEMU SCSI type devices must be rebuilt during re-ipl. The
* iplb.devno is set to the boot position of the target SCSI device.
*/
- if (iplb.pbt == S390_IPL_TYPE_QEMU_SCSI) {
- iplb.devno = qipl.index;
+ if (iplb->pbt == S390_IPL_TYPE_QEMU_SCSI) {
+ iplb->devno = qipl.index;
}
- if (have_iplb && !set_iplb(&iplb)) {
+ if (have_iplb && !set_iplb(iplb)) {
panic("Failed to set IPLB");
}
diff --git a/pc-bios/s390-ccw/main.c b/pc-bios/s390-ccw/main.c
index 76bf743900..c9328f1c51 100644
--- a/pc-bios/s390-ccw/main.c
+++ b/pc-bios/s390-ccw/main.c
@@ -22,7 +22,9 @@
static SubChannelId blk_schid = { .one = 1 };
static char loadparm_str[LOADPARM_LEN + 1];
QemuIplParameters qipl;
-IplParameterBlock iplb __attribute__((__aligned__(PAGE_SIZE)));
+/* Ensure that IPLB and IIRB are page aligned and sequential in memory */
+IplBlocks ipl_data;
+IplParameterBlock *iplb;
bool have_iplb;
static uint16_t cutype;
LowCore *lowcore; /* Yes, this *is* a pointer to address 0 */
@@ -51,7 +53,7 @@ void write_subsystem_identification(void)
void write_iplb_location(void)
{
if (cutype == CU_TYPE_VIRTIO && virtio_get_device_type() != VIRTIO_ID_NET) {
- lowcore->ptr_iplb = ptr2u32(&iplb);
+ lowcore->ptr_iplb = ptr2u32(iplb);
}
}
@@ -162,7 +164,7 @@ static void menu_setup(void)
return;
}
- switch (iplb.pbt) {
+ switch (iplb->pbt) {
case S390_IPL_TYPE_CCW:
case S390_IPL_TYPE_QEMU_SCSI:
menu_set_parms(qipl.qipl_flags & BOOT_MENU_FLAG_MASK,
@@ -191,8 +193,8 @@ static void boot_setup(void)
{
char lpmsg[] = "LOADPARM=[________]\n";
- if (have_iplb && memcmp(iplb.loadparm, NO_LOADPARM, LOADPARM_LEN) != 0) {
- ebcdic_to_ascii((char *) iplb.loadparm, loadparm_str, LOADPARM_LEN);
+ if (have_iplb && memcmp(iplb->loadparm, NO_LOADPARM, LOADPARM_LEN) != 0) {
+ ebcdic_to_ascii((char *) iplb->loadparm, loadparm_str, LOADPARM_LEN);
} else {
sclp_get_loadparm_ascii(loadparm_str);
}
@@ -216,21 +218,21 @@ static bool find_boot_device(void)
VDev *vdev = virtio_get_device();
bool found = false;
- switch (iplb.pbt) {
+ switch (iplb->pbt) {
case S390_IPL_TYPE_CCW:
vdev->scsi_device_selected = false;
- debug_print_int("device no. ", iplb.ccw.devno);
- blk_schid.ssid = iplb.ccw.ssid & 0x3;
+ debug_print_int("device no. ", iplb->ccw.devno);
+ blk_schid.ssid = iplb->ccw.ssid & 0x3;
debug_print_int("ssid ", blk_schid.ssid);
- found = find_subch(iplb.ccw.devno);
+ found = find_subch(iplb->ccw.devno);
break;
case S390_IPL_TYPE_QEMU_SCSI:
vdev->scsi_device_selected = true;
- vdev->selected_scsi_device.channel = iplb.scsi.channel;
- vdev->selected_scsi_device.target = iplb.scsi.target;
- vdev->selected_scsi_device.lun = iplb.scsi.lun;
- blk_schid.ssid = iplb.scsi.ssid & 0x3;
- found = find_subch(iplb.scsi.devno);
+ vdev->selected_scsi_device.channel = iplb->scsi.channel;
+ vdev->selected_scsi_device.target = iplb->scsi.target;
+ vdev->selected_scsi_device.lun = iplb->scsi.lun;
+ blk_schid.ssid = iplb->scsi.ssid & 0x3;
+ found = find_subch(iplb->scsi.devno);
break;
default:
puts("Unsupported IPLB");
@@ -311,10 +313,12 @@ static void probe_boot_device(void)
void main(void)
{
+ iplb = &ipl_data.iplb;
+
copy_qipl();
sclp_setup();
css_setup();
- have_iplb = store_iplb(&iplb);
+ have_iplb = store_iplb(iplb);
if (!have_iplb) {
boot_setup();
probe_boot_device();
diff --git a/pc-bios/s390-ccw/netmain.c b/pc-bios/s390-ccw/netmain.c
index 719a547ada..49afd9100d 100644
--- a/pc-bios/s390-ccw/netmain.c
+++ b/pc-bios/s390-ccw/netmain.c
@@ -488,11 +488,11 @@ static bool virtio_setup(void)
*/
enable_mss_facility();
- if (have_iplb || store_iplb(&iplb)) {
- IPL_assert(iplb.pbt == S390_IPL_TYPE_CCW, "IPL_TYPE_CCW expected");
- dev_no = iplb.ccw.devno;
+ if (have_iplb || store_iplb(iplb)) {
+ IPL_assert(iplb->pbt == S390_IPL_TYPE_CCW, "IPL_TYPE_CCW expected");
+ dev_no = iplb->ccw.devno;
debug_print_int("device no. ", dev_no);
- net_schid.ssid = iplb.ccw.ssid & 0x3;
+ net_schid.ssid = iplb->ccw.ssid & 0x3;
debug_print_int("ssid ", net_schid.ssid);
found = find_net_dev(&schib, dev_no);
} else {
--
2.49.0
^ permalink raw reply related [flat|nested] 51+ messages in thread* [PATCH v3 15/28] hw/s390x/ipl: Add IPIB flags to IPL Parameter Block
2025-06-04 21:56 [PATCH v3 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (13 preceding siblings ...)
2025-06-04 21:56 ` [PATCH v3 14/28] pc-bios/s390-ccw: Define memory for IPLB and convert IPLB to pointers Zhuoying Cai
@ 2025-06-04 21:56 ` Zhuoying Cai
2025-06-04 21:56 ` [PATCH v3 16/28] hw/s390x/ipl: Set iplb->len to maximum length of " Zhuoying Cai
` (12 subsequent siblings)
27 siblings, 0 replies; 51+ messages in thread
From: Zhuoying Cai @ 2025-06-04 21:56 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, pbonzini
Cc: walling, jjherne, jrossi, pasic, borntraeger, farman, iii, eblake,
armbru, qemu-s390x, qemu-devel, zycai
Add IPIB flags to IPL Parameter Block to determine if IPL needs to
perform securely and if IPL Information Report Block (IIRB) exists.
Move DIAG308 flags to a separated header file and add flags for secure IPL.
Secure boot in audit mode will perform if certificate(s) exist in the
key store. IIRB will exist and results of verification will be stored in
IIRB.
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
hw/s390x/ipl.c | 20 ++++++++++++++++++++
hw/s390x/ipl.h | 17 -----------------
include/hw/s390x/ipl/diag308.h | 34 ++++++++++++++++++++++++++++++++++
include/hw/s390x/ipl/qipl.h | 5 ++++-
4 files changed, 58 insertions(+), 18 deletions(-)
create mode 100644 include/hw/s390x/ipl/diag308.h
diff --git a/hw/s390x/ipl.c b/hw/s390x/ipl.c
index 186be923d7..8ac0cee73d 100644
--- a/hw/s390x/ipl.c
+++ b/hw/s390x/ipl.c
@@ -430,6 +430,13 @@ S390IPLCertificateStore *s390_ipl_get_certificate_store(void)
return &ipl->cert_store;
}
+static bool s390_has_certificate(void)
+{
+ S390IPLState *ipl = get_ipl_device();
+
+ return ipl->cert_store.count > 0;
+}
+
static bool s390_build_iplb(DeviceState *dev_st, IplParameterBlock *iplb)
{
CcwDevice *ccw_dev = NULL;
@@ -487,6 +494,19 @@ static bool s390_build_iplb(DeviceState *dev_st, IplParameterBlock *iplb)
s390_ipl_convert_loadparm((char *)lp, iplb->loadparm);
iplb->flags |= DIAG308_FLAGS_LP_VALID;
+ /*
+ * Secure boot in audit mode will perform
+ * if certificate(s) exist in the key store.
+ *
+ * IPL Information Report Block (IIRB) will exist
+ * for secure boot in audit mode.
+ *
+ * Results of secure boot will be stored in IIRB.
+ */
+ if (s390_has_certificate()) {
+ iplb->hdr_flags |= DIAG308_IPIB_FLAGS_IPLIR;
+ }
+
return true;
}
diff --git a/hw/s390x/ipl.h b/hw/s390x/ipl.h
index e26fc1cd6a..3b8cc5474e 100644
--- a/hw/s390x/ipl.h
+++ b/hw/s390x/ipl.h
@@ -23,7 +23,6 @@
#include "qom/object.h"
#include "target/s390x/kvm/pv.h"
-#define DIAG308_FLAGS_LP_VALID 0x80
#define MAX_BOOT_DEVS 8 /* Max number of devices that may have a bootindex */
void s390_ipl_convert_loadparm(char *ascii_lp, uint8_t *ebcdic_lp);
@@ -91,22 +90,6 @@ struct S390IPLState {
};
QEMU_BUILD_BUG_MSG(offsetof(S390IPLState, iplb) & 3, "alignment of iplb wrong");
-#define DIAG_308_RC_OK 0x0001
-#define DIAG_308_RC_NO_CONF 0x0102
-#define DIAG_308_RC_INVALID 0x0402
-#define DIAG_308_RC_NO_PV_CONF 0x0902
-#define DIAG_308_RC_INVAL_FOR_PV 0x0a02
-
-#define DIAG308_RESET_MOD_CLR 0
-#define DIAG308_RESET_LOAD_NORM 1
-#define DIAG308_LOAD_CLEAR 3
-#define DIAG308_LOAD_NORMAL_DUMP 4
-#define DIAG308_SET 5
-#define DIAG308_STORE 6
-#define DIAG308_PV_SET 8
-#define DIAG308_PV_STORE 9
-#define DIAG308_PV_START 10
-
#define S390_IPL_TYPE_FCP 0x00
#define S390_IPL_TYPE_CCW 0x02
#define S390_IPL_TYPE_PV 0x05
diff --git a/include/hw/s390x/ipl/diag308.h b/include/hw/s390x/ipl/diag308.h
new file mode 100644
index 0000000000..6e62f29215
--- /dev/null
+++ b/include/hw/s390x/ipl/diag308.h
@@ -0,0 +1,34 @@
+/*
+ * S/390 DIAGNOSE 308 definitions and structures
+ *
+ * Copyright 2025 IBM Corp.
+ * Author(s): Zhuoying Cai <zycai@linux.ibm.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef S390X_DIAG308_H
+#define S390X_DIAG308_H
+
+#define DIAG_308_RC_OK 0x0001
+#define DIAG_308_RC_NO_CONF 0x0102
+#define DIAG_308_RC_INVALID 0x0402
+#define DIAG_308_RC_NO_PV_CONF 0x0902
+#define DIAG_308_RC_INVAL_FOR_PV 0x0a02
+
+#define DIAG308_RESET_MOD_CLR 0
+#define DIAG308_RESET_LOAD_NORM 1
+#define DIAG308_LOAD_CLEAR 3
+#define DIAG308_LOAD_NORMAL_DUMP 4
+#define DIAG308_SET 5
+#define DIAG308_STORE 6
+#define DIAG308_PV_SET 8
+#define DIAG308_PV_STORE 9
+#define DIAG308_PV_START 10
+
+#define DIAG308_FLAGS_LP_VALID 0x80
+
+#define DIAG308_IPIB_FLAGS_SIPL 0x40
+#define DIAG308_IPIB_FLAGS_IPLIR 0x20
+
+#endif
diff --git a/include/hw/s390x/ipl/qipl.h b/include/hw/s390x/ipl/qipl.h
index 2232b88049..b9e6d5f65b 100644
--- a/include/hw/s390x/ipl/qipl.h
+++ b/include/hw/s390x/ipl/qipl.h
@@ -12,6 +12,8 @@
#ifndef S390X_QIPL_H
#define S390X_QIPL_H
+#include "diag308.h"
+
/* Boot Menu flags */
#define QIPL_FLAG_BM_OPTS_CMD 0x80
#define QIPL_FLAG_BM_OPTS_ZIPL 0x40
@@ -104,7 +106,8 @@ typedef struct IplBlockQemuScsi IplBlockQemuScsi;
union IplParameterBlock {
struct {
uint32_t len;
- uint8_t reserved0[3];
+ uint8_t hdr_flags;
+ uint8_t reserved0[2];
uint8_t version;
uint32_t blk0_len;
uint8_t pbt;
--
2.49.0
^ permalink raw reply related [flat|nested] 51+ messages in thread* [PATCH v3 16/28] hw/s390x/ipl: Set iplb->len to maximum length of IPL Parameter Block
2025-06-04 21:56 [PATCH v3 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (14 preceding siblings ...)
2025-06-04 21:56 ` [PATCH v3 15/28] hw/s390x/ipl: Add IPIB flags to IPL Parameter Block Zhuoying Cai
@ 2025-06-04 21:56 ` Zhuoying Cai
2025-06-04 21:56 ` [PATCH v3 17/28] s390x: Guest support for Secure-IPL Facility Zhuoying Cai
` (11 subsequent siblings)
27 siblings, 0 replies; 51+ messages in thread
From: Zhuoying Cai @ 2025-06-04 21:56 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, pbonzini
Cc: walling, jjherne, jrossi, pasic, borntraeger, farman, iii, eblake,
armbru, qemu-s390x, qemu-devel, zycai
The IPL Information Report Block (IIRB) immediately follows the IPL
Parameter Block (IPLB).
The IPLB struct is allocated 4KB in memory, and iplb->len indicates
the amount of memory currently used by the IPLB.
To ensure proper alignment of the IIRB and prevent overlap, set
iplb->len to the maximum length of the IPLB, allowing alignment
constraints to be determined based on its size.
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
hw/s390x/ipl.c | 6 +++---
hw/s390x/ipl.h | 1 +
2 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/hw/s390x/ipl.c b/hw/s390x/ipl.c
index 8ac0cee73d..d1a972ac8d 100644
--- a/hw/s390x/ipl.c
+++ b/hw/s390x/ipl.c
@@ -459,7 +459,7 @@ static bool s390_build_iplb(DeviceState *dev_st, IplParameterBlock *iplb)
if (scsi_lp && strlen(scsi_lp) > 0) {
lp = scsi_lp;
}
- iplb->len = cpu_to_be32(S390_IPLB_MIN_QEMU_SCSI_LEN);
+ iplb->len = cpu_to_be32(S390_IPLB_MAX_LEN);
iplb->blk0_len =
cpu_to_be32(S390_IPLB_MIN_QEMU_SCSI_LEN - S390_IPLB_HEADER_LEN);
iplb->pbt = S390_IPL_TYPE_QEMU_SCSI;
@@ -470,14 +470,14 @@ static bool s390_build_iplb(DeviceState *dev_st, IplParameterBlock *iplb)
iplb->scsi.ssid = ccw_dev->sch->ssid & 3;
break;
case CCW_DEVTYPE_VFIO:
- iplb->len = cpu_to_be32(S390_IPLB_MIN_CCW_LEN);
+ iplb->len = cpu_to_be32(S390_IPLB_MAX_LEN);
iplb->pbt = S390_IPL_TYPE_CCW;
iplb->ccw.devno = cpu_to_be16(ccw_dev->sch->devno);
iplb->ccw.ssid = ccw_dev->sch->ssid & 3;
break;
case CCW_DEVTYPE_VIRTIO_NET:
case CCW_DEVTYPE_VIRTIO:
- iplb->len = cpu_to_be32(S390_IPLB_MIN_CCW_LEN);
+ iplb->len = cpu_to_be32(S390_IPLB_MAX_LEN);
iplb->blk0_len =
cpu_to_be32(S390_IPLB_MIN_CCW_LEN - S390_IPLB_HEADER_LEN);
iplb->pbt = S390_IPL_TYPE_CCW;
diff --git a/hw/s390x/ipl.h b/hw/s390x/ipl.h
index 3b8cc5474e..01922d80c4 100644
--- a/hw/s390x/ipl.h
+++ b/hw/s390x/ipl.h
@@ -100,6 +100,7 @@ QEMU_BUILD_BUG_MSG(offsetof(S390IPLState, iplb) & 3, "alignment of iplb wrong");
#define S390_IPLB_MIN_CCW_LEN 200
#define S390_IPLB_MIN_FCP_LEN 384
#define S390_IPLB_MIN_QEMU_SCSI_LEN 200
+#define S390_IPLB_MAX_LEN 4096
static inline bool diag_parm_addr_valid(uint64_t addr, size_t size, bool write)
{
--
2.49.0
^ permalink raw reply related [flat|nested] 51+ messages in thread* [PATCH v3 17/28] s390x: Guest support for Secure-IPL Facility
2025-06-04 21:56 [PATCH v3 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (15 preceding siblings ...)
2025-06-04 21:56 ` [PATCH v3 16/28] hw/s390x/ipl: Set iplb->len to maximum length of " Zhuoying Cai
@ 2025-06-04 21:56 ` Zhuoying Cai
2025-06-04 21:56 ` [PATCH v3 18/28] pc-bios/s390-ccw: Refactor zipl_run() Zhuoying Cai
` (10 subsequent siblings)
27 siblings, 0 replies; 51+ messages in thread
From: Zhuoying Cai @ 2025-06-04 21:56 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, pbonzini
Cc: walling, jjherne, jrossi, pasic, borntraeger, farman, iii, eblake,
armbru, qemu-s390x, qemu-devel, zycai
Introduce Secure-IPL (SIPL) facility.
Use fac_ipl to represent bytes 136 and 137 for IPL device facilities
of the SCLP Read Info block.
Availability of SIPL facility is determined by byte 136 bit 1 of the
SCLP Read Info block. Byte 136's facilities cannot be represented
without the availability of the extended-length-SCCB, so add it as a
check for consistency.
When SIPL facility is installed, the IPL Parameter Block length must
contains value that is multiple of 8 bytes.
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
hw/s390x/sclp.c | 2 ++
include/hw/s390x/sclp.h | 4 +++-
target/s390x/cpu_features.c | 3 +++
target/s390x/cpu_features.h | 1 +
target/s390x/cpu_features_def.h.inc | 3 +++
target/s390x/cpu_models.c | 2 ++
target/s390x/gen-features.c | 2 ++
target/s390x/kvm/kvm.c | 3 +++
8 files changed, 19 insertions(+), 1 deletion(-)
diff --git a/hw/s390x/sclp.c b/hw/s390x/sclp.c
index 9718564fa4..69d3328a3d 100644
--- a/hw/s390x/sclp.c
+++ b/hw/s390x/sclp.c
@@ -145,6 +145,8 @@ static void read_SCP_info(SCLPDevice *sclp, SCCB *sccb)
if (s390_has_feat(S390_FEAT_EXTENDED_LENGTH_SCCB)) {
s390_get_feat_block(S390_FEAT_TYPE_SCLP_FAC134,
&read_info->fac134);
+ s390_get_feat_block(S390_FEAT_TYPE_SCLP_FAC_IPL,
+ read_info->fac_ipl);
}
read_info->facilities = cpu_to_be64(SCLP_HAS_CPU_INFO |
diff --git a/include/hw/s390x/sclp.h b/include/hw/s390x/sclp.h
index d32f6180e0..bfd330c340 100644
--- a/include/hw/s390x/sclp.h
+++ b/include/hw/s390x/sclp.h
@@ -136,7 +136,9 @@ typedef struct ReadInfo {
uint32_t hmfai;
uint8_t _reserved7[134 - 128]; /* 128-133 */
uint8_t fac134;
- uint8_t _reserved8[144 - 135]; /* 135-143 */
+ uint8_t _reserved8;
+ uint8_t fac_ipl[2]; /* 136-137 */
+ uint8_t _reserved9[144 - 137]; /* 138-143 */
struct CPUEntry entries[];
/*
* When the Extended-Length SCCB (ELS) feature is enabled the
diff --git a/target/s390x/cpu_features.c b/target/s390x/cpu_features.c
index 99089ab3f5..3f3d6a80af 100644
--- a/target/s390x/cpu_features.c
+++ b/target/s390x/cpu_features.c
@@ -149,6 +149,9 @@ void s390_fill_feat_block(const S390FeatBitmap features, S390FeatType type,
clear_be_bit(s390_feat_def(S390_FEAT_DIAG_318)->bit, data);
clear_be_bit(s390_feat_def(S390_FEAT_DIAG_320)->bit, data);
break;
+ case S390_FEAT_TYPE_SCLP_FAC_IPL:
+ clear_be_bit(s390_feat_def(S390_FEAT_SIPL)->bit, data);
+ break;
default:
return;
}
diff --git a/target/s390x/cpu_features.h b/target/s390x/cpu_features.h
index 5635839d03..b038198555 100644
--- a/target/s390x/cpu_features.h
+++ b/target/s390x/cpu_features.h
@@ -24,6 +24,7 @@ typedef enum {
S390_FEAT_TYPE_SCLP_CONF_CHAR,
S390_FEAT_TYPE_SCLP_CONF_CHAR_EXT,
S390_FEAT_TYPE_SCLP_FAC134,
+ S390_FEAT_TYPE_SCLP_FAC_IPL,
S390_FEAT_TYPE_SCLP_CPU,
S390_FEAT_TYPE_MISC,
S390_FEAT_TYPE_PLO,
diff --git a/target/s390x/cpu_features_def.h.inc b/target/s390x/cpu_features_def.h.inc
index 7b13a95d98..956bd8a123 100644
--- a/target/s390x/cpu_features_def.h.inc
+++ b/target/s390x/cpu_features_def.h.inc
@@ -140,6 +140,9 @@ DEF_FEAT(SIE_IBS, "ibs", SCLP_CONF_CHAR_EXT, 10, "SIE: Interlock-and-broadcast-s
DEF_FEAT(DIAG_318, "diag318", SCLP_FAC134, 0, "Control program name and version codes")
DEF_FEAT(DIAG_320, "cstore", SCLP_FAC134, 5, "Provide Certificate Store functions")
+/* Features exposed via SCLP SCCB Facilities byte 136 - 137 (bit numbers relative to byte-136) */
+DEF_FEAT(SIPL, "sipl", SCLP_FAC_IPL, 1, "Secure-IPL facility")
+
/* Features exposed via SCLP CPU info. */
DEF_FEAT(SIE_F2, "sief2", SCLP_CPU, 4, "SIE: interception format 2 (Virtual SIE)")
DEF_FEAT(SIE_SKEY, "skey", SCLP_CPU, 5, "SIE: Storage-key facility")
diff --git a/target/s390x/cpu_models.c b/target/s390x/cpu_models.c
index 33ef5c190c..ab46204d9e 100644
--- a/target/s390x/cpu_models.c
+++ b/target/s390x/cpu_models.c
@@ -263,6 +263,7 @@ bool s390_has_feat(S390Feat feat)
case S390_FEAT_SIE_CMMA:
case S390_FEAT_SIE_PFMFI:
case S390_FEAT_SIE_IBS:
+ case S390_FEAT_SIPL:
case S390_FEAT_CONFIGURATION_TOPOLOGY:
return false;
break;
@@ -507,6 +508,7 @@ static void check_consistency(const S390CPUModel *model)
{ S390_FEAT_AP_QUEUE_INTERRUPT_CONTROL, S390_FEAT_AP },
{ S390_FEAT_DIAG_318, S390_FEAT_EXTENDED_LENGTH_SCCB },
{ S390_FEAT_DIAG_320, S390_FEAT_EXTENDED_LENGTH_SCCB },
+ { S390_FEAT_SIPL, S390_FEAT_EXTENDED_LENGTH_SCCB },
{ S390_FEAT_NNPA, S390_FEAT_VECTOR },
{ S390_FEAT_RDP, S390_FEAT_LOCAL_TLB_CLEARING },
{ S390_FEAT_UV_FEAT_AP, S390_FEAT_AP },
diff --git a/target/s390x/gen-features.c b/target/s390x/gen-features.c
index 982d6122ac..dd9c2eba07 100644
--- a/target/s390x/gen-features.c
+++ b/target/s390x/gen-features.c
@@ -721,6 +721,7 @@ static uint16_t full_GEN16_GA1[] = {
S390_FEAT_UV_FEAT_AP,
S390_FEAT_UV_FEAT_AP_INTR,
S390_FEAT_DIAG_320,
+ S390_FEAT_SIPL,
};
static uint16_t full_GEN17_GA1[] = {
@@ -925,6 +926,7 @@ static uint16_t qemu_MAX[] = {
S390_FEAT_PRNO_TRNG,
S390_FEAT_EXTENDED_LENGTH_SCCB,
S390_FEAT_DIAG_320,
+ S390_FEAT_SIPL,
};
/****** END FEATURE DEFS ******/
diff --git a/target/s390x/kvm/kvm.c b/target/s390x/kvm/kvm.c
index 840330709b..fc9cad32a1 100644
--- a/target/s390x/kvm/kvm.c
+++ b/target/s390x/kvm/kvm.c
@@ -2520,6 +2520,9 @@ bool kvm_s390_get_host_cpu_model(S390CPUModel *model, Error **errp)
set_bit(S390_FEAT_DIAG_320, model->features);
+ /* Secure-IPL facility is handled entirely within QEMU */
+ set_bit(S390_FEAT_SIPL, model->features);
+
/* Test for Ultravisor features that influence secure guest behavior */
query_uv_feat_guest(model->features);
--
2.49.0
^ permalink raw reply related [flat|nested] 51+ messages in thread* [PATCH v3 18/28] pc-bios/s390-ccw: Refactor zipl_run()
2025-06-04 21:56 [PATCH v3 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (16 preceding siblings ...)
2025-06-04 21:56 ` [PATCH v3 17/28] s390x: Guest support for Secure-IPL Facility Zhuoying Cai
@ 2025-06-04 21:56 ` Zhuoying Cai
2025-06-04 21:56 ` [PATCH v3 19/28] pc-bios/s390-ccw: Refactor zipl_load_segment function Zhuoying Cai
` (9 subsequent siblings)
27 siblings, 0 replies; 51+ messages in thread
From: Zhuoying Cai @ 2025-06-04 21:56 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, pbonzini
Cc: walling, jjherne, jrossi, pasic, borntraeger, farman, iii, eblake,
armbru, qemu-s390x, qemu-devel, zycai
Refactor to enhance readability before enabling secure IPL in later
patches.
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
pc-bios/s390-ccw/bootmap.c | 58 ++++++++++++++++++++++----------------
1 file changed, 34 insertions(+), 24 deletions(-)
diff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c
index 0f8baa0198..ced5190888 100644
--- a/pc-bios/s390-ccw/bootmap.c
+++ b/pc-bios/s390-ccw/bootmap.c
@@ -674,6 +674,38 @@ static int zipl_load_segment(ComponentEntry *entry)
return 0;
}
+static int zipl_run_normal(ComponentEntry *entry, uint8_t *tmp_sec)
+{
+ while (entry->component_type == ZIPL_COMP_ENTRY_LOAD ||
+ entry->component_type == ZIPL_COMP_ENTRY_SIGNATURE) {
+
+ /* Secure boot is off, so we skip signature entries */
+ if (entry->component_type == ZIPL_COMP_ENTRY_SIGNATURE) {
+ entry++;
+ continue;
+ }
+
+ if (zipl_load_segment(entry)) {
+ return -1;
+ }
+
+ entry++;
+
+ if ((uint8_t *)&entry[1] > tmp_sec + MAX_SECTOR_SIZE) {
+ puts("Wrong entry value");
+ return -EINVAL;
+ }
+ }
+
+ if (entry->component_type != ZIPL_COMP_ENTRY_EXEC) {
+ puts("No EXEC entry");
+ return -EINVAL;
+ }
+
+ write_reset_psw(entry->compdat.load_psw);
+ return 0;
+}
+
/* Run a zipl program */
static int zipl_run(ScsiBlockPtr *pte)
{
@@ -700,34 +732,12 @@ static int zipl_run(ScsiBlockPtr *pte)
/* Load image(s) into RAM */
entry = (ComponentEntry *)(&header[1]);
- while (entry->component_type == ZIPL_COMP_ENTRY_LOAD ||
- entry->component_type == ZIPL_COMP_ENTRY_SIGNATURE) {
-
- /* We don't support secure boot yet, so we skip signature entries */
- if (entry->component_type == ZIPL_COMP_ENTRY_SIGNATURE) {
- entry++;
- continue;
- }
-
- if (zipl_load_segment(entry)) {
- return -1;
- }
- entry++;
-
- if ((uint8_t *)(&entry[1]) > (tmp_sec + MAX_SECTOR_SIZE)) {
- puts("Wrong entry value");
- return -EINVAL;
- }
- }
-
- if (entry->component_type != ZIPL_COMP_ENTRY_EXEC) {
- puts("No EXEC entry");
- return -EINVAL;
+ if (zipl_run_normal(entry, tmp_sec)) {
+ return -1;
}
/* should not return */
- write_reset_psw(entry->compdat.load_psw);
jump_to_IPL_code(0);
return -1;
}
--
2.49.0
^ permalink raw reply related [flat|nested] 51+ messages in thread* [PATCH v3 19/28] pc-bios/s390-ccw: Refactor zipl_load_segment function
2025-06-04 21:56 [PATCH v3 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (17 preceding siblings ...)
2025-06-04 21:56 ` [PATCH v3 18/28] pc-bios/s390-ccw: Refactor zipl_run() Zhuoying Cai
@ 2025-06-04 21:56 ` Zhuoying Cai
2025-06-04 21:56 ` [PATCH v3 20/28] pc-bios/s390-ccw: Add signature verification for secure IPL in audit mode Zhuoying Cai
` (8 subsequent siblings)
27 siblings, 0 replies; 51+ messages in thread
From: Zhuoying Cai @ 2025-06-04 21:56 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, pbonzini
Cc: walling, jjherne, jrossi, pasic, borntraeger, farman, iii, eblake,
armbru, qemu-s390x, qemu-devel, zycai
Make the address variable a parameter of zipl_load_segment and return
segment length.
Modify this function for reuse in the next patch, which allows
loading segment or signature data to the destination memory address.
Add a comp_len variable to store the length of a segment and return this
variable in zipl_load_segment.
comp_len variable is necessary to store the calculated segment length and
is used during signature verification. Return the length on success, or
a negative return code on failure.
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
pc-bios/s390-ccw/bootmap.c | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c
index ced5190888..2513e6c131 100644
--- a/pc-bios/s390-ccw/bootmap.c
+++ b/pc-bios/s390-ccw/bootmap.c
@@ -613,19 +613,18 @@ static int ipl_eckd(void)
* IPL a SCSI disk
*/
-static int zipl_load_segment(ComponentEntry *entry)
+static int zipl_load_segment(ComponentEntry *entry, uint64_t address)
{
const int max_entries = (MAX_SECTOR_SIZE / sizeof(ScsiBlockPtr));
ScsiBlockPtr *bprs = (void *)sec;
const int bprs_size = sizeof(sec);
block_number_t blockno;
- uint64_t address;
int i;
char err_msg[] = "zIPL failed to read BPRS at 0xZZZZZZZZZZZZZZZZ";
char *blk_no = &err_msg[30]; /* where to print blockno in (those ZZs) */
+ int comp_len = 0;
blockno = entry->data.blockno;
- address = entry->compdat.load_addr;
debug_print_int("loading segment at block", blockno);
debug_print_int("addr", address);
@@ -662,6 +661,9 @@ static int zipl_load_segment(ComponentEntry *entry)
*/
break;
}
+
+ comp_len += bprs->size * (bprs[i].blockct + 1);
+
address = virtio_load_direct(cur_desc[0], cur_desc[1], 0,
(void *)address);
if (!address) {
@@ -671,7 +673,7 @@ static int zipl_load_segment(ComponentEntry *entry)
}
} while (blockno);
- return 0;
+ return comp_len;
}
static int zipl_run_normal(ComponentEntry *entry, uint8_t *tmp_sec)
@@ -685,7 +687,7 @@ static int zipl_run_normal(ComponentEntry *entry, uint8_t *tmp_sec)
continue;
}
- if (zipl_load_segment(entry)) {
+ if (zipl_load_segment(entry, entry->compdat.load_addr) < 0) {
return -1;
}
--
2.49.0
^ permalink raw reply related [flat|nested] 51+ messages in thread* [PATCH v3 20/28] pc-bios/s390-ccw: Add signature verification for secure IPL in audit mode
2025-06-04 21:56 [PATCH v3 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (18 preceding siblings ...)
2025-06-04 21:56 ` [PATCH v3 19/28] pc-bios/s390-ccw: Refactor zipl_load_segment function Zhuoying Cai
@ 2025-06-04 21:56 ` Zhuoying Cai
2025-06-04 21:56 ` [PATCH v3 21/28] s390x: Guest support for Secure-IPL Code Loading Attributes Facility (SCLAF) Zhuoying Cai
` (7 subsequent siblings)
27 siblings, 0 replies; 51+ messages in thread
From: Zhuoying Cai @ 2025-06-04 21:56 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, pbonzini
Cc: walling, jjherne, jrossi, pasic, borntraeger, farman, iii, eblake,
armbru, qemu-s390x, qemu-devel, zycai
Enable secure IPL in audit mode, which performs signature verification,
but any error does not terminate the boot process. Only warnings will be
logged to the console instead.
Add a comp_len variable to store the length of a segment in
zipl_load_segment. comp_len variable is necessary to store the
calculated segment length and is used during signature verification.
Return the length on success, or a negative return code on failure.
Secure IPL in audit mode requires at least one certificate provided in
the key store along with necessary facilities (Secure IPL Facility,
Certificate Store Facility and secure IPL extension support).
Note: Secure IPL in audit mode is implemented for the SCSI scheme of
virtio-blk/virtio-scsi devices.
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
pc-bios/s390-ccw/Makefile | 3 +-
pc-bios/s390-ccw/bootmap.c | 192 +++++++++++++++++++++++++++++++++-
pc-bios/s390-ccw/bootmap.h | 9 ++
pc-bios/s390-ccw/main.c | 9 ++
pc-bios/s390-ccw/s390-ccw.h | 14 +++
pc-bios/s390-ccw/sclp.c | 44 ++++++++
pc-bios/s390-ccw/sclp.h | 6 ++
pc-bios/s390-ccw/secure-ipl.c | 175 +++++++++++++++++++++++++++++++
pc-bios/s390-ccw/secure-ipl.h | 97 +++++++++++++++++
9 files changed, 546 insertions(+), 3 deletions(-)
create mode 100644 pc-bios/s390-ccw/secure-ipl.c
create mode 100644 pc-bios/s390-ccw/secure-ipl.h
diff --git a/pc-bios/s390-ccw/Makefile b/pc-bios/s390-ccw/Makefile
index dc69dd484f..fedb89a387 100644
--- a/pc-bios/s390-ccw/Makefile
+++ b/pc-bios/s390-ccw/Makefile
@@ -34,7 +34,8 @@ QEMU_DGFLAGS = -MMD -MP -MT $@ -MF $(@D)/$(*F).d
.PHONY : all clean build-all distclean
OBJECTS = start.o main.o bootmap.o jump2ipl.o sclp.o menu.o netmain.o \
- virtio.o virtio-net.o virtio-scsi.o virtio-blkdev.o cio.o dasd-ipl.o
+ virtio.o virtio-net.o virtio-scsi.o virtio-blkdev.o cio.o dasd-ipl.o \
+ secure-ipl.o
SLOF_DIR := $(SRC_PATH)/../../roms/SLOF
diff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c
index 2513e6c131..d4b53024a1 100644
--- a/pc-bios/s390-ccw/bootmap.c
+++ b/pc-bios/s390-ccw/bootmap.c
@@ -8,6 +8,7 @@
* directory.
*/
+#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "s390-ccw.h"
@@ -15,6 +16,7 @@
#include "bootmap.h"
#include "virtio.h"
#include "bswap.h"
+#include "secure-ipl.h"
#ifdef DEBUG
/* #define DEBUG_FALLBACK */
@@ -34,6 +36,9 @@ static uint8_t sec[MAX_SECTOR_SIZE*4] __attribute__((__aligned__(PAGE_SIZE)));
const uint8_t el_torito_magic[] = "EL TORITO SPECIFICATION"
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
+/* sector for storing signatures */
+static uint8_t sig_sec[MAX_SECTOR_SIZE] __attribute__((__aligned__(PAGE_SIZE)));
+
/*
* Match two CCWs located after PSW and eight filler bytes.
* From libmagic and arch/s390/kernel/head.S.
@@ -676,6 +681,158 @@ static int zipl_load_segment(ComponentEntry *entry, uint64_t address)
return comp_len;
}
+static uint32_t zipl_handle_sig_entry(ComponentEntry *entry)
+{
+ uint32_t sig_len;
+
+ if (zipl_load_segment(entry, (uint64_t)sig_sec) < 0) {
+ return -1;
+ }
+
+ if (entry->compdat.sig_info.format != DER_SIGNATURE_FORMAT) {
+ puts("Signature is not in DER format");
+ return -1;
+ }
+ sig_len = entry->compdat.sig_info.sig_len;
+
+ return sig_len;
+}
+
+static int handle_certificate(int *cert_table, uint64_t **cert,
+ uint64_t cert_len, uint8_t cert_idx,
+ IplSignatureCertificateList *certs, int cert_index)
+{
+ bool unused;
+
+ unused = cert_table[cert_idx] == -1;
+ if (unused) {
+ if (zipl_secure_request_certificate(*cert, cert_idx)) {
+ zipl_secure_cert_list_add(certs, cert_index, *cert, cert_len);
+ cert_table[cert_idx] = cert_index;
+ *cert += cert_len;
+ } else {
+ puts("Could not get certificate");
+ return -1;
+ }
+
+ /* increment cert_index for the next cert entry */
+ return ++cert_index;
+ }
+
+ return cert_index;
+}
+
+static int zipl_run_secure(ComponentEntry *entry, uint8_t *tmp_sec)
+{
+ bool found_signature = false;
+ IplDeviceComponentList comps;
+ IplSignatureCertificateList certs;
+ uint64_t *cert = NULL;
+ int cert_index = 0;
+ int comp_index = 0;
+ uint64_t comp_addr;
+ int comp_len;
+ bool have_sig;
+ uint32_t sig_len;
+ uint64_t cert_len = -1;
+ uint8_t cert_idx = -1;
+ bool verified;
+ /*
+ * Store indices of cert entry that have already used for signature verification
+ * to prevent allocating the same certificate multiple times.
+ * cert_table index: index of certificate from qemu cert store used for verification
+ * cert_table value: index of cert entry in cert list that contains the certificate
+ */
+ int cert_table[MAX_CERTIFICATES] = { [0 ... MAX_CERTIFICATES - 1] = -1};
+
+ if (!zipl_secure_ipl_supported()) {
+ return -1;
+ }
+
+ zipl_secure_init_lists(&comps, &certs);
+ cert = malloc(CERT_MAX_SIZE * MAX_CERTIFICATES);
+
+ have_sig = false;
+ while (entry->component_type == ZIPL_COMP_ENTRY_LOAD ||
+ entry->component_type == ZIPL_COMP_ENTRY_SIGNATURE) {
+
+ if (entry->component_type == ZIPL_COMP_ENTRY_SIGNATURE) {
+ /* There should never be two signatures in a row */
+ if (have_sig) {
+ return -1;
+ }
+
+ sig_len = zipl_handle_sig_entry(entry);
+ if (sig_len < 0) {
+ return -1;
+ }
+
+ have_sig = true;
+ } else {
+ comp_addr = entry->compdat.load_addr;
+ comp_len = zipl_load_segment(entry, comp_addr);
+ if (comp_len < 0) {
+ return -1;
+ }
+
+ if (have_sig) {
+ verified = verify_signature(comp_len, comp_addr,
+ sig_len, (uint64_t)sig_sec,
+ &cert_len, &cert_idx);
+
+ if (verified) {
+ cert_index = handle_certificate(cert_table, &cert,
+ cert_len, cert_idx,
+ &certs, cert_index);
+ if (cert_index == -1) {
+ return -1;
+ }
+
+ puts("Verified component");
+ zipl_secure_comp_list_add(&comps, comp_index, cert_table[cert_idx],
+ comp_addr, comp_len,
+ S390_IPL_COMPONENT_FLAG_SC |
+ S390_IPL_COMPONENT_FLAG_CSV);
+ } else {
+ zipl_secure_comp_list_add(&comps, comp_index, -1,
+ comp_addr, comp_len,
+ S390_IPL_COMPONENT_FLAG_SC);
+ zipl_secure_print(verified, "Could not verify component");
+ }
+
+ comp_index++;
+ found_signature = true;
+ /* After a signature is used another new one can be accepted */
+ have_sig = false;
+ }
+ }
+
+ entry++;
+
+ if ((uint8_t *)(&entry[1]) > tmp_sec + MAX_SECTOR_SIZE) {
+ puts("Wrong entry value");
+ return -EINVAL;
+ }
+ }
+
+ if (entry->component_type != ZIPL_COMP_ENTRY_EXEC) {
+ puts("No EXEC entry");
+ return -EINVAL;
+ }
+
+ if (!found_signature) {
+ zipl_secure_print(found_signature,
+ "Secure boot is on, but components are not signed");
+ }
+
+ if (zipl_secure_update_iirb(&comps, &certs)) {
+ zipl_secure_print(false, "Failed to write IPL Information Report Block");
+ }
+ write_reset_psw(entry->compdat.load_psw);
+
+ return 0;
+}
+
static int zipl_run_normal(ComponentEntry *entry, uint8_t *tmp_sec)
{
while (entry->component_type == ZIPL_COMP_ENTRY_LOAD ||
@@ -735,8 +892,17 @@ static int zipl_run(ScsiBlockPtr *pte)
/* Load image(s) into RAM */
entry = (ComponentEntry *)(&header[1]);
- if (zipl_run_normal(entry, tmp_sec)) {
- return -1;
+ switch (boot_mode) {
+ case ZIPL_SECURE_AUDIT_MODE:
+ if (zipl_run_secure(entry, tmp_sec)) {
+ return -1;
+ }
+ break;
+ case ZIPL_NORMAL_MODE:
+ if (zipl_run_normal(entry, tmp_sec)) {
+ return -1;
+ }
+ break;
}
/* should not return */
@@ -1095,17 +1261,35 @@ static int zipl_load_vscsi(void)
* IPL starts here
*/
+ZiplBootMode zipl_mode(uint8_t hdr_flags)
+{
+ bool sipl_set = hdr_flags & DIAG308_IPIB_FLAGS_SIPL;
+ bool iplir_set = hdr_flags & DIAG308_IPIB_FLAGS_IPLIR;
+
+ if (!sipl_set && iplir_set) {
+ return ZIPL_SECURE_AUDIT_MODE;
+ }
+
+ return ZIPL_NORMAL_MODE;
+}
+
void zipl_load(void)
{
VDev *vdev = virtio_get_device();
if (vdev->is_cdrom) {
+ if (boot_mode == ZIPL_SECURE_AUDIT_MODE) {
+ panic("Secure boot from ISO image is not supported!");
+ }
ipl_iso_el_torito();
puts("Failed to IPL this ISO image!");
return;
}
if (virtio_get_device_type() == VIRTIO_ID_NET) {
+ if (boot_mode == ZIPL_SECURE_AUDIT_MODE) {
+ panic("Virtio net boot device does not support secure boot!");
+ }
netmain();
puts("Failed to IPL from this network!");
return;
@@ -1116,6 +1300,10 @@ void zipl_load(void)
return;
}
+ if (boot_mode == ZIPL_SECURE_AUDIT_MODE) {
+ panic("ECKD boot device does not support secure boot!");
+ }
+
switch (virtio_get_device_type()) {
case VIRTIO_ID_BLOCK:
zipl_load_vblk();
diff --git a/pc-bios/s390-ccw/bootmap.h b/pc-bios/s390-ccw/bootmap.h
index 95943441d3..e48823a835 100644
--- a/pc-bios/s390-ccw/bootmap.h
+++ b/pc-bios/s390-ccw/bootmap.h
@@ -88,9 +88,18 @@ typedef struct BootMapTable {
BootMapPointer entry[];
} __attribute__ ((packed)) BootMapTable;
+#define DER_SIGNATURE_FORMAT 1
+
+typedef struct SignatureInformation {
+ uint8_t format;
+ uint8_t reserved[3];
+ uint32_t sig_len;
+} __attribute__((packed)) SignatureInformation;
+
typedef union ComponentEntryData {
uint64_t load_psw;
uint64_t load_addr;
+ SignatureInformation sig_info;
} ComponentEntryData;
typedef struct ComponentEntry {
diff --git a/pc-bios/s390-ccw/main.c b/pc-bios/s390-ccw/main.c
index c9328f1c51..38962da1dd 100644
--- a/pc-bios/s390-ccw/main.c
+++ b/pc-bios/s390-ccw/main.c
@@ -28,6 +28,7 @@ IplParameterBlock *iplb;
bool have_iplb;
static uint16_t cutype;
LowCore *lowcore; /* Yes, this *is* a pointer to address 0 */
+ZiplBootMode boot_mode;
#define LOADPARM_PROMPT "PROMPT "
#define LOADPARM_EMPTY " "
@@ -272,9 +273,17 @@ static int virtio_setup(void)
static void ipl_boot_device(void)
{
+ if (boot_mode == 0) {
+ boot_mode = zipl_mode(iplb->hdr_flags);
+ }
+
switch (cutype) {
case CU_TYPE_DASD_3990:
case CU_TYPE_DASD_2107:
+ if (boot_mode == ZIPL_SECURE_AUDIT_MODE) {
+ panic("Passthrough (vfio) device does not support secure boot!");
+ }
+
dasd_ipl(blk_schid, cutype);
break;
case CU_TYPE_VIRTIO:
diff --git a/pc-bios/s390-ccw/s390-ccw.h b/pc-bios/s390-ccw/s390-ccw.h
index 6cdce3e5e5..648f407dc5 100644
--- a/pc-bios/s390-ccw/s390-ccw.h
+++ b/pc-bios/s390-ccw/s390-ccw.h
@@ -39,6 +39,9 @@ typedef unsigned long long u64;
#define MIN_NON_ZERO(a, b) ((a) == 0 ? (b) : \
((b) == 0 ? (a) : (MIN(a, b))))
#endif
+#ifndef ROUND_UP
+#define ROUND_UP(n, d) (((n) + (d) - 1) & -(0 ? (n) : (d)))
+#endif
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
@@ -64,6 +67,8 @@ void sclp_print(const char *string);
void sclp_set_write_mask(uint32_t receive_mask, uint32_t send_mask);
void sclp_setup(void);
void sclp_get_loadparm_ascii(char *loadparm);
+bool sclp_is_diag320_on(void);
+bool sclp_is_sipl_on(void);
int sclp_read(char *str, size_t count);
/* virtio.c */
@@ -76,6 +81,15 @@ int virtio_read(unsigned long sector, void *load_addr);
/* bootmap.c */
void zipl_load(void);
+typedef enum ZiplBootMode {
+ ZIPL_NORMAL_MODE = 1,
+ ZIPL_SECURE_AUDIT_MODE = 2,
+} ZiplBootMode;
+
+extern ZiplBootMode boot_mode;
+
+ZiplBootMode zipl_mode(uint8_t hdr_flags);
+
/* jump2ipl.c */
void write_reset_psw(uint64_t psw);
int jump_to_IPL_code(uint64_t address);
diff --git a/pc-bios/s390-ccw/sclp.c b/pc-bios/s390-ccw/sclp.c
index 4a07de018d..0b03c3164f 100644
--- a/pc-bios/s390-ccw/sclp.c
+++ b/pc-bios/s390-ccw/sclp.c
@@ -113,6 +113,50 @@ void sclp_get_loadparm_ascii(char *loadparm)
}
}
+static void sclp_get_fac134(uint8_t *fac134)
+{
+
+ ReadInfo *sccb = (void *)_sccb;
+
+ memset((char *)_sccb, 0, sizeof(ReadInfo));
+ sccb->h.length = SCCB_SIZE;
+ if (!sclp_service_call(SCLP_CMDW_READ_SCP_INFO, sccb)) {
+ *fac134 = sccb->fac134;
+ }
+}
+
+bool sclp_is_diag320_on(void)
+{
+ uint8_t fac134 = 0;
+
+ sclp_get_fac134(&fac134);
+ return fac134 & SCCB_FAC134_DIAG320_BIT;
+}
+
+/*
+ * Get fac_ipl (byte 136 and byte 137 of the SCLP Read Info block)
+ * for IPL device facilities.
+ */
+static void sclp_get_fac_ipl(uint16_t *fac_ipl)
+{
+
+ ReadInfo *sccb = (void *)_sccb;
+
+ memset((char *)_sccb, 0, sizeof(ReadInfo));
+ sccb->h.length = SCCB_SIZE;
+ if (!sclp_service_call(SCLP_CMDW_READ_SCP_INFO, sccb)) {
+ *fac_ipl = sccb->fac_ipl;
+ }
+}
+
+bool sclp_is_sipl_on(void)
+{
+ uint16_t fac_ipl = 0;
+
+ sclp_get_fac_ipl(&fac_ipl);
+ return fac_ipl & SCCB_FAC_IPL_SIPL_BIT;
+}
+
int sclp_read(char *str, size_t count)
{
ReadEventData *sccb = (void *)_sccb;
diff --git a/pc-bios/s390-ccw/sclp.h b/pc-bios/s390-ccw/sclp.h
index 64b53cad29..cf147f4634 100644
--- a/pc-bios/s390-ccw/sclp.h
+++ b/pc-bios/s390-ccw/sclp.h
@@ -50,6 +50,8 @@ typedef struct SCCBHeader {
} __attribute__((packed)) SCCBHeader;
#define SCCB_DATA_LEN (SCCB_SIZE - sizeof(SCCBHeader))
+#define SCCB_FAC134_DIAG320_BIT 0x4
+#define SCCB_FAC_IPL_SIPL_BIT 0x4000
typedef struct ReadInfo {
SCCBHeader h;
@@ -57,6 +59,10 @@ typedef struct ReadInfo {
uint8_t rnsize;
uint8_t reserved[13];
uint8_t loadparm[LOADPARM_LEN];
+ uint8_t reserved1[102];
+ uint8_t fac134;
+ uint8_t reserved2;
+ uint16_t fac_ipl;
} __attribute__((packed)) ReadInfo;
typedef struct SCCB {
diff --git a/pc-bios/s390-ccw/secure-ipl.c b/pc-bios/s390-ccw/secure-ipl.c
new file mode 100644
index 0000000000..da795079f4
--- /dev/null
+++ b/pc-bios/s390-ccw/secure-ipl.c
@@ -0,0 +1,175 @@
+/*
+ * S/390 Secure IPL
+ *
+ * Copyright 2025 IBM Corp.
+ * Author(s): Zhuoying Cai <zycai@linux.ibm.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include "s390-ccw.h"
+#include "secure-ipl.h"
+
+uint8_t vcb_data[MAX_SECTOR_SIZE * 4] __attribute__((__aligned__(PAGE_SIZE)));
+uint8_t vcssb_data[VCSSB_MAX_LEN] __attribute__((__aligned__(PAGE_SIZE)));
+
+VCStorageSizeBlock *zipl_secure_get_vcssb(void)
+{
+ VCStorageSizeBlock *vcssb;
+ int rc;
+
+ vcssb = (VCStorageSizeBlock *)vcssb_data;
+ /* avoid retrieving vcssb multiple times */
+ if (vcssb->length == VCSSB_MAX_LEN) {
+ return vcssb;
+ }
+
+ rc = diag320(vcssb, DIAG_320_SUBC_QUERY_VCSI);
+ if (rc != DIAG_320_RC_OK) {
+ return NULL;
+ }
+
+ return vcssb;
+}
+
+uint32_t zipl_secure_request_certificate(uint64_t *cert, uint8_t index)
+{
+ VCStorageSizeBlock *vcssb;
+ VCBlock *vcb;
+ VCEntry *vce;
+ uint64_t rc = 0;
+ uint32_t cert_len = 0;
+
+ /* Get Verification Certificate Storage Size block with DIAG320 subcode 1 */
+ vcssb = zipl_secure_get_vcssb();
+ if (vcssb == NULL) {
+ return 0;
+ }
+
+ /*
+ * Request single entry
+ * Fill input fields of single-entry VCB
+ */
+ vcb = (VCBlock *)vcb_data;
+ vcb->in_len = ROUND_UP(vcssb->max_single_vcb_len, PAGE_SIZE);
+ vcb->first_vc_index = index + 1;
+ vcb->last_vc_index = index + 1;
+
+ rc = diag320(vcb, DIAG_320_SUBC_STORE_VC);
+ if (rc == DIAG_320_RC_OK) {
+ vce = (VCEntry *)vcb->vce_buf;
+ cert_len = vce->cert_len;
+ memcpy(cert, (uint8_t *)vce + vce->cert_offset, vce->cert_len);
+ /* clear out region for next cert(s) */
+ memcpy(vcb_data, 0, sizeof(vcb_data));
+ }
+
+ return cert_len;
+}
+
+void zipl_secure_cert_list_add(IplSignatureCertificateList *certs, int cert_index,
+ uint64_t *cert, uint64_t cert_len)
+{
+ if (cert_index > MAX_CERTIFICATES - 1) {
+ printf("Warning: Ignoring cert entry [%d] because it's over %d entires\n",
+ cert_index + 1, MAX_CERTIFICATES);
+ return;
+ }
+
+ certs->cert_entries[cert_index].addr = (uint64_t)cert;
+ certs->cert_entries[cert_index].len = cert_len;
+ certs->ipl_info_header.len += sizeof(certs->cert_entries[cert_index]);
+}
+
+void zipl_secure_comp_list_add(IplDeviceComponentList *comps, int comp_index,
+ int cert_index, uint64_t comp_addr,
+ uint64_t comp_len, uint8_t flags)
+{
+ if (comp_index > MAX_CERTIFICATES - 1) {
+ printf("Warning: Ignoring comp entry [%d] because it's over %d entires\n",
+ comp_index + 1, MAX_CERTIFICATES);
+ return;
+ }
+
+ comps->device_entries[comp_index].addr = comp_addr;
+ comps->device_entries[comp_index].len = comp_len;
+ comps->device_entries[comp_index].flags = flags;
+ comps->device_entries[comp_index].cert_index = cert_index;
+ comps->ipl_info_header.len += sizeof(comps->device_entries[comp_index]);
+}
+
+int zipl_secure_update_iirb(IplDeviceComponentList *comps,
+ IplSignatureCertificateList *certs)
+{
+ IplInfoReportBlock *iirb;
+ IplDeviceComponentList *iirb_comps;
+ IplSignatureCertificateList *iirb_certs;
+ uint32_t iirb_hdr_len;
+ uint32_t comps_len;
+ uint32_t certs_len;
+
+ if (iplb->len % 8 != 0) {
+ panic("IPL parameter block length field value is not multiple of 8 bytes");
+ }
+
+ iirb_hdr_len = sizeof(IplInfoReportBlockHeader);
+ comps_len = comps->ipl_info_header.len;
+ certs_len = certs->ipl_info_header.len;
+ if ((comps_len + certs_len + iirb_hdr_len) > sizeof(IplInfoReportBlock)) {
+ puts("Not enough space to hold all components and certificates in IIRB");
+ return -1;
+ }
+
+ /* IIRB immediately follows IPLB */
+ iirb = &ipl_data.iirb;
+ iirb->hdr.len = iirb_hdr_len;
+
+ /* Copy IPL device component list after IIRB Header */
+ iirb_comps = (IplDeviceComponentList *) iirb->info_blks;
+ memcpy(iirb_comps, comps, comps_len);
+
+ /* Update IIRB length */
+ iirb->hdr.len += comps_len;
+
+ /* Copy IPL sig cert list after IPL device component list */
+ iirb_certs = (IplSignatureCertificateList *) (iirb->info_blks +
+ iirb_comps->ipl_info_header.len);
+ memcpy(iirb_certs, certs, certs_len);
+
+ /* Update IIRB length */
+ iirb->hdr.len += certs_len;
+
+ return 0;
+}
+
+bool zipl_secure_ipl_supported(void)
+{
+ if (!sclp_is_sipl_on()) {
+ puts("Secure IPL Facility is not supported by the hypervisor!");
+ return false;
+ }
+
+ if (!is_secure_ipl_extension_supported()) {
+ puts("Secure IPL extensions are not supported by the hypervisor!");
+ return false;
+ }
+
+ if (!(sclp_is_diag320_on() && is_cert_store_facility_supported())) {
+ puts("Certificate Store Facility is not supported by the hypervisor!");
+ return false;
+ }
+
+ return true;
+}
+
+void zipl_secure_init_lists(IplDeviceComponentList *comps,
+ IplSignatureCertificateList *certs)
+{
+ comps->ipl_info_header.ibt = IPL_IBT_COMPONENTS;
+ comps->ipl_info_header.len = sizeof(comps->ipl_info_header);
+
+ certs->ipl_info_header.ibt = IPL_IBT_CERTIFICATES;
+ certs->ipl_info_header.len = sizeof(certs->ipl_info_header);
+}
diff --git a/pc-bios/s390-ccw/secure-ipl.h b/pc-bios/s390-ccw/secure-ipl.h
new file mode 100644
index 0000000000..c45a99bdf5
--- /dev/null
+++ b/pc-bios/s390-ccw/secure-ipl.h
@@ -0,0 +1,97 @@
+/*
+ * S/390 Secure IPL
+ *
+ * Copyright 2025 IBM Corp.
+ * Author(s): Zhuoying Cai <zycai@linux.ibm.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef _PC_BIOS_S390_CCW_SECURE_IPL_H
+#define _PC_BIOS_S390_CCW_SECURE_IPL_H
+
+#include <diag320.h>
+#include <diag508.h>
+
+VCStorageSizeBlock *zipl_secure_get_vcssb(void);
+uint32_t zipl_secure_request_certificate(uint64_t *cert, uint8_t index);
+void zipl_secure_cert_list_add(IplSignatureCertificateList *certs, int cert_index,
+ uint64_t *cert, uint64_t cert_len);
+void zipl_secure_comp_list_add(IplDeviceComponentList *comps, int comp_index,
+ int cert_index, uint64_t comp_addr,
+ uint64_t comp_len, uint8_t flags);
+int zipl_secure_update_iirb(IplDeviceComponentList *comps,
+ IplSignatureCertificateList *certs);
+bool zipl_secure_ipl_supported(void);
+void zipl_secure_init_lists(IplDeviceComponentList *comps,
+ IplSignatureCertificateList *certs);
+
+static inline void zipl_secure_print(bool term, const char *message)
+{
+ switch (boot_mode) {
+ case ZIPL_SECURE_AUDIT_MODE:
+ IPL_check(term, message);
+ break;
+ default:
+ break;
+ }
+}
+
+static inline uint64_t diag320(void *data, unsigned long subcode)
+{
+ register unsigned long addr asm("0") = (unsigned long)data;
+ register unsigned long rc asm("1") = 0;
+
+ asm volatile ("diag %0,%2,0x320\n"
+ : "+d" (addr), "+d" (rc)
+ : "d" (subcode)
+ : "memory", "cc");
+ return rc;
+}
+
+static inline bool is_cert_store_facility_supported(void)
+{
+ uint64_t d320_ism;
+
+ diag320(&d320_ism, DIAG_320_SUBC_QUERY_ISM);
+ return (d320_ism & DIAG_320_ISM_QUERY_VCSI) &&
+ (d320_ism & DIAG_320_ISM_STORE_VC);
+}
+
+static inline uint64_t _diag508(void *data, unsigned long subcode)
+{
+ register unsigned long addr asm("0") = (unsigned long)data;
+ register unsigned long rc asm("1") = 0;
+
+ asm volatile ("diag %0,%2,0x508\n"
+ : "+d" (addr), "+d" (rc)
+ : "d" (subcode)
+ : "memory", "cc");
+ return rc;
+}
+
+static inline bool is_secure_ipl_extension_supported(void)
+{
+ uint64_t d508_subcodes;
+
+ d508_subcodes = _diag508(NULL, DIAG_508_SUBC_QUERY_SUBC);
+ return d508_subcodes & DIAG_508_SUBC_SIG_VERIF;
+}
+
+static inline bool verify_signature(uint64_t comp_len, uint64_t comp_addr,
+ uint64_t sig_len, uint64_t sig_addr,
+ uint64_t *cert_len, uint8_t *cert_idx)
+{
+ Diag508SignatureVerificationBlock svb = {{}, comp_len, comp_addr,
+ sig_len, sig_addr };
+
+ if (_diag508(&svb, DIAG_508_SUBC_SIG_VERIF) == DIAG_508_RC_OK) {
+ *cert_len = svb.csi.len;
+ *cert_idx = svb.csi.idx;
+ return true;
+ }
+
+ return false;
+}
+
+#endif /* _PC_BIOS_S390_CCW_SECURE_IPL_H */
--
2.49.0
^ permalink raw reply related [flat|nested] 51+ messages in thread* [PATCH v3 21/28] s390x: Guest support for Secure-IPL Code Loading Attributes Facility (SCLAF)
2025-06-04 21:56 [PATCH v3 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (19 preceding siblings ...)
2025-06-04 21:56 ` [PATCH v3 20/28] pc-bios/s390-ccw: Add signature verification for secure IPL in audit mode Zhuoying Cai
@ 2025-06-04 21:56 ` Zhuoying Cai
2025-06-04 21:56 ` [PATCH v3 22/28] pc-bios/s390-ccw: Add additional security checks for secure boot Zhuoying Cai
` (6 subsequent siblings)
27 siblings, 0 replies; 51+ messages in thread
From: Zhuoying Cai @ 2025-06-04 21:56 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, pbonzini
Cc: walling, jjherne, jrossi, pasic, borntraeger, farman, iii, eblake,
armbru, qemu-s390x, qemu-devel, zycai
The secure-IPL-code-loading-attributes facility (SCLAF)
provides additional security during IPL.
Availability of SCLAF is determined by byte 136 bit 3 of the
SCLP Read Info block.
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
target/s390x/cpu_features.c | 1 +
target/s390x/cpu_features_def.h.inc | 1 +
target/s390x/cpu_models.c | 3 +++
target/s390x/gen-features.c | 2 ++
target/s390x/kvm/kvm.c | 3 +++
5 files changed, 10 insertions(+)
diff --git a/target/s390x/cpu_features.c b/target/s390x/cpu_features.c
index 3f3d6a80af..8d5614fa59 100644
--- a/target/s390x/cpu_features.c
+++ b/target/s390x/cpu_features.c
@@ -151,6 +151,7 @@ void s390_fill_feat_block(const S390FeatBitmap features, S390FeatType type,
break;
case S390_FEAT_TYPE_SCLP_FAC_IPL:
clear_be_bit(s390_feat_def(S390_FEAT_SIPL)->bit, data);
+ clear_be_bit(s390_feat_def(S390_FEAT_SCLAF)->bit, data);
break;
default:
return;
diff --git a/target/s390x/cpu_features_def.h.inc b/target/s390x/cpu_features_def.h.inc
index 956bd8a123..2e91817d75 100644
--- a/target/s390x/cpu_features_def.h.inc
+++ b/target/s390x/cpu_features_def.h.inc
@@ -142,6 +142,7 @@ DEF_FEAT(DIAG_320, "cstore", SCLP_FAC134, 5, "Provide Certificate Store function
/* Features exposed via SCLP SCCB Facilities byte 136 - 137 (bit numbers relative to byte-136) */
DEF_FEAT(SIPL, "sipl", SCLP_FAC_IPL, 1, "Secure-IPL facility")
+DEF_FEAT(SCLAF, "sclaf", SCLP_FAC_IPL, 3, "Secure-IPL-code-loading-attributes facility")
/* Features exposed via SCLP CPU info. */
DEF_FEAT(SIE_F2, "sief2", SCLP_CPU, 4, "SIE: interception format 2 (Virtual SIE)")
diff --git a/target/s390x/cpu_models.c b/target/s390x/cpu_models.c
index ab46204d9e..cb1c6b5350 100644
--- a/target/s390x/cpu_models.c
+++ b/target/s390x/cpu_models.c
@@ -264,6 +264,7 @@ bool s390_has_feat(S390Feat feat)
case S390_FEAT_SIE_PFMFI:
case S390_FEAT_SIE_IBS:
case S390_FEAT_SIPL:
+ case S390_FEAT_SCLAF:
case S390_FEAT_CONFIGURATION_TOPOLOGY:
return false;
break;
@@ -509,6 +510,8 @@ static void check_consistency(const S390CPUModel *model)
{ S390_FEAT_DIAG_318, S390_FEAT_EXTENDED_LENGTH_SCCB },
{ S390_FEAT_DIAG_320, S390_FEAT_EXTENDED_LENGTH_SCCB },
{ S390_FEAT_SIPL, S390_FEAT_EXTENDED_LENGTH_SCCB },
+ { S390_FEAT_SCLAF, S390_FEAT_EXTENDED_LENGTH_SCCB },
+ { S390_FEAT_SCLAF, S390_FEAT_SIPL },
{ S390_FEAT_NNPA, S390_FEAT_VECTOR },
{ S390_FEAT_RDP, S390_FEAT_LOCAL_TLB_CLEARING },
{ S390_FEAT_UV_FEAT_AP, S390_FEAT_AP },
diff --git a/target/s390x/gen-features.c b/target/s390x/gen-features.c
index dd9c2eba07..dd7e8bee6d 100644
--- a/target/s390x/gen-features.c
+++ b/target/s390x/gen-features.c
@@ -722,6 +722,7 @@ static uint16_t full_GEN16_GA1[] = {
S390_FEAT_UV_FEAT_AP_INTR,
S390_FEAT_DIAG_320,
S390_FEAT_SIPL,
+ S390_FEAT_SCLAF,
};
static uint16_t full_GEN17_GA1[] = {
@@ -927,6 +928,7 @@ static uint16_t qemu_MAX[] = {
S390_FEAT_EXTENDED_LENGTH_SCCB,
S390_FEAT_DIAG_320,
S390_FEAT_SIPL,
+ S390_FEAT_SCLAF,
};
/****** END FEATURE DEFS ******/
diff --git a/target/s390x/kvm/kvm.c b/target/s390x/kvm/kvm.c
index fc9cad32a1..be3ad7316d 100644
--- a/target/s390x/kvm/kvm.c
+++ b/target/s390x/kvm/kvm.c
@@ -2523,6 +2523,9 @@ bool kvm_s390_get_host_cpu_model(S390CPUModel *model, Error **errp)
/* Secure-IPL facility is handled entirely within QEMU */
set_bit(S390_FEAT_SIPL, model->features);
+ /* Secure-IPL-code-loading-attributes facility is handled entirely within QEMU */
+ set_bit(S390_FEAT_SCLAF, model->features);
+
/* Test for Ultravisor features that influence secure guest behavior */
query_uv_feat_guest(model->features);
--
2.49.0
^ permalink raw reply related [flat|nested] 51+ messages in thread* [PATCH v3 22/28] pc-bios/s390-ccw: Add additional security checks for secure boot
2025-06-04 21:56 [PATCH v3 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (20 preceding siblings ...)
2025-06-04 21:56 ` [PATCH v3 21/28] s390x: Guest support for Secure-IPL Code Loading Attributes Facility (SCLAF) Zhuoying Cai
@ 2025-06-04 21:56 ` Zhuoying Cai
2025-06-04 21:56 ` [PATCH v3 23/28] Add secure-boot to s390-ccw-virtio machine type option Zhuoying Cai
` (5 subsequent siblings)
27 siblings, 0 replies; 51+ messages in thread
From: Zhuoying Cai @ 2025-06-04 21:56 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, pbonzini
Cc: walling, jjherne, jrossi, pasic, borntraeger, farman, iii, eblake,
armbru, qemu-s390x, qemu-devel, zycai
Add additional checks to ensure that components do not overlap with
signed components when loaded into memory.
Add additional checks to ensure the load addresses of unsigned components
are greater than or equal to 0x2000.
When the secure IPL code loading attributes facility (SCLAF) is installed,
all signed components must contain a secure code loading attributes block
(SCLAB).
The SCLAB provides further validation of information on where to load the
signed binary code from the load device, and where to start the execution
of the loaded OS code.
When SCLAF is installed, its content must be evaluated during secure IPL.
However, a missing SCLAB will not be reported in audit mode. The SCALB
checking will be skipped in this case.
Add IPL Information Error Indicators (IIEI) and Component Error
Indicators (CEI) for IPL Information Report Block (IIRB).
When SCLAF is installed, additional secure boot checks are performed
during zipl and store results of verification into IIRB.
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
pc-bios/s390-ccw/bootmap.c | 43 ++++++-
pc-bios/s390-ccw/iplb.h | 43 ++++++-
pc-bios/s390-ccw/s390-ccw.h | 1 +
pc-bios/s390-ccw/sclp.c | 8 ++
pc-bios/s390-ccw/sclp.h | 1 +
pc-bios/s390-ccw/secure-ipl.c | 232 ++++++++++++++++++++++++++++++++++
pc-bios/s390-ccw/secure-ipl.h | 20 +++
7 files changed, 344 insertions(+), 4 deletions(-)
diff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c
index d4b53024a1..cc851c476f 100644
--- a/pc-bios/s390-ccw/bootmap.c
+++ b/pc-bios/s390-ccw/bootmap.c
@@ -745,6 +745,13 @@ static int zipl_run_secure(ComponentEntry *entry, uint8_t *tmp_sec)
*/
int cert_table[MAX_CERTIFICATES] = { [0 ... MAX_CERTIFICATES - 1] = -1};
+ int sclab_count = 0;
+ int global_sclab_count = 0;
+ uint64_t sclab_load_psw = 0;
+
+ SecureIplCompAddrRange comp_addr_range[MAX_CERTIFICATES];
+ int addr_range_index = 0;
+
if (!zipl_secure_ipl_supported()) {
return -1;
}
@@ -775,7 +782,17 @@ static int zipl_run_secure(ComponentEntry *entry, uint8_t *tmp_sec)
return -1;
}
- if (have_sig) {
+ zipl_secure_addr_overlap_check(comp_addr_range, &addr_range_index,
+ comp_addr, comp_addr + comp_len, have_sig);
+
+ if (!have_sig) {
+ zipl_secure_check_unsigned_comp(comp_addr, &comps, comp_index,
+ cert_index, comp_len);
+ } else {
+ zipl_secure_check_sclab(comp_addr, &comps, comp_len, comp_index,
+ &sclab_count, &sclab_load_psw,
+ &global_sclab_count);
+
verified = verify_signature(comp_len, comp_addr,
sig_len, (uint64_t)sig_sec,
&cert_len, &cert_idx);
@@ -800,11 +817,12 @@ static int zipl_run_secure(ComponentEntry *entry, uint8_t *tmp_sec)
zipl_secure_print(verified, "Could not verify component");
}
- comp_index++;
found_signature = true;
/* After a signature is used another new one can be accepted */
have_sig = false;
}
+
+ comp_index++;
}
entry++;
@@ -821,8 +839,29 @@ static int zipl_run_secure(ComponentEntry *entry, uint8_t *tmp_sec)
}
if (!found_signature) {
+ comps.ipl_info_header.iiei |= S390_IPL_INFO_IIEI_NO_SIGED_COMP;
zipl_secure_print(found_signature,
"Secure boot is on, but components are not signed");
+ } else {
+ if (sclab_count == 0) {
+ comps.ipl_info_header.iiei |= S390_IPL_INFO_IIEI_NO_SCLAB;
+ zipl_secure_print(false, "No recognizable SCLAB");
+ }
+
+ /* Verify PSW from the final component entry with PSW from the global SCLAB. */
+ if ((comps.ipl_info_header.iiei & S390_IPL_INFO_IIEI_NO_SCLAB) == 0) {
+ if (global_sclab_count == 0) {
+ comps.ipl_info_header.iiei |= S390_IPL_INFO_IIEI_NO_GLOBAL_SCLAB;
+ zipl_secure_print(false, "Global SCLAB does not exists");
+ } else if (global_sclab_count == 1 && sclab_load_psw) {
+ zipl_secure_load_psw_check(comp_addr_range, addr_range_index,
+ sclab_load_psw, entry->compdat.load_psw,
+ &comps, comp_index);
+ } else {
+ /* Program will only reach here in audit mode */
+ puts("Multiple global SCLABs");
+ }
+ }
}
if (zipl_secure_update_iirb(&comps, &certs)) {
diff --git a/pc-bios/s390-ccw/iplb.h b/pc-bios/s390-ccw/iplb.h
index 11302e004d..566e5f78b6 100644
--- a/pc-bios/s390-ccw/iplb.h
+++ b/pc-bios/s390-ccw/iplb.h
@@ -32,11 +32,17 @@ struct IplInfoReportBlockHeader {
} __attribute__ ((packed));
typedef struct IplInfoReportBlockHeader IplInfoReportBlockHeader;
+#define S390_IPL_INFO_IIEI_NO_SIGED_COMP 0x8000 /* bit 0 */
+#define S390_IPL_INFO_IIEI_NO_SCLAB 0x4000 /* bit 1 */
+#define S390_IPL_INFO_IIEI_NO_GLOBAL_SCLAB 0x2000 /* bit 2 */
+#define S390_IPL_INFO_IIEI_MORE_GLOBAL_SCLAB 0x1000 /* bit 3 */
+
struct IplInfoBlockHeader {
uint32_t len;
uint8_t ibt;
uint8_t reserved1[3];
- uint8_t reserved2[8];
+ uint16_t iiei;
+ uint8_t reserved2[6];
} __attribute__ ((packed));
typedef struct IplInfoBlockHeader IplInfoBlockHeader;
@@ -60,13 +66,25 @@ typedef struct IplSignatureCertificateList IplSignatureCertificateList;
#define S390_IPL_COMPONENT_FLAG_SC 0x80
#define S390_IPL_COMPONENT_FLAG_CSV 0x40
+#define S390_IPL_COMPONENT_CEI_INVALID_SCLAB 0x80000000 /* bit 0 */
+#define S390_IPL_COMPONENT_CEI_INVALID_SCLAB_LEN 0x40000000 /* bit 1 */
+#define S390_IPL_COMPONENT_CEI_INVALID_SCLAB_FORMAT 0x20000000 /* bit 2 */
+#define S390_IPL_COMPONENT_CEI_UNMATCHED_SCLAB_LOAD_ADDR 0x10000000 /* bit 3 */
+#define S390_IPL_COMPONENT_CEI_UNMATCHED_SCLAB_LOAD_PSW 0x8000000 /* bit 4 */
+#define S390_IPL_COMPONENT_CEI_INVALID_LOAD_PSW 0x4000000 /* bit 5 */
+#define S390_IPL_COMPONENT_CEI_SCLAB_OLA_NOT_ONE 0x1000000 /* bit 7 */
+#define S390_IPL_COMPONENT_CEI_SCLAB_LOAD_ADDR_NOT_ZERO 0x400000 /* bit 9 */
+#define S390_IPL_COMPONENT_CEI_SCLAB_LOAD_PSW_NOT_ZERO 0x200000 /* bit 10 */
+#define S390_IPL_COMPONENT_CEI_INVALID_UNSIGNED_ADDR 0x100000 /* bit 11 */
+
struct IplDeviceComponentEntry {
uint64_t addr;
uint64_t len;
uint8_t flags;
uint8_t reserved1[5];
uint16_t cert_index;
- uint8_t reserved2[8];
+ uint32_t cei;
+ uint8_t reserved2[4];
} __attribute__ ((packed));
typedef struct IplDeviceComponentEntry IplDeviceComponentEntry;
@@ -93,6 +111,27 @@ typedef struct IplBlocks IplBlocks;
extern IplBlocks ipl_data __attribute__((__aligned__(PAGE_SIZE)));
+#define S390_IPL_SCLAB_FLAG_OPSW 0x8000
+#define S390_IPL_SCLAB_FLAG_OLA 0x4000
+
+struct SecureCodeLoadingAttributesBlock {
+ uint8_t format;
+ uint8_t reserved1;
+ uint16_t flags;
+ uint8_t reserved2[4];
+ uint64_t load_psw;
+ uint64_t load_addr;
+ uint64_t reserved3[];
+} __attribute__ ((packed));
+typedef struct SecureCodeLoadingAttributesBlock SecureCodeLoadingAttributesBlock;
+
+struct SclabOriginLocator {
+ uint8_t reserved[2];
+ uint16_t len;
+ uint8_t magic[4];
+} __attribute__ ((packed));
+typedef struct SclabOriginLocator SclabOriginLocator;
+
#define S390_IPL_TYPE_FCP 0x00
#define S390_IPL_TYPE_CCW 0x02
#define S390_IPL_TYPE_QEMU_SCSI 0xff
diff --git a/pc-bios/s390-ccw/s390-ccw.h b/pc-bios/s390-ccw/s390-ccw.h
index 648f407dc5..85f92685f6 100644
--- a/pc-bios/s390-ccw/s390-ccw.h
+++ b/pc-bios/s390-ccw/s390-ccw.h
@@ -69,6 +69,7 @@ void sclp_setup(void);
void sclp_get_loadparm_ascii(char *loadparm);
bool sclp_is_diag320_on(void);
bool sclp_is_sipl_on(void);
+bool sclp_is_sclaf_on(void);
int sclp_read(char *str, size_t count);
/* virtio.c */
diff --git a/pc-bios/s390-ccw/sclp.c b/pc-bios/s390-ccw/sclp.c
index 0b03c3164f..16f973dde8 100644
--- a/pc-bios/s390-ccw/sclp.c
+++ b/pc-bios/s390-ccw/sclp.c
@@ -157,6 +157,14 @@ bool sclp_is_sipl_on(void)
return fac_ipl & SCCB_FAC_IPL_SIPL_BIT;
}
+bool sclp_is_sclaf_on(void)
+{
+ uint16_t fac_ipl = 0;
+
+ sclp_get_fac_ipl(&fac_ipl);
+ return fac_ipl & SCCB_FAC_IPL_SCLAF_BIT;
+}
+
int sclp_read(char *str, size_t count)
{
ReadEventData *sccb = (void *)_sccb;
diff --git a/pc-bios/s390-ccw/sclp.h b/pc-bios/s390-ccw/sclp.h
index cf147f4634..3441020d6b 100644
--- a/pc-bios/s390-ccw/sclp.h
+++ b/pc-bios/s390-ccw/sclp.h
@@ -52,6 +52,7 @@ typedef struct SCCBHeader {
#define SCCB_DATA_LEN (SCCB_SIZE - sizeof(SCCBHeader))
#define SCCB_FAC134_DIAG320_BIT 0x4
#define SCCB_FAC_IPL_SIPL_BIT 0x4000
+#define SCCB_FAC_IPL_SCLAF_BIT 0x1000
typedef struct ReadInfo {
SCCBHeader h;
diff --git a/pc-bios/s390-ccw/secure-ipl.c b/pc-bios/s390-ccw/secure-ipl.c
index da795079f4..3ae568e4f3 100644
--- a/pc-bios/s390-ccw/secure-ipl.c
+++ b/pc-bios/s390-ccw/secure-ipl.c
@@ -9,6 +9,7 @@
#include <string.h>
#include <stdio.h>
+#include "bootmap.h"
#include "s390-ccw.h"
#include "secure-ipl.h"
@@ -161,6 +162,12 @@ bool zipl_secure_ipl_supported(void)
return false;
}
+ if (!sclp_is_sclaf_on()) {
+ puts("Secure IPL Code Loading Attributes Facility is not supported by" \
+ " the hypervisor!");
+ return false;
+ }
+
return true;
}
@@ -173,3 +180,228 @@ void zipl_secure_init_lists(IplDeviceComponentList *comps,
certs->ipl_info_header.ibt = IPL_IBT_CERTIFICATES;
certs->ipl_info_header.len = sizeof(certs->ipl_info_header);
}
+
+static bool is_comp_overlap(SecureIplCompAddrRange *comp_addr_range, int addr_range_index,
+ uint64_t start_addr, uint64_t end_addr)
+{
+ /* neither a signed nor an unsigned component can overlap with a signed component */
+ for (int i = 0; i < addr_range_index; i++) {
+ if ((comp_addr_range[i].start_addr <= end_addr &&
+ start_addr <= comp_addr_range[i].end_addr) &&
+ comp_addr_range[i].is_signed) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static void comp_addr_range_add(SecureIplCompAddrRange *comp_addr_range,
+ int addr_range_index, bool is_signed,
+ uint64_t start_addr, uint64_t end_addr)
+{
+ comp_addr_range[addr_range_index].is_signed = is_signed;
+ comp_addr_range[addr_range_index].start_addr = start_addr;
+ comp_addr_range[addr_range_index].end_addr = end_addr;
+}
+
+static void unsigned_addr_check(uint64_t load_addr, IplDeviceComponentList *comps,
+ int comp_index)
+{
+ bool is_addr_valid;
+
+ is_addr_valid = load_addr >= 0x2000;
+ if (!is_addr_valid) {
+ comps->device_entries[comp_index].cei |=
+ S390_IPL_COMPONENT_CEI_INVALID_UNSIGNED_ADDR;
+ zipl_secure_print(is_addr_valid, "Load address is less than 0x2000");
+ }
+}
+
+void zipl_secure_addr_overlap_check(SecureIplCompAddrRange *comp_addr_range,
+ int *addr_range_index,
+ uint64_t start_addr, uint64_t end_addr,
+ bool is_signed)
+{
+ bool overlap;
+
+ overlap = is_comp_overlap(comp_addr_range, *addr_range_index,
+ start_addr, end_addr);
+ if (!overlap) {
+ comp_addr_range_add(comp_addr_range, *addr_range_index, is_signed,
+ start_addr, end_addr);
+ *addr_range_index += 1;
+ } else {
+ zipl_secure_print(!overlap, "Component addresses overlap");
+ }
+}
+
+static void valid_sclab_check(SclabOriginLocator *sclab_locator,
+ IplDeviceComponentList *comps, int comp_index)
+{
+ bool is_magic_match;
+ bool is_len_valid;
+
+ /* identifies the presence of SCLAB */
+ is_magic_match = magic_match(sclab_locator->magic, ZIPL_MAGIC);
+ if (!is_magic_match) {
+ comps->device_entries[comp_index].cei |= S390_IPL_COMPONENT_CEI_INVALID_SCLAB;
+
+ /* a missing SCLAB will not be reported in audit mode */
+ return;
+ }
+
+ is_len_valid = sclab_locator->len >= 32;
+ if (!is_len_valid) {
+ comps->device_entries[comp_index].cei |= S390_IPL_COMPONENT_CEI_INVALID_SCLAB_LEN;
+ comps->device_entries[comp_index].cei |= S390_IPL_COMPONENT_CEI_INVALID_SCLAB;
+ zipl_secure_print(is_len_valid, "Invalid SCLAB length");
+ }
+}
+
+static void sclab_format_check(SecureCodeLoadingAttributesBlock *sclab,
+ IplDeviceComponentList *comps, int comp_index)
+{
+ bool valid_format;
+
+ valid_format = sclab->format == 0;
+ if (!valid_format) {
+ comps->device_entries[comp_index].cei |=
+ S390_IPL_COMPONENT_CEI_INVALID_SCLAB_FORMAT;
+ }
+ zipl_secure_print(valid_format, "Format-0 SCLAB is not being used");
+}
+
+static void sclab_opsw_check(SecureCodeLoadingAttributesBlock *sclab,
+ int *global_sclab_count, uint64_t *sclab_load_psw,
+ IplDeviceComponentList *comps, int comp_index)
+{
+ bool is_load_psw_zero;
+ bool is_ola_on;
+ bool has_one_glob_sclab;
+
+ /* OPSW is zero */
+ if (!(sclab->flags & S390_IPL_SCLAB_FLAG_OPSW)) {
+ is_load_psw_zero = sclab->load_psw == 0;
+ if (!is_load_psw_zero) {
+ comps->device_entries[comp_index].cei |=
+ S390_IPL_COMPONENT_CEI_SCLAB_LOAD_PSW_NOT_ZERO;
+ zipl_secure_print(is_load_psw_zero,
+ "Load PSW is not zero when Override PSW bit is zero");
+ }
+ } else {
+ is_ola_on = sclab->flags & S390_IPL_SCLAB_FLAG_OLA;
+ if (!is_ola_on) {
+ comps->device_entries[comp_index].cei |=
+ S390_IPL_COMPONENT_CEI_SCLAB_OLA_NOT_ONE;
+ zipl_secure_print(is_ola_on,
+ "Override Load Address bit is not set to one in the global SCLAB");
+ }
+
+ *global_sclab_count += 1;
+ if (*global_sclab_count == 1) {
+ *sclab_load_psw = sclab->load_psw;
+ } else {
+ has_one_glob_sclab = false;
+ comps->ipl_info_header.iiei |= S390_IPL_INFO_IIEI_MORE_GLOBAL_SCLAB;
+ zipl_secure_print(has_one_glob_sclab, "More than one global SCLAB");
+ }
+ }
+}
+
+static void sclab_ola_check(SecureCodeLoadingAttributesBlock *sclab,
+ uint64_t load_addr, IplDeviceComponentList *comps,
+ int comp_index)
+{
+ bool is_load_addr_zero;
+ bool is_matched;
+
+ /* OLA is zero */
+ if (!(sclab->flags & S390_IPL_SCLAB_FLAG_OLA)) {
+ is_load_addr_zero = sclab->load_addr == 0;
+ if (!is_load_addr_zero) {
+ comps->device_entries[comp_index].cei |=
+ S390_IPL_COMPONENT_CEI_SCLAB_LOAD_ADDR_NOT_ZERO;
+ zipl_secure_print(is_load_addr_zero,
+ "Load Address is not zero when Override Load Address bit is zero");
+ }
+ } else {
+ is_matched = sclab->load_addr == load_addr;
+ if (!is_matched) {
+ comps->device_entries[comp_index].cei |=
+ S390_IPL_COMPONENT_CEI_UNMATCHED_SCLAB_LOAD_ADDR;
+ zipl_secure_print(is_matched,
+ "Load Address does not match with component load address");
+ }
+ }
+}
+
+static bool is_psw_valid(uint64_t psw, SecureIplCompAddrRange *comp_addr_range,
+ int range_index)
+{
+ uint32_t addr = psw & 0x3FFFFFFF;
+
+ /* PSW points to the beginning of a signed binary code component */
+ for (int i = 0; i < range_index; i++) {
+ if (comp_addr_range[i].is_signed && comp_addr_range[i].start_addr == addr) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void zipl_secure_load_psw_check(SecureIplCompAddrRange *comp_addr_range,
+ int addr_range_index, uint64_t sclab_load_psw,
+ uint64_t load_psw, IplDeviceComponentList *comps,
+ int comp_index)
+{
+ bool is_valid;
+ bool is_matched;
+
+ is_valid = is_psw_valid(sclab_load_psw, comp_addr_range, addr_range_index) &&
+ is_psw_valid(load_psw, comp_addr_range, addr_range_index);
+ if (!is_valid) {
+ comps->device_entries[comp_index].cei |= S390_IPL_COMPONENT_CEI_INVALID_LOAD_PSW;
+ zipl_secure_print(is_valid, "Invalid PSW");
+ }
+
+ is_matched = load_psw == sclab_load_psw;
+ if (!is_matched) {
+ comps->device_entries[comp_index].cei |=
+ S390_IPL_COMPONENT_CEI_UNMATCHED_SCLAB_LOAD_PSW;
+ zipl_secure_print(is_matched,
+ "Load PSW does not match with PSW in component");
+ }
+}
+
+void zipl_secure_check_unsigned_comp(uint64_t comp_addr, IplDeviceComponentList *comps,
+ int comp_index, int cert_index, uint64_t comp_len)
+{
+ unsigned_addr_check(comp_addr, comps, comp_index);
+
+ zipl_secure_comp_list_add(comps, comp_index, cert_index, comp_addr, comp_len, 0x00);
+}
+
+void zipl_secure_check_sclab(uint64_t comp_addr, IplDeviceComponentList *comps,
+ uint64_t comp_len, int comp_index, int *sclab_count,
+ uint64_t *sclab_load_psw, int *global_sclab_count)
+{
+ SclabOriginLocator *sclab_locator;
+ SecureCodeLoadingAttributesBlock *sclab;
+
+ sclab_locator = (SclabOriginLocator *)(comp_addr + comp_len - 8);
+ valid_sclab_check(sclab_locator, comps, comp_index);
+
+ if ((comps->device_entries[comp_index].cei &
+ S390_IPL_COMPONENT_CEI_INVALID_SCLAB) == 0) {
+ *sclab_count += 1;
+ sclab = (SecureCodeLoadingAttributesBlock *)(comp_addr + comp_len -
+ sclab_locator->len);
+
+ sclab_format_check(sclab, comps, comp_index);
+ sclab_opsw_check(sclab, global_sclab_count, sclab_load_psw,
+ comps, comp_index);
+ sclab_ola_check(sclab, comp_addr, comps, comp_index);
+ }
+}
diff --git a/pc-bios/s390-ccw/secure-ipl.h b/pc-bios/s390-ccw/secure-ipl.h
index c45a99bdf5..9a260981fd 100644
--- a/pc-bios/s390-ccw/secure-ipl.h
+++ b/pc-bios/s390-ccw/secure-ipl.h
@@ -26,6 +26,26 @@ bool zipl_secure_ipl_supported(void);
void zipl_secure_init_lists(IplDeviceComponentList *comps,
IplSignatureCertificateList *certs);
+typedef struct SecureIplCompAddrRange {
+ bool is_signed;
+ uint64_t start_addr;
+ uint64_t end_addr;
+} SecureIplCompAddrRange;
+
+void zipl_secure_addr_overlap_check(SecureIplCompAddrRange *comp_addr_range,
+ int *addr_range_index,
+ uint64_t start_addr, uint64_t end_addr,
+ bool is_signed);
+void zipl_secure_load_psw_check(SecureIplCompAddrRange *comp_addr_range,
+ int addr_range_index, uint64_t sclab_load_psw,
+ uint64_t load_psw, IplDeviceComponentList *comps,
+ int comp_index);
+void zipl_secure_check_unsigned_comp(uint64_t comp_addr, IplDeviceComponentList *comps,
+ int comp_index, int cert_index, uint64_t comp_len);
+void zipl_secure_check_sclab(uint64_t comp_addr, IplDeviceComponentList *comps,
+ uint64_t comp_len, int comp_index, int *sclab_count,
+ uint64_t *sclab_load_psw, int *global_sclab_count);
+
static inline void zipl_secure_print(bool term, const char *message)
{
switch (boot_mode) {
--
2.49.0
^ permalink raw reply related [flat|nested] 51+ messages in thread* [PATCH v3 23/28] Add secure-boot to s390-ccw-virtio machine type option
2025-06-04 21:56 [PATCH v3 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (21 preceding siblings ...)
2025-06-04 21:56 ` [PATCH v3 22/28] pc-bios/s390-ccw: Add additional security checks for secure boot Zhuoying Cai
@ 2025-06-04 21:56 ` Zhuoying Cai
2025-06-04 21:56 ` [PATCH v3 24/28] hw/s390x/ipl: Set IPIB flags for secure IPL Zhuoying Cai
` (4 subsequent siblings)
27 siblings, 0 replies; 51+ messages in thread
From: Zhuoying Cai @ 2025-06-04 21:56 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, pbonzini
Cc: walling, jjherne, jrossi, pasic, borntraeger, farman, iii, eblake,
armbru, qemu-s390x, qemu-devel, zycai
Add secure-boot as a parameter of s390-ccw-virtio machine type option.
The `secure-boot=on|off` parameter is implemented to enable secure IPL.
By default, secure-boot is set to false if not specified in
the command line.
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
hw/s390x/s390-virtio-ccw.c | 22 ++++++++++++++++++++++
include/hw/s390x/s390-virtio-ccw.h | 1 +
qemu-options.hx | 6 +++++-
3 files changed, 28 insertions(+), 1 deletion(-)
diff --git a/hw/s390x/s390-virtio-ccw.c b/hw/s390x/s390-virtio-ccw.c
index 144ef52f34..d064a00dc8 100644
--- a/hw/s390x/s390-virtio-ccw.c
+++ b/hw/s390x/s390-virtio-ccw.c
@@ -814,6 +814,21 @@ static void machine_set_boot_certificates(Object *obj, const char *str,
ms->boot_certificates = g_strdup(str);
}
+static inline bool machine_get_secure_boot(Object *obj, Error **errp)
+{
+ S390CcwMachineState *ms = S390_CCW_MACHINE(obj);
+
+ return ms->secure_boot;
+}
+
+static inline void machine_set_secure_boot(Object *obj, bool value,
+ Error **errp)
+{
+ S390CcwMachineState *ms = S390_CCW_MACHINE(obj);
+
+ ms->secure_boot = value;
+}
+
static void ccw_machine_class_init(ObjectClass *oc, const void *data)
{
MachineClass *mc = MACHINE_CLASS(oc);
@@ -873,6 +888,13 @@ static void ccw_machine_class_init(ObjectClass *oc, const void *data)
machine_set_boot_certificates);
object_class_property_set_description(oc, "boot-certificates",
"provide path to a directory or a single certificate for secure boot");
+
+ object_class_property_add_bool(oc, "secure-boot",
+ machine_get_secure_boot,
+ machine_set_secure_boot);
+ object_class_property_set_description(oc, "secure-boot",
+ "enable/disable secure boot");
+
}
static inline void s390_machine_initfn(Object *obj)
diff --git a/include/hw/s390x/s390-virtio-ccw.h b/include/hw/s390x/s390-virtio-ccw.h
index 45adc8bce6..901e013089 100644
--- a/include/hw/s390x/s390-virtio-ccw.h
+++ b/include/hw/s390x/s390-virtio-ccw.h
@@ -32,6 +32,7 @@ struct S390CcwMachineState {
uint64_t memory_limit;
uint64_t max_pagesize;
char *boot_certificates;
+ bool secure_boot;
SCLPDevice *sclp;
};
diff --git a/qemu-options.hx b/qemu-options.hx
index 6d01f8c4b2..f453967dde 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -44,7 +44,8 @@ DEF("machine", HAS_ARG, QEMU_OPTION_machine, \
" memory-backend='backend-id' specifies explicitly provided backend for main RAM (default=none)\n"
" cxl-fmw.0.targets.0=firsttarget,cxl-fmw.0.targets.1=secondtarget,cxl-fmw.0.size=size[,cxl-fmw.0.interleave-granularity=granularity]\n"
" smp-cache.0.cache=cachename,smp-cache.0.topology=topologylevel\n"
- " boot-certificates='/path/directory:/path/file' provide a path to a directory or a boot certificate\n",
+ " boot-certificates='/path/directory:/path/file' provide a path to a directory or a boot certificate\n"
+ " secure-boot=on|off enable/disable secure boot (default=off) \n",
QEMU_ARCH_ALL)
SRST
``-machine [type=]name[,prop=value[,...]]``
@@ -205,6 +206,9 @@ SRST
``boot-certificates='/path/directory:/path/file'``
Provide a path to a directory or a boot certificate on the host [s390x only].
A colon may be used to delineate multiple paths.
+
+ ``secure-boot=on|off``
+ Enables or disables secure boot on s390-ccw guest. The default is off.
ERST
DEF("M", HAS_ARG, QEMU_OPTION_M,
--
2.49.0
^ permalink raw reply related [flat|nested] 51+ messages in thread* [PATCH v3 24/28] hw/s390x/ipl: Set IPIB flags for secure IPL
2025-06-04 21:56 [PATCH v3 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (22 preceding siblings ...)
2025-06-04 21:56 ` [PATCH v3 23/28] Add secure-boot to s390-ccw-virtio machine type option Zhuoying Cai
@ 2025-06-04 21:56 ` Zhuoying Cai
2025-06-04 21:56 ` [PATCH v3 25/28] pc-bios/s390-ccw: Handle true secure IPL mode Zhuoying Cai
` (3 subsequent siblings)
27 siblings, 0 replies; 51+ messages in thread
From: Zhuoying Cai @ 2025-06-04 21:56 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, pbonzini
Cc: walling, jjherne, jrossi, pasic, borntraeger, farman, iii, eblake,
armbru, qemu-s390x, qemu-devel, zycai
If `-secure-boot on` is specified on the command line option, indicating
true secure IPL enabled, set Secure-IPL bit and IPL-Information-Report
bit on in IPIB Flags field, and trigger true secure IPL in the S390 BIOS.
Any error that occurs during true secure IPL will cause the IPL to
terminate.
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
hw/s390x/ipl.c | 18 +++++++++++++++++-
1 file changed, 17 insertions(+), 1 deletion(-)
diff --git a/hw/s390x/ipl.c b/hw/s390x/ipl.c
index d1a972ac8d..a196e1d648 100644
--- a/hw/s390x/ipl.c
+++ b/hw/s390x/ipl.c
@@ -437,6 +437,11 @@ static bool s390_has_certificate(void)
return ipl->cert_store.count > 0;
}
+static bool s390_secure_boot_enabled(void)
+{
+ return S390_CCW_MACHINE(qdev_get_machine())->secure_boot;
+}
+
static bool s390_build_iplb(DeviceState *dev_st, IplParameterBlock *iplb)
{
CcwDevice *ccw_dev = NULL;
@@ -494,6 +499,17 @@ static bool s390_build_iplb(DeviceState *dev_st, IplParameterBlock *iplb)
s390_ipl_convert_loadparm((char *)lp, iplb->loadparm);
iplb->flags |= DIAG308_FLAGS_LP_VALID;
+ /*
+ * If secure-boot is enabled, then toggle the secure IPL flags to trigger
+ * secure boot in the s390 BIOS.
+ *
+ * Boot process will terminate if any error occurs during secure boot.
+ *
+ * If SIPL is on, IPLIR must also be on.
+ */
+ if (s390_secure_boot_enabled()) {
+ iplb->hdr_flags |= (DIAG308_IPIB_FLAGS_SIPL | DIAG308_IPIB_FLAGS_IPLIR);
+ }
/*
* Secure boot in audit mode will perform
* if certificate(s) exist in the key store.
@@ -503,7 +519,7 @@ static bool s390_build_iplb(DeviceState *dev_st, IplParameterBlock *iplb)
*
* Results of secure boot will be stored in IIRB.
*/
- if (s390_has_certificate()) {
+ else if (s390_has_certificate()) {
iplb->hdr_flags |= DIAG308_IPIB_FLAGS_IPLIR;
}
--
2.49.0
^ permalink raw reply related [flat|nested] 51+ messages in thread* [PATCH v3 25/28] pc-bios/s390-ccw: Handle true secure IPL mode
2025-06-04 21:56 [PATCH v3 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (23 preceding siblings ...)
2025-06-04 21:56 ` [PATCH v3 24/28] hw/s390x/ipl: Set IPIB flags for secure IPL Zhuoying Cai
@ 2025-06-04 21:56 ` Zhuoying Cai
2025-06-04 21:56 ` [PATCH v3 26/28] pc-bios/s390-ccw: Handle secure boot with multiple boot devices Zhuoying Cai
` (2 subsequent siblings)
27 siblings, 0 replies; 51+ messages in thread
From: Zhuoying Cai @ 2025-06-04 21:56 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, pbonzini
Cc: walling, jjherne, jrossi, pasic, borntraeger, farman, iii, eblake,
armbru, qemu-s390x, qemu-devel, zycai
When secure boot is enabled (-secure-boot on) and certificate(s) are
provided, the boot operates in True Secure IPL mode.
Any verification error during True Secure IPL mode will cause the
entire boot process to terminate.
Secure IPL in audit mode requires at least one certificate provided in
the key store along with necessary facilities. If secure boot is enabled
but no certificate is provided, the boot process will also terminate, as
this is not a valid secure boot configuration.
Note: True Secure IPL mode is implemented for the SCSI scheme of
virtio-blk/virtio-scsi devices.
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
pc-bios/s390-ccw/bootmap.c | 16 +++++++++++++---
pc-bios/s390-ccw/main.c | 6 +++++-
pc-bios/s390-ccw/s390-ccw.h | 2 ++
pc-bios/s390-ccw/secure-ipl.c | 5 +++++
pc-bios/s390-ccw/secure-ipl.h | 3 +++
5 files changed, 28 insertions(+), 4 deletions(-)
diff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c
index cc851c476f..64abf8164d 100644
--- a/pc-bios/s390-ccw/bootmap.c
+++ b/pc-bios/s390-ccw/bootmap.c
@@ -932,6 +932,9 @@ static int zipl_run(ScsiBlockPtr *pte)
entry = (ComponentEntry *)(&header[1]);
switch (boot_mode) {
+ case ZIPL_SECURE_INVALID_MODE:
+ return -1;
+ case ZIPL_SECURE_MODE:
case ZIPL_SECURE_AUDIT_MODE:
if (zipl_run_secure(entry, tmp_sec)) {
return -1;
@@ -1304,9 +1307,16 @@ ZiplBootMode zipl_mode(uint8_t hdr_flags)
{
bool sipl_set = hdr_flags & DIAG308_IPIB_FLAGS_SIPL;
bool iplir_set = hdr_flags & DIAG308_IPIB_FLAGS_IPLIR;
+ VCStorageSizeBlock *vcssb;
if (!sipl_set && iplir_set) {
return ZIPL_SECURE_AUDIT_MODE;
+ } else if (sipl_set && iplir_set) {
+ vcssb = zipl_secure_get_vcssb();
+ if (vcssb == NULL || vcssb->length == 4) {
+ return ZIPL_SECURE_INVALID_MODE;
+ }
+ return ZIPL_SECURE_MODE;
}
return ZIPL_NORMAL_MODE;
@@ -1317,7 +1327,7 @@ void zipl_load(void)
VDev *vdev = virtio_get_device();
if (vdev->is_cdrom) {
- if (boot_mode == ZIPL_SECURE_AUDIT_MODE) {
+ if (boot_mode == ZIPL_SECURE_AUDIT_MODE || boot_mode == ZIPL_SECURE_MODE) {
panic("Secure boot from ISO image is not supported!");
}
ipl_iso_el_torito();
@@ -1326,7 +1336,7 @@ void zipl_load(void)
}
if (virtio_get_device_type() == VIRTIO_ID_NET) {
- if (boot_mode == ZIPL_SECURE_AUDIT_MODE) {
+ if (boot_mode == ZIPL_SECURE_AUDIT_MODE || boot_mode == ZIPL_SECURE_MODE) {
panic("Virtio net boot device does not support secure boot!");
}
netmain();
@@ -1339,7 +1349,7 @@ void zipl_load(void)
return;
}
- if (boot_mode == ZIPL_SECURE_AUDIT_MODE) {
+ if (boot_mode == ZIPL_SECURE_AUDIT_MODE || boot_mode == ZIPL_SECURE_MODE) {
panic("ECKD boot device does not support secure boot!");
}
diff --git a/pc-bios/s390-ccw/main.c b/pc-bios/s390-ccw/main.c
index 38962da1dd..3e17550854 100644
--- a/pc-bios/s390-ccw/main.c
+++ b/pc-bios/s390-ccw/main.c
@@ -277,10 +277,14 @@ static void ipl_boot_device(void)
boot_mode = zipl_mode(iplb->hdr_flags);
}
+ if (boot_mode == ZIPL_SECURE_INVALID_MODE) {
+ panic("Need at least one certificate for secure boot!");
+ }
+
switch (cutype) {
case CU_TYPE_DASD_3990:
case CU_TYPE_DASD_2107:
- if (boot_mode == ZIPL_SECURE_AUDIT_MODE) {
+ if (boot_mode == ZIPL_SECURE_AUDIT_MODE || boot_mode == ZIPL_SECURE_MODE) {
panic("Passthrough (vfio) device does not support secure boot!");
}
diff --git a/pc-bios/s390-ccw/s390-ccw.h b/pc-bios/s390-ccw/s390-ccw.h
index 85f92685f6..bf20efe88e 100644
--- a/pc-bios/s390-ccw/s390-ccw.h
+++ b/pc-bios/s390-ccw/s390-ccw.h
@@ -83,8 +83,10 @@ int virtio_read(unsigned long sector, void *load_addr);
void zipl_load(void);
typedef enum ZiplBootMode {
+ ZIPL_SECURE_INVALID_MODE = -1,
ZIPL_NORMAL_MODE = 1,
ZIPL_SECURE_AUDIT_MODE = 2,
+ ZIPL_SECURE_MODE = 3,
} ZiplBootMode;
extern ZiplBootMode boot_mode;
diff --git a/pc-bios/s390-ccw/secure-ipl.c b/pc-bios/s390-ccw/secure-ipl.c
index 3ae568e4f3..ceb038e803 100644
--- a/pc-bios/s390-ccw/secure-ipl.c
+++ b/pc-bios/s390-ccw/secure-ipl.c
@@ -248,6 +248,11 @@ static void valid_sclab_check(SclabOriginLocator *sclab_locator,
comps->device_entries[comp_index].cei |= S390_IPL_COMPONENT_CEI_INVALID_SCLAB;
/* a missing SCLAB will not be reported in audit mode */
+ if (boot_mode == ZIPL_SECURE_MODE) {
+ zipl_secure_print(is_magic_match,
+ "Magic is not matched. SCLAB does not exist");
+ }
+
return;
}
diff --git a/pc-bios/s390-ccw/secure-ipl.h b/pc-bios/s390-ccw/secure-ipl.h
index 9a260981fd..79b80a551d 100644
--- a/pc-bios/s390-ccw/secure-ipl.h
+++ b/pc-bios/s390-ccw/secure-ipl.h
@@ -52,6 +52,9 @@ static inline void zipl_secure_print(bool term, const char *message)
case ZIPL_SECURE_AUDIT_MODE:
IPL_check(term, message);
break;
+ case ZIPL_SECURE_MODE:
+ IPL_assert(term, message);
+ break;
default:
break;
}
--
2.49.0
^ permalink raw reply related [flat|nested] 51+ messages in thread* [PATCH v3 26/28] pc-bios/s390-ccw: Handle secure boot with multiple boot devices
2025-06-04 21:56 [PATCH v3 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (24 preceding siblings ...)
2025-06-04 21:56 ` [PATCH v3 25/28] pc-bios/s390-ccw: Handle true secure IPL mode Zhuoying Cai
@ 2025-06-04 21:56 ` Zhuoying Cai
2025-06-04 21:56 ` [PATCH v3 27/28] hw/s390x/ipl: Handle secure boot without specifying a boot device Zhuoying Cai
2025-06-04 21:56 ` [PATCH v3 28/28] docs: Add secure IPL documentation Zhuoying Cai
27 siblings, 0 replies; 51+ messages in thread
From: Zhuoying Cai @ 2025-06-04 21:56 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, pbonzini
Cc: walling, jjherne, jrossi, pasic, borntraeger, farman, iii, eblake,
armbru, qemu-s390x, qemu-devel, zycai
The current approach to enabling secure boot relies on providing
-secure-boot and -boot-certificates options, which apply to all boot
devices.
With the possibility of multiple boot devices, secure boot expects all
provided devices to be supported and eligible (e.g.,
virtio-blk/virtio-scsi using the SCSI scheme).
If multiple boot devices are provided and include an unsupported (e.g.,
ECKD, VFIO) or a non-eligible (e.g., Net) device, the boot process will
terminate with an error logged to the console.
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
pc-bios/s390-ccw/bootmap.c | 28 +++++++++-----
pc-bios/s390-ccw/main.c | 74 ++++++++++++++++++++++++++++++++++---
pc-bios/s390-ccw/s390-ccw.h | 1 +
3 files changed, 88 insertions(+), 15 deletions(-)
diff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c
index 64abf8164d..053d433073 100644
--- a/pc-bios/s390-ccw/bootmap.c
+++ b/pc-bios/s390-ccw/bootmap.c
@@ -1322,23 +1322,35 @@ ZiplBootMode zipl_mode(uint8_t hdr_flags)
return ZIPL_NORMAL_MODE;
}
+int zipl_check_scsi_mbr_magic(void)
+{
+ ScsiMbr *mbr = (void *)sec;
+
+ /* Grab the MBR */
+ memset(sec, FREE_SPACE_FILLER, sizeof(sec));
+ if (virtio_read(0, mbr)) {
+ puts("Cannot read block 0");
+ return -EIO;
+ }
+
+ if (!magic_match(mbr->magic, ZIPL_MAGIC)) {
+ return -1;
+ }
+
+ return 0;
+}
+
void zipl_load(void)
{
VDev *vdev = virtio_get_device();
if (vdev->is_cdrom) {
- if (boot_mode == ZIPL_SECURE_AUDIT_MODE || boot_mode == ZIPL_SECURE_MODE) {
- panic("Secure boot from ISO image is not supported!");
- }
ipl_iso_el_torito();
puts("Failed to IPL this ISO image!");
return;
}
if (virtio_get_device_type() == VIRTIO_ID_NET) {
- if (boot_mode == ZIPL_SECURE_AUDIT_MODE || boot_mode == ZIPL_SECURE_MODE) {
- panic("Virtio net boot device does not support secure boot!");
- }
netmain();
puts("Failed to IPL from this network!");
return;
@@ -1349,10 +1361,6 @@ void zipl_load(void)
return;
}
- if (boot_mode == ZIPL_SECURE_AUDIT_MODE || boot_mode == ZIPL_SECURE_MODE) {
- panic("ECKD boot device does not support secure boot!");
- }
-
switch (virtio_get_device_type()) {
case VIRTIO_ID_BLOCK:
zipl_load_vblk();
diff --git a/pc-bios/s390-ccw/main.c b/pc-bios/s390-ccw/main.c
index 3e17550854..ffb87e264b 100644
--- a/pc-bios/s390-ccw/main.c
+++ b/pc-bios/s390-ccw/main.c
@@ -271,8 +271,43 @@ static int virtio_setup(void)
return ret;
}
-static void ipl_boot_device(void)
+static void validate_secure_boot_device(void)
+{
+ switch (cutype) {
+ case CU_TYPE_DASD_3990:
+ case CU_TYPE_DASD_2107:
+ panic("Passthrough (vfio) device does not support secure boot!");
+ break;
+ case CU_TYPE_VIRTIO:
+ if (virtio_setup() == 0) {
+ VDev *vdev = virtio_get_device();
+
+ if (vdev->is_cdrom) {
+ panic("Secure boot from ISO image is not supported!");
+ }
+
+ if (virtio_get_device_type() == VIRTIO_ID_NET) {
+ panic("Virtio net boot device does not support secure boot!");
+ }
+
+ if (zipl_check_scsi_mbr_magic()) {
+ panic("ECKD boot device does not support secure boot!");
+ }
+ }
+ break;
+ default:
+ panic("Secure boot from unexpected device type is not supported!");
+ }
+
+ printf("SCSI boot device supports secure boot.\n");
+}
+
+static void check_secure_boot_support(void)
{
+ bool have_iplb_copy;
+ IplParameterBlock *iplb_copy;
+ QemuIplParameters *qipl_copy;
+
if (boot_mode == 0) {
boot_mode = zipl_mode(iplb->hdr_flags);
}
@@ -281,13 +316,40 @@ static void ipl_boot_device(void)
panic("Need at least one certificate for secure boot!");
}
+ if (boot_mode == ZIPL_NORMAL_MODE) {
+ return;
+ }
+
+ /*
+ * Store copies of have_iplb, iplb and qipl.
+ * They will be updated in load_next_iplb().
+ */
+ have_iplb_copy = have_iplb;
+ iplb_copy = malloc(sizeof(IplParameterBlock));
+ qipl_copy = malloc(sizeof(QemuIplParameters));
+
+ memcpy(qipl_copy, &qipl, sizeof(QemuIplParameters));
+ memcpy(iplb_copy, iplb, sizeof(IplParameterBlock));
+
+ while (have_iplb_copy) {
+ if (have_iplb_copy && find_boot_device()) {
+ validate_secure_boot_device();
+ }
+ have_iplb_copy = load_next_iplb();
+ }
+
+ memcpy(&qipl, qipl_copy, sizeof(QemuIplParameters));
+ memcpy(iplb, iplb_copy, sizeof(IplParameterBlock));
+
+ free(qipl_copy);
+ free(iplb_copy);
+}
+
+static void ipl_boot_device(void)
+{
switch (cutype) {
case CU_TYPE_DASD_3990:
case CU_TYPE_DASD_2107:
- if (boot_mode == ZIPL_SECURE_AUDIT_MODE || boot_mode == ZIPL_SECURE_MODE) {
- panic("Passthrough (vfio) device does not support secure boot!");
- }
-
dasd_ipl(blk_schid, cutype);
break;
case CU_TYPE_VIRTIO:
@@ -337,6 +399,8 @@ void main(void)
probe_boot_device();
}
+ check_secure_boot_support();
+
while (have_iplb) {
boot_setup();
if (have_iplb && find_boot_device()) {
diff --git a/pc-bios/s390-ccw/s390-ccw.h b/pc-bios/s390-ccw/s390-ccw.h
index bf20efe88e..a26233afac 100644
--- a/pc-bios/s390-ccw/s390-ccw.h
+++ b/pc-bios/s390-ccw/s390-ccw.h
@@ -92,6 +92,7 @@ typedef enum ZiplBootMode {
extern ZiplBootMode boot_mode;
ZiplBootMode zipl_mode(uint8_t hdr_flags);
+int zipl_check_scsi_mbr_magic(void);
/* jump2ipl.c */
void write_reset_psw(uint64_t psw);
--
2.49.0
^ permalink raw reply related [flat|nested] 51+ messages in thread* [PATCH v3 27/28] hw/s390x/ipl: Handle secure boot without specifying a boot device
2025-06-04 21:56 [PATCH v3 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (25 preceding siblings ...)
2025-06-04 21:56 ` [PATCH v3 26/28] pc-bios/s390-ccw: Handle secure boot with multiple boot devices Zhuoying Cai
@ 2025-06-04 21:56 ` Zhuoying Cai
2025-06-04 21:56 ` [PATCH v3 28/28] docs: Add secure IPL documentation Zhuoying Cai
27 siblings, 0 replies; 51+ messages in thread
From: Zhuoying Cai @ 2025-06-04 21:56 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, pbonzini
Cc: walling, jjherne, jrossi, pasic, borntraeger, farman, iii, eblake,
armbru, qemu-s390x, qemu-devel, zycai
If secure boot in audit mode or True Secure IPL mode is enabled without
specifying a boot device, the boot process will terminate with an error.
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
hw/s390x/ipl.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/hw/s390x/ipl.c b/hw/s390x/ipl.c
index a196e1d648..da50b52c75 100644
--- a/hw/s390x/ipl.c
+++ b/hw/s390x/ipl.c
@@ -764,6 +764,16 @@ void s390_ipl_prepare_cpu(S390CPU *cpu)
s390_ipl_create_cert_store(&ipl->cert_store);
if (!ipl->iplb_valid) {
ipl->iplb_valid = s390_init_all_iplbs(ipl);
+
+ /*
+ * Secure IPL without specifying a boot device.
+ * IPLB is not generated if no boot device is defined.
+ */
+ if ((s390_has_certificate() || s390_secure_boot_enabled()) &&
+ !ipl->iplb_valid) {
+ error_report("No boot device defined for Secure IPL");
+ exit(1);
+ }
} else {
ipl->qipl.chain_len = 0;
}
--
2.49.0
^ permalink raw reply related [flat|nested] 51+ messages in thread* [PATCH v3 28/28] docs: Add secure IPL documentation
2025-06-04 21:56 [PATCH v3 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (26 preceding siblings ...)
2025-06-04 21:56 ` [PATCH v3 27/28] hw/s390x/ipl: Handle secure boot without specifying a boot device Zhuoying Cai
@ 2025-06-04 21:56 ` Zhuoying Cai
2025-06-06 11:04 ` Daniel P. Berrangé
2025-06-17 21:29 ` Collin Walling
27 siblings, 2 replies; 51+ messages in thread
From: Zhuoying Cai @ 2025-06-04 21:56 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, pbonzini
Cc: walling, jjherne, jrossi, pasic, borntraeger, farman, iii, eblake,
armbru, qemu-s390x, qemu-devel, zycai
Add documentation for secure IPL
Signed-off-by: Collin Walling <walling@linux.ibm.com>
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
docs/specs/s390x-secure-ipl.rst | 145 +++++++++++++++++++++++++++++++
docs/system/s390x/secure-ipl.rst | 129 +++++++++++++++++++++++++++
2 files changed, 274 insertions(+)
create mode 100644 docs/specs/s390x-secure-ipl.rst
create mode 100644 docs/system/s390x/secure-ipl.rst
diff --git a/docs/specs/s390x-secure-ipl.rst b/docs/specs/s390x-secure-ipl.rst
new file mode 100644
index 0000000000..53de036dfa
--- /dev/null
+++ b/docs/specs/s390x-secure-ipl.rst
@@ -0,0 +1,145 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+s390 Secure IPL
+===============
+
+Secure IPL, also known as secure boot, enables s390-ccw virtual machines to
+leverage qcrypto libraries and z/Arch implementations to verify the integrity of
+guest kernels. These operations are rely on userspace invocations and QEMU
+handling. The user provides one or more certificates via the command line
+options, which populates a certificate store. DIAGNOSE 'X'320' is invoked by
+userspace to query cert store info and retrieve specific certificates from QEMU.
+DIAGNOSE 'X'508' is used by userspace to leverage qcrypto libraries to perform
+signature-verification in QEMU. Lastly, userspace generates and appends an
+IPL Information Report Block (IIRB) at the end of the IPL Parameter Block.
+
+The steps are as follows:
+
+- Userspace retrieves data payload from disk (e.g. stage3 boot loader, kernel)
+- Userspace checks the validity of the SCLAB
+- Userspace invokes DIAG 508 subcode 1 and provides it the payload
+- QEMU handles DIAG 508 request by reading the payload and retrieving the
+ certificate store
+- QEMU DIAG 508 utilizes qcrypto libraries to perform signature-verification on
+ the payload, attempting with each cert in the store (until success or exhausted)
+- QEMU DIAG 508 returns:
+
+ - success: index of cert used to verify payload
+ - failure: error code
+
+- Userspace responds to this operation:
+
+ - success: retrieves cert from store via DIAG 320 using returned index
+ - failure: reports with warning (audit mode), aborts with error (secure mode)
+
+- Userspace appends IIRB at the end of the IPLB
+- Userspace kicks off IPL
+
+
+s390 Certificate Store
+======================
+
+Secure boot relies on user certificates for signature-verification. Normally,
+these certificates would be stored somewhere on the LPAR. Instead, for virtual
+guests, a certificate store is implemented within QEMU. This store will read
+any certificates provided by the user via command-line, which are expected to
+be stored somewhere on the host file system. Once these certificates are stored,
+they are ready to be queried/requested by DIAGNOSE 'X'320' or used for
+verification by DIAGNOSE 'X'508'.
+
+DIAGNOSE function code 'X'320' - Certificate Store Facility
+-----------------------------------------------------------
+
+DIAGNOSE 'X'320' is used to provide support to query the certificate store.
+
+Subcode 0 - query installed subcodes
+ Returns a 256-bit installed subcodes mask (ISM) stored in the installed
+ subcodes block (ISB). This mask indicates which sucodes are currently
+ installed and available for use.
+
+Subcode 1 - query verification certificate storage information
+ Provides the information required to determine the amount of memory needed to
+ store one or more verification-certificates (VCs) from the certificate store (CS).
+
+ Upon successful completion, this subcode returns various storage size values for
+ verification-certificate blocks (VCBs).
+
+ The output is returned in the verification-certificate-storage-size block (VCSSB).
+
+Subcode 2 - store verification certificates
+ Provides VCs that are in the certificate store.
+
+ The output is provided in a VCB, which includes a common header followed by zero
+ or more verification-certificate entries (VCEs).
+
+ The first-VC index and last-VC index fields of VCB specify the range of VCs
+ to be stored by subcode 2. Stored count and remained count fields specify the
+ number of VCs stored and could not be stored in the VCB due to insufficient
+ storage specified in the VCB input length field.
+
+ VCE contains various information of a VC from the CS.
+
+
+Secure IPL Functions
+====================
+
+IPL Information Report Block
+----------------------------
+
+The IPL Parameter Block (IPLPB), utilized for IPL operation, is extended with an
+IPL Information Report Block (IIRB), which contains the results from secure IPL
+operations such as:
+
+* component data
+* verification results
+* certificate data
+
+
+Secure Code Loading Attributes Facility
+---------------------------------
+
+Secure Code Loading Attributes Facility (SCLAF) provides additional security during IPL.
+
+When SCLAF is available, its behavior depends on the IPL Modes.
+
+* secure mode: IPL will terminate on any errors detected by this facility.
+* audit mode: IPL may proceed regardless of any errors detected by this facility.
+
+Errors detected by the SCLAF are reported in IIRB.
+
+Unsigned components may only be loaded at absolute storage address x’2000’ or higher.
+
+Signed components must include a Secure Code Loading Attribute Block (SCLAB),
+which is located at the very end of the signed component.
+
+**Secure Code Loading Attribute Block (SCLAB)**
+
+The SCLAB is located at the end of each signed component. It defines the code loading
+attributes for the component and may:
+
+* Provide direction on how to process the rest of the component.
+
+* Provide further validation of information on where to load the signed binary code
+ from the load device.
+
+* Specify where to start the execution of the loaded OS code.
+
+
+DIAGNOSE function code 'X'508' - KVM IPL extensions
+---------------------------------------------------
+
+DIAGNOSE 'X'508' is reserved for KVM guest use in order to facilitate
+communication of additional IPL operations that cannot be handled by userspace,
+such as signature verification for secure IPL.
+
+If the function code specifies 0x508, KVM IPL extension functions are performed.
+These functions are meant to provide extended functionality for s390 guest boot
+that requires assistance from QEMU.
+
+Subcode 0 - query installed subcodes
+ Returns a 64-bit mask indicating which subcodes are supported.
+
+Subcode 1 - perform signature verification
+ Used to perform signature-verification on a signed component, leveraging
+ qcrypto libraries to perform this operation and pulling from the certificate
+ store.
diff --git a/docs/system/s390x/secure-ipl.rst b/docs/system/s390x/secure-ipl.rst
new file mode 100644
index 0000000000..f94b21f9fd
--- /dev/null
+++ b/docs/system/s390x/secure-ipl.rst
@@ -0,0 +1,129 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+s390 Secure IPL
+===============
+
+Secure IPL, also known as secure boot, enables s390-ccw virtual machines to
+verify the integrity of guest kernels.
+
+This document explains how to use secure IPL with s390x in QEMU. It covers
+new command line options for providing certificates and enabling secure IPL,
+the different IPL modes (Normal, Audit, and Secure), and system requirements.
+
+A quickstart guide is provided to demonstrate how to generate certificates,
+sign images, and start a guest in Secure Mode.
+
+
+Secure IPL Command Line Options
+===============================
+
+New parameters have been introduced to s390-ccw-virtio machine type option
+to support secure IPL. These parameters allow users to provide certificates
+and enable secure IPL directly via the command line.
+
+Providing Certificates
+----------------------
+
+The certificate store can be populated by supplying a comma-delimited list of
+certificates on the command-line:
+
+.. code-block:: shell
+
+ qemu-system-s390x -machine s390-ccw-virtio, \
+ boot-certificates=/.../qemu/certs:/another/path/cert.der
+
+Enabling Secure IPL
+-------------------
+
+Different IPL modes may be toggled with the following command line option:
+
+.. code-block:: shell
+
+ qemu-system-s390x -machine s390-ccw-virtio,secure-boot=on|off
+
+Additionally, the provision of certificates affect the mode.
+
+
+IPL Modes
+=========
+
+Normal Mode
+-----------
+
+The absence of both certificates and the ``secure-boot`` option will attempt to
+IPL a guest without secure IPL operations. No checks are performed, and no
+warnings/errors are reported. This is the default mode, and can be explicitly
+enabled with ``secure-boot=off``.
+
+
+Audit Mode
+----------
+
+With *only* the presence of certificates in the store, it is assumed that secure
+boot operations should be performed with errors reported as warnings. As such,
+the secure IPL operations will be performed, and any errors that stem from these
+operations will report a warning via the SCLP console.
+
+
+Secure Mode
+-----------
+
+With *both* the presence of certificates in the store and the ``secure-boot=on``
+option, it is understood that secure boot should be performed with errors
+reported and boot will abort.
+
+
+Constraints
+===========
+
+The following constraints apply when attempting to secure IPL an s390 guest:
+
+- z16 CPU model
+- certificates must be in X.509 DER format
+- only sha256 encryption is supported
+- only support for SCSI scheme of virtio-blk/virtio-scsi devices
+- a boot device must be specified
+- any unsupported devices (e.g., ECKD and VFIO) or non-eligible devices (e.g.,
+ Net) will cause the entire boot process terminating early with an error
+ logged to the console.
+
+
+Secure IPL Quickstart
+=====================
+
+Build QEMU with gnutls enabled:
+
+.. code-block:: shell
+
+ ./configure … --enable-gnutls
+
+Generate certificate (e.g. via openssl):
+
+.. code-block:: shell
+
+ openssl req -new -x509 -newkey rsa:2048 -keyout mykey.priv \
+ -outform DER -out mycert.der -days 36500 \
+ -subj "/CN=My Name/" -nodes
+
+Sign Images (e.g. via sign-file):
+
+- signing must be performed on a KVM guest filesystem
+- sign-file script used in the example below is located within the kernel source
+ repo
+
+.. code-block:: shell
+
+ ./sign-file sha256 mykey.priv mycert.der /boot/vmlinuz-…
+ ./sign-file sha256 mykey.priv mycert.der /usr/lib/s390-tools/stage3.bin
+
+Run zipl with secure boot enabled
+
+.. code-block:: shell
+
+ zipl --secure 1 -V
+
+Start Guest with Cmd Options:
+
+.. code-block:: shell
+
+ qemu-system-s390x -machine s390-ccw-virtio,secure-boot=on,boot-certificates=mycert.der ...
--
2.49.0
^ permalink raw reply related [flat|nested] 51+ messages in thread* Re: [PATCH v3 28/28] docs: Add secure IPL documentation
2025-06-04 21:56 ` [PATCH v3 28/28] docs: Add secure IPL documentation Zhuoying Cai
@ 2025-06-06 11:04 ` Daniel P. Berrangé
2025-06-17 21:29 ` Collin Walling
1 sibling, 0 replies; 51+ messages in thread
From: Daniel P. Berrangé @ 2025-06-06 11:04 UTC (permalink / raw)
To: Zhuoying Cai
Cc: thuth, richard.henderson, david, pbonzini, walling, jjherne,
jrossi, pasic, borntraeger, farman, iii, eblake, armbru,
qemu-s390x, qemu-devel
On Wed, Jun 04, 2025 at 05:56:56PM -0400, Zhuoying Cai wrote:
> Add documentation for secure IPL
>
> Signed-off-by: Collin Walling <walling@linux.ibm.com>
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
> docs/specs/s390x-secure-ipl.rst | 145 +++++++++++++++++++++++++++++++
> docs/system/s390x/secure-ipl.rst | 129 +++++++++++++++++++++++++++
> 2 files changed, 274 insertions(+)
> create mode 100644 docs/specs/s390x-secure-ipl.rst
> create mode 100644 docs/system/s390x/secure-ipl.rst
>
> +Secure IPL Quickstart
> +=====================
> +
> +Build QEMU with gnutls enabled:
> +
> +.. code-block:: shell
> +
> + ./configure … --enable-gnutls
> +
> +Generate certificate (e.g. via openssl):
> +
> +.. code-block:: shell
> +
> + openssl req -new -x509 -newkey rsa:2048 -keyout mykey.priv \
> + -outform DER -out mycert.der -days 36500 \
> + -subj "/CN=My Name/" -nodes
Please illustrate with gnutls 'certtool' for consistency
with other cert creation docs we have at:
https://www.qemu.org/docs/master/system/tls.html
With regards,
Daniel
--
|: https://berrange.com -o- https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org -o- https://fstop138.berrange.com :|
|: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH v3 28/28] docs: Add secure IPL documentation
2025-06-04 21:56 ` [PATCH v3 28/28] docs: Add secure IPL documentation Zhuoying Cai
2025-06-06 11:04 ` Daniel P. Berrangé
@ 2025-06-17 21:29 ` Collin Walling
1 sibling, 0 replies; 51+ messages in thread
From: Collin Walling @ 2025-06-17 21:29 UTC (permalink / raw)
To: Zhuoying Cai, thuth, berrange, richard.henderson, david, pbonzini
Cc: jjherne, jrossi, pasic, borntraeger, farman, iii, eblake, armbru,
qemu-s390x, qemu-devel
On 6/4/25 5:56 PM, Zhuoying Cai wrote:
> Add documentation for secure IPL
>
> Signed-off-by: Collin Walling <walling@linux.ibm.com>
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
> docs/specs/s390x-secure-ipl.rst | 145 +++++++++++++++++++++++++++++++
> docs/system/s390x/secure-ipl.rst | 129 +++++++++++++++++++++++++++
> 2 files changed, 274 insertions(+)
> create mode 100644 docs/specs/s390x-secure-ipl.rst
> create mode 100644 docs/system/s390x/secure-ipl.rst
>
> diff --git a/docs/specs/s390x-secure-ipl.rst b/docs/specs/s390x-secure-ipl.rst
> new file mode 100644
> index 0000000000..53de036dfa
> --- /dev/null
> +++ b/docs/specs/s390x-secure-ipl.rst
> @@ -0,0 +1,145 @@
> +.. SPDX-License-Identifier: GPL-2.0-or-later
> +
> +s390 Secure IPL
> +===============
> +
> +Secure IPL, also known as secure boot, enables s390-ccw virtual machines to
> +leverage qcrypto libraries and z/Arch implementations to verify the integrity of
> +guest kernels. These operations are rely on userspace invocations and QEMU
> +handling. The user provides one or more certificates via the command line
> +options, which populates a certificate store. DIAGNOSE 'X'320' is invoked by
> +userspace to query cert store info and retrieve specific certificates from QEMU.
> +DIAGNOSE 'X'508' is used by userspace to leverage qcrypto libraries to perform
> +signature-verification in QEMU. Lastly, userspace generates and appends an
> +IPL Information Report Block (IIRB) at the end of the IPL Parameter Block.
> +
Let's reword the above to:
Secure IPL (a.k.a. secure boot) enables s390-ccw virtual machines to
leverage qcrypto libraries and z/Architecture emulations to verify the
integrity of signed kernels. The qcrypto libraries are used to perform
certificate validation and signature-verification, whereas the
z/Architecture emulations are used to ensure secure IPL data has not
been tampered with, convey data between QEMU and userspace, and set up
the relevant secure IPL data structures with verification results.
To find out more about using this feature, see docs/system/s390x/secure-ipl.
Note that "userspace" will refer to the s390-ccw BIOS unless stated
otherwise.
Both QEMU and userspace work in tandem to perform secure IPL. The Secure
Loading Attributes Facility (SCLAF) is used to check the Secure Code
Loading Attribute Block (SCLAB) and ensure that secure IPL data has not
been tampered with. DIAGNOSE 'X'320' is invoked by userspace to query
the certificate store info and retrieve specific certificates from QEMU.
DIAGNOSE 'X'508' is used by userspace to leverage qcrypto libraries to
perform signature-verification in QEMU. Lastly, userspace generates and
appends an IPL Information Report Block (IIRB) at the end of the IPL
Parameter Block, which is used by the kernel to store signed and
verified entries.
The logical steps are as follows:
- Userspace reads data payload from disk (e.g. stage3 boot loader, kernel)
> +- Userspace checks the validity of the SCLAB
> +- Userspace invokes DIAG 508 subcode 1 and provides it the payload
> +- QEMU handles DIAG 508 request by reading the payload and retrieving the
> + certificate store
> +- QEMU DIAG 508 utilizes qcrypto libraries to perform signature-verification on
> + the payload, attempting with each cert in the store (until success or exhausted)
> +- QEMU DIAG 508 returns:
> +
> + - success: index of cert used to verify payload
> + - failure: error code
> +
> +- Userspace responds to this operation:
> +
> + - on success: retrieves cert from store via DIAG 320 using returned index
> + - on failure: reports with warning (audit mode), aborts with error (secure mode)
> +
> +- Userspace appends IIRB at the end of the IPLB
> +- Userspace kicks off IPL
> +
> +
Add:
More information regarding the respective DIAGNOSE commands and IPL data
structures are outlined within this document.
Add header:
s390 Certificate Store and Functions
====================================
> +s390 Certificate Store
> +======================
Change:
To a sub header
> +
> +Secure boot relies on user certificates for signature-verification. Normally,> +these certificates would be stored somewhere on the LPAR. Instead,
for virtual> +guests, a certificate store is implemented within QEMU.
This store will read
> +any certificates provided by the user via command-line, which are expected to
> +be stored somewhere on the host file system. Once these certificates are stored,
> +they are ready to be queried/requested by DIAGNOSE 'X'320' or used for
> +verification by DIAGNOSE 'X'508'.
Reword the above to:
A certificate store is implemented for s390-ccw guests to retain within
memory all certificates provided by the user via the command-line, which
are expected to be stored somewhere on the host's file system. The store
will keep track of the number of certificates, their respective size,
and a summation of the sizes.
> +
> +DIAGNOSE function code 'X'320' - Certificate Store Facility
> +-----------------------------------------------------------
> +
> +DIAGNOSE 'X'320' is used to provide support to query the certificate store.
> +
Reword the sentence above to:
DIAGNOSE 'X'320' is used to provide support for userspace to directly
query the s390 certificate store. Userspace may be the s390-ccw BIOS or
the guest kernel.
> +Subcode 0 - query installed subcodes
> + Returns a 256-bit installed subcodes mask (ISM) stored in the installed
> + subcodes block (ISB). This mask indicates which sucodes are currently
> + installed and available for use.
> +
> +Subcode 1 - query verification certificate storage information
> + Provides the information required to determine the amount of memory needed to
> + store one or more verification-certificates (VCs) from the certificate store (CS).
> +
> + Upon successful completion, this subcode returns various storage size values for
> + verification-certificate blocks (VCBs).
> +
> + The output is returned in the verification-certificate-storage-size block (VCSSB).
> +
> +Subcode 2 - store verification certificates
> + Provides VCs that are in the certificate store.
> +
> + The output is provided in a VCB, which includes a common header followed by zero
> + or more verification-certificate entries (VCEs).
> +
> + The first-VC index and last-VC index fields of VCB specify the range of VCs
> + to be stored by subcode 2. Stored count and remained count fields specify the
> + number of VCs stored and could not be stored in the VCB due to insufficient
> + storage specified in the VCB input length field.
> +
> + VCE contains various information of a VC from the CS.
> +
> +
> +Secure IPL Functions
Change to:
Secure IPL Data Structures, Facilities, and Functions
> +====================
> +
> +IPL Information Report Block
> +----------------------------
> +
> +The IPL Parameter Block (IPLPB), utilized for IPL operation, is extended with an
> +IPL Information Report Block (IIRB), which contains the results from secure IPL
> +operations such as:
> +
> +* component data
> +* verification results
> +* certificate data
> +
> +
> +Secure Code Loading Attributes Facility
> +---------------------------------
> +
> +Secure Code Loading Attributes Facility (SCLAF) provides additional security during IPL.
> +
Feedback:
Can you provide a sentence or two describing how this "provides
additional security"? What exactly does this do?
> +When SCLAF is available, its behavior depends on the IPL Modes.
> +
> +* secure mode: IPL will terminate on any errors detected by this facility.
> +* audit mode: IPL may proceed regardless of any errors detected by this facility.
Feedback:
These modes are described in the other document, and the error behavior
is covered there as well. You can remove the two lines above.
> +
> +Errors detected by the SCLAF are reported in IIRB.
> +
> +Unsigned components may only be loaded at absolute storage address x’2000’ or higher.
> +
> +Signed components must include a Secure Code Loading Attribute Block (SCLAB),
> +which is located at the very end of the signed component.
> +
> +**Secure Code Loading Attribute Block (SCLAB)**
> +
> +The SCLAB is located at the end of each signed component. It defines the code loading
> +attributes for the component and may:
> +
> +* Provide direction on how to process the rest of the component.
> +
> +* Provide further validation of information on where to load the signed binary code
> + from the load device.
> +
> +* Specify where to start the execution of the loaded OS code.
> +
Feedback:
I think it reads better by simply combining the SCLAF and SCLAB sections
(i.e. no sub sub header for SCLAB). Simply have SCLAF reference that a
SCLAB will be generated, and go into detail what it contains and what
it's for.
The SCLAF/SCLAB section is a bit difficult to digest as to what exactly
it does for secure IPL... might be a bit *too* technical :)
> +
> +DIAGNOSE function code 'X'508' - KVM IPL extensions
> +---------------------------------------------------
> +
> +DIAGNOSE 'X'508' is reserved for KVM guest use in order to facilitate
> +communication of additional IPL operations that cannot be handled by userspace,
> +such as signature verification for secure IPL.
> +
> +If the function code specifies 0x508, KVM IPL extension functions are performed.
> +These functions are meant to provide extended functionality for s390 guest boot
> +that requires assistance from QEMU.
> +
> +Subcode 0 - query installed subcodes
> + Returns a 64-bit mask indicating which subcodes are supported.
> +
> +Subcode 1 - perform signature verification
> + Used to perform signature-verification on a signed component, leveraging
> + qcrypto libraries to perform this operation and pulling from the certificate
> + store.
Reword to:
Perform signature-verification on a signed component, using certificates
from the certificate store and leveraging qcrypto libraries to perform
this operation.
> diff --git a/docs/system/s390x/secure-ipl.rst b/docs/system/s390x/secure-ipl.rst
> new file mode 100644
> index 0000000000..f94b21f9fd
> --- /dev/null
> +++ b/docs/system/s390x/secure-ipl.rst
> @@ -0,0 +1,129 @@
> +.. SPDX-License-Identifier: GPL-2.0-or-later
> +
> +s390 Secure IPL
> +===============
> +
> +Secure IPL, also known as secure boot, enables s390-ccw virtual machines to
> +verify the integrity of guest kernels.
Add:
For technical details of this feature, see docs/specs/s390x-secure-ipl
> +
> +This document explains how to use secure IPL with s390x in QEMU. It covers
> +new command line options for providing certificates and enabling secure IPL,
> +the different IPL modes (Normal, Audit, and Secure), and system requirements.
> +
> +A quickstart guide is provided to demonstrate how to generate certificates,
> +sign images, and start a guest in Secure Mode.
> +
> +
> +Secure IPL Command Line Options
> +===============================
> +
> +New parameters have been introduced to s390-ccw-virtio machine type option
> +to support secure IPL. These parameters allow users to provide certificates
> +and enable secure IPL directly via the command line.
> +
> +Providing Certificates
> +----------------------
> +
> +The certificate store can be populated by supplying a comma-delimited list of
> +certificates on the command-line:
> +
> +.. code-block:: shell
> +
> + qemu-system-s390x -machine s390-ccw-virtio, \
> + boot-certificates=/.../qemu/certs:/another/path/cert.der
> +
> +Enabling Secure IPL
> +-------------------
> +
> +Different IPL modes may be toggled with the following command line option:
> +
> +.. code-block:: shell
> +
> + qemu-system-s390x -machine s390-ccw-virtio,secure-boot=on|off
> +
> +Additionally, the provision of certificates affect the mode.
> +
> +
> +IPL Modes
> +=========
> +
> +Normal Mode
> +-----------
> +
> +The absence of both certificates and the ``secure-boot`` option will attempt to
> +IPL a guest without secure IPL operations. No checks are performed, and no
> +warnings/errors are reported. This is the default mode, and can be explicitly
> +enabled with ``secure-boot=off``.
> +
> +
> +Audit Mode
> +----------
> +
> +With *only* the presence of certificates in the store, it is assumed that secure
> +boot operations should be performed with errors reported as warnings. As such,
> +the secure IPL operations will be performed, and any errors that stem from these
> +operations will report a warning via the SCLP console.
> +
> +
> +Secure Mode
> +-----------
> +
> +With *both* the presence of certificates in the store and the ``secure-boot=on``
> +option, it is understood that secure boot should be performed with errors
> +reported and boot will abort.
> +
> +
> +Constraints
> +===========
> +
> +The following constraints apply when attempting to secure IPL an s390 guest:
> +
> +- z16 CPU model
> +- certificates must be in X.509 DER format
> +- only sha256 encryption is supported
> +- only support for SCSI scheme of virtio-blk/virtio-scsi devices
> +- a boot device must be specified
> +- any unsupported devices (e.g., ECKD and VFIO) or non-eligible devices (e.g.,
> + Net) will cause the entire boot process terminating early with an error
> + logged to the console.
> +
> +
> +Secure IPL Quickstart
> +=====================
> +
> +Build QEMU with gnutls enabled:
> +
> +.. code-block:: shell
> +
> + ./configure … --enable-gnutls
> +
> +Generate certificate (e.g. via openssl):
> +
> +.. code-block:: shell
> +
> + openssl req -new -x509 -newkey rsa:2048 -keyout mykey.priv \
> + -outform DER -out mycert.der -days 36500 \
> + -subj "/CN=My Name/" -nodes
> +
> +Sign Images (e.g. via sign-file):
> +
> +- signing must be performed on a KVM guest filesystem
> +- sign-file script used in the example below is located within the kernel source
> + repo
> +
> +.. code-block:: shell
> +
> + ./sign-file sha256 mykey.priv mycert.der /boot/vmlinuz-…
> + ./sign-file sha256 mykey.priv mycert.der /usr/lib/s390-tools/stage3.bin
> +
> +Run zipl with secure boot enabled
> +
> +.. code-block:: shell
> +
> + zipl --secure 1 -V
> +
> +Start Guest with Cmd Options:
> +
> +.. code-block:: shell
> +
> + qemu-system-s390x -machine s390-ccw-virtio,secure-boot=on,boot-certificates=mycert.der ...
--
Regards,
Collin
^ permalink raw reply [flat|nested] 51+ messages in thread