From 9d11f68544f18ede1043c1e21a744d224e89b8c7 Mon Sep 17 00:00:00 2001 From: Seth Terashima Date: Wed, 10 Apr 2024 16:40:15 -0700 Subject: [PATCH] Support adding keys with different roles Previously, the API only allowed adding keys with the Owner or Driver roles. This expands the API to allow keys with other roles. See pkg/protocol/protocol.md for documentation on the different roles. --- cmd/tesla-control/commands.go | 17 +++++++++-------- pkg/vehicle/security.go | 27 +++++++++++++++++++++++++-- pkg/vehicle/vcsec.go | 8 +------- 3 files changed, 35 insertions(+), 17 deletions(-) diff --git a/cmd/tesla-control/commands.go b/cmd/tesla-control/commands.go index 59dabd26..5b802abb 100644 --- a/cmd/tesla-control/commands.go +++ b/cmd/tesla-control/commands.go @@ -13,6 +13,7 @@ import ( "github.com/teslamotors/vehicle-command/pkg/account" "github.com/teslamotors/vehicle-command/pkg/cli" "github.com/teslamotors/vehicle-command/pkg/protocol" + "github.com/teslamotors/vehicle-command/pkg/protocol/protobuf/keys" "github.com/teslamotors/vehicle-command/pkg/protocol/protobuf/vcsec" "github.com/teslamotors/vehicle-command/pkg/vehicle" ) @@ -229,12 +230,12 @@ var commands = map[string]*Command{ requiresFleetAPI: false, args: []Argument{ Argument{name: "PUBLIC_KEY", help: "file containing public key (or corresponding private key)"}, - Argument{name: "ROLE", help: "One of: owner, driver"}, + Argument{name: "ROLE", help: "One of: owner, driver, fm (fleet manager), vehicle_monitor, charging_manager"}, Argument{name: "FORM_FACTOR", help: "One of: nfc_card, ios_device, android_device, cloud_key"}, }, handler: func(ctx context.Context, acct *account.Account, car *vehicle.Vehicle, args map[string]string) error { - role := strings.ToUpper(args["ROLE"]) - if role != "OWNER" && role != "DRIVER" { + role, ok := keys.Role_value["ROLE_"+strings.ToUpper(args["ROLE"])] + if !ok { return fmt.Errorf("%w: invalid ROLE", ErrCommandLineArgs) } formFactor, ok := vcsec.KeyFormFactor_value["KEY_FORM_FACTOR_"+strings.ToUpper(args["FORM_FACTOR"])] @@ -245,7 +246,7 @@ var commands = map[string]*Command{ if err != nil { return fmt.Errorf("invalid public key: %s", err) } - return car.AddKey(ctx, publicKey, role == "OWNER", vcsec.KeyFormFactor(formFactor)) + return car.AddKeyWithRole(ctx, publicKey, keys.Role(role), vcsec.KeyFormFactor(formFactor)) }, }, "add-key-request": &Command{ @@ -254,12 +255,12 @@ var commands = map[string]*Command{ requiresFleetAPI: false, args: []Argument{ Argument{name: "PUBLIC_KEY", help: "file containing public key (or corresponding private key)"}, - Argument{name: "ROLE", help: "One of: owner, driver"}, + Argument{name: "ROLE", help: "One of: owner, driver, fm (fleet manager), vehicle_monitor, charging_manager"}, Argument{name: "FORM_FACTOR", help: "One of: nfc_card, ios_device, android_device, cloud_key"}, }, handler: func(ctx context.Context, acct *account.Account, car *vehicle.Vehicle, args map[string]string) error { - role := strings.ToUpper(args["ROLE"]) - if role != "OWNER" && role != "DRIVER" { + role, ok := keys.Role_value["ROLE_"+strings.ToUpper(args["ROLE"])] + if !ok { return fmt.Errorf("%w: invalid ROLE", ErrCommandLineArgs) } formFactor, ok := vcsec.KeyFormFactor_value["KEY_FORM_FACTOR_"+strings.ToUpper(args["FORM_FACTOR"])] @@ -270,7 +271,7 @@ var commands = map[string]*Command{ if err != nil { return fmt.Errorf("invalid public key: %s", err) } - if err := car.SendAddKeyRequest(ctx, publicKey, role == "OWNER", vcsec.KeyFormFactor(formFactor)); err != nil { + if err := car.SendAddKeyRequestWithRole(ctx, publicKey, keys.Role(role), vcsec.KeyFormFactor(formFactor)); err != nil { return err } fmt.Printf("Sent add-key request to %s. Confirm by tapping NFC card on center console.\n", car.VIN()) diff --git a/pkg/vehicle/security.go b/pkg/vehicle/security.go index e27d53ed..9a2b9239 100644 --- a/pkg/vehicle/security.go +++ b/pkg/vehicle/security.go @@ -9,6 +9,7 @@ import ( "github.com/teslamotors/vehicle-command/pkg/connector" "github.com/teslamotors/vehicle-command/pkg/protocol" carserver "github.com/teslamotors/vehicle-command/pkg/protocol/protobuf/carserver" + "github.com/teslamotors/vehicle-command/pkg/protocol/protobuf/keys" "github.com/teslamotors/vehicle-command/pkg/protocol/protobuf/vcsec" ) @@ -178,10 +179,21 @@ func (v *Vehicle) TriggerHomelink(ctx context.Context, latitude float32, longitu // AddKey adds a public key to the vehicle's whitelist. If isOwner is true, the new key can // authorize changes to vehicle access controls, such as adding/removing other keys. func (v *Vehicle) AddKey(ctx context.Context, publicKey *ecdh.PublicKey, isOwner bool, formFactor vcsec.KeyFormFactor) error { + if isOwner { + return v.AddKeyWithRole(ctx, publicKey, keys.Role_ROLE_OWNER, formFactor) + } + return v.AddKeyWithRole(ctx, publicKey, keys.Role_ROLE_DRIVER, formFactor) +} + +// AddKeyWithRole adds a public key to the vehicle's whitelist. See [Protocol Specification] for +// more information on roles. +// +// [Protocol Specification]: https://github.com/teslamotors/vehicle-command/blob/main/pkg/protocol/protocol.md#roles +func (v *Vehicle) AddKeyWithRole(ctx context.Context, publicKey *ecdh.PublicKey, role keys.Role, formFactor vcsec.KeyFormFactor) error { if publicKey.Curve() != ecdh.P256() { return protocol.ErrInvalidPublicKey } - payload := addKeyPayload(publicKey, isOwner, formFactor) + payload := addKeyPayload(publicKey, role, formFactor) encodedPayload, err := proto.Marshal(payload) if err != nil { return err @@ -276,13 +288,24 @@ func (v *Vehicle) Unlock(ctx context.Context) error { // attempting to call v.SessionInfo with the domain argument set to // [universal.Domain_DOMAIN_INFOTAINMENT]. func (v *Vehicle) SendAddKeyRequest(ctx context.Context, publicKey *ecdh.PublicKey, isOwner bool, formFactor vcsec.KeyFormFactor) error { + if isOwner { + return v.SendAddKeyRequestWithRole(ctx, publicKey, keys.Role_ROLE_OWNER, formFactor) + } + return v.SendAddKeyRequestWithRole(ctx, publicKey, keys.Role_ROLE_DRIVER, formFactor) +} + +// SendAddKeyRequestWithRole behaves like [SendAddKeyRequest] except the new key's role can be +// specified explicitly. See [Protocol Specification] for more information on roles. +// +// [Protocol Specification]: https://github.com/teslamotors/vehicle-command/blob/main/pkg/protocol/protocol.md#roles +func (v *Vehicle) SendAddKeyRequestWithRole(ctx context.Context, publicKey *ecdh.PublicKey, role keys.Role, formFactor vcsec.KeyFormFactor) error { if publicKey.Curve() != ecdh.P256() { return protocol.ErrInvalidPublicKey } if _, ok := v.conn.(connector.FleetAPIConnector); ok { return protocol.ErrRequiresBLE } - encodedPayload, err := proto.Marshal(addKeyPayload(publicKey, isOwner, formFactor)) + encodedPayload, err := proto.Marshal(addKeyPayload(publicKey, role, formFactor)) if err != nil { return err } diff --git a/pkg/vehicle/vcsec.go b/pkg/vehicle/vcsec.go index f07434e6..e48c05cf 100644 --- a/pkg/vehicle/vcsec.go +++ b/pkg/vehicle/vcsec.go @@ -123,13 +123,7 @@ func (v *Vehicle) executeWhitelistOperation(ctx context.Context, payload []byte) return err } -func addKeyPayload(publicKey *ecdh.PublicKey, isOwner bool, formFactor vcsec.KeyFormFactor) *vcsec.UnsignedMessage { - var role keys.Role - if isOwner { - role = keys.Role_ROLE_OWNER - } else { - role = keys.Role_ROLE_DRIVER - } +func addKeyPayload(publicKey *ecdh.PublicKey, role keys.Role, formFactor vcsec.KeyFormFactor) *vcsec.UnsignedMessage { return &vcsec.UnsignedMessage{ SubMessage: &vcsec.UnsignedMessage_WhitelistOperation{ WhitelistOperation: &vcsec.WhitelistOperation{