Skip to content

Commit c0e0d53

Browse files
authored
[launcher] Fix CEL RTMR extending (google#635)
Add image tests for TDX machines Add TDX support in the fakeverifier
1 parent 9e03b3b commit c0e0d53

File tree

7 files changed

+123
-18
lines changed

7 files changed

+123
-18
lines changed

launcher/agent/agent.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,7 @@ func CreateAttestationAgent(tpm io.ReadWriteCloser, akFetcher util.TpmKeyFetcher
121121
for _, sel := range pcrSels {
122122
hashAlgo, err := sel.Hash.Hash()
123123
if err != nil {
124-
if err != nil {
125-
return nil, fmt.Errorf("failed to get TPM hash algorithm: %v", err)
126-
}
124+
return nil, fmt.Errorf("failed to get TPM hash algorithm: %v", err)
127125
}
128126
hashAlgos = append(hashAlgos, hashAlgo)
129127
}
@@ -352,8 +350,8 @@ func (t *tdxAttestRoot) GetCEL() gecel.CEL {
352350
}
353351

354352
func (t *tdxAttestRoot) Extend(c gecel.Content) error {
355-
return t.cosCel.AppendEvent(c, []crypto.Hash{crypto.SHA384}, cel.CosRTMR+1, func(ch crypto.Hash, mrIndex int, digest []byte) error {
356-
return rtmr.ExtendEventLogSysfs(mrIndex-1, ch, digest) // MR_INDEX - 1 == RTMR_INDEX
353+
return t.cosCel.AppendEvent(c, []crypto.Hash{crypto.SHA384}, cel.CosCCELMRIndex, func(_ crypto.Hash, mrIndex int, digest []byte) error {
354+
return rtmr.ExtendDigestSysfs(mrIndex-1, digest) // MR_INDEX - 1 == RTMR_INDEX
357355
})
358356
}
359357

launcher/cloudbuild.yaml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,22 @@ steps:
133133
--substitutions _IMAGE_NAME=${OUTPUT_IMAGE_PREFIX}-debug-${OUTPUT_IMAGE_SUFFIX},_IMAGE_PROJECT=${PROJECT_ID}
134134
exit
135135
136+
- name: 'gcr.io/cloud-builders/gcloud'
137+
id: DebugImageTestsTDX
138+
waitFor: ['DebugImageBuild']
139+
env:
140+
- 'OUTPUT_IMAGE_PREFIX=${_OUTPUT_IMAGE_PREFIX}'
141+
- 'OUTPUT_IMAGE_SUFFIX=${_OUTPUT_IMAGE_SUFFIX}'
142+
- 'PROJECT_ID=$PROJECT_ID'
143+
script: |
144+
#!/usr/bin/env bash
145+
146+
cd launcher/image/test
147+
echo "running debug image tests on ${OUTPUT_IMAGE_PREFIX}-debug-${OUTPUT_IMAGE_SUFFIX}"
148+
gcloud builds submit --config=test_debug_cloudbuild.yaml --region us-west1 \
149+
--substitutions _CC=TDX,_IMAGE_NAME=${OUTPUT_IMAGE_PREFIX}-debug-${OUTPUT_IMAGE_SUFFIX},_IMAGE_PROJECT=${PROJECT_ID}
150+
exit
151+
136152
- name: 'gcr.io/cloud-builders/gcloud'
137153
id: HardenedImageTests
138154
waitFor: ['HardenedImageBuild']
@@ -148,6 +164,23 @@ steps:
148164
gcloud builds submit --config=test_hardened_cloudbuild.yaml --region us-west1 \
149165
--substitutions _IMAGE_NAME=${OUTPUT_IMAGE_PREFIX}-hardened-${OUTPUT_IMAGE_SUFFIX},_IMAGE_PROJECT=${PROJECT_ID}
150166
exit
167+
168+
- name: 'gcr.io/cloud-builders/gcloud'
169+
id: HardenedImageTestsTDX
170+
waitFor: ['HardenedImageBuild']
171+
env:
172+
- 'OUTPUT_IMAGE_PREFIX=${_OUTPUT_IMAGE_PREFIX}'
173+
- 'OUTPUT_IMAGE_SUFFIX=${_OUTPUT_IMAGE_SUFFIX}'
174+
- 'PROJECT_ID=$PROJECT_ID'
175+
script: |
176+
#!/usr/bin/env bash
177+
178+
cd launcher/image/test
179+
echo "running hardened image tests on ${OUTPUT_IMAGE_PREFIX}-hardened-${OUTPUT_IMAGE_SUFFIX}"
180+
gcloud builds submit --config=test_hardened_cloudbuild.yaml --region us-west1 \
181+
--substitutions _CC=TDX,_IMAGE_NAME=${OUTPUT_IMAGE_PREFIX}-hardened-${OUTPUT_IMAGE_SUFFIX},_IMAGE_PROJECT=${PROJECT_ID}
182+
exit
183+
151184
- name: 'gcr.io/cloud-builders/gcloud'
152185
id: LaunchPolicyTests
153186
waitFor: ['HardenedImageBuild']

launcher/image/entrypoint.sh

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ main() {
1818
systemctl enable container-runner.service
1919
systemctl start container-runner.service
2020
systemctl start fluent-bit.service
21-
2221
}
2322

2423
main

launcher/image/test/create_vm.sh

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ print_usage() {
99
echo " -f <metadataFromFile>: read a metadata value from a file; specified in format key=filePath"
1010
echo " -n <instanceName>: instance name"
1111
echo " -z <instanceZone>: instance zone"
12+
echo " -c <confidentialComputing>: TDX or SEV"
1213
exit 1
1314
}
1415

@@ -18,6 +19,20 @@ create_vm() {
1819
exit 1
1920
fi
2021

22+
if [ -z "$CC" ]; then
23+
CC='SEV'
24+
fi
25+
26+
MACHINE_TYPE=''
27+
if [[ "${CC}" == "SEV" ]]; then
28+
MACHINE_TYPE='n2d-standard-2'
29+
elif [[ "${CC}" == "TDX" ]]; then
30+
MACHINE_TYPE='c3-standard-4'
31+
else
32+
echo "unsupported confidential computing type: ${CC}"
33+
exit 1
34+
fi
35+
2136
# use the fake verifier for all tests
2237
FAKE_VERIFIER='test-fake-verifier=true'
2338

@@ -48,10 +63,10 @@ create_vm() {
4863
ADDTL_DISK_RANGE=$(($MAX_DISK_SIZE_GB - $MIN_DISK_SIZE + 1))
4964
DISK_SIZE_GB=$(($MIN_DISK_SIZE + ($RANDOM % $ADDTL_DISK_RANGE)))
5065

51-
gcloud compute instances create $VM_NAME --confidential-compute --maintenance-policy=TERMINATE \
52-
--machine-type=n2d-standard-2 --boot-disk-size=$DISK_SIZE_GB --scopes=cloud-platform --zone $ZONE \
53-
--image=$IMAGE_NAME --image-project=$PROJECT_NAME --shielded-secure-boot $APPEND_METADATA \
54-
$APPEND_METADATA_FILE
66+
gcloud compute instances create $VM_NAME --confidential-compute-type=$CC --maintenance-policy=TERMINATE \
67+
--machine-type=$MACHINE_TYPE --boot-disk-size=$DISK_SIZE_GB --scopes=cloud-platform --zone $ZONE \
68+
--image=$IMAGE_NAME --image-project=$PROJECT_NAME --shielded-secure-boot $APPEND_METADATA \
69+
$APPEND_METADATA_FILE
5570
}
5671

5772
IMAGE_NAME=''
@@ -60,17 +75,19 @@ METADATA=''
6075
PROJECT_NAME=''
6176
VM_NAME=''
6277
ZONE=''
78+
CC='SEV' # default using sev
6379

6480
# In getopts, a ':' following a letter means that that flag takes an argument.
6581
# For example, i: means -i takes an additional argument.
66-
while getopts 'i:f:m:p:n:z:' flag; do
82+
while getopts 'i:f:m:p:n:z:c:' flag; do
6783
case "${flag}" in
6884
i) IMAGE_NAME=${OPTARG} ;;
6985
f) METADATA_FILE=${OPTARG} ;;
7086
m) METADATA=${OPTARG} ;;
7187
p) PROJECT_NAME=${OPTARG} ;;
7288
n) VM_NAME=${OPTARG} ;;
7389
z) ZONE=${OPTARG} ;;
90+
c) CC=${OPTARG} ;;
7491
*) print_usage ;;
7592
esac
7693
done

