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{