Files
cocos/pkg/attestation/corimgen/generator.go
T
Sammy Kerata Oina c1cbcec851
CI / lint (push) Has been cancelled
CI / test (agent) (push) Has been cancelled
CI / test (cli) (push) Has been cancelled
CI / test (cmd) (push) Has been cancelled
CI / test (internal) (push) Has been cancelled
CI / test (manager, true) (push) Has been cancelled
CI / test (pkg) (push) Has been cancelled
CI / upload-coverage (push) Has been cancelled
COCOS-577 - Introduce Go-based CoRIM generation and deprecate Rust attestation policy scripts. (#578)
* feat: Introduce Go-based CoRIM generation and deprecate Rust attestation policy scripts.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* feat: Update dependencies and refactor attestation policy handling

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* refactor: Migrate attestation verification to use CoRIM and remove deprecated policy handling and EAT verification tests.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Removed the `tdx` and `sev-snp` attestation policy scripts and their build configurations, along with related build and installation steps from the main Makefile.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* chore: Remove Rust CI workflow and Cargo Dependabot configuration, and enhance Go test setup for attestation policy paths.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* refactor: Use WriteString instead of Write([]byte) for writing policy file content in test.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* feat: Refactor `ca-bundle` command to fetch bundles by product string using a configurable HTTP getter with improved error handling, and simplify `attestation_policy` command usage.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* fix: ignore return value of cmd.Help()

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* feat: Implement CoRIM generation for Azure and GCP attestation policies and add a CLI command to download and verify GCP OVMF files.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* feat: Upgrade Python virtual environment setup to include setuptools and wheel, append computation ID to Docker container names, and improve test robustness with error assertions and conditional skips for runtime tests.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* test: Enhance attestation verification tests, including CoRIM integration and specific platform types like Azure SNP, vTPM, TDX, and IGVM.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* feat: Add comprehensive test cases for `VerifyWithCoRIM` including success and measurement mismatch, and refine reference value validation.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* feat: Add Azure and TDX attestation verification tests and abstract external service dependencies for improved testability.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* feat: Add new test cases for Azure measurement extraction, EAT platform types, IGVM measurement stopping, vTPM CoRIM verification, and GCP OVMF download CLI.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* test: enhance CLI CoRIM generation and ATLS certificate verification tests, and refactor the Azure MAA client to use an interface.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

---------

Signed-off-by: Sammy Oina <sammyoina@gmail.com>
2026-03-19 17:01:24 +01:00

214 lines
6.3 KiB
Go

// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package corimgen
import (
"crypto"
"encoding/hex"
"fmt"
"strings"
"github.com/google/uuid"
"github.com/veraison/corim/comid"
"github.com/veraison/corim/corim"
"github.com/veraison/go-cose"
)
// Legacy SNP Defaults.
const (
SNPDefaultVmpl = 2
SNPDefaultMeasurement = "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" // 48 bytes
)
// Legacy TDX Defaults.
var (
TDXDefaultMrSeam = "5b38e33a6487958b72c3c12a938eaa5e3fd4510c51aeeab58c7d5ecee41d7c436489d6c8e4f92f160b7cad34207b00c1"
TDXDefaultMrTd = "91eb2b44d141d4ece09f0c75c2c53d247a3c68edd7fafe8a3520c942a604a407de03ae6dc5f87f27428b2538873118b7"
TDXDefaultRTMRs = []string{
"ce0891f46a18db93e7691f1cf73ed76593f7dec1b58f0927ccb56a99242bf63bc9551561f9ee7833d40395fae59547ab",
"062ac322e26b10874a84977a09735408a856aec77ff62b4975b1e90e33c18f05220ea522cdbffc3b2cf4451cc209e418",
"5fd86e8c3d5e45386f1ed0852de7e83ae1b774ee4366bd5213c9890e8e3ac8fad3f7e690891d37f7c81ac20a445cc0ff",
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
}
)
// Options defines the configuration for CoRIM generation.
type Options struct {
Platform string // "snp" or "tdx"
Measurement string // Hex-encoded measurement
Product string // SNP processor product name
SVN uint64 // Security Version Number
Policy uint64 // SNP policy flags
RTMRs string // TDX RTMRs (comma-separated hex)
MrSeam string // TDX MRSEAM (hex)
HostData string // SNP host data (hex)
LaunchTCB uint64 // SNP minimum launch TCB
SigningKey crypto.Signer // Optional COSE signing key
}
// GenerateCoRIM generates a CoRIM attestation policy using veraison/corim.
// If SigningKey is provided, the CoRIM will be signed using COSE_Sign1.
func GenerateCoRIM(opts Options) ([]byte, error) {
// Apply defaults
applyDefaults(&opts)
// Create CoMID
comidObj, err := CreateCoMID(opts)
if err != nil {
return nil, fmt.Errorf("failed to create CoMID: %w", err)
}
// Create unsigned CoRIM
unsignedCorim := corim.NewUnsignedCorim()
unsignedCorim.SetID(opts.Platform + "-corim-" + uuid.New().String())
unsignedCorim.AddComid(*comidObj)
// If no signing key, return unsigned CoRIM
if opts.SigningKey == nil {
return unsignedCorim.ToCBOR()
}
// Sign the CoRIM
signedCorim := &corim.SignedCorim{}
signedCorim.UnsignedCorim = *unsignedCorim
// Create COSE signer (use ES256 for ECDSA keys)
signer, err := cose.NewSigner(cose.AlgorithmES256, opts.SigningKey)
if err != nil {
return nil, fmt.Errorf("failed to create COSE signer: %w", err)
}
// Sign the CoRIM - Sign() returns the signed CBOR bytes
signedCBOR, err := signedCorim.Sign(signer)
if err != nil {
return nil, fmt.Errorf("failed to sign CoRIM: %w", err)
}
return signedCBOR, nil
}
// applyDefaults applies platform-specific defaults to options.
func applyDefaults(opts *Options) {
if opts.Platform == "snp" {
if opts.Measurement == "" {
opts.Measurement = SNPDefaultMeasurement
}
} else if opts.Platform == "tdx" {
if opts.Measurement == "" {
opts.Measurement = TDXDefaultMrTd
}
if opts.MrSeam == "" {
opts.MrSeam = TDXDefaultMrSeam
}
if opts.RTMRs == "" {
opts.RTMRs = strings.Join(TDXDefaultRTMRs, ",")
}
}
}
// createCoMID creates a CoMID object for the given platform.
func CreateCoMID(opts Options) (*comid.Comid, error) {
comidObj := comid.NewComid()
// Set tag identity
tagID := opts.Platform + "-tag-" + uuid.New().String()
comidObj.SetTagIdentity(tagID, 0)
// Create reference value with environment and measurements
refVal, err := createReferenceValue(opts)
if err != nil {
return nil, err
}
comidObj.AddReferenceValue(*refVal)
return comidObj, nil
}
// createReferenceValue creates a reference value triple for the platform.
func createReferenceValue(opts Options) (*comid.ReferenceValue, error) {
refVal := &comid.ReferenceValue{}
// Create environment
env := comid.Environment{}
// Set class (platform identifier) - convert google UUID to comid UUID
googleUUID := uuid.New()
classUUID := comid.NewClassUUID(comid.UUID(googleUUID))
env.Class = classUUID
// Add instance if product specified (SNP) - use UUID based on product name
if opts.Product != "" {
// Create a deterministic UUID from the product name
productUUID := uuid.NewSHA1(uuid.NameSpaceOID, []byte(opts.Product))
instance, err := comid.NewUUIDInstance(comid.UUID(productUUID))
if err != nil {
return nil, fmt.Errorf("failed to create instance: %w", err)
}
env.Instance = instance
}
refVal.Environment = env
// Decode main measurement
measBytes, err := hex.DecodeString(opts.Measurement)
if err != nil {
return nil, fmt.Errorf("failed to decode measurement: %w", err)
}
// Create main measurement with UUID key
measUUID := uuid.New()
mval, err := comid.NewUUIDMeasurement(comid.UUID(measUUID))
if err != nil {
return nil, fmt.Errorf("failed to create measurement: %w", err)
}
// Add digest with SHA-256 algorithm (algID = 1)
mval.AddDigest(1, measBytes)
// Add SVN if specified
if opts.SVN > 0 {
mval.SetSVN(opts.SVN)
}
// Initialize measurements slice
refVal.Measurements = comid.Measurements{*mval}
// Platform-specific additions
if opts.Platform == "tdx" {
// Add MRSEAM
if opts.MrSeam != "" {
mrSeamBytes, err := hex.DecodeString(opts.MrSeam)
if err != nil {
return nil, fmt.Errorf("failed to decode MRSEAM: %w", err)
}
seamUUID := uuid.New()
seamMval, err := comid.NewUUIDMeasurement(comid.UUID(seamUUID))
if err != nil {
return nil, fmt.Errorf("failed to create MRSEAM measurement: %w", err)
}
seamMval.AddDigest(1, mrSeamBytes)
refVal.Measurements = append(refVal.Measurements, *seamMval)
}
// Add RTMRs
if opts.RTMRs != "" {
for _, rtmr := range strings.Split(opts.RTMRs, ",") {
rtmrBytes, err := hex.DecodeString(strings.TrimSpace(rtmr))
if err != nil {
return nil, fmt.Errorf("failed to decode RTMR: %w", err)
}
rtmrUUID := uuid.New()
rtmrMval, err := comid.NewUUIDMeasurement(comid.UUID(rtmrUUID))
if err != nil {
return nil, fmt.Errorf("failed to create RTMR measurement: %w", err)
}
rtmrMval.AddDigest(1, rtmrBytes)
refVal.Measurements = append(refVal.Measurements, *rtmrMval)
}
}
}
return refVal, nil
}