-
Notifications
You must be signed in to change notification settings - Fork 157
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
"This code is a Contribution to the OpenConfig Feature Profiles project ("Work") made under the Google Software Grant and Corporate Contributor License Agreement ("CLA") and governed by the Apache License 2.0. No other rights or licenses in or to any of Nokia's intellectual property are granted for any other purpose. This code is provided on an "as is" basis without any warranties of any kind."
- Loading branch information
Showing
12 changed files
with
1,652 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
# attestz-1: Validate attestz for initial install | ||
|
||
## Summary | ||
|
||
In TPM enrollment workflow the switch owner verifies device's Initial Attestation Key (IAK) and Initial DevID (IDevID) certificates (signed by the switch vendor CA) and installs/rotates owner IAK (oIAK) and owner IDevID (oIDevID) certificates (signed by switch owner CA). In TPM attestation workflow switch owner verifies that the device's end-to-end boot state (bootloader, OS, secure boot policy, etc.) matches owner's expectations. | ||
|
||
## Procedure | ||
|
||
Test should verify all success and failure/corner-case scenarios for TPM enrollment and attestation workflows that are specified in [attestz Readme](https://github.com/openconfig/attestz/blob/main/README.md). | ||
|
||
TPM enrollment workflow consists of two APIs defined in openconfig/attestz/blob/main/proto/tpm_enrollz.proto: `GetIakCert` and `RotateOIakCert`. | ||
TPM attestation workflow consists of a single API defined in openconfig/attestz/blob/main/proto/tpm_attestz.proto: `Attest`. | ||
The tests should comprehensively cover the behavior for all three APIs when used both separately and sequentially. | ||
Finally, the tests should cover both initial install/bootstrapping, oIAK/oIDevID rotation and post-install re-attestation workflows. | ||
|
||
## Test Setup | ||
|
||
1. Switch vendor provisioned the device with IAK and IDevID certs following TCG spec [Section 5.2](https://trustedcomputinggroup.org/wp-content/uploads/TPM-2p0-Keys-for-Device-Identity-and-Attestation_v1_r12_pub10082021.pdf#page=20) and [Section 6.2](https://trustedcomputinggroup.org/wp-content/uploads/TPM-2p0-Keys-for-Device-Identity-and-Attestation_v1_r12_pub10082021.pdf#page=30). | ||
2. The device successfully completed the bootz workflow where it obtained and applied all configurations/credentials/certificates and booted into the right OS image. | ||
3. Device is serving `enrollz` and `attestz` gRPC endpoints. | ||
|
||
### attestz-1: Validate attestz for initial install | ||
|
||
The test validates that the device completes TPM enrollment and attestation during initial device bootstrapping/install. | ||
|
||
| ID | Case | Result | | ||
| --- | ---- | ------ | | ||
| attestz-1.1 | Successful enrollment and attestation | Device obtained oIAK and oIDevID certs, updated default SSL profile to rely on the oIDevID cert, and passed attestation for all control cards | | ||
| attestz-1.2 | IAK/IDevID are not present on the device | `GetIakCert` fails with missing IAK/IDevID error | | ||
| attestz-1.3 | Bad request for `GetIakCertRequest`, `RotateOIakCertRequest` and `AttestRequest`. Examples: `ControlCardSelection control_card_selection` is not specified or `control_card_id.role = 0`. Invalid `control_card_id.serial` or `control_card_id.slot` | `GetIakCert`, `RotateOIakCert` and `Attest` fail with detailed invalid request error | | ||
| attestz-1.4 | Store oIAK/oIDevId certs that have different underlying IAK/IDevID pub keys or intended for other control card | `RotateOIakCert` fails with detailed invalid request error | | ||
| attestz-1.5 | `enrollz` workflow followed by a device reboot still results in a successful `attestz` workflow | Device obtained oIAK and oIDevID certs and passed attestation for all control cards | | ||
| attestz-1.6 | Full factory reset of the device after a successful `enrollz` workflow deletes oIAK and oIDevID certs, but does not affect IAK and IDevID certs | After factory reset the device fails to perform `attestz` workflow due to missing oIAK and oIDevID certs | | ||
| attestz-1.7 | Out of bound or repeated `pcr_indices` in `AttestRequest` | `Attest` fails with detailed invalid request error | | ||
| attestz-1.8 | RMA scenarios where an active control card ensures that a newly inserted standby control card completes TPM enrollment and attestation before obtaining **its own set** of owner-issued production credentials/certificates (no sharing of owner-issued production security artifacts is allowed between control cards) | `attestz` on a newly inserted control card fails before the card successfully completes TPM enrollment workflow; all RPCs relying on owner-issued credentials/certs fail on a newly inserted control card before the card successfully completes TPM enrollment and attestation workflows | | ||
| attestz-1.9 | Regardless of which control card was active during `enrollz`, both control cards should be able to successfully complete `attestz` workflow as active control cards | Device obtained oIAK and oIDevID certs and passed attestation for all control cards | | ||
|
||
1. Call `GetIakCert` for an active control card with correct `ControlCardSelection`. | ||
2. Verify that correct IDevID cert was used for establishing TLS session: | ||
* Cert structure matches TCG specification [Section 8](https://trustedcomputinggroup.org/wp-content/uploads/TPM-2p0-Keys-for-Device-Identity-and-Attestation_v1_r12_pub10082021.pdf#page=55). | ||
* Cert is not expired. | ||
* Cert is signed by switch vendor CA. | ||
* Cert is tied to the active control card. | ||
3. Verify IAK cert: | ||
* Cert structure matches TCG spec (similar to IDevID above). | ||
* Cert is not expired. | ||
* Cert is signed by switch vendor CA. | ||
* Cert is tied to the active control card. | ||
* IAK and IDevID cert contain the same device serial number field. | ||
4. Verify that the device returned the correct `ControlCardVendorId` with all fields populated. | ||
5. Issue owner IAK (oIAK) and owner IDevID (oIDevID) certs, which are based on the same underlying public keys, have the same structure and fields, but are signed by a different - owner - CA. | ||
6. Call `RotateOIakCert` to store newly issued oIAK and oIDevID certs and verify successful response. | ||
7. Call `GetIakCert` for a standby control card with correct `ControlCardSelection`. | ||
8. Repeat step (2) (TLS session will be secured by active control card's IDevID) and verify IDevID cert of standby control card was specified in the response payload. | ||
9. Repeat steps (3-6) for the standby control card. | ||
10. Call `Attest` for active control card with correct `ControlCardSelection`, random nonce, hash algo of choice (all should be supported and tested) and all PCR indices. | ||
11. Verify that the correct oIDevID cert was used for establishing TLS session. | ||
12. Verify that the device returned the correct `ControlCardVendorId` with all fields populated. | ||
13. Verify oIAK cert is the same as the one installed earlier. | ||
14. Verify all `pcr_values` match expectations. | ||
15. Verify `quote_signature` signature with oIAK cert. | ||
16. Use `pcr_values` and `quoted` to recompute PCR Quote digest and verify that it matches the one used in `quote_signature`. | ||
17. Call `Attest` for standby control card with correct `ControlCardSelection`, random nonce, hash algo of choice (all should be supported and tested) and all PCR indices. | ||
18. Verify that the oIDevID cert of active control card was used for establishing TLS session and verify that oIDevID cert of standby control card was specified in the response payload. | ||
19. Repeat steps (12-16) for the standby control card. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,276 @@ | ||
// Copyright 2024 Google LLC | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package attestz1 | ||
|
||
import ( | ||
"context" | ||
"flag" | ||
"fmt" | ||
"strings" | ||
"testing" | ||
|
||
cdpb "github.com/openconfig/attestz/proto/common_definitions" | ||
attestzpb "github.com/openconfig/attestz/proto/tpm_attestz" | ||
enrollzpb "github.com/openconfig/attestz/proto/tpm_enrollz" | ||
"github.com/openconfig/featureprofiles/internal/fptest" | ||
"github.com/openconfig/featureprofiles/internal/security/attestz" | ||
"github.com/openconfig/featureprofiles/internal/security/svid" | ||
"github.com/openconfig/ondatra" | ||
"github.com/openconfig/ondatra/gnmi" | ||
"github.com/openconfig/ondatra/gnmi/oc" | ||
) | ||
|
||
const ( | ||
cdpbActive = cdpb.ControlCardRole_CONTROL_CARD_ROLE_ACTIVE | ||
cdpbStandby = cdpb.ControlCardRole_CONTROL_CARD_ROLE_STANDBY | ||
) | ||
|
||
var ( | ||
vendorCaCertPem = flag.String("switch_vendor_ca_cert", "", "a pem file for vendor ca cert used for verifying iDevID/IAK Certs") | ||
ownerCaCertPem = flag.String("switch_owner_ca_cert", "../testdata/owner-ca.cert.pem", "a pem file for ca cert that will be used to sign oDevID/oIAK/mTLS Certs") | ||
ownerCaKeyPem = flag.String("switch_owner_ca_key", "../testdata/owner-ca.key.pem", "a pem file for ca key that will be used to sign oDevID/oIAK/mTLS Certs") | ||
) | ||
|
||
func TestMain(m *testing.M) { | ||
fptest.RunTests(m) | ||
} | ||
|
||
func TestAttestz1(t *testing.T) { | ||
dut := ondatra.DUT(t, "dut") | ||
// Retrieve vendor ca certificate from testdata if not provided in test args. | ||
if *vendorCaCertPem == "" { | ||
*vendorCaCertPem = fmt.Sprintf("../testdata/%s-ca.cert.pem", strings.ToLower(dut.Vendor().String())) | ||
} | ||
|
||
attestzTarget, attestzServer := attestz.SetupBaseline(t, dut) | ||
t.Cleanup(func() { | ||
gnmi.Delete(t, dut, gnmi.OC().System().GrpcServer(*attestzServer.Name).Config()) | ||
attestz.DeleteProfile(t, dut, *attestzServer.SslProfileId) | ||
}) | ||
tc := &attestz.TlsConf{ | ||
Target: attestzTarget, | ||
CaKeyFile: *ownerCaKeyPem, | ||
CaCertFile: *ownerCaCertPem, | ||
} | ||
|
||
// Find active and standby card. | ||
activeCard, standbyCard := attestz.FindControlCards(t, dut) | ||
|
||
t.Run("Attestz-1.1 - Successful enrollment and attestation", func(t *testing.T) { | ||
// Enroll for active & standby card. | ||
activeCard.EnrollzWorkflow(t, dut, tc, *vendorCaCertPem) | ||
standbyCard.EnrollzWorkflow(t, dut, tc, *vendorCaCertPem) | ||
|
||
// Attest for active & standby card. | ||
activeCard.AttestzWorkflow(t, dut, tc) | ||
standbyCard.AttestzWorkflow(t, dut, tc) | ||
}) | ||
|
||
t.Run("Attestz-1.3 - Bad request", func(t *testing.T) { | ||
var as *attestz.AttestzSession | ||
as = tc.NewAttestzSession(t) | ||
defer as.Conn.Close() | ||
invalidSerial := attestz.ParseSerialSelection("000") | ||
|
||
_, err := as.EnrollzClient.GetIakCert(context.Background(), &enrollzpb.GetIakCertRequest{ | ||
ControlCardSelection: invalidSerial, | ||
}) | ||
if err != nil { | ||
t.Logf("Got expected error for GetIakCert bad request %v", err) | ||
} else { | ||
t.Fatal("GetIakCert rpc succeeded but expected to fail.") | ||
} | ||
|
||
attestzRequest := &enrollzpb.RotateOIakCertRequest{ | ||
ControlCardSelection: invalidSerial, | ||
} | ||
_, err = as.EnrollzClient.RotateOIakCert(context.Background(), attestzRequest) | ||
if err != nil { | ||
t.Logf("Got expected error for RotateOIakCert bad request %v", err) | ||
} else { | ||
t.Fatal("RotateOIakCert rpc succeeded but expected to fail.") | ||
} | ||
|
||
_, err = as.AttestzClient.Attest(context.Background(), &attestzpb.AttestRequest{ | ||
ControlCardSelection: invalidSerial, | ||
}) | ||
if err != nil { | ||
t.Logf("Got expected error for Attest bad request %v", err) | ||
} else { | ||
t.Fatal("Attest rpc succeeded but expected to fail.") | ||
} | ||
|
||
}) | ||
|
||
t.Run("Attestz-1.4 - Incorrect Public Key", func(t *testing.T) { | ||
roleA := attestz.ParseRoleSelection(cdpbActive) | ||
roleB := attestz.ParseRoleSelection(cdpbStandby) | ||
|
||
// Get vendor certs. | ||
var as *attestz.AttestzSession | ||
as = tc.NewAttestzSession(t) | ||
defer as.Conn.Close() | ||
resp := as.GetVendorCerts(t, roleA) | ||
activeCard.IAKCert, activeCard.IDevIDCert = resp.IakCert, resp.IdevidCert | ||
resp = as.GetVendorCerts(t, roleB) | ||
standbyCard.IAKCert, standbyCard.IDevIDCert = resp.IakCert, resp.IdevidCert | ||
|
||
caKey, caCert, err := svid.LoadKeyPair(tc.CaKeyFile, tc.CaCertFile) | ||
if err != nil { | ||
t.Fatalf("Could not load ca key/cert. error: %v", err) | ||
} | ||
|
||
// Generate active card's oIAK/oIDevId certs with standby card's public key (to simulate incorrect public key). | ||
standbyIAKCert, err := attestz.LoadCertificate(standbyCard.IAKCert) | ||
if err != nil { | ||
t.Fatalf("Error loading IAK cert for standby card. error: %v", err) | ||
} | ||
t.Logf("Generating oIAK cert for card %v with incorrect public key", activeCard.Name) | ||
oIAKCert := attestz.GenOwnerCert(t, caKey, caCert, activeCard.IAKCert, standbyIAKCert.PublicKey, tc.Target) | ||
|
||
standbyIDevIDCert, err := attestz.LoadCertificate(standbyCard.IDevIDCert) | ||
if err != nil { | ||
t.Fatalf("Error loading IDevID Cert for standby card. error: %v", err) | ||
} | ||
t.Logf("Generating oDevID cert for card %v with incorrect public key", activeCard.Name) | ||
oDevIDCert := attestz.GenOwnerCert(t, caKey, caCert, activeCard.IDevIDCert, standbyIDevIDCert.PublicKey, tc.Target) | ||
|
||
// Verify rotate rpc fails. | ||
attestzRequest := &enrollzpb.RotateOIakCertRequest{ | ||
ControlCardSelection: roleA, | ||
OiakCert: oIAKCert, | ||
OidevidCert: oDevIDCert, | ||
SslProfileId: *attestzServer.SslProfileId, | ||
} | ||
_, err = as.EnrollzClient.RotateOIakCert(context.Background(), attestzRequest) | ||
if err != nil { | ||
t.Logf("Got expected error for RotateOIakCert bad request %v", err) | ||
} else { | ||
t.Fatal("RotateOIakCert rpc succeeded but expected to fail.") | ||
} | ||
}) | ||
|
||
t.Run("Attestz-1.5 - Device Reboot", func(t *testing.T) { | ||
activeCard.EnrollzWorkflow(t, dut, tc, *vendorCaCertPem) | ||
standbyCard.EnrollzWorkflow(t, dut, tc, *vendorCaCertPem) | ||
|
||
// Trigger reboot. | ||
attestz.RebootDut(t, dut) | ||
t.Logf("Wait for cards to get synchronized after reboot ...") | ||
attestz.SwitchoverReady(t, dut, activeCard.Name, standbyCard.Name) | ||
|
||
// Check active card after reboot & swap control card struct if required. | ||
rr := gnmi.Get[oc.E_Platform_ComponentRedundantRole](t, dut, gnmi.OC().Component(activeCard.Name).RedundantRole().State()) | ||
if rr != oc.Platform_ComponentRedundantRole_PRIMARY { | ||
t.Logf("Card roles have changed. %s is the new active card.", standbyCard.Name) | ||
*activeCard, *standbyCard = *standbyCard, *activeCard | ||
activeCard.Role = cdpbActive | ||
standbyCard.Role = cdpbStandby | ||
} | ||
|
||
// Verify attest workflow after reboot. | ||
activeCard.AttestzWorkflow(t, dut, tc) | ||
standbyCard.AttestzWorkflow(t, dut, tc) | ||
}) | ||
|
||
t.Run("Attestz-1.6 - Factory Reset", func(t *testing.T) { | ||
activeCard.EnrollzWorkflow(t, dut, tc, *vendorCaCertPem) | ||
standbyCard.EnrollzWorkflow(t, dut, tc, *vendorCaCertPem) | ||
|
||
// Trigger factory reset. | ||
attestz.FactoryResetDut(t, dut) | ||
t.Logf("Wait for cards to get synchronized after factory reset ...") | ||
attestz.SwitchoverReady(t, dut, activeCard.Name, standbyCard.Name) | ||
|
||
// Setup baseline configs again after factory reset (ensure bootz pushes relevant configs used by ondatra bindings). | ||
attestzTarget, attestzServer = attestz.SetupBaseline(t, dut) | ||
activeCard, standbyCard = attestz.FindControlCards(t, dut) | ||
|
||
var as *attestz.AttestzSession | ||
as = tc.NewAttestzSession(t) | ||
defer as.Conn.Close() | ||
|
||
_, err := as.AttestzClient.Attest(context.Background(), &attestzpb.AttestRequest{ | ||
ControlCardSelection: attestz.ParseRoleSelection(cdpbActive), | ||
Nonce: attestz.GenNonce(t), | ||
HashAlgo: attestzpb.Tpm20HashAlgo_TPM20HASH_ALGO_SHA256, | ||
PcrIndices: attestz.PcrIndices, | ||
}) | ||
if err != nil { | ||
t.Logf("Got expected error for Attest rpc of active card %s. error: %s", activeCard.Name, err) | ||
} else { | ||
t.Fatalf("Attest rpc for active card %s succeeded but expected to fail.", activeCard.Name) | ||
} | ||
}) | ||
|
||
t.Run("Attestz-1.7 - Invalid PCR indices", func(t *testing.T) { | ||
activeCard.EnrollzWorkflow(t, dut, tc, *vendorCaCertPem) | ||
var as *attestz.AttestzSession | ||
as = tc.NewAttestzSession(t) | ||
defer as.Conn.Close() | ||
_, err := as.AttestzClient.Attest(context.Background(), &attestzpb.AttestRequest{ | ||
ControlCardSelection: attestz.ParseRoleSelection(activeCard.Role), | ||
Nonce: attestz.GenNonce(t), | ||
HashAlgo: attestzpb.Tpm20HashAlgo_TPM20HASH_ALGO_SHA256, | ||
PcrIndices: []int32{25, -25}, | ||
}) | ||
if err != nil { | ||
t.Logf("Got expected error for Attest bad request %v", err) | ||
} else { | ||
t.Fatal("Expected error in Attest with invalid pcr indices") | ||
} | ||
}) | ||
|
||
// Ensure factory reset test ran before running this test to simulate rma scenario. | ||
t.Run("Attestz-1.8 - Attest failure on standby card", func(t *testing.T) { | ||
// Enroll & attest active card. | ||
activeCard.EnrollzWorkflow(t, dut, tc, *vendorCaCertPem) | ||
activeCard.AttestzWorkflow(t, dut, tc) | ||
|
||
var as *attestz.AttestzSession | ||
as = tc.NewAttestzSession(t) | ||
defer as.Conn.Close() | ||
_, err := as.AttestzClient.Attest(context.Background(), &attestzpb.AttestRequest{ | ||
ControlCardSelection: attestz.ParseRoleSelection(cdpbStandby), | ||
Nonce: attestz.GenNonce(t), | ||
HashAlgo: attestzpb.Tpm20HashAlgo_TPM20HASH_ALGO_SHA256, | ||
PcrIndices: attestz.PcrIndices, | ||
}) | ||
if err != nil { | ||
t.Logf("Got expected error for Attest rpc of standby card %s. error: %s", standbyCard.Name, err) | ||
} else { | ||
t.Fatalf("Attest rpc for standby card %s succeeded but expected to fail.", standbyCard.Name) | ||
} | ||
}) | ||
|
||
t.Run("Attestz-1.9 - Control Card Switchover", func(t *testing.T) { | ||
activeCard.EnrollzWorkflow(t, dut, tc, *vendorCaCertPem) | ||
standbyCard.EnrollzWorkflow(t, dut, tc, *vendorCaCertPem) | ||
|
||
// Trigger control card switchover. | ||
attestz.SwitchoverCards(t, dut, activeCard.Name, standbyCard.Name) | ||
t.Logf("Wait for cards to get synchronized after switchover ...") | ||
attestz.SwitchoverReady(t, dut, activeCard.Name, standbyCard.Name) | ||
|
||
// Swap control card struct after switchover. | ||
*activeCard, *standbyCard = *standbyCard, *activeCard | ||
activeCard.Role = cdpbActive | ||
standbyCard.Role = cdpbStandby | ||
|
||
// Verify attest workflow after switchover. | ||
activeCard.AttestzWorkflow(t, dut, tc) | ||
standbyCard.AttestzWorkflow(t, dut, tc) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto | ||
# proto-message: Metadata | ||
|
||
uuid: "49517013-0588-4565-afbc-3ea41d8db257" | ||
plan_id: "attestz-1" | ||
description: "Validate attestz for initial install" | ||
testbed: TESTBED_DUT |
Oops, something went wrong.