Note: This is a public test instance of Red Hat Bugzilla. The data contained within is a snapshot of the live data so any changes you make will not be reflected in the production Bugzilla. Email is disabled so feel free to test any aspect of the site that you want. File any problems you find or give feedback at bugzilla.redhat.com.
Bug 1762421 - Feature request: support for TPM 2.0 Authorized Policies in Clevis TPM Pin
Summary: Feature request: support for TPM 2.0 Authorized Policies in Clevis TPM Pin
Keywords:
Status: CLOSED NEXTRELEASE
Alias: None
Product: Fedora
Classification: Fedora
Component: clevis
Version: 31
Hardware: x86_64
OS: Linux
unspecified
unspecified
Target Milestone: ---
Assignee: Sergio Correia
QA Contact: Fedora Extras Quality Assurance
URL:
Whiteboard:
Depends On:
Blocks: IoT
TreeView+ depends on / blocked
 
Reported: 2019-10-16 17:12 UTC by nicolasoliver03
Modified: 2020-08-25 10:36 UTC (History)
8 users (show)

Fixed In Version: clevis-pin-tpm2-0.1.2-1.fc33
Doc Type: If docs needed, set a value
Doc Text:
Clone Of:
Environment:
Last Closed: 2020-08-25 10:36:41 UTC
Type: Bug
Embargoed:


Attachments (Terms of Use)

Description nicolasoliver03 2019-10-16 17:12:17 UTC
Today, Clevis supports "static" PCR Policies through the TPM Pin. This makes using the TPM much more easier for regular users, replacing all the TPM2 Tools command with a single instruction for encryption and decryption.

Unfortunately, by using this strategy, a future update of binaries that affects the values of the PCR banks selected in the policy can cause the inability to unseal the secrets previously sealed to those PCRs. This is called PCR fragility (or PCR brittleness).

Fragility of PCR values was one of the most annoying problems with the 1.0 family of TPMs. Both keys and data can be locked to certain PCRs having particular values. But if keys or data are locked to a PCR that represents the BIOS of a system, it's tricky to upgrade the BIOS. Typically, before a BIOS upgrade was performed on TPM 1.2 systems, all secrets locked to PCR 0, for example (which represents the BIOS), had to be unsealed and then resealed after the upgrade was done. This is both a manageability nightmare and a nuisance to users.

In the TPM 2.0 specification, you can seal things to a PCR value approved by a particular signer instead of to a particular PCR value (although this can still be done if you wish). That is, you can have the TPM release a secret only if PCRs are in a state approved (via a digital signature) by a particular authority.

With the TPM2 Tools 4.0 release, Authorized Policies support is provided thorough the tpm2_policyauthorize command.
This allows to setup a policy that can be updated in the future to react to system updates that affects the PCR. The process to use tpm2_policyauthorize is a little bit complex, but I think Clevis can replace that complexity as it already did with tpm2_policypcr.

The way to use Authorized Policies today is specified below in that long script. The script was tested against the following versions

    TPM2_SIMULATOR_VERSION=1332
    TPM2_TSS_VERSION=2.3.1
    TPM2_ABRMD_VERSION=2.2.0
    TPM2_TOOLS_VERSION=4.0

NOTE: This is a test similar to the abrmd_policyauthorize.sh
test in the tpm2-software/tpm2-tools repo (thank you @idesai for your help). It uses a simulator, and can be executed in a Docker container. It does not uses the resource manager, and that is why there are several tpm2_flushcontext --transient commands issued. The resource manager aware version would be a little shorter.

    # Simulator Setup
    tpm_server -rm > /dev/null 2>&1 &
    TPM2_SERVER_PID=$!
    sleep 1
    export TPM2TOOLS_TCTI=mssim
    tpm2_startup --clear
    
    # Create a PCR policy
    tpm2_startauthsession --session session.ctx
    tpm2_policypcr \
        --session session.ctx \
        --pcr-list sha256:0,1 \
        --policy pcr.policy
    tpm2_flushcontext session.ctx
    rm -f session.ctx
    
    # Generate public/private key pair for signing
    openssl genrsa -out signing.priv.pem
    openssl rsa \
        -in signing.priv.pem \
        -out signing.pub.pem \
        -pubout
    
    # Loading the public key in TPM
    tpm2_loadexternal \
        --key-algorithm rsa \
        --hierarchy o \
        --public signing.pub.pem \
        --key-context signing.key.ctx \
        --name signing.key.name
    
    # Flush transient objects
    tpm2_flushcontext --transient
    
    # Create an authorized policy
    tpm2_startauthsession --session session.ctx
    tpm2_policyauthorize \
        --session session.ctx \
        --policy authorized.policy \
        --name signing.key.name \
        --input pcr.policy
    tpm2_flushcontext session.ctx
    rm -f session.ctx signing.key.ctx
    
    # Create the TPM object that holds the secret
    echo "Secret" > secret.txt
    tpm2_createprimary \
        --hierarchy o \
        --hash-algorithm sha256 \
        --key-algorithm rsa \
        --key-context primary.ctx
    tpm2_create \
        --parent-context primary.ctx \
        --hash-algorithm sha256 \
        --public key.obj.pub \
        --private key.obj.priv \
        --sealing-input secret.txt \
        --policy authorized.policy
    
    # Flush transient objects
    tpm2_flushcontext --transient
    
    # Load the generated object in the TPM
    tpm2_load \
        --parent-context primary.ctx \
        --public key.obj.pub \
        --private key.obj.priv \
        --name key.obj.name \
        --key-context key.obj.ctx
    
    # Persist the TPM Object
    tpm2_evictcontrol \
        --hierarchy o \
        --object-context key.obj.ctx \
        0x81010001
    
    # Flush transient objects
    tpm2_flushcontext --transient
    
    # Sign the PCR policy
    openssl dgst \
        -sign signing.priv.pem \
        -out pcr.policy.signature \
        pcr.policy
    
    # Load the keys to be used in signature verification
    tpm2_loadexternal \
        --key-algorithm rsa \
        --hierarchy o \
        --public signing.pub.pem \
        --key-context signing.key.ctx \
        --name signing.key.name
    
    # Verify the signature
    tpm2_verifysignature \
        --ticket verification.tkt \
        --key-context signing.key.ctx \
        --hash-algorithm sha256 \
        --message pcr.policy \
        --signature pcr.policy.signature \
        --format rsassa
    
    # Flush transient objects
    tpm2_flushcontext --transient
    
    # Unseal the TPM secret using the Authorized PCR Policy
    tpm2_startauthsession \
        --policy-session \
        --session session.ctx
    tpm2_policypcr \
        --session session.ctx \
        --pcr-list sha256:0,1
    tpm2_policyauthorize \
        --session session.ctx \
        --policy authorized.policy \
        --input pcr.policy \
        --name signing.key.name \
        --ticket verification.tkt
    tpm2_unseal \
        --object-context 0x81010001 \
        --auth session:session.ctx
    tpm2_flushcontext session.ctx
    rm -f session.ctx

