81 lines
2.1 KiB
Go
81 lines
2.1 KiB
Go
package device
|
|
|
|
import (
|
|
"crypto/aes"
|
|
"crypto/cipher"
|
|
"crypto/rand"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
)
|
|
|
|
type CredentialVault struct{ key []byte }
|
|
|
|
func NewCredentialVault(encodedKey string, allowRandom bool) (*CredentialVault, error) {
|
|
var key []byte
|
|
if encodedKey != "" {
|
|
decoded, err := base64.StdEncoding.DecodeString(encodedKey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("SENSE_CREDENTIAL_KEY must be base64 encoded")
|
|
}
|
|
key = decoded
|
|
} else if allowRandom {
|
|
key = make([]byte, 32)
|
|
if _, err := rand.Read(key); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if len(key) != 32 {
|
|
return nil, fmt.Errorf("SENSE_CREDENTIAL_KEY must decode to 32 bytes")
|
|
}
|
|
return &CredentialVault{key: key}, nil
|
|
}
|
|
|
|
func (v *CredentialVault) Encrypt(username, password string) ([]byte, error) {
|
|
plaintext, err := json.Marshal(map[string]string{"username": username, "password": password})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
block, err := aes.NewCipher(v.key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
gcm, err := cipher.NewGCM(block)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
nonce := make([]byte, gcm.NonceSize())
|
|
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
|
|
return nil, err
|
|
}
|
|
return gcm.Seal(nonce, nonce, plaintext, []byte("sense-device-credential-v1")), nil
|
|
}
|
|
|
|
func (v *CredentialVault) Decrypt(ciphertext []byte) (string, string, error) {
|
|
block, err := aes.NewCipher(v.key)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
gcm, err := cipher.NewGCM(block)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
if len(ciphertext) < gcm.NonceSize() {
|
|
return "", "", fmt.Errorf("invalid credential ciphertext")
|
|
}
|
|
nonce, encrypted := ciphertext[:gcm.NonceSize()], ciphertext[gcm.NonceSize():]
|
|
plaintext, err := gcm.Open(nil, nonce, encrypted, []byte("sense-device-credential-v1"))
|
|
if err != nil {
|
|
return "", "", fmt.Errorf("decrypt device credential: %w", err)
|
|
}
|
|
var value struct {
|
|
Username string `json:"username"`
|
|
Password string `json:"password"`
|
|
}
|
|
if err := json.Unmarshal(plaintext, &value); err != nil {
|
|
return "", "", err
|
|
}
|
|
return value.Username, value.Password, nil
|
|
}
|