mirror of
https://github.com/ultravioletrs/cocos.git
synced 2026-06-23 04:10:25 +00:00
8eb1fac9ad
* Refactor and update dependencies in the project - Updated go.sum to replace `github.com/absmach/magistrala` with `github.com/absmach/supermq` across various modules. - Removed VSock configuration from environment variables and QEMU arguments. - Updated QEMU configuration and related tests to remove references to guest CID and VSock. - Added new HTTP transport layer for API endpoints in the manager. - Introduced Prometheus monitoring configuration with alert rules and Alertmanager setup. - Updated service and VM interfaces to remove unused methods and references. - Refactored tests to align with the new structure and dependencies. Signed-off-by: Sammy Oina <sammyoina@gmail.com> * Add MaxVMs configuration and enforce limit on VM creation Signed-off-by: Sammy Oina <sammyoina@gmail.com> * Add comprehensive tests for HTTP transport handlers and endpoints Signed-off-by: Sammy Oina <sammyoina@gmail.com> * Add test case for exceeding maximum number of VMs in TestRun Signed-off-by: Sammy Oina <sammyoina@gmail.com> * Improve error handling in TestHandlerWithCustomRouter to ensure response writing is checked Signed-off-by: Sammy Oina <sammyoina@gmail.com> * Update dependencies to latest versions - Upgrade cel.dev/expr from v0.23.0 to v0.24.0 - Upgrade github.com/absmach/supermq from v0.16.0 to v0.17.0 - Upgrade github.com/cenkalti/backoff from v4.3.0 to v5.0.2 - Upgrade github.com/cncf/xds/go to v0.0.0-20250501225837-2ac532fd4443 - Upgrade github.com/go-chi/chi/v5 from v5.2.1 to v5.2.2 - Upgrade github.com/go-jose/go-jose/v3 from v3.0.3 to v3.0.4 - Upgrade github.com/gofrs/uuid/v5 from v5.3.0 to v5.3.2 - Upgrade github.com/prometheus/client_golang from v1.22.0 to v1.23.0 - Upgrade github.com/prometheus/client_model from v0.6.1 to v0.6.2 - Upgrade github.com/prometheus/common from v0.62.0 to v0.65.0 - Upgrade github.com/prometheus/procfs from v0.15.1 to v0.16.1 - Upgrade go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp from v0.60.0 to v0.62.0 - Upgrade go.opentelemetry.io/otel/exporters/otlp/otlptrace from v1.36.0 to v1.37.0 - Upgrade golang.org/x/crypto from v0.39.0 to v0.40.0 - Upgrade golang.org/x/sys from v0.33.0 to v0.34.0 - Upgrade golang.org/x/text from v0.26.0 to v0.27.0 - Upgrade golang.org/x/time from v0.11.0 to v0.12.0 - Upgrade google.golang.org/grpc from v1.73.0 to v1.74.2 Signed-off-by: Sammy Oina <sammyoina@gmail.com> --------- Signed-off-by: Sammy Oina <sammyoina@gmail.com>
439 lines
12 KiB
Go
439 lines
12 KiB
Go
// Copyright (c) Ultraviolet
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package vtpm
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strconv"
|
|
|
|
"github.com/absmach/supermq/pkg/errors"
|
|
"github.com/google/go-sev-guest/abi"
|
|
"github.com/google/go-sev-guest/proto/check"
|
|
"github.com/google/go-sev-guest/proto/sevsnp"
|
|
"github.com/google/go-tpm-tools/client"
|
|
"github.com/google/go-tpm-tools/proto/attest"
|
|
ptpm "github.com/google/go-tpm-tools/proto/tpm"
|
|
"github.com/google/go-tpm-tools/server"
|
|
"github.com/google/go-tpm/legacy/tpm2"
|
|
"github.com/google/go-tpm/tpmutil"
|
|
"github.com/ultravioletrs/cocos/pkg/attestation"
|
|
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
|
|
"golang.org/x/crypto/sha3"
|
|
"google.golang.org/protobuf/encoding/protojson"
|
|
"google.golang.org/protobuf/encoding/prototext"
|
|
"google.golang.org/protobuf/proto"
|
|
)
|
|
|
|
var (
|
|
_ attestation.Provider = (*provider)(nil)
|
|
_ attestation.Verifier = (*verifier)(nil)
|
|
)
|
|
|
|
const (
|
|
eventLog = "/sys/kernel/security/tpm0/binary_bios_measurements"
|
|
Nonce = 32
|
|
PCR15 = 15
|
|
PCR16 = 16
|
|
Hash1 = 20
|
|
Hash256 = 32
|
|
Hash384 = 48
|
|
)
|
|
|
|
var (
|
|
ExternalTPM io.ReadWriteCloser
|
|
ErrNoHashAlgo = errors.New("hash algo is not supported")
|
|
ErrFetchQuote = errors.New("failed to fetch vTPM quote")
|
|
ErrAttestationPolicyOpen = errors.New("failed to open Attestation Policy file")
|
|
ErrAttestationPolicyDecode = errors.New("failed to decode Attestation Policy file")
|
|
ErrAttestationPolicyMissing = errors.New("failed due to missing Attestation Policy file")
|
|
ErrProtoMarshalFailed = errors.New("failed to marshal protojson")
|
|
ErrJsonMarshalFailed = errors.New("failed to marshal json")
|
|
ErrJsonUnarshalFailed = errors.New("failed to unmarshal json")
|
|
)
|
|
|
|
type tpm struct {
|
|
io.ReadWriteCloser
|
|
}
|
|
|
|
func (et tpm) EventLog() ([]byte, error) {
|
|
return os.ReadFile(eventLog)
|
|
}
|
|
|
|
func OpenTpm() (io.ReadWriteCloser, error) {
|
|
if ExternalTPM != nil {
|
|
return tpm{ExternalTPM}, nil
|
|
}
|
|
|
|
tw := tpm{}
|
|
var err error
|
|
|
|
tw.ReadWriteCloser, err = tpm2.OpenTPM("/dev/tpmrm0")
|
|
if os.IsNotExist(err) {
|
|
tw.ReadWriteCloser, err = tpm2.OpenTPM("/dev/tpm0")
|
|
}
|
|
|
|
return tw, err
|
|
}
|
|
|
|
func ExtendPCR(pcrIndex int, value []byte) error {
|
|
rwc, err := OpenTpm()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer rwc.Close()
|
|
|
|
fixedSha256Hash := sha3.Sum256(value)
|
|
if err := tpm2.PCRExtend(rwc, tpmutil.Handle(pcrIndex), tpm2.AlgSHA256, fixedSha256Hash[:], ""); err != nil {
|
|
return err
|
|
}
|
|
|
|
fixedSha384Hash := sha3.Sum384(value)
|
|
if err := tpm2.PCRExtend(rwc, tpmutil.Handle(pcrIndex), tpm2.AlgSHA384, fixedSha384Hash[:], ""); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type provider struct {
|
|
teeAttestaion bool
|
|
vmpl uint
|
|
}
|
|
|
|
func NewProvider(teeAttestation bool, vmpl uint) attestation.Provider {
|
|
return &provider{
|
|
teeAttestaion: teeAttestation,
|
|
vmpl: vmpl,
|
|
}
|
|
}
|
|
|
|
func (v provider) Attestation(teeNonce []byte, vTpmNonce []byte) ([]byte, error) {
|
|
return Attest(teeNonce, vTpmNonce, v.teeAttestaion, v.vmpl)
|
|
}
|
|
|
|
func (v provider) TeeAttestation(teeNonce []byte) ([]byte, error) {
|
|
return quoteprovider.FetchAttestation(teeNonce, v.vmpl)
|
|
}
|
|
|
|
func (v provider) VTpmAttestation(vTpmNonce []byte) ([]byte, error) {
|
|
quote, err := FetchQuote(vTpmNonce)
|
|
if err != nil {
|
|
return []byte{}, errors.Wrap(ErrFetchQuote, err)
|
|
}
|
|
|
|
return proto.Marshal(quote)
|
|
}
|
|
|
|
func (v provider) AzureAttestationToken(tokenNonce []byte) ([]byte, error) {
|
|
return nil, errors.New("Azure attestation token is not supported")
|
|
}
|
|
|
|
type verifier struct {
|
|
writer io.Writer
|
|
Policy *attestation.Config
|
|
}
|
|
|
|
func NewVerifier(writer io.Writer) attestation.Verifier {
|
|
policy := &attestation.Config{
|
|
Config: &check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}},
|
|
PcrConfig: &attestation.PcrConfig{},
|
|
}
|
|
|
|
return &verifier{
|
|
writer: writer,
|
|
Policy: policy,
|
|
}
|
|
}
|
|
|
|
func NewVerifierWithPolicy(pubKey []byte, writer io.Writer, policy *attestation.Config) attestation.Verifier {
|
|
if policy == nil {
|
|
return NewVerifier(writer)
|
|
}
|
|
|
|
return &verifier{
|
|
writer: writer,
|
|
Policy: policy,
|
|
}
|
|
}
|
|
|
|
func (v verifier) VerifTeeAttestation(report []byte, teeNonce []byte) error {
|
|
attestReport, err := abi.ReportToProto(report)
|
|
if err != nil {
|
|
return errors.Wrap(fmt.Errorf("failed to convert TEE report to proto"), err)
|
|
}
|
|
|
|
attestationReport := sevsnp.Attestation{Report: attestReport, CertificateChain: nil}
|
|
return quoteprovider.VerifyAttestationReportTLS(&attestationReport, teeNonce, v.Policy)
|
|
}
|
|
|
|
func (v verifier) VerifVTpmAttestation(report []byte, vTpmNonce []byte) error {
|
|
return VerifyQuote(report, vTpmNonce, v.writer, v.Policy)
|
|
}
|
|
|
|
func (v verifier) VerifyAttestation(report []byte, teeNonce []byte, vTpmNonce []byte) error {
|
|
return VTPMVerify(report, teeNonce, vTpmNonce, v.writer, v.Policy)
|
|
}
|
|
|
|
func (v verifier) JSONToPolicy(path string) error {
|
|
return ReadPolicy(path, v.Policy)
|
|
}
|
|
|
|
func Attest(teeNonce []byte, vTPMNonce []byte, teeAttestaion bool, vmpl uint) ([]byte, error) {
|
|
attestation, err := FetchQuote(vTPMNonce)
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
|
|
if teeAttestaion {
|
|
err = addTEEAttestation(attestation, teeNonce, vmpl)
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
}
|
|
|
|
return marshalQuote(attestation)
|
|
}
|
|
|
|
func VTPMVerify(quote []byte, teeNonce []byte, vtpmNonce []byte, writer io.Writer, policy *attestation.Config) error {
|
|
if err := VerifyQuote(quote, vtpmNonce, writer, policy); err != nil {
|
|
return fmt.Errorf("failed to verify vTPM quote: %v", err)
|
|
}
|
|
|
|
attestation := &attest.Attestation{}
|
|
|
|
err := proto.Unmarshal(quote, attestation)
|
|
if err != nil {
|
|
return errors.Wrap(fmt.Errorf("failed to unmarshal quote"), err)
|
|
}
|
|
|
|
if err := quoteprovider.VerifyAttestationReportTLS(attestation.GetSevSnpAttestation(), teeNonce, policy); err != nil {
|
|
return fmt.Errorf("failed to verify TEE attestation report: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func VerifyQuote(quote []byte, vtpmNonce []byte, writer io.Writer, policy *attestation.Config) error {
|
|
attestation := &attest.Attestation{}
|
|
|
|
err := proto.Unmarshal(quote, attestation)
|
|
if err != nil {
|
|
return errors.Wrap(fmt.Errorf("failed to unmarshal quote"), err)
|
|
}
|
|
|
|
ak := attestation.GetAkPub()
|
|
pub, err := tpm2.DecodePublic(ak)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cryptoPub, err := pub.Key()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ms, err := server.VerifyAttestation(attestation, server.VerifyOpts{Nonce: vtpmNonce, TrustedAKs: []crypto.PublicKey{cryptoPub}})
|
|
if err != nil {
|
|
return errors.Wrap(fmt.Errorf("failed to verify attestation"), err)
|
|
}
|
|
|
|
if err := checkExpectedPCRValues(attestation, policy); err != nil {
|
|
return fmt.Errorf("PCR values do not match expected PCR values: %w", err)
|
|
}
|
|
|
|
if writer != nil {
|
|
marshalOptions := prototext.MarshalOptions{Multiline: true, EmitASCII: true}
|
|
|
|
out, err := marshalOptions.Marshal(ms)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
if _, err := writer.Write(out); err != nil {
|
|
return fmt.Errorf("failed to write verified attestation report: %v", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func marshalQuote(attestation *attest.Attestation) ([]byte, error) {
|
|
out, err := proto.Marshal(attestation)
|
|
if err != nil {
|
|
return []byte{}, errors.Wrap(fmt.Errorf("failed to marshal vTPM attestation report"), err)
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
func FetchQuote(nonce []byte) (*attest.Attestation, error) {
|
|
rwc, err := OpenTpm()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rwc.Close()
|
|
|
|
attestationKey, err := client.AttestationKeyRSA(rwc)
|
|
if err != nil {
|
|
return nil, errors.Wrap(fmt.Errorf("failed to create attestation key: %v", err), err)
|
|
}
|
|
defer attestationKey.Close()
|
|
|
|
var fixedNonce [Nonce]byte
|
|
copy(fixedNonce[:], nonce)
|
|
attestOpts := client.AttestOpts{}
|
|
attestOpts.Nonce = fixedNonce[:]
|
|
|
|
attestOpts.TCGEventLog, err = client.GetEventLog(rwc)
|
|
if err != nil {
|
|
return nil, errors.Wrap(fmt.Errorf("failed to retrieve TCG Event Log: %v", err), err)
|
|
}
|
|
|
|
attestation, err := attestationKey.Attest(attestOpts)
|
|
if err != nil {
|
|
return nil, errors.Wrap(fmt.Errorf("failed to attest: %v", err), err)
|
|
}
|
|
|
|
return attestation, nil
|
|
}
|
|
|
|
func addTEEAttestation(attestation *attest.Attestation, nonce []byte, vmpl uint) error {
|
|
rawTeeAttestation, err := quoteprovider.FetchAttestation(nonce, vmpl)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to fetch TEE attestation report: %v", err)
|
|
}
|
|
|
|
extReport, err := abi.ReportCertsToProto(rawTeeAttestation)
|
|
if err != nil {
|
|
return errors.Wrap(fmt.Errorf("failed to convert TEE report to proto"), err)
|
|
}
|
|
attestation.TeeAttestation = &attest.Attestation_SevSnpAttestation{
|
|
SevSnpAttestation: extReport,
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func checkExpectedPCRValues(attQuote *attest.Attestation, policy *attestation.Config) error {
|
|
quotes := attQuote.GetQuotes()
|
|
for i := range quotes {
|
|
quote := quotes[i]
|
|
var pcrMap map[string]string
|
|
|
|
switch quote.Pcrs.Hash {
|
|
case ptpm.HashAlgo_SHA256:
|
|
pcrMap = policy.PcrConfig.PCRValues.Sha256
|
|
case ptpm.HashAlgo_SHA384:
|
|
pcrMap = policy.PcrConfig.PCRValues.Sha384
|
|
case ptpm.HashAlgo_SHA1:
|
|
pcrMap = policy.PcrConfig.PCRValues.Sha1
|
|
default:
|
|
return errors.Wrap(ErrNoHashAlgo, fmt.Errorf("algo: %s", ptpm.HashAlgo_name[int32(quote.Pcrs.Hash)]))
|
|
}
|
|
|
|
for i, v := range pcrMap {
|
|
index, err := strconv.ParseInt(i, 10, 32)
|
|
if err != nil {
|
|
return errors.Wrap(fmt.Errorf("error converting PCR index to int32"), err)
|
|
}
|
|
value, err := hex.DecodeString(v)
|
|
if err != nil {
|
|
return errors.Wrap(fmt.Errorf("error converting PCR value to byte"), err)
|
|
}
|
|
if !bytes.Equal(quote.Pcrs.Pcrs[uint32(index)], value) {
|
|
return fmt.Errorf("for algo %s PCR[%d] expected %s but found %s", ptpm.HashAlgo_name[int32(quote.Pcrs.Hash)], index, hex.EncodeToString(value), hex.EncodeToString(quote.Pcrs.Pcrs[uint32(index)]))
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getPCRValue(index int, algorithm tpm2.Algorithm) ([]byte, error) {
|
|
rwc, err := OpenTpm()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rwc.Close()
|
|
|
|
pcrValue, err := tpm2.ReadPCR(rwc, index, algorithm)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return pcrValue, nil
|
|
}
|
|
|
|
func GetPCRSHA1Value(index int) ([]byte, error) {
|
|
return getPCRValue(index, tpm2.AlgSHA1)
|
|
}
|
|
|
|
func GetPCRSHA256Value(index int) ([]byte, error) {
|
|
return getPCRValue(index, tpm2.AlgSHA256)
|
|
}
|
|
|
|
func GetPCRSHA384Value(index int) ([]byte, error) {
|
|
return getPCRValue(index, tpm2.AlgSHA384)
|
|
}
|
|
|
|
func ReadPolicy(policyPath string, attestationConfiguration *attestation.Config) error {
|
|
if policyPath != "" {
|
|
policyData, err := os.ReadFile(policyPath)
|
|
if err != nil {
|
|
return errors.Wrap(ErrAttestationPolicyOpen, err)
|
|
}
|
|
|
|
return ReadPolicyFromByte(policyData, attestationConfiguration)
|
|
}
|
|
|
|
return ErrAttestationPolicyMissing
|
|
}
|
|
|
|
func ReadPolicyFromByte(policyData []byte, attestationConfiguration *attestation.Config) error {
|
|
unmarshalOptions := protojson.UnmarshalOptions{AllowPartial: true, DiscardUnknown: true}
|
|
|
|
if err := unmarshalOptions.Unmarshal(policyData, attestationConfiguration.Config); err != nil {
|
|
return errors.Wrap(ErrAttestationPolicyDecode, err)
|
|
}
|
|
|
|
if err := json.Unmarshal(policyData, attestationConfiguration.PcrConfig); err != nil {
|
|
return errors.Wrap(ErrAttestationPolicyDecode, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func ConvertPolicyToJSON(attestationConfiguration *attestation.Config) ([]byte, error) {
|
|
pbJson, err := protojson.Marshal(attestationConfiguration.Config)
|
|
if err != nil {
|
|
return nil, errors.Wrap(ErrProtoMarshalFailed, err)
|
|
}
|
|
|
|
var pbMap map[string]interface{}
|
|
if err := json.Unmarshal(pbJson, &pbMap); err != nil {
|
|
return nil, errors.Wrap(ErrJsonUnarshalFailed, err)
|
|
}
|
|
|
|
pcrJson, err := json.Marshal(attestationConfiguration.PcrConfig)
|
|
if err != nil {
|
|
return nil, errors.Wrap(ErrJsonMarshalFailed, err)
|
|
}
|
|
|
|
var pcrMap map[string]interface{}
|
|
if err := json.Unmarshal(pcrJson, &pcrMap); err != nil {
|
|
return nil, errors.Wrap(ErrJsonUnarshalFailed, err)
|
|
}
|
|
|
|
for k, v := range pcrMap {
|
|
pbMap[k] = v
|
|
}
|
|
|
|
return json.MarshalIndent(pbMap, "", " ")
|
|
}
|