osresearch / safeboot

Scripts to slightly improve the security of the Linux boot process with UEFI Secure Boot and TPM support
https://safeboot.dev/
GNU General Public License v2.0
268 stars 28 forks source link

Complex policy expressions #134

Closed nicowilliams closed 2 years ago

nicowilliams commented 2 years ago

This allows one to express complex policies (ones with alternations) using a simple language.

The pattern is simply that conjunctions are tpm2 policy* command-lines joined with a ';' argument, and alternations are tpm2 policyor commands with arguments that are themselves policies surrounded by '(' and ')' arguments.

For example:

    $ sbin/tpm2-policy                          \
    tpm2 policyor                           \
        '(' tpm2 policycommandcode TPM2_CC_Sign ')'         \
        '(' tpm2 policycommandcode TPM2_CC_RSA_Decrypt ')' ';'  \
    tpm2 policypcr -l "sha256:11"

which allows an entity sporting such a policy to be used for signing or decryption only, and only when PCR#11 is cleared.

The same, verbosely:

    $ sbin/tpm2-policy -v                       \
    tpm2 policyor                           \
        '(' tpm2 policycommandcode TPM2_CC_Sign ')'         \
        '(' tpm2 policycommandcode TPM2_CC_RSA_Decrypt ')' ';'  \
    tpm2 policypcr -l "sha256:11"
        Running: (AND) exec_policyOR_helper ( tpm2 policycommandcode TPM2_CC_Sign ) ( tpm2 policycommandcode TPM2_CC_RSA_Decrypt ) ;
            Running: (AND) tpm2 policycommandcode --session /tmp/tmp.jbXmSKOtzR/session-0-0 --policy /tmp/tmp.jbXmSKOtzR/policy-0-0 TPM2_CC_Sign
    cc6918b226273b08f5bd406d7f10cf160f0a7d13dfd83b7770ccbcd1aa80d811
    cc6918b226273b08f5bd406d7f10cf160f0a7d13dfd83b7770ccbcd1aa80d811
            Running: (AND) tpm2 policycommandcode --session /tmp/tmp.jbXmSKOtzR/session-0-1 --policy /tmp/tmp.jbXmSKOtzR/policy-0-1 TPM2_CC_RSA_Decrypt
    3c29869a1312094782b86df5a430caae587f5dfb16dfa3f7204151054c1340d2
    3c29869a1312094782b86df5a430caae587f5dfb16dfa3f7204151054c1340d2
        ORing: cc6918b226273b08f5bd406d7f10cf160f0a7d13dfd83b7770ccbcd1aa80d811 3c29869a1312094782b86df5a430caae587f5dfb16dfa3f7204151054c1340d2
        Running: tpm2 policyor --session /tmp/tmp.jbXmSKOtzR/session --policy /tmp/tmp.jbXmSKOtzR/policy sha256:/tmp/tmp.jbXmSKOtzR/policy-0-0,/tmp/tmp.jbXmSKOtzR/policy-0-1
    39faada14fb6d5deba4315d8bce0247813262ebf15e1aee35477d26759f1b29e
        Running: (AND) tpm2 policypcr --session /tmp/tmp.jbXmSKOtzR/session --policy /tmp/tmp.jbXmSKOtzR/policy -l sha256:11
    3b9ea8dca851fac5d077d7b6925b8cc38847619e4b9a0f026ca5cc6262ff8a1c
    3b9ea8dca851fac5d077d7b6925b8cc38847619e4b9a0f026ca5cc6262ff8a1c
    $

One can also execute a policy in a policy session using sbin/tpm2-policy:

: ; /safeboot/sbin/tpm2-policy -e sessdup                                       \
        tpm2 policyor '(' tpm2 policycommandcode TPM2_CC_Duplicate ';'          \
                          tpm2 policysecret --object-context primary.ctx ')'    \
                      '(' tpm2 policycommandcode TPM2_CC_Sign ')'
bef56b8c1cc84e11edd717528d2cd99356bd2bbf8f015209c3f84aeeaba8e8a2
6152da7336d14b50adc37165c3b3a75af43426f7fb285b14e0f3e7145c8abf97
6152da7336d14b50adc37165c3b3a75af43426f7fb285b14e0f3e7145c8abf97
cc6918b226273b08f5bd406d7f10cf160f0a7d13dfd83b7770ccbcd1aa80d811
cc6918b226273b08f5bd406d7f10cf160f0a7d13dfd83b7770ccbcd1aa80d811
30f6e1c393911768102f2a8a1ca5218eb6b837e12527d97cc38aae12027c7c9b
30f6e1c393911768102f2a8a1ca5218eb6b837e12527d97cc38aae12027c7c9b

