This commit is contained in:
Sudo-Ivan
2024-12-31 15:15:06 -06:00
parent 99d8e44182
commit f3d22dfcd4
8 changed files with 489 additions and 169 deletions

View File

@@ -1,6 +1,6 @@
# Reticulum-Go # Reticulum-Go
[Reticulum Network](https://github.com/markqvist/Reticulum) implementation in Go [Reticulum Network](https://github.com/markqvist/Reticulum) implementation in Go.
Aiming for full spec compatibility with the Python version 0.8.8+. Aiming for full spec compatibility with the Python version 0.8.8+.
@@ -8,9 +8,7 @@ Aiming for full spec compatibility with the Python version 0.8.8+.
Packages: Packages:
- `github.com/pelletier/go-toml`
- `golang.org/x/crypto` - `golang.org/x/crypto`
- `gopkg.in/yaml.v3`
## To-Do List ## To-Do List
@@ -108,7 +106,9 @@ Packages:
- [x] Cleanup routines - [x] Cleanup routines
### Compatibility ### Compatibility
- [ ] RNS Utilities Compatibility - [ ] RNS Utilities.
- [ ] Reticulum config.
### Testing & Validation ### Testing & Validation
- [ ] Unit tests for all components (Link, Resource, Destination, Identity, Packet, Transport, Interface) - [ ] Unit tests for all components (Link, Resource, Destination, Identity, Packet, Transport, Interface)
@@ -124,7 +124,7 @@ Packages:
### Cleanup ### Cleanup
- [ ] Seperate Cryptography from identity.go to their own files. - [ ] Seperate Cryptography from identity.go to their own files.
- [ ] Move constants to their own files. - [ ] Move constants to their own files.
- [ ] Remove TOML stuff, use reticulum config. - [ ] Remove default community interfaces in default config creation after testing.
### Other ### Other
- [ ] Rate limiting - [ ] Rate limiting

30
SECURITY.md Normal file
View File

@@ -0,0 +1,30 @@
# Security Policy
I use [Socket](https://socket.dev/) and [Deepsource](https://deepsource.com/) for this project.
## Cryptography Dependencies
- golang.org/x/crypto for core cryptographic primitives
- hkdf
- curve25519
- go/crypto
- ed25519
- sha256
- rand
- aes
- cipher
- hmac
## Reporting a Vulnerability
Please report any security vulnerabilities to [rns@quad4.io](mailto:rns@quad4.io)
**PGP Key:**
```
-----BEGIN PGP PUBLIC KEY BLOCK-----
xjMEZ3RaxBYJKwYBBAHaRw8BAQdAcW8OFXyQ6KuqoTWKVbULYgakD

2
go.mod
View File

@@ -3,7 +3,5 @@ module github.com/Sudo-Ivan/reticulum-go
go 1.23.4 go 1.23.4
require ( require (
github.com/pelletier/go-toml v1.9.5
golang.org/x/crypto v0.31.0 golang.org/x/crypto v0.31.0
gopkg.in/yaml.v3 v3.0.1
) )

View File

@@ -1,11 +1,14 @@
package config package config
import ( import (
"bufio"
"fmt"
"os" "os"
"path/filepath" "path/filepath"
"strconv"
"strings"
"github.com/Sudo-Ivan/reticulum-go/pkg/common" "github.com/Sudo-Ivan/reticulum-go/pkg/common"
"github.com/pelletier/go-toml"
) )
const ( const (
@@ -44,35 +47,168 @@ func EnsureConfigDir() error {
return os.MkdirAll(configDir, 0755) return os.MkdirAll(configDir, 0755)
} }
// parseValue parses string values into appropriate types
func parseValue(value string) interface{} {
value = strings.TrimSpace(value)
// Try bool
if value == "true" {
return true
}
if value == "false" {
return false
}
// Try int
if i, err := strconv.Atoi(value); err == nil {
return i
}
// Return as string
return value
}
// LoadConfig loads the configuration from the specified path // LoadConfig loads the configuration from the specified path
func LoadConfig(path string) (*common.ReticulumConfig, error) { func LoadConfig(path string) (*common.ReticulumConfig, error) {
data, err := os.ReadFile(path) file, err := os.Open(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer file.Close()
cfg := DefaultConfig() cfg := DefaultConfig()
if err := toml.Unmarshal(data, cfg); err != nil { cfg.ConfigPath = path
return nil, err
scanner := bufio.NewScanner(file)
var currentInterface *common.InterfaceConfig
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
// Skip comments and empty lines
if line == "" || strings.HasPrefix(line, "#") {
continue
}
// Handle interface sections
if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
name := strings.Trim(line, "[]")
currentInterface = &common.InterfaceConfig{Name: name}
cfg.Interfaces[name] = currentInterface
continue
}
parts := strings.SplitN(line, "=", 2)
if len(parts) != 2 {
continue
}
key := strings.TrimSpace(parts[0])
value := strings.TrimSpace(parts[1])
if currentInterface != nil {
// Parse interface config
switch key {
case "type":
currentInterface.Type = value
case "enabled":
currentInterface.Enabled = value == "true"
case "address":
currentInterface.Address = value
case "port":
currentInterface.Port, _ = strconv.Atoi(value)
case "target_host":
currentInterface.TargetHost = value
case "target_port":
currentInterface.TargetPort, _ = strconv.Atoi(value)
case "discovery_port":
currentInterface.DiscoveryPort, _ = strconv.Atoi(value)
case "data_port":
currentInterface.DataPort, _ = strconv.Atoi(value)
case "discovery_scope":
currentInterface.DiscoveryScope = value
case "group_id":
currentInterface.GroupID = value
}
} else {
// Parse global config
switch key {
case "enable_transport":
cfg.EnableTransport = value == "true"
case "share_instance":
cfg.ShareInstance = value == "true"
case "shared_instance_port":
cfg.SharedInstancePort, _ = strconv.Atoi(value)
case "instance_control_port":
cfg.InstanceControlPort, _ = strconv.Atoi(value)
case "panic_on_interface_error":
cfg.PanicOnInterfaceErr = value == "true"
case "loglevel":
cfg.LogLevel, _ = strconv.Atoi(value)
}
}
} }
cfg.ConfigPath = path
return cfg, nil return cfg, nil
} }
// SaveConfig saves the configuration to the specified path // SaveConfig saves the configuration to the specified path
func SaveConfig(cfg *common.ReticulumConfig) error { func SaveConfig(cfg *common.ReticulumConfig) error {
data, err := toml.Marshal(cfg) if cfg.ConfigPath == "" {
if err != nil { return fmt.Errorf("config path not set")
return err
} }
return os.WriteFile(cfg.ConfigPath, data, 0644) var builder strings.Builder
// Write global config
builder.WriteString("# Reticulum Configuration\n")
builder.WriteString(fmt.Sprintf("enable_transport = %v\n", cfg.EnableTransport))
builder.WriteString(fmt.Sprintf("share_instance = %v\n", cfg.ShareInstance))
builder.WriteString(fmt.Sprintf("shared_instance_port = %d\n", cfg.SharedInstancePort))
builder.WriteString(fmt.Sprintf("instance_control_port = %d\n", cfg.InstanceControlPort))
builder.WriteString(fmt.Sprintf("panic_on_interface_error = %v\n", cfg.PanicOnInterfaceErr))
builder.WriteString(fmt.Sprintf("loglevel = %d\n\n", cfg.LogLevel))
// Write interface configs
for name, iface := range cfg.Interfaces {
builder.WriteString(fmt.Sprintf("[%s]\n", name))
builder.WriteString(fmt.Sprintf("type = %s\n", iface.Type))
builder.WriteString(fmt.Sprintf("enabled = %v\n", iface.Enabled))
if iface.Address != "" {
builder.WriteString(fmt.Sprintf("address = %s\n", iface.Address))
}
if iface.Port != 0 {
builder.WriteString(fmt.Sprintf("port = %d\n", iface.Port))
}
if iface.TargetHost != "" {
builder.WriteString(fmt.Sprintf("target_host = %s\n", iface.TargetHost))
}
if iface.TargetPort != 0 {
builder.WriteString(fmt.Sprintf("target_port = %d\n", iface.TargetPort))
}
if iface.DiscoveryPort != 0 {
builder.WriteString(fmt.Sprintf("discovery_port = %d\n", iface.DiscoveryPort))
}
if iface.DataPort != 0 {
builder.WriteString(fmt.Sprintf("data_port = %d\n", iface.DataPort))
}
if iface.DiscoveryScope != "" {
builder.WriteString(fmt.Sprintf("discovery_scope = %s\n", iface.DiscoveryScope))
}
if iface.GroupID != "" {
builder.WriteString(fmt.Sprintf("group_id = %s\n", iface.GroupID))
}
builder.WriteString("\n")
}
return os.WriteFile(cfg.ConfigPath, []byte(builder.String()), 0644)
} }
// CreateDefaultConfig creates a default configuration file // CreateDefaultConfig creates a default configuration file
func CreateDefaultConfig(path string) error { func CreateDefaultConfig(path string) error {
cfg := DefaultConfig() cfg := DefaultConfig()
cfg.ConfigPath = path
// Add Auto Interface // Add Auto Interface
cfg.Interfaces["Auto Discovery"] = &common.InterfaceConfig{ cfg.Interfaces["Auto Discovery"] = &common.InterfaceConfig{
@@ -84,7 +220,7 @@ func CreateDefaultConfig(path string) error {
DataPort: 42671, DataPort: 42671,
} }
// Add RNS Amsterdam Testnet interface // Add default interfaces
cfg.Interfaces["RNS Testnet Amsterdam"] = &common.InterfaceConfig{ cfg.Interfaces["RNS Testnet Amsterdam"] = &common.InterfaceConfig{
Type: "TCPClientInterface", Type: "TCPClientInterface",
Enabled: true, Enabled: true,
@@ -92,7 +228,6 @@ func CreateDefaultConfig(path string) error {
TargetPort: 4965, TargetPort: 4965,
} }
// Add RNS BetweenTheBorders Testnet interface
cfg.Interfaces["RNS Testnet BetweenTheBorders"] = &common.InterfaceConfig{ cfg.Interfaces["RNS Testnet BetweenTheBorders"] = &common.InterfaceConfig{
Type: "TCPClientInterface", Type: "TCPClientInterface",
Enabled: true, Enabled: true,
@@ -100,7 +235,6 @@ func CreateDefaultConfig(path string) error {
TargetPort: 4242, TargetPort: 4242,
} }
// Add local UDP interface
cfg.Interfaces["Local UDP"] = &common.InterfaceConfig{ cfg.Interfaces["Local UDP"] = &common.InterfaceConfig{
Type: "UDPInterface", Type: "UDPInterface",
Enabled: true, Enabled: true,
@@ -108,16 +242,11 @@ func CreateDefaultConfig(path string) error {
Port: 37696, Port: 37696,
} }
data, err := toml.Marshal(cfg)
if err != nil {
return err
}
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
return err return err
} }
return os.WriteFile(path, data, 0644) return SaveConfig(cfg)
} }
// InitConfig initializes the configuration system // InitConfig initializes the configuration system

View File

@@ -1,77 +1,77 @@
package common package common
import ( import (
"fmt" "fmt"
) )
const ( const (
DEFAULT_SHARED_INSTANCE_PORT = 37428 DEFAULT_SHARED_INSTANCE_PORT = 37428
DEFAULT_INSTANCE_CONTROL_PORT = 37429 DEFAULT_INSTANCE_CONTROL_PORT = 37429
DEFAULT_LOG_LEVEL = 20 DEFAULT_LOG_LEVEL = 20
) )
// ConfigProvider interface for accessing configuration // ConfigProvider interface for accessing configuration
type ConfigProvider interface { type ConfigProvider interface {
GetConfigPath() string GetConfigPath() string
GetLogLevel() int GetLogLevel() int
GetInterfaces() map[string]InterfaceConfig GetInterfaces() map[string]InterfaceConfig
} }
// InterfaceConfig represents interface configuration // InterfaceConfig represents interface configuration
type InterfaceConfig struct { type InterfaceConfig struct {
Name string `toml:"name"` Name string
Type string `toml:"type"` Type string
Enabled bool `toml:"enabled"` Enabled bool
Address string `toml:"address"` Address string
Port int `toml:"port"` Port int
TargetHost string `toml:"target_host"` TargetHost string
TargetPort int `toml:"target_port"` TargetPort int
TargetAddress string `toml:"target_address"` TargetAddress string
Interface string `toml:"interface"` Interface string
KISSFraming bool `toml:"kiss_framing"` KISSFraming bool
I2PTunneled bool `toml:"i2p_tunneled"` I2PTunneled bool
PreferIPv6 bool `toml:"prefer_ipv6"` PreferIPv6 bool
MaxReconnTries int `toml:"max_reconnect_tries"` MaxReconnTries int
Bitrate int64 `toml:"bitrate"` Bitrate int64
MTU int `toml:"mtu"` MTU int
GroupID string GroupID string
DiscoveryScope string DiscoveryScope string
DiscoveryPort int DiscoveryPort int
DataPort int DataPort int
} }
// ReticulumConfig represents the main configuration structure // ReticulumConfig represents the main configuration structure
type ReticulumConfig struct { type ReticulumConfig struct {
ConfigPath string `toml:"-"` ConfigPath string
EnableTransport bool `toml:"enable_transport"` EnableTransport bool
ShareInstance bool `toml:"share_instance"` ShareInstance bool
SharedInstancePort int `toml:"shared_instance_port"` SharedInstancePort int
InstanceControlPort int `toml:"instance_control_port"` InstanceControlPort int
PanicOnInterfaceErr bool `toml:"panic_on_interface_error"` PanicOnInterfaceErr bool
LogLevel int `toml:"loglevel"` LogLevel int
Interfaces map[string]*InterfaceConfig `toml:"interfaces"` Interfaces map[string]*InterfaceConfig
} }
// NewReticulumConfig creates a new ReticulumConfig with default values // NewReticulumConfig creates a new ReticulumConfig with default values
func NewReticulumConfig() *ReticulumConfig { func NewReticulumConfig() *ReticulumConfig {
return &ReticulumConfig{ return &ReticulumConfig{
EnableTransport: true, EnableTransport: true,
ShareInstance: false, ShareInstance: false,
SharedInstancePort: DEFAULT_SHARED_INSTANCE_PORT, SharedInstancePort: DEFAULT_SHARED_INSTANCE_PORT,
InstanceControlPort: DEFAULT_INSTANCE_CONTROL_PORT, InstanceControlPort: DEFAULT_INSTANCE_CONTROL_PORT,
PanicOnInterfaceErr: false, PanicOnInterfaceErr: false,
LogLevel: DEFAULT_LOG_LEVEL, LogLevel: DEFAULT_LOG_LEVEL,
Interfaces: make(map[string]*InterfaceConfig), Interfaces: make(map[string]*InterfaceConfig),
} }
} }
// Validate checks if the configuration is valid // Validate checks if the configuration is valid
func (c *ReticulumConfig) Validate() error { func (c *ReticulumConfig) Validate() error {
if c.SharedInstancePort < 1 || c.SharedInstancePort > 65535 { if c.SharedInstancePort < 1 || c.SharedInstancePort > 65535 {
return fmt.Errorf("invalid shared instance port: %d", c.SharedInstancePort) return fmt.Errorf("invalid shared instance port: %d", c.SharedInstancePort)
} }
if c.InstanceControlPort < 1 || c.InstanceControlPort > 65535 { if c.InstanceControlPort < 1 || c.InstanceControlPort > 65535 {
return fmt.Errorf("invalid instance control port: %d", c.InstanceControlPort) return fmt.Errorf("invalid instance control port: %d", c.InstanceControlPort)
} }
return nil return nil
} }

View File

@@ -1,49 +1,270 @@
package config package config
import ( import (
"io/ioutil" "bufio"
"gopkg.in/yaml.v3" "fmt"
"os"
"path/filepath"
"strconv"
"strings"
) )
type Config struct { type Config struct {
Identity struct { Identity struct {
Name string `yaml:"name"` Name string
StoragePath string `yaml:"storage_path"` StoragePath string
} `yaml:"identity"` }
Interfaces []struct { Interfaces []struct {
Name string `yaml:"name"` Name string
Type string `yaml:"type"` Type string
Enabled bool `yaml:"enabled"` Enabled bool
ListenPort int `yaml:"listen_port"` ListenPort int
ListenIP string `yaml:"listen_ip"` ListenIP string
KissFraming bool `yaml:"kiss_framing"` KissFraming bool
I2PTunneled bool `yaml:"i2p_tunneled"` I2PTunneled bool
} `yaml:"interfaces"` }
Transport struct { Transport struct {
AnnounceInterval int `yaml:"announce_interval"` AnnounceInterval int
PathRequestTimeout int `yaml:"path_request_timeout"` PathRequestTimeout int
MaxHops int `yaml:"max_hops"` MaxHops int
BitrateLimit int64 `yaml:"bitrate_limit"` BitrateLimit int64
} `yaml:"transport"` }
Logging struct { Logging struct {
Level string `yaml:"level"` Level string
File string `yaml:"file"` File string
} `yaml:"logging"` }
} }
func LoadConfig(path string) (*Config, error) { func LoadConfig(path string) (*Config, error) {
data, err := ioutil.ReadFile(path) file, err := os.Open(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer file.Close()
var cfg Config cfg := &Config{}
if err := yaml.Unmarshal(data, &cfg); err != nil { scanner := bufio.NewScanner(file)
return nil, err var currentSection string
}
return &cfg, nil for scanner.Scan() {
} line := strings.TrimSpace(scanner.Text())
// Skip comments and empty lines
if line == "" || strings.HasPrefix(line, "#") {
continue
}
// Handle sections
if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
currentSection = strings.Trim(line, "[]")
// If this is an interface section, append new interface
if strings.HasPrefix(currentSection, "interface ") {
cfg.Interfaces = append(cfg.Interfaces, struct {
Name string
Type string
Enabled bool
ListenPort int
ListenIP string
KissFraming bool
I2PTunneled bool
}{})
}
continue
}
// Parse key-value pairs
parts := strings.SplitN(line, "=", 2)
if len(parts) != 2 {
continue
}
key := strings.TrimSpace(parts[0])
value := strings.TrimSpace(parts[1])
switch currentSection {
case "identity":
switch key {
case "name":
cfg.Identity.Name = value
case "storage_path":
cfg.Identity.StoragePath = value
}
case "transport":
switch key {
case "announce_interval":
cfg.Transport.AnnounceInterval, _ = strconv.Atoi(value)
case "path_request_timeout":
cfg.Transport.PathRequestTimeout, _ = strconv.Atoi(value)
case "max_hops":
cfg.Transport.MaxHops, _ = strconv.Atoi(value)
case "bitrate_limit":
cfg.Transport.BitrateLimit, _ = strconv.ParseInt(value, 10, 64)
}
case "logging":
switch key {
case "level":
cfg.Logging.Level = value
case "file":
cfg.Logging.File = value
}
default:
// Handle interface sections
if strings.HasPrefix(currentSection, "interface ") && len(cfg.Interfaces) > 0 {
iface := &cfg.Interfaces[len(cfg.Interfaces)-1]
switch key {
case "name":
iface.Name = value
case "type":
iface.Type = value
case "enabled":
iface.Enabled = value == "true"
case "listen_port":
iface.ListenPort, _ = strconv.Atoi(value)
case "listen_ip":
iface.ListenIP = value
case "kiss_framing":
iface.KissFraming = value == "true"
case "i2p_tunneled":
iface.I2PTunneled = value == "true"
}
}
}
}
if err := scanner.Err(); err != nil {
return nil, err
}
return cfg, nil
}
func SaveConfig(cfg *Config, path string) error {
var builder strings.Builder
// Write Identity section
builder.WriteString("[identity]\n")
builder.WriteString(fmt.Sprintf("name = %s\n", cfg.Identity.Name))
builder.WriteString(fmt.Sprintf("storage_path = %s\n\n", cfg.Identity.StoragePath))
// Write Transport section
builder.WriteString("[transport]\n")
builder.WriteString(fmt.Sprintf("announce_interval = %d\n", cfg.Transport.AnnounceInterval))
builder.WriteString(fmt.Sprintf("path_request_timeout = %d\n", cfg.Transport.PathRequestTimeout))
builder.WriteString(fmt.Sprintf("max_hops = %d\n", cfg.Transport.MaxHops))
builder.WriteString(fmt.Sprintf("bitrate_limit = %d\n\n", cfg.Transport.BitrateLimit))
// Write Logging section
builder.WriteString("[logging]\n")
builder.WriteString(fmt.Sprintf("level = %s\n", cfg.Logging.Level))
builder.WriteString(fmt.Sprintf("file = %s\n\n", cfg.Logging.File))
// Write Interface sections
for _, iface := range cfg.Interfaces {
builder.WriteString(fmt.Sprintf("[interface %s]\n", iface.Name))
builder.WriteString(fmt.Sprintf("type = %s\n", iface.Type))
builder.WriteString(fmt.Sprintf("enabled = %v\n", iface.Enabled))
builder.WriteString(fmt.Sprintf("listen_port = %d\n", iface.ListenPort))
builder.WriteString(fmt.Sprintf("listen_ip = %s\n", iface.ListenIP))
builder.WriteString(fmt.Sprintf("kiss_framing = %v\n", iface.KissFraming))
builder.WriteString(fmt.Sprintf("i2p_tunneled = %v\n\n", iface.I2PTunneled))
}
return os.WriteFile(path, []byte(builder.String()), 0644)
}
func GetConfigDir() string {
homeDir, err := os.UserHomeDir()
if err != nil {
// Fallback to current directory if home directory cannot be determined
return ".reticulum-go"
}
return filepath.Join(homeDir, ".reticulum-go")
}
func GetDefaultConfigPath() string {
return filepath.Join(GetConfigDir(), "config")
}
func EnsureConfigDir() error {
configDir := GetConfigDir()
return os.MkdirAll(configDir, 0755)
}
func InitConfig() (*Config, error) {
// Ensure config directory exists
if err := EnsureConfigDir(); err != nil {
return nil, fmt.Errorf("failed to create config directory: %v", err)
}
configPath := GetDefaultConfigPath()
// Check if config file exists
if _, err := os.Stat(configPath); os.IsNotExist(err) {
// Create default config
cfg := &Config{}
// Set default values
cfg.Identity.Name = "reticulum-node"
cfg.Identity.StoragePath = filepath.Join(GetConfigDir(), "storage")
cfg.Transport.AnnounceInterval = 300
cfg.Transport.PathRequestTimeout = 15
cfg.Transport.MaxHops = 8
cfg.Transport.BitrateLimit = 1000000
cfg.Logging.Level = "info"
cfg.Logging.File = filepath.Join(GetConfigDir(), "reticulum.log")
// Add default interfaces
cfg.Interfaces = append(cfg.Interfaces, struct {
Name string
Type string
Enabled bool
ListenPort int
ListenIP string
KissFraming bool
I2PTunneled bool
}{
Name: "Local UDP",
Type: "UDPInterface",
Enabled: true,
ListenPort: 37697,
ListenIP: "0.0.0.0",
})
cfg.Interfaces = append(cfg.Interfaces, struct {
Name string
Type string
Enabled bool
ListenPort int
ListenIP string
KissFraming bool
I2PTunneled bool
}{
Name: "Auto Discovery",
Type: "AutoInterface",
Enabled: true,
ListenPort: 29717,
})
// Save default config
if err := SaveConfig(cfg, configPath); err != nil {
return nil, fmt.Errorf("failed to save default config: %v", err)
}
}
// Load config
cfg, err := LoadConfig(configPath)
if err != nil {
return nil, fmt.Errorf("failed to load config: %v", err)
}
return cfg, nil
}

View File

@@ -322,7 +322,8 @@ func (d *Destination) Encrypt(plaintext []byte) ([]byte, error) {
if key == nil { if key == nil {
return nil, errors.New("no ratchet key available") return nil, errors.New("no ratchet key available")
} }
return d.identity.EncryptSymmetric(plaintext, key) // CBC encryption with HMAC for group messages
return d.identity.EncryptWithHMAC(plaintext, key)
default: default:
return nil, errors.New("unsupported destination type for encryption") return nil, errors.New("unsupported destination type for encryption")
} }

View File

@@ -57,51 +57,6 @@ var (
ratchetPersistLock sync.Mutex ratchetPersistLock sync.Mutex
) )
func encryptAESGCM(key, plaintext []byte) ([]byte, error) {
block, err := aes.NewCipher(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
}
ciphertext := gcm.Seal(nonce, nonce, plaintext, nil)
return ciphertext, nil
}
func decryptAESGCM(key, ciphertext []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
nonceSize := gcm.NonceSize()
if len(ciphertext) < nonceSize {
return nil, err
}
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return nil, err
}
return plaintext, nil
}
func encryptAESCBC(key, plaintext []byte) ([]byte, error) { func encryptAESCBC(key, plaintext []byte) ([]byte, error) {
block, err := aes.NewCipher(key) block, err := aes.NewCipher(key)
if err != nil { if err != nil {
@@ -412,20 +367,6 @@ func (i *Identity) GetCurrentRatchetKey() []byte {
return latestKey return latestKey
} }
func (i *Identity) EncryptSymmetric(plaintext []byte, key []byte) ([]byte, error) {
if len(key) != 32 {
return nil, errors.New("invalid key length")
}
return encryptAESGCM(key, plaintext)
}
func (i *Identity) DecryptSymmetric(ciphertext []byte, key []byte) ([]byte, error) {
if len(key) != 32 {
return nil, errors.New("invalid key length")
}
return decryptAESGCM(key, ciphertext)
}
func (i *Identity) Decrypt(ciphertextToken []byte, ratchets [][]byte, enforceRatchets bool, ratchetIDReceiver *common.RatchetIDReceiver) ([]byte, error) { func (i *Identity) Decrypt(ciphertextToken []byte, ratchets [][]byte, enforceRatchets bool, ratchetIDReceiver *common.RatchetIDReceiver) ([]byte, error) {
if i.privateKey == nil { if i.privateKey == nil {
return nil, errors.New("decryption failed because identity does not hold a private key") return nil, errors.New("decryption failed because identity does not hold a private key")