Subject: [PATCH] [FEAT VS2038] pvattest: Create, perform, and verify attestation measurements
From: Steffen Eiden <seiden@linux.ibm.com>

Summary:     pvattest: Create, perform, and verify attestation measurements
Description: pvattest is a tool to attest an IBM Secure Execution guest.
             
             In a trusted environment, one can create a request using
             `pvattest create`. To get a measurement of an untrusted
             IBM Secure Execution guest call 'pvattest perform'.
             Again in a trusted environment, call 'pvattest verify'
             to verify that the measurement is the expected one.
             
             The tool runs on s390 and x86.
             It has the same requirements like libpv and therefore
             requires openssl v1.1.1+, glib2.56+, and libcurl.
             Additionally, to measure, the linux kernel must provide
             the Ultravisor userspace interface `uvdevice` at /dev/uv
             and must be executed  on an IBM Secure Execution guest on
             hardware with Ultravisor attestation support,
             like IBM z16 or later.
Upstream-ID: 3ab06d77fb1b405411c3f257188962535950fab1
Problem-ID:  VS2038

Upstream-Description:

             pvattest: Create, perform, and verify attestation measurements

             pvattest is a tool to attest an IBM Secure Execution guest.

             In a trusted environment, one can create a request using
             `pvattest create`. To get a measurement of an untrusted
             IBM Secure Execution guest call 'pvattest perform'.
             Again in a trusted environment, call 'pvattest verify'
             to verify that the measurement is the expected one.

             The tool runs on s390 and x86.
             It has the same requirements like libpv and therefore
             requires openssl v1.1.1+, glib2.56+, and libcurl.
             Additionally, to measure, the linux kernel must provide
             the Ultravisor userspace interface `uvdevice` at /dev/uv
             and must be executed  on an IBM Secure Execution guest on
             hardware with Ultravisor attestation support, like IBM z16 or later.

             Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
             Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
             Signed-off-by: Jan Hoeppner <hoeppner@linux.ibm.com>


Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
---
 .gitignore                      |    2 
 Makefile                        |    2 
 README.md                       |   20 +
 pvattest/Makefile               |   22 +
 pvattest/README.md              |  100 ++++++
 pvattest/man/Makefile           |    9 
 pvattest/man/pvattest-create.1  |   73 ++++
 pvattest/man/pvattest-perform.1 |   47 ++
 pvattest/man/pvattest-verify.1  |   60 +++
 pvattest/man/pvattest.1         |  104 ++++++
 pvattest/src/.gitignore         |    2 
 pvattest/src/Makefile           |  124 +++++++
 pvattest/src/arcb.c             |  423 ++++++++++++++++++++++++++
 pvattest/src/arcb.h             |  152 +++++++++
 pvattest/src/argparse.c         |  649 ++++++++++++++++++++++++++++++++++++++++
 pvattest/src/argparse.h         |  106 ++++++
 pvattest/src/attestation.c      |  148 +++++++++
 pvattest/src/attestation.h      |   99 ++++++
 pvattest/src/common.c           |   47 ++
 pvattest/src/common.h           |   37 ++
 pvattest/src/config.h           |   31 +
 pvattest/src/exchange_format.c  |  480 +++++++++++++++++++++++++++++
 pvattest/src/exchange_format.h  |  166 ++++++++++
 pvattest/src/log.c              |  181 +++++++++++
 pvattest/src/log.h              |   67 ++++
 pvattest/src/pvattest.c         |  392 ++++++++++++++++++++++++
 pvattest/src/types.h            |   18 +
 pvattest/src/uvio.c             |  177 ++++++++++
 pvattest/src/uvio.h             |  110 ++++++
 29 files changed, 3844 insertions(+), 4 deletions(-)

--- a/.gitignore
+++ b/.gitignore
@@ -64,6 +64,7 @@ lsstp/lsstp
 mon_tools/mon_fsstatd
 mon_tools/mon_procd
 osasnmpd/osasnmpd
+pvattest/src/pvattest
 qetharp/qetharp
 qethqoat/qethqoat
 systemd/cpacfstatsd.service
@@ -117,3 +118,4 @@ zkey/kmip/zkey-kmip.so
 zkey/zkey
 zkey/zkey-cryptsetup
 zpcictl/zpcictl
+**/.detect-openssl.dep.c
--- a/Makefile
+++ b/Makefile
@@ -16,7 +16,7 @@ TOOL_DIRS = zipl zdump fdasd dasdfmt das
 	   vmconvert vmcp man mon_tools dasdinfo vmur cpuplugd ipl_tools \
 	   ziomon iucvterm hyptop cmsfs-fuse qethqoat zfcpdump zdsfs cpumf \
 	   systemd hmcdrvfs cpacfstats zdev dump2tar zkey netboot etc zpcictl \
-	   genprotimg lsstp hsci hsavmcore chreipl-fcp-mpath
+	   genprotimg lsstp hsci hsavmcore chreipl-fcp-mpath pvattest
 
 SUB_DIRS = $(BASELIB_DIRS) $(LIB_DIRS) $(TOOL_DIRS)
 
--- a/README.md
+++ b/README.md
@@ -33,6 +33,9 @@ Package contents
  * genprotimg:
    Create a protected virtualization image.
 
+ * pvattest:
+   Create, perform, and verify protected virtualization attestation measurements.
+
  * udev rules:
    - 59-dasd.rules: rules for unique DASD device nodes created in /dev/disk/.
    - 57-osasnmpd.rules: udev rules for osasnmpd.
@@ -300,12 +303,13 @@ build options:
 | net-snmp       | `HAVE_SNMP`        | osasnmpd                              |
 | glibc-static   | `HAVE_LIBC_STATIC` | zfcpdump                              |
 | openssl        | `HAVE_OPENSSL`     | genprotimg, zkey, libekmfweb,         |
-|                |                    | libkmipclient                         |
+|                |                    | libkmipclient, pvattest               |
 | cryptsetup     | `HAVE_CRYPTSETUP2` | zkey-cryptsetup                       |
 | json-c         | `HAVE_JSONC`       | zkey-cryptsetup, libekmfweb,          |
 |                |                    | libkmipclient                         |
-| glib2          | `HAVE_GLIB2`       | genprotimg                            |
-| libcurl        | `HAVE_LIBCURL`     | genprotimg, libekmfweb, libkmipclient |
+| glib2          | `HAVE_GLIB2`       | genprotimg, pvattest                  |
+| libcurl        | `HAVE_LIBCURL`     | genprotimg, libekmfweb, libkmipclient,|
+|                |                    | pvattest                              |
 | libxml2        | `HAVE_LIBXML2`     | libkmipclient                         |
 | systemd        | `HAVE_SYSTEMD`     | hsavmcore                             |
 
@@ -337,6 +341,16 @@ the different tools are provided:
 
   The runtime requirements are: openssl-libs (>= 1.1.0) and glib2.
 
+* pvattest:
+  For building pvattest you need OpenSSL version 1.1.1 or newer
+  installed (openssl-devel.rpm). Also required is glib2.56 or newer
+  (glib2-devel.rpm) and libcurl.
+  Tip: you may skip the pvattest build by adding
+  `HAVE_OPENSSL=0`, `HAVE_LIBCURL=0`, or `HAVE_GLIB2=0`.
+
+  The runtime requirements are: openssl-libs (>= 1.1.1) and
+  glib2.56 or newer.
+
 * osasnmpd:
   You need at least the NET-SNMP 5.1.x package (net-snmp-devel.rpm)
   installed, before building the osasnmpd subagent.
