Files
netbird/util/crypt/crypt.go
Misha Bragin e586c20e36 [management, infrastructure, idp] Simplified IdP Management - Embedded IdP (#5008)
Embed Dex as a built-in IdP to simplify self-hosting setup.
Adds an embedded OIDC Identity Provider (Dex) with local user management and optional external IdP connectors (Google/GitHub/OIDC/SAML), plus device-auth flow for CLI login. Introduces instance onboarding/setup endpoints (including owner creation), field-level encryption for sensitive user data, a streamlined self-hosting provisioning script, and expanded APIs + test coverage for IdP management.

more at https://github.com/netbirdio/netbird/pull/5008#issuecomment-3718987393
2026-01-07 14:52:32 +01:00

97 lines
2.5 KiB
Go

package crypt
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"fmt"
"io"
)
// FieldEncrypt provides AES-GCM encryption for sensitive fields.
type FieldEncrypt struct {
block cipher.Block
}
// NewFieldEncrypt creates a new FieldEncrypt with the given base64-encoded key.
// The key must be 32 bytes when decoded (for AES-256).
func NewFieldEncrypt(base64Key string) (*FieldEncrypt, error) {
key, err := base64.StdEncoding.DecodeString(base64Key)
if err != nil {
return nil, fmt.Errorf("decode encryption key: %w", err)
}
if len(key) != 32 {
return nil, fmt.Errorf("encryption key must be 32 bytes, got %d", len(key))
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, fmt.Errorf("create cipher: %w", err)
}
return &FieldEncrypt{block: block}, nil
}
// Encrypt encrypts the given plaintext and returns base64-encoded ciphertext.
// Returns empty string for empty input.
func (f *FieldEncrypt) Encrypt(plaintext string) (string, error) {
if plaintext == "" {
return "", nil
}
gcm, err := cipher.NewGCM(f.block)
if err != nil {
return "", fmt.Errorf("create GCM: %w", err)
}
nonce := make([]byte, gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return "", fmt.Errorf("generate nonce: %w", err)
}
ciphertext := gcm.Seal(nonce, nonce, []byte(plaintext), nil)
return base64.StdEncoding.EncodeToString(ciphertext), nil
}
// Decrypt decrypts the given base64-encoded ciphertext and returns the plaintext.
// Returns empty string for empty input.
func (f *FieldEncrypt) Decrypt(ciphertext string) (string, error) {
if ciphertext == "" {
return "", nil
}
data, err := base64.StdEncoding.DecodeString(ciphertext)
if err != nil {
return "", fmt.Errorf("decode ciphertext: %w", err)
}
gcm, err := cipher.NewGCM(f.block)
if err != nil {
return "", fmt.Errorf("create GCM: %w", err)
}
nonceSize := gcm.NonceSize()
if len(data) < nonceSize {
return "", fmt.Errorf("ciphertext too short")
}
nonce, ciphertextBytes := data[:nonceSize], data[nonceSize:]
plaintext, err := gcm.Open(nil, nonce, ciphertextBytes, nil)
if err != nil {
return "", fmt.Errorf("decrypt: %w", err)
}
return string(plaintext), nil
}
// GenerateKey generates a new random 32-byte encryption key and returns it as base64.
func GenerateKey() (string, error) {
key := make([]byte, 32)
if _, err := io.ReadFull(rand.Reader, key); err != nil {
return "", fmt.Errorf("generate key: %w", err)
}
return base64.StdEncoding.EncodeToString(key), nil
}