Vendor dependencies and expose control program binaries via nix build. Uses nixpkgs-unstable for Go 1.26 support. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
997 lines
29 KiB
Go
997 lines
29 KiB
Go
package tpm2
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/aes"
|
|
"crypto/cipher"
|
|
"crypto/hmac"
|
|
"crypto/rand"
|
|
"encoding/binary"
|
|
"fmt"
|
|
|
|
"github.com/google/go-tpm/tpm2/transport"
|
|
)
|
|
|
|
// Session represents a session in the TPM.
|
|
type Session interface {
|
|
// Initializes the session, if needed. Has no effect if not needed or
|
|
// already done. Some types of sessions may need to be initialized
|
|
// just-in-time, e.g., to support calling patterns that help the user
|
|
// securely authorize their actions without writing a lot of code.
|
|
Init(tpm transport.TPM) error
|
|
// Cleans up the session, if needed.
|
|
// Some types of session need to be cleaned up if the command failed,
|
|
// again to support calling patterns that help the user securely
|
|
// authorize their actions without writing a lot of code.
|
|
CleanupFailure(tpm transport.TPM) error
|
|
// The last nonceTPM for this session.
|
|
NonceTPM() TPM2BNonce
|
|
// Updates nonceCaller to a new random value.
|
|
NewNonceCaller() error
|
|
// Computes the authorization HMAC for the session.
|
|
// If this is the first authorization session for a command, and
|
|
// there is another session (or sessions) for parameter
|
|
// decryption and/or encryption, then addNonces contains the
|
|
// nonceTPMs from each of them, respectively (see Part 1, 19.6.5)
|
|
Authorize(cc TPMCC, parms, addNonces []byte, names []TPM2BName, authIndex int) (*TPMSAuthCommand, error)
|
|
// Validates the response for the session.
|
|
// Updates NonceTPM for the session.
|
|
Validate(rc TPMRC, cc TPMCC, parms []byte, names []TPM2BName, authIndex int, auth *TPMSAuthResponse) error
|
|
// Returns true if this is an encryption session.
|
|
IsEncryption() bool
|
|
// Returns true if this is a decryption session.
|
|
IsDecryption() bool
|
|
// If this session is used for parameter decryption, encrypts the
|
|
// parameter. Otherwise, does not modify the parameter.
|
|
Encrypt(parameter []byte) error
|
|
// If this session is used for parameter encryption, encrypts the
|
|
// parameter. Otherwise, does not modify the parameter.
|
|
Decrypt(parameter []byte) error
|
|
// Returns the handle value of this session.
|
|
Handle() TPMHandle
|
|
}
|
|
|
|
// CPHash calculates the TPM command parameter hash for a given Command.
|
|
// N.B. Authorization sessions on handles are ignored, but names aren't.
|
|
func CPHash[R any](alg TPMIAlgHash, cmd Command[R, *R]) (*TPM2BDigest, error) {
|
|
cc := cmd.Command()
|
|
names, err := cmdNames(cmd)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
parms, err := cmdParameters(cmd, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
digest, err := cpHash(alg, cc, names, parms)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &TPM2BDigest{
|
|
Buffer: digest,
|
|
}, nil
|
|
}
|
|
|
|
// pwSession represents a password-pseudo-session.
|
|
type pwSession struct {
|
|
auth []byte
|
|
}
|
|
|
|
// PasswordAuth assembles a password pseudo-session with the given auth value.
|
|
func PasswordAuth(auth []byte) Session {
|
|
return &pwSession{
|
|
auth: auth,
|
|
}
|
|
}
|
|
|
|
// Init is not required and has no effect for a password session.
|
|
func (s *pwSession) Init(_ transport.TPM) error { return nil }
|
|
|
|
// Cleanup is not required and has no effect for a password session.
|
|
func (s *pwSession) CleanupFailure(_ transport.TPM) error { return nil }
|
|
|
|
// NonceTPM normally returns the last nonceTPM value from the session.
|
|
// Since a password session is a pseudo-session with the auth value stuffed
|
|
// in where the HMAC should go, this is not used.
|
|
func (s *pwSession) NonceTPM() TPM2BNonce { return TPM2BNonce{} }
|
|
|
|
// NewNonceCaller updates the nonceCaller for this session.
|
|
// Password sessions don't have nonces.
|
|
func (s *pwSession) NewNonceCaller() error { return nil }
|
|
|
|
// Computes the authorization structure for the session.
|
|
func (s *pwSession) Authorize(_ TPMCC, _, _ []byte, _ []TPM2BName, _ int) (*TPMSAuthCommand, error) {
|
|
return &TPMSAuthCommand{
|
|
Handle: TPMRSPW,
|
|
Nonce: TPM2BNonce{},
|
|
Attributes: TPMASession{},
|
|
Authorization: TPM2BData{
|
|
Buffer: s.auth,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// Validates the response session structure for the session.
|
|
func (s *pwSession) Validate(_ TPMRC, _ TPMCC, _ []byte, _ []TPM2BName, _ int, auth *TPMSAuthResponse) error {
|
|
if len(auth.Nonce.Buffer) != 0 {
|
|
return fmt.Errorf("expected empty nonce in response auth to PW session, got %x", auth.Nonce)
|
|
}
|
|
expectedAttrs := TPMASession{
|
|
ContinueSession: true,
|
|
}
|
|
if auth.Attributes != expectedAttrs {
|
|
return fmt.Errorf("expected only ContinueSession in response auth to PW session, got %v", auth.Attributes)
|
|
}
|
|
if len(auth.Authorization.Buffer) != 0 {
|
|
return fmt.Errorf("expected empty HMAC in response auth to PW session, got %x", auth.Authorization)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// IsEncryption returns true if this is an encryption session.
|
|
// Password sessions can't be used for encryption.
|
|
func (s *pwSession) IsEncryption() bool { return false }
|
|
|
|
// IsDecryption returns true if this is a decryption session.
|
|
// Password sessions can't be used for decryption.
|
|
func (s *pwSession) IsDecryption() bool { return false }
|
|
|
|
// If this session is used for parameter decryption, encrypts the
|
|
// parameter. Otherwise, does not modify the parameter.
|
|
// Password sessions can't be used for decryption.
|
|
func (s *pwSession) Encrypt(_ []byte) error { return nil }
|
|
|
|
// If this session is used for parameter encryption, encrypts the
|
|
// parameter. Otherwise, does not modify the parameter.
|
|
// Password sessions can't be used for encryption.
|
|
func (s *pwSession) Decrypt(_ []byte) error { return nil }
|
|
|
|
// Handle returns the handle value associated with this session.
|
|
// In the case of a password session, this is always TPM_RS_PW.
|
|
func (s *pwSession) Handle() TPMHandle { return TPMRSPW }
|
|
|
|
// cpHash calculates the TPM command parameter hash.
|
|
// cpHash = hash(CC || names || parms)
|
|
func cpHash(alg TPMIAlgHash, cc TPMCC, names []TPM2BName, parms []byte) ([]byte, error) {
|
|
ha, err := alg.Hash()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
h := ha.New()
|
|
binary.Write(h, binary.BigEndian, cc)
|
|
for _, name := range names {
|
|
h.Write(name.Buffer)
|
|
}
|
|
h.Write(parms)
|
|
return h.Sum(nil), nil
|
|
}
|
|
|
|
// rpHash calculates the TPM response parameter hash.
|
|
// rpHash = hash(RC || CC || parms)
|
|
func rpHash(alg TPMIAlgHash, rc TPMRC, cc TPMCC, parms []byte) ([]byte, error) {
|
|
ha, err := alg.Hash()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
h := ha.New()
|
|
binary.Write(h, binary.BigEndian, rc)
|
|
binary.Write(h, binary.BigEndian, cc)
|
|
h.Write(parms)
|
|
return h.Sum(nil), nil
|
|
}
|
|
|
|
// sessionOptions represents extra options used when setting up an HMAC or policy session.
|
|
type sessionOptions struct {
|
|
auth []byte
|
|
password bool
|
|
bindHandle TPMIDHEntity
|
|
bindName TPM2BName
|
|
bindAuth []byte
|
|
saltHandle TPMIDHObject
|
|
saltPub TPMTPublic
|
|
attrs TPMASession
|
|
symmetric TPMTSymDef
|
|
trialPolicy bool
|
|
}
|
|
|
|
// defaultOptions represents the default options used when none are provided.
|
|
func defaultOptions() sessionOptions {
|
|
return sessionOptions{
|
|
symmetric: TPMTSymDef{
|
|
Algorithm: TPMAlgNull,
|
|
},
|
|
bindHandle: TPMRHNull,
|
|
saltHandle: TPMRHNull,
|
|
}
|
|
}
|
|
|
|
// AuthOption is an option for setting up an auth session variadically.
|
|
type AuthOption func(*sessionOptions)
|
|
|
|
// Auth uses the session to prove knowledge of the object's auth value.
|
|
func Auth(auth []byte) AuthOption {
|
|
return func(o *sessionOptions) {
|
|
o.auth = auth
|
|
}
|
|
}
|
|
|
|
// Password is a policy-session-only option that specifies to provide the
|
|
// object's auth value in place of the authorization HMAC when authorizing.
|
|
// For HMAC sessions, has the same effect as using Auth.
|
|
// Deprecated: This is not recommended and is only provided for completeness;
|
|
// use Auth instead.
|
|
func Password(auth []byte) AuthOption {
|
|
return func(o *sessionOptions) {
|
|
o.auth = auth
|
|
o.password = true
|
|
}
|
|
}
|
|
|
|
// Bound specifies that this session's session key should depend on the auth
|
|
// value of the given object.
|
|
func Bound(handle TPMIDHEntity, name TPM2BName, auth []byte) AuthOption {
|
|
return func(o *sessionOptions) {
|
|
o.bindHandle = handle
|
|
o.bindName = name
|
|
o.bindAuth = auth
|
|
}
|
|
}
|
|
|
|
// Salted specifies that this session's session key should depend on an
|
|
// encrypted seed value using the given public key.
|
|
// 'handle' must refer to a loaded RSA or ECC key.
|
|
func Salted(handle TPMIDHObject, pub TPMTPublic) AuthOption {
|
|
return func(o *sessionOptions) {
|
|
o.saltHandle = handle
|
|
o.saltPub = pub
|
|
}
|
|
}
|
|
|
|
// parameterEncryptiontpm2ion specifies whether the session-encrypted
|
|
// parameters are encrypted on the way into the TPM, out of the TPM, or both.
|
|
type parameterEncryptiontpm2ion int
|
|
|
|
const (
|
|
// EncryptIn specifies a decrypt session.
|
|
EncryptIn parameterEncryptiontpm2ion = 1 + iota
|
|
// EncryptOut specifies an encrypt session.
|
|
EncryptOut
|
|
// EncryptInOut specifies a decrypt+encrypt session.
|
|
EncryptInOut
|
|
)
|
|
|
|
// AESEncryption uses the session to encrypt the first parameter sent to/from
|
|
// the TPM.
|
|
// Note that only commands whose first command/response parameter is a 2B can
|
|
// support session encryption.
|
|
func AESEncryption(keySize TPMKeyBits, dir parameterEncryptiontpm2ion) AuthOption {
|
|
return func(o *sessionOptions) {
|
|
o.attrs.Decrypt = (dir == EncryptIn || dir == EncryptInOut)
|
|
o.attrs.Encrypt = (dir == EncryptOut || dir == EncryptInOut)
|
|
o.symmetric = TPMTSymDef{
|
|
Algorithm: TPMAlgAES,
|
|
KeyBits: NewTPMUSymKeyBits(
|
|
TPMAlgAES,
|
|
TPMKeyBits(keySize),
|
|
),
|
|
Mode: NewTPMUSymMode(
|
|
TPMAlgAES,
|
|
TPMAlgCFB,
|
|
),
|
|
}
|
|
}
|
|
}
|
|
|
|
// Audit uses the session to compute extra HMACs.
|
|
// An Audit session can be used with GetSessionAuditDigest to obtain attestation
|
|
// over a sequence of commands.
|
|
func Audit() AuthOption {
|
|
return func(o *sessionOptions) {
|
|
o.attrs.Audit = true
|
|
}
|
|
}
|
|
|
|
// AuditExclusive is like an audit session, but even more powerful.
|
|
// This allows an audit session to additionally indicate that no other auditable
|
|
// commands were executed other than the ones described by the audit hash.
|
|
func AuditExclusive() AuthOption {
|
|
return func(o *sessionOptions) {
|
|
o.attrs.Audit = true
|
|
o.attrs.AuditExclusive = true
|
|
}
|
|
}
|
|
|
|
// Trial indicates that the policy session should be in trial-mode.
|
|
// This allows using the TPM to calculate policy hashes.
|
|
// This option has no effect on non-Policy sessions.
|
|
func Trial() AuthOption {
|
|
return func(o *sessionOptions) {
|
|
o.trialPolicy = true
|
|
}
|
|
}
|
|
|
|
// hmacSession generally implements the HMAC session.
|
|
type hmacSession struct {
|
|
sessionOptions
|
|
hash TPMIAlgHash
|
|
nonceSize int
|
|
handle TPMHandle
|
|
sessionKey []byte
|
|
// last nonceCaller
|
|
nonceCaller TPM2BNonce
|
|
// last nonceTPM
|
|
nonceTPM TPM2BNonce
|
|
}
|
|
|
|
// HMAC sets up a just-in-time HMAC session that is used only once.
|
|
// A real session is created, but just in time and it is flushed when used.
|
|
func HMAC(hash TPMIAlgHash, nonceSize int, opts ...AuthOption) Session {
|
|
// Set up a one-off session that knows the auth value.
|
|
sess := hmacSession{
|
|
sessionOptions: defaultOptions(),
|
|
hash: hash,
|
|
nonceSize: nonceSize,
|
|
handle: TPMRHNull,
|
|
}
|
|
for _, opt := range opts {
|
|
opt(&sess.sessionOptions)
|
|
}
|
|
return &sess
|
|
}
|
|
|
|
// HMACSession sets up a reusable HMAC session that needs to be closed.
|
|
func HMACSession(t transport.TPM, hash TPMIAlgHash, nonceSize int, opts ...AuthOption) (s Session, close func() error, err error) {
|
|
// Set up a not-one-off session that knows the auth value.
|
|
sess := hmacSession{
|
|
sessionOptions: defaultOptions(),
|
|
hash: hash,
|
|
nonceSize: nonceSize,
|
|
handle: TPMRHNull,
|
|
}
|
|
for _, opt := range opts {
|
|
opt(&sess.sessionOptions)
|
|
}
|
|
// This session is reusable and is closed with the function we'll
|
|
// return.
|
|
sess.sessionOptions.attrs.ContinueSession = true
|
|
|
|
// Initialize the session.
|
|
if err := sess.Init(t); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
closer := func() error {
|
|
_, err := (&FlushContext{FlushHandle: sess.handle}).Execute(t)
|
|
return err
|
|
}
|
|
|
|
return &sess, closer, nil
|
|
}
|
|
|
|
// getEncryptedSalt creates a salt value for salted sessions.
|
|
// Returns the encrypted salt and plaintext salt, or an error value.
|
|
func getEncryptedSalt(pub TPMTPublic) (*TPM2BEncryptedSecret, []byte, error) {
|
|
key, err := ImportEncapsulationKey(&pub)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
salt, encSalt, err := CreateEncryptedSalt(rand.Reader, key)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
return &TPM2BEncryptedSecret{
|
|
Buffer: encSalt,
|
|
}, salt, nil
|
|
}
|
|
|
|
// Init initializes the session, just in time, if needed.
|
|
func (s *hmacSession) Init(t transport.TPM) error {
|
|
if s.handle != TPMRHNull {
|
|
// Session is already initialized.
|
|
return nil
|
|
}
|
|
|
|
// Get a high-quality nonceCaller for our use.
|
|
// Store it with the session object for later reference.
|
|
s.nonceCaller = TPM2BNonce{
|
|
Buffer: make([]byte, s.nonceSize),
|
|
}
|
|
if _, err := rand.Read(s.nonceCaller.Buffer); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Start up the actual auth session.
|
|
sasCmd := StartAuthSession{
|
|
TPMKey: s.saltHandle,
|
|
Bind: s.bindHandle,
|
|
NonceCaller: s.nonceCaller,
|
|
SessionType: TPMSEHMAC,
|
|
Symmetric: s.symmetric,
|
|
AuthHash: s.hash,
|
|
}
|
|
var salt []byte
|
|
if s.saltHandle != TPMRHNull {
|
|
var err error
|
|
var encSalt *TPM2BEncryptedSecret
|
|
encSalt, salt, err = getEncryptedSalt(s.saltPub)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sasCmd.EncryptedSalt = *encSalt
|
|
}
|
|
sasRsp, err := sasCmd.Execute(t)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.handle = TPMHandle(sasRsp.SessionHandle.HandleValue())
|
|
s.nonceTPM = sasRsp.NonceTPM
|
|
// Part 1, 19.6
|
|
ha, err := s.hash.Hash()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if s.bindHandle != TPMRHNull || len(salt) != 0 {
|
|
var authSalt []byte
|
|
authSalt = append(authSalt, s.bindAuth...)
|
|
authSalt = append(authSalt, salt...)
|
|
s.sessionKey = KDFa(ha, authSalt, "ATH", s.nonceTPM.Buffer, s.nonceCaller.Buffer, ha.Size()*8)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Cleanup cleans up the session, if needed.
|
|
func (s *hmacSession) CleanupFailure(t transport.TPM) error {
|
|
// The user is already responsible to clean up this session.
|
|
if s.attrs.ContinueSession {
|
|
return nil
|
|
}
|
|
fc := FlushContext{FlushHandle: s.handle}
|
|
if _, err := fc.Execute(t); err != nil {
|
|
return err
|
|
}
|
|
s.handle = TPMRHNull
|
|
return nil
|
|
}
|
|
|
|
// NonceTPM returns the last nonceTPM value from the session.
|
|
// May be nil, if the session hasn't been initialized yet.
|
|
func (s *hmacSession) NonceTPM() TPM2BNonce { return s.nonceTPM }
|
|
|
|
// To avoid a circular dependency on gotpm by tpm2, implement a
|
|
// tiny serialization by hand for TPMASession here
|
|
func attrsToBytes(attrs TPMASession) []byte {
|
|
var res byte
|
|
if attrs.ContinueSession {
|
|
res |= (1 << 0)
|
|
}
|
|
if attrs.AuditExclusive {
|
|
res |= (1 << 1)
|
|
}
|
|
if attrs.AuditReset {
|
|
res |= (1 << 2)
|
|
}
|
|
if attrs.Decrypt {
|
|
res |= (1 << 5)
|
|
}
|
|
if attrs.Encrypt {
|
|
res |= (1 << 6)
|
|
}
|
|
if attrs.Audit {
|
|
res |= (1 << 7)
|
|
}
|
|
return []byte{res}
|
|
}
|
|
|
|
// computeHMAC computes an authorization HMAC according to various equations in
|
|
// Part 1.
|
|
// This applies to both commands and responses.
|
|
// The value of key depends on whether the session is bound and/or salted.
|
|
// pHash cpHash for a command, or an rpHash for a response.
|
|
// nonceNewer in a command is the new nonceCaller sent in the command session packet.
|
|
// nonceNewer in a response is the new nonceTPM sent in the response session packet.
|
|
// nonceOlder in a command is the last nonceTPM sent by the TPM for this session.
|
|
// This may be when the session was created, or the last time it was used.
|
|
// nonceOlder in a response is the corresponding nonceCaller sent in the command.
|
|
func computeHMAC(alg TPMIAlgHash, key, pHash, nonceNewer, nonceOlder, addNonces []byte, attrs TPMASession) ([]byte, error) {
|
|
ha, err := alg.Hash()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
mac := hmac.New(ha.New, key)
|
|
mac.Write(pHash)
|
|
mac.Write(nonceNewer)
|
|
mac.Write(nonceOlder)
|
|
mac.Write(addNonces)
|
|
mac.Write(attrsToBytes(attrs))
|
|
return mac.Sum(nil), nil
|
|
}
|
|
|
|
// Trim trailing zeros from the auth value. Part 1, 19.6.5, Note 2
|
|
// Does not allocate a new underlying byte array.
|
|
func hmacKeyFromAuthValue(auth []byte) []byte {
|
|
key := auth
|
|
for i := len(key) - 1; i >= 0; i-- {
|
|
if key[i] == 0 {
|
|
key = key[:i]
|
|
}
|
|
}
|
|
return key
|
|
}
|
|
|
|
// NewNonceCaller updates the nonceCaller for this session.
|
|
func (s *hmacSession) NewNonceCaller() error {
|
|
_, err := rand.Read(s.nonceCaller.Buffer)
|
|
return err
|
|
}
|
|
|
|
// Authorize computes the authorization structure for the session.
|
|
// Unlike the TPM spec, authIndex is zero-based.
|
|
func (s *hmacSession) Authorize(cc TPMCC, parms, addNonces []byte, names []TPM2BName, authIndex int) (*TPMSAuthCommand, error) {
|
|
if s.handle == TPMRHNull {
|
|
// Session is not initialized.
|
|
return nil, fmt.Errorf("session not initialized")
|
|
}
|
|
|
|
// Part 1, 19.6
|
|
// HMAC key is (sessionKey || auth) unless this session is authorizing
|
|
// its bind target
|
|
var hmacKey []byte
|
|
hmacKey = append(hmacKey, s.sessionKey...)
|
|
if len(s.bindName.Buffer) == 0 || authIndex >= len(names) || !bytes.Equal(names[authIndex].Buffer, s.bindName.Buffer) {
|
|
hmacKey = append(hmacKey, hmacKeyFromAuthValue(s.auth)...)
|
|
}
|
|
|
|
// Compute the authorization HMAC.
|
|
cph, err := cpHash(s.hash, cc, names, parms)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
hmac, err := computeHMAC(s.hash, hmacKey, cph, s.nonceCaller.Buffer, s.nonceTPM.Buffer, addNonces, s.attrs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result := TPMSAuthCommand{
|
|
Handle: s.handle,
|
|
Nonce: s.nonceCaller,
|
|
Attributes: s.attrs,
|
|
Authorization: TPM2BData{
|
|
Buffer: hmac,
|
|
},
|
|
}
|
|
return &result, nil
|
|
}
|
|
|
|
// Validate validates the response session structure for the session.
|
|
// It updates nonceTPM from the TPM's response.
|
|
func (s *hmacSession) Validate(rc TPMRC, cc TPMCC, parms []byte, names []TPM2BName, authIndex int, auth *TPMSAuthResponse) error {
|
|
// Track the new nonceTPM for the session.
|
|
s.nonceTPM = auth.Nonce
|
|
// Track the session being automatically flushed.
|
|
if !auth.Attributes.ContinueSession {
|
|
s.handle = TPMRHNull
|
|
}
|
|
|
|
// Part 1, 19.6
|
|
// HMAC key is (sessionKey || auth) unless this session is authorizing
|
|
// its bind target
|
|
var hmacKey []byte
|
|
hmacKey = append(hmacKey, s.sessionKey...)
|
|
if len(s.bindName.Buffer) == 0 || authIndex >= len(names) || !bytes.Equal(names[authIndex].Buffer, s.bindName.Buffer) {
|
|
hmacKey = append(hmacKey, hmacKeyFromAuthValue(s.auth)...)
|
|
}
|
|
|
|
// Compute the authorization HMAC.
|
|
rph, err := rpHash(s.hash, rc, cc, parms)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
mac, err := computeHMAC(s.hash, hmacKey, rph, s.nonceTPM.Buffer, s.nonceCaller.Buffer, nil, auth.Attributes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Compare the HMAC (constant time)
|
|
if !hmac.Equal(mac, auth.Authorization.Buffer) {
|
|
return fmt.Errorf("incorrect authorization HMAC")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// IsEncryption returns true if this is an encryption session.
|
|
func (s *hmacSession) IsEncryption() bool {
|
|
return s.attrs.Encrypt
|
|
}
|
|
|
|
// IsDecryption returns true if this is a decryption session.
|
|
func (s *hmacSession) IsDecryption() bool {
|
|
return s.attrs.Decrypt
|
|
}
|
|
|
|
// Encrypt decrypts the parameter in place, if this session is used for
|
|
// parameter decryption. Otherwise, it does not modify the parameter.
|
|
func (s *hmacSession) Encrypt(parameter []byte) error {
|
|
if !s.IsDecryption() {
|
|
return nil
|
|
}
|
|
// Only AES-CFB is supported.
|
|
bits, err := s.symmetric.KeyBits.AES()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
keyBytes := *bits / 8
|
|
keyIVBytes := int(keyBytes) + 16
|
|
var sessionValue []byte
|
|
sessionValue = append(sessionValue, s.sessionKey...)
|
|
sessionValue = append(sessionValue, s.auth...)
|
|
ha, err := s.hash.Hash()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
keyIV := KDFa(ha, sessionValue, "CFB", s.nonceCaller.Buffer, s.nonceTPM.Buffer, keyIVBytes*8)
|
|
key, err := aes.NewCipher(keyIV[:keyBytes])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
stream := cipher.NewCFBEncrypter(key, keyIV[keyBytes:])
|
|
stream.XORKeyStream(parameter, parameter)
|
|
return nil
|
|
}
|
|
|
|
// Decrypt encrypts the parameter in place, if this session is used for
|
|
// parameter encryption. Otherwise, it does not modify the parameter.
|
|
func (s *hmacSession) Decrypt(parameter []byte) error {
|
|
if !s.IsEncryption() {
|
|
return nil
|
|
}
|
|
// Only AES-CFB is supported.
|
|
bits, err := s.symmetric.KeyBits.AES()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
keyBytes := *bits / 8
|
|
keyIVBytes := int(keyBytes) + 16
|
|
// Part 1, 21.1
|
|
var sessionValue []byte
|
|
sessionValue = append(sessionValue, s.sessionKey...)
|
|
sessionValue = append(sessionValue, s.auth...)
|
|
ha, err := s.hash.Hash()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
keyIV := KDFa(ha, sessionValue, "CFB", s.nonceTPM.Buffer, s.nonceCaller.Buffer, keyIVBytes*8)
|
|
key, err := aes.NewCipher(keyIV[:keyBytes])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
stream := cipher.NewCFBDecrypter(key, keyIV[keyBytes:])
|
|
stream.XORKeyStream(parameter, parameter)
|
|
return nil
|
|
}
|
|
|
|
// Handle returns the handle value of the session.
|
|
// If the session is created with HMAC (instead of HMACSession) this will be
|
|
// TPM_RH_NULL.
|
|
func (s *hmacSession) Handle() TPMHandle {
|
|
return s.handle
|
|
}
|
|
|
|
// PolicyCallback represents an object's policy in the form of a function.
|
|
// This function makes zero or more TPM policy commands and returns error.
|
|
type PolicyCallback = func(tpm transport.TPM, handle TPMISHPolicy, nonceTPM TPM2BNonce) error
|
|
|
|
// policySession generally implements the policy session.
|
|
type policySession struct {
|
|
sessionOptions
|
|
hash TPMIAlgHash
|
|
nonceSize int
|
|
handle TPMHandle
|
|
sessionKey []byte
|
|
// last nonceCaller
|
|
nonceCaller TPM2BNonce
|
|
// last nonceTPM
|
|
nonceTPM TPM2BNonce
|
|
callback *PolicyCallback
|
|
}
|
|
|
|
// Policy sets up a just-in-time policy session that created each time it's
|
|
// needed.
|
|
// Each time the policy is created, the callback is invoked to authorize the
|
|
// session.
|
|
// A real session is created, but just in time, and it is flushed when used.
|
|
func Policy(hash TPMIAlgHash, nonceSize int, callback PolicyCallback, opts ...AuthOption) Session {
|
|
// Set up a one-off session that knows the auth value.
|
|
sess := policySession{
|
|
sessionOptions: defaultOptions(),
|
|
hash: hash,
|
|
nonceSize: nonceSize,
|
|
handle: TPMRHNull,
|
|
callback: &callback,
|
|
}
|
|
for _, opt := range opts {
|
|
opt(&sess.sessionOptions)
|
|
}
|
|
return &sess
|
|
}
|
|
|
|
// PolicySession opens a policy session that needs to be closed.
|
|
// The caller is responsible to call whichever policy commands they want in the
|
|
// session.
|
|
// Note that the TPM resets a policy session after it is successfully used.
|
|
func PolicySession(t transport.TPM, hash TPMIAlgHash, nonceSize int, opts ...AuthOption) (s Session, close func() error, err error) {
|
|
// Set up a not-one-off session that knows the auth value.
|
|
sess := policySession{
|
|
sessionOptions: defaultOptions(),
|
|
hash: hash,
|
|
nonceSize: nonceSize,
|
|
handle: TPMRHNull,
|
|
}
|
|
for _, opt := range opts {
|
|
opt(&sess.sessionOptions)
|
|
}
|
|
|
|
// This session is reusable and is closed with the function we'll
|
|
// return.
|
|
sess.sessionOptions.attrs.ContinueSession = true
|
|
|
|
// Initialize the session.
|
|
if err := sess.Init(t); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
closer := func() error {
|
|
_, err := (&FlushContext{sess.handle}).Execute(t)
|
|
return err
|
|
}
|
|
|
|
return &sess, closer, nil
|
|
}
|
|
|
|
// Init initializes the session, just in time, if needed.
|
|
func (s *policySession) Init(t transport.TPM) error {
|
|
if s.handle != TPMRHNull {
|
|
// Session is already initialized.
|
|
return nil
|
|
}
|
|
|
|
// Get a high-quality nonceCaller for our use.
|
|
// Store it with the session object for later reference.
|
|
s.nonceCaller = TPM2BNonce{
|
|
Buffer: make([]byte, s.nonceSize),
|
|
}
|
|
if _, err := rand.Read(s.nonceCaller.Buffer); err != nil {
|
|
return err
|
|
}
|
|
|
|
sessType := TPMSEPolicy
|
|
if s.sessionOptions.trialPolicy {
|
|
sessType = TPMSETrial
|
|
}
|
|
|
|
// Start up the actual auth session.
|
|
sasCmd := StartAuthSession{
|
|
TPMKey: s.saltHandle,
|
|
Bind: s.bindHandle,
|
|
NonceCaller: s.nonceCaller,
|
|
SessionType: sessType,
|
|
Symmetric: s.symmetric,
|
|
AuthHash: s.hash,
|
|
}
|
|
var salt []byte
|
|
if s.saltHandle != TPMRHNull {
|
|
var err error
|
|
var encSalt *TPM2BEncryptedSecret
|
|
encSalt, salt, err = getEncryptedSalt(s.saltPub)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sasCmd.EncryptedSalt = *encSalt
|
|
}
|
|
sasRsp, err := sasCmd.Execute(t)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.handle = TPMHandle(sasRsp.SessionHandle.HandleValue())
|
|
s.nonceTPM = sasRsp.NonceTPM
|
|
// Part 1, 19.6
|
|
if s.bindHandle != TPMRHNull || len(salt) != 0 {
|
|
var authSalt []byte
|
|
authSalt = append(authSalt, s.bindAuth...)
|
|
authSalt = append(authSalt, salt...)
|
|
ha, err := s.hash.Hash()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.sessionKey = KDFa(ha, authSalt, "ATH", s.nonceTPM.Buffer, s.nonceCaller.Buffer, ha.Size()*8)
|
|
}
|
|
|
|
// Call the callback to execute the policy, if needed
|
|
if s.callback != nil {
|
|
if err := (*s.callback)(t, s.handle, s.nonceTPM); err != nil {
|
|
return fmt.Errorf("executing policy: %w", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// CleanupFailure cleans up the session, if needed.
|
|
func (s *policySession) CleanupFailure(t transport.TPM) error {
|
|
// The user is already responsible to clean up this session.
|
|
if s.attrs.ContinueSession {
|
|
return nil
|
|
}
|
|
fc := FlushContext{FlushHandle: s.handle}
|
|
if _, err := fc.Execute(t); err != nil {
|
|
return err
|
|
}
|
|
s.handle = TPMRHNull
|
|
return nil
|
|
}
|
|
|
|
// NonceTPM returns the last nonceTPM value from the session.
|
|
// May be nil, if the session hasn't been initialized yet.
|
|
func (s *policySession) NonceTPM() TPM2BNonce { return s.nonceTPM }
|
|
|
|
// NewNonceCaller updates the nonceCaller for this session.
|
|
func (s *policySession) NewNonceCaller() error {
|
|
_, err := rand.Read(s.nonceCaller.Buffer)
|
|
return err
|
|
}
|
|
|
|
// Authorize computes the authorization structure for the session.
|
|
func (s *policySession) Authorize(cc TPMCC, parms, addNonces []byte, names []TPM2BName, _ int) (*TPMSAuthCommand, error) {
|
|
if s.handle == TPMRHNull {
|
|
// Session is not initialized.
|
|
return nil, fmt.Errorf("session not initialized")
|
|
}
|
|
|
|
var hmac []byte
|
|
if s.password {
|
|
hmac = s.auth
|
|
} else {
|
|
// Part 1, 19.6
|
|
// HMAC key is (sessionKey || auth).
|
|
var hmacKey []byte
|
|
hmacKey = append(hmacKey, s.sessionKey...)
|
|
hmacKey = append(hmacKey, hmacKeyFromAuthValue(s.auth)...)
|
|
|
|
// Compute the authorization HMAC.
|
|
cph, err := cpHash(s.hash, cc, names, parms)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
hmac, err = computeHMAC(s.hash, hmacKey, cph, s.nonceCaller.Buffer, s.nonceTPM.Buffer, addNonces, s.attrs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
result := TPMSAuthCommand{
|
|
Handle: s.handle,
|
|
Nonce: s.nonceCaller,
|
|
Attributes: s.attrs,
|
|
Authorization: TPM2BData{
|
|
Buffer: hmac,
|
|
},
|
|
}
|
|
return &result, nil
|
|
}
|
|
|
|
// Validate valitades the response session structure for the session.
|
|
// Updates nonceTPM from the TPM's response.
|
|
func (s *policySession) Validate(rc TPMRC, cc TPMCC, parms []byte, _ []TPM2BName, _ int, auth *TPMSAuthResponse) error {
|
|
// Track the new nonceTPM for the session.
|
|
s.nonceTPM = auth.Nonce
|
|
// Track the session being automatically flushed.
|
|
if !auth.Attributes.ContinueSession {
|
|
s.handle = TPMRHNull
|
|
}
|
|
|
|
if s.password {
|
|
// If we used a password, expect no nonce and no response HMAC.
|
|
if len(auth.Nonce.Buffer) != 0 {
|
|
return fmt.Errorf("expected empty nonce in response auth to PW policy, got %x", auth.Nonce)
|
|
}
|
|
if len(auth.Authorization.Buffer) != 0 {
|
|
return fmt.Errorf("expected empty HMAC in response auth to PW policy, got %x", auth.Authorization)
|
|
}
|
|
} else {
|
|
// Part 1, 19.6
|
|
// HMAC key is (sessionKey || auth).
|
|
var hmacKey []byte
|
|
hmacKey = append(hmacKey, s.sessionKey...)
|
|
hmacKey = append(hmacKey, hmacKeyFromAuthValue(s.auth)...)
|
|
// Compute the authorization HMAC.
|
|
rph, err := rpHash(s.hash, rc, cc, parms)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
mac, err := computeHMAC(s.hash, hmacKey, rph, s.nonceTPM.Buffer, s.nonceCaller.Buffer, nil, auth.Attributes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Compare the HMAC (constant time)
|
|
if !hmac.Equal(mac, auth.Authorization.Buffer) {
|
|
return fmt.Errorf("incorrect authorization HMAC")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// IsEncryption returns true if this is an encryption session.
|
|
func (s *policySession) IsEncryption() bool {
|
|
return s.attrs.Encrypt
|
|
}
|
|
|
|
// IsDecryption returns true if this is a decryption session.
|
|
func (s *policySession) IsDecryption() bool {
|
|
return s.attrs.Decrypt
|
|
}
|
|
|
|
// Encrypt encrypts the parameter in place, if this session is used for
|
|
// parameter decryption. Otherwise, it does not modify the parameter.
|
|
func (s *policySession) Encrypt(parameter []byte) error {
|
|
if !s.IsDecryption() {
|
|
return nil
|
|
}
|
|
// Only AES-CFB is supported.
|
|
bits, err := s.symmetric.KeyBits.AES()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
keyBytes := *bits / 8
|
|
keyIVBytes := int(keyBytes) + 16
|
|
var sessionValue []byte
|
|
sessionValue = append(sessionValue, s.sessionKey...)
|
|
sessionValue = append(sessionValue, s.auth...)
|
|
ha, err := s.hash.Hash()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
keyIV := KDFa(ha, sessionValue, "CFB", s.nonceCaller.Buffer, s.nonceTPM.Buffer, keyIVBytes*8)
|
|
key, err := aes.NewCipher(keyIV[:keyBytes])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
stream := cipher.NewCFBEncrypter(key, keyIV[keyBytes:])
|
|
stream.XORKeyStream(parameter, parameter)
|
|
return nil
|
|
}
|
|
|
|
// Decrypt decrypts the parameter in place, if this session is used for
|
|
// parameter encryption. Otherwise, it does not modify the parameter.
|
|
func (s *policySession) Decrypt(parameter []byte) error {
|
|
if !s.IsEncryption() {
|
|
return nil
|
|
}
|
|
// Only AES-CFB is supported.
|
|
bits, err := s.symmetric.KeyBits.AES()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
keyBytes := *bits / 8
|
|
keyIVBytes := int(keyBytes) + 16
|
|
// Part 1, 21.1
|
|
var sessionValue []byte
|
|
sessionValue = append(sessionValue, s.sessionKey...)
|
|
sessionValue = append(sessionValue, s.auth...)
|
|
ha, err := s.hash.Hash()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
keyIV := KDFa(ha, sessionValue, "CFB", s.nonceTPM.Buffer, s.nonceCaller.Buffer, keyIVBytes*8)
|
|
key, err := aes.NewCipher(keyIV[:keyBytes])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
stream := cipher.NewCFBDecrypter(key, keyIV[keyBytes:])
|
|
stream.XORKeyStream(parameter, parameter)
|
|
return nil
|
|
}
|
|
|
|
// Handle returns the handle value of the session.
|
|
// If the session is created with Policy (instead of PolicySession) this will be
|
|
// TPM_RH_NULL.
|
|
func (s *policySession) Handle() TPMHandle {
|
|
return s.handle
|
|
}
|