From 85a52f17f2f6843a91085bcb351e8ebfb037d6ba Mon Sep 17 00:00:00 2001 From: Gareth <31602864+gmacf@users.noreply.github.com> Date: Tue, 15 Aug 2023 06:40:07 +0000 Subject: [PATCH 01/14] Implement Entitymanager --- go.mod | 1 + server/entitymanager/entitymanager.go | 113 +++++++++-- server/entitymanager/entitymanager_test.go | 225 +++++++++++++++++++++ server/server.go | 2 +- server/service/service.go | 6 +- 5 files changed, 331 insertions(+), 16 deletions(-) create mode 100644 server/entitymanager/entitymanager_test.go diff --git a/go.mod b/go.mod index a565cef..e856ed0 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.21 require ( github.com/golang/glog v1.1.2 + github.com/google/go-cmp v0.5.9 github.com/openconfig/gnmi v0.0.0-20220617175856-41246b1b3507 github.com/openconfig/gnsi v1.2.1 go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 diff --git a/server/entitymanager/entitymanager.go b/server/entitymanager/entitymanager.go index 1af777e..fac91c3 100644 --- a/server/entitymanager/entitymanager.go +++ b/server/entitymanager/entitymanager.go @@ -2,38 +2,125 @@ package entitymanager import ( + "crypto" + "crypto/rsa" + "crypto/sha256" + "github.com/openconfig/bootz/proto/bootz" "github.com/openconfig/bootz/server/service" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" + + log "github.com/golang/glog" ) // InMemoryEntityManager provides a simple in memory handler // for Entities. -type InMemoryEntityManager struct{} +type InMemoryEntityManager struct { + // inventory represents an organization's inventory of owned chassis. + chassisInventory map[service.EntityLookup]*service.ChassisEntity + // represents the current status of known control cards + controlCardStatuses map[string]bootz.ControlCardState_ControlCardStatus +} // ResolveChassis returns an entity based on the provided lookup. func (m *InMemoryEntityManager) ResolveChassis(lookup *service.EntityLookup) (*service.ChassisEntity, error) { - return nil, status.Errorf(codes.Unimplemented, "Unimplemented") + if e, ok := m.chassisInventory[*lookup]; ok { + return e, nil + } + + return nil, status.Errorf(codes.NotFound, "chassis %+v not found in inventory", *lookup) +} + +func (m *InMemoryEntityManager) GetBootstrapData(c *bootz.ControlCard) (*bootz.BootstrapDataResponse, error) { + // First check if we are expecting this control card. + if c.SerialNumber == "" { + return nil, status.Errorf(codes.InvalidArgument, "no serial number provided") + } + if _, ok := m.controlCardStatuses[c.GetSerialNumber()]; !ok { + return nil, status.Errorf(codes.NotFound, "control card %v not found in inventory", c.GetSerialNumber()) + } + // Construct the response. This emulator hardcodes these values but a real Bootz server would not. + // TODO: Populate these placeholders with realistic ones. + return &bootz.BootstrapDataResponse{ + SerialNum: c.SerialNumber, + IntendedImage: &bootz.SoftwareImage{ + Name: "Default Image", + Version: "1.0", + Url: "https://path/to/image", + OsImageHash: "ABCDEF", + HashAlgorithm: "SHA256", + }, + BootPasswordHash: "ABCD123", + ServerTrustCert: "FakeTLSCert", + BootConfig: &bootz.BootConfig{ + VendorConfig: []byte("Vendor Config"), + OcConfig: []byte("OC Config"), + }, + Credentials: &bootz.Credentials{}, + // TODO: Populate pathz, authz and certificates. + }, nil +} + +func (m *InMemoryEntityManager) SetStatus(req *bootz.ReportStatusRequest) error { + if len(req.GetStates()) == 0 { + return status.Errorf(codes.InvalidArgument, "no control card states provided") + } + log.Infof("Bootstrap Status: %v: Status message: %v", req.GetStatus(), req.GetStatusMessage()) + + for _, c := range req.GetStates() { + previousStatus, ok := m.controlCardStatuses[c.GetSerialNumber()] + if !ok { + return status.Errorf(codes.NotFound, "control card %v not found in inventory", c.GetSerialNumber()) + } + log.Infof("control card %v changed status from %v to %v", c.GetSerialNumber(), previousStatus, c.GetStatus()) + m.controlCardStatuses[c.GetSerialNumber()] = c.GetStatus() + } + return nil } -// GetBootstrapData returns the Bootstrap data for the provided control card. -func (m *InMemoryEntityManager) GetBootstrapData(*bootz.ControlCard) (*bootz.BootstrapDataResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "Unimplemented") +// Sign unmarshals the SignedResponse bytes then generates a signature from its Ownership Certificate private key. +func (m *InMemoryEntityManager) Sign(resp *bootz.GetBootstrapDataResponse, priv *rsa.PrivateKey) error { + if resp.GetSignedResponse() == nil { + return status.Errorf(codes.InvalidArgument, "empty signed response") + } + signedResponseBytes, err := proto.Marshal(resp.GetSignedResponse()) + if err != nil { + return err + } + hashed := sha256.Sum256(signedResponseBytes) + sig, err := rsa.SignPKCS1v15(nil, priv, crypto.SHA256, hashed[:]) + if err != nil { + return err + } + resp.ResponseSignature = string(sig) + return nil } -// SetStatus returns the current status based on the status request. -func (m *InMemoryEntityManager) SetStatus(*bootz.ReportStatusRequest) error { - return status.Errorf(codes.Unimplemented, "Unimplemented") +// AddControlCard adds a new control card to the entity manager. +func (m *InMemoryEntityManager) AddControlCard(serial string) *InMemoryEntityManager { + m.controlCardStatuses[serial] = bootz.ControlCardState_CONTROL_CARD_STATUS_UNSPECIFIED + return m } -// Sign populates the signing fields of the provided Bootstrap data response. -// If fields are set they will be overwritten. -func (m *InMemoryEntityManager) Sign(*bootz.GetBootstrapDataResponse) error { - return status.Errorf(codes.Unimplemented, "Unimplemented") +// AddChassis adds a new chassis to the entity manager. +func (m *InMemoryEntityManager) AddChassis(bootMode bootz.BootMode, manufacturer string, serial string) *InMemoryEntityManager { + l := service.EntityLookup{ + Manufacturer: manufacturer, + SerialNumber: serial, + } + e := service.ChassisEntity{ + BootMode: bootMode, + } + m.chassisInventory[l] = &e + return m } // New returns a new in-memory entity manager. func New() *InMemoryEntityManager { - return &InMemoryEntityManager{} + return &InMemoryEntityManager{ + chassisInventory: make(map[service.EntityLookup]*service.ChassisEntity), + controlCardStatuses: make(map[string]bootz.ControlCardState_ControlCardStatus), + } } diff --git a/server/entitymanager/entitymanager_test.go b/server/entitymanager/entitymanager_test.go new file mode 100644 index 0000000..192df83 --- /dev/null +++ b/server/entitymanager/entitymanager_test.go @@ -0,0 +1,225 @@ +package entitymanager + +import ( + "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/openconfig/bootz/proto/bootz" + "github.com/openconfig/bootz/server/service" + "google.golang.org/protobuf/proto" +) + +func TestResolveChassis(t *testing.T) { + tests := []struct { + desc string + input *service.EntityLookup + want *service.ChassisEntity + wantErr bool + }{ + { + desc: "Default device", + input: &service.EntityLookup{ + SerialNumber: "123", + Manufacturer: "Cisco", + }, + want: &service.ChassisEntity{ + BootMode: bootz.BootMode_BOOT_MODE_SECURE, + }, + }, + { + desc: "Chassis Not Found", + input: &service.EntityLookup{ + SerialNumber: "456", + Manufacturer: "Cisco", + }, + want: nil, + wantErr: true, + }, + } + + em := New().AddChassis(bootz.BootMode_BOOT_MODE_SECURE, "Cisco", "123") + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + got, err := em.ResolveChassis(test.input) + if (err != nil) != test.wantErr { + t.Fatalf("ResolveChassis(%v) err = %v, want %v", test.input, err, test.wantErr) + } + if !cmp.Equal(got, test.want) { + t.Errorf("ResolveChassis(%v) got %v, want %v", test.input, got, test.want) + } + }) + } +} + +func TestSign(t *testing.T) { + tests := []struct { + desc string + resp *bootz.GetBootstrapDataResponse + wantErr bool + }{ + { + desc: "Success", + resp: &bootz.GetBootstrapDataResponse{ + SignedResponse: &bootz.BootstrapDataSigned{ + Responses: []*bootz.BootstrapDataResponse{ + {SerialNum: "123A"}, + }, + }, + }, + wantErr: false, + }, + { + desc: "Empty response", + resp: &bootz.GetBootstrapDataResponse{}, + wantErr: true, + }, + } + + em := New() + priv, err := rsa.GenerateKey(rand.Reader, 4096) + if err != nil { + t.Fatal(err) + } + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + err := em.Sign(test.resp, priv) + if err != nil { + if test.wantErr { + t.Skip() + } + t.Errorf("Sign() err = %v, want %v", err, test.wantErr) + } + signedResponseBytes, err := proto.Marshal(test.resp.GetSignedResponse()) + if err != nil { + t.Fatal(err) + } + hashed := sha256.Sum256(signedResponseBytes) + err = rsa.VerifyPKCS1v15(&priv.PublicKey, crypto.SHA256, hashed[:], []byte(test.resp.GetResponseSignature())) + if err != nil { + t.Errorf("Sign() err == %v, want %v", err, test.wantErr) + } + }) + } +} + +func TestSetStatus(t *testing.T) { + tests := []struct { + desc string + input *bootz.ReportStatusRequest + wantErr bool + }{ + { + desc: "No control card states", + input: &bootz.ReportStatusRequest{ + Status: bootz.ReportStatusRequest_BOOTSTRAP_STATUS_SUCCESS, + StatusMessage: "Bootstrap status succeeded", + }, + wantErr: true, + }, + { + desc: "Control card initialized", + input: &bootz.ReportStatusRequest{ + Status: bootz.ReportStatusRequest_BOOTSTRAP_STATUS_SUCCESS, + StatusMessage: "Bootstrap status succeeded", + States: []*bootz.ControlCardState{ + { + SerialNumber: "123A", + Status: *bootz.ControlCardState_CONTROL_CARD_STATUS_INITIALIZED.Enum(), + }, + }, + }, + wantErr: false, + }, + { + desc: "Unknown control card", + input: &bootz.ReportStatusRequest{ + Status: bootz.ReportStatusRequest_BOOTSTRAP_STATUS_SUCCESS, + StatusMessage: "Bootstrap status succeeded", + States: []*bootz.ControlCardState{ + { + SerialNumber: "123C", + Status: *bootz.ControlCardState_CONTROL_CARD_STATUS_INITIALIZED.Enum(), + }, + }, + }, + wantErr: true, + }, + } + + em := New().AddChassis(bootz.BootMode_BOOT_MODE_SECURE, "Cisco", "123").AddControlCard("123A") + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + err := em.SetStatus(test.input) + if (err != nil) != test.wantErr { + t.Errorf("SetStatus(%v) err = %v, want %v", test.input, err, test.wantErr) + } + }) + } +} + +func TestGetBootstrapData(t *testing.T) { + tests := []struct { + desc string + input *bootz.ControlCard + want *bootz.BootstrapDataResponse + wantErr bool + }{ + { + desc: "No serial number", + input: &bootz.ControlCard{}, + wantErr: true, + }, + { + desc: "Control card not found", + input: &bootz.ControlCard{ + SerialNumber: "456A", + }, + wantErr: true, + }, + { + desc: "Successful bootstrap", + input: &bootz.ControlCard{ + SerialNumber: "123A", + }, + want: &bootz.BootstrapDataResponse{ + SerialNum: "123A", + IntendedImage: &bootz.SoftwareImage{ + Name: "Default Image", + Version: "1.0", + Url: "https://path/to/image", + OsImageHash: "ABCDEF", + HashAlgorithm: "SHA256", + }, + BootPasswordHash: "ABCD123", + ServerTrustCert: "FakeTLSCert", + BootConfig: &bootz.BootConfig{ + VendorConfig: []byte("Vendor Config"), + OcConfig: []byte("OC Config"), + }, + Credentials: &bootz.Credentials{}, + }, + wantErr: false, + }, + } + + em := New().AddChassis(bootz.BootMode_BOOT_MODE_SECURE, "Cisco", "123").AddControlCard("123A") + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + got, err := em.GetBootstrapData(test.input) + if (err != nil) != test.wantErr { + t.Errorf("GetBootstrapData(%v) err = %v, want %v", test.input, err, test.wantErr) + } + if !proto.Equal(got, test.want) { + t.Errorf("GetBootstrapData(%v) got %v, want %v", test.input, got, test.want) + } + }) + } +} diff --git a/server/server.go b/server/server.go index 8183e9c..8f82a85 100644 --- a/server/server.go +++ b/server/server.go @@ -28,7 +28,7 @@ func main() { if *port == "" { log.Exitf("no port selected. specify with the -port flag") } - em := entitymanager.New() + em := entitymanager.New().AddChassis(bootz.BootMode_BOOT_MODE_SECURE, "Cisco", "123").AddControlCard("123A").AddControlCard("123B") c := service.New(em) s := grpc.NewServer() diff --git a/server/service/service.go b/server/service/service.go index 8d26b63..57600c6 100644 --- a/server/service/service.go +++ b/server/service/service.go @@ -2,6 +2,7 @@ package service import ( "context" + "crypto/rsa" "github.com/openconfig/bootz/proto/bootz" "github.com/openconfig/gnmi/errlist" @@ -26,7 +27,7 @@ type EntityManager interface { ResolveChassis(*EntityLookup) (*ChassisEntity, error) GetBootstrapData(*bootz.ControlCard) (*bootz.BootstrapDataResponse, error) SetStatus(*bootz.ReportStatusRequest) error - Sign(*bootz.GetBootstrapDataResponse) error + Sign(*bootz.GetBootstrapDataResponse, *rsa.PrivateKey) error } type Service struct { @@ -74,8 +75,9 @@ func (s *Service) GetBootstrapRequest(ctx context.Context, req *bootz.GetBootstr }, } // Sign the response if Nonce is provided. + // TODO: Populate Sign() with an RSA key. if req.Nonce != "" { - if err := s.em.Sign(resp); err != nil { + if err := s.em.Sign(resp, nil); err != nil { return nil, status.Errorf(codes.Internal, "failed to sign bootz response") } } From aad2bd2b37b5d086549ca297eebeade61d9141af Mon Sep 17 00:00:00 2001 From: Gareth <31602864+gmacf@users.noreply.github.com> Date: Tue, 15 Aug 2023 07:44:20 +0000 Subject: [PATCH 02/14] Read keypairs and OVs from disk --- server/server.go | 81 ++++++++++++++++++++++++++++++++++++++- server/service/service.go | 17 ++++++++ 2 files changed, 97 insertions(+), 1 deletion(-) diff --git a/server/server.go b/server/server.go index 8f82a85..ce8508f 100644 --- a/server/server.go +++ b/server/server.go @@ -9,6 +9,9 @@ import ( "flag" "fmt" "net" + "os" + "path/filepath" + "strings" "github.com/openconfig/bootz/proto/bootz" "github.com/openconfig/bootz/server/entitymanager" @@ -19,15 +22,91 @@ import ( ) var ( - port = flag.String("port", "", "The port to start the Bootz server on localhost") + port = flag.String("port", "", "The port to start the Bootz server on localhost") + artifactDirectory = flag.String("artifact_dir", "../testdata/", "The relative directory to look into for certificates, private keys and OVs.") ) +// readKeyPair reads the cert/key pair from the specified artifacts directory. +// Certs must have the format {name}_pub.pem and keys must have the format {name}_priv.pem +func readKeypair(name string) (*service.KeyPair, error) { + cert, err := os.ReadFile(filepath.Join(*artifactDirectory, fmt.Sprintf("%v_pub.pem", name))) + if err != nil { + return nil, fmt.Errorf("unable to read %v cert: %v", name, err) + } + key, err := os.ReadFile(filepath.Join(*artifactDirectory, fmt.Sprintf("%v_priv.pem", name))) + if err != nil { + return nil, fmt.Errorf("unable to read %v key: %v", name, err) + } + return &service.KeyPair{ + Cert: string(cert), + Key: string(key), + }, nil +} + +// readOVs discovers and reads all available OVs in the artifacts directory. +func readOVs() (service.OVList, error) { + ovs := make(service.OVList) + files, err := os.ReadDir(*artifactDirectory) + if err != nil { + return nil, fmt.Errorf("unable to list files in artifact directory: %v", err) + } + for _, f := range files { + if strings.HasPrefix(f.Name(), "ov") { + bytes, err := os.ReadFile(filepath.Join(*artifactDirectory, f.Name())) + if err != nil { + return nil, err + } + trimmed := strings.TrimPrefix(f.Name(), "ov_") + trimmed = strings.TrimSuffix(trimmed, ".txt") + ovs[trimmed] = string(bytes) + } + } + if len(ovs) == 0 { + return nil, fmt.Errorf("found no OVs in artifacts directory") + } + return ovs, err +} + +// parseSecurityArtifacts reads from the specified directory to find the required keypairs and ownership vouchers. +func parseSecurityArtifacts() (*service.SecurityArtifacts, error) { + oc, err := readKeypair("oc") + if err != nil { + return nil, err + } + pdc, err := readKeypair("pdc") + if err != nil { + return nil, err + } + vendorCA, err := readKeypair("vendorca") + if err != nil { + return nil, err + } + ovs, err := readOVs() + if err != nil { + return nil, err + } + return &service.SecurityArtifacts{ + OC: oc, + PDC: pdc, + VendorCA: vendorCA, + OV: ovs, + }, nil +} + func main() { flag.Parse() if *port == "" { log.Exitf("no port selected. specify with the -port flag") } + if *artifactDirectory == "" { + log.Exitf("no artifact directory specified") + } + // TODO: Pass security artifacts to service. + _, err := parseSecurityArtifacts() + if err != nil { + log.Exit(err) + } em := entitymanager.New().AddChassis(bootz.BootMode_BOOT_MODE_SECURE, "Cisco", "123").AddControlCard("123A").AddControlCard("123B") c := service.New(em) s := grpc.NewServer() diff --git a/server/service/service.go b/server/service/service.go index 57600c6..2de53cd 100644 --- a/server/service/service.go +++ b/server/service/service.go @@ -10,6 +10,23 @@ import ( "google.golang.org/grpc/status" ) +// OVList is a mapping of control card serial number to ownership voucher. +type OVList map[string]string + +// KeyPair is a struct containing PEM-encoded certificates and private keys. +type KeyPair struct { + Cert string + Key string +} + +// SecurityArtifacts contains all KeyPairs and OVs needed for the Bootz Server. +type SecurityArtifacts struct { + OC *KeyPair + PDC *KeyPair + VendorCA *KeyPair + OV OVList +} + // EntityLookup provides a way to resolve chassis and control cards // in the EntityManager. type EntityLookup struct { From 987e2f88c11dd725b55e3693b3be076ed6eb090a Mon Sep 17 00:00:00 2001 From: Gareth <31602864+gmacf@users.noreply.github.com> Date: Tue, 15 Aug 2023 13:23:30 +0000 Subject: [PATCH 03/14] Include ownership voucher in response --- server/entitymanager/entitymanager.go | 12 +++++- server/entitymanager/entitymanager_test.go | 46 ++++++++++++++++++++-- server/server.go | 6 +-- server/service/service.go | 8 ++++ 4 files changed, 64 insertions(+), 8 deletions(-) diff --git a/server/entitymanager/entitymanager.go b/server/entitymanager/entitymanager.go index fac91c3..e27c94f 100644 --- a/server/entitymanager/entitymanager.go +++ b/server/entitymanager/entitymanager.go @@ -22,6 +22,7 @@ type InMemoryEntityManager struct { chassisInventory map[service.EntityLookup]*service.ChassisEntity // represents the current status of known control cards controlCardStatuses map[string]bootz.ControlCardState_ControlCardStatus + artifacts *service.SecurityArtifacts } // ResolveChassis returns an entity based on the provided lookup. @@ -98,6 +99,14 @@ func (m *InMemoryEntityManager) Sign(resp *bootz.GetBootstrapDataResponse, priv return nil } +// FetchOwnershipVoucher retrieves the ownership voucher for a control card +func (m *InMemoryEntityManager) FetchOwnershipVoucher(serial string) (string, error) { + if ov, ok := m.artifacts.OV[serial]; ok { + return ov, nil + } + return "", status.Errorf(codes.NotFound, "OV for serial %v not found", serial) +} + // AddControlCard adds a new control card to the entity manager. func (m *InMemoryEntityManager) AddControlCard(serial string) *InMemoryEntityManager { m.controlCardStatuses[serial] = bootz.ControlCardState_CONTROL_CARD_STATUS_UNSPECIFIED @@ -118,8 +127,9 @@ func (m *InMemoryEntityManager) AddChassis(bootMode bootz.BootMode, manufacturer } // New returns a new in-memory entity manager. -func New() *InMemoryEntityManager { +func New(artifacts *service.SecurityArtifacts) *InMemoryEntityManager { return &InMemoryEntityManager{ + artifacts: artifacts, chassisInventory: make(map[service.EntityLookup]*service.ChassisEntity), controlCardStatuses: make(map[string]bootz.ControlCardState_ControlCardStatus), } diff --git a/server/entitymanager/entitymanager_test.go b/server/entitymanager/entitymanager_test.go index 192df83..eb56f26 100644 --- a/server/entitymanager/entitymanager_test.go +++ b/server/entitymanager/entitymanager_test.go @@ -13,6 +13,44 @@ import ( "google.golang.org/protobuf/proto" ) +func TestFetchOwnershipVoucher(t *testing.T) { + tests := []struct { + desc string + serial string + want string + wantErr bool + }{ + { + desc: "Missing OV", + serial: "123B", + wantErr: true, + }, + { + desc: "Found OV", + serial: "123A", + want: "test_ov", + wantErr: false, + }, + } + + artifacts := &service.SecurityArtifacts{ + OV: service.OVList{"123A": "test_ov"}, + } + em := New(artifacts) + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + got, err := em.FetchOwnershipVoucher(test.serial) + if (err != nil) != test.wantErr { + t.Fatalf("FetchOwnershipVoucher(%v) err = %v, want %v", test.serial, err, test.wantErr) + } + if !cmp.Equal(got, test.want) { + t.Errorf("FetchOwnershipVoucher(%v) got %v, want %v", test.serial, got, test.want) + } + }) + } +} + func TestResolveChassis(t *testing.T) { tests := []struct { desc string @@ -41,7 +79,7 @@ func TestResolveChassis(t *testing.T) { }, } - em := New().AddChassis(bootz.BootMode_BOOT_MODE_SECURE, "Cisco", "123") + em := New(nil).AddChassis(bootz.BootMode_BOOT_MODE_SECURE, "Cisco", "123") for _, test := range tests { t.Run(test.desc, func(t *testing.T) { @@ -80,7 +118,7 @@ func TestSign(t *testing.T) { }, } - em := New() + em := New(nil) priv, err := rsa.GenerateKey(rand.Reader, 4096) if err != nil { t.Fatal(err) @@ -152,7 +190,7 @@ func TestSetStatus(t *testing.T) { }, } - em := New().AddChassis(bootz.BootMode_BOOT_MODE_SECURE, "Cisco", "123").AddControlCard("123A") + em := New(nil).AddChassis(bootz.BootMode_BOOT_MODE_SECURE, "Cisco", "123").AddControlCard("123A") for _, test := range tests { t.Run(test.desc, func(t *testing.T) { @@ -209,7 +247,7 @@ func TestGetBootstrapData(t *testing.T) { }, } - em := New().AddChassis(bootz.BootMode_BOOT_MODE_SECURE, "Cisco", "123").AddControlCard("123A") + em := New(nil).AddChassis(bootz.BootMode_BOOT_MODE_SECURE, "Cisco", "123").AddControlCard("123A") for _, test := range tests { t.Run(test.desc, func(t *testing.T) { diff --git a/server/server.go b/server/server.go index ce8508f..f3b4d80 100644 --- a/server/server.go +++ b/server/server.go @@ -102,12 +102,12 @@ func main() { if *artifactDirectory == "" { log.Exitf("no artifact directory specified") } - // TODO: Pass security artifacts to service. - _, err := parseSecurityArtifacts() + sa, err := parseSecurityArtifacts() if err != nil { log.Exit(err) } - em := entitymanager.New().AddChassis(bootz.BootMode_BOOT_MODE_SECURE, "Cisco", "123").AddControlCard("123A").AddControlCard("123B") + em := entitymanager.New(sa) + em.AddChassis(bootz.BootMode_BOOT_MODE_SECURE, "Cisco", "123").AddControlCard("123A").AddControlCard("123B") c := service.New(em) s := grpc.NewServer() diff --git a/server/service/service.go b/server/service/service.go index 2de53cd..b305c01 100644 --- a/server/service/service.go +++ b/server/service/service.go @@ -45,6 +45,7 @@ type EntityManager interface { GetBootstrapData(*bootz.ControlCard) (*bootz.BootstrapDataResponse, error) SetStatus(*bootz.ReportStatusRequest) error Sign(*bootz.GetBootstrapDataResponse, *rsa.PrivateKey) error + FetchOwnershipVoucher(string) (string, error) } type Service struct { @@ -86,7 +87,14 @@ func (s *Service) GetBootstrapRequest(ctx context.Context, req *bootz.GetBootstr if errs.Err() != nil { return nil, errs.Err() } + // Fetch the OV of the active control card + ov, err := s.em.FetchOwnershipVoucher(req.GetControlCardState().GetSerialNumber()) + if err != nil { + return nil, err + } + resp := &bootz.GetBootstrapDataResponse{ + OwnershipVoucher: []byte(ov), SignedResponse: &bootz.BootstrapDataSigned{ Responses: responses, }, From c0ae8a445e3196590ed615e41a2931302ce95583 Mon Sep 17 00:00:00 2001 From: Gareth <31602864+gmacf@users.noreply.github.com> Date: Tue, 15 Aug 2023 13:35:08 +0000 Subject: [PATCH 04/14] Move ownership voucher setting to entitymanager --- server/entitymanager/entitymanager.go | 8 +++++++- server/entitymanager/entitymanager_test.go | 17 ++++++++++++++--- server/service/service.go | 10 ++-------- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/server/entitymanager/entitymanager.go b/server/entitymanager/entitymanager.go index e27c94f..0cf4b23 100644 --- a/server/entitymanager/entitymanager.go +++ b/server/entitymanager/entitymanager.go @@ -82,7 +82,7 @@ func (m *InMemoryEntityManager) SetStatus(req *bootz.ReportStatusRequest) error } // Sign unmarshals the SignedResponse bytes then generates a signature from its Ownership Certificate private key. -func (m *InMemoryEntityManager) Sign(resp *bootz.GetBootstrapDataResponse, priv *rsa.PrivateKey) error { +func (m *InMemoryEntityManager) Sign(resp *bootz.GetBootstrapDataResponse, serial string, priv *rsa.PrivateKey) error { if resp.GetSignedResponse() == nil { return status.Errorf(codes.InvalidArgument, "empty signed response") } @@ -96,6 +96,12 @@ func (m *InMemoryEntityManager) Sign(resp *bootz.GetBootstrapDataResponse, priv return err } resp.ResponseSignature = string(sig) + // Populate the OV + ov, err := m.FetchOwnershipVoucher(serial) + if err != nil { + return err + } + resp.OwnershipVoucher = []byte(ov) return nil } diff --git a/server/entitymanager/entitymanager_test.go b/server/entitymanager/entitymanager_test.go index eb56f26..94b6a7b 100644 --- a/server/entitymanager/entitymanager_test.go +++ b/server/entitymanager/entitymanager_test.go @@ -97,11 +97,14 @@ func TestResolveChassis(t *testing.T) { func TestSign(t *testing.T) { tests := []struct { desc string + serial string resp *bootz.GetBootstrapDataResponse + wantOV string wantErr bool }{ { - desc: "Success", + desc: "Success", + serial: "123A", resp: &bootz.GetBootstrapDataResponse{ SignedResponse: &bootz.BootstrapDataSigned{ Responses: []*bootz.BootstrapDataResponse{ @@ -109,6 +112,7 @@ func TestSign(t *testing.T) { }, }, }, + wantOV: "test_ov", wantErr: false, }, { @@ -118,7 +122,6 @@ func TestSign(t *testing.T) { }, } - em := New(nil) priv, err := rsa.GenerateKey(rand.Reader, 4096) if err != nil { t.Fatal(err) @@ -126,7 +129,12 @@ func TestSign(t *testing.T) { for _, test := range tests { t.Run(test.desc, func(t *testing.T) { - err := em.Sign(test.resp, priv) + artifacts := &service.SecurityArtifacts{ + OV: service.OVList{test.serial: test.wantOV}, + } + em := New(artifacts) + + err := em.Sign(test.resp, test.serial, priv) if err != nil { if test.wantErr { t.Skip() @@ -142,6 +150,9 @@ func TestSign(t *testing.T) { if err != nil { t.Errorf("Sign() err == %v, want %v", err, test.wantErr) } + if got, want := string(test.resp.GetOwnershipVoucher()), test.wantOV; got != want { + t.Errorf("Sign() ov = %v, want %v", got, want) + } }) } } diff --git a/server/service/service.go b/server/service/service.go index b305c01..4c8d4fd 100644 --- a/server/service/service.go +++ b/server/service/service.go @@ -44,7 +44,7 @@ type EntityManager interface { ResolveChassis(*EntityLookup) (*ChassisEntity, error) GetBootstrapData(*bootz.ControlCard) (*bootz.BootstrapDataResponse, error) SetStatus(*bootz.ReportStatusRequest) error - Sign(*bootz.GetBootstrapDataResponse, *rsa.PrivateKey) error + Sign(*bootz.GetBootstrapDataResponse, string, *rsa.PrivateKey) error FetchOwnershipVoucher(string) (string, error) } @@ -87,14 +87,8 @@ func (s *Service) GetBootstrapRequest(ctx context.Context, req *bootz.GetBootstr if errs.Err() != nil { return nil, errs.Err() } - // Fetch the OV of the active control card - ov, err := s.em.FetchOwnershipVoucher(req.GetControlCardState().GetSerialNumber()) - if err != nil { - return nil, err - } resp := &bootz.GetBootstrapDataResponse{ - OwnershipVoucher: []byte(ov), SignedResponse: &bootz.BootstrapDataSigned{ Responses: responses, }, @@ -102,7 +96,7 @@ func (s *Service) GetBootstrapRequest(ctx context.Context, req *bootz.GetBootstr // Sign the response if Nonce is provided. // TODO: Populate Sign() with an RSA key. if req.Nonce != "" { - if err := s.em.Sign(resp, nil); err != nil { + if err := s.em.Sign(resp, req.GetControlCardState().GetSerialNumber(), nil); err != nil { return nil, status.Errorf(codes.Internal, "failed to sign bootz response") } } From d6451802d1a1fb8108f5a239be6c1a05fac06385 Mon Sep 17 00:00:00 2001 From: Gareth <31602864+gmacf@users.noreply.github.com> Date: Tue, 15 Aug 2023 13:41:51 +0000 Subject: [PATCH 05/14] Include OC in response --- server/entitymanager/entitymanager.go | 3 +++ server/entitymanager/entitymanager_test.go | 10 ++++++++-- server/service/service.go | 1 + 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/server/entitymanager/entitymanager.go b/server/entitymanager/entitymanager.go index 0cf4b23..db6033b 100644 --- a/server/entitymanager/entitymanager.go +++ b/server/entitymanager/entitymanager.go @@ -83,6 +83,7 @@ func (m *InMemoryEntityManager) SetStatus(req *bootz.ReportStatusRequest) error // Sign unmarshals the SignedResponse bytes then generates a signature from its Ownership Certificate private key. func (m *InMemoryEntityManager) Sign(resp *bootz.GetBootstrapDataResponse, serial string, priv *rsa.PrivateKey) error { + // Sign the response if resp.GetSignedResponse() == nil { return status.Errorf(codes.InvalidArgument, "empty signed response") } @@ -102,6 +103,8 @@ func (m *InMemoryEntityManager) Sign(resp *bootz.GetBootstrapDataResponse, seria return err } resp.OwnershipVoucher = []byte(ov) + // Populate the OC + resp.OwnershipCertificate = []byte(m.artifacts.OC.Cert) return nil } diff --git a/server/entitymanager/entitymanager_test.go b/server/entitymanager/entitymanager_test.go index 94b6a7b..32d58a1 100644 --- a/server/entitymanager/entitymanager_test.go +++ b/server/entitymanager/entitymanager_test.go @@ -100,6 +100,7 @@ func TestSign(t *testing.T) { serial string resp *bootz.GetBootstrapDataResponse wantOV string + wantOC string wantErr bool }{ { @@ -113,6 +114,7 @@ func TestSign(t *testing.T) { }, }, wantOV: "test_ov", + wantOC: "test_oc", wantErr: false, }, { @@ -131,6 +133,7 @@ func TestSign(t *testing.T) { t.Run(test.desc, func(t *testing.T) { artifacts := &service.SecurityArtifacts{ OV: service.OVList{test.serial: test.wantOV}, + OC: &service.KeyPair{Cert: test.wantOC}, } em := New(artifacts) @@ -150,8 +153,11 @@ func TestSign(t *testing.T) { if err != nil { t.Errorf("Sign() err == %v, want %v", err, test.wantErr) } - if got, want := string(test.resp.GetOwnershipVoucher()), test.wantOV; got != want { - t.Errorf("Sign() ov = %v, want %v", got, want) + if gotOV, wantOV := string(test.resp.GetOwnershipVoucher()), test.wantOV; gotOV != wantOV { + t.Errorf("Sign() ov = %v, want %v", gotOV, wantOV) + } + if gotOC, wantOC := string(test.resp.GetOwnershipCertificate()), test.wantOC; gotOC != wantOC { + t.Errorf("Sign() oc = %v, want %v", gotOC, wantOC) } }) } diff --git a/server/service/service.go b/server/service/service.go index 4c8d4fd..1c91cb6 100644 --- a/server/service/service.go +++ b/server/service/service.go @@ -96,6 +96,7 @@ func (s *Service) GetBootstrapRequest(ctx context.Context, req *bootz.GetBootstr // Sign the response if Nonce is provided. // TODO: Populate Sign() with an RSA key. if req.Nonce != "" { + resp.SignedResponse.Nonce = req.Nonce if err := s.em.Sign(resp, req.GetControlCardState().GetSerialNumber(), nil); err != nil { return nil, status.Errorf(codes.Internal, "failed to sign bootz response") } From b3ae89294df1066175ce10e5f07e082ad0cdb694 Mon Sep 17 00:00:00 2001 From: Gareth <31602864+gmacf@users.noreply.github.com> Date: Tue, 15 Aug 2023 14:00:11 +0000 Subject: [PATCH 06/14] Use OC key to sign response --- server/entitymanager/entitymanager.go | 12 +++++++++++- server/entitymanager/entitymanager_test.go | 13 +++++++++++-- server/service/service.go | 5 ++--- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/server/entitymanager/entitymanager.go b/server/entitymanager/entitymanager.go index db6033b..84b9d2f 100644 --- a/server/entitymanager/entitymanager.go +++ b/server/entitymanager/entitymanager.go @@ -5,6 +5,8 @@ import ( "crypto" "crypto/rsa" "crypto/sha256" + "crypto/x509" + "encoding/pem" "github.com/openconfig/bootz/proto/bootz" "github.com/openconfig/bootz/server/service" @@ -82,8 +84,16 @@ func (m *InMemoryEntityManager) SetStatus(req *bootz.ReportStatusRequest) error } // Sign unmarshals the SignedResponse bytes then generates a signature from its Ownership Certificate private key. -func (m *InMemoryEntityManager) Sign(resp *bootz.GetBootstrapDataResponse, serial string, priv *rsa.PrivateKey) error { +func (m *InMemoryEntityManager) Sign(resp *bootz.GetBootstrapDataResponse, serial string) error { // Sign the response + block, _ := pem.Decode([]byte(m.artifacts.OC.Key)) + if block == nil { + return status.Errorf(codes.Internal, "unable to decode OC private key") + } + priv, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return err + } if resp.GetSignedResponse() == nil { return status.Errorf(codes.InvalidArgument, "empty signed response") } diff --git a/server/entitymanager/entitymanager_test.go b/server/entitymanager/entitymanager_test.go index 32d58a1..bcd6094 100644 --- a/server/entitymanager/entitymanager_test.go +++ b/server/entitymanager/entitymanager_test.go @@ -5,6 +5,8 @@ import ( "crypto/rand" "crypto/rsa" "crypto/sha256" + "crypto/x509" + "encoding/pem" "testing" "github.com/google/go-cmp/cmp" @@ -128,16 +130,23 @@ func TestSign(t *testing.T) { if err != nil { t.Fatal(err) } + privPEM := &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(priv), + } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { artifacts := &service.SecurityArtifacts{ OV: service.OVList{test.serial: test.wantOV}, - OC: &service.KeyPair{Cert: test.wantOC}, + OC: &service.KeyPair{ + Cert: test.wantOC, + Key: string(pem.EncodeToMemory(privPEM)), + }, } em := New(artifacts) - err := em.Sign(test.resp, test.serial, priv) + err := em.Sign(test.resp, test.serial) if err != nil { if test.wantErr { t.Skip() diff --git a/server/service/service.go b/server/service/service.go index 1c91cb6..21da53d 100644 --- a/server/service/service.go +++ b/server/service/service.go @@ -2,7 +2,6 @@ package service import ( "context" - "crypto/rsa" "github.com/openconfig/bootz/proto/bootz" "github.com/openconfig/gnmi/errlist" @@ -44,7 +43,7 @@ type EntityManager interface { ResolveChassis(*EntityLookup) (*ChassisEntity, error) GetBootstrapData(*bootz.ControlCard) (*bootz.BootstrapDataResponse, error) SetStatus(*bootz.ReportStatusRequest) error - Sign(*bootz.GetBootstrapDataResponse, string, *rsa.PrivateKey) error + Sign(*bootz.GetBootstrapDataResponse, string) error FetchOwnershipVoucher(string) (string, error) } @@ -97,7 +96,7 @@ func (s *Service) GetBootstrapRequest(ctx context.Context, req *bootz.GetBootstr // TODO: Populate Sign() with an RSA key. if req.Nonce != "" { resp.SignedResponse.Nonce = req.Nonce - if err := s.em.Sign(resp, req.GetControlCardState().GetSerialNumber(), nil); err != nil { + if err := s.em.Sign(resp, req.GetControlCardState().GetSerialNumber()); err != nil { return nil, status.Errorf(codes.Internal, "failed to sign bootz response") } } From 83a69ef10b50a9af1cdc33466ae73f9f2075a224 Mon Sep 17 00:00:00 2001 From: Gareth <31602864+gmacf@users.noreply.github.com> Date: Wed, 16 Aug 2023 01:41:40 +0000 Subject: [PATCH 07/14] Address PR comments --- server/entitymanager/entitymanager.go | 11 +++++++++-- server/entitymanager/entitymanager_test.go | 23 ++++++++++------------ 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/server/entitymanager/entitymanager.go b/server/entitymanager/entitymanager.go index 84b9d2f..b849781 100644 --- a/server/entitymanager/entitymanager.go +++ b/server/entitymanager/entitymanager.go @@ -7,6 +7,7 @@ import ( "crypto/sha256" "crypto/x509" "encoding/pem" + "sync" "github.com/openconfig/bootz/proto/bootz" "github.com/openconfig/bootz/server/service" @@ -25,6 +26,7 @@ type InMemoryEntityManager struct { // represents the current status of known control cards controlCardStatuses map[string]bootz.ControlCardState_ControlCardStatus artifacts *service.SecurityArtifacts + mu sync.Mutex } // ResolveChassis returns an entity based on the provided lookup. @@ -72,6 +74,8 @@ func (m *InMemoryEntityManager) SetStatus(req *bootz.ReportStatusRequest) error } log.Infof("Bootstrap Status: %v: Status message: %v", req.GetStatus(), req.GetStatusMessage()) + m.mu.Lock() + defer m.mu.Unlock() for _, c := range req.GetStates() { previousStatus, ok := m.controlCardStatuses[c.GetSerialNumber()] if !ok { @@ -128,20 +132,23 @@ func (m *InMemoryEntityManager) FetchOwnershipVoucher(serial string) (string, er // AddControlCard adds a new control card to the entity manager. func (m *InMemoryEntityManager) AddControlCard(serial string) *InMemoryEntityManager { + m.mu.Lock() + defer m.mu.Unlock() m.controlCardStatuses[serial] = bootz.ControlCardState_CONTROL_CARD_STATUS_UNSPECIFIED return m } // AddChassis adds a new chassis to the entity manager. func (m *InMemoryEntityManager) AddChassis(bootMode bootz.BootMode, manufacturer string, serial string) *InMemoryEntityManager { + m.mu.Lock() + defer m.mu.Unlock() l := service.EntityLookup{ Manufacturer: manufacturer, SerialNumber: serial, } - e := service.ChassisEntity{ + m.chassisInventory[l] = &service.ChassisEntity{ BootMode: bootMode, } - m.chassisInventory[l] = &e return m } diff --git a/server/entitymanager/entitymanager_test.go b/server/entitymanager/entitymanager_test.go index bcd6094..9b6e880 100644 --- a/server/entitymanager/entitymanager_test.go +++ b/server/entitymanager/entitymanager_test.go @@ -21,19 +21,16 @@ func TestFetchOwnershipVoucher(t *testing.T) { serial string want string wantErr bool - }{ - { - desc: "Missing OV", - serial: "123B", - wantErr: true, - }, - { - desc: "Found OV", - serial: "123A", - want: "test_ov", - wantErr: false, - }, - } + }{{ + desc: "Missing OV", + serial: "123B", + wantErr: true, + }, { + desc: "Found OV", + serial: "123A", + want: "test_ov", + wantErr: false, + }} artifacts := &service.SecurityArtifacts{ OV: service.OVList{"123A": "test_ov"}, From 01db7a0784b3852ab9d4a737c8df853e90dffd66 Mon Sep 17 00:00:00 2001 From: Gareth <31602864+gmacf@users.noreply.github.com> Date: Thu, 17 Aug 2023 04:39:11 +0000 Subject: [PATCH 08/14] Fix some base64 encoding errors and gRPC server errors. Bootz flow is now in a workable state. --- client/client.go | 10 +++++++--- server/entitymanager/entitymanager.go | 3 ++- server/service/service.go | 4 ++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/client/client.go b/client/client.go index 252c224..98c6126 100644 --- a/client/client.go +++ b/client/client.go @@ -31,7 +31,7 @@ const nonceLength = 16 var ( insecureBoot = flag.Bool("insecure_boot", false, "Whether to start the emulated device in non-secure mode. This informs Bootz server to not provide ownership certificates or vouchers.") port = flag.String("port", "", "The port to listen to on localhost for the bootz server.") - rootCA = flag.String("root_ca_cert_path", "../testdata/ca.pem", "The relative path to a file contained a PEM encoded certificate for the manufacturer CA.") + rootCA = flag.String("root_ca_cert_path", "../testdata/vendorca_pub.pem", "The relative path to a file containing a PEM encoded certificate for the manufacturer CA.") ) type OwnershipVoucher struct { @@ -134,11 +134,15 @@ func validateArtifacts(serialNumber string, resp *bootz.GetBootstrapDataResponse return err } hashed := sha256.Sum256(signedResponseBytes) + decodedSig, err := base64.StdEncoding.DecodeString(resp.GetResponseSignature()) + if err != nil { + return err + } // Verify the signature with the ownership certificate's public key. Currently only RSA keys are supported. switch pub := ocCert.PublicKey.(type) { case *rsa.PublicKey: - err = rsa.VerifyPKCS1v15(pub, crypto.SHA256, hashed[:], []byte(resp.GetResponseSignature())) + err = rsa.VerifyPKCS1v15(pub, crypto.SHA256, hashed[:], decodedSig) if err != nil { return fmt.Errorf("signature not verified: %v", err) } @@ -165,7 +169,7 @@ func generateNonce() (string, error) { if err != nil { return "", err } - return string(b), nil + return base64.StdEncoding.EncodeToString(b), nil } func main() { diff --git a/server/entitymanager/entitymanager.go b/server/entitymanager/entitymanager.go index b849781..831ff28 100644 --- a/server/entitymanager/entitymanager.go +++ b/server/entitymanager/entitymanager.go @@ -6,6 +6,7 @@ import ( "crypto/rsa" "crypto/sha256" "crypto/x509" + "encoding/base64" "encoding/pem" "sync" @@ -110,7 +111,7 @@ func (m *InMemoryEntityManager) Sign(resp *bootz.GetBootstrapDataResponse, seria if err != nil { return err } - resp.ResponseSignature = string(sig) + resp.ResponseSignature = base64.StdEncoding.EncodeToString(sig) // Populate the OV ov, err := m.FetchOwnershipVoucher(serial) if err != nil { diff --git a/server/service/service.go b/server/service/service.go index 21da53d..d450d20 100644 --- a/server/service/service.go +++ b/server/service/service.go @@ -52,7 +52,7 @@ type Service struct { em EntityManager } -func (s *Service) GetBootstrapRequest(ctx context.Context, req *bootz.GetBootstrapDataRequest) (*bootz.GetBootstrapDataResponse, error) { +func (s *Service) GetBootstrapData(ctx context.Context, req *bootz.GetBootstrapDataRequest) (*bootz.GetBootstrapDataResponse, error) { if len(req.ChassisDescriptor.ControlCards) == 0 { return nil, status.Errorf(codes.InvalidArgument, "request must include at least one control card") } @@ -104,7 +104,7 @@ func (s *Service) GetBootstrapRequest(ctx context.Context, req *bootz.GetBootstr } func (s *Service) ReportStatus(ctx context.Context, req *bootz.ReportStatusRequest) (*bootz.EmptyResponse, error) { - return nil, s.em.SetStatus(req) + return &bootz.EmptyResponse{}, s.em.SetStatus(req) } // Public API for allowing the device configuration to be set for each device the From 5b056f2387e48c0574de8e40dee8356dde67ec63 Mon Sep 17 00:00:00 2001 From: Gareth <31602864+gmacf@users.noreply.github.com> Date: Thu, 17 Aug 2023 04:46:30 +0000 Subject: [PATCH 09/14] Fix EM.Sign() test --- server/entitymanager/entitymanager_test.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/server/entitymanager/entitymanager_test.go b/server/entitymanager/entitymanager_test.go index 9b6e880..e1a4a23 100644 --- a/server/entitymanager/entitymanager_test.go +++ b/server/entitymanager/entitymanager_test.go @@ -6,6 +6,7 @@ import ( "crypto/rsa" "crypto/sha256" "crypto/x509" + "encoding/base64" "encoding/pem" "testing" @@ -155,7 +156,11 @@ func TestSign(t *testing.T) { t.Fatal(err) } hashed := sha256.Sum256(signedResponseBytes) - err = rsa.VerifyPKCS1v15(&priv.PublicKey, crypto.SHA256, hashed[:], []byte(test.resp.GetResponseSignature())) + sigDecoded, err := base64.StdEncoding.DecodeString(test.resp.GetResponseSignature()) + if err != nil { + t.Fatal(err) + } + err = rsa.VerifyPKCS1v15(&priv.PublicKey, crypto.SHA256, hashed[:], sigDecoded) if err != nil { t.Errorf("Sign() err == %v, want %v", err, test.wantErr) } From 5890f27cf7d5c690009f6f4f93d3c4685fda197c Mon Sep 17 00:00:00 2001 From: Gareth <31602864+gmacf@users.noreply.github.com> Date: Fri, 18 Aug 2023 01:59:23 +0000 Subject: [PATCH 10/14] Address PR comments --- server/entitymanager/entitymanager.go | 7 +- server/entitymanager/entitymanager_test.go | 184 ++++++++++----------- 2 files changed, 91 insertions(+), 100 deletions(-) diff --git a/server/entitymanager/entitymanager.go b/server/entitymanager/entitymanager.go index 831ff28..7e2a1da 100644 --- a/server/entitymanager/entitymanager.go +++ b/server/entitymanager/entitymanager.go @@ -22,12 +22,13 @@ import ( // InMemoryEntityManager provides a simple in memory handler // for Entities. type InMemoryEntityManager struct { + mu sync.Mutex // inventory represents an organization's inventory of owned chassis. chassisInventory map[service.EntityLookup]*service.ChassisEntity // represents the current status of known control cards controlCardStatuses map[string]bootz.ControlCardState_ControlCardStatus - artifacts *service.SecurityArtifacts - mu sync.Mutex + // stores the security artifacts required by Bootz Server (OVs, OC and PDC) + artifacts *service.SecurityArtifacts } // ResolveChassis returns an entity based on the provided lookup. @@ -90,7 +91,6 @@ func (m *InMemoryEntityManager) SetStatus(req *bootz.ReportStatusRequest) error // Sign unmarshals the SignedResponse bytes then generates a signature from its Ownership Certificate private key. func (m *InMemoryEntityManager) Sign(resp *bootz.GetBootstrapDataResponse, serial string) error { - // Sign the response block, _ := pem.Decode([]byte(m.artifacts.OC.Key)) if block == nil { return status.Errorf(codes.Internal, "unable to decode OC private key") @@ -107,6 +107,7 @@ func (m *InMemoryEntityManager) Sign(resp *bootz.GetBootstrapDataResponse, seria return err } hashed := sha256.Sum256(signedResponseBytes) + // TODO: Add support for EC keys too. sig, err := rsa.SignPKCS1v15(nil, priv, crypto.SHA256, hashed[:]) if err != nil { return err diff --git a/server/entitymanager/entitymanager_test.go b/server/entitymanager/entitymanager_test.go index e1a4a23..d0619f4 100644 --- a/server/entitymanager/entitymanager_test.go +++ b/server/entitymanager/entitymanager_test.go @@ -57,26 +57,24 @@ func TestResolveChassis(t *testing.T) { input *service.EntityLookup want *service.ChassisEntity wantErr bool - }{ - { - desc: "Default device", - input: &service.EntityLookup{ - SerialNumber: "123", - Manufacturer: "Cisco", - }, - want: &service.ChassisEntity{ - BootMode: bootz.BootMode_BOOT_MODE_SECURE, - }, + }{{ + desc: "Default device", + input: &service.EntityLookup{ + SerialNumber: "123", + Manufacturer: "Cisco", }, - { - desc: "Chassis Not Found", - input: &service.EntityLookup{ - SerialNumber: "456", - Manufacturer: "Cisco", - }, - want: nil, - wantErr: true, + want: &service.ChassisEntity{ + BootMode: bootz.BootMode_BOOT_MODE_SECURE, }, + }, { + desc: "Chassis Not Found", + input: &service.EntityLookup{ + SerialNumber: "456", + Manufacturer: "Cisco", + }, + want: nil, + wantErr: true, + }, } em := New(nil).AddChassis(bootz.BootMode_BOOT_MODE_SECURE, "Cisco", "123") @@ -102,26 +100,24 @@ func TestSign(t *testing.T) { wantOV string wantOC string wantErr bool - }{ - { - desc: "Success", - serial: "123A", - resp: &bootz.GetBootstrapDataResponse{ - SignedResponse: &bootz.BootstrapDataSigned{ - Responses: []*bootz.BootstrapDataResponse{ - {SerialNum: "123A"}, - }, + }{{ + desc: "Success", + serial: "123A", + resp: &bootz.GetBootstrapDataResponse{ + SignedResponse: &bootz.BootstrapDataSigned{ + Responses: []*bootz.BootstrapDataResponse{ + {SerialNum: "123A"}, }, }, - wantOV: "test_ov", - wantOC: "test_oc", - wantErr: false, - }, - { - desc: "Empty response", - resp: &bootz.GetBootstrapDataResponse{}, - wantErr: true, }, + wantOV: "test_ov", + wantOC: "test_oc", + wantErr: false, + }, { + desc: "Empty response", + resp: &bootz.GetBootstrapDataResponse{}, + wantErr: true, + }, } priv, err := rsa.GenerateKey(rand.Reader, 4096) @@ -179,43 +175,40 @@ func TestSetStatus(t *testing.T) { desc string input *bootz.ReportStatusRequest wantErr bool - }{ - { - desc: "No control card states", - input: &bootz.ReportStatusRequest{ - Status: bootz.ReportStatusRequest_BOOTSTRAP_STATUS_SUCCESS, - StatusMessage: "Bootstrap status succeeded", - }, - wantErr: true, + }{{ + desc: "No control card states", + input: &bootz.ReportStatusRequest{ + Status: bootz.ReportStatusRequest_BOOTSTRAP_STATUS_SUCCESS, + StatusMessage: "Bootstrap status succeeded", }, - { - desc: "Control card initialized", - input: &bootz.ReportStatusRequest{ - Status: bootz.ReportStatusRequest_BOOTSTRAP_STATUS_SUCCESS, - StatusMessage: "Bootstrap status succeeded", - States: []*bootz.ControlCardState{ - { - SerialNumber: "123A", - Status: *bootz.ControlCardState_CONTROL_CARD_STATUS_INITIALIZED.Enum(), - }, + wantErr: true, + }, { + desc: "Control card initialized", + input: &bootz.ReportStatusRequest{ + Status: bootz.ReportStatusRequest_BOOTSTRAP_STATUS_SUCCESS, + StatusMessage: "Bootstrap status succeeded", + States: []*bootz.ControlCardState{ + { + SerialNumber: "123A", + Status: *bootz.ControlCardState_CONTROL_CARD_STATUS_INITIALIZED.Enum(), }, }, - wantErr: false, }, - { - desc: "Unknown control card", - input: &bootz.ReportStatusRequest{ - Status: bootz.ReportStatusRequest_BOOTSTRAP_STATUS_SUCCESS, - StatusMessage: "Bootstrap status succeeded", - States: []*bootz.ControlCardState{ - { - SerialNumber: "123C", - Status: *bootz.ControlCardState_CONTROL_CARD_STATUS_INITIALIZED.Enum(), - }, + wantErr: false, + }, { + desc: "Unknown control card", + input: &bootz.ReportStatusRequest{ + Status: bootz.ReportStatusRequest_BOOTSTRAP_STATUS_SUCCESS, + StatusMessage: "Bootstrap status succeeded", + States: []*bootz.ControlCardState{ + { + SerialNumber: "123C", + Status: *bootz.ControlCardState_CONTROL_CARD_STATUS_INITIALIZED.Enum(), }, }, - wantErr: true, }, + wantErr: true, + }, } em := New(nil).AddChassis(bootz.BootMode_BOOT_MODE_SECURE, "Cisco", "123").AddControlCard("123A") @@ -236,43 +229,40 @@ func TestGetBootstrapData(t *testing.T) { input *bootz.ControlCard want *bootz.BootstrapDataResponse wantErr bool - }{ - { - desc: "No serial number", - input: &bootz.ControlCard{}, - wantErr: true, + }{{ + desc: "No serial number", + input: &bootz.ControlCard{}, + wantErr: true, + }, { + desc: "Control card not found", + input: &bootz.ControlCard{ + SerialNumber: "456A", }, - { - desc: "Control card not found", - input: &bootz.ControlCard{ - SerialNumber: "456A", - }, - wantErr: true, + wantErr: true, + }, { + desc: "Successful bootstrap", + input: &bootz.ControlCard{ + SerialNumber: "123A", }, - { - desc: "Successful bootstrap", - input: &bootz.ControlCard{ - SerialNumber: "123A", + want: &bootz.BootstrapDataResponse{ + SerialNum: "123A", + IntendedImage: &bootz.SoftwareImage{ + Name: "Default Image", + Version: "1.0", + Url: "https://path/to/image", + OsImageHash: "ABCDEF", + HashAlgorithm: "SHA256", }, - want: &bootz.BootstrapDataResponse{ - SerialNum: "123A", - IntendedImage: &bootz.SoftwareImage{ - Name: "Default Image", - Version: "1.0", - Url: "https://path/to/image", - OsImageHash: "ABCDEF", - HashAlgorithm: "SHA256", - }, - BootPasswordHash: "ABCD123", - ServerTrustCert: "FakeTLSCert", - BootConfig: &bootz.BootConfig{ - VendorConfig: []byte("Vendor Config"), - OcConfig: []byte("OC Config"), - }, - Credentials: &bootz.Credentials{}, + BootPasswordHash: "ABCD123", + ServerTrustCert: "FakeTLSCert", + BootConfig: &bootz.BootConfig{ + VendorConfig: []byte("Vendor Config"), + OcConfig: []byte("OC Config"), }, - wantErr: false, + Credentials: &bootz.Credentials{}, }, + wantErr: false, + }, } em := New(nil).AddChassis(bootz.BootMode_BOOT_MODE_SECURE, "Cisco", "123").AddControlCard("123A") From b5e0b665e1ae4b53faab2670f2ae8c9b946a11ff Mon Sep 17 00:00:00 2001 From: Gareth <31602864+gmacf@users.noreply.github.com> Date: Tue, 22 Aug 2023 04:12:51 +0000 Subject: [PATCH 11/14] Use TLS connection secured by the PDC. --- client/client.go | 7 ++++--- server/server.go | 36 +++++++++++++++++++++++++++++++----- server/service/service.go | 11 ++++++----- 3 files changed, 41 insertions(+), 13 deletions(-) diff --git a/client/client.go b/client/client.go index 98c6126..e9a7646 100644 --- a/client/client.go +++ b/client/client.go @@ -6,6 +6,7 @@ import ( "crypto/rand" "crypto/rsa" "crypto/sha256" + "crypto/tls" "crypto/x509" "encoding/base64" "encoding/json" @@ -21,7 +22,7 @@ import ( "github.com/openconfig/bootz/proto/bootz" "go.mozilla.org/pkcs7" "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/credentials" "google.golang.org/protobuf/proto" ) @@ -223,8 +224,8 @@ func main() { // 2. Bootstrapping Service // Device initiates a TLS-secured gRPC connection with the Bootz server. - // TODO: Make this use TLS. - conn, err := grpc.Dial(bootzAddress, grpc.WithTransportCredentials(insecure.NewCredentials())) + // For the initial bootstrap, TLS certificates presented by the server are implicitly trusted. + conn, err := grpc.Dial(bootzAddress, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{InsecureSkipVerify: true}))) if err != nil { log.Exitf("Unable to connect to Bootstrap Server: %v", err) } diff --git a/server/server.go b/server/server.go index f3b4d80..b1d4b10 100644 --- a/server/server.go +++ b/server/server.go @@ -6,6 +6,8 @@ package main import ( + "crypto/tls" + "crypto/x509" "flag" "fmt" "net" @@ -17,6 +19,7 @@ import ( "github.com/openconfig/bootz/server/entitymanager" "github.com/openconfig/bootz/server/service" "google.golang.org/grpc" + "google.golang.org/grpc/credentials" log "github.com/golang/glog" ) @@ -67,6 +70,15 @@ func readOVs() (service.OVList, error) { return ovs, err } +// generateServerTlsCert creates a new TLS keypair from the PDC. +func generateServerTlsCert(pdc *service.KeyPair) (*tls.Certificate, error) { + tlsCert, err := tls.X509KeyPair([]byte(pdc.Cert), []byte(pdc.Key)) + if err != nil { + return nil, fmt.Errorf("unable to generate Server TLS Certificate from PDC %v", err) + } + return &tlsCert, err +} + // parseSecurityArtifacts reads from the specified directory to find the required keypairs and ownership vouchers. func parseSecurityArtifacts() (*service.SecurityArtifacts, error) { oc, err := readKeypair("oc") @@ -85,11 +97,16 @@ func parseSecurityArtifacts() (*service.SecurityArtifacts, error) { if err != nil { return nil, err } + tlsCert, err := generateServerTlsCert(pdc) + if err != nil { + return nil, err + } return &service.SecurityArtifacts{ - OC: oc, - PDC: pdc, - VendorCA: vendorCA, - OV: ovs, + OC: oc, + PDC: pdc, + VendorCA: vendorCA, + OV: ovs, + TLSKeypair: tlsCert, }, nil } @@ -109,7 +126,16 @@ func main() { em := entitymanager.New(sa) em.AddChassis(bootz.BootMode_BOOT_MODE_SECURE, "Cisco", "123").AddControlCard("123A").AddControlCard("123B") c := service.New(em) - s := grpc.NewServer() + + trustBundle := x509.NewCertPool() + if !trustBundle.AppendCertsFromPEM([]byte(sa.PDC.Cert)) { + log.Exitf("unable to add PDC cert to trust pool") + } + tls := &tls.Config{ + Certificates: []tls.Certificate{*sa.TLSKeypair}, + RootCAs: trustBundle, + } + s := grpc.NewServer(grpc.Creds(credentials.NewTLS(tls))) lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%v", *port)) if err != nil { diff --git a/server/service/service.go b/server/service/service.go index d450d20..e00a4ae 100644 --- a/server/service/service.go +++ b/server/service/service.go @@ -2,6 +2,7 @@ package service import ( "context" + "crypto/tls" "github.com/openconfig/bootz/proto/bootz" "github.com/openconfig/gnmi/errlist" @@ -20,10 +21,11 @@ type KeyPair struct { // SecurityArtifacts contains all KeyPairs and OVs needed for the Bootz Server. type SecurityArtifacts struct { - OC *KeyPair - PDC *KeyPair - VendorCA *KeyPair - OV OVList + OC *KeyPair + PDC *KeyPair + VendorCA *KeyPair + OV OVList + TLSKeypair *tls.Certificate } // EntityLookup provides a way to resolve chassis and control cards @@ -93,7 +95,6 @@ func (s *Service) GetBootstrapData(ctx context.Context, req *bootz.GetBootstrapD }, } // Sign the response if Nonce is provided. - // TODO: Populate Sign() with an RSA key. if req.Nonce != "" { resp.SignedResponse.Nonce = req.Nonce if err := s.em.Sign(resp, req.GetControlCardState().GetSerialNumber()); err != nil { From fdd39bea36d6e7aa6e06495aa5d2e49ab44adf95 Mon Sep 17 00:00:00 2001 From: Gareth <31602864+gmacf@users.noreply.github.com> Date: Tue, 22 Aug 2023 04:21:14 +0000 Subject: [PATCH 12/14] Add comments to security artifacts. --- server/service/service.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/server/service/service.go b/server/service/service.go index e00a4ae..94d90cc 100644 --- a/server/service/service.go +++ b/server/service/service.go @@ -20,11 +20,19 @@ type KeyPair struct { } // SecurityArtifacts contains all KeyPairs and OVs needed for the Bootz Server. +// Currently, RSA is the only encryption standard supported by these artifacts. type SecurityArtifacts struct { - OC *KeyPair - PDC *KeyPair - VendorCA *KeyPair - OV OVList + // The Ownership Certificate is an x509 certificate/private key pair signed by the PDC. + // The certificate is presented to the device during bootstrapping and is used to validate the Ownership Voucher. + OC *KeyPair + // The Pinned Domain Certificate is an x509 certificate/private key pair which acts as a certificate authority on the owner's side. + // This certificate is included in OVs and is also used to generate a server TLS Cert in this implementation. + PDC *KeyPair + // The Vendor CA represents a certificate authority on the vendor side. This CA signs Ownership Vouchers which are verified by the device. + VendorCA *KeyPair + // Ownership Vouchers are a list of PKCS7 messages signed by the Vendor CA. There is one per control card. + OV OVList + // The TLSKeypair is a TLS certificate used to secure connections between device and server. It is derived from the Pinned Domain Cert. TLSKeypair *tls.Certificate } From f92029415251224021d0d7f7fe9015476aec115d Mon Sep 17 00:00:00 2001 From: Gareth <31602864+gmacf@users.noreply.github.com> Date: Tue, 22 Aug 2023 07:28:38 +0000 Subject: [PATCH 13/14] Add READMEs to client and server. --- client/README.md | 20 ++++++++++++-------- server/README.md | 21 +++++++++++++++++++++ testdata/README.md | 3 ++- testdata/ca.pem | 32 -------------------------------- 4 files changed, 35 insertions(+), 41 deletions(-) create mode 100644 server/README.md delete mode 100644 testdata/ca.pem diff --git a/client/README.md b/client/README.md index 0cb0bd3..c0b4f84 100644 --- a/client/README.md +++ b/client/README.md @@ -1,11 +1,21 @@ -# Bootz Client Emulator +# Bootz Client Reference emulation -The code located in this directory is intended to emulator a typical Bootz +The code located in this directory is intended to emulate a typical Bootz client. Where appropriate, some device-specific functions such as downloading and image or applying a config are mocked out and are simply logged. ## Usage +First, make sure the server is running. See [server readme](../server/README.md). + +To run the client, build it and then run it with at least the `port` flag specified. The default value of `root_ca_cert_path` works for this implementation. We recommend using the flag `alsologtostderr` to get a verbose output. + +```shell +cd client +go build client.go +./client -port 8080 -alsologtostderr +``` + ### Flags * `port`: The port to listen to the Bootz Server on localhost. @@ -14,9 +24,3 @@ and image or applying a config are mocked out and are simply logged. * `root_ca_cert_path`: A path to a file that contains a PEM encoded certificate for the trusted ZTP Signing authority. This certificate will be used to validate the ownership voucher. - -### Root CA - -Included in this directory is a file named `ca.pem`. This file should contain -the PEM encoded certificate that the device will use to validate ownership -vouchers. diff --git a/server/README.md b/server/README.md new file mode 100644 index 0000000..bca52df --- /dev/null +++ b/server/README.md @@ -0,0 +1,21 @@ +# Bootz Server Reference Implementation + +The code located in this directory is intended to emulate a typical Bootz +server. + +## Usage + +To run the server, build it and then run it with at least the `port` flag specified. The default value of `artifact_dir` works for this implementation. We recommend using the flag `alsologtostderr` to get a verbose output. + +```shell +cd server +go build server.go +./server -port 8080 -alsologtostderr +``` + +Once running, run the client implementation in another terminal. See [client readme](../client/README.md). + +### Flags + +* `port`: The port to start to the Bootz Server on localhost. +* `artifact_dir`: A relative directory to look for security artifacts. See README.md in the testdata directory for an explanation of these. \ No newline at end of file diff --git a/testdata/README.md b/testdata/README.md index 280afa6..e4f12b5 100644 --- a/testdata/README.md +++ b/testdata/README.md @@ -28,7 +28,8 @@ real world, a reliable CA chain should be used instead. ### pdc_{pub|priv}.pem This is an x509 certificate/RSA keypair that represents the owner's Pinned -Domain Cert. +Domain Cert. This keypair is also used to create a TLS certificate for a +secure connection. Note: In this example these certifcates are self-signed for convenience. In the real world, a reliable CA chain should be used instead. diff --git a/testdata/ca.pem b/testdata/ca.pem deleted file mode 100644 index 4703e6a..0000000 --- a/testdata/ca.pem +++ /dev/null @@ -1,32 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFeDCCA2CgAwIBAgITKAAAABSVcgp8E6vpUAAAAAAAFDANBgkqhkiG9w0BAQsF -ADA6MTgwNgYDVQQDEy9BcmlzdGEgTmV0d29ya3MgSW50ZXJuYWwgSVQgUm9vdCBD -ZXJ0IEF1dGhvcml0eTAeFw0yMjA0MDcyMTUyMTNaFw0zNzA0MDcyMjAyMTNaMIGv -MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEUMBIGA1UEBxMLU2Fu -dGEgQ2xhcmExHTAbBgNVBAoTFEFyaXN0YSBOZXR3b3JrcyBJbmMuMR8wHQYDVQQL -ExZJbmZvcm1hdGlvbiBUZWNobm9sb2d5MTUwMwYDVQQDEyxBcmlzdGEgTmV0d29y -a3MgWlRQIFNpZ25lciBJc3N1aW5nIEF1dGhvcml0eTCBmzAQBgcqhkjOPQIBBgUr -gQQAIwOBhgAEAMTxjD+CQTUrDRu6y+9GjoiFqL9iJrq6oxhx2OP/318JgrOY+i0W -8ai4L/KIh3GEa3T41ezEqyNAABt+Uw6PFNlvAI6B9YdO1dP88696wfzCg2cPr+FH -v93a5aZlSYdhxe5ZUliQx6tT71CMJrUZJjsTYkhr6YUpZjUXW4MBG+7D8QeBo4IB -hzCCAYMwHQYDVR0OBBYEFGAPOuSq2hZHcPxzVgqfmtE8fWAuMB8GA1UdIwQYMBaA -FC3v2ubvzxx3Ub9aAqkozZQAreHKMHQGA1UdHwRtMGswaaBnoGWGY2ZpbGU6Ly8v -L1dJTi0zRzZJRzAySzFSMC9DZXJ0RW5yb2xsL0FyaXN0YSUyME5ldHdvcmtzJTIw -SW50ZXJuYWwlMjBJVCUyMFJvb3QlMjBDZXJ0JTIwQXV0aG9yaXR5LmNybDCBkQYI -KwYBBQUHAQEEgYQwgYEwfwYIKwYBBQUHMAKGc2ZpbGU6Ly8vL1dJTi0zRzZJRzAy -SzFSMC9DZXJ0RW5yb2xsL1dJTi0zRzZJRzAySzFSMF9BcmlzdGElMjBOZXR3b3Jr -cyUyMEludGVybmFsJTIwSVQlMjBSb290JTIwQ2VydCUyMEF1dGhvcml0eS5jcnQw -GQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwDwYDVR0TAQH/BAUwAwEB/zALBgNV -HQ8EBAMCAYYwDQYJKoZIhvcNAQELBQADggIBAABN+601/TPZY7YGolaRI2hbgpdP -adQZ1psrR5nqEplJkHcjLEqhXFD4b4NVerTSuIxiUBI4scEy6/R7WRUEWgLQx90y -Vd9YFayB3U2ilhPtbflc2p6SabQcjldWp9Y5AAl2vDaki8UrrizxuPeaxcroh1iI -aj9437MaxTCSm42dxmCExmylL+Q3oZ88ZRfaFoCa0S2XzQxxBbznpzLKQrgMG9fQ -b0b7FRzTOnYWcy57Mpn8/ZqFX/ZymUv3kb2iqlP5IFR0/uxeYcjKxHwjnnlX56RS -s5MJD25Lr4OgJPFCSi5TiQUyoLGWfqFvrdyDbpLEj8lnFkUuloR5RVJrRlfWQ2fO -RvYjcUqlQ1AlFtPh0PCR7SyF94imMdKGx1pimOChsgDBC1ry+XcctLgvTw9kQzcx -E2u6H9mwTbHp4iZaI+OXJkQQGSE+x84hdRe4yXldH7fTFbMOuvFBM5QzdBnlx+C/ -Uf5Tc0UvaKT+aCOjhQ1cl09J9ruyPCukRnDKDZS+jj0b5cvB2VP+aKo6R3o1Gnkx -vRDZLZrPbqxAiDWgYzZdDP6AEk2roe5/T5HlDEpLxtLVkzmFT704Z7pFn8utWIvA -FFJAm3CKlnNrN6VYuhiv6uOMuL9+6qjBBN+rGmr9fNPcQWepKKNuReQ+48HCmxKl -T8meIZH2lwZ+DZbk ------END CERTIFICATE----- \ No newline at end of file From ed54fc782d03ecfc4745407c880c6bd3bcc17e56 Mon Sep 17 00:00:00 2001 From: Gareth <31602864+gmacf@users.noreply.github.com> Date: Wed, 23 Aug 2023 01:15:26 +0000 Subject: [PATCH 14/14] Make TLS transport configurable --- client/client.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/client/client.go b/client/client.go index e9a7646..257b598 100644 --- a/client/client.go +++ b/client/client.go @@ -30,9 +30,10 @@ import ( const nonceLength = 16 var ( - insecureBoot = flag.Bool("insecure_boot", false, "Whether to start the emulated device in non-secure mode. This informs Bootz server to not provide ownership certificates or vouchers.") - port = flag.String("port", "", "The port to listen to on localhost for the bootz server.") - rootCA = flag.String("root_ca_cert_path", "../testdata/vendorca_pub.pem", "The relative path to a file containing a PEM encoded certificate for the manufacturer CA.") + verifyTLSCert = flag.Bool("verify_tls_cert", false, "Whether to verify the TLS certificate presented by the Bootz server. If false, all TLS connections are implicity trusted.") + insecureBoot = flag.Bool("insecure_boot", false, "Whether to start the emulated device in non-secure mode. This informs Bootz server to not provide ownership certificates or vouchers.") + port = flag.String("port", "", "The port to listen to on localhost for the bootz server.") + rootCA = flag.String("root_ca_cert_path", "../testdata/vendorca_pub.pem", "The relative path to a file containing a PEM encoded certificate for the manufacturer CA.") ) type OwnershipVoucher struct { @@ -224,8 +225,8 @@ func main() { // 2. Bootstrapping Service // Device initiates a TLS-secured gRPC connection with the Bootz server. - // For the initial bootstrap, TLS certificates presented by the server are implicitly trusted. - conn, err := grpc.Dial(bootzAddress, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{InsecureSkipVerify: true}))) + tlsConfig := &tls.Config{InsecureSkipVerify: !*verifyTLSCert} + conn, err := grpc.Dial(bootzAddress, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))) if err != nil { log.Exitf("Unable to connect to Bootstrap Server: %v", err) }