--- /dev/null
+++ b/pvattest/Makefile
@@ -0,0 +1,22 @@
+# Common definitions
+include ../common.mak
+
+.DEFAULT_GOAL := all
+
+PKGDATADIR := "$(DESTDIR)$(TOOLS_DATADIR)/pvattest"
+SUBDIRS := src man
+RECURSIVE_TARGETS := all-recursive clean-recursive install-recursive
+
+all: all-recursive
+
+install: all install-recursive
+
+clean: clean-recursive
+
+$(RECURSIVE_TARGETS):
+	@target=`echo $@ |sed s/-recursive//`; \
+		for d in $(SUBDIRS); do \
+			$(MAKE) -C $$d $$target || exit 1; \
+		done
+
+.PHONY: all install clean $(RECURSIVE_TARGETS)
--- /dev/null
+++ b/pvattest/README.md
@@ -0,0 +1,100 @@
+# pvattest
+
+Use `pvattest` to attest an IBM Secure Execution guest running on z16 and later.
+
+With `pvattest` you can create attestation requests in a trusted environment and attest
+an IBM Secure Execution for Linux guest to verify that a provider is running the correct image.
+To achieve this, use the following commands:
+  * `create` On a trusted system, creates an attestation request.
+  * `perform` Performs an attestation measurement on the SE-guest to be attested. For this a
+    attestation request is sent to the Ultravisor (UV) and the answer received. The `perform`
+    command requires IBM z16 or later z/Architecture hardware.
+  * `verify` On a trusted system, compares the answer from the Ultravisor to the
+    expected answer. If they differ, the Secure Execution guest might be a different guest
+    than expected, or not secure at all.
+
+For meaningful results, run `create` and `verify` only in a trusted environment,
+like your workstation or a previously attested IBM Secure Execution guest.
+Otherwise, the attestation can be compromised.
+For all certificates, revocation lists, and host-key documents, both the PEM and DER input
+formats are supported. If you run this program on a non S390 System, 'perform' is not be available.
+
+## Getting started
+
+If all dependencies are met (see the s390-tools README) issue `make` in the source tree to build `pvattest`.
+
+## Details
+### create
+`pvattest create` needs the host-key-document, a location to store the
+attestation request protection key, and a location to store the request data.
+Unless the `--no-verify` flag is set it additionally requires the IBM signing key
+and the intermediate CA. The output contains the request in binary form which serves as input
+to `pvattest perform`. Must be run in a trusted environment. Especially, do not create the request
+on a system you want to attest. The attestation request protection key is valid for this request only,
+must be kept until the verification is completed and must be destroyed afterwards
+Keep the key secret.
+
+### perform
+`pvattest perform` needs a request in binary form generated by `pvattest create`and
+a location to store the output. It will send the request to the device at `/dev/uv`
+which passes the request to the Ultravisor.
+Kernel will then send the request to the Ultravisor which will calculate the answer.
+The Answer is then passed back to userspace and handled by `pvattest`
+The output includes the original request and the answer from the Ultravisor.
+
+### verify
+`pvattest verify` needs the SE-guest header, the attestation request protection key,
+and the attestation request and the response to the `pvattest perform` command from the Ultravisor.
+It calculates the measurement in the trusted environment and compares it to the response from
+the Ultravisor in the previous step.
+The following return codes are possible:
+
+0. successful verification: The calculated measurement matches the response from the Ultravisor
+
+1. failed verification: The command ended with an error, for example, because of incorrect input or an invalid SE header
+
+2. failed verification: The calculated measurement does not match the response from the Ultravisor
+
+Run `pvattest verify` in a trusted environment. Especially, do not verify on the system you want to attest.
+
+## Measurement
+The measurement is a cryptographic measurement of the following block.
+Only HMAC-SHA512 is supported.
+
+| Start   | Size       | Content                                                       |
+|---------|------------|---------------------------------------------------------------|
+| 0x0     | 0x40       | Page List Digest (from SE header)                             |
+| 0x40    | 0x40       | Address List Digest (from SE header)                          |
+| 0x80    | 0x40       | Tweak List Digest (from SE header)                            |
+| 0xc0    | 0x10       | SE Header Tag (from SE header)                                |
+| 0xd0    | 0x10       | Configuration UID (generated by UV, included in the answer)   |
+| 0xe0    | 0x02       | User Data Length (defined during measurement on the SE-guest) |
+| 0xe2    | 0x02       | Zeros                                                         |
+| 0xe4    | 0x04       | Additional Data Length (set by UV, included in the answer)    |
+| 0xe8    | 0 - 0x100  | User Data (generated during measurement on the SE-guest)      |
+| ...     | 0 or 0x10  | Optional Nonce (generated during request creation)            |
+| ...     | 0 - 0x8000 | Additional Data (generated by UV, included in the answer)     |
+
+### User Data
+By default `pvattest` does not include any User Data, therefore the length is zero.
+`User Data` is data generated by the SE guest and passed to UV during the measurement.
+The `User Data` must be known to or be replicable by the verifier to verify the correctness of the User Data.
+The addition of user data is currently an experimental setting.
+
+### Additional Data
+`Additional data` is data known to the Ultravisor. By default UV will not include any `Additional Data`.
+Adding `Additional Data` is currently an experimental setting.
+
+## Example
+
+Create an attestation request in a trusted environment:
+
+`pvattest create -k hkd.crt --arpk arp.key -o arcb.bin --cert IntermediateCA.crt --cert IbmSigningKey.crt`
+
+Perform an attestation measurement on an IBM Secure Execution guest:
+
+`pvattest perform --input arcb.bin --output measurement.bin`
+
+Verify the response from the Ultravisor against the attestation request in a trusted environment:
+
+`pvattest verify --input measurement.bin --arpk arp.key --hdr se_guest.hdr`
--- /dev/null
+++ b/pvattest/man/Makefile
@@ -0,0 +1,9 @@
+include ../../common.mak
+
+all:
+
+install:
+	$(INSTALL) -d -m 755 $(DESTDIR)$(MANDIR)/man1
+	$(INSTALL) -m 644 -c *.1 -t $(DESTDIR)$(MANDIR)/man1
+
+.PHONY: all install clean
--- /dev/null
+++ b/pvattest/man/pvattest-create.1
@@ -0,0 +1,73 @@
+.\" Copyright 2022 IBM Corp.
+.\" s390-tools is free software; you can redistribute it and/or modify
+.\" it under the terms of the MIT license. See LICENSE for details.
+.\"
+.TH pvattest-create 1 "07 June 2022" "s390-tools" "Attestation Manual"
+.nh
+.ad l
+.SH NAME
+\fBpvattest [OPTION?] create [OPTIONS] \fP- create an attestation measurement request
+\fB
+.SH DESCRIPTION
+Prepare attestation measurement requests for an IBM Secure Execution guest. Only prepare attestation requests in a trusted environment, such as your workstation. The 'pvattest create' command creates a randomly generated key to protect the attestation request. This key is only valid for this specific request.In order to avoid compromising the attestation, do not publish the protection key and delete it after verification. Every 'create' command generates a new, random protection key.
+.SH OPTIONS
+.TP
+.B
+\fB-h\fP, \fB--help\fP
+Show help options
+.TP
+.B
+\fB-k\fP, \fB--host-key-document\fP=\fBFILE\fP
+Specify one or more host key documents.
+.TP
+.B
+\fB-C\fP, \fB--cert\fP=\fBFILE\fP
+Specifies  the  certificate that is used to establish a chain of trust for the verification of the host-key documents. Specify this option twice to specify the IBM Z signing key and the intermediate CA certificate (signed by the root CA). Required. Ignored when \fB--no-verify\fP is specified.
+.TP
+.B
+\fB--crl\fP=\fBFILE\fP
+Specify \fBFILE\fP to be a certificate revocation list (optional).
+.TP
+.B
+\fB--root-ca\fP=\fBFILE\fP
+Use \fBFILE\fP as the trusted root CA instead the root CAs that are installed on the system (optional).
+.TP
+.B
+\fB-o\fP, \fB--output\fP=\fBFILE\fP
+\fBFILE\fP specifies the output for the attestation request control block.
+.TP
+.B
+\fB-a\fP, \fB--arpk\fP=\fBFILE\fP
+Save the protection key as GCM-AES256 key in \fBFILE\fP Do not publish this key, otherwise your attestation is compromised.
+.TP
+.B
+\fB--no-verify\fP
+Disable the host-key-document verification. Does not require the host-key documents to be valid. For testing purposes, do not use for a production image. (Optional)
+.TP
+.B
+\fB--offline\fP
+Don't download CRLs (optional).
+.TP
+.B
+\fB-V\fP, \fB--verbose\fP
+Provide more detailed output (optional)
+.SH EXAMPLE
+Create an attestation request with the protection key 'arp.key', write the request to 'arcb.bin', and verify the host-key document using the CA-signed key 'DigiCertCA.crt' and the intermediate key 'IbmSigningKey.crt'.
+.PP
+.nf
+.fam C
+        pvattest create -k hkd.crt --arpk arp.key -o attreq.bin --cert DigiCertCA.crt --cert IbmSigningKey.crt
+
+.fam T
+.fi
+Create an attestation request with the protection key 'arp.key', write the request to 'arcb.bin', verify the host-key document using the CA-signed key 'DigiCertCA.crt' and the intermediate key 'IbmSigningKey.crt', and instead of downloading the certificate revocation list use certificate revocation lists 'DigiCertCA.crl', 'IbmSigningKey.crl', and 'rootCA.crl'.
+.PP
+.nf
+.fam C
+        pvattest create -k hkd.crt --arpk arp.key -o attreq.bin --cert DigiCertCA.crt --cert IbmSigningKey.crt --offline --crl DigiCertCA.crl --crl IbmSigningKey.crl --crl rootCA.crl
+
+
+.fam T
+.fi
+.SH SEE ALSO
+\fBpvattest\fP(1), \fBpvattest-verify\fP(1), \fBpvattest-perform\fP(1)
--- /dev/null
+++ b/pvattest/man/pvattest-perform.1
@@ -0,0 +1,47 @@
+.\" Copyright 2022 IBM Corp.
+.\" s390-tools is free software; you can redistribute it and/or modify
+.\" it under the terms of the MIT license. See LICENSE for details.
+.\"
+.TH pvattest-perform 1 "07 June 2022" "s390-tools" "Attestation Manual"
+.nh
+.ad l
+.SH NAME
+\fBpvattest [OPTION?] perform [OPTIONS] \fP- execute an attestation measurement request
+\fB
+.SH DESCRIPTION
+Run a measurement of this system using '/dev/uv'. Works only if this device is available and the attestation Ultravisor facility is present. The input must be an attestation request created with 'pvattest create'. Output will contain the original request, the attestation measurement result, the configuration UID, and if requested in the request Additional Data.
+.RE
+.PP
+
+.SH OPTIONS
+.TP
+.B
+\fB-h\fP, \fB--help\fP
+Show help options
+.TP
+.B
+\fB-i\fP, \fB--input\fP=\fBFILE\fP
+\fBFILE\fP specifies the attestation request as input.
+.TP
+.B
+\fB-o\fP, \fB--output\fP=\fBFILE\fP
+\fBFILE\fP specifies the output for the attestation result.
+.TP
+.B
+\fB-V\fP, \fB--verbose\fP
+Provide more detailed output (optional)
+.RE
+.PP
+
+.SH EXAMPLE
+Perform an attestation measurement with the attestation request 'arcb.bin' and write the output to 'measurement.bin'.
+.PP
+.nf
+.fam C
+        pvattest perform --input attreq.bin --output attresp.bin
+
+
+.fam T
+.fi
+.SH SEE ALSO
+\fBpvattest\fP(1), \fBpvattest-create\fP(1), \fBpvattest-verify\fP(1)
--- /dev/null
+++ b/pvattest/man/pvattest-verify.1
@@ -0,0 +1,60 @@
+.\" Copyright 2022 IBM Corp.
+.\" s390-tools is free software; you can redistribute it and/or modify
+.\" it under the terms of the MIT license. See LICENSE for details.
+.\"
+.TH pvattest-verify 1 "07 June 2022" "s390-tools" "Attestation Manual"
+.nh
+.ad l
+.SH NAME
+\fBpvattest [OPTION?] verify [OPTIONS] \fP- verify an attestation measurement
+\fB
+.SH DESCRIPTION
+Verify that a previously generated attestation measurement of an IBM Secure Execution guest is as expected. Only verify attestation requests in a trusted environment, such as your workstation. Input must contain the response as produced by 'pvattest perform'. The protection key must be the one that was used to create the request by 'pvattest create'. Please delete it after verification. The header must be the IBM Secure Execution header of the image that was attested during 'pvattest perform'
+.RE
+.PP
+
+.SH OPTIONS
+.TP
+.B
+\fB-h\fP, \fB--help\fP
+Show help options
+.TP
+.B
+\fB-i\fP, \fB--input\fP=\fBFILE\fP
+\fBFILE\fP specifies the attestation result as input.
+.TP
+.B
+\fB--hdr\fP=\fBFILE\fP
+Specify the header of the guest image. Exactly one is required.
+.TP
+.B
+\fB-a\fP, \fB--arpk\fP=\fBFILE\fP
+Use \fBFILE\fP to specify the GCM-AES256 key to decrypt the attestation request. Delete this key after verification.
+.TP
+.B
+\fB-V\fP, \fB--verbose\fP
+Provide more detailed output (optional)
+.RE
+.PP
+
+.SH EXAMPLE
+To verify a measurement in 'measurement.bin' with the protection key 'arp.kep' and SE-guest header 'se_guest.hdr'.
+.PP
+.nf
+.fam C
+        pvattest verify --input attresp.bin --arpk arp.key --hdr se_guest.hdr
+
+.fam T
+.fi
+If the verification was successful the program exists with zero.
+If the verification failed it exists with 2 and prints the following to stderr:
+.PP
+.nf
+.fam C
+        ERROR: Attestation measurement verification failed:
+               Calculated and received attestation measurement are not the same.
+
+.fam T
+.fi
+.SH SEE ALSO
+\fBpvattest\fP(1), \fBpvattest-create\fP(1), \fBpvattest-perform\fP(1)
--- /dev/null
+++ b/pvattest/man/pvattest.1
@@ -0,0 +1,104 @@
+.\" Copyright 2022 IBM Corp.
+.\" s390-tools is free software; you can redistribute it and/or modify
+.\" it under the terms of the MIT license. See LICENSE for details.
+.\"
+.TH pvattest 1 "07 June 2022" "s390-tools" "Attestation Manual"
+.nh
+.ad l
+.SH NAME
+\fBpvattest [OPTION?] COMMAND [OPTIONS] \fP- create, perform, and verify attestation measurements
+\fB
+.RE
+\fB
+.SH SYNOPSIS
+.nf
+.fam C
+\fBpvattest\fP \fIcreate\fP [\fIOPTIONS\fP]
+\fBpvattest\fP \fIperform\fP [\fIOPTIONS\fP]
+\fBpvattest\fP \fIverify\fP [\fIOPTIONS\fP]
+
+.fam T
+.fi
+.fam T
+.fi
+.SH DESCRIPTION
+Use \fBpvattest\fP to attest that an IBM Secure Execution guest is the correct guest, and that it was started in a secure manner.
+Run '\fBpvattest\fP \fIcreate\fP' and '\fBpvattest\fP \fIverify\fP' in a trusted environment only.
+.PP
+.nf
+.fam C
+        create    On a trusted system, creates an attestation request.
+
+        perform   On the SE-guest to be attested, sends the attestation request to the Ultravisor and receives the answer.
+
+        verify    On a trusted system, compares the answer from the Ultravisor to the one from your trusted environment. If they differ, the Secure Execution guest might be compromised.
+
+.fam T
+.fi
+For meaningful results, run '\fIcreate\fP' and '\fIverify\fP' in a trusted environment, like your workstation or a previously attested IBM Secure Execution guest. Otherwise, the attestation might be tampered with. For all certificates, revocation lists, and host-key documents, both the PEM and DER input formats are supported. If you run \fBpvattest\fP on a machine architecture other than z/Architecture, 'measure' is not available.
+.PP
+Use '\fBpvattest\fP [COMMAND] \fB-h\fP' to get detailed help
+.RE
+.PP
+
+.SH OPTIONS
+.TP
+.B
+\fB-h\fP, \fB--help\fP
+Show help options
+.TP
+.B
+\fB-v\fP, \fB--version\fP
+Print the version and exit.
+.TP
+.B
+\fB-V\fP, \fB--verbose\fP
+Provide more detailed output (optional)
+.RE
+.PP
+
+.SH EXAMPLE
+For details refer to the man page of the command.
+.PP
+Create the request on a trusted system.
+.PP
+.nf
+.fam C
+	trusted:~$ pvattest create -k hkd.crt --cert CA.crt --cert ibmsk.crt --arpk arp.key -o attreq.bin
+
+.fam T
+.fi
+On the SE-guest, \fIperform\fP the attestation.
+.PP
+.nf
+.fam C
+	seguest:~$ pvattest perform -i attreq.bin -o attresp.bin
+
+.fam T
+.fi
+On a trusted system, \fIverify\fP that the response is correct. Here, the protection key from the creation and the SE-guest’s header is used to \fIverify\fP the measurement.
+.PP
+.nf
+.fam C
+	trusted:~$ pvattest verify -i attresp.bin --arpk arp.key --hdr se_guest.hdr
+	trusted:~$ echo $?
+	0
+
+.fam T
+.fi
+
+If the measurements do not match \fBpvattest\fP exits with code 2 and emits an error message. The SE-guest attestation failed.
+.PP
+.nf
+.fam C
+	trusted:~$ pvattest verify -i wrongresp.bin --arpk arp.key --hdr se_guest.hdr
+	ERROR: Attestation measurement verification failed:
+	       Calculated and received attestation measurement are not the same.
+	trusted:~$ echo $?
+	2
+
+.fam T
+.fi
+
+.SH SEE ALSO
+\fBpvattest\fP-\fIcreate\fP(1), \fBpvattest-\fIverify\fP\fP(1), \fBpvattest\fP-\fIperform\fP(1)
--- /dev/null
+++ b/pvattest/src/.gitignore
@@ -0,0 +1,2 @@
+.check-dep-pvattest
+.detect-openssl.dep.c
--- /dev/null
+++ b/pvattest/src/Makefile
@@ -0,0 +1,124 @@
+include ../../common.mak
+
+BIN_PROGRAM = pvattest
+PKGDATADIR ?= "$(DESTDIR)$(TOOLS_DATADIR)/$(BIN_PROGRAM)"
+
+SRC_DIR := $(dir $(realpath $(firstword $(MAKEFILE_LIST))))
+ROOT_DIR = $(rootdir)
+PVATTESTDIR := $(ROOT_DIR)/pvattest
+INCLUDE_PATHS = "$(SRC_DIR)" "$(ROOT_DIR)/include"
+INCLUDE_PARMS = $(addprefix -I,$(INCLUDE_PATHS))
+
+LIBPV_DIR = $(ROOT_DIR)/libpv
+LIBPV = $(LIBPV_DIR)/libpv.a
+
+WARNINGS := -Wall -Wextra -Wshadow \
+	    -Wcast-align -Wwrite-strings -Wmissing-prototypes \
+	    -Wmissing-declarations -Wredundant-decls -Wnested-externs \
+	    -Wno-long-long -Wuninitialized -Wconversion -Wstrict-prototypes \
+	    -Wpointer-arith -Wno-error=inline \
+	    -Wno-unused-function -Wno-unused-parameter -Wno-unused-variable \
+	    -Werror \
+	    $(NULL)
+
+PVATTEST_SRCS := $(wildcard *.c) \
+		$(NULL)
+
+$(BIN_PROGRAM)_SRCS := \
+		$(PVATTEST_SRCS) \
+		$(NULL)
+
+$(BIN_PROGRAM)_OBJS := $($(BIN_PROGRAM)_SRCS:.c=.o)
+
+ifneq ($(shell sh -c 'command -v pkg-config'),)
+GLIB2_CFLAGS := $(shell pkg-config --silence-errors --cflags glib-2.0)
+GLIB2_LIBS := $(shell pkg-config --silence-errors --libs glib-2.0)
+LIBCRYPTO_CFLAGS := $(shell pkg-config --silence-errors --cflags libcrypto openssl)
+LIBCRYPTO_LIBS := $(shell pkg-config --silence-errors --libs libcrypto openssl)
+LIBCURL_CFLAGS := $(shell pkg-config --silence-errors --cflags libcurl)
+LIBCURL_LIBS := $(shell pkg-config --silence-errors --libs libcurl)
+else
+GLIB2_CFLAGS := -I/usr/include/glib-2.0 -I/usr/lib64/glib-2.0/include
+GLIB2_LIBS := -lglib-2.0
+LIBCRYPTO_CFLAGS :=
+LIBCRYPTO_LIBS := -lcrypto -lssl
+LIBCURL_CFLAGS := -I/usr/include/s390x-linux-gnu
+LIBCURL_LIBS := -lcurl
+endif
+
+ALL_CFLAGS += -std=gnu11 \
+	      -DPKGDATADIR=$(PKGDATADIR) \
+	      -DOPENSSL_API_COMPAT=0x10101000L \
+	      $(GLIB2_CFLAGS) \
+	      $(LIBCRYPTO_CFLAGS) \
+	      $(LIBCURL_CFLAGS) \
+	      $(WARNINGS) \
+	      $(NULL)
+
+ifneq ($(call check_header_prereq,"asm/uvdevice.h"),yes)
+	ALL_CFLAGS += -DPVATTEST_NO_PERFORM
+endif
+
+ALL_CPPFLAGS += $(INCLUDE_PARMS)
+LDLIBS += $(GLIB2_LIBS) $(LIBCRYPTO_LIBS) $(LIBCURL_LIBS)
+
+BUILD_TARGETS := skip-$(BIN_PROGRAM)
+INSTALL_TARGETS := skip-$(BIN_PROGRAM)
+ifneq (${HAVE_OPENSSL},0)
+ifneq (${HAVE_GLIB2},0)
+ifneq (${HAVE_LIBCURL}, 0)
+	BUILD_TARGETS := $(BIN_PROGRAM)
+	INSTALL_TARGETS := install-$(BIN_PROGRAM)
+endif
+endif
+endif
+
+all: $(BUILD_TARGETS) .check-dep-$(BIN_PROGRAM)
+
+install: $(INSTALL_TARGETS)
+
+$(BIN_PROGRAM): $($(BIN_PROGRAM)_OBJS) $(LIBPV)
+
+skip-$(BIN_PROGRAM):
+	echo "  SKIP    $(BIN_PROGRAM) due to unresolved dependencies"
+
+clean:
+	$(RM) -f -- $($(BIN_PROGRAM)_OBJS) $(BIN_PROGRAM) .check-dep-$(BIN_PROGRAM) .detect-openssl.dep.c
+
+install-$(BIN_PROGRAM): $(BIN_PROGRAM)
+	$(INSTALL) -d -m 755 $(DESTDIR)$(USRBINDIR)
+	$(INSTALL) -c $^ $(DESTDIR)$(USRBINDIR)
+
+
+.PHONY: all install clean skip-$(BIN_PROGRAM) install-$(BIN_PROGRAM)
+
+$($(BIN_PROGRAM)_OBJS): .check-dep-$(BIN_PROGRAM)
+
+.detect-openssl.dep.c:
+	echo "#include <openssl/evp.h>" > $@
+	echo "#if OPENSSL_VERSION_NUMBER < 0x10101000L" >> $@
+	echo "  #error openssl version 1.1.0 is required" >> $@
+	echo "#endif" >> $@
+	echo "static void __attribute__((unused)) test(void) {" >> $@
+	echo "    EVP_MD_CTX *ctx = EVP_MD_CTX_new();" >> $@
+	echo "    EVP_MD_CTX_free(ctx);" >> $@
+	echo "}" >> $@
+
+.check-dep-$(BIN_PROGRAM): .detect-openssl.dep.c
+	$(call check_dep, \
+		"$(BIN_PROGRAM)", \
+		"glib.h", \
+		"glib2-devel / libglib2.0-dev", \
+		"HAVE_GLIB2=0")
+	$(call check_dep, \
+		"$(BIN_PROGRAM)", \
+		"openssl/evp.h", \
+		"openssl-devel / libssl-dev version >= 1.1.0", \
+		"HAVE_OPENSSL=0", \
+		"-I.")
+	$(call check_dep, \
+		"$(BIN_PROGRAM)", \
+		"curl/curl.h", \
+		"libcurl-devel", \
+		"HAVE_LIBCURL=0")
+	touch $@
--- /dev/null
+++ b/pvattest/src/arcb.c
@@ -0,0 +1,423 @@
+/*
+ * Attestation Request Control Block related functions
+ *
+ * Copyright IBM Corp. 2022
+ *
+ * s390-tools is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ */
+/* Must be included before any other header */
+#include "config.h"
+
+#include <openssl/evp.h>
+#include <stdlib.h>
+
+#include "libpv/crypto.h"
+#include "libpv/hash.h"
+
+#include "arcb.h"
+#include "common.h"
+#include "log.h"
+
+#define ARVN_VERSION_1 0x0100
+#define MAX_ARL 0x2000
+
+typedef struct arcb_v1_hdr {
+	uint64_t reserved0; /* 0x0000  */
+	be32_t arvn; /* 0x0008  */
+	be32_t arl; /* 0x000c  */
+	uint8_t iv[ARCB_V1_IV_SIZE]; /* 0x0010  */
+	uint32_t reserved1c; /* 0x001c  */
+	uint8_t reserved20[7]; /* 0x0020  */
+	uint8_t nks; /* 0x0027  */
+	uint32_t reserved28; /* 0x0028  */
+	be32_t sea; /* 0x002c  */
+	be64_t paf; /* 0x0030  */
+	be32_t mai; /* 0x0038  */
+	uint32_t reserved3c; /* 0x003c  */
+	PvEcdhPubKey cpk; /* 0x0040  */
+} __packed arcb_v1_hdr_t;
+G_STATIC_ASSERT(sizeof(arcb_v1_hdr_t) == 0xe0);
+
+typedef struct arcb_v1_key_slot {
+	uint8_t phkh[ARCB_V1_PHKH_SIZE];
+	uint8_t warpk[ARCB_V1_ATTEST_PROT_KEY_SIZE];
+	uint8_t kst[ARCB_V1_TAG_SIZE];
+} __packed arcb_v1_key_slot_t;
+G_STATIC_ASSERT(sizeof(arcb_v1_key_slot_t) == 0x50);
+
+struct arcb_v1 {
+	/* authenticated data */
+	uint32_t arvn;
+	uint32_t mai;
+	uint64_t paf;
+	GBytes *iv;
+	EVP_PKEY *evp_cust_pub_key;
+	GSList *host_key_slots;
+
+	/* confidential data */
+	GBytes *confidential_measurement_key;
+	GBytes *confidential_optional_nonce;
+	GBytes *confidential_att_req_prot_key;
+};
+
+void arcb_v1_clear_free(arcb_v1_t *arcb)
+{
+	if (!arcb)
+		return;
+
+	g_slist_free_full(arcb->host_key_slots, g_free);
+	g_bytes_unref(arcb->confidential_measurement_key);
+	g_bytes_unref(arcb->confidential_optional_nonce);
+	g_bytes_unref(arcb->confidential_att_req_prot_key);
+	g_bytes_unref(arcb->iv);
+	EVP_PKEY_free(arcb->evp_cust_pub_key);
+	g_free(arcb);
+}
+
+static void arcb_v1_set_paf(arcb_v1_t *arcb, const uint64_t paf, GError **error)
+{
+	const uint64_t known_flags = ARCB_V1_PAF_ALL & ~ARCB_V1_PAF_NONCE;
+
+	if ((paf & ARCB_V1_PAF_NONCE) != 0) {
+		g_set_error(error, ARCB_ERROR, ARCB_ERR_INVALID_PAF,
+			    _("The given paf (%#.16lx) specifies the NONCE flag (%#.16lx)."), paf,
+			    ARCB_V1_PAF_NONCE);
+		return;
+	}
+	if ((paf & ~known_flags) != 0)
+		pvattest_log_warning(
+			_("The given paf (%#.16lx) specifies unknown flags. Use at your own risk!"),
+			paf, known_flags);
+	arcb->paf = paf;
+}
+
+arcb_v1_t *arcb_v1_new(GBytes *arpk, GBytes *iv, uint32_t mai, EVP_PKEY *evp_cpk, GBytes *mkey,
+		       uint64_t paf, GError **error)
+{
+	g_autoptr(arcb_v1_t) arcb = g_new0(arcb_v1_t, 1);
+
+	g_assert(g_bytes_get_size(iv) == ARCB_V1_IV_SIZE);
+	g_assert(g_bytes_get_size(arpk) == ARCB_V1_ATTEST_PROT_KEY_SIZE);
+	g_assert(g_bytes_get_size(mkey) == HMAC_SHA512_KEY_SIZE);
+
+	pv_wrapped_g_assert(arpk);
+	pv_wrapped_g_assert(iv);
+	pv_wrapped_g_assert(evp_cpk);
+	pv_wrapped_g_assert(mkey);
+
+	arcb->arvn = ARVN_VERSION_1;
+	arcb->mai = mai;
+	arcb_v1_set_paf(arcb, paf, error);
+	if (*error)
+		return NULL;
+	arcb->iv = g_bytes_ref(iv);
+
+	if (EVP_PKEY_up_ref(evp_cpk) != 1)
+		g_abort();
+	arcb->evp_cust_pub_key = evp_cpk;
+
+	arcb->confidential_att_req_prot_key = g_bytes_ref(arpk);
+	arcb->confidential_measurement_key = g_bytes_ref(mkey);
+
+	return g_steal_pointer(&arcb);
+}
+
+int arcb_v1_add_key_slot(arcb_v1_t *arcb, EVP_PKEY *evp_host, GError **error)
+{
+	g_autoptr(GBytes) warpk = NULL, tag = NULL, phkh = NULL;
+	g_autoptr(GBytes) exchange_key = NULL, iv = NULL;
+	g_autofree arcb_v1_key_slot_t *key_slot = NULL;
+	g_autofree PvEcdhPubKey *ecdh_host = NULL;
+	g_autofree uint8_t *iv_raw = NULL;
+	PvCipherParms parms;
+	int64_t gcm_rc;
+
+	g_assert(arcb->confidential_att_req_prot_key);
+
+	pv_wrapped_g_assert(arcb);
+	pv_wrapped_g_assert(evp_host);
+
+	/* encrypt (=wrap) attestation request protection key, store warpk + tag */
+	exchange_key = pv_derive_exchange_key(arcb->evp_cust_pub_key, evp_host, error);
+	if (!exchange_key)
+		return -1;
+
+	iv_raw = g_malloc0(ARCB_V1_IV_SIZE);
+	iv = g_bytes_new_take(g_steal_pointer(&iv_raw), ARCB_V1_IV_SIZE);
+	if (!iv)
+		g_abort();
+
+	parms.key = exchange_key;
+	parms.iv = iv;
+	parms.cipher = EVP_aes_256_gcm();
+	parms.tag_size = ARCB_V1_TAG_SIZE;
+	gcm_rc = pv_gcm_encrypt(arcb->confidential_att_req_prot_key, NULL, &parms, &warpk, &tag,
+				error);
+	if (gcm_rc != ARCB_V1_ATTEST_PROT_KEY_SIZE)
+		return -1;
+
+	/* calculate public host key hash */
+	ecdh_host = pv_evp_pkey_to_ecdh_pub_key(evp_host, error);
+	if (!ecdh_host)
+		return -1;
+	phkh = pv_sha256_hash(ecdh_host->data, sizeof(ecdh_host->data), error);
+	if (!phkh)
+		return -1;
+
+	/* copy to list */
+	g_assert(g_bytes_get_size(warpk) == sizeof(key_slot->warpk));
+	g_assert(g_bytes_get_size(tag) == sizeof(key_slot->kst));
+	g_assert(g_bytes_get_size(phkh) == sizeof(key_slot->phkh));
+
+	key_slot = g_malloc0(sizeof(*key_slot));
+	pv_gbytes_memcpy(key_slot->warpk, sizeof(key_slot->warpk), warpk);
+	pv_gbytes_memcpy(key_slot->kst, sizeof(key_slot->warpk), tag);
+	pv_gbytes_memcpy(key_slot->phkh, sizeof(key_slot->warpk), phkh);
+
+	arcb->host_key_slots = g_slist_prepend(arcb->host_key_slots, g_steal_pointer(&key_slot));
+	return 0;
+}
+
+void arcb_v1_set_nonce(arcb_v1_t *arcb, GBytes *nonce)
+{
+	pv_wrapped_g_assert(arcb);
+	pv_wrapped_g_assert(nonce);
+	arcb_v1_rm_nonce(arcb);
+	g_assert(!arcb->confidential_optional_nonce);
+
+	g_assert(g_bytes_get_size(nonce) == ARCB_V1_NONCE_SIZE);
+	arcb->confidential_optional_nonce = g_bytes_ref(nonce);
+
+	arcb->paf |= ARCB_V1_PAF_NONCE;
+}
+
+void arcb_v1_rm_nonce(arcb_v1_t *arcb)
+{
+	pv_wrapped_g_assert(arcb);
+	if (!arcb->confidential_optional_nonce)
+		return;
+	g_bytes_unref(arcb->confidential_optional_nonce);
+	arcb->confidential_optional_nonce = NULL;
+	arcb->paf &= ~ARCB_V1_PAF_NONCE;
+}
+
+GBytes *arcb_v1_serialize(const arcb_v1_t *arcb, GError **error)
+{
+	pv_wrapped_g_assert(arcb);
+	g_autoptr(GByteArray) arcb_gba = NULL;
+	g_autoptr(GBytes) confidential_area = NULL;
+	g_autoptr(GBytes) aad = NULL;
+	g_autoptr(GBytes) art = NULL;
+	g_autoptr(GBytes) encrypted_area = NULL;
+	g_autoptr(GBytes) result = NULL;
+	g_autofree PvEcdhPubKey *ecdh_cpk = NULL;
+	PvCipherParms parms = {
+		.cipher = EVP_aes_256_gcm(),
+		.tag_size = AES_256_GCM_TAG_SIZE,
+	};
+	size_t att_req_len = 0, nks = 0, sea = 0;
+
+	arcb_v1_hdr_t hdr = {
+		.arvn = GUINT32_TO_BE(arcb->arvn),
+		.paf = GUINT64_TO_BE(arcb->paf),
+		.mai = GUINT32_TO_BE(arcb->mai),
+	};
+
+	g_assert(arcb->host_key_slots);
+
+	/* calculate sizes */
+	nks = g_slist_length(arcb->host_key_slots);
+	g_assert(nks < 0xFF);
+
+	sea = g_bytes_get_size(arcb->confidential_measurement_key);
+	if (arcb->confidential_optional_nonce)
+		sea += g_bytes_get_size(arcb->confidential_optional_nonce);
+
+	g_assert(sea == HMAC_SHA512_KEY_SIZE || sea == HMAC_SHA512_KEY_SIZE + ARCB_V1_NONCE_SIZE);
+
+	att_req_len = sizeof(hdr) + nks * sizeof(arcb_v1_key_slot_t) + HMAC_SHA512_KEY_SIZE +
+		      ARCB_V1_TAG_SIZE;
+	if (arcb->confidential_optional_nonce)
+		att_req_len += ARCB_V1_NONCE_SIZE;
+
+	g_assert(att_req_len <= MAX_ARL);
+
+	/* copy plain data to contiguous memory  */
+	hdr.arl = GUINT32_TO_BE((uint32_t)att_req_len);
+
+	pv_gbytes_memcpy(hdr.iv, ARCB_V1_IV_SIZE, arcb->iv);
+	hdr.nks = (uint8_t)nks;
+	hdr.sea = GUINT32_TO_BE((uint32_t)sea);
+	ecdh_cpk = pv_evp_pkey_to_ecdh_pub_key(arcb->evp_cust_pub_key, error);
+	memcpy(&hdr.cpk, ecdh_cpk, sizeof(*ecdh_cpk));
+	arcb_gba = g_byte_array_sized_new((guint)att_req_len);
+	g_byte_array_append(arcb_gba, (const uint8_t *)&hdr, sizeof(hdr));
+
+	for (GSList *elem = arcb->host_key_slots; elem; elem = elem->next)
+		g_byte_array_append(arcb_gba, elem->data, sizeof(arcb_v1_key_slot_t));
+
+	/* encrypt the confidential data */
+	confidential_area = secure_gbytes_concat(arcb->confidential_measurement_key,
+						 arcb->confidential_optional_nonce);
+	parms.key = arcb->confidential_att_req_prot_key;
+	parms.iv = arcb->iv;
+	aad = g_bytes_new(arcb_gba->data, arcb_gba->len);
+	pv_gcm_encrypt(confidential_area, aad, &parms, &encrypted_area, &art, error);
+	if (*error)
+		return NULL;
+
+	g_byte_array_append(arcb_gba, g_bytes_get_data(encrypted_area, NULL), (guint)sea);
+	g_byte_array_append(arcb_gba, g_bytes_get_data(art, NULL), ARCB_V1_TAG_SIZE);
+
+	result = g_byte_array_free_to_bytes(arcb_gba);
+	arcb_gba = NULL;
+	return g_steal_pointer(&result);
+}
+
+uint32_t arcb_v1_get_required_measurement_size(const arcb_v1_t *arcb, GError **error)
+{
+	pv_wrapped_g_assert(arcb);
+	switch (arcb->mai) {
+	case MAI_HMAC_SHA512:
+		return HMAC_SHA512_KEY_SIZE;
+	default:
+		g_set_error(error, ARCB_ERROR, ARCB_ERR_INVALID_MAI,
+			    _("Unknown measurement algorithm ID specified (%#x)."), arcb->mai);
+		return 0;
+	}
+}
+
+uint32_t arcb_v1_get_required_additional_size(const arcb_v1_t *arcb)
+{
+	uint32_t size = 0;
+
+	pv_wrapped_g_assert(arcb);
+
+	if (arcb_v1_additional_has_phkh_image(arcb))
+		size += ARCB_V1_PHKH_SIZE;
+	if (arcb_v1_additional_has_phkh_attest(arcb))
+		size += ARCB_V1_PHKH_SIZE;
+	return size;
+}
+
+gboolean arcb_v1_use_nonce(const arcb_v1_t *arcb)
+{
+	pv_wrapped_g_assert(arcb);
+	return arcb->confidential_optional_nonce != NULL;
+}
+
+gboolean arcb_v1_additional_has_phkh_image(const arcb_v1_t *arcb)
+{
+	pv_wrapped_g_assert(arcb);
+	return (arcb->paf & ARCB_V1_PAF_AAD_PHKH_HEADER) != 0;
+}
+
+gboolean arcb_v1_additional_has_phkh_attest(const arcb_v1_t *arcb)
+{
+	pv_wrapped_g_assert(arcb);
+	return (arcb->paf & ARCB_V1_PAF_AAD_PHKH_ATTEST) != 0;
+}
+
+GBytes *arcb_v1_get_measurement_key(const arcb_v1_t *arcb)
+{
+	pv_wrapped_g_assert(arcb);
+	return g_bytes_ref(arcb->confidential_measurement_key);
+}
+
+GBytes *arcb_v1_get_nonce(const arcb_v1_t *arcb)
+{
+	pv_wrapped_g_assert(arcb);
+	if (arcb->confidential_optional_nonce)
+		return g_bytes_ref(arcb->confidential_optional_nonce);
+	return NULL;
+}
+
+GBytes *arcb_v1_get_arp_key(const arcb_v1_t *arcb)
+{
+	pv_wrapped_g_assert(arcb);
+	return g_bytes_ref(arcb->confidential_att_req_prot_key);
+}
+
+static gboolean is_v1_arcb(size_t aad_size, size_t sea, size_t arl, size_t serialized_arcb_size,
+			   uint32_t arcb_version, gboolean has_nonce)
+{
+	gboolean result = aad_size + sea + ARCB_V1_TAG_SIZE == arl;
+
+	result &= arl <= serialized_arcb_size;
+	result &= arcb_version == ARVN_VERSION_1;
+	result &= has_nonce ? sea == HMAC_SHA512_KEY_SIZE + ARCB_V1_NONCE_SIZE :
+				    sea == HMAC_SHA512_KEY_SIZE;
+	return result;
+}
+
+gboolean arcb_v1_verify_serialized_arcb(GBytes *serialized_arcb, GBytes *arpk,
+					GBytes **measurement_key, GBytes **optional_nonce,
+					GError **error)
+{
+	g_autoptr(GBytes) encr = NULL, decr = NULL, aad = NULL, tag = NULL, iv = NULL;
+	const struct arcb_v1_hdr *serialized_arcb_hdr;
+	const uint8_t *encr_u8, *aad_u8, *tag_u8;
+	const uint8_t *serialized_arcb_u8;
+	size_t serialized_arcb_size;
+	uint32_t arcb_version, mai;
+	size_t aad_size, arl, sea;
+	PvCipherParms parms;
+	gboolean has_nonce;
+	uint64_t paf;
+
+	pv_wrapped_g_assert(serialized_arcb);
+	pv_wrapped_g_assert(arpk);
+	serialized_arcb_u8 = g_bytes_get_data(serialized_arcb, &serialized_arcb_size);
+	serialized_arcb_hdr = (const arcb_v1_hdr_t *)serialized_arcb_u8;
+	arl = GUINT32_FROM_BE(serialized_arcb_hdr->arl);
+	arcb_version = GUINT32_FROM_BE(serialized_arcb_hdr->arvn);
+	mai = GUINT32_FROM_BE(serialized_arcb_hdr->mai);
+
+	aad_u8 = serialized_arcb_u8;
+	aad_size = sizeof(*serialized_arcb_hdr) +
+		   serialized_arcb_hdr->nks * sizeof(arcb_v1_key_slot_t);
+	encr_u8 = aad_u8 + aad_size;
+	sea = GUINT32_FROM_BE(serialized_arcb_hdr->sea);
+	tag_u8 = encr_u8 + sea;
+	paf = GUINT64_FROM_BE(serialized_arcb_hdr->paf);
+	has_nonce = (paf & ARCB_V1_PAF_NONCE) != 0;
+
+	if (!is_v1_arcb(aad_size, sea, arl, serialized_arcb_size, arcb_version, has_nonce)) {
+		g_set_error(error, ARCB_ERROR, ARCB_ERR_INVALID_ARCB,
+			    _("The provided attestation request is not valid"));
+		return FALSE;
+	}
+	if (mai != MAI_HMAC_SHA512) {
+		g_set_error(error, ARCB_ERROR, ARCB_ERR_INVALID_MAI,
+			    _("Unsupported measurement argument ID (%#x)"), mai);
+		return FALSE;
+	}
+
+	aad = g_bytes_new(aad_u8, aad_size);
+	encr = g_bytes_new(encr_u8, sea);
+	tag = g_bytes_new(tag_u8, ARCB_V1_TAG_SIZE);
+	iv = g_bytes_new(serialized_arcb_hdr->iv, sizeof(serialized_arcb_hdr->iv));
+
+	parms.cipher = EVP_aes_256_gcm();
+	parms.tag_size = AES_256_GCM_TAG_SIZE;
+	parms.key = arpk;
+	parms.iv = iv;
+	pv_gcm_decrypt(encr, aad, tag, &parms, &decr, error);
+	if (*error) {
+		GError *tmp_error = NULL;
+
+		g_set_error(&tmp_error, ARCB_ERROR, ARCB_ERR_INVALID_ARCB,
+			    _("Cannot verify the attestation request: %s"), (*error)->message);
+		g_clear_error(error);
+		g_propagate_error(error, tmp_error);
+		return FALSE;
+	}
+	if (measurement_key)
+		*measurement_key = g_bytes_new(g_bytes_get_data(decr, NULL), HMAC_SHA512_KEY_SIZE);
+	if (optional_nonce && has_nonce)
+		*optional_nonce =
+			g_bytes_new((uint8_t *)g_bytes_get_data(decr, NULL) + HMAC_SHA512_KEY_SIZE,
+				    ARCB_V1_NONCE_SIZE);
+	return TRUE;
+}
--- /dev/null
+++ b/pvattest/src/arcb.h
@@ -0,0 +1,152 @@
+/*
+ * Attestation Request Control Block related functions
+ *
+ * Copyright IBM Corp. 2022
+ *
+ * s390-tools is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ */
+#ifndef PVATTEST_ARCB_H
+#define PVATTEST_ARCB_H
+/* Must be included before any other header */
+#include "config.h"
+
+#include "libpv/glib-helper.h"
+
+#include "lib/zt_common.h"
+#include "libpv/crypto.h"
+
+#include "types.h"
+
+#define MAI_HMAC_RESERVED_INVALID 0
+#define MAI_HMAC_SHA512 0x1
+
+#define HMAC_SHA512_KEY_SIZE 64
+#define ARCB_V1_ATTEST_PROT_KEY_SIZE 32
+#define ARCB_V1_NONCE_SIZE 16
+#define ARCB_V1_TAG_SIZE 16
+#define ARCB_V1_IV_SIZE 12
+#define ARCB_V1_PHKH_SIZE 32
+
+#define BIT(bit) ((uint64_t)1 << (63 - (bit)))
+/* Optional nonce in ARCB */
+#define ARCB_V1_PAF_NONCE BIT(1)
+/* Public host key hash used to unseal SE header added to additional data to be measured */
+#define ARCB_V1_PAF_AAD_PHKH_HEADER BIT(2)
+/* Public host key hash used to unseal this attestation added to additional data to be measured */
+#define ARCB_V1_PAF_AAD_PHKH_ATTEST BIT(3)
+/* Temporary backup-host-key use allowed */
+#define ARCB_V1_PAF_TMP_BACKUP_ALLOWED BIT(62)
+
+/* Global not-host-specific key allowed */
+#define ARCB_V1_PAF_GLOBAL_NHS_KEY_ALLOWED BIT(63)
+
+#define ARCB_V1_PAF_ALL                                                                  \
+	(ARCB_V1_PAF_NONCE | ARCB_V1_PAF_AAD_PHKH_HEADER | ARCB_V1_PAF_AAD_PHKH_ATTEST | \
+	 ARCB_V1_PAF_TMP_BACKUP_ALLOWED | ARCB_V1_PAF_GLOBAL_NHS_KEY_ALLOWED)
+
+typedef struct arcb_v1 arcb_v1_t;
+
+/** arcb_v1_new:
+ *
+ * @arpk: Attestation Request Protection key. AES-GCM-256 key to
+ *        protect Measurement Key and Nonce.
+ *        Must be ´ARCB_V1_ATTEST_PROT_KEY_SIZE´ bytes long.
+ * @iv: IV for protecting Measuremt Key and Nonce.
+ *      Should be random for each new ARPK.
+ *      Must be ´ARCB_V1_IV_SIZE´ bytes long.
+ * @mai:  Measurement Algorithm Identifier for the attestation measurement.
+ *       See ´enum mai´
+ * @evp_cpk: Customer key in EVP_PKEY format. Must contain private and public key pair.
+ * @mkey: Measurement key to calculate the Measurement.
+ *        Must be ´HMAC_SHA512_KEY_SIZE´ bytes long.
+ * @paf: Plain text Attestation Flags. See ´enum plaintext_attestattion_flags´.
+ *       ´ARCB_V1_PAF_NONCE´ must not be set.
+ * @error: GError. *error will != NULL if error occours.
+ *
+ * arpk, mkey, and iv must me correct size
+ * If not this is considered as a programming error (No warning;
+ * Results in Assertion or undefined behavior).
+ *
+ * GBytes will be ref'ed.
+ *
+ * All numbers must be in system byte order and will be converted to big endian
+ * if needed.
+ *
+ * Returns: (nullable) (transfer full): new ARCB context.
+ */
+arcb_v1_t *arcb_v1_new(GBytes *arpk, GBytes *iv, uint32_t mai, EVP_PKEY *evp_cpk, GBytes *mkey,
+		       uint64_t paf, GError **error) PV_NONNULL(1, 2, 4, 5);
+void arcb_v1_clear_free(arcb_v1_t *arcb);
+WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(arcb_v1_t, arcb_v1_clear_free)
+
+/** arcb_v1_add_key_slot:
+ *
+ * @arcb: ARCB context.
+ * @evp_host: Host public key.
+ * @error: GError. *error will != NULL if error occours.
+ *
+ * Builds a key slot. Calculates exchange key, wraps ARPK with the exchange key.
+ * Calculates the public host key hash. Calculates the key slot tag.
+ * Adds it to the ARCB.
+ *
+ * Returns: 0 in case of success, -1 otherwise
+ */
+int arcb_v1_add_key_slot(arcb_v1_t *arcb, EVP_PKEY *evp_host, GError **error) PV_NONNULL(1, 2);
+void arcb_v1_set_nonce(arcb_v1_t *arcb, GBytes *nonce) PV_NONNULL(1, 2);
+void arcb_v1_rm_nonce(arcb_v1_t *arcb) PV_NONNULL(1);
+
+/** arcb_v1_serialize:
+ *
+ * @arcb: ARCB context.
+ * @error: GError. *error will != NULL if error occurs.
+ *
+ * Will create a valid ARCB for the UV. Including encrypting confidential data.
+ * At least one key_slot must be added beforehand.
+ *
+ * Returns: (nullable) (transfer full):  The serialized ARCB which can be added to the
+ * 		Retrieve Attestation Measurement UVC as GBytes.
+ */
+GBytes *arcb_v1_serialize(const arcb_v1_t *arcb, GError **error) PV_NONNULL(1, 2);
+
+uint32_t arcb_v1_get_required_measurement_size(const arcb_v1_t *arcb, GError **error)
+	PV_NONNULL(1, 2);
+uint32_t arcb_v1_get_required_additional_size(const arcb_v1_t *arcb) PV_NONNULL(1);
+gboolean arcb_v1_use_nonce(const arcb_v1_t *arcb) PV_NONNULL(1);
+gboolean arcb_v1_additional_has_phkh_image(const arcb_v1_t *arcb) PV_NONNULL(1);
+gboolean arcb_v1_additional_has_phkh_attest(const arcb_v1_t *arcb) PV_NONNULL(1);
+
+GBytes *arcb_v1_get_measurement_key(const arcb_v1_t *arcb) PV_NONNULL(1);
+GBytes *arcb_v1_get_nonce(const arcb_v1_t *arcb) PV_NONNULL(1);
+GBytes *arcb_v1_get_arp_key(const arcb_v1_t *arcb) PV_NONNULL(1);
+
+/** arcb_v1_verify_serialized_arcb:
+ *
+ * @serialized_arcb: binary ARCB in UV readable format.
+ * @arpk: Attestation Request Protection key that was used to create serialized_arpk
+ * @measurement_key: Output parameter: decrypted measurement key if no error.
+ *                   May be NULL if not interested for this output.
+ * @optional_nonce: Output parameter: decrypted nonce if no error.
+ *                  May be NULL if not interested for this output.
+ * @error: GError. *error will != NULL if error occurs.
+ *
+ *
+ * Checks if sizes are sound and flags are known by this implementation.
+ * Decrypts Measurement key and nonce (if given) and verifies ARCB tag.
+ *
+ * Returns: TRUE if ARCB is valid, including matching ARCB tag. Otherwise FALSE.
+ *
+ */
+gboolean arcb_v1_verify_serialized_arcb(GBytes *serialized_arcb, GBytes *arpk,
+					GBytes **measurement_key, GBytes **optional_nonce,
+					GError **error) PV_NONNULL(1, 2);
+
+#define ARCB_ERROR g_quark_from_static_string("pv-arcb_error-quark")
+typedef enum arcb_error {
+	ARCB_ERR_INVALID_ARCB,
+	ARCB_ERR_INVALID_PAF,
+	ARCB_ERR_INVALID_MAI,
+	ARCB_ERR_UNABLE_ENCR_ARPK,
+} arcb_error_e;
+
+#endif /* PVATTEST_ARCB_H */
--- /dev/null
+++ b/pvattest/src/argparse.c
@@ -0,0 +1,649 @@
+/*
+ * Definitions used for parsing arguments.
+ *
+ * Copyright IBM Corp. 2022
+ *
+ * s390-tools is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ */
+/* Must be included before any other header */
+#include "config.h"
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include "argparse.h"
+#include "log.h"
+#include "common.h"
+
+#define DEFAULT_OUTPUT_FILE_NAME "attest.bin"
+#define DEFAULT_OPTION_PHKH_IMG FALSE
+#define DEFAULT_OPTION_PHKH_ATT FALSE
+#define DEFAULT_OPTION_NO_VERIFY FALSE
+#define DEFAULT_OPTION_ONLINE TRUE
+#define DEFAULT_OPTION_NONCE TRUE
+
+static pvattest_config_t pvattest_config = {
+	.general = {
+		.log_level = PVATTEST_LOG_LVL_DEFAULT,
+	},
+	.create = {
+		.output_path = NULL,
+		.host_key_document_paths = NULL,
+		.crl_paths = NULL,
+		.root_ca_path = NULL,
+		.certificate_paths = NULL,
+		.arp_key_out_path = NULL,
+		.phkh_img = DEFAULT_OPTION_PHKH_IMG,
+		.phkh_att = DEFAULT_OPTION_PHKH_ATT,
+		.online = DEFAULT_OPTION_ONLINE,
+		.use_nonce = DEFAULT_OPTION_NONCE,
+		.paf = 0,
+		.x_aad_size = -1,
+	},
+	.perform = {
+		.output_path = NULL,
+		.input_path = NULL,
+	},
+	.verify = {
+		.input_path = NULL,
+		.hdr_path = NULL,
+		.arp_key_in_path = NULL,
+	},
+};
+typedef gboolean (*verify_options_fn_t)(GError **);
+
+static gboolean check_for_non_null(const void *ptr, const char *msg, GError **error)
+{
+	if (!ptr) {
+		g_set_error(error, PVATTEST_ERROR, PVATTEST_ERR_INV_ARG, "%s", msg);
+		return FALSE;
+	}
+	return TRUE;
+}
+
+static gboolean _check_for_invalid_path(const char *path, gboolean must_exist, GError **error)
+{
+	int cached_errno = 0;
+
+	g_assert(path);
+
+	if (must_exist) {
+		if (access(path, F_OK | R_OK) != 0)
+			cached_errno = errno;
+	}
+	if (cached_errno) {
+		g_set_error(error, PVATTEST_ERROR, PVATTEST_ERR_INV_ARG, "Cannot access '%s': %s",
+			    path, g_strerror(cached_errno));
+		return FALSE;
+	}
+	return TRUE;
+}
+
+static gboolean check_for_optional_invalid_path(const char *path, gboolean must_exist,
+						GError **error)
+{
+	if (!path)
+		return TRUE;
+	return _check_for_invalid_path(path, must_exist, error);
+}
+
+static gboolean check_for_invalid_path(const char *path, gboolean must_exist, const char *null_msg,
+				       GError **error)
+{
+	if (!check_for_non_null(path, null_msg, error))
+		return FALSE;
+	return _check_for_invalid_path(path, must_exist, error);
+}
+
+static gboolean _check_file_list(char **path_list, gboolean must_exist, GError **error)
+{
+	char *path = NULL;
+	for (char **path_it = path_list; path_it != NULL && *path_it != NULL; path_it++) {
+		path = *path_it;
+		if (!_check_for_invalid_path(path, must_exist, error))
+			return FALSE;
+	}
+	return TRUE;
+}
+
+static gboolean check_optional_file_list(char **path_list, gboolean must_exist, GError **error)
+{
+	if (!path_list)
+		return TRUE;
+	return _check_file_list(path_list, must_exist, error);
+}
+
+static gboolean check_file_list(char **path_list, gboolean must_exist, const char *null_msg,
+				GError **error)
+{
+	if (!check_for_non_null(path_list, null_msg, error))
+		return FALSE;
+	return _check_file_list(path_list, must_exist, error);
+}
+
+static gboolean hex_str_toull(const char *nptr, uint64_t *dst, GError **error)
+{
+	uint64_t value;
+	gchar *end;
+
+	g_assert(dst);
+
+	if (!g_str_is_ascii(nptr)) {
+		g_set_error(
+			error, PVATTEST_ERROR, PVATTEST_ERR_INV_ARG,
+			_("Invalid value: '%s'. A hexadecimal value is required, for example '0xcfe'"),
+			nptr);
+		return FALSE;
+	}
+
+	value = g_ascii_strtoull(nptr, &end, 16);
+	if ((value == G_MAXUINT64 && errno == ERANGE) || (end && *end != '\0')) {
+		g_set_error(
+			error, PVATTEST_ERROR, PVATTEST_ERR_INV_ARG,
+			_("Invalid value: '%s'. A hexadecimal value is required, for example '0xcfe'"),
+			nptr);
+		return FALSE;
+	}
+	*dst = value;
+	return TRUE;
+}
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
+
+/************************* SHARED OPTIONS *************************************/
+/* NOTE REQUIRED */
+#define _entry_host_key_document(__arg_data, __indent)                                            \
+	{                                                                                         \
+		.long_name = "host-key-document", .short_name = 'k', .flags = G_OPTION_FLAG_NONE, \
+		.arg = G_OPTION_ARG_FILENAME_ARRAY, .arg_data = __arg_data,                       \
+		.description = "Specify one or more host key documents.\n",                       \
+		.arg_description = "FILE",                                                        \
+	}
+
+/* NOTE REQUIRED */
+#define _entry_certs(__arg_data, __indent)                                                            \
+	{                                                                                             \
+		.long_name = "cert", .short_name = 'C', .flags = G_OPTION_FLAG_NONE,                  \
+		.arg = G_OPTION_ARG_FILENAME_ARRAY, .arg_data = __arg_data,                           \
+		.description =                                                                        \
+			"Specifies  the  certificate that is used to establish a chain\n" __indent    \
+			"of trust for the verification of the host-key documents. Specify\n" __indent \
+			"this option twice to specify the IBM Z signing key and the\n" __indent       \
+			"intermediate CA certificate (signed by the root CA). Required.\n" __indent   \
+			"Ignored when --no-verify is specified.\n",                                   \
+		.arg_description = "FILE",                                                            \
+	}
+
+/* NOTE REQUIRED */
+#define _entry_crls(__arg_data, __indent)                                                    \
+	{                                                                                    \
+		.long_name = "crl", .short_name = 0, .flags = G_OPTION_FLAG_NONE,            \
+		.arg = G_OPTION_ARG_FILENAME_ARRAY, .arg_data = __arg_data,                  \
+		.description = "Specify FILE to be a certificate revocation list\n" __indent \
+			       "(optional).",                                                \
+		.arg_description = "FILE",                                                   \
+	}
+
+/* NOTE REQUIRED */
+#define _entry_root_ca(__arg_data, __indent)                                            \
+	{                                                                               \
+		.long_name = "root-ca", .short_name = 0, .flags = G_OPTION_FLAG_NONE,   \
+		.arg = G_OPTION_ARG_FILENAME_ARRAY, .arg_data = __arg_data,             \
+		.description = "Use FILE as the trusted root CA instead the\n" __indent \
+			       "root CAs that are installed on the system (optional).", \
+		.arg_description = "FILE",                                              \
+	}
+
+/* NOTE REQUIRED */
+#define _entry_guest_hdr(__arg_data, __indent)                                               \
+	{                                                                                    \
+		.long_name = "hdr", .short_name = 0, .flags = G_OPTION_FLAG_NONE,            \
+		.arg = G_OPTION_ARG_FILENAME, .arg_data = __arg_data,                        \
+		.description =                                                               \
+			"Specify the header of the guest image. Exactly one is required.\n", \
+		.arg_description = "FILE",                                                   \
+	}
+
+/* NOTE REQUIRED */
+#define _entry_input(__arg_data, __additional_text, __indent)                         \
+	{                                                                             \
+		.long_name = "input", .short_name = 'i', .flags = G_OPTION_FLAG_NONE, \
+		.arg = G_OPTION_ARG_FILENAME, .arg_data = __arg_data,                 \
+		.description = "FILE specifies the " __additional_text "\n" __indent  \
+			       " as input.\n",                                        \
+		.arg_description = "FILE",                                            \
+	}
+
+/* NOTE REQUIRED */
+#define _entry_output(__arg_data, __additional_text, __indent)                                  \
+	{                                                                                       \
+		.long_name = "output", .short_name = 'o', .flags = G_OPTION_FLAG_NONE,          \
+		.arg = G_OPTION_ARG_FILENAME, .arg_data = __arg_data,                           \
+		.description = "FILE specifies the output for the\n" __indent __additional_text \
+			       ".\n",                                                           \
+		.arg_description = "FILE",                                                      \
+	}
+
+/* NOTE REQUIRED */
+#define _entry_att_prot_key_save(__arg_data, __indent)                                           \
+	{                                                                                        \
+		.long_name = "arpk", .short_name = 'a', .flags = G_OPTION_FLAG_NONE,             \
+		.arg = G_OPTION_ARG_FILENAME, .arg_data = __arg_data,                            \
+		.description =                                                                   \
+			"Save the protection key as GCM-AES256 key in FILE\n" __indent           \
+			"Do not publish this key, otherwise your attestation is compromised.\n", \
+		.arg_description = "FILE",                                                       \
+	}
+
+/* NOTE REQUIRED */
+#define _entry_att_prot_key_load(__arg_data, __indent)                                        \
+	{                                                                                     \
+		.long_name = "arpk", .short_name = 'a', .flags = G_OPTION_FLAG_NONE,          \
+		.arg = G_OPTION_ARG_FILENAME, .arg_data = __arg_data,                         \
+		.description = "Use FILE to specify the GCM-AES256 key to decrypt\n" __indent \
+			       "the attestation request.\n" __indent                          \
+			       "Delete this key after verification.\n",                       \
+		.arg_description = "FILE",                                                    \
+	}
+
+#define _entry_phkh_img(__arg_data, __indent)                                            \
+	{                                                                                \
+		.long_name = "x-phkh-img", .short_name = 0, .flags = G_OPTION_FLAG_NONE, \
+		.arg = G_OPTION_ARG_NONE, .arg_data = __arg_data,                        \
+		.description = "add the public host key hash of the\n" __indent          \
+			       "image header used to decrypt\n" __indent                 \
+			       "the secure guest to the measurement. (optional)\n"       \
+	}
+
+#define _entry_phkh_att(__arg_data, __indent)                                             \
+	{                                                                                 \
+		.long_name = "x-phkh-att", .short_name = 0, .flags = G_OPTION_FLAG_NONE,  \
+		.arg = G_OPTION_ARG_NONE, .arg_data = __arg_data,                         \
+		.description = "add the public host key hash of the\n" __indent           \
+			       "attestation header used to decrypt\n" __indent            \
+			       "the attestation request to the measurement. (optional)\n" \
+	}
+
+#define _entry_no_verify(__arg_data, __indent)                                                \
+	{                                                                                     \
+		.long_name = "no-verify", .short_name = 0, .flags = G_OPTION_FLAG_NONE,       \
+		.arg = G_OPTION_ARG_NONE, .arg_data = __arg_data,                             \
+		.description =                                                                \
+			"Disable the host-key-document verification.\n" __indent              \
+			"Does not require the host-key documents to be valid.\n" __indent     \
+			"For testing purposes, do not use for a production image.\n" __indent \
+			"(optional)\n",                                                       \
+	}
+
+#define _entry_offline_maps_to_online(__arg_data, __indent)                              \
+	{                                                                                \
+		.long_name = "offline", .short_name = 0, .flags = G_OPTION_FLAG_REVERSE, \
+		.arg = G_OPTION_ARG_NONE, .arg_data = __arg_data,                        \
+		.description = "Don't download CRLs (optional).\n",                      \
+	}
+
+#define _entry_verbose(__indent)                                                          \
+	{                                                                                 \
+		.long_name = "verbose", .short_name = 'V', .flags = G_OPTION_FLAG_NO_ARG, \
+		.arg = G_OPTION_ARG_CALLBACK, .arg_data = &increase_log_lvl,              \
+		.description = "Provide more detailed output (optional)\n",               \
+		.arg_description = NULL,                                                  \
+	}
+
+#define _entry_x_paf(__arg_data, __indent)                                             \
+	{                                                                              \
+		.long_name = "x-paf", .short_name = 0, .flags = G_OPTION_FLAG_NONE,    \
+		.arg = G_OPTION_ARG_CALLBACK, .arg_data = __arg_data,                  \
+		.description = "Specify the Plain text Attestation Flags\n" __indent   \
+			       "as a hexadecimal value. Flags that change\n" __indent  \
+			       "the paf (--phkh-*) take precedence over\n" __indent    \
+			       "this flag.\n" __indent                                 \
+			       "Setting the nonce paf is not allowed here.\n" __indent \
+			       "(optional, default 0x0)\n",                            \
+		.arg_description = "HEX",                                              \
+	}
+
+#define _entry_x_no_nonce(__arg_data, __indent)                                             \
+	{                                                                                   \
+		.long_name = "x-no-nonce", .short_name = 0, .flags = G_OPTION_FLAG_REVERSE, \
+		.arg = G_OPTION_ARG_NONE, .arg_data = __arg_data,                           \
+		.description = "Do not use a nonce in the request.\n" __indent              \
+			       "(optional, not recommended)\n"                              \
+	}
+
+#define _entry_x_aad_size(__arg_data, __indent)                                                   \
+	{                                                                                         \
+		.long_name = "x-add-size", .short_name = 0, .flags = G_OPTION_FLAG_NONE,          \
+		.arg = G_OPTION_ARG_INT, .arg_data = __arg_data,                                  \
+		.description = "Specify the size of the additional area\n" __indent               \
+			       "Overwrite every flag that changes\n" __indent                     \
+			       "this size implicitly. No verification is performed!\n" __indent   \
+			       "Ignored if negative.\n" __indent "(optional, default ignored)\n", \
+		.arg_description = "INT"                                                          \
+	}
+
+#define _entry_x_user_data(__arg_data, __indent)                                                  \
+	{                                                                                         \
+		.long_name = "x-user-data", .short_name = 0, .flags = G_OPTION_FLAG_NONE,         \
+		.arg = G_OPTION_ARG_FILENAME, .arg_data = __arg_data,                             \
+		.description = "Use FILE to specify the user data.\n", .arg_description = "FILE", \
+	}
+
+static gboolean increase_log_lvl(G_GNUC_UNUSED const char *option_name,
+				 G_GNUC_UNUSED const char *value, G_GNUC_UNUSED void *data,
+				 G_GNUC_UNUSED GError **error)
+{
+	pvattest_log_increase_log_lvl(&pvattest_config.general.log_level);
+	return TRUE;
+}
+
+static gboolean create_set_paf(G_GNUC_UNUSED const char *option_name, const char *value,
+			       G_GNUC_UNUSED void *data, GError **error)
+{
+	return hex_str_toull(value, &pvattest_config.create.paf, error);
+}
+
+/***************************** GENERAL OPTIONS ********************************/
+static gboolean print_version = FALSE;
+
+static GOptionEntry general_options[] = {
+	{
+		.long_name = "version",
+		.short_name = 'v',
+		.flags = G_OPTION_FLAG_NONE,
+		.arg = G_OPTION_ARG_NONE,
+		.arg_data = &print_version,
+		.description = "Print the version and exit.\n",
+		.arg_description = NULL,
+	},
+	_entry_verbose(""),
+	{ NULL },
+};
+
+/************************* CREATE ATTESTATION OPTIONS *************************/
+#define create_indent "                                   "
+
+static GOptionEntry create_options[] = {
+	_entry_host_key_document(&pvattest_config.create.host_key_document_paths, create_indent),
+	_entry_certs(&pvattest_config.create.certificate_paths, create_indent),
+	_entry_crls(&pvattest_config.create.crl_paths, create_indent),
+	_entry_root_ca(&pvattest_config.create.root_ca_path, create_indent),
+	_entry_output(&pvattest_config.create.output_path, "attestation request", create_indent),
+	_entry_att_prot_key_save(&pvattest_config.create.arp_key_out_path, create_indent),
+
+	_entry_no_verify(&pvattest_config.create.no_verify, create_indent),
+	_entry_offline_maps_to_online(&pvattest_config.create.online, create_indent),
+	_entry_verbose(create_indent),
+	{ NULL }
+};
+
+static GOptionEntry experimental_create_options[] = {
+	_entry_x_no_nonce(&pvattest_config.create.use_nonce, create_indent),
+	_entry_x_paf(&create_set_paf, create_indent),
+	_entry_x_aad_size(&pvattest_config.create.x_aad_size, create_indent),
+	_entry_phkh_img(&pvattest_config.create.phkh_img, create_indent),
+	_entry_phkh_att(&pvattest_config.create.phkh_att, create_indent),
+	{ NULL }
+};
+
+static gboolean verify_create(GError **error)
+{
+	if (!check_file_list(pvattest_config.create.host_key_document_paths, TRUE,
+			     _("Specify --host-key-document at least once."), error))
+		return FALSE;
+	if (!pvattest_config.create.no_verify) {
+		if (!check_file_list(
+			    pvattest_config.create.certificate_paths, TRUE,
+			    _("Either specify the IBM Z signing key and"
+			      " intermediate CA certificate\nby using the '--cert' option, or"
+			      " use the '--no-verify' flag to disable the\nhost-key document"
+			      " verification completely (at your own risk).\n"
+			      "Only use this option in test environments or if"
+			      " you trust the unverified document."),
+			    error))
+			return FALSE;
+	}
+	if (!check_for_invalid_path(pvattest_config.create.arp_key_out_path, FALSE,
+				    _("Missing argument for --arpk."), error))
+		return FALSE;
+	if (!check_for_invalid_path(pvattest_config.create.output_path, FALSE,
+				    _("Missing argument for --output."), error))
+		return FALSE;
+	if (!check_optional_file_list(pvattest_config.create.crl_paths, TRUE, error))
+		return FALSE;
+	if (!check_for_optional_invalid_path(pvattest_config.create.root_ca_path, TRUE, error))
+		return FALSE;
+	return TRUE;
+};
+
+/************************* MEASUREMENT OPTIONS ********************************/
+#define perform_indent "                            "
+
+static GOptionEntry perform_options[] = {
+	_entry_input(&pvattest_config.perform.input_path, "attestation request", perform_indent),
+	_entry_output(&pvattest_config.perform.output_path, "attestation result", perform_indent),
+	_entry_verbose(perform_indent),
+	{ NULL },
+};
+
+static GOptionEntry experimental_perform_options[] = {
+	_entry_x_user_data(&pvattest_config.perform.user_data_path, perform_indent),
+	{ NULL },
+};
+
+static gboolean verify_perform(GError **error)
+{
+	if (!check_for_invalid_path(pvattest_config.perform.input_path, TRUE,
+				    _("Missing argument for --input."), error))
+		return FALSE;
+	if (!check_for_invalid_path(pvattest_config.perform.output_path, FALSE,
+				    _("Missing argument for --output."), error))
+		return FALSE;
+	if (!check_for_optional_invalid_path(pvattest_config.perform.user_data_path, TRUE, error))
+		return FALSE;
+	return TRUE;
+}
+
+/************************* VERIFY OPTIONS ************************************/
+#define verify_indent "                       "
+
+static GOptionEntry verify_options[] = {
+	_entry_input(&pvattest_config.verify.input_path, "attestation result", verify_indent),
+	_entry_guest_hdr(&pvattest_config.verify.hdr_path, verify_indent),
+	_entry_att_prot_key_load(&pvattest_config.verify.arp_key_in_path, verify_indent),
+	_entry_verbose(verify_indent),
+	{ NULL },
+};
+
+static gboolean verify_verify(GError **error)
+{
+	if (!check_for_invalid_path(pvattest_config.verify.input_path, TRUE,
+				    _("Missing argument for --input."), error))
+		return FALSE;
+	if (!check_for_invalid_path(pvattest_config.verify.hdr_path, TRUE,
+				    _("Missing argument for --hdr."), error))
+		return FALSE;
+	if (!check_for_invalid_path(pvattest_config.verify.arp_key_in_path, TRUE,
+				    _("Missing argument for --arpk."), error))
+		return FALSE;
+	return TRUE;
+}
+
+/************************** OPTIONS END ***************************************/
+
+#pragma GCC diagnostic pop
+
+static char summary[] =
+	"\n"
+	"Create, perform, and verify attestation measurements for IBM Secure Execution guest"
+	" systems.\n"
+	"\n"
+	"COMMANDS\n"
+	"  create    On a trusted system, creates an attestation request.\n"
+	"  perform   On the SE-guest to be attested, sends the attestation request\n"
+	"            to the Ultravisor and receives the answer.\n"
+#ifndef PVATTEST_COMPILE_PERFORM
+	"            (not supported on this platform)\n"
+#endif /* PVATTEST_COMPILE_PERFORM */
+
+	"  verify    On a trusted system, compares the one from your trusted system.\n"
+	"            If they differ, the Secure Execution guest might not be compromised\n"
+	"\n"
+	"Use '" GETTEXT_PACKAGE " [COMMAND] -h' to get detailed help\n";
+static char create_summary[] =
+	"Create attestation measurement requests to attest an\n"
+	"IBM Secure Execution guest. Only build attestation requests in a trusted\n"
+	"environment such as your Workstation.\n"
+	"To avoid compromising the attestation do not publish the\n"
+	"protection key and delete it after verification.\n"
+	"Every 'create' will generate a new, random protection key.\n";
+static char perform_summary[] =
+#ifndef PVATTEST_COMPILE_PERFORM
+	"This system does NOT support 'perform'.\n"
+#endif /* PVATTEST_COMPILE_PERFORM */
+	"Perform a measurement of this IBM Secure Execution guest using '/dev/uv'.\n";
+static char verify_summary[] =
+	"Verify that a previously generated attestation measurement of an\n"
+	"IBM Secure Execution guest yielded the expected results.\n"
+	"Verify attestation requests only in a trusted environment, such as your workstation.";
+
+static void print_version_and_exit(void)
+{
+	printf("%s version %s\n", GETTEXT_PACKAGE, RELEASE_STRING);
+	printf("%s\n", COPYRIGHT_NOTICE);
+	exit(EXIT_SUCCESS);
+}
+
+static GOptionContext *create_ctx(GOptionEntry *options, GOptionEntry *experimental_options,
+				  const char *param_name, const char *opt_summary)
+{
+	GOptionContext *ret = g_option_context_new(param_name);
+	GOptionGroup *x_group = NULL;
+	g_option_context_add_main_entries(ret, options, NULL);
+	g_option_context_set_summary(ret, opt_summary);
+	if (experimental_options) {
+		x_group = g_option_group_new(
+			"experimental",
+			"Experimental Options; Do not use in a production environment",
+			"Show experimental options", NULL, NULL);
+		g_option_group_add_entries(x_group, experimental_options);
+		g_option_context_add_group(ret, x_group);
+	}
+	return ret;
+}
+
+enum pvattest_command pvattest_parse(int *argc, char **argvp[], pvattest_config_t **config,
+				     GError **error)
+{
+	g_autoptr(GOptionContext) main_context = NULL, subc_context = NULL;
+	char **argv = *argvp;
+	enum pvattest_command subc = PVATTEST_SUBC_INVALID;
+	verify_options_fn_t verify_options_fn = NULL;
+
+	pv_wrapped_g_assert(argc);
+	pv_wrapped_g_assert(argvp);
+	pv_wrapped_g_assert(config);
+
+	/*
+	 * First parse until the first non dash argument. This must be one of the commands.
+	 * (strict POSIX parsing)
+	 */
+	main_context = g_option_context_new(
+		"COMMAND [OPTIONS] - create, perform, and verify attestation measurements");
+	g_option_context_set_strict_posix(main_context, TRUE);
+	g_option_context_add_main_entries(main_context, general_options, NULL);
+	g_option_context_set_summary(main_context, summary);
+
+	if (!g_option_context_parse(main_context, argc, argvp, error))
+		return PVATTEST_SUBC_INVALID;
+	if (print_version)
+		print_version_and_exit();
+
+	/*
+	 * Parse depending on the specified command
+	 */
+	else if (g_strcmp0(argv[1], PVATTEST_SUBC_STR_CREATE) == 0) {
+		subc_context =
+			create_ctx(create_options, experimental_create_options,
+				   "create [OPTIONS] - create an attestation measurement request",
+				   create_summary);
+		subc = PVATTEST_SUBC_CREATE;
+		verify_options_fn = &verify_create;
+	} else if (g_strcmp0(argv[1], PVATTEST_SUBC_STR_PERFORM) == 0) {
+		subc_context =
+			create_ctx(perform_options, experimental_perform_options,
+				   "perform [OPTIONS] - perform an attestation measurement request",
+				   perform_summary);
+		subc = PVATTEST_SUBC_PERFORM;
+		verify_options_fn = &verify_perform;
+#ifndef PVATTEST_COMPILE_PERFORM
+		g_set_error(error, PVATTEST_ERROR, PVATTEST_ERR_INV_ARG,
+			    _("This system does not support the 'perform' command."));
+		return PVATTEST_SUBC_INVALID;
+#endif /* PVATTEST_COMPILE_PERFORM */
+	} else if (g_strcmp0(argv[1], PVATTEST_SUBC_STR_VERIFY) == 0) {
+		subc_context = create_ctx(verify_options, NULL,
+					  "verify [OPTIONS] - verify an attestation measurement",
+					  verify_summary);
+		subc = PVATTEST_SUBC_VERIFY;
+		verify_options_fn = &verify_verify;
+	} else {
+		if (argv[1])
+			g_set_error(error, PVATTEST_ERROR, PVATTEST_ERR_INV_ARGV,
+				    _("Invalid command specified: %s."), argv[1]);
+		else
+			g_set_error(error, PVATTEST_ERROR, PVATTEST_ERR_INV_ARGV,
+				    _("No command specified."));
+		return PVATTEST_SUBC_INVALID;
+	}
+	g_assert(verify_options_fn);
+
+	if (!g_option_context_parse(subc_context, argc, argvp, error))
+		return PVATTEST_SUBC_INVALID;
+
+	if (!verify_options_fn(error))
+		return PVATTEST_SUBC_INVALID;
+
+	*config = &pvattest_config;
+	return subc;
+}
+
+static void pvattest_parse_clear_create_config(pvattest_create_config_t *config)
+{
+	if (!config)
+		return;
+	g_strfreev(config->host_key_document_paths);
+	g_strfreev(config->certificate_paths);
+	g_free(config->arp_key_out_path);
+	g_free(config->output_path);
+}
+
+static void pvattest_parse_clear_perform_config(pvattest_perform_config_t *config)
+{
+	if (!config)
+		return;
+	g_free(config->input_path);
+	g_free(config->output_path);
+}
+
+static void pvattest_parse_clear_verify_config(pvattest_verify_config_t *config)
+{
+	if (!config)
+		return;
+	g_free(config->input_path);
+	g_free(config->hdr_path);
+	g_free(config->arp_key_in_path);
+}
+
+void pvattest_parse_clear_config(pvattest_config_t *config)
+{
+	if (!config)
+		return;
+	pvattest_parse_clear_create_config(&config->create);
+	pvattest_parse_clear_perform_config(&config->perform);
+	pvattest_parse_clear_verify_config(&config->verify);
+}
--- /dev/null
+++ b/pvattest/src/argparse.h
@@ -0,0 +1,106 @@
+/*
+ * Definitions used for parsing arguments.
+ *
+ * Copyright IBM Corp. 2022
+ *
+ * s390-tools is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ */
+#ifndef PVATTEST_ARGPARSE_H
+#define PVATTEST_ARGPARSE_H
+/* Must be included before any other header */
+#include "config.h"
+
+#include <stdint.h>
+
+#include "libpv/glib-helper.h"
+#include "libpv/macros.h"
+
+#define PVATTEST_SUBC_STR_CREATE "create"
+#define PVATTEST_SUBC_STR_PERFORM "perform"
+#define PVATTEST_SUBC_STR_VERIFY "verify"
+
+enum pvattest_command {
+	PVATTEST_SUBC_INVALID,
+	PVATTEST_SUBC_CREATE,
+	PVATTEST_SUBC_PERFORM,
+	PVATTEST_SUBC_VERIFY,
+};
+
+typedef struct {
+	int log_level;
+} pvattest_general_config_t;
+
+typedef struct {
+	char **host_key_document_paths;
+	char **certificate_paths;
+	char **crl_paths;
+	char *root_ca_path;
+
+	char *arp_key_out_path;
+	char *output_path;
+
+	gboolean phkh_img;
+	gboolean phkh_att;
+	gboolean no_verify;
+	gboolean online;
+
+	/* experimental flags */
+	gboolean use_nonce; /* default TRUE */
+	uint64_t paf; /* default 0 */
+	int x_aad_size; /* default -1 -> ignore */
+} pvattest_create_config_t;
+
+typedef struct {
+	char *output_path;
+	char *input_path;
+	/* experimental flags */
+	char *user_data_path; /* default NULL */
+} pvattest_perform_config_t;
+
+typedef struct {
+	char *input_path;
+	char *hdr_path;
+	char *arp_key_in_path;
+} pvattest_verify_config_t;
+
+typedef struct {
+	pvattest_general_config_t general;
+	pvattest_create_config_t create;
+	pvattest_perform_config_t perform;
+	pvattest_verify_config_t verify;
+} pvattest_config_t;
+
+/**
+ * pvattest_parse_clear_config:
+ *
+ * @config: struct to be cleared
+ *
+ * clears but not frees all config.
+ * all non config members such like char* will be freed.
+ */
+void pvattest_parse_clear_config(pvattest_config_t *config);
+
+/**
+ * pvattest_parse:
+ *
+ * @argc: ptr to argument count
+ * @argv: ptr to argument vector
+ * @config: output: ptr to parsed config. Target is statically allocated.
+ *          You are responsible for freeing all non config ptrs.
+ *          use #pvattest_parse_clear_config for that.
+ *
+ * Will not return if verbose or help parsed.
+ *
+ * Returns: selected command as enum
+ */
+enum pvattest_command pvattest_parse(int *argc, char **argvp[], pvattest_config_t **config,
+				     GError **error) PV_NONNULL(1, 2, 3);
+
+#define PVATTEST_ERROR g_quark_from_static_string("pv-pvattest_error-quark")
+typedef enum {
+	PVATTEST_ERR_INV_ARGV,
+	PVATTEST_ERR_INV_ARG,
+} pv_pvattest_error_e;
+
+#endif /* PVATTEST_ARGPARSE_H */
--- /dev/null
+++ b/pvattest/src/attestation.c
@@ -0,0 +1,148 @@
+/*
+ * Attestation related functions
+ *
+ * Copyright IBM Corp. 2022
+ *
+ * s390-tools is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ */
+/* Must be included before any other header */
+#include "config.h"
+
+#include "libpv/cert.h"
+#include "libpv/hash.h"
+#include "libpv/se-hdr.h"
+
+#include "exchange_format.h"
+#include "attestation.h"
+
+G_STATIC_ASSERT(sizeof(((att_meas_ctx_t *)0)->pld) == sizeof(((struct pv_hdr_head *)0)->pld));
+G_STATIC_ASSERT(sizeof(((att_meas_ctx_t *)0)->ald) == sizeof(((struct pv_hdr_head *)0)->ald));
+G_STATIC_ASSERT(sizeof(((att_meas_ctx_t *)0)->tld) == sizeof(((struct pv_hdr_head *)0)->tld));
+G_STATIC_ASSERT(sizeof(((att_meas_ctx_t *)0)->tag) == sizeof(((struct pv_hdr *)0)->tag));
+
+struct att_meas_sizes {
+	uint16_t user_data_len;
+	uint16_t zeros;
+	uint32_t additional_data_len;
+} __packed;
+G_STATIC_ASSERT(sizeof(struct att_meas_sizes) == 8);
+
+/*
+ * All optional arguments may be NULL
+ * user_data is up to 256 bytes long, or NULL.
+ * nonce is 16 bytes long or NULL.
+ * additional_data is up to 32768 bytes long or NULL.
+ */
+GBytes *att_gen_measurement_hmac_sha512(const att_meas_ctx_t *meas_ctx, GBytes *measurement_key,
+					GBytes *optional_user_data, GBytes *optional_nonce,
+					GBytes *optional_additional_data, GError **error)
+{
+	struct att_meas_sizes meas_sizes = {};
+	g_autoptr(HMAC_CTX) hmac_ctx = NULL;
+	size_t additional_data_size = 0;
+	size_t user_data_size = 0;
+	size_t nonce_size = 0;
+
+	pv_wrapped_g_assert(meas_ctx);
+	pv_wrapped_g_assert(measurement_key);
+
+	if (optional_user_data)
+		user_data_size = g_bytes_get_size(optional_user_data);
+	if (optional_additional_data)
+		additional_data_size = g_bytes_get_size(optional_additional_data);
+	if (optional_nonce)
+		nonce_size = g_bytes_get_size(optional_nonce);
+
+	/* checks for these sizes resulting in GErrors are done before */
+	g_assert(user_data_size <= PVATTEST_USER_DATA_MAX_SIZE);
+	g_assert(additional_data_size <= PVATTEST_ADDITIONAL_MAX_SIZE);
+	g_assert(nonce_size == 0 || nonce_size == ARCB_V1_NONCE_SIZE);
+
+	pv_wrapped_g_assert(meas_ctx);
+	pv_wrapped_g_assert(measurement_key);
+
+	hmac_ctx = pv_hmac_ctx_new(measurement_key, EVP_sha512(), error);
+	if (!hmac_ctx)
+		return NULL;
+
+	meas_sizes.user_data_len = GUINT16_TO_BE((uint16_t)user_data_size);
+	meas_sizes.zeros = 0;
+	meas_sizes.additional_data_len = GUINT32_TO_BE((uint32_t)additional_data_size);
+
+	if (pv_hmac_ctx_update_raw(hmac_ctx, meas_ctx, sizeof(*meas_ctx), error) != 0)
+		return NULL;
+
+	/* add the sizes of user and additional data. */
+	if (pv_hmac_ctx_update_raw(hmac_ctx, &meas_sizes, sizeof(meas_sizes), error))
+		return NULL;
+
+	/* update optional data. if NULL passed (or size = 0) nothing will happen to the HMAC_CTX */
+	if (pv_hmac_ctx_update(hmac_ctx, optional_user_data, error) != 0)
+		return NULL;
+	if (pv_hmac_ctx_update(hmac_ctx, optional_nonce, error) != 0)
+		return NULL;
+	if (pv_hmac_ctx_update(hmac_ctx, optional_additional_data, error) != 0)
+		return NULL;
+	return pv_hamc_ctx_finalize(hmac_ctx, error);
+}
+
+att_meas_ctx_t *att_extract_from_hdr(GBytes *se_hdr, GError **error)
+{
+	g_autofree att_meas_ctx_t *meas = NULL;
+	const struct pv_hdr *hdr = NULL;
+	size_t se_hdr_tag_offset;
+	size_t se_hdr_size;
+	uint8_t *hdr_u8;
+
+	pv_wrapped_g_assert(se_hdr);
+
+	hdr = g_bytes_get_data(se_hdr, &se_hdr_size);
+	hdr_u8 = (uint8_t *)hdr;
+
+	if (se_hdr_size < PV_V1_PV_HDR_MIN_SIZE) {
+		g_set_error(error, ATT_ERROR, ATT_ERR_INVALID_HDR,
+			    _("Invalid SE header provided."));
+		return NULL;
+	}
+
+	if (GUINT32_FROM_BE(hdr->head.phs) != se_hdr_size ||
+	    GUINT64_FROM_BE(hdr->head.magic) != PV_MAGIC_NUMBER) {
+		g_set_error(error, ATT_ERROR, ATT_ERR_INVALID_HDR,
+			    _("Invalid SE header provided."));
+		return NULL;
+	}
+
+	se_hdr_tag_offset = GUINT32_FROM_BE(hdr->head.phs) - sizeof(hdr->tag);
+	meas = g_new0(att_meas_ctx_t, 1);
+
+	memcpy(meas->pld, hdr->head.pld, sizeof(meas->pld));
+	memcpy(meas->ald, hdr->head.ald, sizeof(meas->ald));
+	memcpy(meas->tld, hdr->head.tld, sizeof(meas->tld));
+	memcpy(meas->tag, hdr_u8 + se_hdr_tag_offset, sizeof(meas->tag));
+
+	return g_steal_pointer(&meas);
+}
+
+void att_add_uid(att_meas_ctx_t *meas_ctx, GBytes *config_uid)
+{
+	pv_wrapped_g_assert(meas_ctx);
+	pv_wrapped_g_assert(config_uid);
+
+	g_assert(g_bytes_get_size(config_uid) == ATT_CONFIG_UID_SIZE);
+	pv_gbytes_memcpy(meas_ctx->config_uid, ATT_CONFIG_UID_SIZE, config_uid);
+}
+
+gboolean att_verify_measurement(const GBytes *calculated_measurement,
+				const GBytes *uvio_measurement, GError **error)
+{
+	pv_wrapped_g_assert(calculated_measurement);
+	pv_wrapped_g_assert(uvio_measurement);
+
+	if (g_bytes_compare(calculated_measurement, uvio_measurement) != 0) {
+		g_set_error(error, ATT_ERROR, ATT_ERR_MEASUREMENT_VERIFICATION_FAILED,
+			    _("Calculated and received attestation measurement are not equal."));
+		return FALSE;
+	}
+	return TRUE;
+}
--- /dev/null
+++ b/pvattest/src/attestation.h
@@ -0,0 +1,99 @@
+/*
+ * Attestation related functions and definitions.
+ *
+ * Copyright IBM Corp. 2022
+ *
+ * s390-tools is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ */
+#ifndef PVATTEST_ATTESTATION_H
+#define PVATTEST_ATTESTATION_H
+/* Must be included before any other header */
+#include "config.h"
+
+#include <openssl/sha.h>
+
+#include "libpv/glib-helper.h"
+#include "libpv/crypto.h"
+#include "libpv/cert.h"
+
+#include "common.h"
+#include "types.h"
+#include "arcb.h"
+
+#define ATT_CONFIG_UID_SIZE 16
+
+typedef struct {
+	uint8_t pld[SHA512_DIGEST_LENGTH];
+	uint8_t ald[SHA512_DIGEST_LENGTH];
+	uint8_t tld[SHA512_DIGEST_LENGTH];
+	uint8_t tag[AES_256_GCM_TAG_SIZE];
+	uint8_t config_uid[ATT_CONFIG_UID_SIZE];
+} __packed att_meas_ctx_t;
+G_STATIC_ASSERT(sizeof(att_meas_ctx_t) == 224);
+
+/**
+ * att_gen_measurement_hmac_sha512:
+ *
+ * @meas_ctx: measurement context.
+ * @measurement_key: AES-256-GCM key for generating the measurement calculation.
+ * @optional_user_data: NULL or up to 256 bytes GBytes.
+ * @optional_nonce: NULL or a nonce of exactly `ARCB_V1_NONCE_SIZE` bytes
+ * @optional_additional_data: NULL or up to 0x8000 bytes of GBytes.
+ * @error: GError. *error will != NULL if error occurs.
+ *
+ * Calculates the measurement value.
+ * If the input data is the same which the UV used in the Retrieve Attestation Measurement
+ * the result should be identical to the data in the ´Measurement Data Address´ UVC.
+ *
+ * Returns: (nullable) (transfer full): a hmac_sha512 of the given data.
+ */
+GBytes *att_gen_measurement_hmac_sha512(const att_meas_ctx_t *meas_ctx, GBytes *measurement_key,
+					GBytes *optional_user_data, GBytes *optional_nonce,
+					GBytes *optional_additional_data, GError **error)
+	PV_NONNULL(1, 2);
+
+/**
+ * att_extract_from_hdr:
+ *
+ * @se_hdr: binary SE guest header.
+ * @error: GError. *error will != NULL if error occurs.
+ *
+ * Verifies that SE header size and magic ,but no cryptographical verification.
+ * Then, find and extracts pld, ald, tld, and SE tagi and adds it to the context.
+ *
+ * Returns: new attestation measurement context.
+ */
+att_meas_ctx_t *att_extract_from_hdr(GBytes *se_hdr, GError **error) PV_NONNULL(1);
+
+/** att_add_uid:
+ *
+ * @meas_ctx: measurement context.
+ * @config_uid: pointer to config UID. Must be `ATT_CONFIG_UID_SIZE` bytes long.
+ *
+ * Copies the config UID to the measurement context.
+ * Wrong size is considered as a Programming error.
+ */
+void att_add_uid(att_meas_ctx_t *meas_ctx, GBytes *config_uid) PV_NONNULL(1, 2);
+
+/** att_verify_measurement:
+ *
+ * @calculated_measurement: measurement calculated by a trusted system
+ * @uvio_measurement: measurement generated by an UV
+ * @error: GError. *error will != NULL if error occurs.
+ *
+ * Returns: TRUE if measurements are identical, otherwise FALSE
+ */
+gboolean att_verify_measurement(const GBytes *calculated_measurement,
+				const GBytes *uvio_measurement, GError **error) PV_NONNULL(1, 2);
+
+#define ATT_ERROR g_quark_from_static_string("pv-att_error-quark")
+typedef enum att_error {
+	ATT_ERR_INVALID_HDR,
+	ATT_ERR_INVALID_USER_DATA,
+	ATT_ERR_MEASUREMENT_VERIFICATION_FAILED,
+	ATT_ERR_PHKH_NO_FIT_IN_USER_DATA,
+	ATT_ERR_PHKH_NO_MATCH,
+} att_error_e;
+
+#endif /* PVATTEST_ATTESTATION_H */
--- /dev/null
+++ b/pvattest/src/common.c
@@ -0,0 +1,47 @@
+/*
+ * Common functions for pvattest.
+ *
+ * Copyright IBM Corp. 2022
+ *
+ * s390-tools is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ */
+/* Must be included before any other header */
+#include "config.h"
+
+#include <stdio.h>
+#include <sys/stat.h>
+
+#include "libpv/glib-helper.h"
+
+#include "types.h"
+#include "common.h"
+
+gboolean wrapped_g_file_set_content(const char *filename, GBytes *bytes, mode_t mode,
+				    GError **error)
+{
+	const void *data;
+	size_t size;
+	gboolean rc;
+
+	data = g_bytes_get_data(bytes, &size);
+	rc = g_file_set_contents(filename, data, (ssize_t)size, error);
+	if (rc && mode != 0666)
+		chmod(filename, mode);
+	return rc;
+}
+
+GBytes *secure_gbytes_concat(GBytes *lh, GBytes *rh)
+{
+	g_autoptr(GByteArray) lha = NULL;
+
+	if (!lh && !rh)
+		return NULL;
+	if (!lh)
+		return g_bytes_ref(rh);
+	if (!rh)
+		return g_bytes_ref(lh);
+	lha = g_bytes_unref_to_array(g_bytes_ref(lh));
+	g_byte_array_append(lha, g_bytes_get_data(rh, NULL), (guint)g_bytes_get_size(rh));
+	return pv_sec_gbytes_new(lha->data, lha->len);
+}
--- /dev/null
+++ b/pvattest/src/common.h
@@ -0,0 +1,37 @@
+/*
+ * Common functions for pvattest.
+ *
+ *  IBM Corp. 2022
+ *
+ * s390-tools is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ */
+#ifndef PVATTEST_COMMON_H
+#define PVATTEST_COMMON_H
+/* Must be included before any other header */
+#include "config.h"
+
+#include <glib/gi18n-lib.h>
+#include <stdio.h>
+
+#include "libpv/glib-helper.h"
+#include "libpv/macros.h"
+#include "lib/zt_common.h"
+
+#include "types.h"
+
+#define COPYRIGHT_NOTICE "Copyright IBM Corp. 2022"
+
+#define AES_256_GCM_TAG_SIZE 16
+
+gboolean wrapped_g_file_set_content(const char *filename, GBytes *bytes, mode_t mode,
+				    GError **error);
+
+/**
+ * just ref's up if one of them is NULL.
+ * If both NULL returns NULL.
+ * Otherwise returns lh ++ rh
+ */
+GBytes *secure_gbytes_concat(GBytes *lh, GBytes *rh);
+
+#endif /* PVATTEST_COMMON_H */
--- /dev/null
+++ b/pvattest/src/config.h
@@ -0,0 +1,31 @@
+/*
+ * Config file.
+ * Must be include before any other header.
+ *
+ * Copyright IBM Corp. 2022
+ *
+ * s390-tools is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ *
+ */
+#ifndef PVATTEST_CONFIG_H
+#define PVATTEST_CONFIG_H
+#define GETTEXT_PACKAGE "pvattest"
+
+#ifdef __GNUC__
+#ifdef __s390x__
+#ifndef PVATTEST_NO_PERFORM
+#define PVATTEST_COMPILE_PERFORM
+#endif
+#endif
+#endif
+
+#ifdef __clang__
+#ifdef __zarch__
+#ifndef PVATTEST_NO_PERFORM
+#define PVATTEST_COMPILE_PERFORM
+#endif
+#endif
+#endif
+
+#endif /* PVATTEST_CONFIG_H */
--- /dev/null
+++ b/pvattest/src/exchange_format.c
@@ -0,0 +1,480 @@
+/*
+ * Functions for the pvattest exchange format to send attestation requests and responses between
+ * machines .
+ *
+ * Copyright IBM Corp. 2022
+ *
+ * s390-tools is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ */
+/* Must be included before any other header */
+#include "config.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "common.h"
+
+#include "exchange_format.h"
+#include "log.h"
+
+struct exchange_shared_hdr {
+	be64_t magic;
+	be32_t version;
+	be32_t size;
+} __packed;
+
+/*
+ * If size == 0
+ * 	offset ignored.
+ * 	(part does not exist)
+ * if offset >0 and <0x50 -> invalid format
+ * if offset == 0 and size > 0 no data saved, however the request will need this amount of memory to
+ * 	succeed.
+ * 	Only makes sense for measurement and additional data. This however, is not enforced.
+ */
+struct entry {
+	be32_t size;
+	be32_t offset;
+} __packed;
+G_STATIC_ASSERT(sizeof(struct entry) == 8);
+
+struct _exchange_format_v1_hdr {
+	be64_t magic;
+	be32_t version;
+	be32_t size;
+	uint64_t reserved;
+	struct entry serialized_arcb;
+	struct entry measurement;
+	struct entry additional_data;
+	struct entry user_data;
+	struct entry config_uid;
+} __packed;
+G_STATIC_ASSERT(sizeof(exchange_format_v1_hdr_t) == 0x40);
+
+struct _exchange_format_ctx {
+	uint32_t version;
+	uint32_t req_meas_size;
+	uint32_t req_add_size;
+	GBytes *serialized_arcb;
+	GBytes *measurement;
+	GBytes *additional_data;
+	GBytes *user_data;
+	GBytes *config_uid;
+};
+
+/* Use a byte array to avoid any byteorder issues while checking */
+
+#define PVATTEST_EXCHANGE_MAGIC 0x7076617474657374 /* pvattest */
+static const uint8_t exchange_magic[] = { 0x70, 0x76, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74 };
+
+exchange_format_ctx_t *exchange_ctx_new(uint32_t version, GBytes *serialized_arcb,
+					uint32_t req_measurement_size, uint32_t req_additional_size,
+					GError **error)
+{
+	g_autoptr(exchange_format_ctx_t) ctx = NULL;
+
+	pv_wrapped_g_assert(serialized_arcb);
+
+	if (version != PVATTEST_EXCHANGE_VERSION_1_00) {
+		g_set_error(error, EXCHANGE_FORMAT_ERROR, EXCHANGE_FORMAT_ERROR_UNSUPPORTED_VERSION,
+			    _("'%d' unsupported version."), version);
+		return NULL;
+	}
+
+	ctx = g_malloc0(sizeof(*ctx));
+	ctx->version = version;
+
+	exchange_set_serialized_arcb(ctx, serialized_arcb);
+	ctx->req_meas_size = req_measurement_size;
+	ctx->req_add_size = req_additional_size;
+
+	return g_steal_pointer(&ctx);
+}
+
+static GBytes *get_content(GBytes *file_content, const struct entry *entry, const size_t max_size,
+			   GError **error)
+{
+	uint64_t size = GUINT32_FROM_BE(entry->size);
+	uint64_t offset = GUINT32_FROM_BE(entry->offset);
+	size_t file_size = 0;
+	const uint8_t *file_content_u8 = g_bytes_get_data(file_content, &file_size);
+
+	if (size == 0 || offset == 0)
+		return NULL;
+
+	if (offset < sizeof(exchange_format_v1_hdr_t) || offset + size > file_size ||
+	    size > max_size) {
+		g_set_error(error, EXCHANGE_FORMAT_ERROR, EXCHANGE_FORMAT_ERROR_INVALID_FORMAT,
+			    _("Input file is not in a valid format."));
+		return NULL;
+	}
+	return g_bytes_new(file_content_u8 + offset, size);
+}
+
+static gboolean check_format(const struct exchange_shared_hdr *hdr)
+{
+	if (memcmp(exchange_magic, &hdr->magic, sizeof(exchange_magic)) == 0)
+		return TRUE;
+	return FALSE;
+}
+
+exchange_format_ctx_t *exchange_ctx_from_file(const char *filename, GError **error)
+{
+	g_autoptr(exchange_format_ctx_t) ctx = g_malloc0(sizeof(*ctx));
+	const struct exchange_shared_hdr *hdr = NULL;
+	const exchange_format_v1_hdr_t *hdr_v1 = NULL;
+	g_autoptr(GBytes) file_content = NULL;
+	size_t config_uid_size = 0;
+	size_t file_size;
+
+	pv_wrapped_g_assert(filename);
+
+	file_content = pv_file_get_content_as_g_bytes(filename, error);
+	if (!file_content)
+		return NULL;
+	hdr = (const struct exchange_shared_hdr *)g_bytes_get_data(file_content, &file_size);
+
+	if (file_size < sizeof(*hdr) || !check_format(hdr)) {
+		g_set_error(error, EXCHANGE_FORMAT_ERROR, EXCHANGE_FORMAT_ERROR_INVALID_FORMAT,
+			    _("'%s' is not in a valid format."), filename);
+		return NULL;
+	}
+
+	if (GUINT32_FROM_BE(hdr->version) != PVATTEST_EXCHANGE_VERSION_1_00) {
+		g_set_error(error, EXCHANGE_FORMAT_ERROR, EXCHANGE_FORMAT_ERROR_INVALID_FORMAT,
+			    _("The version (%#x) of '%s' is not supported"),
+			    GUINT32_FROM_BE(hdr->version), filename);
+		return NULL;
+	}
+
+	/* get the header */
+	if (file_size < sizeof(exchange_format_v1_hdr_t)) {
+		g_set_error(error, EXCHANGE_FORMAT_ERROR, EXCHANGE_FORMAT_ERROR_INVALID_FORMAT,
+			    _("'%s' is not in a valid format."), filename);
+		return NULL;
+	}
+	hdr_v1 = (const exchange_format_v1_hdr_t *)hdr;
+
+	/* get entries if present */
+	ctx->serialized_arcb =
+		get_content(file_content, &hdr_v1->serialized_arcb, PVATTEST_ARCB_MAX_SIZE, error);
+	if (*error)
+		return NULL;
+	ctx->measurement = get_content(file_content, &hdr_v1->measurement,
+				       PVATTEST_MEASUREMENT_MAX_SIZE, error);
+	if (*error)
+		return NULL;
+	ctx->additional_data = get_content(file_content, &hdr_v1->additional_data,
+					   PVATTEST_ADDITIONAL_MAX_SIZE, error);
+	if (*error)
+		return NULL;
+	ctx->user_data =
+		get_content(file_content, &hdr_v1->user_data, PVATTEST_USER_DATA_MAX_SIZE, error);
+	if (*error)
+		return NULL;
+	ctx->config_uid = get_content(file_content, &hdr_v1->config_uid, PVATTEST_UID_SIZE, error);
+	if (*error)
+		return NULL;
+
+	if (ctx->config_uid)
+		config_uid_size = g_bytes_get_size(ctx->config_uid);
+
+	if (config_uid_size != PVATTEST_UID_SIZE && config_uid_size != 0) {
+		g_set_error(error, EXCHANGE_FORMAT_ERROR, EXCHANGE_FORMAT_ERROR_INVALID_FORMAT,
+			    _("'%s' is not in a valid format."), filename);
+		return NULL;
+	}
+	ctx->req_meas_size = GUINT32_FROM_BE(hdr_v1->measurement.size);
+	ctx->req_add_size = GUINT32_FROM_BE(hdr_v1->additional_data.size);
+	ctx->version = GUINT32_TO_BE(hdr->version);
+
+	return g_steal_pointer(&ctx);
+}
+
+void clear_free_exchange_ctx(exchange_format_ctx_t *ctx)
+{
+	if (!ctx)
+		return;
+
+	if (ctx->serialized_arcb)
+		g_bytes_unref(ctx->serialized_arcb);
+	if (ctx->measurement)
+		g_bytes_unref(ctx->measurement);
+	if (ctx->additional_data)
+		g_bytes_unref(ctx->additional_data);
+	if (ctx->user_data)
+		g_bytes_unref(ctx->user_data);
+	if (ctx->config_uid)
+		g_bytes_unref(ctx->config_uid);
+
+	g_free(ctx);
+}
+
+void exchange_set_serialized_arcb(exchange_format_ctx_t *ctx, GBytes *serialized_arcb)
+{
+	pv_wrapped_g_assert(ctx);
+	pv_wrapped_g_assert(serialized_arcb);
+
+	g_bytes_ref(serialized_arcb);
+	g_bytes_unref(ctx->serialized_arcb);
+	ctx->serialized_arcb = serialized_arcb;
+}
+
+void exchange_set_measurement(exchange_format_ctx_t *ctx, GBytes *measurement)
+{
+	pv_wrapped_g_assert(ctx);
+	pv_wrapped_g_assert(measurement);
+
+	g_bytes_ref(measurement);
+	g_bytes_unref(ctx->measurement);
+	ctx->measurement = measurement;
+}
+
+void exchange_set_additional_data(exchange_format_ctx_t *ctx, GBytes *additional_data)
+{
+	pv_wrapped_g_assert(ctx);
+	pv_wrapped_g_assert(additional_data);
+
+	g_bytes_ref(additional_data);
+	g_bytes_unref(ctx->additional_data);
+	ctx->additional_data = additional_data;
+}
+
+void exchange_set_user_data(exchange_format_ctx_t *ctx, GBytes *user_data)
+{
+	pv_wrapped_g_assert(ctx);
+	pv_wrapped_g_assert(user_data);
+
+	g_bytes_ref(user_data);
+	g_bytes_unref(ctx->user_data);
+	ctx->user_data = user_data;
+}
+
+void exchange_set_config_uid(exchange_format_ctx_t *ctx, GBytes *config_uid)
+{
+	pv_wrapped_g_assert(ctx);
+	pv_wrapped_g_assert(config_uid);
+
+	g_bytes_ref(config_uid);
+	g_bytes_unref(ctx->config_uid);
+	ctx->config_uid = config_uid;
+}
+
+static GBytes *gbytes_ref0(GBytes *bytes)
+{
+	if (!bytes)
+		return NULL;
+	return g_bytes_ref(bytes);
+}
+
+GBytes *exchange_get_serialized_arcb(const exchange_format_ctx_t *ctx)
+{
+	pv_wrapped_g_assert(ctx);
+
+	return gbytes_ref0(ctx->serialized_arcb);
+}
+
+GBytes *exchange_get_measurement(const exchange_format_ctx_t *ctx)
+{
+	pv_wrapped_g_assert(ctx);
+
+	return gbytes_ref0(ctx->measurement);
+}
+
+GBytes *exchange_get_additional_data(const exchange_format_ctx_t *ctx)
+{
+	pv_wrapped_g_assert(ctx);
+
+	return gbytes_ref0(ctx->additional_data);
+}
+
+GBytes *exchange_get_user_data(const exchange_format_ctx_t *ctx)
+{
+	pv_wrapped_g_assert(ctx);
+
+	return gbytes_ref0(ctx->user_data);
+}
+
+GBytes *exchange_get_config_uid(const exchange_format_ctx_t *ctx)
+{
+	pv_wrapped_g_assert(ctx);
+
+	return gbytes_ref0(ctx->config_uid);
+}
+
+uint32_t exchange_get_requested_measurement_size(const exchange_format_ctx_t *ctx)
+{
+	pv_wrapped_g_assert(ctx);
+
+	return ctx->req_meas_size;
+}
+
+uint32_t exchange_get_requested_additional_data_size(const exchange_format_ctx_t *ctx)
+{
+	pv_wrapped_g_assert(ctx);
+
+	return ctx->req_add_size;
+}
+
+static struct entry add_g_bytes(GBytes *bytes, FILE *file, GError **error)
+{
+	struct entry result = {};
+	long offset;
+	size_t size;
+	const void *data = g_bytes_get_data(bytes, &size);
+
+	g_assert(size <= G_MAXUINT32);
+
+	offset = pv_file_tell(file, error);
+	g_assert(offset <= G_MAXUINT32);
+	if (offset < 0)
+		return result;
+
+	result.offset = GUINT32_TO_BE((uint32_t)offset);
+	result.size = GUINT32_TO_BE((uint32_t)size);
+	pv_file_write(file, data, size, error);
+	return result;
+}
+
+int exchange_write_to_file(const exchange_format_ctx_t *ctx, const char *filename, GError **error)
+{
+	exchange_format_v1_hdr_t hdr = {
+		.magic = GUINT64_TO_BE(PVATTEST_EXCHANGE_MAGIC),
+		.version = GUINT32_TO_BE(ctx->version),
+	};
+	size_t file_size = sizeof(hdr);
+	g_autoptr(FILE) file = NULL;
+	struct stat file_stat;
+	long actual_file_size;
+	size_t tmp_size;
+
+	pv_wrapped_g_assert(ctx);
+	pv_wrapped_g_assert(filename);
+
+	file = pv_file_open(filename, "w", error);
+	if (!file)
+		return -1;
+
+	if (fstat(fileno(file), &file_stat) != 0 || !S_ISREG(file_stat.st_mode)) {
+		g_set_error(error, EXCHANGE_FORMAT_ERROR,
+			    EXCHANGE_FORMAT_ERROR_UNSUPPORTED_FILE_TYPE,
+			    "Only regular files are supported: '%s'", filename);
+		return -1;
+	}
+
+	if (pv_file_seek(file, sizeof(exchange_format_v1_hdr_t), SEEK_SET, error))
+		return -1;
+
+	if (ctx->serialized_arcb) {
+		hdr.serialized_arcb = add_g_bytes(ctx->serialized_arcb, file, error);
+		if (*error)
+			return -1;
+		file_size += g_bytes_get_size(ctx->serialized_arcb);
+	}
+	if (ctx->measurement) {
+		hdr.measurement = add_g_bytes(ctx->measurement, file, error);
+		if (*error)
+			return -1;
+		file_size += g_bytes_get_size(ctx->measurement);
+	} else {
+		hdr.measurement.size = GUINT32_TO_BE(ctx->req_meas_size);
+	}
+
+	if (ctx->additional_data) {
+		hdr.additional_data = add_g_bytes(ctx->additional_data, file, error);
+		if (*error)
+			return -1;
+		file_size += g_bytes_get_size(ctx->additional_data);
+	} else {
+		hdr.additional_data.size = GUINT32_TO_BE(ctx->req_add_size);
+	}
+
+	if (ctx->user_data) {
+		tmp_size = g_bytes_get_size(ctx->user_data);
+		g_assert(tmp_size <= PVATTEST_USER_DATA_MAX_SIZE);
+		tmp_size = MIN(tmp_size, PVATTEST_USER_DATA_MAX_SIZE); /* should be a noop */
+		hdr.user_data = add_g_bytes(ctx->user_data, file, error);
+		if (*error)
+			return -1;
+		file_size += g_bytes_get_size(ctx->user_data);
+	}
+	if (ctx->config_uid) {
+		tmp_size = g_bytes_get_size(ctx->config_uid);
+		g_assert(tmp_size == PVATTEST_UID_SIZE);
+		tmp_size = MIN(tmp_size, PVATTEST_UID_SIZE); /* should be a noop */
+		hdr.config_uid = add_g_bytes(ctx->config_uid, file, error);
+		if (*error)
+			return -1;
+		file_size += g_bytes_get_size(ctx->config_uid);
+	}
+
+	/*
+	 * This case should never happen. It could be seen as a programming error as:
+	 * ARCB is restricted by kernel (and this tool) to be max 1M, Additional+meas to max 8pages
+	 * userdata to 256B and config uid to 16b this is way less than 4G.
+	 *
+	 * However, lets be conservative and trow an error instead of an assertion.
+	 */
+	if (file_size > UINT32_MAX) {
+		g_set_error(
+			error, EXCHANGE_FORMAT_ERROR, EXCHANGE_FORMAT_ERROR_INVALID_FORMAT,
+			"The exchange file format cannot handle this much data in one blob. (%#lx bytes)",
+			file_size);
+		return -1;
+	}
+	hdr.size = GUINT32_TO_BE((uint32_t)file_size);
+	if (pv_file_seek(file, 0, SEEK_SET, error) != 0)
+		return -1;
+	if (sizeof(hdr) != pv_file_write(file, &hdr, sizeof(hdr), error))
+		return -1;
+	if (pv_file_seek(file, 0, SEEK_END, error) != 0)
+		return -1;
+	actual_file_size = pv_file_tell(file, error);
+	if (actual_file_size < 0)
+		return -1;
+	if (actual_file_size != (uint32_t)file_size) {
+		g_set_error(
+			error, EXCHANGE_FORMAT_ERROR, EXCHANGE_FORMAT_ERROR_WRITE,
+			"The exchange file size doesn't match the expectations: %ld bytes != %lu bytes",
+			actual_file_size, file_size);
+		return -1;
+	}
+
+	return 0;
+}
+
+static void print_entry(const char *name, GBytes *data, const gboolean print_data, FILE *stream)
+{
+	if (!data)
+		return;
+	fprintf(stream, _("%s (%#lx bytes)"), name, g_bytes_get_size(data));
+	if (print_data) {
+		fprintf(stream, ":\n");
+		printf_hexdump(g_bytes_get_data(data, NULL), g_bytes_get_size(data), 16, "      ",
+			       stream);
+	}
+	fprintf(stream, "\n");
+}
+
+void exchange_info_print(const exchange_format_ctx_t *ctx, const gboolean print_data, FILE *stream)
+{
+	pv_wrapped_g_assert(ctx);
+	pv_wrapped_g_assert(stream);
+
+	fprintf(stream, _("Version: %#x\n"), ctx->version);
+	fprintf(stream, _("Sections:\n"));
+	print_entry(_("  ARCB"), ctx->serialized_arcb, print_data, stream);
+	print_entry(_("  Measurement"), ctx->measurement, print_data, stream);
+	print_entry(_("  Additional Data"), ctx->additional_data, print_data, stream);
+	print_entry(_("  User Data"), ctx->user_data, print_data, stream);
+	print_entry(_("  Config UID"), ctx->config_uid, print_data, stream);
+	if (!ctx->measurement)
+		fprintf(stream, _("Required measurement size: %#x\n"), ctx->req_meas_size);
+	if (!ctx->additional_data)
+		fprintf(stream, _("Required additional data size: %#x\n"), ctx->req_add_size);
+}
--- /dev/null
+++ b/pvattest/src/exchange_format.h
@@ -0,0 +1,166 @@
+/*
+ * Definitions for the pvattest exchange format to send attestation requests and responses between
+ * machines. The "exchange format" is a simple file format to send labeled binary blobs between
+ * pvattest instances on different machines. All sizes, etc are in big endian.
+ *
+ * Copyright IBM Corp. 2022
+ *
+ * s390-tools is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ */
+#ifndef PVATTEST_EXCHANGE_FORMAT_H
+#define PVATTEST_EXCHANGE_FORMAT_H
+/* Must be included before any other header */
+#include "config.h"
+
+#include "libpv/glib-helper.h"
+
+#include "types.h"
+#include "common.h"
+
+/* Similar to linux/arch/s390x/include/uapi/uvdevice.h as this part needs to be
+ * architecture independent.
+ */
+#define PVATTEST_UID_SIZE 0x10UL
+#define PVATTEST_USER_DATA_MAX_SIZE 0x100UL
+#define PVATTEST_ARCB_MAX_SIZE 0x100000
+#define PVATTEST_MEASUREMENT_MAX_SIZE 0x8000
+#define PVATTEST_ADDITIONAL_MAX_SIZE 0x8000
+
+#define PVATTEST_EXCHANGE_V_INVALID 0
+#define PVATTEST_EXCHANGE_VERSION_1_00 0x0100
+
+typedef struct _exchange_format_v1_hdr exchange_format_v1_hdr_t;
+typedef struct _exchange_format_ctx exchange_format_ctx_t;
+
+/**
+ * exchange_ctx_new:
+ *
+ * @version: Format version. Currently, only version 1 supported.
+ * @serialized_arcb: ARCB as #GBytes
+ * @req_measurement_size: Measurement size the given ARCB needs.
+ * @req_measurement_size: Additional Data size the given ARCB needs.
+ * @error: GError. *error will != NULL if error occurs.
+ *
+ * Returns: (nullable) (transfer full): new, empty exchange format context
+ *
+ */
+exchange_format_ctx_t *exchange_ctx_new(const uint32_t version, GBytes *serialized_arcb,
+					const uint32_t req_measurement_size,
+					const uint32_t req_additional_size, GError **error)
+	PV_NONNULL(2);
+
+/**
+ * exchange_ctx_from_file:
+ *
+ * @filename: name of the file to be loaded
+ * @error: GError. *error will != NULL if error occurs.
+ *
+ * Loads all blobs from file and caches them in the context structure.
+ *
+ * Returns: (nullable) (transfer full): exchange format context filled with data from file
+ *
+ */
+exchange_format_ctx_t *exchange_ctx_from_file(const char *filename, GError **error) PV_NONNULL(1);
+void clear_free_exchange_ctx(exchange_format_ctx_t *ctx);
+WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(exchange_format_ctx_t, clear_free_exchange_ctx)
+
+/**
+ * exchange_set_serialized_arcb:
+ *
+ * @ctx: exchange format context
+ * @serialized_arcb: blob to add.
+ *
+ * Adds blob to the exchange format. Unreferences old data if already set.
+ */
+void exchange_set_serialized_arcb(exchange_format_ctx_t *ctx, GBytes *serialized_arcb)
+	PV_NONNULL(1, 2);
+
+/**
+ * exchange_set_measurement:
+ *
+ * @ctx: exchange format context
+ * @measurement: blob to add.
+ *
+ * Adds blob to the exchange format. Unreferences old data if already set.
+ */
+void exchange_set_measurement(exchange_format_ctx_t *ctx, GBytes *measurement) PV_NONNULL(1, 2);
+
+/**
+ * exchange_set_additional_data:
+ *
+ * @ctx: exchange format context
+ * @additional_data: blob to add.
+ *
+ * Adds blob to the exchange format. Unreferences old data if already set.
+ */
+void exchange_set_additional_data(exchange_format_ctx_t *ctx, GBytes *additional_data)
+	PV_NONNULL(1, 2);
+
+/**
+ * exchange_set_user_data:
+ *
+ * @ctx: exchange format context
+ * @user_data: blob to add.
+ *
+ * Adds blob to the exchange format. Unreferences old data if already set.
+ */
+void exchange_set_user_data(exchange_format_ctx_t *ctx, GBytes *user_data) PV_NONNULL(1, 2);
+
+/**
+ * exchange_set_config_uid:
+ *
+ * @ctx: exchange format context
+ * @config_uid: blob to add.
+ *
+ * Adds blob to the exchange format. Unreferences old data if already set.
+ */
+void exchange_set_config_uid(exchange_format_ctx_t *ctx, GBytes *config_uid) PV_NONNULL(1, 2);
+
+GBytes *exchange_get_serialized_arcb(const exchange_format_ctx_t *ctx) PV_NONNULL(1);
+GBytes *exchange_get_measurement(const exchange_format_ctx_t *ctx) PV_NONNULL(1);
+GBytes *exchange_get_additional_data(const exchange_format_ctx_t *ctx) PV_NONNULL(1);
+GBytes *exchange_get_user_data(const exchange_format_ctx_t *ctx) PV_NONNULL(1);
+GBytes *exchange_get_config_uid(const exchange_format_ctx_t *ctx) PV_NONNULL(1);
+
+uint32_t exchange_get_requested_measurement_size(const exchange_format_ctx_t *ctx) PV_NONNULL(1);
+uint32_t exchange_get_requested_additional_data_size(const exchange_format_ctx_t *ctx)
+	PV_NONNULL(1);
+
+/**
+ * exchange_write_to_file:
+ *
+ * @ctx: exchange format context
+ * @filename: name of the file to be loaded
+ * @error: GError. *error will != NULL if error occours.
+ *
+ * Takes all Data in the context and writes them into a file.
+ * Places the exchange format header before the data.
+ *
+ * Returns: 0 in case of success, -1 otherwise.
+ */
+int exchange_write_to_file(const exchange_format_ctx_t *ctx, const char *filename, GError **error)
+	PV_NONNULL(1, 2);
+
+/**
+ * exchange_info_print:
+ *
+ * @ctx: exchange format context
+ * @print_data: TRUE: print present data + label names
+ *              FALSE: just print label names of present data
+ * @stream: FILE* stream to print data
+ *
+ * Prints the content of @ctx to @stream.
+ */
+void exchange_info_print(const exchange_format_ctx_t *ctx, const gboolean print_data, FILE *stream)
+	PV_NONNULL(1, 3);
+
+#define EXCHANGE_FORMAT_ERROR g_quark_from_static_string("pv-exchange-format_error-quark")
+typedef enum {
+	EXCHANGE_FORMAT_ERROR_INVALID_FORMAT,
+	EXCHANGE_FORMAT_ERROR_UNSUPPORTED_VERSION,
+	EXCHANGE_FORMAT_ERROR_WRITE,
+	EXCHANGE_FORMAT_ERROR_UNSUPPORTED_FILE_TYPE,
+} exchange_error_e;
+
+#endif /* PVATTEST_EXCHANGE_FORMAT_H */
--- /dev/null
+++ b/pvattest/src/log.c
@@ -0,0 +1,181 @@
+/*
+ * Functions used for logging.
+ *
+ * Copyright IBM Corp. 2022
+ *
+ * s390-tools is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ */
+/* Must be included before any other header */
+#include "config.h"
+
+#include <stdio.h>
+#include <stdarg.h>
+
+#include "types.h"
+#include "common.h"
+#include "log.h"
+
+void pvattest_log_increase_log_lvl(int *log_lvl)
+{
+	if (*log_lvl >= PVATTEST_LOG_LVL_MAX)
+		return;
+	*log_lvl = *log_lvl << 1;
+}
+
+void pvattest_log_error(const char *format, ...)
+{
+	va_list argp;
+
+	va_start(argp, format);
+	g_logv(NULL, PVATTEST_LOG_LVL_ERROR, format, argp);
+	va_end(argp);
+}
+
+void pvattest_log_warning(const char *format, ...)
+{
+	va_list argp;
+
+	va_start(argp, format);
+	g_logv(NULL, PVATTEST_LOG_LVL_WARNING, format, argp);
+	va_end(argp);
+}
+
+void pvattest_log_info(const char *format, ...)
+{
+	va_list argp;
+
+	va_start(argp, format);
+	g_logv(NULL, PVATTEST_LOG_LVL_INFO, format, argp);
+	va_end(argp);
+}
+
+void pvattest_log_debug(const char *format, ...)
+{
+	va_list argp;
+
+	va_start(argp, format);
+	g_logv(NULL, PVATTEST_LOG_LVL_DEBUG, format, argp);
+	va_end(argp);
+}
+
+static void _log_print(FILE *stream, const char *prefix, const char *message, const char *postfix)
+{
+	g_autofree char *prefix_empty = NULL, *new_msg = NULL;
+	size_t prefix_len = strlen(prefix);
+	char **message_v;
+
+	if (!prefix || prefix_len == 0) {
+		printf("%s%s", message, postfix);
+		return;
+	}
+
+	message_v = g_strsplit(message, "\n", 0);
+	prefix_empty = g_malloc0(prefix_len + 2);
+
+	snprintf(prefix_empty, prefix_len + 2, "\n%*c\b", (int)prefix_len, ' ');
+	new_msg = g_strjoinv(prefix_empty, message_v);
+
+	fprintf(stream, "%s%s%s", prefix, new_msg, postfix);
+
+	g_strfreev(message_v);
+}
+
+static void _log_logger(GLogLevelFlags level, const char *message, int log_level,
+			gboolean use_prefix, const char *postfix)
+{
+	const char *prefix = "";
+
+	/* filter out messages depending on debugging level */
+	if ((level & PVATTEST_LOG_LVL_DEBUG) && log_level < PVATTEST_LOG_LVL_DEBUG)
+		return;
+
+	if ((level & PVATTEST_LOG_LVL_INFO) && log_level < PVATTEST_LOG_LVL_INFO)
+		return;
+
+	if (use_prefix && level & (G_LOG_LEVEL_WARNING | PVATTEST_LOG_LVL_WARNING))
+		prefix = _("WARNING: ");
+
+	if (use_prefix && level & (G_LOG_LEVEL_ERROR | PVATTEST_LOG_LVL_ERROR))
+		prefix = _("ERROR: ");
+
+	if (use_prefix && level & (G_LOG_LEVEL_DEBUG | PVATTEST_LOG_LVL_DEBUG))
+		prefix = _("DEBUG: ");
+
+	if (level & (G_LOG_LEVEL_WARNING | G_LOG_LEVEL_ERROR | PVATTEST_LOG_LVL_WARNING |
+		     PVATTEST_LOG_LVL_ERROR))
+		_log_print(stderr, prefix, message, postfix);
+	else
+		_log_print(stdout, prefix, message, postfix);
+}
+
+/**
+ * prefixes type. and adds a "\n" ad the end.
+ */
+void pvattest_log_default_logger(const char *log_domain G_GNUC_UNUSED, GLogLevelFlags level,
+				 const char *message, void *user_data)
+{
+	int log_level = *(int *)user_data;
+
+	_log_logger(level, message, log_level, TRUE, "\n");
+}
+
+/*
+ * writes message as it is if log level is high enough.
+ */
+void pvattest_log_plain_logger(const char *log_domain G_GNUC_UNUSED, GLogLevelFlags level,
+			       const char *message, void *user_data)
+{
+	int log_level = *(int *)user_data;
+
+	_log_logger(level, message, log_level, FALSE, "");
+}
+
+void hexdump(const void *data, size_t size, size_t len, const char *prefix, GLogLevelFlags log_lvl)
+{
+	const uint8_t *data_b = data;
+
+	pv_wrapped_g_assert(data);
+
+	g_log(PVATTEST_HEXDUMP_LOG_DOMAIN, log_lvl, "%s0x0000  ", prefix);
+	for (size_t i = 0; i < size; i++) {
+		g_log(PVATTEST_HEXDUMP_LOG_DOMAIN, log_lvl, "%02x", data_b[i]);
+		if (i % 2 == 1)
+			g_log(PVATTEST_HEXDUMP_LOG_DOMAIN, log_lvl, " ");
+		if (i == size - 1)
+			break;
+		if (i % len == len - 1)
+			g_log(PVATTEST_HEXDUMP_LOG_DOMAIN, log_lvl, "\n%s0x%04lx  ", prefix, i + 1);
+	}
+	g_log(PVATTEST_HEXDUMP_LOG_DOMAIN, log_lvl, "\n");
+}
+
+void printf_hexdump(const void *data, size_t size, size_t len, const char *prefix, FILE *stream)
+{
+	const uint8_t *data_b = data;
+
+	pv_wrapped_g_assert(data);
+	pv_wrapped_g_assert(stream);
+
+	fprintf(stream, "%s0x0000  ", prefix);
+	for (size_t i = 0; i < size; i++) {
+		fprintf(stream, "%02x", data_b[i]);
+		if (i % 2 == 1)
+			fprintf(stream, " ");
+		if (i == size - 1)
+			break;
+		if (i % len == len - 1)
+			fprintf(stream, "\n%s0x%04lx  ", prefix, i + 1);
+	}
+	fprintf(stream, "\n");
+}
+
+void pvattest_log_GError(const char *info, GError *error)
+{
+	pv_wrapped_g_assert(info);
+
+	if (!error)
+		return;
+
+	pvattest_log_error("%s:\n%s", info, error->message);
+}
--- /dev/null
+++ b/pvattest/src/log.h
@@ -0,0 +1,67 @@
+/*
+ * Definitions used for logging.
+ *
+ * Copyright IBM Corp. 2022
+ *
+ * s390-tools is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ */
+#ifndef PVATTEST_LOG_H
+#define PVATTEST_LOG_H
+/* Must be included before any other header */
+#include "config.h"
+
+#include "libpv/glib-helper.h"
+#include "libpv/macros.h"
+
+#define PVATTEST_LOG_LVL_TOOL_ALL (1 << (G_LOG_LEVEL_USER_SHIFT))
+#define PVATTEST_LOG_LVL_ERROR (1 << (G_LOG_LEVEL_USER_SHIFT))
+#define PVATTEST_LOG_LVL_WARNING (1 << (G_LOG_LEVEL_USER_SHIFT + 1))
+#define PVATTEST_LOG_LVL_INFO (1 << (G_LOG_LEVEL_USER_SHIFT + 2))
+#define PVATTEST_LOG_LVL_DEBUG (1 << (G_LOG_LEVEL_USER_SHIFT + 3))
+
+#define PVATTEST_LOG_LVL_DEFAULT PVATTEST_LOG_LVL_WARNING
+#define PVATTEST_LOG_LVL_MAX PVATTEST_LOG_LVL_DEBUG
+
+#define PVATTEST_HEXDUMP_LOG_DOMAIN "pvattest_hdump"
+
+void pvattest_log_increase_log_lvl(int *log_lvl);
+void pvattest_log_error(const char *format, ...);
+void pvattest_log_warning(const char *format, ...);
+void pvattest_log_info(const char *format, ...);
+void pvattest_log_debug(const char *format, ...);
+
+/** pvattest_log_default_logger:
+ *
+ * A #GLogFunc implementation.
+ * Prefixes log level and adds a "\n" ad the end.
+ */
+void pvattest_log_default_logger(const char *log_domain, GLogLevelFlags level, const char *message,
+				 void *user_data);
+/* pvattest_log_plain_logger:
+ *
+ * A #GLogFunc implementation.
+ * Writes message as it is if log level is high enough.
+ */
+void pvattest_log_plain_logger(const char *log_domain, GLogLevelFlags level, const char *message,
+			       void *user_data);
+#define dhexdump(v, s)                                                            \
+	{                                                                         \
+		pvattest_log_debug("%s (%li byte):", #v, s);                      \
+		hexdump(v, s, 16L, "    ", PVATTEST_LOG_LVL_DEBUG);               \
+		g_log(PVATTEST_HEXDUMP_LOG_DOMAIN, PVATTEST_LOG_LVL_DEBUG, "\n"); \
+	}
+#define gbhexdump(v)                                                                 \
+	{                                                                            \
+		pvattest_log_debug("%s:(%li byte):", #v, g_bytes_get_size(v));       \
+		hexdump(g_bytes_get_data(v, NULL), g_bytes_get_size(v), 16L, "    ", \
+			PVATTEST_LOG_LVL_DEBUG);                                     \
+		g_log(PVATTEST_HEXDUMP_LOG_DOMAIN, PVATTEST_LOG_LVL_DEBUG, "\n");    \
+	}
+void hexdump(const void *data, size_t size, size_t len, const char *prefix, GLogLevelFlags log_lvl)
+	PV_NONNULL(1);
+void printf_hexdump(const void *data, size_t size, size_t len, const char *prefix, FILE *stream)
+	PV_NONNULL(1, 5);
+void pvattest_log_GError(const char *info, GError *error) PV_NONNULL(1);
+
+#endif /* PVATTEST_LOG_H */
--- /dev/null
+++ b/pvattest/src/pvattest.c
@@ -0,0 +1,392 @@
+/*
+ * Entry point for the pvattest tool.
+ *
+ * Copyright IBM Corp. 2022
+ *
+ * s390-tools is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ */
+/* Must be included before any other header */
+#include "config.h"
+
+#include <stdio.h>
+#include <unistd.h>
+
+#include <openssl/evp.h>
+
+#include "libpv/crypto.h"
+#include "libpv/cert.h"
+
+#include "uvio.h"
+#include "common.h"
+#include "attestation.h"
+#include "arcb.h"
+#include "argparse.h"
+#include "exchange_format.h"
+#include "log.h"
+
+#define PVATTEST_NID NID_secp521r1
+#define PVATTEST_UV_PATH "/dev/uv"
+#define PVATTEST_EXIT_MEASURE_NOT_VERIFIED 2
+
+enum pvattest_error {
+	PVATTEST_ERROR_INVAL_ATT_RESULT,
+};
+
+static arcb_v1_t *create_arcb(char **host_key_paths, const gboolean use_nonce,
+			      const gboolean phkh_img, const gboolean phkh_att,
+			      const uint64_t user_paf, GError **error)
+{
+	g_autoptr(GBytes) arpk = NULL, meas_key = NULL, nonce = NULL, iv = NULL;
+	g_autoslist(PvX509WithPath) host_keys_with_path = NULL;
+	g_autoslist(EVP_PKEY) evp_host_keys = NULL;
+	const uint32_t mai = MAI_HMAC_SHA512;
+	g_autoptr(EVP_PKEY) evp_cpk = NULL;
+	g_autoptr(arcb_v1_t) arcb = NULL;
+	uint64_t paf = user_paf;
+
+	g_assert(host_key_paths);
+
+	arpk = pv_generate_key(EVP_aes_256_gcm(), error);
+	if (!arpk)
+		return NULL;
+	iv = pv_generate_iv(EVP_aes_256_gcm(), error);
+	if (!iv)
+		return NULL;
+	evp_cpk = pv_generate_ec_key(PVATTEST_NID, error);
+	if (!evp_cpk)
+		return NULL;
+	meas_key = pv_generate_rand_data(HMAC_SHA512_KEY_SIZE, error);
+	if (!meas_key)
+		return NULL;
+
+	if (phkh_img)
+		paf |= ARCB_V1_PAF_AAD_PHKH_HEADER;
+	if (phkh_att)
+		paf |= ARCB_V1_PAF_AAD_PHKH_ATTEST;
+
+	arcb = arcb_v1_new(arpk, iv, mai, evp_cpk, meas_key, paf, error);
+	if (!arcb)
+		return NULL;
+	if (use_nonce) {
+		nonce = pv_generate_rand_data(ARCB_V1_NONCE_SIZE, error);
+		if (!nonce)
+			return NULL;
+		arcb_v1_set_nonce(arcb, nonce);
+	}
+
+	host_keys_with_path = pv_load_certificates(host_key_paths, error);
+	if (!host_keys_with_path)
+		return NULL;
+
+	/* Extract EVP_PKEY structures and verify that the correct elliptic
+	 * curve is used.
+	 */
+	evp_host_keys = pv_get_ec_pubkeys(host_keys_with_path, PVATTEST_NID, error);
+	if (!evp_host_keys)
+		return NULL;
+	for (GSList *iter = evp_host_keys; iter; iter = iter->next) {
+		EVP_PKEY *host_key = iter->data;
+
+		if (arcb_v1_add_key_slot(arcb, host_key, error) < 0)
+			return NULL;
+	}
+	return g_steal_pointer(&arcb);
+}
+
+#define __PVATTEST_CREATE_ERROR_MSG _("Creating the attestation request failed")
+static int do_create(const pvattest_create_config_t *create_config)
+{
+	g_autoptr(exchange_format_ctx_t) output_ctx = NULL;
+	uint32_t measurement_size, additional_data_size;
+	g_autoptr(GBytes) serialized_arcb = NULL;
+	g_autoptr(arcb_v1_t) arcb = NULL;
+	g_autoptr(GError) error = NULL;
+	g_autoptr(GBytes) arpk = NULL;
+
+	if (!create_config->use_nonce)
+		pvattest_log_warning(_("No nonce used. (Experimental setting)"));
+
+	if (create_config->no_verify) {
+		pvattest_log_warning(_("Host-key document verification is disabled.\n"
+				       "The attestation result could be compromised!"));
+		pvattest_log_debug(_("Verification skipped."));
+	} else {
+		if (pv_verify_host_key_docs_by_path(
+			    create_config->host_key_document_paths, create_config->root_ca_path,
+			    create_config->crl_paths, create_config->certificate_paths,
+			    create_config->online, &error) < 0)
+			goto err_exit;
+		pvattest_log_debug(_("Verification passed."));
+	}
+
+	/* build attestation request */
+	arcb = create_arcb(create_config->host_key_document_paths, create_config->use_nonce,
+			   create_config->phkh_img, create_config->phkh_att, create_config->paf,
+			   &error);
+	if (!arcb)
+		goto err_exit;
+
+	additional_data_size = arcb_v1_get_required_additional_size(arcb);
+	if (create_config->x_aad_size >= 0) {
+		g_assert_cmpint(create_config->x_aad_size, <=, UINT32_MAX);
+		additional_data_size = (uint32_t)create_config->x_aad_size;
+	}
+	measurement_size = arcb_v1_get_required_measurement_size(arcb, &error);
+	if (error)
+		goto err_exit;
+
+	serialized_arcb = arcb_v1_serialize(arcb, &error);
+	if (!serialized_arcb)
+		goto err_exit;
+
+	/* write attestation request data to file */
+	output_ctx = exchange_ctx_new(PVATTEST_EXCHANGE_VERSION_1_00, serialized_arcb,
+				      measurement_size, additional_data_size, &error);
+	if (!output_ctx)
+		goto err_exit;
+	if (exchange_write_to_file(output_ctx, create_config->output_path, &error) < 0)
+		goto err_exit;
+	pvattest_log_debug(_("ARCB written to file."));
+
+	/* write attestation request protection key to file */
+	arpk = arcb_v1_get_arp_key(arcb);
+	wrapped_g_file_set_content(create_config->arp_key_out_path, arpk, 0600, &error);
+	if (error)
+		goto err_exit;
+	pvattest_log_debug(_("ARPK written to file."));
+
+	return EXIT_SUCCESS;
+
+err_exit:
+	pvattest_log_GError(__PVATTEST_CREATE_ERROR_MSG, error);
+	return EXIT_FAILURE;
+}
+
+#ifdef PVATTEST_COMPILE_PERFORM
+#define __PVATTEST_MEASURE_ERROR_MSG _("Performing the attestation measurement failed")
+static int do_perform(pvattest_perform_config_t *perform_config)
+{
+	g_autoptr(GBytes) serialized_arcb = NULL, user_data = NULL, measurement = NULL,
+			  additional_data = NULL, config_uid = NULL;
+	size_t uv_measurement_data_size, uv_addidtional_data_size;
+	g_autoptr(exchange_format_ctx_t) exchange_ctx = NULL;
+	uint32_t measurement_size, additional_data_size;
+	g_autoptr(uvio_attest_t) uvio_attest = NULL;
+	g_autoptr(GError) error = NULL;
+	be16_t uv_rc;
+	int uv_fd;
+
+	exchange_ctx = exchange_ctx_from_file(perform_config->input_path, &error);
+	if (!exchange_ctx)
+		goto err_exit;
+
+	serialized_arcb = exchange_get_serialized_arcb(exchange_ctx);
+	if (!serialized_arcb) {
+		g_set_error(&error, PVATTEST_ERROR, ARCB_ERR_INVALID_ARCB,
+			    _("The input does not provide an attestation request."));
+
+		goto err_exit;
+	}
+
+	measurement_size = exchange_get_requested_measurement_size(exchange_ctx);
+	additional_data_size = exchange_get_requested_additional_data_size(exchange_ctx);
+
+	pvattest_log_debug(_("Input data loaded."));
+
+	if (perform_config->user_data_path) {
+		user_data = pv_file_get_content_as_g_bytes(perform_config->user_data_path, &error);
+		if (!user_data)
+			goto err_exit;
+		pvattest_log_debug(_("Added user data from '%s'"), perform_config->user_data_path);
+	}
+	uvio_attest = build_attestation_v1_ioctl(serialized_arcb, user_data, measurement_size,
+						 additional_data_size, &error);
+	if (!uvio_attest)
+		goto err_exit;
+
+	pvattest_log_debug(_("attestation context generated."));
+
+	/* execute attestation */
+	uv_fd = uvio_open(PVATTEST_UV_PATH, &error);
+	if (uv_fd < 0)
+		goto err_exit;
+
+	uv_rc = uvio_ioctl_attest(uv_fd, uvio_attest, &error);
+	close(uv_fd);
+	if (uv_rc != UVC_EXECUTED)
+		goto err_exit;
+	pvattest_log_debug(_("attestation measurement successful. rc = %#x"), uv_rc);
+
+	/* write to file */
+	measurement = uvio_get_measurement(uvio_attest);
+	additional_data = uvio_get_additional_data(uvio_attest);
+	config_uid = uvio_get_config_uid(uvio_attest);
+
+	uv_measurement_data_size = measurement == NULL ? 0 : g_bytes_get_size(measurement);
+	if (uv_measurement_data_size != measurement_size) {
+		g_set_error(&error, PVATTEST_ERROR, PVATTEST_ERROR_INVAL_ATT_RESULT,
+			    "The measurement size returned by Ultravisor is not as expected.");
+		goto err_exit;
+	}
+
+	uv_addidtional_data_size = additional_data == NULL ? 0 : g_bytes_get_size(additional_data);
+	if (uv_addidtional_data_size != additional_data_size) {
+		g_set_error(&error, PVATTEST_ERROR, PVATTEST_ERROR_INVAL_ATT_RESULT,
+			    "The additional data size returned by Ultravisor is not as expected.");
+		goto err_exit;
+	}
+
+	exchange_set_measurement(exchange_ctx, measurement);
+	if (additional_data)
+		exchange_set_additional_data(exchange_ctx, additional_data);
+	exchange_set_config_uid(exchange_ctx, config_uid);
+	if (user_data)
+		exchange_set_user_data(exchange_ctx, user_data);
+
+	if (exchange_write_to_file(exchange_ctx, perform_config->output_path, &error) < 0)
+		goto err_exit;
+
+	pvattest_log_debug(_("Output written to file."));
+
+	return EXIT_SUCCESS;
+
+err_exit:
+	pvattest_log_GError(__PVATTEST_MEASURE_ERROR_MSG, error);
+	return EXIT_FAILURE;
+}
+#endif /* PVATTEST_COMPILE_PERFORM */
+
+#define __PVATTEST_VERIFY_ERROR_MSG _("Attestation measurement verification failed")
+static int do_verify(pvattest_verify_config_t *verify_config)
+{
+	g_autoptr(GBytes) user_data = NULL, uv_measurement = NULL, additional_data = NULL,
+			  image_hdr = NULL, calc_measurement = NULL, config_uid = NULL,
+			  meas_key = NULL, arp_key = NULL, nonce = NULL, serialized_arcb = NULL;
+	g_autofree att_meas_ctx_t *measurement_hdr = NULL;
+	g_autoptr(exchange_format_ctx_t) input_ctx = NULL;
+	g_autoptr(GError) error = NULL;
+	gboolean rc;
+
+	image_hdr = pv_file_get_content_as_g_bytes(verify_config->hdr_path, &error);
+	if (!image_hdr)
+		goto err_exit;
+
+	measurement_hdr = att_extract_from_hdr(image_hdr, &error);
+	if (!measurement_hdr)
+		goto err_exit;
+
+	pvattest_log_debug(_("Image header loaded."));
+
+	input_ctx = exchange_ctx_from_file(verify_config->input_path, &error);
+	if (!input_ctx)
+		goto err_exit;
+
+	config_uid = exchange_get_config_uid(input_ctx);
+	uv_measurement = exchange_get_measurement(input_ctx);
+	user_data = exchange_get_user_data(input_ctx);
+	additional_data = exchange_get_additional_data(input_ctx);
+	serialized_arcb = exchange_get_serialized_arcb(input_ctx);
+
+	if (!uv_measurement || !serialized_arcb) {
+		g_set_error(&error, PVATTEST_ERROR, PVATTEST_SUBC_INVALID,
+			    _("Input data has no measurement"));
+		goto err_exit;
+	}
+	pvattest_log_debug(_("Input data loaded."));
+
+	att_add_uid(measurement_hdr, config_uid);
+
+	arp_key = pv_file_get_content_as_g_bytes(verify_config->arp_key_in_path, &error);
+	if (!arp_key)
+		goto err_exit;
+	pvattest_log_debug(_("ARPK loaded."));
+
+	rc = arcb_v1_verify_serialized_arcb(serialized_arcb, arp_key, &meas_key, &nonce, &error);
+	if (!rc)
+		goto err_exit;
+
+	pvattest_log_debug(_("Input ARCB verified."));
+
+	calc_measurement = att_gen_measurement_hmac_sha512(measurement_hdr, meas_key, user_data,
+							   nonce, additional_data, &error);
+	if (!calc_measurement)
+		goto err_exit;
+	pvattest_log_debug(_("Measurement calculated."));
+
+	if (!att_verify_measurement(calc_measurement, uv_measurement, &error)) {
+		pvattest_log_GError(__PVATTEST_VERIFY_ERROR_MSG, error);
+		pvattest_log_debug(_("Measurement values:"));
+		gbhexdump(uv_measurement);
+		gbhexdump(calc_measurement);
+		return PVATTEST_EXIT_MEASURE_NOT_VERIFIED;
+	}
+
+	pvattest_log_debug(_("Measurement verified."));
+
+	return EXIT_SUCCESS;
+
+err_exit:
+	pvattest_log_GError(__PVATTEST_VERIFY_ERROR_MSG, error);
+	return EXIT_FAILURE;
+}
+
+/*
+ * Will not free the config structs, but the nested char* etc.
+ * that's what we need to do as we will receive a statically allocated config_t
+ * Not defined in the parse header as someone might incorrectly assume
+ * that the config pointers will be freed.
+ */
+WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(pvattest_config_t, pvattest_parse_clear_config)
+int main(int argc, char *argv[])
+{
+	int appl_log_lvl = PVATTEST_LOG_LVL_DEFAULT;
+	g_autoptr(pvattest_config_t) config = NULL;
+	g_autoptr(GError) error = NULL;
+	enum pvattest_command command;
+	int rc;
+
+	/* setting up the default log handler to filter messages based on the
+	 * log level specified by the user.
+	 */
+	g_log_set_handler(NULL, G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION,
+			  &pvattest_log_default_logger, &appl_log_lvl);
+	/* setting up the log handler for hexdumps (no prefix and '\n' at end of
+	 * message)to filter messages based on the log level specified by the
+	 * user.
+	 */
+	g_log_set_handler(PVATTEST_HEXDUMP_LOG_DOMAIN,
+			  G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION,
+			  &pvattest_log_plain_logger, &appl_log_lvl);
+
+	command = pvattest_parse(&argc, &argv, &config, &error);
+	if (command == PVATTEST_SUBC_INVALID) {
+		pvattest_log_error(_("%s\nTry '%s --help' for more information"), error->message,
+				   GETTEXT_PACKAGE);
+		exit(EXIT_FAILURE);
+	}
+	g_assert(config);
+	appl_log_lvl = config->general.log_level;
+
+	pv_init();
+
+	switch (command) {
+	case PVATTEST_SUBC_CREATE:
+		rc = do_create(&config->create);
+		break;
+#ifdef PVATTEST_COMPILE_PERFORM
+	case PVATTEST_SUBC_PERFORM:
+		rc = do_perform(&config->perform);
+		break;
+#endif /* PVATTEST_COMPILE_PERFORM */
+	case PVATTEST_SUBC_VERIFY:
+		rc = do_verify(&config->verify);
+		break;
+	default:
+		g_return_val_if_reached(EXIT_FAILURE);
+	}
+
+	pv_cleanup();
+
+	return rc;
+}
--- /dev/null
+++ b/pvattest/src/types.h
@@ -0,0 +1,18 @@
+/*
+ * Common data type definitions and functions
+ *
+ * Copyright IBM Corp. 2022
+ *
+ * s390-tools is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ */
+#ifndef PVATTEST_TYPES_H
+#define PVATTEST_TYPES_H
+#include <stdint.h>
+
+/* Types to mark values as big endian. */
+typedef uint16_t be16_t;
+typedef uint32_t be32_t;
+typedef uint64_t be64_t;
+
+#endif /* PVATTEST_TYPES_H */
--- /dev/null
+++ b/pvattest/src/uvio.c
@@ -0,0 +1,177 @@
+/*
+ * UV device (uvio) related functions and definitions.
+ * uses s390 only (kernel) features.
+ *
+ * Copyright IBM Corp. 2022
+ *
+ * s390-tools is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ */
+/* Must be included before any other header */
+#include "config.h"
+
+#ifdef PVATTEST_COMPILE_PERFORM
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+
+#include "attestation.h"
+#include "uvio.h"
+#include "common.h"
+#include "log.h"
+
+/* some helper macros */
+#define U64_TO_PTR(v) ((void *)(v))
+#define PTR_TO_U64(ptr) ((uint64_t)(ptr))
+
+uvio_attest_t *build_attestation_v1_ioctl(GBytes *serialized_arcb, GBytes *user_data,
+					  const uint32_t measurement_size,
+					  const uint32_t add_data_size, GError **error)
+{
+	g_autoptr(uvio_attest_t) uvio_attest = NULL;
+	size_t arcb_size;
+	void *arcb;
+
+	pv_wrapped_g_assert(serialized_arcb);
+
+	g_bytes_ref(serialized_arcb);
+	arcb = g_bytes_unref_to_data(serialized_arcb, &arcb_size);
+
+	uvio_attest = g_malloc0(sizeof(*uvio_attest));
+	uvio_attest->arcb_addr = PTR_TO_U64(g_steal_pointer(&arcb));
+	g_assert_cmpuint(arcb_size, <, UINT32_MAX);
+	uvio_attest->arcb_len = GUINT32_TO_BE((uint32_t)arcb_size);
+	/* transferred the local ownership of the arcb from this function to uvio_attest; nullify pointer */
+	g_steal_pointer(&serialized_arcb);
+
+	if (user_data) {
+		if (g_bytes_get_size(user_data) > sizeof(uvio_attest->user_data)) {
+			g_set_error(error, ATT_ERROR, ATT_ERR_INVALID_USER_DATA,
+				    _("User data larger than %li bytes"),
+				    sizeof(uvio_attest->user_data));
+			return NULL;
+		}
+		uvio_attest->user_data_len = GUINT16_TO_BE((uint16_t)g_bytes_get_size(user_data));
+		pv_gbytes_memcpy(uvio_attest->user_data, uvio_attest->user_data_len, user_data);
+	}
+
+	uvio_attest->meas_len = GUINT32_TO_BE(measurement_size);
+	uvio_attest->meas_addr = PTR_TO_U64(g_malloc0(uvio_attest->meas_len));
+
+	uvio_attest->add_data_len = GUINT32_TO_BE(add_data_size);
+	uvio_attest->add_data_addr = PTR_TO_U64(g_malloc0(uvio_attest->add_data_len));
+
+	return g_steal_pointer(&uvio_attest);
+}
+
+void uvio_attest_free(uvio_attest_t *attest)
+{
+	if (!attest)
+		return;
+
+	g_free(U64_TO_PTR(attest->arcb_addr));
+	g_free(U64_TO_PTR(attest->meas_addr));
+	g_free(U64_TO_PTR(attest->add_data_addr));
+	g_free(attest);
+}
+
+GBytes *uvio_get_measurement(const uvio_attest_t *attest)
+{
+	pv_wrapped_g_assert(attest);
+
+	if (attest->meas_addr == (__u64)0)
+		return NULL;
+	return g_bytes_new(U64_TO_PTR(attest->meas_addr), GUINT32_FROM_BE(attest->meas_len));
+}
+
+GBytes *uvio_get_additional_data(const uvio_attest_t *attest)
+{
+	pv_wrapped_g_assert(attest);
+
+	if (attest->add_data_addr == (__u64)0)
+		return NULL;
+	return g_bytes_new(U64_TO_PTR(attest->add_data_addr),
+			   GUINT32_FROM_BE(attest->add_data_len));
+}
+
+GBytes *uvio_get_config_uid(const uvio_attest_t *attest)
+{
+	pv_wrapped_g_assert(attest);
+
+	return g_bytes_new(attest->config_uid, sizeof(attest->config_uid));
+}
+
+uint16_t uvio_ioctl(const int uv_fd, const unsigned int cmd, const uint32_t flags,
+		    const void *argument, const uint32_t argument_size, GError **error)
+{
+	g_autofree struct uvio_ioctl_cb *uv_ioctl = g_malloc0(sizeof(*uv_ioctl));
+	int rc, cached_errno;
+
+	pv_wrapped_g_assert(argument);
+
+	uv_ioctl->flags = flags;
+	uv_ioctl->argument_addr = PTR_TO_U64(argument);
+	uv_ioctl->argument_len = argument_size;
+	rc = ioctl(uv_fd, cmd, uv_ioctl);
+	cached_errno = errno;
+
+	if (rc < 0) {
+		g_set_error(error, UVIO_ERROR, UVIO_ERR_UV_IOCTL, _("ioctl failed: %s "),
+			    g_strerror(cached_errno));
+		return 0;
+	}
+
+	if (uv_ioctl->uv_rc != UVC_EXECUTED)
+		g_set_error(error, UVIO_ERROR, UVIO_ERR_UV_NOT_OK,
+			    _("Ultravisor call returned '%#x' (%s)"), uv_ioctl->uv_rc,
+			    uvio_uv_rc_to_str(uv_ioctl->uv_rc));
+	return GUINT16_FROM_BE(uv_ioctl->uv_rc);
+}
+
+uint16_t uvio_ioctl_attest(const int uv_fd, uvio_attest_t *attest, GError **error)
+{
+	pv_wrapped_g_assert(attest);
+
+	return uvio_ioctl(uv_fd, UVIO_IOCTL_ATT, 0, attest, sizeof(*attest), error);
+}
+
+int uvio_open(const char *uv_path, GError **error)
+{
+	pv_wrapped_g_assert(uv_path);
+
+	int uv_fd;
+	int cached_errno;
+
+	uv_fd = open(uv_path, O_RDWR);
+	cached_errno = errno;
+	if (uv_fd < 0)
+		g_set_error(error, UVIO_ERROR, UVIO_ERR_UV_OPEN,
+			    _("Cannot open uv driver at %s: %s"), uv_path,
+			    g_strerror(cached_errno));
+	return uv_fd;
+}
+
+const char *uvio_uv_rc_to_str(const int rc)
+{
+	switch (rc) {
+	case 0x106:
+		return _("Unsupported attestation request version");
+	case 0x108:
+		return _("Number of key slots is greater than the maximum number supported");
+	case 0x10a:
+		return _("Unsupported plaintext attestation flags");
+	case 0x10c:
+		return _(
+			"Unable to decrypt attestation request control block. No valid host-key was provided");
+	case 0x10d:
+		return _("Measurement data length is too small to store measurement");
+	case 0x10e:
+		return _("Additional data length is too small to store measurement");
+	default:
+		return _("Unknown code");
+	}
+}
+
+#endif /* PVATTEST_COMPILE_PERFORM */
--- /dev/null
+++ b/pvattest/src/uvio.h
@@ -0,0 +1,110 @@
+/*
+ * UV device (uvio) related functions and definitions.
+ *
+ * Copyright IBM Corp. 2022
+ *
+ * s390-tools is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ */
+#ifndef PVATTEST_UVIO_H
+#define PVATTEST_UVIO_H
+#include "config.h"
+
+#ifdef PVATTEST_COMPILE_PERFORM
+
+#include <sys/ioctl.h>
+#include <asm/uvdevice.h>
+
+#include "libpv/glib-helper.h"
+
+#include "arcb.h"
+#include "common.h"
+
+#define UVC_EXECUTED 0x0001
+
+typedef struct uvio_attest uvio_attest_t;
+G_STATIC_ASSERT(sizeof(uvio_attest_t) == 0x138);
+G_STATIC_ASSERT(sizeof(struct uvio_ioctl_cb) == 0x40);
+
+/**
+ * build_attestation_v1_ioctl:
+ * @serialized_arcb: A ARCB in binary format
+ * @user_data (optional): up to 256 bytes of user data to be added to the measurement
+ * @measurement_size: Size of the measurement result to be allocated
+ * @add_data_size: Size of the additional data to be allocated
+ * @error: return location for a #GError
+ *
+ * Builds the structure to be passed to `/dev/uv` for attestation IOCTLs and
+ * allocates any required memory.
+ *
+ * Returns: (nullable) (transfer full): Pointer to a uvio_attest_t to be passed to `/dev/uv`
+ */
+uvio_attest_t *build_attestation_v1_ioctl(GBytes *serialized_arcb, GBytes *user_data,
+					  const uint32_t measurement_size,
+					  const uint32_t add_data_size, GError **error)
+	PV_NONNULL(1);
+GBytes *uvio_get_measurement(const uvio_attest_t *attest) PV_NONNULL(1);
+GBytes *uvio_get_additional_data(const uvio_attest_t *attest) PV_NONNULL(1);
+GBytes *uvio_get_config_uid(const uvio_attest_t *attest) PV_NONNULL(1);
+void uvio_attest_free(uvio_attest_t *attest);
+WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(uvio_attest_t, uvio_attest_free)
+
+/**
+ * uvio_ioctl:
+ * @uv_fd: file descriptor to the UV-device
+ * @cmd: IOCTL cmd
+ * @flags: flags for the uv IOCTL
+ * @argument: pointer to the payload
+ * @argument_size: size of #argument
+ * @error: return location for a #GError
+ *
+ * Builds the IOCTL structure using, flags and argument, performs the IOCTL, and returns the UV rc in big endian.
+ * If the device driver emits an error code, a corresponding #GError will be created.
+ * Use the specialized calls (uvio_ioctl_*).
+ *
+ * Returns: UV rc if no device error occurred (>0)
+ * 	    0 on #GError
+ */
+uint16_t uvio_ioctl(const int uv_fd, const unsigned int cmd, const uint32_t flags,
+		    const void *argument, const uint32_t argument_size, GError **error)
+	PV_NONNULL(4);
+/**
+ * uvio_ioctl_attest:
+ * @uv_fd: file descriptor to the UV-device
+ * @attest: pointer to the attestation request
+ * @error: return location for a #GError
+ *
+ * Wraps 'uvio_ioctl' for attestation.
+ *
+ * Returns: UV rc if no device error occurred (>0)
+ * 	    0 on #GError
+ */
+uint16_t uvio_ioctl_attest(const int uv_fd, uvio_attest_t *attest, GError **error) PV_NONNULL(2);
+
+/**
+ * uvio_open:
+ * @uv_path: path of the UV-device usually at /dev/uv
+ * @error: return location for a #GError
+ *
+ * Returns: File descriptor for the UV-device
+ *	    0 on #GError
+ */
+int uvio_open(const char *uv_path, GError **error) PV_NONNULL(1);
+
+/**
+ * uvio_uv_rc_to_str:
+ * @rc: UV return code
+ *
+ * Returns: Pointer to an error string corresponding to the given UV-rc.
+ */
+const char *uvio_uv_rc_to_str(const int rc);
+
+#define UVIO_ERROR g_quark_from_static_string("pv-uvio_error-quark")
+typedef enum {
+	UVIO_ERR_UV_IOCTL,
+	UVIO_ERR_UV_OPEN,
+	UVIO_ERR_UV_NOT_OK,
+} uvio_error_e;
+
+#endif /* PVATTEST_COMPILE_PERFORM */
+#endif /* PVATTEST_UVIO_H */
