259 lines
7.3 KiB
Go
259 lines
7.3 KiB
Go
package kiro
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/binary"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"math/rand"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
type RuntimeFingerprint struct {
|
|
OIDCSDKVersion string
|
|
RuntimeSDKVersion string
|
|
StreamingSDKVersion string
|
|
OSType string
|
|
OSVersion string
|
|
NodeVersion string
|
|
KiroVersion string
|
|
KiroHash string
|
|
}
|
|
|
|
type runtimeFingerprintManager struct {
|
|
mu sync.RWMutex
|
|
fingerprints map[string]*RuntimeFingerprint
|
|
}
|
|
|
|
var (
|
|
globalRuntimeFingerprintManager *runtimeFingerprintManager
|
|
globalRuntimeFingerprintManagerOnce sync.Once
|
|
|
|
oidcSDKVersions = []string{"3.980.0", "3.975.0", "3.972.0", "3.808.0", "3.738.0", "3.737.0", "3.736.0", "3.735.0"}
|
|
runtimeSDKVersions = []string{"1.0.0"}
|
|
streamingSDKVersions = []string{"1.0.34"}
|
|
osTypes = []string{"darwin", "win32"}
|
|
osVersions = map[string][]string{
|
|
"darwin": {"24.6.0"},
|
|
"win32": {"10.0.22631"},
|
|
}
|
|
nodeVersions = []string{"22.22.0"}
|
|
kiroVersions = []string{
|
|
"0.11.132", "0.11.131", "0.11.130",
|
|
}
|
|
)
|
|
|
|
func globalRuntimeFingerprints() *runtimeFingerprintManager {
|
|
globalRuntimeFingerprintManagerOnce.Do(func() {
|
|
globalRuntimeFingerprintManager = &runtimeFingerprintManager{
|
|
fingerprints: make(map[string]*RuntimeFingerprint),
|
|
}
|
|
})
|
|
return globalRuntimeFingerprintManager
|
|
}
|
|
|
|
func (m *runtimeFingerprintManager) Get(accountKey, machineID string) *RuntimeFingerprint {
|
|
lookupKey := fingerprintLookupKey(accountKey, "runtime")
|
|
machineID = normalizeMachineIDOrFallback(machineID, lookupKey)
|
|
|
|
m.mu.RLock()
|
|
if fp, ok := m.fingerprints[lookupKey]; ok && fp.KiroHash == machineID {
|
|
m.mu.RUnlock()
|
|
return fp
|
|
}
|
|
m.mu.RUnlock()
|
|
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if fp, ok := m.fingerprints[lookupKey]; ok && fp.KiroHash == machineID {
|
|
return fp
|
|
}
|
|
fp := generateRuntimeFingerprint(lookupKey, machineID)
|
|
m.fingerprints[lookupKey] = fp
|
|
return fp
|
|
}
|
|
|
|
func generateRuntimeFingerprint(accountKey, machineID string) *RuntimeFingerprint {
|
|
hash := sha256.Sum256([]byte(accountKey))
|
|
seed := int64(binary.BigEndian.Uint64(hash[:8]))
|
|
rng := rand.New(rand.NewSource(seed))
|
|
|
|
osType := goOSToNodePlatform(runtime.GOOS)
|
|
if !containsString(osTypes, osType) {
|
|
osType = osTypes[rng.Intn(len(osTypes))]
|
|
}
|
|
osVersionPool := osVersions[osType]
|
|
if len(osVersionPool) == 0 {
|
|
osVersionPool = osVersions["darwin"]
|
|
}
|
|
|
|
return &RuntimeFingerprint{
|
|
OIDCSDKVersion: oidcSDKVersions[rng.Intn(len(oidcSDKVersions))],
|
|
RuntimeSDKVersion: runtimeSDKVersions[rng.Intn(len(runtimeSDKVersions))],
|
|
StreamingSDKVersion: streamingSDKVersions[rng.Intn(len(streamingSDKVersions))],
|
|
OSType: osType,
|
|
OSVersion: osVersionPool[rng.Intn(len(osVersionPool))],
|
|
NodeVersion: nodeVersions[rng.Intn(len(nodeVersions))],
|
|
KiroVersion: kiroVersions[rng.Intn(len(kiroVersions))],
|
|
KiroHash: machineID,
|
|
}
|
|
}
|
|
|
|
func goOSToNodePlatform(goos string) string {
|
|
switch strings.TrimSpace(goos) {
|
|
case "windows":
|
|
return "win32"
|
|
default:
|
|
return strings.TrimSpace(goos)
|
|
}
|
|
}
|
|
|
|
func containsString(items []string, target string) bool {
|
|
for _, item := range items {
|
|
if item == target {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func BuildAccountKey(clientID, clientIDHash, refreshToken, profileArn string, accountID int64) string {
|
|
switch {
|
|
case strings.TrimSpace(clientIDHash) != "":
|
|
return clientIDHash
|
|
case strings.TrimSpace(clientID) != "":
|
|
return shortSHA(clientID)
|
|
case strings.TrimSpace(refreshToken) != "":
|
|
return shortSHA(refreshToken)
|
|
case strings.TrimSpace(profileArn) != "":
|
|
return shortSHA(profileArn)
|
|
case accountID > 0:
|
|
return shortSHA(fmt.Sprintf("account:%d", accountID))
|
|
default:
|
|
return shortSHA(uuid.NewString())
|
|
}
|
|
}
|
|
|
|
func NormalizeMachineID(machineID string) (string, bool) {
|
|
trimmed := strings.TrimSpace(machineID)
|
|
if len(trimmed) == 64 && isHexString(trimmed) {
|
|
return strings.ToLower(trimmed), true
|
|
}
|
|
withoutDashes := strings.ReplaceAll(trimmed, "-", "")
|
|
if len(withoutDashes) == 32 && isHexString(withoutDashes) {
|
|
normalized := strings.ToLower(withoutDashes)
|
|
return normalized + normalized, true
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
func BuildMachineID(refreshToken, apiKey, fallbackKey string) string {
|
|
if refreshToken = strings.TrimSpace(refreshToken); refreshToken != "" {
|
|
return sha256Hex("KotlinNativeAPI/" + refreshToken)
|
|
}
|
|
if apiKey = strings.TrimSpace(apiKey); apiKey != "" {
|
|
return sha256Hex("KiroAPIKey/" + apiKey)
|
|
}
|
|
if fallbackKey = strings.TrimSpace(fallbackKey); fallbackKey != "" {
|
|
return sha256Hex("KiroFallback/" + fallbackKey)
|
|
}
|
|
return sha256Hex("KiroFallback/default")
|
|
}
|
|
|
|
func shortSHA(seed string) string {
|
|
sum := sha256.Sum256([]byte(seed))
|
|
return hex.EncodeToString(sum[:8])
|
|
}
|
|
|
|
func sha256Hex(seed string) string {
|
|
sum := sha256.Sum256([]byte(seed))
|
|
return hex.EncodeToString(sum[:])
|
|
}
|
|
|
|
func isHexString(value string) bool {
|
|
for _, c := range value {
|
|
if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func normalizeMachineIDOrFallback(machineID, fallbackKey string) string {
|
|
if normalized, ok := NormalizeMachineID(machineID); ok {
|
|
return normalized
|
|
}
|
|
return BuildMachineID("", "", fallbackKey)
|
|
}
|
|
|
|
func fingerprintLookupKey(accountKey, fallback string) string {
|
|
key := strings.TrimSpace(accountKey)
|
|
if key != "" {
|
|
return key
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
func BuildRuntimeUserAgent(accountKey, machineID string) string {
|
|
fp := globalRuntimeFingerprints().Get(accountKey, machineID)
|
|
return fmt.Sprintf(
|
|
"aws-sdk-js/%s ua/2.1 os/%s#%s lang/js md/nodejs#%s api/codewhispererstreaming#%s m/E KiroIDE-%s-%s",
|
|
fp.StreamingSDKVersion,
|
|
fp.OSType,
|
|
fp.OSVersion,
|
|
fp.NodeVersion,
|
|
fp.StreamingSDKVersion,
|
|
fp.KiroVersion,
|
|
fp.KiroHash,
|
|
)
|
|
}
|
|
|
|
func BuildRuntimeAmzUserAgent(accountKey, machineID string) string {
|
|
fp := globalRuntimeFingerprints().Get(accountKey, machineID)
|
|
return fmt.Sprintf(
|
|
"aws-sdk-js/%s KiroIDE-%s-%s",
|
|
fp.StreamingSDKVersion,
|
|
fp.KiroVersion,
|
|
fp.KiroHash,
|
|
)
|
|
}
|
|
|
|
func BuildOIDCHeaders(accountKey, machineID string) map[string]string {
|
|
fp := globalRuntimeFingerprints().Get(fingerprintLookupKey(accountKey, "oidc-session"), machineID)
|
|
return map[string]string{
|
|
"Content-Type": "application/json",
|
|
"x-amz-user-agent": fmt.Sprintf("aws-sdk-js/%s KiroIDE", fp.OIDCSDKVersion),
|
|
"User-Agent": fmt.Sprintf("aws-sdk-js/%s ua/2.1 os/%s#%s lang/js md/nodejs#%s api/sso-oidc#%s m/E KiroIDE", fp.OIDCSDKVersion, fp.OSType, fp.OSVersion, fp.NodeVersion, fp.OIDCSDKVersion),
|
|
"amz-sdk-invocation-id": uuid.NewString(),
|
|
"amz-sdk-request": "attempt=1; max=4",
|
|
}
|
|
}
|
|
|
|
func BuildLoginHeaders(accountKey, machineID string) map[string]string {
|
|
fp := globalRuntimeFingerprints().Get(fingerprintLookupKey(accountKey, "login"), machineID)
|
|
return map[string]string{
|
|
"Content-Type": "application/json",
|
|
"User-Agent": fmt.Sprintf("KiroIDE-%s-%s", fp.KiroVersion, fp.KiroHash),
|
|
"Accept": "application/json, text/plain, */*",
|
|
}
|
|
}
|
|
|
|
func ExponentialBackoffWithJitter(attempt int, baseDelay, maxDelay time.Duration) time.Duration {
|
|
if attempt < 0 {
|
|
attempt = 0
|
|
}
|
|
delay := baseDelay << attempt
|
|
if delay > maxDelay {
|
|
delay = maxDelay
|
|
}
|
|
const jitterFactor = 0.3
|
|
seed := rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
jitter := 1 + ((seed.Float64()*2 - 1) * jitterFactor)
|
|
return time.Duration(float64(delay) * jitter)
|
|
}
|