Skip to content

Commit

Permalink
Merge pull request #103 from alexmwu/policy
Browse files Browse the repository at this point in the history
Add Policy object for server-side remote attestation
  • Loading branch information
alexmwu authored Oct 27, 2021
2 parents 71d789d + 065fd5e commit 83ded98
Show file tree
Hide file tree
Showing 18 changed files with 1,446 additions and 82 deletions.
30 changes: 22 additions & 8 deletions client/attest.go
Original file line number Diff line number Diff line change
@@ -1,24 +1,38 @@
package client

import (
"errors"
"fmt"

pb "github.com/google/go-tpm-tools/proto/attest"
)

// AttestOpts allows for optional Attest functionality to be enabled.
type AttestOpts interface{}
// AttestOpts allows for customizing the functionality of Attest.
type AttestOpts struct {
// A unique, application-specific nonce used to guarantee freshness of the
// attestation. This must not be empty, and should generally be long enough
// to make brute force attacks infeasible.
//
// For security reasons, applications should not allow for attesting with
// arbitrary, externally-provided nonces. The nonce should be prefixed or
// otherwise bound (i.e. via a KDF) to application-specific data. For more
// information on why this is an issue, see this paper on robust remote
// attestation protocols:
// https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.70.4562&rep=rep1&type=pdf
Nonce []byte
}

