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
270 stars 28 forks source link

Policy Expressions, takes 2 and 3 #144

Open nicowilliams opened 3 years ago

nicowilliams commented 3 years ago
             WIP WIP WIP -- minimally tested

Let's try a different approach to expressing complex policies:

One possible and interesting policy would be to use policyauthorize to allow a policy to be pluggable, but to have the signer only sign policy plugins that contain policysigned w/ nonceTpm. This is interesting because TPM2_PolicyAuthorize() does not provide any way to expire or revoke signatures, but TPM2_PolicySigned() does have a way to strongly bind a signature to a particular session (via nonceTpm inclusion in the hash extended into the policyDigest), and also a way to expire the signature (via time relative to the start of the session).

Use of keys TPM2_Duplicate()ed to the TPM to deliver authValues as passwords for different users also seems interesting.

Such a policy would allow a laptop to boot normally with a user password, and after emergency updates boot with an admin OTP, or after any mishaps via a superadmin password, or with a separate policy that blesses the current PCRs as golden.

Such a policy could be used for sealing an NV index, or it could be set on local storage keys encrypted to the TPM's EKpub via sbin/tpm2-send, allowing for unattended server booting post-attestation, or attended server booting post-mishap (if the encrypted assets get stored "in the clear").

nicowilliams commented 3 years ago

Ok! I've a trivial test script to demo sbin/tpm2-policy.

nicowilliams commented 3 years ago

I've also added long options for bash scripts. I should probably separate getopts_long() into its own commit though.

Also, I should make a policyor() shell function so that alternations can be handled without having to run the policyor restricted bash scripting wrapper.

nicowilliams commented 3 years ago

So, I have explored enough that I can write a jq+bash+tpm2-tools thing now. A policy consists of: a) parameters, b) some (possibly none) bindings for those parameters, possibly referenced by URI, or possibly to be prompted for, c) the actual policy commands. Because one might want to create a policy as just a reference to another but with some parameters bound, a policy should itself be possible to refer to either by value or by URI, and, really, any subset of the policy should be possible to express either directly or by reference.

So we're looking at something like:

{
  "name":"...",
  "params":[...],
  "bindings":[...],
  "policy":"<URI>"
}

or

{
  "name":"...",
  "params":[...],
  "bindings":[...],
  "policy":[...]
}

Each policy command would be either a string containing a URI or an object naming the policy command and its command parameters, and each command parameter could be inlined or a reference to a parameter to the policy, or -in the case of PolicyOr- a sub-policy. URIs could be relative, in which case they might be defined elsewhere in the same JSON text or be a reference to another policy provided by the caller or lying around alongside the referrer.

Each parameter would be an object with a name and a type, and possibly some default content.

Each binding would be an object with a name of a parameter and contents for it.

Parameter types would include:

Perhaps a parameter could specify some default content but not all (e.g., hash and signature algorithms, but not signatures, naturally), so that a binding need only provide the missing parts.

References to parameters would become appropriate values of the appropriate types in the actual policy commands. E.g., object contexts might get loaded and referred to by handle, tickets might be passed in by value, etc.

PolicySigned and PolicySecret commands could be replaced automatically with PolicyTicket as needed.

Perhaps a policy could refer to or include another and specify alternatives for PolicyOr commands in the other. For example, one might have a policy that allows for N users to authenticate with a smartcard or password, say, and one might then define a policy that refers to that policy and specifies the PolicyOr alternatives identifying one particular user.

There would be two evaluation modes: trial (to compute a policyDigest for a policy), and authorization (to satisfy a policy and get access to a protected resource). For trial sessions fewer parameters may need bindings at evaluation time than for authorization sessions -- the evaluator needs to understand this specifically, and more generally needs to know which parameters need bindings and which don't. Indeed, an evaluator might choose alternatives to evaluate based on the parameters for which there are bindings, that or it must be told explicitly. The evaluator should then import and/or load all the TPM objects specified, save their contexts as a needed, flush TPM objects as needed, verify signatures, and load saved objects as needed right before executing policy commands that refer to them.

nicowilliams commented 3 years ago

Parameters have to go with the policy definitions. Default bindings can too. External bindings would be provided by other policies or command-line arguments.

Internal parameters would be a thing (tickets, for example, things output by TPM commands internal to the policy that are needed by other commands).

So maybe something more like:

{
  "name":"...",
  "bindings":[...],
  "policy": "<URI>"
}

or

{
  "name":"...",
  "bindings":[...],
  "policy": {
    "parameters":[...],
    "policyDef":[...]
}

A policyDef would be an array of:

{
  "command":"policyX",
  "cp<Name>":"<value> or else an object with a param reference",
  "...":"...",
  "rp<Name>":{"name":"<internal-param-name>","display":true},
  "...":...
}

A parameter reference would be:

{  "name":"<parameter-name>", "sub":"<sub-name>"  }

where <sub-name> might be something like "name", "hash-alg", "sign-alg", etc., for referring to an aspect of a parameter.

Policy holes (PolicyAuthorize, PolicyAuthorizeNV, and PolicyOr) have to be first. The alternatives to PolicyOr would be policies, either defined inline or referenced by URI.

A parameter needs:

We might want to make things like hash and signature algorithms ancillary properties of a parameter naming a key, or we might want to make them separate parameters. I think the former.

{
  "name":"...",
  "internal":false,
  "type":"TPM2B_PUBLIC",
  "default":"<base64-encoded>"
}

A binding might be a prompt for a value or an actual value. If the parameter is something like a cryptographic key, then the value might be multipart (one part might be the key, another might be the various associated algorithms).

nicowilliams commented 3 years ago

I'm also thinking that this rbash prototype may be good enough for now because it's... simple and easy.

nicowilliams commented 3 years ago

I've pushed a WIP of take 3, this time using JSON and jq, all inspired by take 2.

nicowilliams commented 3 years ago

https://trustedcomputinggroup.org/wp-content/uploads/TSS_JSON_Policy_v0p7_r08_pub.pdf

The code in take 3 is not implementing that.