Internally we have exec_policy in functions.sh that can execute a policy in either a trial session or in policy session, and if in a policy session then the caller must supply the indices of alternatives to take in the policy, otherwise the caller must not supply those.

I.e.,

    # Compute policyDigest of the policy above:
    tpm2_flushall
    tpm2 startauthsession --session session
    exec_policy session policy                      \
    tpm2 policyor                           \
        '(' tpm2 policycommandcode TPM2_CC_Sign ')'         \
        '(' tpm2 policycommandcode TPM2_CC_RSA_Decrypt ')' ';'  \
    tpm2 policypcr -l "sha256:11"
    # Execute the policy above with the second alternative:
    tpm2_flushall
    tpm2 startauthsession --session session
    exec_policy 1 session policy                    \
    tpm2 policyor                           \
        '(' tpm2 policycommandcode TPM2_CC_Sign ')'         \
        '(' tpm2 policycommandcode TPM2_CC_RSA_Decrypt ')' ';'  \
    tpm2 policypcr -l "sha256:11"

This is recursive, so it generalizes to policies of arbitrary complexity, limited to nesting alternations up to nine (9) times, or the shell's recursion limit if it be lower.

nicowilliams commented 2 years ago

It's possible that the -e option won't work for policies with several more alternations than I tried.

The correct way to evaluate a policy with alternations would be to first evaluate all the alternatives not-taken in trial sessions, flushing loaded sessions as one goes, then start a policy session for all the taken alternatives and execute those, with any tpm2 policyors referring to not-taken alternatives by their already-computed policyDigests. This way there is no risk of running out of loaded session handle space on the TPM.

nicowilliams commented 2 years ago

The correct way to evaluate a policy with alternations would be to first evaluate all the alternatives not-taken in trial sessions, flushing loaded sessions as one goes, then start a policy session for all the taken alternatives and execute those, with any tpm2 policyors referring to not-taken alternatives by their already-computed policyDigests. This way there is no risk of running out of loaded session handle space on the TPM.

Fixed. Now the policy is first executed in trial sessions (one per alternation + one for the top-level), the policy digests are saved, then the chosen alternatives are executed in a single policy session and the previously computed policy digests are used for the alternatives not-taken. This minimizes the number of saved session contexts used that must be available concurrently.

EDIT: Well... the current implementation uses N trial sessions concurrently, where N is the alternation depth. This could be reduced to 1 by calling first executing all the leaf alternations, then their parents, and so on.

nicowilliams commented 2 years ago

I don't know how to fix this:

In /github/workspace/tests/test-enroll.sh line 16:
. "$TOP/functions.sh"
  ^-----------------^ SC1091: Not following: functions.sh was not specified as input (see shellcheck -x).

The make shellcheck target wasn't even listing that file, but even now that I added it...

nicowilliams commented 2 years ago

@osresearch has requested making the language pithier by not requiring that every sub-command be tpm2_policy* or tpm2 policy*. So one would write tpm2-policy secret ... instead of tpm2-policy tpm2 policysecret ....

nicowilliams commented 2 years ago

Also, clearly the language will need something like symbolic bindings for user-provided items, and/or maybe inlined literals like public keys.

For example, suppose a policy uses TPM2_PolicySigned(), and needs to specify a public key for the signer... The key in that case might need to be inlined in the policy language as a literal (line-joined PEM?) or as a reference to a literal that follows the policy in the same file (like a here-doc), and since we might need several such literals, we might need to name them and be able to refer to them by $name in the sub-commands. Items such as policyRefs might need to be caller-provided, in which case we'd need a way to refer to them by $name in the sub-commands, with a way for the user to provide them on the tpm2-policy command-line.

We then might as well have the alternatives-taken argument for policy session evaluation be given as a tpm2-policy command option.

nicowilliams commented 2 years ago

So we might have:

$ cat > policy.def
or ( secret endorsement ) ( signed $signer $policyRef )
--
signer <<EOS
<PEM of signer pubkey here>
EOS
^D
$ tpm2-policy -s session.ctx -L policy.dat -a 1 -V policyRef=abcd ./policy.def

or

$ cat > policy.def
or ( secret $entity ) ( signed $signer $policyRef )
--
entity: load <<EOPRIV <<EOPUB
<base64-encoded private part>
EOPRIV
<base64-encoded public part>
EOPUB
signer <<EOS
<PEM of signer pubkey here>
EOS
^D
$ tpm2-policy -s session.ctx -L policy.dat -a 1 -V policyRef=abcd ./policy.def

