Add Nix flake for mciasctl and mciasgrpcctl
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>
This commit is contained in:
996
vendor/github.com/google/go-tpm/tpm2/sessions.go
generated
vendored
Normal file
996
vendor/github.com/google/go-tpm/tpm2/sessions.go
generated
vendored
Normal file
@@ -0,0 +1,996 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user