launcher/image/test/test_debug_cloudbuild.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ substitutions:
44
'_CLEANUP': 'true'
55
'_VM_NAME_PREFIX': 'cs-debug-test'
66
'_ZONE': 'us-central1-a'
7+
'_CC': ''
78
'_WORKLOAD_IMAGE': 'us-west1-docker.pkg.dev/confidential-space-images-dev/cs-integ-test-images/basic-test:latest,tee-cmd=["newCmd"],tee-env-ALLOWED_OVERRIDE=overridden'
89
steps:
910
- name: 'gcr.io/cloud-builders/gcloud'
@@ -16,6 +17,7 @@ steps:
1617
'-m', 'tee-image-reference=${_WORKLOAD_IMAGE},tee-container-log-redirect=true',
1718
'-n', '${_VM_NAME_PREFIX}-${BUILD_ID}',
1819
'-z', '${_ZONE}',
20+
'-c', '${_CC}',
1921
]
2022
- name: 'gcr.io/cloud-builders/gcloud'
2123
id: BasicWorkloadTest

launcher/image/test/test_hardened_cloudbuild.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ substitutions:
88
'_CLEANUP': 'true'
99
'_VM_NAME_PREFIX': 'cs-hardened-test'
1010
'_ZONE': 'us-west1-a'
11+
'_CC': ''
1112
'_WORKLOAD_IMAGE': 'us-west1-docker.pkg.dev/confidential-space-images-dev/cs-integ-test-images/basic-test:latest'
1213
steps:
1314
- name: 'gcr.io/cloud-builders/gcloud'
@@ -21,6 +22,7 @@ steps:
2122
'-m', 'tee-image-reference=${_WORKLOAD_IMAGE},tee-container-log-redirect=true,tee-cmd=["newCmd"],tee-env-ALLOWED_OVERRIDE=overridden',
2223
'-n', '${_VM_NAME_PREFIX}-${BUILD_ID}',
2324
'-z', '${_ZONE}',
25+
'-c', '${_CC}',
2426
]
2527
- name: 'gcr.io/cloud-builders/gcloud'
2628
id: BasicWorkloadTest

