diff --git a/src/EncryptionSupport.go b/src/EncryptionSupport.go new file mode 100644 index 0000000..ad1ba46 --- /dev/null +++ b/src/EncryptionSupport.go @@ -0,0 +1,94 @@ +package main + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/sha256" + "log" + + "github.com/gin-gonic/gin" +) + +// decryptAccessToken funct to decrypt tokens from database +func decryptAccessToken(data string, encryptionKey string) string { + + /* + From Adrian.... + I had a look at how to decode the binary input without additional libraries. Below is sample code for Elixir. An important detail is that "Additional Authenticated Data (AAD) " is required to decrypt the tokens. The AAD is a fixed string, in this case "AES256GCM”. + << _type::bytes-1, length::integer, _tag::bytes-size(length), iv::bytes-12, ciphertag::bytes-16, ciphertext::bytes >> = input + key = :crypto.hash(:sha256, key) + aad = "AES256GCM" + plaintext = :crypto.crypto_one_time_aead(:aes_256_gcm, key, iv, ciphertext, aad, ciphertag, false) + + How the encrypted content looks like.... + +----------------------------------------------------------+----------------------+ + | HEADER | BODY | + +-------------------+---------------+----------------------+----------------------+ + | Key Tag (n bytes) | IV (n bytes) | Ciphertag (16 bytes) | Ciphertext (n bytes) | + +-------------------+---------------+----------------------+----------------------+ + | |_________________________________ + | | + +---------------+-----------------+-------------------+ + | Type (1 byte) | Length (1 byte) | Key Tag (n bytes) | + +---------------+-----------------+-------------------+ + */ + + h := sha256.New() + h.Write([]byte(encryptionKey)) + if gin.IsDebugging() { + log.Printf("[debug] decryptAccessToken - Key: %x \n", h.Sum(nil)) + } + + key := h.Sum(nil) + block, err := aes.NewCipher(key) + if err != nil { + panic(err.Error()) + } + + // first byte + keyType := int([]rune(data)[0]) + // second byte + keyLen := int([]rune(data)[1]) + keyTag := data[2 : 2+keyLen] + if gin.IsDebugging() { + log.Printf("[debug] decryptAccessToken - Type: %d \n", keyType) + log.Printf("[debug] decryptAccessToken - Length: %d \n", keyLen) + log.Printf("[debug] decryptAccessToken - Key Tag: %s \n", keyTag) + } + + /* + With AES.GCM, 12-byte IV length is necessary for interoperability reasons. + See https://github.com/danielberkompas/cloak/issues/93 + IV and nonce are often used interchangeably. Essentially though, an IV is a nonce with an additional requirement: it must be selected in a non-predictable way + https://medium.com/@fridakahsas/salt-nonces-and-ivs-whats-the-difference-d7a44724a447#:~:text=IV%20and%20nonce%20are%20often,an%20IV%20must%20be%20random. + */ + + nonce := data[2+keyLen : 2+keyLen+12] + if gin.IsDebugging() { + log.Printf("[debug] decryptAccessToken - IV (hex): %x \n", nonce) + + ciphertag := data[2+keyLen+12 : 2+keyLen+12+16] + log.Printf("[debug] decryptAccessToken - Ciphertag (hex): %x \n", ciphertag) + } + + aesgcm, err := cipher.NewGCMWithTagSize(block, 16) + if err != nil { + panic(err.Error()) + } + + // https://stackoverflow.com/a/68353192 + // golang aes expects cipertag to append ciphertext.... + ciphertextTag := data[2+keyLen+12+16:] + data[2+keyLen+12:2+keyLen+12+16] + + // AES256GCM -- Additional Authenticated Data (AAD) + plaintext, err := aesgcm.Open(nil, []byte(nonce), []byte(ciphertextTag), []byte("AES256GCM")) + if err != nil { + panic(err.Error()) + } + + if gin.IsDebugging() { + // fmt.Printf("[debug] decryptAccessToken - Decrypted: %s\n", plaintext) + } + + return string(plaintext) +} diff --git a/src/v1_TeslaMateAPICarsCommand.go b/src/v1_TeslaMateAPICarsCommand.go index 95e8f3f..ec72f56 100644 --- a/src/v1_TeslaMateAPICarsCommand.go +++ b/src/v1_TeslaMateAPICarsCommand.go @@ -1,6 +1,7 @@ package main import ( + "database/sql" "encoding/json" "io/ioutil" "log" @@ -16,6 +17,7 @@ func TeslaMateAPICarsCommandV1(c *gin.Context) { // creating required vars var ( + CarsCommandsError1 = "Unable to load cars." TeslaAccessToken, TeslaVehicleID string jsonData map[string]interface{} err error @@ -78,33 +80,36 @@ func TeslaMateAPICarsCommandV1(c *gin.Context) { FROM cars WHERE id = $1 LIMIT 1;` - rows, err := db.Query(query, CarID) + row := db.QueryRow(query, CarID) - // checking for errors in query - if err != nil { - TeslaMateAPIHandleErrorResponse(c, "TeslaMateAPICarsCommandV1", "Unable to load cars.", err.Error()) + err = row.Scan( + &TeslaVehicleID, + &TeslaAccessToken, + ) + + switch err { + case sql.ErrNoRows: + TeslaMateAPIHandleErrorResponse(c, "TeslaMateAPICarsCommandV1", "No rows were returned!", err.Error()) + return + case nil: + // nothing wrong.. continuing + break + default: + TeslaMateAPIHandleErrorResponse(c, "TeslaMateAPICarsCommandV1", CarsCommandsError1, err.Error()) return } - // defer closing rows - defer rows.Close() - - // looping through all results (even if it's only one..) - for rows.Next() { - // scanning row and putting values into the drive - err = rows.Scan( - &TeslaVehicleID, - &TeslaAccessToken, - ) - } - - // checking for errors in query when doing scan action - if err != nil { - log.Println("[error] TeslaMateAPICarsCommandV1 error in sql query:", err) - TeslaMateAPIHandleOtherResponse(c, http.StatusInternalServerError, "TeslaMateAPICarsCommandV1", gin.H{"error": "internal sql query error"}) + // load ENCRYPTION_KEY environment variable + teslaMateEncryptionKey := getEnv("ENCRYPTION_KEY", "") + if teslaMateEncryptionKey == "" { + log.Println("[error] TeslaMateAPICarsCommandV1 can't get ENCRYPTION_KEY.. will fail to perform command.") + TeslaMateAPIHandleOtherResponse(c, http.StatusInternalServerError, "TeslaMateAPICarsCommandV1", gin.H{"error": "missing ENCRYPTION_KEY env variable"}) return } + // decrypt access token + TeslaAccessToken = decryptAccessToken(TeslaAccessToken, teslaMateEncryptionKey) + client := &http.Client{} req, _ := http.NewRequest(http.MethodPost, "https://owner-api.teslamotors.com/api/1/vehicles/"+TeslaVehicleID+command, strings.NewReader(string(reqBody))) req.Header.Set("Authorization", "Bearer "+TeslaAccessToken)