// Attest generates an Attestation containing the TCG Event Log and a Quote over
// all PCR banks. The provided nonce can be used to guarantee freshness of the
// attestation. This function will return an error if the key is not a
// restricted signing key.
//
// An optional AttestOpts can also be passed. Currently, this parameter must be nil.
func (k *Key) Attest(nonce []byte, opts AttestOpts) (*pb.Attestation, error) {
if opts != nil {
return nil, errors.New("provided AttestOpts must be nil")
// AttestOpts is used for additional configuration of the Attestation process.
// This is primarily used to pass the attestation's nonce:
//
// attestation, err := key.Attest(client.AttestOpts{Nonce: my_nonce})
func (k *Key) Attest(opts AttestOpts) (*pb.Attestation, error) {
if len(opts.Nonce) == 0 {
return nil, fmt.Errorf("provided nonce must not be empty")
}
sels, err := implementedPCRs(k.rw)
if err != nil {
Expand All @@ -30,7 +44,7 @@ func (k *Key) Attest(nonce []byte, opts AttestOpts) (*pb.Attestation, error) {
return nil, fmt.Errorf("failed to encode public area: %w", err)
}
for _, sel := range sels {
quote, err := k.Quote(sel, nonce)
quote, err := k.Quote(sel, opts.Nonce)
if err != nil {
return nil, err
}
Expand Down
6 changes: 3 additions & 3 deletions client/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ func ExampleKey_Attest() {
}
defer ak.Close()

attestation, err := ak.Attest(nonce, nil)
attestation, err := ak.Attest(client.AttestOpts{nonce})
if err != nil {
log.Fatalf("failed to attest: %v", err)
}
Expand All @@ -137,13 +137,13 @@ func ExampleKey_Attest() {
// On verifier, replay event log.
// TODO: decide which hash algorithm to use in the quotes. SHA1 is
// typically undesirable but is the only event log option on some distros.
_, err = server.ParseAndVerifyEventLog(attestation.EventLog, attestation.Quotes[0].Pcrs)
_, err = server.ParseMachineState(attestation.EventLog, attestation.Quotes[0].Pcrs)
if err != nil {
// TODO: handle parsing or replay error.
log.Fatalf("failed to read PCRs: %v", err)
}
fmt.Println(attestation)
// TODO: use events output of ParseAndVerifyEventLog.
// TODO: use events output of ParseMachineState.
}

func Example_sealAndUnseal() {
Expand Down
2 changes: 1 addition & 1 deletion client/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ func (k *Key) Unseal(in *pb.SealedBytes, opts UnsealOpts) ([]byte, error) {
func (k *Key) Quote(selpcr tpm2.PCRSelection, extraData []byte) (*pb.Quote, error) {
// Make sure that we have a valid signing key before trying quote
var err error
if _, err = getSigningHashAlg(k); err != nil {
if _, err = internal.GetSigningHashAlg(k.pubArea); err != nil {
return nil, err
}
if !k.hasAttribute(tpm2.FlagRestricted) {
Expand Down
2 changes: 1 addition & 1 deletion client/quote_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ func TestAttest(t *testing.T) {
}
defer ak.Close()

attestation, err := ak.Attest([]byte("some nonce"), nil)
attestation, err := ak.Attest(client.AttestOpts{[]byte("some nonce")})
if !key.shouldSucceed {
if err == nil {
t.Error("expected failure when calling Attest")
Expand Down
31 changes: 3 additions & 28 deletions client/signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"math/big"
"sync"

"github.com/google/go-tpm-tools/internal"
"github.com/google/go-tpm/tpm2"
)

Expand Down Expand Up @@ -77,7 +78,7 @@ func (k *Key) GetSigner() (crypto.Signer, error) {
if k.hasAttribute(tpm2.FlagRestricted) {
return nil, fmt.Errorf("restricted keys are not supported")
}
hashAlg, err := getSigningHashAlg(k)
hashAlg, err := internal.GetSigningHashAlg(k.pubArea)
if err != nil {
return nil, err
}
Expand All @@ -94,7 +95,7 @@ func (k *Key) GetSigner() (crypto.Signer, error) {
// on a restriced key, the TPM itself will hash the provided data, failing the
// signing operation if the data begins with TPM_GENERATED_VALUE.
func (k *Key) SignData(data []byte) ([]byte, error) {
hashAlg, err := getSigningHashAlg(k)
hashAlg, err := internal.GetSigningHashAlg(k.pubArea)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -130,32 +131,6 @@ func (k *Key) SignData(data []byte) ([]byte, error) {
return getSignature(sig)
}

func getSigningHashAlg(k *Key) (tpm2.Algorithm, error) {
if !k.hasAttribute(tpm2.FlagSign) {
return tpm2.AlgNull, fmt.Errorf("non-signing key used with signing operation")
}

var sigScheme *tpm2.SigScheme
switch k.pubArea.Type {
case tpm2.AlgRSA:
sigScheme = k.pubArea.RSAParameters.Sign
case tpm2.AlgECC:
sigScheme = k.pubArea.ECCParameters.Sign
default:
return tpm2.AlgNull, fmt.Errorf("unsupported key type: %v", k.pubArea.Type)
}

if sigScheme == nil {
return tpm2.AlgNull, fmt.Errorf("unsupported null signing scheme")
}
switch sigScheme.Alg {
case tpm2.AlgRSAPSS, tpm2.AlgRSASSA, tpm2.AlgECDSA:
return sigScheme.Hash, nil
default:
return tpm2.AlgNull, fmt.Errorf("unsupported signing algorithm: %v", sigScheme.Alg)
}
}

func getSignature(sig *tpm2.Signature) ([]byte, error) {
switch sig.Alg {
case tpm2.AlgRSASSA:
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module github.com/google/go-tpm-tools

// Move to new-style build tags when updating to 1.17
go 1.16

require (
Expand Down
35 changes: 35 additions & 0 deletions internal/public.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package internal

import (
"fmt"

"github.com/google/go-tpm/tpm2"
)

// GetSigningHashAlg returns the hash algorithm used for a signing key. Returns
// an error if an algorithm isn't supported, or the key is not a signing key.
func GetSigningHashAlg(pubArea tpm2.Public) (tpm2.Algorithm, error) {
if pubArea.Attributes&tpm2.FlagSign == 0 {
return tpm2.AlgNull, fmt.Errorf("non-signing key used with signing operation")
}

var sigScheme *tpm2.SigScheme
switch pubArea.Type {
case tpm2.AlgRSA:
sigScheme = pubArea.RSAParameters.Sign
case tpm2.AlgECC:
sigScheme = pubArea.ECCParameters.Sign
default:
return tpm2.AlgNull, fmt.Errorf("unsupported key type: %v", pubArea.Type)
}

if sigScheme == nil {
return tpm2.AlgNull, fmt.Errorf("unsupported null signing scheme")
}
switch sigScheme.Alg {
case tpm2.AlgRSAPSS, tpm2.AlgRSASSA, tpm2.AlgECDSA:
return sigScheme.Hash, nil
default:
return tpm2.AlgNull, fmt.Errorf("unsupported signing algorithm: %v", sigScheme.Alg)
}
}
74 changes: 74 additions & 0 deletions proto/attest.proto
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,77 @@ message Attestation {
// Optional information about a GCE instance, unused outside of GCE
GCEInstanceInfo instance_info = 4;
}

// Type of hardware technology used to protect this instance
enum GCEConfidentialTechnology {
NONE = 0;
AMD_SEV = 1;
AMD_SEV_ES = 2;
}

// The platform/firmware state for this instance
message PlatformState {
oneof firmware {
// Raw S-CRTM version identifier (EV_S_CRTM_VERSION)
bytes scrtm_version_id = 1;
// Virtual GCE firmware version (parsed from S-CRTM version id)
uint32 gce_version = 2;
}
// Set to NONE on non-GCE instances or non-Confidential Shielded GCE instances
GCEConfidentialTechnology technology = 3;
// Only set for GCE instances
GCEInstanceInfo instance_info = 4;
}

// A parsed event from the TCG event log
message Event {
// The Platform Control Register (PCR) this event was extended into.
uint32 pcr_index = 1;
// The type of this event. Note that this value is not verified, so it should
// only be used as a hint during event parsing.
uint32 untrusted_type = 2;
// The raw data associated to this event. The meaning of this data is
// specific to the type of the event.
bytes data = 3;
// The event digest actually extended into the TPM. This is often the hash of
// the data field, but in some cases it may have a type-specific calculation.
bytes digest = 4;
// This is true if hash(data) == digest.
bool digest_verified = 5;
}

// The verified state of a booted machine, obtained from an Attestation
message MachineState {
PlatformState platform = 1;

// SecureBootState secure_boot = 2;

// The complete parsed TCG Event Log, including those events used to
// create the PlatformState.
repeated Event raw_events = 3;
// The hash algorithm used when verifying the Attestation. This indicates:
// - which PCR bank was used for for quote validation and event log replay
// - the hash algorithm used to calculate event digests
tpm.HashAlgo hash = 4;
}

// A policy dictating which values of PlatformState to allow
message PlatformPolicy {
// If PlatformState.firmware contains a scrtm_version_id, it must appear
// in this list. For use with a GCE VM, minimum_gce_firmware_version is
// often a better alternative.
repeated bytes allowed_scrtm_version_ids = 1;
// If PlatformState.firmware contains a minimum_gce_firmware_version, it must
// be greater than or equal to this value. Currently, the max version is 1.
uint32 minimum_gce_firmware_version = 2;
// The PlatformState's technology must be at least as secure as
// the specified minimum_technology (i.e. AMD_SEV_ES > AMD_SEV > NONE).
GCEConfidentialTechnology minimum_technology = 3;
}

// A policy dictating which type of MachineStates to allow
message Policy {
PlatformPolicy platform = 1;

// SecureBootPolicy secure_boot = 2;
}
Loading

0 comments on commit 83ded98

Please sign in to comment.