Any prompting for passwords/authValues would have to be handled too.

nicowilliams commented 2 years ago

@williamcroberts maybe tpm2-tools could use something like this, a policy language for EA policies. I'm just playing here, and currently it's really half-baked.

@kgoldman maybe we can even design an EA policy language that TCG might standardize?

williamcroberts commented 2 years ago

@williamcroberts maybe tpm2-tools could use something like this, a policy language for EA policies. I'm just playing here, and currently it's really half-baked.

@kgoldman maybe we can even design an EA policy language that TCG might standardize?

It already exists inside of FAPI and the TSS WG is actually working on pulling that out of FAPI and standardizing the policy language and a library for handling it in a separate spec.

nicowilliams commented 2 years ago

@williamcroberts maybe tpm2-tools could use something like this, a policy language for EA policies. I'm just playing here, and currently it's really half-baked. @kgoldman maybe we can even design an EA policy language that TCG might standardize?

It already exists inside of FAPI and the TSS WG is actually working on pulling that out of FAPI and standardizing the policy language and a library for handling it in a separate spec.

Oh! Ok, I'll take a look. Thanks for the tip.

williamcroberts commented 2 years ago

@williamcroberts maybe tpm2-tools could use something like this, a policy language for EA policies. I'm just playing here, and currently it's really half-baked. @kgoldman maybe we can even design an EA policy language that TCG might standardize?

It already exists inside of FAPI and the TSS WG is actually working on pulling that out of FAPI and standardizing the policy language and a library for handling it in a separate spec.

Oh! Ok, I'll take a look. Thanks for the tip.

FYI theirs also a policy generator GUI: https://tpm2-software.github.io/fapipolicies/

nicowilliams commented 2 years ago

@williamcroberts does the tpm2-tools stack support FAPI JSON policies?

williamcroberts commented 2 years ago

@williamcroberts does the tpm2-tools stack support FAPI JSON policies?

IIUC, The tss2 prefixed tools do. However, we wanted to create a new tpm2 policy tool that takes the engine out of FAPI and supports it.

nicowilliams commented 2 years ago

IIUC, The tss2 prefixed tools do. However, we wanted to create a new tpm2 policy tool that takes the engine out of FAPI and supports it.

Yes. In fact, I'm thinking of writing a bash + jq script to do just that using existing tpm2_* commands.

I'm not keen on the tss2 tools' abstraction of "let's pretend there's a database of metadata-rich entities". I really like the very thin tpm2 tools' abstraction of just adding automatic context save/load to TPM2_*() commands. The tss2 tool approach cannot and, really, MUST NOT know about everything a TPM is used for, so this idea of storing a DB of extra metadata in /etc/ just does not work. The tpm2 tooling's thin abstraction means the user is on the hook for flushing contexts out of the TPM (though that too could be automated), but otherwise by being such a thin abstraction I can focus on the TCG TPM 2.0 Library parts 1 and 3 and easily build complex applications out of simple tools.

williamcroberts commented 2 years ago

IIUC, The tss2 prefixed tools do. However, we wanted to create a new tpm2 policy tool that takes the engine out of FAPI and supports it.

Yes. In fact, I'm thinking of writing a bash + jq script to do just that using existing tpm2_* commands.

I'm not keen on the tss2 tools' abstraction of "let's pretend there's a database of metadata-rich entities". I really like the very thin tpm2 tools' abstraction of just adding automatic context save/load to TPM2_*() commands. The tss2 tool approach cannot and, really, MUST NOT know about everything a TPM is used for, so this idea of storing a DB of extra metadata in /etc/ just does not work. The tpm2 tooling's thin abstraction means the user is on the hook for flushing contexts out of the TPM (though that too could be automated), but otherwise by being such a thin abstraction I can focus on the TCG TPM 2.0 Library parts 1 and 3 and easily build complex applications out of simple tools.

The policy engine code in FAPI that we can rip out just calls callback functions that would be plumbed to TPM commands. So it would keep that thin abstraction while automatically reading the json policy file. It keeps one from having to write a bash + jq thing. I can take a look at ripping this library out today.

williamcroberts commented 2 years ago

Ahh ifapi_policyutil_execute_prepare depends on a FAPI context, and we want to break that dependency. Let me bring this up with Andreas and see what we can come up with.

nicowilliams commented 2 years ago

Ahh ifapi_policyutil_execute_prepare depends on a FAPI context, and we want to break that dependency. Let me bring this up with Andreas and see what we can come up with.

