Files
archived-teslamateapi/src/EncryptionSupport.go
Tobias Lindberg c9db9729f9 cleaning, bumping and enhancing (#243)
* 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
2023-09-22 23:12:21 +02:00

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
}