mirror of
https://github.com/tobiasehlert/teslamateapi.git
synced 2026-02-27 09:54:18 +08:00
* some cleanup - updating constant in group to have explicit type (SA9004) - commenting out empty branch (SA9003) * bump go mods * refactor of nested if statements closes #175 * stop using ioutil as it is deprecated
132 lines
4.2 KiB
Go
132 lines
4.2 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/aes"
|
|
"crypto/cipher"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"log"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
type CarRegionAPI string
|
|
|
|
const (
|
|
ChinaAPI CarRegionAPI = "China"
|
|
GlobalAPI CarRegionAPI = "Global"
|
|
)
|
|
|
|
// 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)
|
|
}
|
|
|
|
// getCarRegionAPI function to get URL from iis in accessToken
|
|
func getCarRegionAPI(accessToken string) CarRegionAPI {
|
|
payload := strings.Split(accessToken, ".")
|
|
if len(payload) != 3 {
|
|
return GlobalAPI
|
|
}
|
|
decodedStr, err := base64.RawStdEncoding.DecodeString(payload[1])
|
|
if err != nil {
|
|
return GlobalAPI
|
|
}
|
|
var result map[string]interface{}
|
|
if err = json.Unmarshal(decodedStr, &result); err != nil {
|
|
return GlobalAPI
|
|
}
|
|
issUrl, err := url.Parse(result["iss"].(string))
|
|
if err != nil {
|
|
return GlobalAPI
|
|
}
|
|
if strings.HasSuffix(issUrl.Host, ".cn") {
|
|
return ChinaAPI
|
|
}
|
|
return GlobalAPI
|
|
}
|