Sure. But I think I can go with jq + bash for now. Basically a bash script that calls a jq program to emit a set of tpm2 commands to eval in order using the jq @sh shell quoting feature.

williamcroberts commented 2 years ago

Ahh ifapi_policyutil_execute_prepare depends on a FAPI context, and we want to break that dependency. Let me bring this up with Andreas and see what we can come up with.

Sure. But I think I can go with jq + bash for now. Basically a bash script that calls a jq program to emit a set of tpm2 commands to eval in order using the jq @sh shell quoting feature.

I was hoping to spur development of a permanent tool so you don't have to maintain both things. You also pick up TPM-less policy calculations.

nicowilliams commented 2 years ago

I was hoping to spur development of a permanent tool so you don't have to maintain both things. You also pick up TPM-less policy calculations.

I'm starting to think that software operations (make credential, trial policies, duplicate) should be implemented in a completely separate codebase. The reason for this is that it could be a very small and clean codebase that way.

As for executing policies from a description language, I do think too that scripting with jq is very tempting as a first prototype, and if the command-line tools are good enough, why not?

williamcroberts commented 2 years ago

I was hoping to spur development of a permanent tool so you don't have to maintain both things. You also pick up TPM-less policy calculations.

I'm starting to think that software operations (make credential, trial policies, duplicate) should be implemented in a completely separate codebase. The reason for this is that it could be a very small and clean codebase that way.

I thought you guys had a script for this no?

As for executing policies from a description language, I do think too that scripting with jq is very tempting as a first prototype, and if the command-line tools are good enough, why not?

prototype yes, i'm trying to spur long term thoughts here and conformity so theirs not 10 ways to do something. Since their already is a format for policies, building something to do that generically for all users has a promising future and would reduce your overall maintenance burden.

AndreasFuchsTPM commented 2 years ago

Hi all... Jumping onto the train here. We already have a running implementation of a policy calculation and execution inside the tss (used internally by libtss2-fapi). That being said, we made sure that this module does not have any other internal dependencies (besides some helpers, but nothing state related), because we already planned for making this standalone at some point.

Long story short, here are the three function we defined (that are knowing-working code) that we could extract into a separate library: https://github.com/tpm2-software/tpm2-tss/blob/ec58f0a43931b18de9c3c5e0cd884892c218041a/src/tss2-fapi/ifapi_policy_instantiate.h#L73-L81 https://github.com/tpm2-software/tpm2-tss/blob/ec58f0a43931b18de9c3c5e0cd884892c218041a/src/tss2-fapi/ifapi_policy_calculate.h#L22-L28 https://github.com/tpm2-software/tpm2-tss/blob/ec58f0a43931b18de9c3c5e0cd884892c218041a/src/tss2-fapi/ifapi_policy_execute.h#L161-L170

Would you be willing to spend some effort on helping me extract the code into a separate library instead of coding a second implementation ?

williamcroberts commented 2 years ago

Hi all... Jumping onto the train here. We already have a running implementation of a policy calculation and execution inside the tss (used internally by libtss2-fapi). That being said, we made sure that this module does not have any other internal dependencies (besides some helpers, but nothing state related), because we already planned for making this standalone at some point.

Long story short, here are the three function we defined (that are knowing-working code) that we could extract into a separate library: https://github.com/tpm2-software/tpm2-tss/blob/ec58f0a43931b18de9c3c5e0cd884892c218041a/src/tss2-fapi/ifapi_policy_instantiate.h#L73-L81 https://github.com/tpm2-software/tpm2-tss/blob/ec58f0a43931b18de9c3c5e0cd884892c218041a/src/tss2-fapi/ifapi_policy_calculate.h#L22-L28 https://github.com/tpm2-software/tpm2-tss/blob/ec58f0a43931b18de9c3c5e0cd884892c218041a/src/tss2-fapi/ifapi_policy_execute.h#L161-L170

Would you be willing to spend some effort on helping me extract the code into a separate library instead of coding a second implementation ?

in progress...

williamcroberts commented 2 years ago

So I have something early started:

Theirs a lot of todos and its very raw and only partially working. But I'd like to get feedback early on direction.

nicowilliams commented 2 years ago

I'm toying with a scheme like so:

For EAs the alternatives to execute would be given to sbin/tpm2-policy as an argument but communicated to the rbash and rbin scripts via env vars. $TMPDIR would be where to put temp items, as one would expect. $TMPDIR/policy-session.ctx would be the policy session. Etc.

nicowilliams commented 2 years ago

Superseded by #144.