mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
rust: pvattest-Rust
Add a CLI compatible Rust implementation of pvattest-C. - All (non-experimental) options are supported and work exactly as in the C implementation. For some options/parameters new variants are available. - `perform` now also accepts positional arguments, while keep accepting -i and -o that was mandatory in the C implementation. - `version` may also be a command instead of an option now. - -V is deprecated - -v increases verbosity instead of showing the version - all experimental options are dropped Acked-by: Qi Feng Huo <huoqif@cn.ibm.com> Acked-by: Marc Hartmayer <mhartmay@linux.ibm.com> Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
This commit is contained in:
Generated
+15
@@ -413,6 +413,21 @@ dependencies = [
|
||||
"utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pvattest"
|
||||
version = "0.10.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"byteorder",
|
||||
"clap",
|
||||
"log",
|
||||
"s390_pv",
|
||||
"serde",
|
||||
"serde_yaml",
|
||||
"utils",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pvsecret"
|
||||
version = "0.10.0"
|
||||
|
||||
@@ -3,6 +3,7 @@ members = [
|
||||
"pv",
|
||||
"pv_core",
|
||||
"pvapconfig",
|
||||
"pvattest",
|
||||
"pvsecret",
|
||||
"utils",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "pvattest"
|
||||
version = "0.10.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = { version = "1.0.70", features = ["std"] }
|
||||
byteorder = "1.3"
|
||||
clap = { version ="4.1", features = ["derive", "wrap_help"]}
|
||||
log = { version = "0.4.6", features = ["std", "release_max_level_debug"] }
|
||||
serde_yaml = "0.9"
|
||||
serde = { version = "1.0.139", features = ["derive"]}
|
||||
zerocopy = { version="0.7", features = ["derive"] }
|
||||
|
||||
pv = { path = "../pv", package = "s390_pv" }
|
||||
utils = { path = "../utils" }
|
||||
@@ -0,0 +1,261 @@
|
||||
<!--
|
||||
Copyright 2024 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.
|
||||
-->
|
||||
# pvattest
|
||||
## Synopsis
|
||||
`pvattest [OPTIONS] <COMMAND>`
|
||||
## Description
|
||||
create, perform, and verify attestation measurements Create, perform, and verify
|
||||
attestation measurements for IBM Secure Execution guest systems.
|
||||
## Commands Overview
|
||||
- **create**
|
||||
<ul>
|
||||
Create an attestation measurement request
|
||||
</ul>
|
||||
|
||||
- **perform**
|
||||
<ul>
|
||||
Send the attestation request to the Ultravisor
|
||||
</ul>
|
||||
|
||||
- **verify**
|
||||
<ul>
|
||||
Verify an attestation response
|
||||
</ul>
|
||||
|
||||
- **version**
|
||||
<ul>
|
||||
Print version information and exit
|
||||
</ul>
|
||||
|
||||
## Options
|
||||
|
||||
`-v`, `--verbose`
|
||||
<ul>
|
||||
Provide more detailed output
|
||||
</ul>
|
||||
|
||||
|
||||
`--version`
|
||||
<ul>
|
||||
Print version information and exit
|
||||
</ul>
|
||||
|
||||
|
||||
`-h`, `--help`
|
||||
<ul>
|
||||
Print help (see a summary with '-h')
|
||||
</ul>
|
||||
|
||||
|
||||
## pvattest create
|
||||
### Synopsis
|
||||
`pvattest create [OPTIONS] --host-key-document <FILE> --output <FILE> --arpk <FILE> <--no-verify|--cert <FILE>>`
|
||||
### Description
|
||||
Create an attestation measurement request Create attestation measurement
|
||||
requests to attest an IBM Secure Execution guest. Only build attestation
|
||||
requests in a trusted environment such as your Workstation. To avoid
|
||||
compromising the attestation do not publish the attestation request protection
|
||||
key and shred it after verification. Every 'create' will generate a new, random
|
||||
protection key.
|
||||
### Options
|
||||
|
||||
`-k`, `--host-key-document <FILE>`
|
||||
<ul>
|
||||
Use FILE as a host-key document. Can be specified multiple times and must be
|
||||
used at least once.
|
||||
</ul>
|
||||
|
||||
|
||||
`--no-verify`
|
||||
<ul>
|
||||
Disable the host-key document verification. Does not require the host-key
|
||||
documents to be valid. Do not use for a production request unless you verified
|
||||
the host-key document beforehand.
|
||||
</ul>
|
||||
|
||||
|
||||
`-C`, `--cert <FILE>`
|
||||
<ul>
|
||||
Use FILE as a certificate to verify the host key or keys. The certificates are
|
||||
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).
|
||||
</ul>
|
||||
|
||||
|
||||
`--crl <FILE>`
|
||||
<ul>
|
||||
Use FILE as a certificate revocation list. The list is used to check whether a
|
||||
certificate of the chain of trust is revoked. Specify this option multiple times
|
||||
to use multiple CRLs.
|
||||
</ul>
|
||||
|
||||
|
||||
`--offline`
|
||||
<ul>
|
||||
Make no attempt to download CRLs
|
||||
</ul>
|
||||
|
||||
|
||||
`--root-ca <ROOT_CA>`
|
||||
<ul>
|
||||
Use FILE as the root-CA certificate for the verification. If omitted, the system
|
||||
wide-root CAs installed on the system are used. Use this only if you trust the
|
||||
specified certificate.
|
||||
</ul>
|
||||
|
||||
|
||||
`-o`, `--output <FILE>`
|
||||
<ul>
|
||||
Write the generated request to FILE
|
||||
</ul>
|
||||
|
||||
|
||||
`--arpk <FILE>`
|
||||
<ul>
|
||||
Save the protection key as unencrypted GCM-AES256 key in FILE Do not publish
|
||||
this key, otherwise your attestation is compromised.
|
||||
</ul>
|
||||
|
||||
|
||||
`--add-data <FLAGS>`
|
||||
<ul>
|
||||
Specify-additional data for the request. Additional data is provided by the
|
||||
Ultravisor and returned during the attestation request and is covered by the
|
||||
attestation measurement. Can be specified multiple times. Optional.
|
||||
Possible values:
|
||||
- **phkh-img**: Request the public host-key-hash of the key that decrypted the SE-image as additional-data
|
||||
- **phkh-att**: Request the public host-key-hash of the key that decrypted the attestation request as additional-data
|
||||
</ul>
|
||||
|
||||
|
||||
`-v`, `--verbose`
|
||||
<ul>
|
||||
Provide more detailed output
|
||||
</ul>
|
||||
|
||||
|
||||
`-h`, `--help`
|
||||
<ul>
|
||||
Print help (see a summary with '-h')
|
||||
</ul>
|
||||
|
||||
|
||||
## pvattest perform
|
||||
### Synopsis
|
||||
`pvattest perform [OPTIONS] [INPUT] [OUTPUT]`
|
||||
### Description
|
||||
Send the attestation request to the Ultravisor Run a measurement of this system
|
||||
through ’/dev/uv’. This device must be accessible and the attestation
|
||||
Ultravisor facility must be present. The input must be an attestation request
|
||||
created with ’pvattest create’. Output will contain the original request and
|
||||
the response from the Ultravisor.
|
||||
### Arguments
|
||||
|
||||
`<INPUT>`
|
||||
<ul>
|
||||
Specify the request to be sent
|
||||
</ul>
|
||||
|
||||
|
||||
`<OUTPUT>`
|
||||
<ul>
|
||||
Write the result to FILE
|
||||
</ul>
|
||||
|
||||
|
||||
### Options
|
||||
|
||||
`-u`, `--user-data <File>`
|
||||
<ul>
|
||||
Provide up to 256 bytes of user input User-data is arbitrary user-defined data
|
||||
appended to the Attestation measurement. It is verified during the Attestation
|
||||
measurement verification. May be any arbitrary data, as long as it is less or
|
||||
equal to 256 bytes
|
||||
</ul>
|
||||
|
||||
|
||||
`-v`, `--verbose`
|
||||
<ul>
|
||||
Provide more detailed output
|
||||
</ul>
|
||||
|
||||
|
||||
`-h`, `--help`
|
||||
<ul>
|
||||
Print help (see a summary with '-h')
|
||||
</ul>
|
||||
|
||||
|
||||
## pvattest verify
|
||||
### Synopsis
|
||||
`pvattest verify [OPTIONS] --input <FILE> --hdr <FILE> --arpk <FILE>`
|
||||
### Description
|
||||
Verify an attestation response 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’.
|
||||
Shred the protection key after the verification. The header must be the IBM
|
||||
Secure Execution header of the image that was attested during ’pvattest
|
||||
perform’
|
||||
### Options
|
||||
|
||||
`-i`, `--input <FILE>`
|
||||
<ul>
|
||||
Specify the attestation request to be verified
|
||||
</ul>
|
||||
|
||||
|
||||
`-o`, `--output <FILE>`
|
||||
<ul>
|
||||
Specify the output for the verification result
|
||||
</ul>
|
||||
|
||||
|
||||
`--hdr <FILE>`
|
||||
<ul>
|
||||
Specifies the header of the guest image. Can be an IBM Secure Execution image
|
||||
created by genprotimg or an extracted IBM Secure Execution header. The header
|
||||
must start at a page boundary.
|
||||
</ul>
|
||||
|
||||
|
||||
`--arpk <FILE>`
|
||||
<ul>
|
||||
Use FILE as the protection key to decrypt the request Do not publish this key,
|
||||
otherwise your attestation is compromised. Delete this key after verification.
|
||||
</ul>
|
||||
|
||||
|
||||
`--format <FORMAT>`
|
||||
<ul>
|
||||
Define the output format
|
||||
Default value: 'yaml'
|
||||
Possible values:
|
||||
- **yaml**: Use yaml format
|
||||
</ul>
|
||||
|
||||
|
||||
`-u`, `--user-data <FILE>`
|
||||
<ul>
|
||||
Write the user data to the FILE if any. Writes the user data, if the response
|
||||
contains any, to FILE The user-data is part of the attestation measurement. If
|
||||
the user-data is written to FILE the user-data was part of the measurement and
|
||||
verified. Emits a warning if the response contains no user-data
|
||||
</ul>
|
||||
|
||||
|
||||
`-v`, `--verbose`
|
||||
<ul>
|
||||
Provide more detailed output
|
||||
</ul>
|
||||
|
||||
|
||||
`-h`, `--help`
|
||||
<ul>
|
||||
Print help (see a summary with '-h')
|
||||
</ul>
|
||||
@@ -0,0 +1,132 @@
|
||||
.\" Copyright 2024 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 "2024-05-15" "s390-tools" "Attestation Manual"
|
||||
.nh
|
||||
.ad l
|
||||
.SH NAME
|
||||
\fBpvattest create\fP - Create an attestation measurement request
|
||||
\fB
|
||||
.SH SYNOPSIS
|
||||
.nf
|
||||
.fam C
|
||||
pvattest create [OPTIONS] --host-key-document <FILE> --output <FILE> --arpk <FILE> <--no-verify|--cert <FILE>>
|
||||
.fam C
|
||||
.fi
|
||||
.SH DESCRIPTION
|
||||
Create attestation measurement requests to attest an IBM Secure Execution guest.
|
||||
Only build attestation requests in a trusted environment such as your
|
||||
Workstation. To avoid compromising the attestation do not publish the
|
||||
attestation request protection key and shred it after verification. Every
|
||||
'create' will generate a new, random protection key.
|
||||
.SH OPTIONS
|
||||
.PP
|
||||
\-k, \-\-host-key-document <FILE>
|
||||
.RS 4
|
||||
Use FILE as a host-key document. Can be specified multiple times and must be
|
||||
used at least once.
|
||||
.RE
|
||||
.RE
|
||||
.PP
|
||||
\-\-no-verify
|
||||
.RS 4
|
||||
Disable the host-key document verification. Does not require the host-key
|
||||
documents to be valid. Do not use for a production request unless you verified
|
||||
the host-key document beforehand.
|
||||
.RE
|
||||
.RE
|
||||
.PP
|
||||
\-C, \-\-cert <FILE>
|
||||
.RS 4
|
||||
Use FILE as a certificate to verify the host key or keys. The certificates are
|
||||
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).
|
||||
.RE
|
||||
.RE
|
||||
.PP
|
||||
\-\-crl <FILE>
|
||||
.RS 4
|
||||
Use FILE as a certificate revocation list. The list is used to check whether a
|
||||
certificate of the chain of trust is revoked. Specify this option multiple times
|
||||
to use multiple CRLs.
|
||||
.RE
|
||||
.RE
|
||||
.PP
|
||||
\-\-offline
|
||||
.RS 4
|
||||
Make no attempt to download CRLs.
|
||||
.RE
|
||||
.RE
|
||||
.PP
|
||||
\-\-root-ca <ROOT_CA>
|
||||
.RS 4
|
||||
Use FILE as the root-CA certificate for the verification. If omitted, the system
|
||||
wide-root CAs installed on the system are used. Use this only if you trust the
|
||||
specified certificate.
|
||||
.RE
|
||||
.RE
|
||||
.PP
|
||||
\-o, \-\-output <FILE>
|
||||
.RS 4
|
||||
Write the generated request to FILE.
|
||||
.RE
|
||||
.RE
|
||||
.PP
|
||||
\-\-arpk <FILE>
|
||||
.RS 4
|
||||
Save the protection key as unencrypted GCM-AES256 key in FILE Do not publish
|
||||
this key, otherwise your attestation is compromised.
|
||||
.RE
|
||||
.RE
|
||||
.PP
|
||||
\-\-add-data <FLAGS>
|
||||
.RS 4
|
||||
Specify-additional data for the request. Additional data is provided by the
|
||||
Ultravisor and returned during the attestation request and is covered by the
|
||||
attestation measurement. Can be specified multiple times. Optional.
|
||||
|
||||
Possible values:
|
||||
.RS 4
|
||||
- \fBphkh-img\fP: Request the public host-key-hash of the key that decrypted the SE-image as additional-data.
|
||||
|
||||
- \fBphkh-att\fP: Request the public host-key-hash of the key that decrypted the attestation request as additional-data.
|
||||
|
||||
.RE
|
||||
.RE
|
||||
.PP
|
||||
\-v, \-\-verbose
|
||||
.RS 4
|
||||
Provide more detailed output.
|
||||
.RE
|
||||
.RE
|
||||
.PP
|
||||
\-h, \-\-help
|
||||
.RS 4
|
||||
Print help (see a summary with '-h').
|
||||
.RE
|
||||
.RE
|
||||
|
||||
.SH EXAMPLES
|
||||
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"
|
||||
.sp
|
||||
\fBpvattest\fR(1)
|
||||
@@ -0,0 +1,71 @@
|
||||
.\" Copyright 2024 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 "2024-05-15" "s390-tools" "Attestation Manual"
|
||||
.nh
|
||||
.ad l
|
||||
.SH NAME
|
||||
\fBpvattest perform\fP - Send the attestation request to the Ultravisor
|
||||
\fB
|
||||
.SH SYNOPSIS
|
||||
.nf
|
||||
.fam C
|
||||
pvattest perform [OPTIONS] [INPUT] [OUTPUT]
|
||||
.fam C
|
||||
.fi
|
||||
.SH DESCRIPTION
|
||||
Run a measurement of this system through ’/dev/uv’. This device must be
|
||||
accessible and the attestation Ultravisor facility must be present. The input
|
||||
must be an attestation request created with ’pvattest create’. Output will
|
||||
contain the original request and the response from the Ultravisor.
|
||||
.SH OPTIONS
|
||||
.PP
|
||||
<INPUT>
|
||||
.RS 4
|
||||
Specify the request to be sent.
|
||||
.RE
|
||||
.RE
|
||||
.PP
|
||||
<OUTPUT>
|
||||
.RS 4
|
||||
Write the result to FILE.
|
||||
.RE
|
||||
.RE
|
||||
|
||||
.PP
|
||||
\-u, \-\-user-data <File>
|
||||
.RS 4
|
||||
Provide up to 256 bytes of user input User-data is arbitrary user-defined data
|
||||
appended to the Attestation measurement. It is verified during the Attestation
|
||||
measurement verification. May be any arbitrary data, as long as it is less or
|
||||
equal to 256 bytes
|
||||
.RE
|
||||
.RE
|
||||
.PP
|
||||
\-v, \-\-verbose
|
||||
.RS 4
|
||||
Provide more detailed output.
|
||||
.RE
|
||||
.RE
|
||||
.PP
|
||||
\-h, \-\-help
|
||||
.RS 4
|
||||
Print help (see a summary with '-h').
|
||||
.RE
|
||||
.RE
|
||||
|
||||
.SH EXAMPLES
|
||||
Perform an attestation measurement with the attestation request 'attreq.bin' and write the output to 'attresp.bin'.
|
||||
.PP
|
||||
.nf
|
||||
.fam C
|
||||
$ pvattest perform attreq.bin attresp.bin
|
||||
|
||||
|
||||
.fam T
|
||||
.fi
|
||||
.SH "SEE ALSO"
|
||||
.sp
|
||||
\fBpvattest\fR(1)
|
||||
@@ -0,0 +1,124 @@
|
||||
.\" Copyright 2024 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 "2024-05-15" "s390-tools" "Attestation Manual"
|
||||
.nh
|
||||
.ad l
|
||||
.SH NAME
|
||||
\fBpvattest verify\fP - Verify an attestation response
|
||||
\fB
|
||||
.SH SYNOPSIS
|
||||
.nf
|
||||
.fam C
|
||||
pvattest verify [OPTIONS] --input <FILE> --hdr <FILE> --arpk <FILE>
|
||||
.fam C
|
||||
.fi
|
||||
.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’. Shred the protection key
|
||||
after the verification. The header must be the IBM Secure Execution header of
|
||||
the image that was attested during ’pvattest perform’
|
||||
.SH OPTIONS
|
||||
.PP
|
||||
\-i, \-\-input <FILE>
|
||||
.RS 4
|
||||
Specify the attestation request to be verified.
|
||||
.RE
|
||||
.RE
|
||||
.PP
|
||||
\-o, \-\-output <FILE>
|
||||
.RS 4
|
||||
Specify the output for the verification result.
|
||||
.RE
|
||||
.RE
|
||||
.PP
|
||||
\-\-hdr <FILE>
|
||||
.RS 4
|
||||
Specifies the header of the guest image. Can be an IBM Secure Execution image
|
||||
created by genprotimg or an extracted IBM Secure Execution header. The header
|
||||
must start at a page boundary.
|
||||
.RE
|
||||
.RE
|
||||
.PP
|
||||
\-\-arpk <FILE>
|
||||
.RS 4
|
||||
Use FILE as the protection key to decrypt the request Do not publish this key,
|
||||
otherwise your attestation is compromised. Delete this key after verification.
|
||||
.RE
|
||||
.RE
|
||||
.PP
|
||||
\-\-format <FORMAT>
|
||||
.RS 4
|
||||
Define the output format.
|
||||
[default: 'yaml']
|
||||
|
||||
Possible values:
|
||||
.RS 4
|
||||
- \fByaml\fP: Use yaml format.
|
||||
|
||||
.RE
|
||||
.RE
|
||||
.PP
|
||||
\-u, \-\-user-data <FILE>
|
||||
.RS 4
|
||||
Write the user data to the FILE if any. Writes the user data, if the response
|
||||
contains any, to FILE The user-data is part of the attestation measurement. If
|
||||
the user-data is written to FILE the user-data was part of the measurement and
|
||||
verified. Emits a warning if the response contains no user-data
|
||||
.RE
|
||||
.RE
|
||||
.PP
|
||||
\-v, \-\-verbose
|
||||
.RS 4
|
||||
Provide more detailed output.
|
||||
.RE
|
||||
.RE
|
||||
.PP
|
||||
\-h, \-\-help
|
||||
.RS 4
|
||||
Print help (see a summary with '-h').
|
||||
.RE
|
||||
.RE
|
||||
|
||||
.SH EXIT STATUS
|
||||
.TP 8
|
||||
.B 0 - Attestation Verified
|
||||
Attesatation measurement verified successfully. Measured guest is in Secure Execution mode.
|
||||
.RE
|
||||
|
||||
.TP 8
|
||||
.B 1 - Program Error
|
||||
Something went wrong during the local calculation or receiving of the measurement value. Refer to the error message.
|
||||
.RE
|
||||
|
||||
.TP 8
|
||||
.B 2 - Attestation NOT Verified
|
||||
Attesation measurement calculation does not match the received value. Measured guest is very likely not in Secure Execution mode.
|
||||
.RE
|
||||
.SH EXAMPLES
|
||||
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"
|
||||
.sp
|
||||
\fBpvattest\fR(1)
|
||||
@@ -0,0 +1,114 @@
|
||||
.\" Copyright 2024 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 "2024-05-15" "s390-tools" "Attestation Manual"
|
||||
.nh
|
||||
.ad l
|
||||
.SH NAME
|
||||
\fBpvattest\fP - create, perform, and verify attestation measurements
|
||||
\fB
|
||||
.SH SYNOPSIS
|
||||
.nf
|
||||
.fam C
|
||||
pvattest [OPTIONS] <COMMAND>
|
||||
.fam C
|
||||
.fi
|
||||
.SH DESCRIPTION
|
||||
Create, perform, and verify attestation measurements for IBM Secure Execution
|
||||
guest systems.
|
||||
.SH "PVATTEST COMMANDS"
|
||||
.PP
|
||||
|
||||
\fBcreate\fR
|
||||
.RS 4
|
||||
Create an attestation measurement request
|
||||
.RE
|
||||
|
||||
.PP
|
||||
|
||||
\fBperform\fR
|
||||
.RS 4
|
||||
Send the attestation request to the Ultravisor
|
||||
.RE
|
||||
|
||||
.PP
|
||||
|
||||
\fBverify\fR
|
||||
.RS 4
|
||||
Verify an attestation response
|
||||
.RE
|
||||
|
||||
.PP
|
||||
|
||||
\fBversion\fR
|
||||
.RS 4
|
||||
Print version information and exit
|
||||
.RE
|
||||
|
||||
.SH OPTIONS
|
||||
.PP
|
||||
\-v, \-\-verbose
|
||||
.RS 4
|
||||
Provide more detailed output.
|
||||
.RE
|
||||
.RE
|
||||
.PP
|
||||
\-\-version
|
||||
.RS 4
|
||||
Print version information and exit.
|
||||
.RE
|
||||
.RE
|
||||
.PP
|
||||
\-h, \-\-help
|
||||
.RS 4
|
||||
Print help (see a summary with '-h').
|
||||
.RE
|
||||
.RE
|
||||
|
||||
.SH EXAMPLES
|
||||
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 attreq.bin 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"
|
||||
.sp
|
||||
\fBpvattest-create\fR(1) \fBpvattest-perform\fR(1) \fBpvattest-verify\fR(1)
|
||||
@@ -0,0 +1,257 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
use clap::{Args, Parser, Subcommand, ValueEnum, ValueHint};
|
||||
use log::warn;
|
||||
use utils::CertificateOptions;
|
||||
|
||||
/// create, perform, and verify attestation measurements
|
||||
///
|
||||
/// Create, perform, and verify attestation measurements for IBM Secure Execution guest systems.
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct CliOptions {
|
||||
/// Provide more detailed output
|
||||
#[arg(short='v', long, action = clap::ArgAction::Count)]
|
||||
verbose: u8,
|
||||
|
||||
/// Deprecated short verbose flag (-V) form the C implementation.
|
||||
///
|
||||
/// If specified a deprecation warning is emitted,
|
||||
#[arg(short = 'V', hide = true, action = clap::ArgAction::Count)]
|
||||
verbose_deprecated: u8,
|
||||
|
||||
/// Print version information and exit
|
||||
#[arg(long)]
|
||||
pub version: bool,
|
||||
|
||||
#[command(subcommand)]
|
||||
pub cmd: Command,
|
||||
}
|
||||
|
||||
impl CliOptions {
|
||||
pub fn verbosity(&self) -> u8 {
|
||||
let verbose_deprecated = self.verbose_deprecated
|
||||
+ match &self.cmd {
|
||||
Command::Create(cmd) => cmd.verbose_deprecated,
|
||||
Command::Perform(cmd) => cmd.verbose_deprecated,
|
||||
Command::Verify(cmd) => cmd.verbose_deprecated,
|
||||
Command::Version => 0,
|
||||
};
|
||||
if verbose_deprecated > 0 {
|
||||
warn!("WARNING: Use of deprecated flag '-V'. Use '-v' or '--verbose' instead.")
|
||||
}
|
||||
verbose_deprecated
|
||||
+ self.verbose
|
||||
+ match &self.cmd {
|
||||
Command::Create(cmd) => cmd.verbose,
|
||||
Command::Perform(cmd) => cmd.verbose,
|
||||
Command::Verify(cmd) => cmd.verbose,
|
||||
Command::Version => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub enum Command {
|
||||
/// Create an attestation measurement request
|
||||
///
|
||||
/// Create attestation measurement requests to attest an IBM Secure Execution guest. Only build
|
||||
/// attestation requests in a trusted environment such as your Workstation. To avoid
|
||||
/// compromising the attestation do not publish the attestation request protection key and
|
||||
/// shred it after verification. Every 'create' will generate a new, random protection key.
|
||||
Create(Box<CreateAttOpt>),
|
||||
|
||||
/// Send the attestation request to the Ultravisor
|
||||
///
|
||||
/// Run a measurement of this system through ’/dev/uv’. This device must be accessible and the
|
||||
/// attestation Ultravisor facility must be present. The input must be an attestation request
|
||||
/// created with ’pvattest create’. Output will contain the original request and the response
|
||||
/// from the Ultravisor.
|
||||
Perform(PerformAttOpt),
|
||||
|
||||
/// Verify an attestation response
|
||||
///
|
||||
/// 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’.
|
||||
/// Shred the protection key after the verification. The header must be the IBM Secure
|
||||
/// Execution header of the image that was attested during ’pvattest perform’
|
||||
Verify(VerifyOpt),
|
||||
|
||||
/// Print version information and exit.
|
||||
#[command(aliases(["--version"]), hide(true))]
|
||||
Version,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
pub struct CreateAttOpt {
|
||||
#[command(flatten)]
|
||||
pub certificate_args: CertificateOptions,
|
||||
|
||||
/// Write the generated request to FILE.
|
||||
#[arg(short, long, value_name = "FILE", value_hint = ValueHint::FilePath,)]
|
||||
pub output: String,
|
||||
|
||||
/// Save the protection key as unencrypted GCM-AES256 key in FILE
|
||||
///
|
||||
/// Do not publish this key, otherwise your attestation is compromised.
|
||||
#[arg(long, value_name = "FILE", value_hint = ValueHint::FilePath,)]
|
||||
pub arpk: String,
|
||||
|
||||
/// Specify-additional data for the request.
|
||||
///
|
||||
/// Additional data is provided by the Ultravisor and returned during the attestation request
|
||||
/// and is covered by the attestation measurement. Can be specified multiple times.
|
||||
/// Optional.
|
||||
#[arg(long, value_name = "FLAGS")]
|
||||
pub add_data: Vec<AttAddFlags>,
|
||||
|
||||
/// Provide more detailed output.
|
||||
#[arg(short='v', long, action = clap::ArgAction::Count)]
|
||||
verbose: u8,
|
||||
|
||||
/// Deprecated short verbose flag (-V) form the C implementation.
|
||||
///
|
||||
/// If specified a deprecation warning is emitted,
|
||||
#[arg(short = 'V', hide = true, action = clap::ArgAction::Count)]
|
||||
verbose_deprecated: u8,
|
||||
}
|
||||
|
||||
#[derive(Debug, ValueEnum, Clone, Copy)]
|
||||
pub enum AttAddFlags {
|
||||
/// Request the public host-key-hash of the key that decrypted the SE-image as additional-data
|
||||
PhkhImg,
|
||||
/// Request the public host-key-hash of the key that decrypted the attestation request as
|
||||
/// additional-data
|
||||
PhkhAtt,
|
||||
}
|
||||
|
||||
// all members s390x only
|
||||
#[derive(Args, Debug)]
|
||||
pub struct PerformAttOpt {
|
||||
/// Specify the request to be sent.
|
||||
#[cfg(target_arch = "s390x")]
|
||||
#[arg(hide=true, short, long, value_name = "FILE", value_hint = ValueHint::FilePath,)]
|
||||
pub input: Option<String>,
|
||||
|
||||
/// Specify the request to be sent.
|
||||
#[cfg(target_arch = "s390x")]
|
||||
#[arg(value_name = "INPUT", value_hint = ValueHint::FilePath,required_unless_present("input"), conflicts_with("input"))]
|
||||
pub input_pos: Option<String>,
|
||||
|
||||
/// Write the result to FILE.
|
||||
#[cfg(target_arch = "s390x")]
|
||||
#[arg(hide=true, short, long, value_name = "FILE", value_hint = ValueHint::FilePath,)]
|
||||
pub output: Option<String>,
|
||||
|
||||
/// Write the result to FILE.
|
||||
#[arg( value_name = "OUTPUT", value_hint = ValueHint::FilePath,required_unless_present("output"), conflicts_with("output"))]
|
||||
#[cfg(target_arch = "s390x")]
|
||||
pub output_pos: Option<String>,
|
||||
|
||||
/// Provide up to 256 bytes of user input
|
||||
///
|
||||
/// User-data is arbitrary user-defined data appended to the Attestation measurement.
|
||||
/// It is verified during the Attestation measurement verification.
|
||||
/// May be any arbitrary data, as long as it is less or equal to 256 bytes
|
||||
#[arg(short, long, value_name = "File", value_hint = ValueHint::FilePath,)]
|
||||
pub user_data: Option<String>,
|
||||
|
||||
/// Provide more detailed output.
|
||||
#[arg(short='v', long, action = clap::ArgAction::Count)]
|
||||
verbose: u8,
|
||||
|
||||
/// Deprecated short verbose flag (-V) form the C implementation.
|
||||
///
|
||||
/// If specified a deprecation warning is emitted,
|
||||
#[arg(short = 'V', hide = true, action = clap::ArgAction::Count)]
|
||||
verbose_deprecated: u8,
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "s390x")]
|
||||
pub struct PerformAttOptComb<'a> {
|
||||
pub input: &'a str,
|
||||
pub output: &'a str,
|
||||
pub user_data: Option<&'a str>,
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "s390x")]
|
||||
impl<'a> From<&'a PerformAttOpt> for PerformAttOptComb<'a> {
|
||||
fn from(value: &'a PerformAttOpt) -> Self {
|
||||
let input = match (&value.input, &value.input_pos) {
|
||||
(None, Some(i)) => i,
|
||||
(Some(i), None) => i,
|
||||
(Some(_), Some(_)) => unreachable!(),
|
||||
(None, None) => unreachable!(),
|
||||
};
|
||||
let output = match (&value.output, &value.output_pos) {
|
||||
(None, Some(o)) => o,
|
||||
(Some(o), None) => o,
|
||||
(Some(_), Some(_)) => unreachable!(),
|
||||
(None, None) => unreachable!(),
|
||||
};
|
||||
let user_data = value.user_data.as_deref();
|
||||
Self {
|
||||
input,
|
||||
output,
|
||||
user_data,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
pub struct VerifyOpt {
|
||||
/// Specify the attestation request to be verified.
|
||||
#[arg(short, long, value_name = "FILE", value_hint = ValueHint::FilePath,)]
|
||||
pub input: String,
|
||||
|
||||
/// Specify the output for the verification result
|
||||
#[arg(short, long, value_name = "FILE", value_hint = ValueHint::FilePath,)]
|
||||
pub output: Option<String>,
|
||||
|
||||
/// Specifies the header of the guest image.
|
||||
///
|
||||
/// Can be an IBM Secure Execution image created by genprotimg or an extracted IBM Secure
|
||||
/// Execution header. The header must start at a page boundary.
|
||||
#[arg(long, value_name = "FILE", value_hint = ValueHint::FilePath)]
|
||||
pub hdr: String,
|
||||
|
||||
/// Use FILE as the protection key to decrypt the request
|
||||
///
|
||||
/// Do not publish this key, otherwise your attestation is compromised.
|
||||
/// Delete this key after verification.
|
||||
#[arg(long, value_name = "FILE", value_hint = ValueHint::FilePath,)]
|
||||
pub arpk: String,
|
||||
|
||||
/// Define the output format.
|
||||
#[arg(long, value_enum, default_value_t)]
|
||||
pub format: VerifyOutputType,
|
||||
|
||||
/// Write the user data to the FILE if any.
|
||||
///
|
||||
/// Writes the user data, if the response contains any, to FILE
|
||||
/// The user-data is part of the attestation measurement. If the user-data is written to FILE
|
||||
/// the user-data was part of the measurement and verified.
|
||||
/// Emits a warning if the response contains no user-data
|
||||
#[arg(long, short ,value_name = "FILE", value_hint = ValueHint::FilePath,)]
|
||||
pub user_data: Option<String>,
|
||||
|
||||
/// Provide more detailed output.
|
||||
#[arg(short='v', long, action = clap::ArgAction::Count)]
|
||||
verbose: u8,
|
||||
|
||||
/// Deprecated short verbose flag (-V) form the C implementation.
|
||||
///
|
||||
/// If specified a deprecation warning is emitted,
|
||||
#[arg(short = 'V', hide = true, action = clap::ArgAction::Count)]
|
||||
verbose_deprecated: u8,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug, Default)]
|
||||
pub enum VerifyOutputType {
|
||||
/// Use yaml format.
|
||||
#[default]
|
||||
Yaml,
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2024
|
||||
//
|
||||
pub mod create;
|
||||
#[cfg(target_arch = "s390x")]
|
||||
pub mod perform;
|
||||
pub mod verify;
|
||||
|
||||
pub use create::create;
|
||||
pub use verify::verify;
|
||||
|
||||
pub const CMD_FN: &[&str] = &["+create", "+verify"];
|
||||
// s390 branch
|
||||
#[cfg(target_arch = "s390x")]
|
||||
mod uv_cmd {
|
||||
pub use super::perform::perform;
|
||||
pub const UV_CMD_FN: &[&str] = &["+perform"];
|
||||
}
|
||||
|
||||
// non s390-branch
|
||||
#[cfg(not(target_arch = "s390x"))]
|
||||
mod uv_cmd {
|
||||
use std::process::ExitCode;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
pub fn perform(_: &crate::cli::PerformAttOpt) -> Result<ExitCode> {
|
||||
bail!("Command only available on s390x")
|
||||
}
|
||||
pub const UV_CMD_FN: &[&str] = &[];
|
||||
}
|
||||
pub use uv_cmd::*;
|
||||
@@ -0,0 +1,67 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
use crate::{
|
||||
cli::{AttAddFlags, CreateAttOpt},
|
||||
exchange::{ExchangeFormatRequest, ExchangeFormatVersion},
|
||||
};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use log::{debug, warn};
|
||||
use pv::{
|
||||
attest::{AttestationFlags, AttestationMeasAlg, AttestationRequest, AttestationVersion},
|
||||
misc::{create_file, write_file},
|
||||
request::{ReqEncrCtx, Request, SymKey, SymKeyType},
|
||||
};
|
||||
use std::process::ExitCode;
|
||||
|
||||
fn flags(cli_flags: &[AttAddFlags]) -> AttestationFlags {
|
||||
let mut att_flags = AttestationFlags::default();
|
||||
for flag in cli_flags {
|
||||
match flag {
|
||||
AttAddFlags::PhkhImg => att_flags.set_image_phkh(),
|
||||
AttAddFlags::PhkhAtt => att_flags.set_attest_phkh(),
|
||||
}
|
||||
}
|
||||
att_flags
|
||||
}
|
||||
|
||||
pub fn create(opt: &CreateAttOpt) -> Result<ExitCode> {
|
||||
let att_version = AttestationVersion::One;
|
||||
let meas_alg = AttestationMeasAlg::HmacSha512;
|
||||
|
||||
let mut arcb = AttestationRequest::new(att_version, meas_alg, flags(&opt.add_data))?;
|
||||
debug!("Generated Attestation request");
|
||||
|
||||
// Add host-key documents
|
||||
opt.certificate_args
|
||||
.get_verified_hkds("attestation request")?
|
||||
.into_iter()
|
||||
.for_each(|k| arcb.add_hostkey(k));
|
||||
debug!("Added all host-keys");
|
||||
|
||||
let encr_ctx =
|
||||
ReqEncrCtx::random(SymKeyType::Aes256).context("Failed to generate random input")?;
|
||||
let ser_arcb = arcb.encrypt(&encr_ctx)?;
|
||||
warn!("Successfully generated the request");
|
||||
|
||||
let mut output = create_file(&opt.output)?;
|
||||
let exch_ctx = ExchangeFormatRequest::new(
|
||||
ser_arcb,
|
||||
meas_alg.exp_size(),
|
||||
arcb.flags().expected_additional_size(),
|
||||
)?;
|
||||
exch_ctx.write(&mut output, ExchangeFormatVersion::One)?;
|
||||
|
||||
let arpk = match encr_ctx.prot_key() {
|
||||
SymKey::Aes256(k) => k,
|
||||
_ => bail!("Unexpected key type"),
|
||||
};
|
||||
write_file(
|
||||
&opt.arpk,
|
||||
arpk.value(),
|
||||
"Attestation request Protection Key",
|
||||
)?;
|
||||
|
||||
Ok(ExitCode::SUCCESS)
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
use crate::{
|
||||
cli::PerformAttOptComb,
|
||||
exchange::{ExchangeFormatRequest, ExchangeFormatResponse, ExchangeFormatVersion},
|
||||
};
|
||||
use anyhow::Result;
|
||||
use pv::{
|
||||
misc::{create_file, open_file, read_file},
|
||||
uv::{AttestationCmd, UvDevice},
|
||||
};
|
||||
use std::process::ExitCode;
|
||||
|
||||
pub fn perform<'a, P>(opt: P) -> Result<ExitCode>
|
||||
where
|
||||
P: Into<PerformAttOptComb<'a>>,
|
||||
{
|
||||
let opt = opt.into();
|
||||
let mut input = open_file(opt.input)?;
|
||||
let mut output = create_file(opt.output)?;
|
||||
let uvdevice = UvDevice::open()?;
|
||||
|
||||
let ex_in = ExchangeFormatRequest::read(&mut input)?;
|
||||
let user_data = opt
|
||||
.user_data
|
||||
.map(|u| read_file(u, "user-data"))
|
||||
.transpose()?;
|
||||
|
||||
let mut cmd = AttestationCmd::new_request(
|
||||
ex_in.arcb.clone().into(),
|
||||
user_data.clone(),
|
||||
ex_in.exp_measurement,
|
||||
ex_in.exp_additional,
|
||||
)?;
|
||||
|
||||
uvdevice.send_cmd(&mut cmd)?;
|
||||
|
||||
let measurement = cmd.measurement();
|
||||
let additional = cmd.additional_owned();
|
||||
let cuid = cmd.cuid();
|
||||
|
||||
let ex_out = ExchangeFormatResponse::new(
|
||||
ex_in.arcb,
|
||||
measurement.to_owned(),
|
||||
additional,
|
||||
user_data,
|
||||
cuid.to_owned(),
|
||||
)?;
|
||||
ex_out.write(&mut output, ExchangeFormatVersion::One)?;
|
||||
|
||||
Ok(ExitCode::SUCCESS)
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
use anyhow::Result;
|
||||
use log::{debug, warn};
|
||||
use pv::{
|
||||
attest::{
|
||||
AdditionalData, AttestationFlags, AttestationItems, AttestationMeasurement,
|
||||
AttestationRequest,
|
||||
},
|
||||
misc::{create_file, open_file, read_exact_file, write_file},
|
||||
request::{openssl::pkey::PKey, BootHdrTags, Confidential, SymKey},
|
||||
};
|
||||
use serde::Serialize;
|
||||
use std::{fmt::Display, process::ExitCode};
|
||||
use utils::HexSlice;
|
||||
|
||||
use crate::{
|
||||
cli::{VerifyOpt, VerifyOutputType},
|
||||
exchange::ExchangeFormatResponse,
|
||||
EXIT_CODE_ATTESTATION_FAIL,
|
||||
};
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct VerifyOutput<'a> {
|
||||
cuid: HexSlice<'a>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
add: Option<HexSlice<'a>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
add_fields: Option<AdditionalData<HexSlice<'a>>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
user_data: Option<HexSlice<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> VerifyOutput<'a> {
|
||||
fn from_exchange(resp: &'a ExchangeFormatResponse, flags: &AttestationFlags) -> Result<Self> {
|
||||
let additional_data_fields = resp
|
||||
.additional()
|
||||
.map(|a| AdditionalData::from_slice(a, flags))
|
||||
.transpose()?;
|
||||
let user_data = resp.user().map(|u| u.into());
|
||||
|
||||
Ok(Self {
|
||||
cuid: resp.config_uid().into(),
|
||||
add: resp.additional().map(|a| a.into()),
|
||||
add_fields: additional_data_fields.map(AdditionalData::from_other),
|
||||
user_data,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for VerifyOutput<'a> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(f, "Config UID:")?;
|
||||
writeln!(f, "{:#}", self.cuid)?;
|
||||
if let Some(data) = &self.add {
|
||||
writeln!(f, "Additional-data:")?;
|
||||
writeln!(f, "{:#}", data)?;
|
||||
}
|
||||
if let Some(data) = &self.add_fields {
|
||||
writeln!(f, "Additional-data content:")?;
|
||||
writeln!(f, "{:#}", data)?;
|
||||
}
|
||||
if let Some(data) = &self.user_data {
|
||||
writeln!(f, "user-data:")?;
|
||||
writeln!(f, "{:#}", data)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify(opt: &VerifyOpt) -> Result<ExitCode> {
|
||||
let mut input = open_file(&opt.input)?;
|
||||
let mut img = open_file(&opt.hdr)?;
|
||||
let output = opt.output.as_ref().map(create_file).transpose()?;
|
||||
let arpk = SymKey::Aes256(
|
||||
read_exact_file(&opt.arpk, "Attestation request protection key").map(Confidential::new)?,
|
||||
);
|
||||
let tags = BootHdrTags::from_se_image(&mut img)?;
|
||||
let exchange = ExchangeFormatResponse::read(&mut input)?;
|
||||
|
||||
let (auth, conf) = AttestationRequest::decrypt_bin(exchange.arcb(), &arpk)?;
|
||||
let meas_key = PKey::hmac(conf.measurement_key())?;
|
||||
let items = AttestationItems::new(
|
||||
&tags,
|
||||
exchange.config_uid(),
|
||||
exchange.user(),
|
||||
conf.nonce().as_ref().map(|v| v.value()),
|
||||
exchange.additional(),
|
||||
);
|
||||
|
||||
let measurement = AttestationMeasurement::calculate(items, auth.mai(), &meas_key)?;
|
||||
|
||||
let uv_meas = exchange.measurement();
|
||||
if !measurement.eq_secure(uv_meas) {
|
||||
debug!("Measurement values:");
|
||||
debug!("Recieved: {}", HexSlice::from(uv_meas));
|
||||
debug!("Calculated: {}", HexSlice::from(measurement.as_ref()));
|
||||
warn!("Attestation measurement verification failed. Calculated and received attestation measurement are not equal.");
|
||||
return Ok(ExitCode::from(EXIT_CODE_ATTESTATION_FAIL));
|
||||
}
|
||||
warn!("Attestation measurement verified");
|
||||
// Error impossible CUID is present Attestation verified
|
||||
let pr_data = VerifyOutput::from_exchange(&exchange, auth.flags())?;
|
||||
|
||||
warn!("{pr_data}");
|
||||
if let Some(mut output) = output {
|
||||
match opt.format {
|
||||
VerifyOutputType::Yaml => serde_yaml::to_writer(&mut output, &pr_data)?,
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(user_data) = &opt.user_data {
|
||||
match exchange.user() {
|
||||
Some(data) => write_file(user_data, data, "user-data")?,
|
||||
None => {
|
||||
warn!("Location for `user-data` specified, but respose does not contain any user-data")
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(ExitCode::SUCCESS)
|
||||
}
|
||||
@@ -0,0 +1,884 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2024
|
||||
use anyhow::{anyhow, bail, Error, Result};
|
||||
use byteorder::ByteOrder;
|
||||
use pv::{assert_size, request::MagicValue, uv::AttestationCmd, uv::ConfigUid};
|
||||
use std::{
|
||||
io::{ErrorKind, Read, Seek, SeekFrom, Write},
|
||||
mem::size_of,
|
||||
};
|
||||
use zerocopy::{AsBytes, BigEndian, FromBytes, FromZeroes, U32, U64};
|
||||
|
||||
const INV_EXCHANGE_FMT_ERROR_TEXT: &str = "The input has not the correct format:";
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, AsBytes, PartialEq, Eq, Default, FromZeroes, FromBytes)]
|
||||
struct Entry {
|
||||
size: U32<BigEndian>,
|
||||
offset: U32<BigEndian>,
|
||||
}
|
||||
assert_size!(Entry, 8);
|
||||
|
||||
/// If size == 0 the offset is ignored. (entry does not exist)
|
||||
/// If offset >0 and <0x40 -> 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.
|
||||
impl Entry {
|
||||
fn new(size: u32, offset: u32) -> Self {
|
||||
Self {
|
||||
size: size.into(),
|
||||
offset: offset.into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// # Panic
|
||||
///
|
||||
/// panics if `val` is larger than `max_size` bytes
|
||||
fn from_slice(val: Option<&[u8]>, max_size: u32, offset: &mut u32) -> Self {
|
||||
match val {
|
||||
Some(val) => {
|
||||
assert!(val.len() <= max_size as usize);
|
||||
let size = val.len() as u32;
|
||||
let res = Self::new(size, *offset);
|
||||
*offset += size;
|
||||
res
|
||||
}
|
||||
None => Self::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// # Panic
|
||||
///
|
||||
/// panics if `val` is larger than `max_size` bytes
|
||||
fn from_exp(val: Option<u32>) -> Self {
|
||||
if let Some(val) = val {
|
||||
Self::new(val, 0)
|
||||
} else {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn from_none() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Reads data from stream if required
|
||||
fn read<R>(&self, reader: &mut R) -> Result<ExpOrData>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
match self {
|
||||
Entry { size, .. } if size.get() == 0 => Ok(ExpOrData::None),
|
||||
Entry { size, offset } if offset.get() == 0 => Ok(ExpOrData::Exp(size.get())),
|
||||
Entry { size, offset } => {
|
||||
reader.seek(SeekFrom::Start(offset.get() as u64))?;
|
||||
let mut buf = vec![0; size.get() as usize];
|
||||
reader.read_exact(&mut buf)?;
|
||||
Ok(ExpOrData::Data(buf))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, AsBytes, FromZeroes, FromBytes)]
|
||||
struct ExchangeFormatV1Hdr {
|
||||
magic: U64<BigEndian>,
|
||||
version: U32<BigEndian>,
|
||||
size: U32<BigEndian>,
|
||||
reserved: U64<BigEndian>,
|
||||
/// v1 specific
|
||||
arcb: Entry,
|
||||
measurement: Entry,
|
||||
additional: Entry,
|
||||
user: Entry,
|
||||
config_uid: Entry,
|
||||
}
|
||||
assert_size!(ExchangeFormatV1Hdr, 0x40);
|
||||
|
||||
impl ExchangeFormatV1Hdr {
|
||||
fn new_request(arcb: &[u8], measurement: u32, additional: u32) -> Result<Self> {
|
||||
let mut offset: u32 = size_of::<ExchangeFormatV1Hdr>() as u32;
|
||||
let arcb_entry = Entry::from_slice(Some(arcb), AttestationCmd::ARCB_MAX_SIZE, &mut offset);
|
||||
let measurement_entry = Entry::from_exp(Some(measurement));
|
||||
let exp_add = match additional {
|
||||
0 => None,
|
||||
size => Some(size),
|
||||
};
|
||||
// TODO min and max size check?
|
||||
let additional_entry = Entry::from_exp(exp_add); //, AttestationCmd::ADDITIONAL_MAX_SIZE, &mut offset);
|
||||
let user_entry = Entry::from_none();
|
||||
let cuid_entry = Entry::from_none();
|
||||
|
||||
Ok(Self {
|
||||
magic: U64::from_bytes(ExchangeMagic::MAGIC),
|
||||
version: ExchangeFormatVersion::One.into(),
|
||||
size: offset.into(),
|
||||
reserved: 0.into(),
|
||||
arcb: arcb_entry,
|
||||
measurement: measurement_entry,
|
||||
additional: additional_entry,
|
||||
user: user_entry,
|
||||
config_uid: cuid_entry,
|
||||
})
|
||||
}
|
||||
|
||||
fn new_response(
|
||||
arcb: &[u8],
|
||||
measurement: &[u8],
|
||||
additional: Option<&[u8]>,
|
||||
user: Option<&[u8]>,
|
||||
config_uid: &[u8],
|
||||
) -> Result<Self> {
|
||||
let mut offset: u32 = size_of::<ExchangeFormatV1Hdr>() as u32;
|
||||
let arcb_entry = Entry::from_slice(Some(arcb), AttestationCmd::ARCB_MAX_SIZE, &mut offset);
|
||||
let measurement_entry = Entry::from_slice(
|
||||
Some(measurement),
|
||||
AttestationCmd::MEASUREMENT_MAX_SIZE,
|
||||
&mut offset,
|
||||
);
|
||||
let additional_entry =
|
||||
Entry::from_slice(additional, AttestationCmd::ADDITIONAL_MAX_SIZE, &mut offset);
|
||||
let user_entry = Entry::from_slice(user, AttestationCmd::USER_MAX_SIZE, &mut offset);
|
||||
let cuid_entry = Entry::from_slice(Some(config_uid), 0x10, &mut offset);
|
||||
|
||||
Ok(Self {
|
||||
magic: U64::from_bytes(ExchangeMagic::MAGIC),
|
||||
version: ExchangeFormatVersion::One.into(),
|
||||
size: offset.into(),
|
||||
reserved: 0.into(),
|
||||
arcb: arcb_entry,
|
||||
measurement: measurement_entry,
|
||||
additional: additional_entry,
|
||||
user: user_entry,
|
||||
config_uid: cuid_entry,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// The magic value used to identify an [`ExchangeFormatRequest`]
|
||||
///
|
||||
/// The magic value is ASCII:
|
||||
/// ```rust
|
||||
/// # use s390_pv_core::attest::ExchangeMagic;
|
||||
/// # use s390_pv_core::request::MagicValue;
|
||||
/// # fn main() {
|
||||
/// # let magic =
|
||||
/// b"pvattest"
|
||||
/// # ;
|
||||
/// # assert!(ExchangeMagic::starts_with_magic(magic));
|
||||
/// # }
|
||||
/// ```
|
||||
pub struct ExchangeMagic;
|
||||
impl MagicValue<8> for ExchangeMagic {
|
||||
const MAGIC: [u8; 8] = [0x70, 0x76, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74];
|
||||
}
|
||||
|
||||
/// Version identifier for an [`ExchangeFormatRequest`]
|
||||
#[repr(u32)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ExchangeFormatVersion {
|
||||
/// Version 1 (= 0x0100)
|
||||
One = 0x0100,
|
||||
}
|
||||
|
||||
impl<E: ByteOrder> TryFrom<U32<E>> for ExchangeFormatVersion {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: U32<E>) -> Result<Self, Self::Error> {
|
||||
if value.get() == Self::One as u32 {
|
||||
Ok(Self::One)
|
||||
} else {
|
||||
bail!(
|
||||
"{INV_EXCHANGE_FMT_ERROR_TEXT} Unsupported version: ({})",
|
||||
value.get()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: ByteOrder> From<ExchangeFormatVersion> for U32<E> {
|
||||
fn from(value: ExchangeFormatVersion) -> Self {
|
||||
(value as u32).into()
|
||||
}
|
||||
}
|
||||
|
||||
/// A parsed exchange entry value
|
||||
///
|
||||
/// An entry can be all zero(None), just a size (Exp) or a offset+size to some data (Data)
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub(crate) enum ExpOrData {
|
||||
Exp(u32),
|
||||
Data(Vec<u8>),
|
||||
None,
|
||||
}
|
||||
|
||||
impl ExpOrData {
|
||||
/// calculates the (expected or real) size
|
||||
fn size(&self) -> u32 {
|
||||
match self {
|
||||
ExpOrData::Exp(s) => *s,
|
||||
// size is max u32 large as read in before
|
||||
ExpOrData::Data(v) => v.len() as u32,
|
||||
ExpOrData::None => 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns data if self is [`ExpOrData::Data`]
|
||||
///
|
||||
/// Consumes itself
|
||||
fn data(self) -> Option<Vec<u8>> {
|
||||
match self {
|
||||
ExpOrData::Data(v) => Some(v),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Option<u32>> for ExpOrData {
|
||||
fn from(value: Option<u32>) -> Self {
|
||||
match value {
|
||||
Some(v) => ExpOrData::Exp(v),
|
||||
None => ExpOrData::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ExpOrData> for Option<u32> {
|
||||
fn from(value: &ExpOrData) -> Self {
|
||||
match value {
|
||||
ExpOrData::Exp(v) => Some(*v),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ExpOrData> for Option<Vec<u8>> {
|
||||
fn from(value: ExpOrData) -> Self {
|
||||
match value {
|
||||
ExpOrData::Exp(s) => Some(vec![0; s as usize]),
|
||||
ExpOrData::Data(d) => Some(d),
|
||||
ExpOrData::None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The _exchange format_ is a simple file format to send labeled binary blobs between
|
||||
/// pvattest instances on different machines.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct ExchangeFormatRequest {
|
||||
// all sizes are guaranteed to fit in the exchange format/UV Call at any time
|
||||
// pub to allow deconstruction of this struct
|
||||
pub arcb: Vec<u8>,
|
||||
pub exp_measurement: u32,
|
||||
pub exp_additional: u32,
|
||||
}
|
||||
|
||||
/// The _exchange format_ is a simple file format to send labeled binary blobs between
|
||||
/// pvattest instances on different machines.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct ExchangeFormatResponse {
|
||||
// all sizes are guaranteed to fit in the exchange format/UV Call at any time
|
||||
// pub to allow deconstruction of this struct
|
||||
pub arcb: Vec<u8>,
|
||||
pub measurement: Vec<u8>,
|
||||
pub additional: Option<Vec<u8>>,
|
||||
pub user: Option<Vec<u8>>,
|
||||
pub config_uid: ConfigUid,
|
||||
}
|
||||
|
||||
impl ExchangeFormatRequest {
|
||||
/// Creates a new exchange context, with an attestation request, expected measurement and
|
||||
/// optional an additional data size. Useful for creating a attestation request.
|
||||
pub fn new(arcb: Vec<u8>, exp_measurement: u32, exp_additional: u32) -> Result<Self> {
|
||||
verify_size(
|
||||
exp_measurement,
|
||||
1,
|
||||
AttestationCmd::MEASUREMENT_MAX_SIZE,
|
||||
"Expected measurement size",
|
||||
)?;
|
||||
verify_size(
|
||||
exp_additional,
|
||||
0,
|
||||
AttestationCmd::ADDITIONAL_MAX_SIZE,
|
||||
"Expected additional data size",
|
||||
)?;
|
||||
verify_slice(&arcb, AttestationCmd::ARCB_MAX_SIZE, "Attestation request")?;
|
||||
|
||||
Ok(Self {
|
||||
arcb,
|
||||
exp_measurement,
|
||||
exp_additional,
|
||||
})
|
||||
}
|
||||
|
||||
fn write_v1<W>(&self, writer: &mut W) -> Result<()>
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
let hdr = ExchangeFormatV1Hdr::new_request(
|
||||
self.arcb.as_slice(),
|
||||
self.exp_measurement,
|
||||
self.exp_additional,
|
||||
)?;
|
||||
writer.write_all(hdr.as_bytes())?;
|
||||
writer.write_all(&self.arcb)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Serializes the encapsulated data into the provides stream in the provided format
|
||||
pub fn write<W>(&self, writer: &mut W, version: ExchangeFormatVersion) -> Result<()>
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
match version {
|
||||
ExchangeFormatVersion::One => self.write_v1(writer),
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads and deserializes the exchange file in the provided stream
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the stream does not contain data in exchange format, CUID or user data
|
||||
/// do not fit, or any IO error that can appear during reading streams.
|
||||
pub fn read<R>(reader: &mut R) -> Result<Self>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
let mut buf = vec![0; size_of::<ExchangeFormatV1Hdr>()];
|
||||
match reader.read_exact(&mut buf) {
|
||||
Ok(it) => it,
|
||||
// report hdr file to small for header
|
||||
Err(err) if err.kind() == ErrorKind::UnexpectedEof => {
|
||||
bail!("{INV_EXCHANGE_FMT_ERROR_TEXT} Invalid Header.");
|
||||
}
|
||||
Err(err) => return Err(err.into()),
|
||||
};
|
||||
|
||||
if !ExchangeMagic::starts_with_magic(&buf) {
|
||||
bail!("{INV_EXCHANGE_FMT_ERROR_TEXT} Does not start with the magic value.",);
|
||||
}
|
||||
|
||||
let hdr = ExchangeFormatV1Hdr::ref_from(buf.as_slice())
|
||||
.ok_or(anyhow!("{INV_EXCHANGE_FMT_ERROR_TEXT} Invalid Header."))?;
|
||||
|
||||
match TryInto::<ExchangeFormatVersion>::try_into(hdr.version)? {
|
||||
ExchangeFormatVersion::One => (),
|
||||
}
|
||||
|
||||
if stream_len(reader)? < hdr.size.get() as u64 {
|
||||
bail!("{INV_EXCHANGE_FMT_ERROR_TEXT} File size too small");
|
||||
}
|
||||
let arcb = hdr.arcb.read(reader)?.data().ok_or(anyhow!(
|
||||
"{INV_EXCHANGE_FMT_ERROR_TEXT} Contains no attestation request.",
|
||||
))?;
|
||||
|
||||
let measurement = hdr.measurement.read(reader)?.size();
|
||||
let additional = hdr.additional.read(reader)?.size();
|
||||
Self::new(arcb, measurement, additional)
|
||||
}
|
||||
}
|
||||
|
||||
// Seek::stream_is unstable
|
||||
// not expose to API users
|
||||
// taken from rust std::io::seek;
|
||||
fn stream_len<S>(seek: &mut S) -> Result<u64>
|
||||
where
|
||||
S: Seek,
|
||||
{
|
||||
let old_pos = seek.stream_position()?;
|
||||
let len = seek.seek(SeekFrom::End(0))?;
|
||||
|
||||
// Avoid seeking a third time when we were already at the end of the
|
||||
// stream. The branch is usually way cheaper than a seek operation.
|
||||
if old_pos != len {
|
||||
seek.seek(SeekFrom::Start(old_pos))?;
|
||||
}
|
||||
|
||||
Ok(len)
|
||||
}
|
||||
|
||||
fn verify_size(size: u32, min_size: u32, max_size: u32, field: &'static str) -> Result<()> {
|
||||
if size < min_size {
|
||||
bail!("{INV_EXCHANGE_FMT_ERROR_TEXT} The {field} field is too small ({size})");
|
||||
}
|
||||
|
||||
if size > max_size {
|
||||
bail!("{INV_EXCHANGE_FMT_ERROR_TEXT} The {field} field is too large ({size})");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// check that a slice has at max `max_size` amount of bytes
|
||||
fn verify_slice(val: &[u8], max_size: u32, field: &'static str) -> Result<()> {
|
||||
if val.len() > max_size as usize {
|
||||
bail!(
|
||||
"{INV_EXCHANGE_FMT_ERROR_TEXT} The {field} field is too large ({})",
|
||||
val.len()
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl ExchangeFormatResponse {
|
||||
/// Creates a new exchange context, with an attestation request, measurement and
|
||||
/// cuid.
|
||||
pub fn new(
|
||||
arcb: Vec<u8>,
|
||||
measurement: Vec<u8>,
|
||||
additional: Option<Vec<u8>>,
|
||||
user: Option<Vec<u8>>,
|
||||
config_uid: ConfigUid,
|
||||
) -> Result<Self> {
|
||||
// should not fail; Already checked during import.
|
||||
verify_slice(
|
||||
&arcb,
|
||||
AttestationCmd::ARCB_MAX_SIZE,
|
||||
"Attestation request data",
|
||||
)?;
|
||||
verify_slice(
|
||||
&measurement,
|
||||
AttestationCmd::MEASUREMENT_MAX_SIZE,
|
||||
"Attestation Measurement",
|
||||
)?;
|
||||
|
||||
if let Some(additional) = &additional {
|
||||
verify_slice(
|
||||
additional,
|
||||
AttestationCmd::ADDITIONAL_MAX_SIZE,
|
||||
"Additional data",
|
||||
)?;
|
||||
}
|
||||
|
||||
if let Some(user) = &user {
|
||||
verify_slice(user, AttestationCmd::USER_MAX_SIZE, "User data")?;
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
arcb,
|
||||
measurement,
|
||||
additional,
|
||||
user,
|
||||
config_uid,
|
||||
})
|
||||
}
|
||||
|
||||
fn write_v1<W>(&self, writer: &mut W) -> Result<()>
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
let hdr = ExchangeFormatV1Hdr::new_response(
|
||||
self.arcb.as_slice(),
|
||||
&self.measurement,
|
||||
self.additional.as_deref(),
|
||||
self.user.as_deref(),
|
||||
&self.config_uid,
|
||||
)?;
|
||||
writer.write_all(hdr.as_bytes())?;
|
||||
writer.write_all(&self.arcb)?;
|
||||
writer.write_all(&self.measurement)?;
|
||||
if let Some(data) = &self.additional {
|
||||
writer.write_all(data)?;
|
||||
}
|
||||
if let Some(data) = &self.user {
|
||||
writer.write_all(data)?;
|
||||
}
|
||||
writer.write_all(&self.config_uid)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Serializes the encapsulated data into the provides stream in the provided format
|
||||
pub fn write<W>(&self, writer: &mut W, version: ExchangeFormatVersion) -> Result<()>
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
match version {
|
||||
ExchangeFormatVersion::One => self.write_v1(writer),
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads and deserializes the exchange file in the provided stream
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the stream does not contain data in exchange format, CUID or user data
|
||||
/// do not fit, or any IO error that can appear during reading streams.
|
||||
pub fn read<R>(reader: &mut R) -> Result<Self>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
let mut buf = vec![0; size_of::<ExchangeFormatV1Hdr>()];
|
||||
match reader.read_exact(&mut buf) {
|
||||
Ok(it) => it,
|
||||
// report hdr file to small for header
|
||||
Err(err) if err.kind() == ErrorKind::UnexpectedEof => {
|
||||
bail!("{INV_EXCHANGE_FMT_ERROR_TEXT} Invalid Header.");
|
||||
}
|
||||
Err(err) => return Err(err.into()),
|
||||
};
|
||||
|
||||
if !ExchangeMagic::starts_with_magic(&buf) {
|
||||
bail!("{INV_EXCHANGE_FMT_ERROR_TEXT} Does not start with the magic value.");
|
||||
}
|
||||
|
||||
let hdr = ExchangeFormatV1Hdr::ref_from(buf.as_slice())
|
||||
.ok_or(anyhow!("{INV_EXCHANGE_FMT_ERROR_TEXT} Invalid Header."))?;
|
||||
|
||||
match TryInto::<ExchangeFormatVersion>::try_into(hdr.version)? {
|
||||
ExchangeFormatVersion::One => (),
|
||||
}
|
||||
|
||||
if stream_len(reader)? < hdr.size.get() as u64 {
|
||||
bail!("{INV_EXCHANGE_FMT_ERROR_TEXT} File size too small");
|
||||
}
|
||||
let arcb = hdr.arcb.read(reader)?.data().ok_or(anyhow!(
|
||||
"{INV_EXCHANGE_FMT_ERROR_TEXT} Contains no attestation request.",
|
||||
))?;
|
||||
|
||||
// TODO remove unwrap
|
||||
let measurement = hdr.measurement.read(reader)?.data().ok_or(anyhow!(
|
||||
"{INV_EXCHANGE_FMT_ERROR_TEXT} Contains no attestation response (Measurement missing).",
|
||||
))?;
|
||||
let additional = hdr.additional.read(reader)?.data();
|
||||
let user = hdr.user.read(reader)?.data();
|
||||
let config_uid: ConfigUid = match hdr.config_uid.read(reader)?.data() {
|
||||
Some(v) => v.try_into().map_err(|_| {
|
||||
anyhow!(
|
||||
"{INV_EXCHANGE_FMT_ERROR_TEXT} Configuration UID has an invalid size. Expected size 16, is {}",hdr.config_uid.size.get()
|
||||
)
|
||||
})?,
|
||||
None =>
|
||||
bail!("{INV_EXCHANGE_FMT_ERROR_TEXT} Contains no attestation response (CUID missing).")
|
||||
,
|
||||
};
|
||||
Self::new(arcb, measurement, additional, user, config_uid)
|
||||
}
|
||||
|
||||
/// Returns the measurement of this [`ExchangeFormatRequest`].
|
||||
pub fn measurement(&self) -> &[u8] {
|
||||
&self.measurement
|
||||
}
|
||||
|
||||
/// Returns the additional data of this [`ExchangeFormatRequest`].
|
||||
pub fn additional(&self) -> Option<&[u8]> {
|
||||
self.additional.as_deref()
|
||||
}
|
||||
|
||||
/// Returns the user data of this [`ExchangeFormatRequest`].
|
||||
pub fn user(&self) -> Option<&[u8]> {
|
||||
self.user.as_deref()
|
||||
}
|
||||
|
||||
/// Returns the config UID of this [`ExchangeFormatRequest`].
|
||||
///
|
||||
/// # Error
|
||||
/// Returns an error if the [`ExchangeFormatRequest`] contains no CUID,
|
||||
pub fn config_uid(&self) -> &ConfigUid {
|
||||
&self.config_uid
|
||||
}
|
||||
|
||||
/// Returns a reference to the attestation request of this [`ExchangeFormatRequest`].
|
||||
pub fn arcb(&self) -> &[u8] {
|
||||
self.arcb.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
use std::io::Cursor;
|
||||
|
||||
use super::*;
|
||||
use pv::misc::read_file;
|
||||
|
||||
#[test]
|
||||
fn exchange_from_slice() {
|
||||
let val = &[0; 17];
|
||||
let mut offset = 18;
|
||||
|
||||
let entry = Entry::from_slice(Some(val), 20, &mut offset);
|
||||
assert_eq!(
|
||||
entry,
|
||||
Entry {
|
||||
size: 17.into(),
|
||||
offset: 18.into(),
|
||||
}
|
||||
);
|
||||
assert_eq!(offset, 18 + 17);
|
||||
}
|
||||
static ARCB: [u8; 16] = [0x11; 16];
|
||||
static MEASUREMENT: [u8; 64] = [0x12; 64];
|
||||
static ADDITIONAL: [u8; 32] = [0x13; 32];
|
||||
static CUID: [u8; 16] = [0x14; 16];
|
||||
static USER: [u8; 256] = [0x15; 256];
|
||||
|
||||
fn test_read_write_request(
|
||||
path: &'static str,
|
||||
arcb: Vec<u8>,
|
||||
measurement: usize,
|
||||
additional: usize,
|
||||
) {
|
||||
// TODO as 32 checks
|
||||
let ctx_write = ExchangeFormatRequest::new(arcb, measurement as u32, additional as u32)
|
||||
.expect("exchange fmt creation");
|
||||
|
||||
// let mut out = create_file(path).unwrap();
|
||||
let mut out = vec![];
|
||||
ctx_write
|
||||
.write(&mut out, ExchangeFormatVersion::One)
|
||||
.unwrap();
|
||||
|
||||
let buf = read_file(path, "test read exchange").unwrap();
|
||||
|
||||
assert_eq!(out, buf);
|
||||
|
||||
let ctx_read = ExchangeFormatRequest::read(&mut Cursor::new(&mut &buf)).unwrap();
|
||||
|
||||
assert_eq!(ctx_read, ctx_write);
|
||||
}
|
||||
|
||||
fn test_read_write_response(
|
||||
path: &'static str,
|
||||
arcb: Vec<u8>,
|
||||
measurement: Vec<u8>,
|
||||
additional: Option<Vec<u8>>,
|
||||
user: Option<Vec<u8>>,
|
||||
cuid: ConfigUid,
|
||||
) {
|
||||
let ctx_write = ExchangeFormatResponse::new(arcb, measurement, additional, user, cuid)
|
||||
.expect("exchange fmt creation");
|
||||
|
||||
// let mut out = create_file(path).unwrap();
|
||||
|
||||
let mut out = vec![];
|
||||
ctx_write
|
||||
.write(&mut out, ExchangeFormatVersion::One)
|
||||
.unwrap();
|
||||
|
||||
let buf = read_file(path, "test read exchange").unwrap();
|
||||
|
||||
assert_eq!(out, buf);
|
||||
|
||||
let ctx_read = ExchangeFormatResponse::read(&mut Cursor::new(&mut &buf)).unwrap();
|
||||
|
||||
assert_eq!(ctx_read, ctx_write);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_req() {
|
||||
test_read_write_request(
|
||||
concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/tests/assets/",
|
||||
"exp/exchange/full_req.bin"
|
||||
),
|
||||
ARCB.to_vec(),
|
||||
MEASUREMENT.len(),
|
||||
ADDITIONAL.len(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_req() {
|
||||
test_read_write_request(
|
||||
concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/tests/assets/",
|
||||
"exp/exchange/add_req.bin"
|
||||
),
|
||||
ARCB.to_vec(),
|
||||
MEASUREMENT.len(),
|
||||
ADDITIONAL.len(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_req() {
|
||||
ExchangeFormatRequest::new(ARCB.to_vec(), 0, ADDITIONAL.len() as u32).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn min_req() {
|
||||
test_read_write_request(
|
||||
concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/tests/assets/",
|
||||
"exp/exchange/min_req.bin"
|
||||
),
|
||||
ARCB.to_vec(),
|
||||
MEASUREMENT.len(),
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_resp() {
|
||||
test_read_write_response(
|
||||
concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/tests/assets/",
|
||||
"exp/exchange/full_resp.bin"
|
||||
),
|
||||
ARCB.to_vec(),
|
||||
MEASUREMENT.to_vec(),
|
||||
ADDITIONAL.to_vec().into(),
|
||||
USER.to_vec().into(),
|
||||
CUID,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_resp() {
|
||||
test_read_write_response(
|
||||
concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/tests/assets/",
|
||||
"exp/exchange/add_resp.bin"
|
||||
),
|
||||
ARCB.to_vec(),
|
||||
MEASUREMENT.to_vec(),
|
||||
ADDITIONAL.to_vec().into(),
|
||||
None,
|
||||
CUID,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn user_resp() {
|
||||
test_read_write_response(
|
||||
concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/tests/assets/",
|
||||
"exp/exchange/user_resp.bin"
|
||||
),
|
||||
ARCB.to_vec(),
|
||||
MEASUREMENT.to_vec(),
|
||||
None,
|
||||
USER.to_vec().into(),
|
||||
CUID,
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn min_resp() {
|
||||
test_read_write_response(
|
||||
concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/tests/assets/",
|
||||
"exp/exchange/min_resp.bin"
|
||||
),
|
||||
ARCB.to_vec(),
|
||||
MEASUREMENT.to_vec(),
|
||||
None,
|
||||
None,
|
||||
CUID,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resp_no_cuid() {
|
||||
let buf = include_bytes!(concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/tests/assets/",
|
||||
"exp/exchange/min_req.bin"
|
||||
));
|
||||
let _ctx_read = ExchangeFormatResponse::read(&mut Cursor::new(&mut &buf)).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resp_inv_magic() {
|
||||
let mut buf = read_file(
|
||||
concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/tests/assets/",
|
||||
"exp/exchange/min_req.bin"
|
||||
),
|
||||
"test resp inv magic",
|
||||
)
|
||||
.unwrap();
|
||||
// tamper with the magic
|
||||
buf[0] = !buf[0];
|
||||
|
||||
let _ctx_read = ExchangeFormatResponse::read(&mut Cursor::new(&mut &buf)).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_arcb() {
|
||||
let mut buf = read_file(
|
||||
concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/tests/assets/",
|
||||
"exp/exchange/min_req.bin"
|
||||
),
|
||||
"test resp inv magic",
|
||||
)
|
||||
.unwrap();
|
||||
// delete the arcb entry
|
||||
buf[0x18..0x20].copy_from_slice(&[0; 8]);
|
||||
|
||||
let _ctx_read = ExchangeFormatRequest::read(&mut Cursor::new(&mut &buf)).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn small() {
|
||||
let mut buf = read_file(
|
||||
concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/tests/assets/",
|
||||
"exp/exchange/min_req.bin"
|
||||
),
|
||||
"test resp inv magic",
|
||||
)
|
||||
.unwrap();
|
||||
buf.pop();
|
||||
|
||||
let _ctx_read = ExchangeFormatRequest::read(&mut Cursor::new(&mut &buf)).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hdr() {
|
||||
// buffer smaller than the header but containing the magic
|
||||
let buf = [
|
||||
0x70, 0x76, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x1, 0x2, 0x3, 0x4,
|
||||
];
|
||||
|
||||
let _ctx_read = ExchangeFormatRequest::read(&mut Cursor::new(&mut &buf)).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn version() {
|
||||
let mut buf = read_file(
|
||||
concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/tests/assets/",
|
||||
"exp/exchange/min_req.bin"
|
||||
),
|
||||
"test resp inv magic",
|
||||
)
|
||||
.unwrap();
|
||||
// tamper with the version
|
||||
buf[0x8] = 0xff;
|
||||
|
||||
let _ctx_read = ExchangeFormatRequest::read(&mut Cursor::new(&mut &buf)).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cuid_size() {
|
||||
let mut buf = read_file(
|
||||
concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/tests/assets/",
|
||||
"exp/exchange/min_resp.bin"
|
||||
),
|
||||
"test resp inv magic",
|
||||
)
|
||||
.unwrap();
|
||||
// tamper with the cuid size
|
||||
buf[0x3b] = 0xf;
|
||||
|
||||
let _ctx_read = ExchangeFormatResponse::read(&mut Cursor::new(&mut &buf)).unwrap_err();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
mod cli;
|
||||
mod cmd;
|
||||
mod exchange;
|
||||
|
||||
use clap::{CommandFactory, Parser};
|
||||
use cli::CliOptions;
|
||||
use log::trace;
|
||||
use std::process::ExitCode;
|
||||
use utils::{print_cli_error, print_error, print_version, PvLogger};
|
||||
|
||||
use crate::cli::Command;
|
||||
use crate::cmd::*;
|
||||
|
||||
static LOGGER: PvLogger = PvLogger;
|
||||
const FEATURES: &[&[&str]] = &[cmd::CMD_FN, cmd::UV_CMD_FN];
|
||||
const EXIT_CODE_ATTESTATION_FAIL: u8 = 2;
|
||||
const EXIT_CODE_LOGGER_FAIL: u8 = 3;
|
||||
|
||||
fn print_version(verbosity: u8) -> anyhow::Result<ExitCode> {
|
||||
print_version!(verbosity, "2024", FEATURES.concat());
|
||||
Ok(ExitCode::SUCCESS)
|
||||
}
|
||||
|
||||
fn main() -> ExitCode {
|
||||
let cli: CliOptions = match CliOptions::try_parse() {
|
||||
Ok(cli) => cli,
|
||||
Err(e) => return print_cli_error(e, CliOptions::command()),
|
||||
};
|
||||
|
||||
// set up logger/stderr
|
||||
if let Err(e) = LOGGER.start(cli.verbosity()) {
|
||||
// should(TM) never happen
|
||||
eprintln!("Logger error: {e:?}");
|
||||
return EXIT_CODE_LOGGER_FAIL.into();
|
||||
}
|
||||
|
||||
trace!("Trace verbosity, may leak secrets to command-line");
|
||||
trace!("Options {cli:?}");
|
||||
|
||||
let res = match &cli.cmd {
|
||||
Command::Create(opt) => create(opt),
|
||||
Command::Perform(opt) => perform(opt),
|
||||
Command::Verify(opt) => verify(opt),
|
||||
Command::Version => print_version(cli.verbosity()),
|
||||
};
|
||||
match res {
|
||||
Ok(c) => c,
|
||||
Err(e) => print_error(&e, cli.verbosity()),
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user