authentication

package
v0.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 3, 2024 License: Apache-2.0 Imports: 27 Imported by: 0

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

Examples

Constants

View Source
const SharedKeySizeBytes = 16

SharedKeySizeBytes is the length of the cryptographic key shared by a Signer and a Verifier.

View Source
const TeslaSchnorrSHA256 = "Tesla.SS256"

Variables

View Source
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")
)
View Source
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.

func (Error) Error

func (e Error) Error() string

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) NewHMAC

func (b *NativeSession) NewHMAC(label string) hash.Hash

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

func (s *Signer) AuthorizeHMAC(message *universal.RoutableMessage, expiresIn time.Duration) error

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

func (s *Signer) Encrypt(message *universal.RoutableMessage, expiresIn time.Duration) error

Encrypt message's payload in-place. This method adds (authenticated) metadata to the message as well, including the provided expiration time.

func (*Signer) ExportSessionInfo

func (s *Signer) ExportSessionInfo() ([]byte, error)

ExportSessionInfo can be used to write session state to disk, allowing for later resumption using ImportSessionInfo.

func (*Signer) RemotePublicKeyBytes

func (s *Signer) RemotePublicKeyBytes() []byte

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

func (s *Signer) UpdateSignedSessionInfo(challenge, encodedInfo, tag []byte) error

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 (*SigningMethodSchnorrP256) Sign added in v0.1.0

func (s *SigningMethodSchnorrP256) Sign(signingString string, key interface{}) ([]byte, error)

func (*SigningMethodSchnorrP256) Verify added in v0.1.0

func (s *SigningMethodSchnorrP256) Verify(signingString string, signature []byte, key interface{}) error

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

func (v *Verifier) SignedSessionInfo(challenge []byte) (encodedInfo, tag []byte, err error)

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.

func (*Verifier) Verify

func (v *Verifier) Verify(message *universal.RoutableMessage) (plaintext []byte, err error)

Verify message. If payload is encrypted, returns the plaintext. Otherwise extracts and returns the payload as-is.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL