Documentation ¶
Overview ¶
Example ¶
package main import ( "crypto/rand" "fmt" "time" command "github.com/teslamotors/vehicle-command/internal/authentication" universal "github.com/teslamotors/vehicle-command/pkg/protocol/protobuf/universalmessage" ) func main() { /***** One-time setup ********************************************************/ // Executed by Verifier: Generate key pair verifierKey, err := command.NewECDHPrivateKey(rand.Reader) if err != nil { panic(fmt.Sprintf("Failed to generate verifier key: %s", err)) } // Executed by Signer: Generate key pair signerKey, err := command.NewECDHPrivateKey(rand.Reader) if err != nil { panic(fmt.Sprintf("Failed to generate signer key: %s", err)) } // Signer and Verifier exchange public keys through an authenticated channel. // Signer must know Verifier domain and name (see protocol description). domain := universal.Domain_DOMAIN_VEHICLE_SECURITY verifierId := []byte("testVIN-1234") /***** Once per session *****************************************************/ // (A session typically lasts until either Signer or Verifier reboots) verifier, err := command.NewVerifier(verifierKey, verifierId, domain, signerKey.PublicBytes()) if err != nil { panic(fmt.Sprintf("Failed to initialize verifier: %s", err)) } // Signer sends GetSessionInfo request to Verifier, identifying itself by its public key. // Verifier uses the UUID from the request as the challenge value below. challenge := []byte{0, 1, 2, 3, 4, 5, 6, 7} encodedInfo, tag, err := verifier.SignedSessionInfo(challenge) if err != nil { panic(fmt.Sprintf("Failed to get session info: %s", err)) } // Verifier sends encodedInfo, tag to Signer. // Signer executes: signer, err := command.NewAuthenticatedSigner(signerKey, verifierId, challenge, encodedInfo, tag) if err != nil { panic(fmt.Sprintf("Failed to initialize signer: %s", err)) } /***** Once per message *****************************************************/ // Signer constructs message to transmit: message := &universal.RoutableMessage{ ToDestination: &universal.Destination{ SubDestination: &universal.Destination_Domain{Domain: domain}, }, } message.Payload = &universal.RoutableMessage_ProtobufMessageAsBytes{ ProtobufMessageAsBytes: []byte("hello world"), } // Encrypt message. Expires in one minute. if err := signer.Encrypt(message, time.Minute); err != nil { panic(fmt.Sprintf("Failed to encrypt message: %s", err)) } // Signer marshals message, transmits to Verifier // Verifier executes: plaintext, err := verifier.Verify(message) if err != nil { panic(fmt.Sprintf("Failed to decrypt: %s", err)) } fmt.Printf("%s\n", plaintext) // Second message can reuse existing session: message.Payload = &universal.RoutableMessage_ProtobufMessageAsBytes{ ProtobufMessageAsBytes: []byte("Goodbye!"), } if err := signer.Encrypt(message, time.Minute); err != nil { panic(fmt.Sprintf("Failed to encrypt message: %s", err)) } plaintext, err = verifier.Verify(message) if err != nil { panic(fmt.Sprintf("Failed to decrypt: %s", err)) } fmt.Printf("%s\n", plaintext) }
Output: hello world Goodbye!
Index ¶
- Constants
- Variables
- func SignMessageForFleet(privateKey ECDHPrivateKey, app string, message jwt.MapClaims) (string, error)
- func SignMessageForVehicle(privateKey ECDHPrivateKey, vin, app string, message jwt.MapClaims) (string, error)
- type Dispatcher
- type ECDHPrivateKey
- type Error
- type InvalidSignatureError
- type NativeECDHKey
- type NativeSession
- func (b *NativeSession) Decrypt(nonce, ciphertext, associatedData, tag []byte) (plaintext []byte, err error)
- func (b *NativeSession) Encrypt(plaintext, associatedData []byte) (nonce, ciphertext, tag []byte, err error)
- func (b *NativeSession) LocalPublicBytes() []byte
- func (b *NativeSession) NewHMAC(label string) hash.Hash
- func (b *NativeSession) SessionInfoHMAC(id, challenge, encodedInfo []byte) ([]byte, error)
- type Peer
- type Session
- type Signer
- func ImportSessionInfo(private ECDHPrivateKey, verifierName, encodedInfo []byte, ...) (*Signer, error)
- func NewAuthenticatedSigner(private ECDHPrivateKey, verifierName, challenge, encodedInfo, tag []byte) (*Signer, error)
- func NewSigner(private ECDHPrivateKey, verifierName []byte, ...) (*Signer, error)
- func (s *Signer) AuthorizeHMAC(message *universal.RoutableMessage, expiresIn time.Duration) error
- func (s *Signer) Encrypt(message *universal.RoutableMessage, expiresIn time.Duration) error
- func (s *Signer) ExportSessionInfo() ([]byte, error)
- func (s *Signer) RemotePublicKeyBytes() []byte
- func (s *Signer) UpdateSessionInfo(info *signatures.SessionInfo) error
- func (s *Signer) UpdateSignedSessionInfo(challenge, encodedInfo, tag []byte) error
- type SigningMethodSchnorrP256
- type Verifier
- func (v *Verifier) SessionInfo() (*signatures.SessionInfo, error)
- func (v *Verifier) SetSessionInfo(challenge []byte, message *universal.RoutableMessage) error
- func (v *Verifier) SignedSessionInfo(challenge []byte) (encodedInfo, tag []byte, err error)
- func (v *Verifier) Verify(message *universal.RoutableMessage) (plaintext []byte, err error)
Examples ¶
Constants ¶
SharedKeySizeBytes is the length of the cryptographic key shared by a Signer and a Verifier.
const TeslaSchnorrSHA256 = "Tesla.SS256"
Variables ¶
var ( // ErrInvalidPublicKey is an Error raised when a remote peer provides an invalid public key. ErrInvalidPublicKey = newError(errCodeBadParameter, "invalid public key") // ErrInvalidPrivateKey indicates the local peer tried to load an unsupported or malformed // private key. ErrInvalidPrivateKey = errors.New("invalid private key") )
var ( // ErrMetadataFieldTooLong indicates an authenticated field (such as a verifier name) is too // long to be compatible with the serialization format. ErrMetadataFieldTooLong = errors.New("metadata fields can't be more than 255 bytes long") )
Functions ¶
func SignMessageForFleet ¶ added in v0.1.0
func SignMessageForFleet(privateKey ECDHPrivateKey, app string, message jwt.MapClaims) (string, error)
SignMessageForFleet returns a JWT with the provided claims. All vehicles that trust privateKey will accept the JWT. To create a JWT that is valid for a single vehicle, use SignMessageForVehicle.
The function overwrites the audience ("aud") and issuer ("iss") JWT claims.
func SignMessageForVehicle ¶ added in v0.1.0
func SignMessageForVehicle(privateKey ECDHPrivateKey, vin, app string, message jwt.MapClaims) (string, error)
SignMessageForVehicle returns a JWT with the provided claims. Only the vehicle with the given VIN will accept the JWT. To create a JWT that is valid for all vehicles in a fleet, use SignMessageForFleet.
The function overwrites the audience ("aud") and issuer ("iss") JWT claims.
Types ¶
type Dispatcher ¶
type Dispatcher struct {
ECDHPrivateKey
}
Dispatcher facilitates creating connections to multiple vehicles using the same ECDHPrivateKey.
Example ¶
const carCount = 10 const messagesPerCar = 5 var challenges [][]byte // Create dispatcher dispatcherPrivateKey, err := NewECDHPrivateKey(rand.Reader) if err != nil { panic(fmt.Sprintf("Failed to generate dispatcher key: %s", err)) } dispatcher := Dispatcher{dispatcherPrivateKey} // Initialize cars, provision dispatcher public key cars := make([]*Verifier, carCount) for i := 0; i < carCount; i++ { vin := []byte(fmt.Sprintf("%d", i)) VCSECKey, err := NewECDHPrivateKey(rand.Reader) var challenge [16]byte if _, err := rand.Read(challenge[:]); err != nil { panic(fmt.Sprintf("Failed to generate random challenge: %s", err)) } else { challenges = append(challenges, challenge[:]) } if err != nil { panic(fmt.Sprintf("Failed to generate car key: %s", err)) } cars[i], err = NewVerifier(VCSECKey, vin, universal.Domain_DOMAIN_VEHICLE_SECURITY, dispatcherPrivateKey.PublicBytes()) if err != nil { panic(fmt.Sprintf("Failed to initialize car: %s", err)) } } message := &universal.RoutableMessage{ ToDestination: &universal.Destination{ SubDestination: &universal.Destination_Domain{Domain: universal.Domain_DOMAIN_VEHICLE_SECURITY}, }, } for i := 0; i < carCount; i++ { // Fetch session info from vehicle sessionInfo, tag, err := cars[i].SignedSessionInfo(challenges[i]) if err != nil { panic(fmt.Sprintf("Error obtaining session info from car %d: %s", i, err)) } // Give it to the signer (dispatcher). The UUID used to fetch the // session info can be used as the challenge. connection, err := dispatcher.ConnectAuthenticated(cars[i].verifierName, challenges[i], sessionInfo, tag) if err != nil { panic(fmt.Sprintf("Error creating authenticated connection to car %d: %s", i, err)) } // Send several messages to vehicle using connection for j := 0; j < messagesPerCar; j++ { original := []byte(fmt.Sprintf("Message %d for car %d", j, i)) message.Payload = &universal.RoutableMessage_ProtobufMessageAsBytes{ProtobufMessageAsBytes: original} if err := connection.Encrypt(message, time.Minute); err != nil { panic(fmt.Sprintf("Failed to encrypt message: %s", err)) } // This won't happen if err above is nil, just here for illustrative purposes. if bytes.Equal(message.GetProtobufMessageAsBytes(), original) { panic("Message wasn't encrypted!") } if plaintext, err := cars[i].Verify(message); err != nil { panic(fmt.Sprintf("Decryption error :%s", err)) } else if !bytes.Equal(plaintext, original) { panic("Failed to recover original plaintext") } } }
Output:
func (*Dispatcher) Connect ¶
func (d *Dispatcher) Connect(verifierId []byte, sessionInfo *signatures.SessionInfo) (*Signer, error)
func (*Dispatcher) ConnectAuthenticated ¶
func (d *Dispatcher) ConnectAuthenticated(verifierId, challenge, encodedSessionInfo, tag []byte) (*Signer, error)
type ECDHPrivateKey ¶
type ECDHPrivateKey interface { Exchange(remotePublicBytes []byte) (Session, error) PublicBytes() []byte SchnorrSignature(message []byte) ([]byte, error) }
ECDHPrivateKey represents a local private key.
func LoadExternalECDHKey ¶
func LoadExternalECDHKey(filename string) (ECDHPrivateKey, error)
func NewECDHPrivateKey ¶
func NewECDHPrivateKey(rng io.Reader) (ECDHPrivateKey, error)
func UnmarshalECDHPrivateKey ¶
func UnmarshalECDHPrivateKey(privateScalar []byte) ECDHPrivateKey
type Error ¶
type Error struct { Code universal.MessageFault_E Info string }
Error represents a protocol-layer error.
type InvalidSignatureError ¶
type InvalidSignatureError struct { Code universal.MessageFault_E EncodedInfo []byte Tag []byte }
func (*InvalidSignatureError) Error ¶
func (e *InvalidSignatureError) Error() string
type NativeECDHKey ¶
type NativeECDHKey struct {
*ecdsa.PrivateKey
}
func (*NativeECDHKey) Exchange ¶
func (n *NativeECDHKey) Exchange(publicBytes []byte) (Session, error)
func (*NativeECDHKey) Public ¶
func (n *NativeECDHKey) Public() *ecdsa.PublicKey
func (*NativeECDHKey) PublicBytes ¶
func (n *NativeECDHKey) PublicBytes() []byte
func (*NativeECDHKey) SchnorrSignature ¶ added in v0.1.0
func (n *NativeECDHKey) SchnorrSignature(message []byte) ([]byte, error)
type NativeSession ¶
type NativeSession struct {
// contains filtered or unexported fields
}
NativeSession implements the Session interface using native Go.
func (*NativeSession) Decrypt ¶
func (b *NativeSession) Decrypt(nonce, ciphertext, associatedData, tag []byte) (plaintext []byte, err error)
func (*NativeSession) Encrypt ¶
func (b *NativeSession) Encrypt(plaintext, associatedData []byte) (nonce, ciphertext, tag []byte, err error)
func (*NativeSession) LocalPublicBytes ¶
func (b *NativeSession) LocalPublicBytes() []byte
func (*NativeSession) SessionInfoHMAC ¶
func (b *NativeSession) SessionInfoHMAC(id, challenge, encodedInfo []byte) ([]byte, error)
type Peer ¶
type Peer struct {
// contains filtered or unexported fields
}
A Peer is the parent type for Signer and Verifier.
type Session ¶
type Session interface { // Returns the session info HMAC tag for encodedInfo. The challenge is a Signer-provided // anti-replay value. SessionInfoHMAC(id, challenge, encodedInfo []byte) ([]byte, error) // Encrypt plaintext and generate a tag that can be used to authenticate // the ciphertext and associated data. The tag and ciphertext are part of // the same slice, but returned separately for convenience. Encrypt(plaintext, associatedData []byte) (nonce, ciphertext, tag []byte, err error) // Authenticate a ciphertext and its associated data using the tag, then // decrypt it and return the plaintext. Decrypt(nonce, ciphertext, associatedData, tag []byte) (plaintext []byte, err error) // Return the encoded local public key. LocalPublicBytes() []byte // Returns a hash.Hash context that can be used as a KDF rooted in the shared secret. NewHMAC(label string) hash.Hash }
A Session allows encrypting/decrypting/authenticating data using a shared ECDH secret.
type Signer ¶
type Signer struct { Peer // contains filtered or unexported fields }
Signers encrypt messages that are decrypted and verified by a designated Verifier. (Technically speaking, the name is a misnomer since Signers use symmetric-key operations following ECDH key agreement.)
func ImportSessionInfo ¶
func ImportSessionInfo(private ECDHPrivateKey, verifierName, encodedInfo []byte, generatedAt time.Time) (*Signer, error)
ImportSessionInfo allows creation of a Signer with cached SessionInfo. This can be used to avoid a round trip with the Verifier.
func NewAuthenticatedSigner ¶
func NewAuthenticatedSigner(private ECDHPrivateKey, verifierName, challenge, encodedInfo, tag []byte) (*Signer, error)
NewAuthenticatedSigner creates a Signer from encoded and cryptographically verified session info.
func NewSigner ¶
func NewSigner(private ECDHPrivateKey, verifierName []byte, verifierInfo *signatures.SessionInfo) (*Signer, error)
NewSigner creates a Signer that sends authenticated messages to the Verifier named verifierName. In order to use this function, the client needs to obtain verifierInfo from the Verifier.
func (*Signer) AuthorizeHMAC ¶
AuthorizeHMAC adds an authentication tag to message.
This allows the recipient to verify the message has not been tampered with, but the payload is not encrypted. Unencrypted (but authenticated) messages are required by the HTTP proxy. The proxy needs to inspect commands in order to enforce OAuth scopes and determine when a sequence of replies terminates. If a client is not using the HTTP proxy, it should use Encrypt instead of AuthorizeHMAC.
Sensitive data, such as live camera streams, is encrypted on the application layer.
func (*Signer) Encrypt ¶
Encrypt message's payload in-place. This method adds (authenticated) metadata to the message as well, including the provided expiration time.
func (*Signer) ExportSessionInfo ¶
ExportSessionInfo can be used to write session state to disk, allowing for later resumption using ImportSessionInfo.
func (*Signer) RemotePublicKeyBytes ¶
RemotePublicKeyBytes returns the Verifer's public key encoded without point compression.
func (*Signer) UpdateSessionInfo ¶
func (s *Signer) UpdateSessionInfo(info *signatures.SessionInfo) error
UpdateSessionInfo allows s to resync session state with a Verifier. A Verifier may include info in an authentication error message when the error may have resulted from a desync. The Signer can update its session info and then reattempt transmission.
func (*Signer) UpdateSignedSessionInfo ¶
UpdateSignedSessionInfo allows s to resync session state with a Verifier using cryptographically verified session state. See UpdateSessionInfo.
type SigningMethodSchnorrP256 ¶ added in v0.1.0
type SigningMethodSchnorrP256 struct{}
SigningMethodSchnorrP256 implements jwt.SigningMethod using Schnorr signatures over the NIST P-256 curve.
func (*SigningMethodSchnorrP256) Alg ¶ added in v0.1.0
func (s *SigningMethodSchnorrP256) Alg() string
type Verifier ¶
type Verifier struct { Peer // contains filtered or unexported fields }
A Verifier checks the authenticity of commands sent by a Signer.
func NewVerifier ¶
func NewVerifier(private ECDHPrivateKey, id []byte, domain universal.Domain, signerPublicBytes []byte) (*Verifier, error)
NewVerifier returns a Verifier. Set domain to universal.Domain_DOMAIN_BROADCAST if the Verifier shouldn't enforce domain checking. The Verifier's domain must be known in advance by the Signer.
func (*Verifier) SessionInfo ¶
func (v *Verifier) SessionInfo() (*signatures.SessionInfo, error)
SessionInfo contains metadata used to prevent replay and similar attacks. A Signer must have the Verifier's SessionInfo on initialization.
func (*Verifier) SetSessionInfo ¶
func (v *Verifier) SetSessionInfo(challenge []byte, message *universal.RoutableMessage) error
SetSessionInfo attaches up-to-date session info to a message. This is useful when v encounters an error while authenticating a message and wishes to include session info in the error response, thereby allowing the Signer to resync.
func (*Verifier) SignedSessionInfo ¶
SignedSessionInfo returns a protobuf-encoded signatures.SessionInfo along with an authentication tag that a Signer may use to verify the info has not been tampered with.