Merge pull request #18 from nicowilliams/master

Updates to enrollment and attestation docs
This commit is contained in:
Dimitar Tomov 2021-08-01 22:50:07 +03:00 committed by GitHub
commit f481bf9865
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 801 additions and 429 deletions

View file

@ -97,19 +97,21 @@ For more about enrollment see the tutorial specifically for
- `SCn` == server-to-client message number `n` - `SCn` == server-to-client message number `n`
- `{stuff, more_stuff}` == a sequence of data, a "struct" - `{stuff, more_stuff}` == a sequence of data, a "struct"
- `{"key":<value>,...}` == JSON text - `{"key":<value>,...}` == JSON text
- `TPM2_MakeCredential(<args>)` == outputs of calling `TPM2_MakeCredential()` with `args` arguments - `TPM2_Foo(<args>)` == outputs of calling some TPM 2.0 command with `args` arguments
- `TPM2_Certify(<args>)` == outputs of calling `TPM2_Certify()` with `args` arguments
- `XK` == `<X>` key, for some `<X>` purpose (the TPM-resident object and its private key) - `XK` == `<X>` key, for some `<X>` purpose (the TPM-resident object and its private key)
- `EK` == endorsement key (the TPM-resident object and its private key) - `EK` == endorsement key (the TPM-resident object and its private key)
- `AK` == attestation key (the TPM-resident object and its private key) - `AK` == attestation key (the TPM-resident object and its private key)
- `TK` == transport key (the TPM-resident object and its private key) - `TK` == transport key (the TPM-resident object and its private key)
- `WK` == well-known key used only as a `TPM2_ActivateCredential()` activation object, not used for any actual encryption or signing
- `XKpub` == `<X>`'s public key, for some `<X>` purpose - `XKpub` == `<X>`'s public key, for some `<X>` purpose
- `EKpub` == endorsement public key - `EKpub` == `EK` public key
- `AKpub` == attestation public key - `AKpub` == `AK` public key
- `TKpub` == transport public key - `TKpub` == `TK` public key
- `WKpub` == `WK` public key
- `XKname` == `<X>`'s cryptographic name, for some `<X>` purpose - `XKname` == `<X>`'s cryptographic name, for some `<X>` purpose
- `EKname` == endorsement key's cryptographic name - `EKname` == `EK`'s cryptographic name
- `AKname` == attestation key's cryptographic name - `AKname` == `AK`'s cryptographic name
- `WKname` == `WK`'s cryptographic name
## Threat Models ## Threat Models
@ -118,6 +120,7 @@ address:
- attestation client impersonation - attestation client impersonation
- attestation server impersonation - attestation server impersonation
- replay attacks
- unauthorized firmware and/or OS updates - unauthorized firmware and/or OS updates
- theft or compromise of of attestation servers - theft or compromise of of attestation servers
- theft of client devices or their local storage (e.g., disks, JBODs) - theft of client devices or their local storage (e.g., disks, JBODs)
@ -264,9 +267,9 @@ endorsement keys.
Let's start with few observations and security considerations: Let's start with few observations and security considerations:
- Clients need to know which PCRs to quote. E.g., the [Safe Boot](https://safeboot.dev/) - Clients need to know which PCRs to quote. E.g.,
project and the [IBM sample attestation client and server](https://sourceforge.net/projects/ibmtpm20acs/) the [IBM sample attestation client and server](https://sourceforge.net/projects/ibmtpm20acs/)
have the client ask for a list of PCRs and then the client quotes has the client ask for a list of PCRs and then the client quotes
just those. just those.
But clients could just quote all PCRs. It's more data to send, but But clients could just quote all PCRs. It's more data to send, but
@ -279,25 +282,53 @@ Let's start with few observations and security considerations:
stateless method is to use a timestamp and reject requests with old stateless method is to use a timestamp and reject requests with old
timestamps. timestamps.
- Replay protection of server to client responses is mostly either not As well, one can use the `resetCount` from the quote to check if an
needed or implicitly provided by [`TPM2_MakeCredential()`](TPM2_MakeCredential.md) attestation is the first after a reboot or not. Though this does
because `TPM2_MakeCredential()` generates a secret seed that require that the attestation server maintain some writable state
randomizes its outputs even when all the inputs are the same across (namely, the reset count.
multiple calls to it.
- Replay protection of server to client responses is provided by using
a different `AK` each time the client attests. This works because
[`TPM2_MakeCredential()`](/TPM-Commands/TPM2_MakeCredential.md) binds
the `AK` such that
[`TPM2_ActivateCredential()`](/TPM-Commands/TPM2_ActivateCredential.md)
will not succeed unless the server used the same `AKname` as the name
of the `AK` used by the client.
- Ultimately the protocol *must* make use of - Ultimately the protocol *must* make use of
[`TPM2_MakeCredential()`](/TPM-Commands/TPM2_MakeCredential.md) and [`TPM2_MakeCredential()`](/TPM-Commands/TPM2_MakeCredential.md) and
[`TPM2_ActivateCredential()`](/TPM-Commands/TPM2_ActivateCredential.md) in order to [`TPM2_ActivateCredential()`](/TPM-Commands/TPM2_ActivateCredential.md) in order to
authenticate a TPM-running host via its TPM's EKpub. authenticate a TPM-running host via its TPM's EKpub.
> The same is not true of [`TPM2_Quote()`](/TPM-Commands/TPM2_Quote.md)
> because one can build an attestation protocol that does not depend
> on signing quotes. Essentially one can simply send an unsigned
> reading of the client's TPM's PCRs and clock information and use an
> activation object with `adminWithPolicy` set and a `policyDigest`
> of a policy that uses the `TPM2_PolicyCounterTimer() and
> `TPM2_PolicyPCR()` commands to enforce that the `resetCount` and
> the PCRs are as asserted in the protocol. The server can then
> construct the same policy to compute the name of the activation
> object for
> [`TPM2_MakeCredential()`](/TPM-Commands/TPM2_MakeCredential.md),
> knowing that
> [`TPM2_ActivateCredential()`](/TPM-Commands/TPM2_ActivateCredential.md)
> will enforce that policy.
- Privacy protection of client identifiers may be needed, in which case - Privacy protection of client identifiers may be needed, in which case
TLS may be desired. TLS may be desired. Alternatively, the client could encrypt a
session key to a public of the attestation server using
[`TPM2_MakeCredential()`](/TPM-Commands/TPM2_MakeCredential.md), and
then use the session key to encrypt confidential parameters, thus
building something of a TLS-like protocol.
- Even if a single round trip attestation protocol is adequate, a - Even if a single round trip attestation protocol is adequate, a
return routability check may be needed to avoid denial of service return routability check may be needed to avoid denial of service
attacks. I.e., do not run a single round trip attestation protocol attacks. I.e., do not run a single round trip attestation protocol
over UDP without first requiring the client to echo a nonce/cookie. over UDP without first requiring the client to echo a nonce/cookie.
Using TCP effectively provides a return routability check.
- Statelessness on the server side is highly desirable, as that should - Statelessness on the server side is highly desirable, as that should
permit having multiple servers and each of a client's messages can go permit having multiple servers and each of a client's messages can go
to different servers. Conversely, keeping state on the server across to different servers. Conversely, keeping state on the server across
@ -308,14 +339,20 @@ Let's start with few observations and security considerations:
protocol messages could all be idempotent and therefore map well onto protocol messages could all be idempotent and therefore map well onto
HTTP `GET` requests but for the fact that all the things that may be HTTP `GET` requests but for the fact that all the things that may be
have to be sent may not fit on a URI local part or URI query have to be sent may not fit on a URI local part or URI query
parameters, therefore HTTP `POST` is the better option. parameters (and `GET` has no request body), therefore HTTP `POST` is
needed for its ability to send a request body.
### Error Cases Not Shown ### Error Cases Not Shown
Note that error cases are not shown in the protocols described below. Note that error cases are not shown in the protocols described below.
Naturally, in case of error the attestation server will send a suitable Naturally, in case of error the attestation server will send a suitable
error message back to the client. error message back to the client. Providing integrity protection for
error messages is tricky, as there will always be some kinds of errors
for which integrity protection cannot be provided, but also, there is no
natural key with which to sign errors. An actual attestation protocol
specification may require that clients know a public key that the server
can use to sign its errors with.
### Databases, Log Sinks, and Dashboarding / Alerting Systems Not Shown ### Databases, Log Sinks, and Dashboarding / Alerting Systems Not Shown
@ -386,12 +423,12 @@ The client obtains those items IFF (if and only if) the AK is resident
in the same TPM as the EK, courtesy of `TPM2_ActivateCredential()`'s in the same TPM as the EK, courtesy of `TPM2_ActivateCredential()`'s
semantics. semantics.
NOTE well that in single round trip attestation protocols using only > NOTE well that in single round trip attestation protocols using only
decrypt-only EKs it is *essential* that the AKcert not be logged in any > decrypt-only EKs it is *essential* that the AKcert not be logged in
public place since otherwise an attacker can make and send `CS0` using a > any public place since otherwise an attacker can make and send `CS0`
non-TPM-resident AK and any TPM's EKpub/EKcert known to the attacker, > using a non-TPM-resident AK and any TPM's EKpub/EKcert known to the
and then it may recover the AK certificate from the log in spite of > attacker, and then it may recover the AK certificate from the log in
being unable to recover the AK certificate from `SC1`! > spite of being unable to recover the AK certificate from `SC1`!
Alternatively, a single round trip attestation protocol can be Alternatively, a single round trip attestation protocol can be
implemented as an optimization to a two round trip protocol when the AK implemented as an optimization to a two round trip protocol when the AK
@ -407,6 +444,13 @@ database:
Encrypt_session_key({AKcert, filesystem_keys, etc.})} Encrypt_session_key({AKcert, filesystem_keys, etc.})}
``` ```
> NOTE: persisting the `AK` means that the `AK` must not have `stClear`
> set, which in turn means that it can be used and reused across
> reboots, so detecting reboots requires more mutable, synchronized
> state on the server to keep track of clients' `resetCount`s. Mutable,
> synchronized state complicates distributed databases, so it may not be
> desirable.
### Three-Message Attestation Protocol Patterns ### Three-Message Attestation Protocol Patterns
A single round trip protocol using encrypt-only EKpub will not A single round trip protocol using encrypt-only EKpub will not
@ -427,13 +471,13 @@ desirable anyways for monitoring and alerting purposes.
(In this diagram we show the use of a TPM simulator on the server side (In this diagram we show the use of a TPM simulator on the server side
for implementing [`TPM2_MakeCredential()`](/TPM-Commands/TPM2_MakeCredential.md).) for implementing [`TPM2_MakeCredential()`](/TPM-Commands/TPM2_MakeCredential.md).)
NOTE well that in this protocol, like single round trip attestation > NOTE well that in this protocol, like single round trip attestation
protocols using only decrypt-only EKs, it is *essential* that the AKcert > protocols using only decrypt-only EKs, it is *essential* that the
not be logged in any public place since otherwise an attacker can make > AKcert not be logged in any public place since otherwise an attacker
and send `CS0` using a non-TPM-resident AK and any TPM's EKpub/EKcert > can make and send `CS0` using a non-TPM-resident AK and any TPM's
known to the attacker, and then it may recover the AK certificate from > EKpub/EKcert known to the attacker, and then it may recover the AK
the log in spite of being unable to recover the AK certificate from > certificate from the log in spite of being unable to recover the AK
`SC1`! > certificate from `SC1`!
If such a protocol is instantiated over HTTP or TCP, it will really be If such a protocol is instantiated over HTTP or TCP, it will really be
more like a two round trip protocol: more like a two round trip protocol:
@ -575,20 +619,39 @@ to two round trips.
### Actual Protocols: safeboot.dev ### Actual Protocols: safeboot.dev
[Safeboot.dev](https://safeboot.dev) uses a single round trip stateless
attestation protocol, with a separate, one-time enrollment protocol.
``` ```
CS0: <empty> CS0: [ID], EKpub, [EKcert], AKpub, PCRs, eventlog, timestamp,
SC0: nonce, PCR_list
CS1: [ID], EKpub, [EKcert], AKpub, PCRs, eventlog, nonce,
TPM2_Quote(AK, PCRs, extra_data)=Signed_AK({hash-of-PCRs, misc, extra_data}) TPM2_Quote(AK, PCRs, extra_data)=Signed_AK({hash-of-PCRs, misc, extra_data})
SC1: {TPM2_MakeCredential(EKpub, AKpub, session_key), SC0: {TPM2_MakeCredential(EKpub, AKpub, session_key),
Encrypt_session_key({filesystem_keys})} Encrypt_session_key({long-term-secrets-encrypted-to-EKpub})}
with
long-term-secrets-encrypted-to-EKpub =
[{TPM2_MakeCredential(EKpub, WKname(policy), aes_key0),
Encrypt_aes_key0(secret0)},
{TPM2_MakeCredential(EKpub, WKname(policy), aes_key1),
Encrypt_aes_key1(secret1)},
..,
{TPM2_MakeCredential(EKpub, WKname(policy), aes_keyN),
Encrypt_aes_key1(secretN)}]
``` ```
Nonce validation is currently not well-developed in Safeboot. During an initial enrollment step, the enrollment server can create any
If a timestamp is used instead of a nonce, and if the client assumes all number of secrets to deliver to the client later:
PCRs are desired, then this becomes a one round trip protocol.
An AKcert will be added to the Safeboot protocol soon. - local storage / filesystem keys
- private keys and certificates (PKIX, OpenSSH)
- OpenSSH host keys
- Kerberos keys
- etc.
## Attestation Protocol Patterns and Actual Protocols (signing-only EKs) ## Attestation Protocol Patterns and Actual Protocols (signing-only EKs)
@ -762,7 +825,7 @@ would not be performant if, for example, those secrets are being used to
encrypt filesystems. We must inherently trust the client to keep those encrypt filesystems. We must inherently trust the client to keep those
secrets safe when running. secrets safe when running.
## Break-Glass Recovery ## Break-Glass Recovery and Escrow
For break-glass recovery, the simplest thing to do is to store For break-glass recovery, the simplest thing to do is to store
`Encrypt_backupKey({EKpub, hostname, secrets})`, where `backupKey` is an `Encrypt_backupKey({EKpub, hostname, secrets})`, where `backupKey` is an

View file

@ -23,6 +23,19 @@ For example, one might scan an endorsement key (EK) public key or
certificate from a QR code on a shipment manifest and then enroll the certificate from a QR code on a shipment manifest and then enroll the
device using only that information. device using only that information.
## Safeboot.dev Enrollment Protocol
[Safeboot.dev](https://safeboot.dev) has an enrollment script,
`attest-enroll` which can have a trivial HTTP API binding where an
authenticated and authorized client principal `POST`s an `EKpub` and a
device name to the server. The server then creates enrollment state for
the device that will be used during subsequence attestation.
The [safeboot.dev enrollment process](https://github.com/osresearch/safeboot/blob/master/docs/enrollment.md)
does not require any interaction with the enrollee device except to
extract its `EKpub`. When the `EKpub` can be determined in an off-line
manner, then the safeboot.dev enrollment process can be fully off-line.
# Server-Side State to Create during Enrollment # Server-Side State to Create during Enrollment
- device name <-> EKpub binding - device name <-> EKpub binding
@ -52,19 +65,34 @@ device post-enrollment.
## Encrypt-to-TPM Sample Scripts ## Encrypt-to-TPM Sample Scripts
A pair of scripts are included here to demonstrate how to make long-term A pair of scripts used in [safeboot.dev](https://safeboot.dev) are
secrets encrypted to TPMs for use in included here to demonstrate how to make long-term secrets encrypted to
[attestation](/Attestation/README.md) protocols. The method used is the TPMs for use in [attestation](/Attestation/README.md) protocols. The
one described in the [attestation method used is the one described in the [attestation
tutorial](/Attestation/README.md#Secret-Transport-Sub-Protocols) using tutorial](/Attestation/README.md#Secret-Transport-Sub-Protocols) using
either of two methods to encrypt to a TPM:
- The "EK" method of encryption to a TPM uses
[`TPM2_MakeCredential()`](/TPM-Commands/TPM2_MakeCredential.md) and [`TPM2_MakeCredential()`](/TPM-Commands/TPM2_MakeCredential.md) and
[`TPM2_ActivateCredential()`](/TPM-Commands/TPM2_ActivateCredential.md) [`TPM2_ActivateCredential()`](/TPM-Commands/TPM2_ActivateCredential.md)
with a hard-coded, _well-known_ activation key (`WK`) to implement with a hard-coded, _well-known_ activation key (`WK`) to implement
encryption-to-`EKpub` with (optional) sender-asserted authorization encryption-to-`EKpub` with (optional) sender-asserted authorization
policy: policy.
- [`send-to-tpm.sh`](send-to-tpm.sh) - The "TK" method of encryption to a TPM uses a software implementation
- [`tpm-receive.sh`](tpm-receive.sh) of [`TPM2_Duplicate()`](/TPM-Commands/TPM2_Duplicate.md) to wrap the
private part of a "transport key" (`TK`) to the target TPM, then
normal RSA encryption to the public part of the `TK`. The ciphertext
consists of the outputs of `TPM2_Duplicate()` and the ciphertext
produced by RSA encryption to the TK.
The "EK" method is the default. Both methods support sender-asserted
policies.
The scripts:
- [`send-to-tpm`](send-to-tpm)
- [`tpm-receive`](tpm-receive)
You can use these scripts like so: You can use these scripts like so:
@ -76,65 +104,73 @@ You can use these scripts like so:
: ; : ;
: ; # Encrypt the secret to some TPM whose EKpub is in a file named : ; # Encrypt the secret to some TPM whose EKpub is in a file named
: ; # ek.pub: : ; # ek.pub:
: ; /safeboot/sbin/send-to-tpm.sh ek.pub secret.bin cipher.bin : ; /safeboot/sbin/send-to-tpm ek.pub secret.bin cipher.bin
fd32fa22c52cfc8e1a0c29eb38519f87084cab0b04b0d8f020a4d38b2f4e223e : ;
7fdad037a921f7eec4f97c08722692028e96888f0b970dc7b3bb6a9c97e8f988
``` ```
```bash ```bash
: ; # Decrypt the secret: : ; # Decrypt the secret:
: ; tpm-receive.sh cipher.bin secret.bin : ; tpm-receive cipher.bin plaintext.bin
fd32fa22c52cfc8e1a0c29eb38519f87084cab0b04b0d8f020a4d38b2f4e223e
7fdad037a921f7eec4f97c08722692028e96888f0b970dc7b3bb6a9c97e8f988
name: 000be1fe1b777ead331f2da896ced2bf7a3949d732a0c6adf6f0a292567d587c4408
837197674484b3f81a90cc8d46a5d724fd52d76e06520b64f2a1da1b331469aa 837197674484b3f81a90cc8d46a5d724fd52d76e06520b64f2a1da1b331469aa
fd32fa22c52cfc8e1a0c29eb38519f87084cab0b04b0d8f020a4d38b2f4e223e name: 000bc76d1462d32d5e6051d0aa121edfa5ed66b8e7f3632ce3c5a172b1ebd8aabc40
7fdad037a921f7eec4f97c08722692028e96888f0b970dc7b3bb6a9c97e8f988
certinfodata:b7bd59980628c33a14377d53e165c229
: ; : ;
```
- with policy - with policy
> Here we use a policy that `PCR #11` has not been extended. The
> idea is to extend it immediately after decrypting the ciphertext,
> which means that the ciphertext cannot again be decrypted later
> (by, say, some other application with access to the same TPM)
> without rebooting.
```bash ```bash
: ; # Make up a policy (here that PCR11 must be unextended): : ; # Make up a policy (here that PCR11 must be unextended):
: ; dd if=/dev/zero of=pcr.dat bs=32 count=1 : ; dd if=/dev/zero of=pcr.dat bs=32 count=1
: ; policy=(tpm2 policypcr -l sha256:11 -f pcr.dat) : ; policy=(tpm2 policypcr -l sha256:11 -f pcr.dat)
: ; : ;
: ; send-to-tpm.sh ek.pub secret.bin cipher.bin "${policy[@]}" : ; send-to-tpm ek.pub secret.bin cipher.bin "${policy[@]}"
fd32fa22c52cfc8e1a0c29eb38519f87084cab0b04b0d8f020a4d38b2f4e223e fd32fa22c52cfc8e1a0c29eb38519f87084cab0b04b0d8f020a4d38b2f4e223e
7fdad037a921f7eec4f97c08722692028e96888f0b970dc7b3bb6a9c97e8f988 7fdad037a921f7eec4f97c08722692028e96888f0b970dc7b3bb6a9c97e8f988
7fdad037a921f7eec4f97c08722692028e96888f0b970dc7b3bb6a9c97e8f988
policyDigest:
7fdad037a921f7eec4f97c08722692028e96888f0b970dc7b3bb6a9c97e8f988
: ;
``` ```
```bash ```bash
: ; # We have to satisfy the same policy on the receive side: : ; # We have to satisfy the same policy on the receive side:
: ; policy=(tpm2 policypcr -l sha256:11 -f pcr.dat) : ; policy=(tpm2 policypcr -l sha256:11 -f pcr.dat)
: ; : ;
: ; tpm-receive.sh -f cipher.bin "${policy[@]}" : ; tpm-receive cipher.bin plaintext.bin "${policy[@]}"
fd32fa22c52cfc8e1a0c29eb38519f87084cab0b04b0d8f020a4d38b2f4e223e fd32fa22c52cfc8e1a0c29eb38519f87084cab0b04b0d8f020a4d38b2f4e223e
7fdad037a921f7eec4f97c08722692028e96888f0b970dc7b3bb6a9c97e8f988 7fdad037a921f7eec4f97c08722692028e96888f0b970dc7b3bb6a9c97e8f988
name: 000be1fe1b777ead331f2da896ced2bf7a3949d732a0c6adf6f0a292567d587c4408 7fdad037a921f7eec4f97c08722692028e96888f0b970dc7b3bb6a9c97e8f988
837197674484b3f81a90cc8d46a5d724fd52d76e06520b64f2a1da1b331469aa 837197674484b3f81a90cc8d46a5d724fd52d76e06520b64f2a1da1b331469aa
name: 000b20a6cc44c93ad206196c65028f9a8bf2590de0b8f89bca9e968f09f4e616dba6
fd32fa22c52cfc8e1a0c29eb38519f87084cab0b04b0d8f020a4d38b2f4e223e fd32fa22c52cfc8e1a0c29eb38519f87084cab0b04b0d8f020a4d38b2f4e223e
7fdad037a921f7eec4f97c08722692028e96888f0b970dc7b3bb6a9c97e8f988 7fdad037a921f7eec4f97c08722692028e96888f0b970dc7b3bb6a9c97e8f988
certinfodata:b7bd59980628c33a14377d53e165c229 7fdad037a921f7eec4f97c08722692028e96888f0b970dc7b3bb6a9c97e8f988
: ; : ;
``` ```
Multiple policy commands can be separated with a quoted semi-colon: Multiple policy commands can be separated with a quoted semi-colon:
```bash ```bash
send-to-tpm.sh ... tpm2 policyblah ... \; policyfoo ... send-to-tpm ... tpm2 policyblah ... \; policyfoo ...
``` ```
Multiple policy commands can be separated with a quoted semi-colon: Multiple policy commands can be separated with a quoted semi-colon:
```bash ```bash
send-to-tpm.sh ... tpm2 policyblah ... \; policyfoo ... send-to-tpm ... tpm2 policyblah ... \; policyfoo ...
``` ```
When a policy is specified, these scripts will automatically set the When a policy is specified, these scripts will automatically set the
`adminWithPolicy` attribute of the activation object, and will add `adminWithPolicy` attribute of the activation object, and will add
`tpm2 policycommandcode TPM2_CC_ActivateCredential` to the policy, as `tpm2 policycommandcode TPM2_CC_ActivateCredential` ("EK" method) or
that is required for activation objects with `adminWithPolicy` set. `tpm2 policycommandcode TPM2_CC_RSA_Decrypt` ("TK" method) to the
policy.
# Enrollment Semantics # Enrollment Semantics
@ -224,3 +260,20 @@ TPMs then perhaps there is a role for the TPM to play in enrollment.
# Security Considerations # Security Considerations
TBD TBD
The enrollment database, though it contains ciphertexts of secrets
encrypted to enrolled devices' TPMs, is nonetheless to be kept
confidential. This is necessary to avoid attacks where an attacker
compromises an enrolled device then attempts to decrypt those
ciphertexts with the enrolled device's TPM. These ciphertexts should
only be furnished to the device as part of an attestation protocol.
For the same reason, these ciphertexts must be super-encrypted when
delivering them to enrolled devices during attestation.
Ciphertexts in enrolled state should be made with suitable sender-
asserted policies. For example, asserting that `PCR #11` has not been
extended so that immediately after decrypting such a ciphertext the
client can extend `PCR #11` to make decrypting that ciphertext again
impossible without an intervening reboot.

331
Enrollment/send-to-tpm Executable file
View file

@ -0,0 +1,331 @@
#!/bin/bash
PROG=${0##*/}
set -euo pipefail
shopt -s extglob
die() { echo "${PROG:+${PROG}: }$die_msg""$*" >&2 ; exit 1 ; }
info() { ((${VERBOSE:-0})) && echo "$@" >&2 ; return 0 ; }
function usage {
((${1:-1} > 0)) && exec 1>&2
pager=cat
if [[ -t 0 && -t 1 && -t 2 ]]; then
if [[ -z ${PAGER:-} ]] && type less >/dev/null 2>&1; then
pager=less
elif [[ -z ${PAGER:-} ]] && type more >/dev/null 2>&1; then
pager=more
elif [[ -n ${PAGER:-} ]]; then
pager=$PAGER
fi
fi
$pager <<EOF
Usage: $PROG EK-PUB SECRET OUT # Null policy
$PROG EK-PUB SECRET OUT POLICY-CMD [ARGS [\\; ...]]
$PROG -P POLICY EK-PUB SECRET OUT
{$PROG} encrypts a small (up to 32 bytes) {SECRET} file (should
contain an AES key) to a target TPM -identified by {EK-PUB}- with the
caller's optional choice of policy to be enforced by that TPM. The
{EK-PUB} should be a file containing the target's EKpub in
{TPM2B_PUBLIC} format.
Options:
-h This help message.
-M EK|TK Method to use for encryption to TPM (default: EK).
-P POLICY Use the named policy or policyDigest.
-f Overwrite {OUT}.
-x Trace this script.
Policies given as positional arguments should be of the form:
tpm2 policy... args... \\; tpm2 policy args... \\; ...
without any {--session}|{-S} nor {--policy}|{-L} options.
Also, no need to include {tpm2 policycommandcode}, as {$PROG} will add
that automatically.
E.g.:
$ $PROG ./ekpub ./secret ./madecredential \\
tpm2 policypcr -l "sha256:0,1,2,3" -f pcrs
A POLICY can be a digest or an executable.
A POLICY digest would be the SHA-256 policyDigest of a policy.
A POLICY executable would be a program that, if called with no
arguments, outputs a policyDigest.
The two methods of encryption to a TPM are:
- EK Uses {TPM2_MakeCredential()} to encrypt an AES key to
the target's EKpub.
The target uses {TPM2_ActivateCredential()} to decrypt
the AES key.
A well-known key is used as the activation object, and
the given policy is associated with it.
This method produces a single file named {OUT}.
- TK Uses {TPM2_Duplicate()} to encrypt an RSA private key to
the target's EKpub, then encrypts an AES key to that
key's public key. That RSA key we refer to as a
"transport key", or TK.
The target uses {TPM2_Import()} to import the TK,
{TPM2_Load()} to load it, and {TPM2_RSA_Decrypt()} to
decrypt the AES key.
A policy, if given, is set on the TK that the TPM will
enforce when {TPM2_RSA_Decrypt()} is called.
This method produces multiple files besides {OUT},
named:
{OUT}.tk.dpriv
{OUT}.tk.seed
EOF
exit "${1:-1}"
}
force=false
method=EK
policy=
policyDigest=
while getopts +:hfxM:P: opt; do
case "$opt" in
M) method=$OPTARG;;
P) policy=$OPTARG;;
h) usage 0;;
f) force=true;;
x) set -vx;;
*) usage;;
esac
done
shift $((OPTIND - 1))
function err {
echo "ERROR: $*" 1>&2
exit 1
}
case "$method" in
EK) command_code=TPM2_CC_ActivateCredential;;
TK) command_code=TPM2_CC_RSA_Decrypt;;
*) err "METHOD must be \"EK\" or \"TK\"";;
esac
if [[ -n $policy ]] && (($# > 3)); then
echo "Error: -P and policy commands are mutually exclusive" 1>&2
exit 1
fi
if [[ -n $policy ]]; then
(($# == 3)) || usage
if ((${#policy} == 64)) &&
[[ ! -f $policy && $policy = +([0-9a-fA-F]) ]]; then
# $policy is a policyDigest
policyDigest=$policy
elif [[ -x $policy ]]; then
# Run the script in $policy to get a policyDigest
policyDigest=$("$policy")
else
err "Given policy is neither a SHA-256 policyDigest nor a policy script"
fi
fi
(($# >= 3)) || usage
ekpub_file=$1
secret_file=$2
out_file=$3
shift 3
[[ -f ${ekpub_file:-} ]] || usage
[[ -f ${secret_file:-} ]] || usage
[[ -f ${out_file:-} ]] && $force && rm -f "${out_file:-}"
[[ -f ${out_file:-} ]] && err "output file ($out_file) exists"
# Make a temp dir and remove it when we exit:
d=
trap 'rm -rf "$d"' EXIT
d=$(mktemp -d)
# Execute a policy given as arguments.
#
# The first argument may be a command code; if given, then {tpm2}
# {policycommandcode} will be added to the given policy. The rest must be
# {tpm2_policy*} or {tpm2} {policy*} commands w/o any {--session}|{-c} or
# {--policy}|{-L} arguments, and multiple commands may be given separate by
# {';'}.
#
# E.g.,
#
# exec_policy TPM2_CC_ActivateCredential "$@"
# exec_policy tpm2 policypcr ... ';' tpm2 policysigned ...
function exec_policy {
local command_code=''
local add_commandcode=true
local has_policy=false
local -a cmd
if (($# > 0)) && [[ -z $1 || $1 = TPM2_CC_* ]]; then
command_code=$1
shift
fi
while (($# > 0)); do
has_policy=true
cmd=()
while (($# > 0)) && [[ $1 != ';' ]]; do
cmd+=("$1")
if ((${#cmd[@]} == 1)) && [[ ${cmd[0]} = tpm2_* ]]; then
cmd+=( --session "${d}/session.ctx"
--policy "${d}/policy")
elif ((${#cmd[@]} == 2)) && [[ ${cmd[0]} = tpm2 ]]; then
cmd+=( --session "${d}/session.ctx"
--policy "${d}/policy")
fi
shift
done
(($# > 0)) && shift
# Run the policy command in the temp dir. It -or the last command- must
# leave a file there named 'policy'.
"${cmd[@]}" 1>&2 \
|| die "unable to execute policy command: ${cmd[*]}"
[[ ${cmd[0]} = tpm2 ]] && ((${#cmd[@]} == 1)) \
&& die "Policy is incomplete"
[[ ${cmd[0]} = tpm2 && ${cmd[1]} = policycommandcode ]] \
&& add_commandcode=false
[[ ${cmd[0]} = tpm2_policycommandcode ]] \
&& add_commandcode=false
done
if $has_policy && $add_commandcode && [[ -n $command_code ]]; then
tpm2 policycommandcode \
--session "${d}/session.ctx" \
--policy "${d}/policy" \
"$command_code" 1>&2 \
|| die "unable to execute policy command: tpm2 policycommandcode $command_code"
fi
xxd -p -c 100 "${d}/policy"
}
# Compute the policyDigest of a given policy by executing it in a trial
# session.
function make_policyDigest {
tpm2 flushcontext --transient-object
tpm2 flushcontext --loaded-session
tpm2 startauthsession --session "${d}/session.ctx"
exec_policy "$@"
}
# A well-known private key just for the TPM2_MakeCredential()-based encryption
# of secrets to TPMs. It was generated with:
# openssl genpkey -genparam \
# -algorithm EC \
# -out "${d}/ecp.pem" \
# -pkeyopt ec_paramgen_curve:secp384r1 \
# -pkeyopt ec_param_enc:named_curve
# openssl genpkey -paramfile "${d}/ecp.pem"
function wkpriv {
cat <<"EOF"
-----BEGIN PRIVATE KEY-----
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDAlMnCWue7CfXjNLibH
PTJrsOLUcoxqU3FLWYEWMI+HuPnzcwwl7SkKN6cpf4H3oQihZANiAAQ1pw6D5QVw
vymljYVDyrUriOet8zPB/9tq9XJ7A54qsVkaVufAuEJ6GIvD4xUZ27manMosJADS
aW2TLJkwxecRh2eTwPtSx2U32M2/yHeuWRV/0juiIozefPsTAlHAi3E=
-----END PRIVATE KEY-----
EOF
}
# Compute a well-known activation object's name for use in
# TPM2_MakeCredential(), binding a given policy into it.
#
# This version uses a TPM via {tpm2 loadexternal}.
function wkname_tpm {
local attrs='sign'
local has_policy=
wkpriv > "${d}/wkpriv.pem"
tpm2 flushcontext --transient-object
tpm2 flushcontext --loaded-session
tpm2 flushcontext --saved-session 1>&2
# Load
if [[ -n $policyDigest ]]; then
tpm2 startauthsession --session "${d}/session.ctx"
printf '%s' "$policyDigest" | xxd -p -r > "${d}/policy"
echo "policyDigest: $(xxd -p -c 100 < "${d}/policy")" 1>&2
attrs='adminwithpolicy|sign'
has_policy=true
elif (($# > 0)); then
make_policyDigest "$command_code" "$@" 1>&2
attrs='adminwithpolicy|sign'
has_policy=true
# Flush again, but this time not saved sessions
tpm2 flushcontext --transient-object 1>&2
tpm2 flushcontext --loaded-session 1>&2
echo "policyDigest: $(xxd -p -c 100 < "${d}/policy")" 1>&2
fi
# Load the WK
tpm2 loadexternal \
--hierarchy n \
--key-algorithm ecc \
--private "${d}/wkpriv.pem" \
${has_policy:+ --policy "${d}/policy"} \
--attributes "$attrs" \
--key-context "${d}/wk.ctx" \
| grep ^name: | cut -d' ' -f2 \
|| die "unable to load the WK into a TPM for computing its name"
}
case "$method" in
EK) info "Computing WKname"
wkname=$(wkname_tpm "$@") \
|| die "unable to compute the MakeCredential activation object's cryptographic name"
info "Encrypting to EKpub using TPM2_MakeCredential"
tpm2 makecredential \
--tcti "none" \
--encryption-key "${ekpub_file}" \
--name "$wkname" \
--secret "${secret_file}" \
--credential-blob "$out_file" \
|| die "unable to MakeCredential";;
TK) info "Generating TK"
openssl genrsa -out "${d}/tk-priv.pem" \
|| die "unable to create TK private key"
openssl rsa \
-pubout \
-in "${d}/tk-priv.pem" \
-out "${d}/tk.pem" \
|| die "unable to create TK public key"
args=()
if (($# > 0)); then
make_policyDigest "$command_code" "$@" 1>&2
args=("--policy=${d}/policy")
fi
info "Exporting TK to EKpub"
tpm2 duplicate \
--tcti none \
--parent-public="$ekpub_file" \
--wrapper-algorithm=rsa \
"${args[@]}" \
--private-key="${d}/tk-priv.pem" \
--public="${out_file}.tk.pub" \
--private="${out_file}.tk.dpriv" \
--encrypted-seed="${out_file}.tk.seed" \
|| die "$0: unable to duplicate TK into TPM for EK"
info "Encrypting to TK"
openssl rsautl \
-encrypt \
-pubin \
-inkey "${d}/tk.pem" \
-in "$secret_file" \
-out "${out_file}" \
|| die "$0: unable to encrypt to TK" ;;
esac

View file

@ -1,165 +0,0 @@
#!/bin/bash
PROG=${0##*/}
set -euo pipefail
function usage {
((${1:-1} > 0)) && exec 1>&2
cat <<EOF
Usage: $PROG EK-PUB-FILE SECRET-FILE OUT-FILE
$PROG EK-PUB-FILE SECRET-FILE OUT-FILE [POLICY-CMD [ARGS [\\; ...]]]
$PROG -P well-known-key-name EK-PUB-FILE SECRET-FILE OUT-FILE
Encrypts a small secret to a TPM's EKpub with the caller's choice of
policy.
Policies should be specified as a sequence of {tpm2 policy...}
commands, with all necessary arguments except for {--session}|{-S}
and {--policy}|{-L} options. Also, no need to include {tpm2
policycommandcode}, as that will get added. E.g.:
$ $PROG ./ekpub ./secret ./madecredential \\
tpm2 policypcr -l "sha256:0,1,2,3" -f pcrs
Options:
-h This help message.
-P WKname Use the given cryptographic name binding a policy for
recipient to meet.
-f Overwrite OUT-FILE.
-x Trace this script.
EOF
exit ${1:-1}
}
force=false
wkname=
while getopts +:hfxP: opt; do
case "$opt" in
P) wkname=$OPTARG;;
h) usage 0;;
f) force=true;;
x) set -vx;;
*) usage;;
esac
done
shift $((OPTIND - 1))
(($# >= 3)) || usage
ekpub_file=$1
secret_file=$2
out_file=$3
shift 3
function err {
echo "ERROR: $*" 1>&2
exit 1
}
[[ -f ${ekpub_file:-} ]] || usage
[[ -f ${secret_file:-} ]] || usage
[[ -f ${out_file:-} ]] && $force && rm -f "${out_file:-}"
[[ -f ${out_file:-} ]] && err "output file ($out_file) exists"
# Make a temp dir and remove it when we exit:
d=
trap 'rm -rf "$d"' EXIT
d=$(mktemp -d)
function exec_policy {
local add_commandcode=true
local has_policy=false
while (($# > 0)); do
has_policy=true
cmd=()
while (($# > 0)) && [[ $1 != ';' ]]; do
cmd+=("$1")
if ((${#cmd[@]} == 1)) && [[ ${cmd[0]} = tpm2_* ]]; then
cmd+=(--session "${d}/session.ctx" --policy "${d}/policy")
elif ((${#cmd[@]} == 2)) && [[ ${cmd[0]} = tpm2 ]]; then
cmd+=(--session "${d}/session.ctx" --policy "${d}/policy")
fi
shift
done
(($# > 0)) && shift
# Run the policy command in the temp dir. It -or the last command- must
# leave a file there named 'policy'.
"${cmd[@]}"
if [[ ${cmd[0]} = tpm2 ]] && ((${#cmd[@]} == 1)); then
echo "Policy is incomplete" 1>&2
exit 1
fi
[[ ${cmd[0]} = tpm2 && ${cmd[1]} = policycommandcode ]] &&
add_commandcode=false
[[ ${cmd[0]} = tpm2_policycommandcode ]] && add_commandcode=false
done
$has_policy && $add_commandcode &&
tpm2 policycommandcode --session "${d}/session.ctx" \
--policy "${d}/policy" \
TPM2_CC_ActivateCredential
}
function make_policyDigest {
# Start a trial session, execute the given policy commands, save the
# policyDigest.
tpm2 startauthsession --session "${d}/session.ctx"
exec_policy "$@"
}
function wkname {
local attrs='decrypt|sign'
local has_policy
# This is the WK. It was generated with:
# openssl genpkey -genparam \
# -algorithm EC \
# -out "${d}/ecp.pem" \
# -pkeyopt ec_paramgen_curve:secp384r1 \
# -pkeyopt ec_param_enc:named_curve
# openssl genpkey -paramfile "${d}/ecp.pem"
cat > "${d}/wkpriv.pem" <<EOF
-----BEGIN PRIVATE KEY-----
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDAlMnCWue7CfXjNLibH
PTJrsOLUcoxqU3FLWYEWMI+HuPnzcwwl7SkKN6cpf4H3oQihZANiAAQ1pw6D5QVw
vymljYVDyrUriOet8zPB/9tq9XJ7A54qsVkaVufAuEJ6GIvD4xUZ27manMosJADS
aW2TLJkwxecRh2eTwPtSx2U32M2/yHeuWRV/0juiIozefPsTAlHAi3E=
-----END PRIVATE KEY-----
EOF
tpm2 flushcontext --transient-object
tpm2 flushcontext --loaded-session
tpm2 flushcontext --saved-session 1>&2
# Load
attrs='decrypt|sign'
if (($# > 0)); then
make_policyDigest "$@" 1>&2
attrs='adminwithpolicy|decrypt|sign'
has_policy=true
# Flush again, but this time not saved sessions
tpm2 flushcontext --transient-object 1>&2
tpm2 flushcontext --loaded-session 1>&2
fi
# Load the WK
tpm2 loadexternal -C n \
-Gecc \
-r "${d}/wkpriv.pem" \
${has_policy:+-L "${d}/policy"} \
-a "$attrs" \
-c "${d}/wk.ctx" |
grep ^name: | cut -d' ' -f2
}
[[ -z $wkname ]] && wkname=$(wkname "$@")
tpm2 makecredential \
--tcti "none" \
--encryption-key "${ekpub_file}" \
--name "$wkname" \
--secret "${secret_file}" \
--credential-blob "$out_file"

284
Enrollment/tpm-receive Executable file
View file

@ -0,0 +1,284 @@
#!/bin/bash
PROG=${0##*/}
set -euo pipefail
shopt -s extglob
die() { echo "${PROG:+${PROG}: }$die_msg""$*" >&2 ; exit 1 ; }
info() { ((${VERBOSE:-0})) && echo "$@" >&2 ; return 0 ; }
function usage {
cat <<EOF
Usage: $PROG CIPHERTEXT OUT [POLICY-CMD [ARGS] [;] ...]
Decrypts the {CIPHERTEXT} file produced by {send-to-tpm}.
If {CIPHERTEXT}.tk.pem, {CIPHERTEXT}.tk.dpriv, {CIPHERTEXT}.tk.pub,
and {CIPHERTEXT}.tk.seed exist, then the "TK" method of encryption is
assumed. Otherwise the "EK" method of encryption is assumed.
See {send-to-tpm} for details of the two encryption-to-TPM methods
supported.
The plaintext is written to the {OUT} file.
If the sender asserted a policy, that policy must be given to {$PROG}
so it can execute and satisfy it.
Policies should be specified as a sequence of {tpm2 policy...}
commands, with all necessary arguments except for {--session}|{-S}
and {--policy}|{-L} options. Also, no need to include {tpm2
policycommandcode}, as that will get added. E.g.:
$ $PROG ./ekpub ./secret ./madecredential \\
tpm2 policypcr -l "sha256:0,1,2,3" -f pcrs
Options:
-h This help message.
-f Overwrite OUT-FILE.
-x Trace this script.
EOF
exit 1
}
force=false
while getopts +:hfx opt; do
case "$opt" in
h) usage 0;;
f) force=true;;
x) set -vx;;
*) usage;;
esac
done
shift $((OPTIND - 1))
(($# >= 2)) || usage
ciphertext_file=$1
out_file=$2
shift 2
[[ ! -f ${ciphertext_file:-} ]] && die "No ciphertext file given"
[[ -f ${out_file:-} ]] && $force && rm -f "$out_file"
[[ ! -f ${out_file:-} ]] || die "Plaintext file exists; use -f?"
d=
trap 'rm -rf "$d"' EXIT
d=$(mktemp -d)
use_tk=true
command_code=TPM2_CC_RSA_Decrypt
for i in dpriv pub seed; do
($use_tk && [[ -s ${ciphertext_file}.tk.$i ]]) || use_tk=false
done
$use_tk || command_code=TPM2_CC_ActivateCredential
# Execute a policy given as arguments.
#
# The first argument may be a command code; if given, then {tpm2}
# {policycommandcode} will be added to the given policy. The rest must be
# {tpm2_policy*} or {tpm2} {policy*} commands w/o any {--session}|{-c} or
# {--policy}|{-L} arguments, and multiple commands may be given separate by
# {';'}.
#
# E.g.,
#
# exec_policy TPM2_CC_ActivateCredential "$@"
# exec_policy tpm2 policypcr ... ';' tpm2 policysigned ...
function exec_policy {
local command_code=''
local add_commandcode=true
local has_policy=false
local -a cmd
if (($# > 0)) && [[ -z $1 || $1 = TPM2_CC_* ]]; then
command_code=$1
shift
fi
while (($# > 0)); do
has_policy=true
cmd=()
while (($# > 0)) && [[ $1 != ';' ]]; do
cmd+=("$1")
if ((${#cmd[@]} == 1)) && [[ ${cmd[0]} = tpm2_* ]]; then
cmd+=( --session "${d}/session.ctx"
--policy "${d}/policy")
elif ((${#cmd[@]} == 2)) && [[ ${cmd[0]} = tpm2 ]]; then
cmd+=( --session "${d}/session.ctx"
--policy "${d}/policy")
fi
shift
done
(($# > 0)) && shift
# Run the policy command in the temp dir. It -or the last command- must
# leave a file there named 'policy'.
"${cmd[@]}" 1>&2 \
|| die "unable to execute policy command: ${cmd[*]}"
[[ ${cmd[0]} = tpm2 ]] && ((${#cmd[@]} == 1)) \
&& die "Policy is incomplete"
[[ ${cmd[0]} = tpm2 && ${cmd[1]} = policycommandcode ]] \
&& add_commandcode=false
[[ ${cmd[0]} = tpm2_policycommandcode ]] \
&& add_commandcode=false
done
if $has_policy && $add_commandcode && [[ -n $command_code ]]; then
tpm2 policycommandcode \
--session "${d}/session.ctx" \
--policy "${d}/policy" \
"$command_code" 1>&2 \
|| die "unable to execute policy command: tpm2 policycommandcode $command_code"
fi
xxd -p -c 100 "${d}/policy"
}
# Compute the policyDigest of a given policy by executing it in a trial
# session.
function make_policyDigest {
tpm2 flushcontext --transient-object
tpm2 flushcontext --loaded-session
tpm2 startauthsession --session "${d}/session.ctx"
exec_policy "$@"
}
# A well-known private key just for the TPM2_MakeCredential()-based encryption
# of secrets to TPMs. It was generated with:
# openssl genpkey -genparam \
# -algorithm EC \
# -out "${d}/ecp.pem" \
# -pkeyopt ec_paramgen_curve:secp384r1 \
# -pkeyopt ec_param_enc:named_curve
# openssl genpkey -paramfile "${d}/ecp.pem"
function wkpriv {
cat <<"EOF"
-----BEGIN PRIVATE KEY-----
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDAlMnCWue7CfXjNLibH
PTJrsOLUcoxqU3FLWYEWMI+HuPnzcwwl7SkKN6cpf4H3oQihZANiAAQ1pw6D5QVw
vymljYVDyrUriOet8zPB/9tq9XJ7A54qsVkaVufAuEJ6GIvD4xUZ27manMosJADS
aW2TLJkwxecRh2eTwPtSx2U32M2/yHeuWRV/0juiIozefPsTAlHAi3E=
-----END PRIVATE KEY-----
EOF
}
# Get the EK handle
tpm2 flushcontext --transient-object
tpm2 flushcontext --loaded-session
tpm2 flushcontext --saved-session 1>&2
tpm2 createek \
--key-algorithm rsa \
--ek-context "${d}/ek.ctx" \
--public "${d}/ek.pub" \
|| die "tpm2: unable to create ek object"
# Make policyDigest
(($# > 0)) && make_policyDigest "$command_code" "$@"
# Create empty auth session for EK
tpm2 flushcontext --transient-object
tpm2 flushcontext --loaded-session
tpm2 startauthsession --session "${d}/sessionek.ctx" --policy-session
tpm2 policysecret --session "${d}/sessionek.ctx" --object-context endorsement
# Execute and satisfy the policy for the TK or WK
function auth {
tpm2 flushcontext --transient-object
tpm2 flushcontext --loaded-session
tpm2 startauthsession \
--session "${d}/session.ctx" \
--policy-session
# exec_policy will {die} if we fail to satisfy the policy
exec_policy "$command_code" "$@"
tpm2 flushcontext --transient-object
tpm2 flushcontext --loaded-session
}
if $use_tk; then
# attempt to load the secret wrapping key into our TPM
# as a transient object
tpm2 flushcontext --transient-object
tpm2 flushcontext --loaded-session
info "tpm2: Importing duplicate transport key"
tpm2 import \
--parent-context "${d}/ek.ctx" \
--parent-auth "session:${d}/sessionek.ctx" \
--key-algorithm rsa \
--input "${ciphertext_file}.tk.dpriv" \
--seed "${ciphertext_file}.tk.seed" \
--public "${ciphertext_file}.tk.pub" \
--private "${d}/tk.priv" \
|| die "tpm2: unable to import duplicate transport key object"
warn "tpm2: Loading duplicate transport key"
tpm2 flushcontext --transient-object
tpm2 flushcontext --loaded-session
tpm2 startauthsession --session "${d}/sessionek.ctx" --policy-session
tpm2 policysecret --session "${d}/sessionek.ctx" --object-context endorsement
tpm2 load \
--parent-context "${d}/ek.ctx" \
--auth "session:${d}/sessionek.ctx" \
--key-context "${d}/tk.ctx" \
--public "${ciphertext_file}.tk.pub" \
--private "${d}/tk.priv" \
|| die "tpm2: unable to load duplicate transport key object"
warn "tpm2: Decrypting with TK"
if (($# > 0)); then
auth "$@"
tpm2 rsadecrypt \
--auth "session:${d}/session.ctx" \
--key-context "${d}/tk.ctx" \
--output "${out_file}" \
"${ciphertext_file}" \
|| die "tpm2: unable to decrypt with transport key"
else
tpm2 rsadecrypt \
--key-context "${d}/tk.ctx" \
--output "${out_file}" \
"${ciphertext_file}" \
|| die "tpm2: unable to decrypt with transport key"
fi
else
# Load the WK for use as the activation object for
# TPM2_ActivateCredential():
tpm2 flushcontext --transient-object 1>&2
tpm2 flushcontext --loaded-session 1>&2
wkpriv > "${d}/wkpriv.pem"
attrs='sign'
adminwithpolicy=
if (($# > 0)); then
attrs='adminwithpolicy|sign'
adminwithpolicy=true
fi
if tpm2 loadexternal \
--hierarchy n \
--key-algorithm ecc \
--private "${d}/wkpriv.pem" \
${adminwithpolicy:+--policy "${d}/policy"} \
--attributes "$attrs" \
--key-context "${d}/wk.ctx" 1>&2; then
true
else
stat=$?
echo "ERROR: Failed to load WK: $?" 1>&2
exit $stat
fi
# If a policy was given to execute, create a policy session and execute
# and satisfy the policy:
activatecredential_args=()
if (($# > 0)); then
activatecredential_args+=(--credentialedkey-auth session:"${d}/session.ctx")
auth "$@"
fi
# Finally, ActivateCredential
tpm2 activatecredential \
--credentialedkey-context "${d}/wk.ctx" \
"${activatecredential_args[@]}" \
--credentialkey-context "${d}/ek.ctx" \
--credentialkey-auth session:"${d}/sessionek.ctx" \
--credential-blob "$ciphertext_file" \
--certinfo-data "$out_file" > /dev/null \
|| die "could not decrypt using TPM2_ActivateCredential()"
fi

View file

@ -1,194 +0,0 @@
#!/bin/bash
PROG=${0##*/}
set -euo pipefail
function usage {
echo "Usage: $PROG [OPTIONS] CIPHERTEXT-FILE OUT-FILE [POLICY-CMD [ARGS] [\; ...]]"
cat <<EOF
Usage: $PROG CIPHERTEXT-FILE OUT-FILE [POLICY-CMD [ARGS] [;] ...]
"Activates" (decrypts) CIPHERTEXT-FILE made with TPM2_MakeCredential and
writes the plaintext to OUT-FILE. If the sender asserted some policy,
that policy must be repeated when invoking this program to decrypt the
secret.
Policies should be specified as a sequence of {tpm2 policy...}
commands, with all necessary arguments except for {--session}|{-S}
and {--policy}|{-L} options. Also, no need to include {tpm2
policycommandcode}, as that will get added. E.g.:
$ $PROG ./ekpub ./secret ./madecredential \\
tpm2 policypcr -l "sha256:0,1,2,3" -f pcrs
Options:
-h This help message.
-f Overwrite OUT-FILE.
-x Trace this script.
EOF
exit 1
}
force=false
verbose=false
while getopts +:hfvx opt; do
case "$opt" in
h) usage 0;;
f) force=true;;
v) verbose=true;;
x) set -vx;;
*) usage;;
esac
done
shift $((OPTIND - 1))
(($# >= 2)) || usage
ciphertext_file=$1
out_file=$2
shift 2
[[ -f ${ciphertext_file:-} ]] || usage
[[ -f ${out_file:-} ]] && $force && rm -f "$out_file"
[[ -f ${out_file:-} ]] && usage
d=
trap 'rm -rf "$d"' EXIT
d=$(mktemp -d)
function v {
if $verbose; then
printf 'Running:'
printf ' %q' "$@"
printf '\n'
fi >/dev/tty || true
if "$@"; then
$verbose && printf '(SUCCESS)\n' >/dev/tty || true
else
stat=$?
printf 'ERROR: Command exited with %d\n' $stat >/dev/tty || true
return $stat
fi
}
function exec_policy {
local add_commandcode=true
local has_policy=false
while (($# > 0)); do
has_policy=true
cmd=()
while (($# > 0)) && [[ $1 != ';' ]]; do
cmd+=("$1")
if ((${#cmd[@]} == 1)) && [[ ${cmd[0]} = tpm2_* ]]; then
cmd+=(--session "${d}/session.ctx" --policy "${d}/policy")
elif ((${#cmd[@]} == 2)) && [[ ${cmd[0]} = tpm2 ]]; then
cmd+=(--session "${d}/session.ctx" --policy "${d}/policy")
fi
shift
done
(($# > 0)) && shift
# Run the policy command in the temp dir. It -or the last command- must
# leave a file there named 'policy'.
"${cmd[@]}"
if [[ ${cmd[0]} = tpm2 ]] && ((${#cmd[@]} == 1)); then
echo "Policy is incomplete" 1>&2
exit 1
fi
[[ ${cmd[0]} = tpm2 && ${cmd[1]} = policycommandcode ]] &&
add_commandcode=false
[[ ${cmd[0]} = tpm2_policycommandcode ]] && add_commandcode=false
done
$has_policy && $add_commandcode &&
tpm2 policycommandcode --session "${d}/session.ctx" \
--policy "${d}/policy" \
TPM2_CC_ActivateCredential
}
function make_policyDigest {
tpm2 flushcontext --transient-object
tpm2 flushcontext --loaded-session
v tpm2 startauthsession --session "${d}/session.ctx"
exec_policy "$@"
}
# Get the EK handle:
tpm2 flushcontext --transient-object
tpm2 flushcontext --loaded-session
tpm2 flushcontext --saved-session 1>&2
tpm2 createek --key-algorithm rsa \
--ek-context "${d}/ek.ctx" \
--public "${d}/ek.pub"
# Make policyDigest and load WK
attrs='decrypt|sign'
adminwithpolicy=
if (($# > 0)); then
make_policyDigest "$@"
attrs='adminwithpolicy|decrypt|sign'
adminwithpolicy=true
fi
rm -f "${d}/session.ctx"
# This is the WK. It was generated with:
# openssl genpkey -genparam \
# -algorithm EC \
# -out "${d}/ecp.pem" \
# -pkeyopt ec_paramgen_curve:secp384r1 \
# -pkeyopt ec_param_enc:named_curve
# openssl genpkey -paramfile "${d}/ecp.pem"
cat > "${d}/wkpriv.pem" <<EOF
-----BEGIN PRIVATE KEY-----
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDAlMnCWue7CfXjNLibH
PTJrsOLUcoxqU3FLWYEWMI+HuPnzcwwl7SkKN6cpf4H3oQihZANiAAQ1pw6D5QVw
vymljYVDyrUriOet8zPB/9tq9XJ7A54qsVkaVufAuEJ6GIvD4xUZ27manMosJADS
aW2TLJkwxecRh2eTwPtSx2U32M2/yHeuWRV/0juiIozefPsTAlHAi3E=
-----END PRIVATE KEY-----
EOF
# Load the WK
v tpm2 flushcontext --transient-object 1>&2
v tpm2 flushcontext --loaded-session 1>&2
if v tpm2 loadexternal -C n \
-Gecc \
-r "${d}/wkpriv.pem" \
${adminwithpolicy:+-L "${d}/policy"} \
-a "$attrs" \
-c "${d}/wk.ctx" > "${d}/out" 2> "${d}/err"; then
cat "${d}/out" 1>&2
else
stat=$?
echo "ERROR: Failed to load WK:" 1>&2
cat "${d}/out"
cat "${d}/err" 1>&2
exit $stat
fi
# Create empty auth session for EK
v tpm2 flushcontext --transient-object
v tpm2 flushcontext --loaded-session
v tpm2 startauthsession --session "${d}/sessionek.ctx" --policy-session
v tpm2 policysecret --session "${d}/sessionek.ctx" --object-context endorsement
activatecredential_args=()
if (($# > 0)); then
activatecredential_args+=(--credentialedkey-auth session:"${d}/session.ctx")
# Create auth session for the WK, since it has adminWithPolicy
v tpm2 flushcontext --transient-object
v tpm2 flushcontext --loaded-session
v tpm2 startauthsession --session "${d}/session.ctx" --policy-session
exec_policy "$@"
v tpm2 flushcontext --transient-object
v tpm2 flushcontext --loaded-session
fi
# Finally, ActivateCredential
$verbose && tpm2 readpublic -c "${d}/wk.ctx" | grep name:
v tpm2 activatecredential --credentialedkey-context "${d}/wk.ctx" \
"${activatecredential_args[@]}" \
--credentialkey-context "${d}/ek.ctx" \
--credentialkey-auth session:"${d}/sessionek.ctx" \
--credential-blob "$ciphertext_file" \
-o "$out_file"