A negative test of this setup is shown below. In this case, the PCR 0 is updated, and the secret cannot be unsealed again. This will be the current behavior with the "static" PCR policies.

    # Negative Test: after pcr extend unsealing should fail
    tpm2_pcrextend 0:sha256=0000000000000000000000000000000000000000000000000000000000000000
    tpm2_startauthsession \
        --policy-session \
        --session session.ctx
    tpm2_policypcr \
        --session session.ctx \
        --pcr-list sha256:0,1
    tpm2_policyauthorize \
        --session session.ctx \
        --policy authorized.policy \
        --input pcr.policy \
        --name signing.key.name \
        --ticket verification.tkt || echo 'Failed'
    tpm2_flushcontext session.ctx
    rm -f session.ctx
    
    # Flush transient objects
    tpm2_flushcontext --transient

And here, the update scenario, where a new valid signed policy is provided for unsealing:

    # Update Test: Providing a new policy with updated values should work
    tpm2_startauthsession --session session.ctx
    tpm2_policypcr \
        --session session.ctx \
        --pcr-list sha256:0,1 \
        --policy newpcr.policy
    tpm2_flushcontext session.ctx
    rm -f session.ctx

    # Update Test: Sign new policy with updated values
    openssl dgst \
        -sign signing.priv.pem \
        -out newpcr.policy.signature \
        newpcr.policy

    # Update Test: Get new verification ticket for the policy
    tpm2_verifysignature \
        --ticket newverification.tkt \
        --key-context signing.key.ctx \
        --hash-algorithm sha256 \
        --message newpcr.policy \
        --signature newpcr.policy.signature \
        --format rsassa

    # Update Test: Unseal with updated policy
    tpm2_startauthsession \
        --policy-session \
        --session session.ctx
    tpm2_policypcr \
        --session session.ctx \
        --pcr-list sha256:0,1
    tpm2_policyauthorize \
        --session session.ctx \
        --policy authorized.policy \
        --input newpcr.policy \
        --name signing.key.name \
        --ticket newverification.tkt
    tpm2_unseal \
        --object-context 0x81010001 \
        --auth session:session.ctx
    tpm2_flushcontext session.ctx
    rm -f session.ctx

Finally, some cleanup and shutdown instructions:

    # Removing persistent handles
    tpm2_getcap handles-persistent
    tpm2_evictcontrol \
        --hierarchy o \
        --object-context 0x81010001

    # Simulator Shutdown
    kill ${TPM2_SERVER_PID}

All this could be replaced with something similar to this in clevis:

    # Encrypt the  key with Clevis
    cat key.txt | clevis encrypt \
        tpm2 '{"pcr_bank":"sha256","pcr_ids":"0", "authorized_policy": true, "signing_auth": "public.key"}' > key.jwe

    # Decrypt the rsa key with Clevis, should work if PCRs are in expected state
    clevis decrypt < key.jwe > key.txt

    # Negative test
    # Extend PCR 0 and attempt to unseal, should fail
    tpm2_pcrextend 0:sha256=0000000000000000000000000000000000000000000000000000000000000000
    clevis decrypt < key.jwe || echo 'Expected to Fail'

    # Update test
    # Provide a new authorized policy contemplating the update, should work
    clevis decrypt tpm2 '{"policy": "authorized.policy" }' < key.jwe

    # Update test
    # Policy should be updated, and secret should be able to unseal
    clevis decrypt < key.jwe > key.txt

Is this something you could implement?

Thank you!

Comment 1 nicolasoliver03 2019-11-15 21:21:42 UTC
TPM2 Tools 4.0 is now available in F31 and F32: http://rpmfind.net/linux/RPM/fedora/devel/rawhide/x86_64/t/tpm2-tools-4.0.1-1.fc32.x86_64.html


Note You need to log in before you can comment on or make changes to this bug.