verifier/fake/fakeverifier.go

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,17 @@ import (
66
"crypto"
77
"encoding/base64"
88
"encoding/binary"
9+
"errors"
910
"fmt"
1011
"strings"
1112
"time"
1213

1314
"github.com/golang-jwt/jwt/v4"
1415
"github.com/google/go-eventlog/proto/state"
1516
"github.com/google/go-eventlog/register"
17+
tabi "github.com/google/go-tdx-guest/abi"
18+
tdxpb "github.com/google/go-tdx-guest/proto/tdx"
19+
"github.com/google/go-tdx-guest/rtmr"
1620
"github.com/google/go-tpm-tools/proto/attest"
1721
"github.com/google/go-tpm-tools/proto/tpm"
1822
"github.com/google/go-tpm-tools/server"
@@ -53,12 +57,37 @@ func (fc *fakeClient) CreateChallenge(_ context.Context) (*verifier.Challenge, e
5357
}, nil
5458
}
5559

56-
// VerifyAttestation calls server.VerifyAttestation against the request's public key.
57-
// It returns the marshaled MachineState as a claim.
58-
func (fc *fakeClient) VerifyAttestation(_ context.Context, req verifier.VerifyAttestationRequest) (*verifier.VerifyAttestationResponse, error) {
59-
// Determine signing algorithm.
60-
signingMethod := jwt.SigningMethodRS256
61-
now := jwt.TimeFunc()
60+
func verifyTDX(req verifier.VerifyAttestationRequest, nonce []byte) (*attest.MachineState, error) {
61+
tdQuote, err := tabi.QuoteToProto(req.TDCCELAttestation.TdQuote)
62+
if err != nil {
63+
return nil, fmt.Errorf("failed to marshal TdQuote: %v", err)
64+
}
65+
quoteV4, ok := tdQuote.(*tdxpb.QuoteV4)
66+
if !ok {
67+
return nil, errors.New("failed to convert TdQuote: not of type QuoteV4")
68+
}
69+
rtmrbank, err := rtmr.GetRtmrsFromTdQuote(quoteV4)
70+
if err != nil {
71+
return nil, err
72+
}
73+
cosState, err := server.ParseCosCELRTMR(req.TDCCELAttestation.CanonicalEventLog, *rtmrbank)
74+
if err != nil {
75+
return nil, fmt.Errorf("failed to validate the Canonical event log: %w", err)
76+
}
77+
opts := rtmr.TdxDefaultOpts(nonce)
78+
fls, err := rtmr.ParseCcelWithTdQuote(req.TDCCELAttestation.CcelData, req.TDCCELAttestation.CcelAcpiTable, quoteV4, &opts)
79+
if err != nil {
80+
return nil, fmt.Errorf("failed to parse CCEL: %w", err)
81+
}
82+
ms, err := server.ConvertToMachineState(fls)
83+
if err != nil {
84+
return nil, fmt.Errorf("failed to convert to MachineState: %w", err)
85+
}
86+
ms.Cos = cosState
87+
return ms, nil
88+
}
89+
90+
func verifyTPM(req verifier.VerifyAttestationRequest, nonce []byte) (*attest.MachineState, error) {
6291
akPub, err := tpm2.DecodePublic(req.Attestation.GetAkPub())
6392
if err != nil {
6493
return nil, fmt.Errorf("failed to decode AKPub as TPMT_PUBLIC: %v", err)
@@ -67,7 +96,7 @@ func (fc *fakeClient) VerifyAttestation(_ context.Context, req verifier.VerifyAt
6796
if err != nil {
6897
return nil, fmt.Errorf("failed to convert TPMT_PUBLIC to crypto.PublicKey: %v", err)
6998
}
70-
ms, err := server.VerifyAttestation(req.Attestation, server.VerifyOpts{Nonce: fc.nonce, TrustedAKs: []crypto.PublicKey{akCrypto}})
99+
ms, err := server.VerifyAttestation(req.Attestation, server.VerifyOpts{Nonce: nonce, TrustedAKs: []crypto.PublicKey{akCrypto}})
71100
if err != nil {
72101
return nil, fmt.Errorf("failed to verify attestation: %v", err)
73102
}
@@ -82,6 +111,31 @@ func (fc *fakeClient) VerifyAttestation(_ context.Context, req verifier.VerifyAt
82111
return nil, fmt.Errorf("failed to validate the Canonical event log: %w", err)
83112
}
84113
ms.Cos = cosState
114+
return ms, nil
115+
}
116+
117+
// VerifyAttestation calls server.VerifyAttestation against the request's public key.
118+
// It returns the marshaled MachineState as a claim.
119+
func (fc *fakeClient) VerifyAttestation(_ context.Context, req verifier.VerifyAttestationRequest) (*verifier.VerifyAttestationResponse, error) {
120+
// Determine signing algorithm.
121+
signingMethod := jwt.SigningMethodRS256
122+
now := jwt.TimeFunc()
123+
var ms *attest.MachineState
124+
var err error
125+
126+
if req.TDCCELAttestation != nil {
127+
ms, err = verifyTDX(req, fc.nonce)
128+
if err != nil {
129+
return nil, err
130+
}
131+
} else if req.Attestation != nil {
132+
ms, err = verifyTPM(req, fc.nonce)
133+
if err != nil {
134+
return nil, err
135+
}
136+
} else {
137+
return nil, errors.New("contains no attestation in the request")
138+
}
85139

86140
msJSON, err := protojson.Marshal(ms)
87141
if err != nil {

0 commit comments

Comments
 (0)