Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ go_sdk.download(version = "1.26.4")

go_deps = use_extension("@bazel_gazelle//:extensions.bzl", "go_deps")
go_deps.from_file(go_mod = "//:go.mod")
use_repo(go_deps, "com_github_coredhcp_coredhcp", "com_github_golang_glog", "com_github_google_go_cmp", "com_github_google_go_tpm", "com_github_insomniacslk_dhcp", "com_github_openconfig_monax", "org_golang_google_grpc", "org_golang_google_grpc_cmd_protoc_gen_go_grpc", "org_golang_google_protobuf", "org_golang_x_net", "org_mozilla_go_pkcs7")
use_repo(go_deps, "com_github_coredhcp_coredhcp", "com_github_golang_glog", "com_github_google_go_cmp", "com_github_google_go_tpm", "com_github_insomniacslk_dhcp", "com_github_jonboulle_clockwork", "com_github_openconfig_monax", "org_golang_google_grpc", "org_golang_google_grpc_cmd_protoc_gen_go_grpc", "org_golang_google_protobuf", "org_golang_x_net", "org_mozilla_go_pkcs7")

go_deps_dev = use_extension("@bazel_gazelle//:extensions.bzl", "go_deps", dev_dependency = True)

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require (
github.com/google/go-cmp v0.7.0
github.com/google/go-tpm v0.9.8
github.com/insomniacslk/dhcp v0.0.0-20260603135910-a415979eb11e
github.com/jonboulle/clockwork v0.5.0
github.com/openconfig/attestz v0.6.15
github.com/openconfig/gnsi v1.9.1
github.com/openconfig/monax v0.0.0-20260720151517-883500dc841b
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/insomniacslk/dhcp v0.0.0-20260603135910-a415979eb11e h1:7j1+lOuGBg7PQF1RxeVx0iP+/GpiAxSG/F+9t5JOS94=
github.com/insomniacslk/dhcp v0.0.0-20260603135910-a415979eb11e/go.mod h1:qfvBmyDNp+/liLEYWRvqny/PEz9hGe2Dz833eXILSmo=
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
Expand Down
2 changes: 2 additions & 0 deletions server/service/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ go_library(
"//proto:bootz",
"@com_github_golang_glog//:glog",
"@com_github_google_go_tpm//tpm2",
"@com_github_jonboulle_clockwork//:clockwork",
"@openconfig_attestz//proto:tpm_enrollz_go",
"@openconfig_attestz//service/biz:enrollz_biz",
"@openconfig_attestz//service/biz:tpm20_utils",
Expand All @@ -50,6 +51,7 @@ go_test(
"//proto:bootz",
"@com_github_google_go_cmp//cmp",
"@com_github_google_go_tpm//tpm2",
"@com_github_jonboulle_clockwork//:clockwork",
"@openconfig_attestz//proto:tpm_enrollz_go",
"@openconfig_attestz//service/biz:enrollz_biz",
"@openconfig_attestz//service/biz:tpm20_utils",
Expand Down
54 changes: 39 additions & 15 deletions server/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,11 @@ import (
"net"
"slices"
"strings"
"time"

log "github.com/golang/glog"
"github.com/google/go-tpm/tpm2"
"github.com/jonboulle/clockwork"
"github.com/openconfig/attestz/service/biz"
ownercertificate "github.com/openconfig/bootz/common/owner_certificate"
"github.com/openconfig/bootz/common/signature"
Expand All @@ -62,6 +64,10 @@ const (
stateAttested
)

const (
hmacChallengeTimeout = 10 * time.Second
)

// ArtifactManager is an interface for providing security artifacts to the Bootz service. These artifacts are
// associated either with the Bootz server itself (e.g. the Bootz server trust anchor keypair), or with a specific
// control card in a chassis (e.g. Ownership Vouchers, EK/PPK keys).
Expand Down Expand Up @@ -95,27 +101,30 @@ type Service struct {
am ArtifactManager
cm ChassisManager
tpm20 biz.TPM20Utils
clock clockwork.Clock
}

type streamSession struct {
stream bpb.Bootstrap_BootstrapStreamServer
currentState int
chassis *types.Chassis // Store chassis info for later stages
status *bpb.ReportStatusRequest // Store status for later stages
clientNonce string // client nonce from bootstrap request
serverNonce string // For TPM 2.0 with IDevID nonce challenge
hmacSensitive *tpm2.TPMTSensitive // For TPM 2.0 without IDevID HMAC challenge
stream bpb.Bootstrap_BootstrapStreamServer
currentState int
chassis *types.Chassis // Store chassis info for later stages
status *bpb.ReportStatusRequest // Store status for later stages
clientNonce string // client nonce from bootstrap request
serverNonce string // For TPM 2.0 with IDevID nonce challenge
hmacSensitive *tpm2.TPMTSensitive // For TPM 2.0 without IDevID HMAC challenge
challengeSentTime time.Time
}

type streamSessionV1 struct {
stream bpb.Bootstrap_BootstrapStreamV1Server
currentState int
chassis *types.Chassis // Store chassis info for later stages
status *bpb.ReportStatusRequest // Store status for later stages
clientNonce string // client nonce from bootstrap request
serverNonce []byte // For TPM 2.0 with IDevID nonce challenge
hmacSensitive *tpm2.TPMTSensitive // For TPM 2.0 without IDevID HMAC challenge
hmacKey []byte // For TPM 1.2 EK challenge
stream bpb.Bootstrap_BootstrapStreamV1Server
currentState int
chassis *types.Chassis // Store chassis info for later stages
status *bpb.ReportStatusRequest // Store status for later stages
clientNonce string // client nonce from bootstrap request
serverNonce []byte // For TPM 2.0 with IDevID nonce challenge
hmacSensitive *tpm2.TPMTSensitive // For TPM 2.0 without IDevID HMAC challenge
hmacKey []byte // For TPM 1.2 EK challenge
challengeSentTime time.Time
}

// TPMAsymCAContents is the TPM_ASYM_CA_CONTENTS structure defined in the TPM 1.2 specification.
Expand Down Expand Up @@ -344,6 +353,10 @@ func (s *Service) BootstrapStream(stream bpb.Bootstrap_BootstrapStreamServer) er
if session.hmacSensitive == nil {
return status.Errorf(codes.FailedPrecondition, "received unexpected TPM 2.0 HMAC challenge response")
}
if elapsed := s.clock.Since(session.challengeSentTime); elapsed > hmacChallengeTimeout {
log.Errorf("HMAC challenge timeout exceeded for device %s: took %v, limit %v", session.chassis.ActiveSerial, elapsed, hmacChallengeTimeout)
return status.Errorf(codes.DeadlineExceeded, "HMAC challenge timeout exceeded")
}
hmacResponse := challengeResponse.GetHmacChallengeResponse()

tpm2BAttest, err := tpm2.Unmarshal[tpm2.TPM2BAttest](hmacResponse.GetIakCertifyInfo())
Expand Down Expand Up @@ -537,6 +550,10 @@ func (s *Service) BootstrapStreamV1(stream bpb.Bootstrap_BootstrapStreamV1Server
if session.hmacSensitive == nil {
return status.Errorf(codes.FailedPrecondition, "received unexpected TPM20HMAC challenge response")
}
if elapsed := s.clock.Since(session.challengeSentTime); elapsed > hmacChallengeTimeout {
log.Errorf("HMAC challenge timeout exceeded for device %s: took %v, limit %v", session.chassis.ActiveSerial, elapsed, hmacChallengeTimeout)
return status.Errorf(codes.DeadlineExceeded, "HMAC challenge timeout exceeded")
}
// Verify HMAC challenge response.
mac := challengeResponse.GetTpm20Hmac().GetHmac()
tpm2BAttest, err := tpm2.Unmarshal[tpm2.TPM2BAttest](mac.GetIakCertifyInfo())
Expand Down Expand Up @@ -710,6 +727,9 @@ func (s *Service) BootstrapStreamV1(stream bpb.Bootstrap_BootstrapStreamV1Server
return status.Errorf(codes.Internal, "failed to send BootstrapStreamResponseV1 message: %v", err)
}
log.Infof("Sent BootstrapStreamResponseV1 message to device %s", session.chassis.ActiveSerial)
if (session.currentState == stateChallengeSent || session.currentState == stateReauthChallengeSent) && session.hmacSensitive != nil {
session.challengeSentTime = s.clock.Now()
}
}
}

Expand Down Expand Up @@ -1050,6 +1070,9 @@ func (s *Service) establishSessionAndSendChallenge(session *streamSession) error
return err
}
log.Infof("Sent challenge to device %s", session.chassis.ActiveSerial)
if session.hmacSensitive != nil {
session.challengeSentTime = s.clock.Now()
}
return nil
}

Expand Down Expand Up @@ -1150,5 +1173,6 @@ func New(am ArtifactManager, cm ChassisManager, tpm20 biz.TPM20Utils) (*Service,
am: am,
cm: cm,
tpm20: tpm20,
clock: clockwork.NewRealClock(),
}, nil
}
51 changes: 45 additions & 6 deletions server/service/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@ import (
"math/big"
"net"
"testing"
"time"

"github.com/google/go-cmp/cmp"
"github.com/google/go-tpm/tpm2"
"github.com/jonboulle/clockwork"
"github.com/openconfig/attestz/service/biz"
ownercertificate "github.com/openconfig/bootz/common/owner_certificate"
ownershipvoucher "github.com/openconfig/bootz/common/ownership_voucher"
Expand Down Expand Up @@ -239,12 +241,13 @@ func TestBootstrapStream(t *testing.T) {
idNoIdevid := &bpb.Identity{Type: &bpb.Identity_EkPpkPub{EkPpkPub: true}}

tests := []struct {
name string
req *bpb.BootstrapStreamRequest
id *bpb.Identity
wantErrCode codes.Code
signedNonce []byte
reportStatus bool
name string
req *bpb.BootstrapStreamRequest
id *bpb.Identity
wantErrCode codes.Code
wantSecondRecvErrCode codes.Code
signedNonce []byte
reportStatus bool
}{
{
name: "Missing Identity - Invalid Argument",
Expand All @@ -271,6 +274,12 @@ func TestBootstrapStream(t *testing.T) {
req: statusReq,
id: idNoIdevid,
},
{
name: "TPM 2.0 no-IDevID Flow Timeout - Failure",
req: initialReq,
id: idNoIdevid,
wantSecondRecvErrCode: codes.DeadlineExceeded,
},
}

for _, test := range tests {
Expand All @@ -279,6 +288,11 @@ func TestBootstrapStream(t *testing.T) {
if err != nil {
t.Fatalf("New() failed: %v", err)
}
var fakeClock *clockwork.FakeClock
if test.name == "TPM 2.0 no-IDevID Flow Timeout - Failure" {
fakeClock = clockwork.NewFakeClock()
s.clock = fakeClock
}
srv := grpc.NewServer(grpc.Creds(insecure.NewCredentials()))
bpb.RegisterBootstrapServer(srv, s)
addr := startTestServer(t, srv)
Expand Down Expand Up @@ -371,12 +385,23 @@ func TestBootstrapStream(t *testing.T) {
default:
t.Fatalf("Unexpected challenge type %T", challengeType)
}
if fakeClock != nil {
fakeClock.Advance(11 * time.Second)
}
if err := stream.Send(responseReq); err != nil {
t.Fatalf("stream.Send(responseReq) failed: %v", err)
}

// === Second Recv: Expect Bootstrap Data or Error ===
finalResp, err := stream.Recv()
if test.wantSecondRecvErrCode != codes.OK {
if err == nil {
t.Errorf("stream.Recv() got response %v, want error code %v", finalResp, test.wantSecondRecvErrCode)
} else if stat, ok := status.FromError(err); ok && stat.Code() != test.wantSecondRecvErrCode {
t.Errorf("stream.Recv() got error code %v, want %v: %v", stat.Code(), test.wantSecondRecvErrCode, err)
}
return
}
if err != nil {
t.Fatalf("stream.Recv() for final response got unexpected error: %v", err)
}
Expand Down Expand Up @@ -530,6 +555,12 @@ func TestBootstrapStreamV1(t *testing.T) {
id: idIdevid,
wantCodes: []codes.Code{codes.OK, codes.OK},
},
{
name: "TPM 2.0 EK Flow Timeout - Failure",
req: initialReq,
id: idTPM20EK,
wantCodes: []codes.Code{codes.OK, codes.DeadlineExceeded},
},
Comment thread
gh4683 marked this conversation as resolved.
}

for _, test := range tests {
Expand All @@ -538,6 +569,11 @@ func TestBootstrapStreamV1(t *testing.T) {
if err != nil {
t.Fatalf("New() failed: %v", err)
}
var fakeClock *clockwork.FakeClock
if test.name == "TPM 2.0 EK Flow Timeout - Failure" {
fakeClock = clockwork.NewFakeClock()
s.clock = fakeClock
}
srv := grpc.NewServer(grpc.Creds(insecure.NewCredentials()))
bpb.RegisterBootstrapServer(srv, s)
addr := startTestServer(t, srv)
Expand Down Expand Up @@ -643,6 +679,9 @@ func TestBootstrapStreamV1(t *testing.T) {
},
}
}
if fakeClock != nil {
fakeClock.Advance(11 * time.Second)
}
case 2: // Send Report Status Request.
request = proto.Clone(statusReq).(*bpb.BootstrapStreamRequestV1)
}
Expand Down
Loading