bluesky viewer in the terminal
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 120 lines 3.0 kB view raw
1package config 2 3import ( 4 "crypto/aes" 5 "crypto/cipher" 6 "crypto/rand" 7 "crypto/sha256" 8 "encoding/base64" 9 "errors" 10 "io" 11 "os" 12) 13 14// EncryptToken encrypts a plaintext token using AES-256-GCM with a key derived from SHA256 as a [base64]-encoded ciphertext with prepended nonce. 15// The encryption key is derived from either SKYCLI_SECRET env var or machine-specific identifier. 16func EncryptToken(plaintext string) (string, error) { 17 if plaintext == "" { 18 return "", nil 19 } 20 21 key, err := getDerivedKey() 22 if err != nil { 23 return "", err 24 } 25 26 block, err := aes.NewCipher(key) 27 if err != nil { 28 return "", &CryptoError{Op: "NewCipher", Err: err} 29 } 30 31 gcm, err := cipher.NewGCM(block) 32 if err != nil { 33 return "", &CryptoError{Op: "NewGCM", Err: err} 34 } 35 36 nonce := make([]byte, gcm.NonceSize()) 37 if _, err := io.ReadFull(rand.Reader, nonce); err != nil { 38 return "", &CryptoError{Op: "GenerateNonce", Err: err} 39 } 40 41 ciphertext := gcm.Seal(nonce, nonce, []byte(plaintext), nil) 42 return base64.StdEncoding.EncodeToString(ciphertext), nil 43} 44 45// DecryptToken decrypts a base64-encoded token encrypted with [EncryptToken]. 46// Returns the original plaintext token or an error if decryption fails. 47func DecryptToken(encrypted string) (string, error) { 48 if encrypted == "" { 49 return "", nil 50 } 51 52 key, err := getDerivedKey() 53 if err != nil { 54 return "", err 55 } 56 57 ciphertext, err := base64.StdEncoding.DecodeString(encrypted) 58 if err != nil { 59 return "", &CryptoError{Op: "DecodeBase64", Err: err} 60 } 61 62 block, err := aes.NewCipher(key) 63 if err != nil { 64 return "", &CryptoError{Op: "NewCipher", Err: err} 65 } 66 67 gcm, err := cipher.NewGCM(block) 68 if err != nil { 69 return "", &CryptoError{Op: "NewGCM", Err: err} 70 } 71 72 nonceSize := gcm.NonceSize() 73 if len(ciphertext) < nonceSize { 74 return "", &CryptoError{Op: "DecryptToken", Err: errors.New("ciphertext too short")} 75 } 76 77 nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:] 78 plaintext, err := gcm.Open(nil, nonce, ciphertext, nil) 79 if err != nil { 80 return "", &CryptoError{Op: "Decrypt", Err: err} 81 } 82 83 return string(plaintext), nil 84} 85 86// getDerivedKey derives a 32-byte AES key from either SKYCLI_SECRET env var or a combination of hostname and username. 87// Uses SHA256 for key derivation. 88func getDerivedKey() ([]byte, error) { 89 secret := os.Getenv("SKYCLI_SECRET") 90 if secret == "" { 91 hostname, err := os.Hostname() 92 if err != nil { 93 return nil, &CryptoError{Op: "GetHostname", Err: err} 94 } 95 username := os.Getenv("USER") 96 if username == "" { 97 username = os.Getenv("USERNAME") // Windows 98 } 99 secret = hostname + ":" + username 100 } 101 102 hash := sha256.Sum256([]byte(secret)) 103 return hash[:], nil 104} 105 106// CryptoError represents an error that occurred during cryptographic operations 107type CryptoError struct { 108 Op string 109 Err error 110} 111 112func (e *CryptoError) Error() string { 113 return "crypto." + e.Op + ": " + e.Err.Error() 114} 115 116func (e *CryptoError) Unwrap() error { 117 return e.Err 118} 119 120var _ error = &CryptoError{}