mirror of https://github.com/kubernetes/kops.git
171 lines
4.6 KiB
Go
171 lines
4.6 KiB
Go
package client
|
|
|
|
import (
|
|
"crypto"
|
|
"fmt"
|
|
"io"
|
|
"math"
|
|
|
|
pb "github.com/google/go-tpm-tools/proto/tpm"
|
|
"github.com/google/go-tpm/legacy/tpm2"
|
|
)
|
|
|
|
// NumPCRs is set to the spec minimum of 24, as that's all go-tpm supports.
|
|
const NumPCRs = 24
|
|
|
|
// We hard-code SHA256 as the policy session hash algorithms. Note that this
|
|
// differs from the PCR hash algorithm (which selects the bank of PCRs to use)
|
|
// and the Public area Name algorithm. We also chose this for compatibility with
|
|
// github.com/google/go-tpm/legacy/tpm2, as it hardcodes the nameAlg as SHA256 in
|
|
// several places. Two constants are used to avoid repeated conversions.
|
|
const (
|
|
SessionHashAlg = crypto.SHA256
|
|
SessionHashAlgTpm = tpm2.AlgSHA256
|
|
)
|
|
|
|
// CertifyHashAlgTpm is the hard-coded algorithm used in certify PCRs.
|
|
const CertifyHashAlgTpm = tpm2.AlgSHA256
|
|
|
|
func min(a, b int) int {
|
|
if a < b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|
|
|
|
// allocatedPCRs returns a list of selections corresponding to the TPM's implemented PCRs.
|
|
func allocatedPCRs(rw io.ReadWriter) ([]tpm2.PCRSelection, error) {
|
|
caps, moreData, err := tpm2.GetCapability(rw, tpm2.CapabilityPCRs, math.MaxUint32, 0)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("listing implemented PCR banks: %w", err)
|
|
}
|
|
if moreData {
|
|
return nil, fmt.Errorf("extra data from GetCapability")
|
|
}
|
|
var sels []tpm2.PCRSelection
|
|
for _, cap := range caps {
|
|
sel, ok := cap.(tpm2.PCRSelection)
|
|
if !ok {
|
|
return nil, fmt.Errorf("unexpected data from GetCapability")
|
|
}
|
|
// skip empty (unallocated) PCR selections
|
|
if len(sel.PCRs) == 0 {
|
|
continue
|
|
}
|
|
sels = append(sels, sel)
|
|
}
|
|
return sels, nil
|
|
}
|
|
|
|
// ReadPCRs fetches all the PCR values specified in sel, making multiple calls
|
|
// to the TPM if necessary.
|
|
func ReadPCRs(rw io.ReadWriter, sel tpm2.PCRSelection) (*pb.PCRs, error) {
|
|
pl := pb.PCRs{
|
|
Hash: pb.HashAlgo(sel.Hash),
|
|
Pcrs: map[uint32][]byte{},
|
|
}
|
|
|
|
for i := 0; i < len(sel.PCRs); i += 8 {
|
|
end := min(i+8, len(sel.PCRs))
|
|
pcrSel := tpm2.PCRSelection{
|
|
Hash: sel.Hash,
|
|
PCRs: sel.PCRs[i:end],
|
|
}
|
|
|
|
pcrMap, err := tpm2.ReadPCRs(rw, pcrSel)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for pcr, val := range pcrMap {
|
|
pl.Pcrs[uint32(pcr)] = val
|
|
}
|
|
}
|
|
|
|
return &pl, nil
|
|
}
|
|
|
|
// ReadAllPCRs fetches all the PCR values from all implemented PCR banks.
|
|
func ReadAllPCRs(rw io.ReadWriter) ([]*pb.PCRs, error) {
|
|
sels, err := allocatedPCRs(rw)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
allPcrs := make([]*pb.PCRs, len(sels))
|
|
for i, sel := range sels {
|
|
allPcrs[i], err = ReadPCRs(rw, sel)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("reading bank %x PCRs: %w", sel.Hash, err)
|
|
}
|
|
}
|
|
return allPcrs, nil
|
|
}
|
|
|
|
// SealOpts specifies the PCR values that should be used for Seal().
|
|
type SealOpts struct {
|
|
// Current seals data to the current specified PCR selection.
|
|
Current tpm2.PCRSelection
|
|
// Target predictively seals data to the given specified PCR values.
|
|
Target *pb.PCRs
|
|
}
|
|
|
|
// UnsealOpts specifies the options that should be used for Unseal().
|
|
// Currently, it specifies the PCRs that need to pass certification in order to
|
|
// successfully unseal.
|
|
// CertifyHashAlgTpm is the hard-coded algorithm that must be used with
|
|
// UnsealOpts.
|
|
type UnsealOpts struct {
|
|
// CertifyCurrent certifies that a selection of current PCRs have the same
|
|
// value when sealing.
|
|
CertifyCurrent tpm2.PCRSelection
|
|
// CertifyExpected certifies that the TPM had a specific set of PCR values when sealing.
|
|
CertifyExpected *pb.PCRs
|
|
}
|
|
|
|
// FullPcrSel will return a full PCR selection based on the total PCR number
|
|
// of the TPM with the given hash algo.
|
|
func FullPcrSel(hash tpm2.Algorithm) tpm2.PCRSelection {
|
|
sel := tpm2.PCRSelection{Hash: hash}
|
|
for i := 0; i < NumPCRs; i++ {
|
|
sel.PCRs = append(sel.PCRs, int(i))
|
|
}
|
|
return sel
|
|
}
|
|
|
|
func mergePCRSelAndProto(rw io.ReadWriter, sel tpm2.PCRSelection, proto *pb.PCRs) (*pb.PCRs, error) {
|
|
if proto == nil || len(proto.GetPcrs()) == 0 {
|
|
return ReadPCRs(rw, sel)
|
|
}
|
|
if len(sel.PCRs) == 0 {
|
|
return proto, nil
|
|
}
|
|
if sel.Hash != tpm2.Algorithm(proto.Hash) {
|
|
return nil, fmt.Errorf("current hash (%v) differs from target hash (%v)",
|
|
sel.Hash, tpm2.Algorithm(proto.Hash))
|
|
}
|
|
|
|
// At this point, both sel and proto are non-empty.
|
|
// Verify no overlap in sel and proto PCR indexes.
|
|
overlap := make([]int, 0)
|
|
targetMap := proto.GetPcrs()
|
|
for _, pcrVal := range sel.PCRs {
|
|
if _, found := targetMap[uint32(pcrVal)]; found {
|
|
overlap = append(overlap, pcrVal)
|
|
}
|
|
}
|
|
if len(overlap) != 0 {
|
|
return nil, fmt.Errorf("found PCR overlap: %v", overlap)
|
|
}
|
|
|
|
currentPcrs, err := ReadPCRs(rw, sel)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for pcr, val := range proto.GetPcrs() {
|
|
currentPcrs.Pcrs[pcr] = val
|
|
}
|
|
return currentPcrs, nil
|
|
}
|