mirror of
https://github.com/tpm2dev/tpm.dev.tutorials.git
synced 2024-11-24 06:42:11 +00:00
Merge pull request #18 from nicowilliams/master
Updates to enrollment and attestation docs
This commit is contained in:
commit
f481bf9865
6 changed files with 801 additions and 429 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
[`TPM2_MakeCredential()`](/TPM-Commands/TPM2_MakeCredential.md) and
|
either of two methods to encrypt to a TPM:
|
||||||
[`TPM2_ActivateCredential()`](/TPM-Commands/TPM2_ActivateCredential.md)
|
|
||||||
with a hard-coded, _well-known_ activation key (`WK`) to implement
|
|
||||||
encryption-to-`EKpub` with (optional) sender-asserted authorization
|
|
||||||
policy:
|
|
||||||
|
|
||||||
- [`send-to-tpm.sh`](send-to-tpm.sh)
|
- The "EK" method of encryption to a TPM uses
|
||||||
- [`tpm-receive.sh`](tpm-receive.sh)
|
[`TPM2_MakeCredential()`](/TPM-Commands/TPM2_MakeCredential.md) and
|
||||||
|
[`TPM2_ActivateCredential()`](/TPM-Commands/TPM2_ActivateCredential.md)
|
||||||
|
with a hard-coded, _well-known_ activation key (`WK`) to implement
|
||||||
|
encryption-to-`EKpub` with (optional) sender-asserted authorization
|
||||||
|
policy.
|
||||||
|
|
||||||
|
- The "TK" method of encryption to a TPM uses a software implementation
|
||||||
|
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
331
Enrollment/send-to-tpm
Executable 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
|
|
@ -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
284
Enrollment/tpm-receive
Executable 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
|
|
@ -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"
|
|
Loading…
Reference in a new issue