Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support adding keys with different roles #230

Merged
merged 1 commit into from
Apr 11, 2024
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
17 changes: 9 additions & 8 deletions cmd/tesla-control/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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"])]
Expand All @@ -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{
Expand All @@ -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"])]
Expand All @@ -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())
Expand Down
27 changes: 25 additions & 2 deletions pkg/vehicle/security.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down
8 changes: 1 addition & 7 deletions pkg/vehicle/vcsec.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
Loading