Add credential obfuscation system
- build-obfuscated.sh: XOR encryption with random 256-bit key - obfuscation/obfuscation.go: Runtime de-obfuscation package - OBFUSCATION.md: Documentation and security comparison - Prevents casual extraction with 'strings' command - Medium security: Good for personal use, env vars for production
This commit is contained in:
264
OBFUSCATION.md
Normal file
264
OBFUSCATION.md
Normal file
@@ -0,0 +1,264 @@
|
|||||||
|
# Credential Obfuscation for Nextcloud Tools
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
When credentials are embedded in Go binaries via `ldflags`, they appear as plain text and can be easily extracted using the `strings` command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ strings ~/bin/nextcloud-client
|
||||||
|
https://teamworkapps.com
|
||||||
|
wltbagent@shortcutsolutions.net
|
||||||
|
1b8a28ca2fc26820fee3f9a8524c351b
|
||||||
|
```
|
||||||
|
|
||||||
|
This is a security risk for distributed binaries.
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
We've implemented a **XOR cipher with random key** approach:
|
||||||
|
|
||||||
|
1. **At Build Time**: Credentials are XOR-encrypted with a randomly generated 256-bit key
|
||||||
|
2. **Encoded**: The encrypted data is Base64-encoded for safe Go embedding
|
||||||
|
3. **At Runtime**: The binary de-obfuscates credentials in memory only
|
||||||
|
4. **Never on Disk**: De-obfuscated credentials never touch the disk
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
### Build Process
|
||||||
|
|
||||||
|
The `build-obfuscated.sh` script:
|
||||||
|
|
||||||
|
1. **Generates random key**: 256-bit (64 hex characters) via `openssl rand -hex 32`
|
||||||
|
2. **XOR encrypts credentials**: Each credential XOR'd with the key
|
||||||
|
3. **Base64 encodes**: Makes it safe for Go string embedding
|
||||||
|
4. **Embeds via ldflags**: Encrypted credentials + key embedded in binary
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate random key
|
||||||
|
OBFUSCATION_KEY=$(openssl rand -hex 32)
|
||||||
|
|
||||||
|
# XOR encrypt with key
|
||||||
|
obfuscated=$(xor_encrypt "$CREDENTIAL" "$OBFUSCATION_KEY")
|
||||||
|
|
||||||
|
# Base64 encode for embedding
|
||||||
|
obfuscated_b64=$(echo -n "$obfuscated" | base64 -w0)
|
||||||
|
|
||||||
|
# Build with obfuscated credentials
|
||||||
|
go build -ldflags="-X main.ObfuscatedServer=$obfuscated_b64 \
|
||||||
|
-X main.ObfuscatedUser=$obfuscated_b64 \
|
||||||
|
-X main.ObfuscatedPassword=$obfuscated_b64 \
|
||||||
|
-X main.ObfuscationKey=$OBFUSCATION_KEY" ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Runtime De-obfuscation
|
||||||
|
|
||||||
|
The `obfuscation.go` package provides de-obfuscation:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// DeobfuscateString reverses XOR obfuscation
|
||||||
|
func DeobfuscateString(obfuscatedBase64, key string) (string, error) {
|
||||||
|
// Decode base64
|
||||||
|
obfuscated, _ := base64.StdEncoding.DecodeString(obfuscatedBase64)
|
||||||
|
|
||||||
|
// XOR de-obfuscate
|
||||||
|
result := make([]byte, len(obfuscated))
|
||||||
|
keyBytes := []byte(key)
|
||||||
|
|
||||||
|
for i := 0; i < len(obfuscated); i++ {
|
||||||
|
result[i] = obfuscated[i] ^ keyBytes[i%len(keyBytes)]
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(result), nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
At runtime, tools call:
|
||||||
|
|
||||||
|
```go
|
||||||
|
server, user, password, err := GetDeobfuscatedCredentials()
|
||||||
|
if err != nil {
|
||||||
|
// Handle error
|
||||||
|
}
|
||||||
|
// Now use server, user, password (these are the actual values)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Levels
|
||||||
|
|
||||||
|
### Current Implementation (Medium Security)
|
||||||
|
|
||||||
|
**Strengths:**
|
||||||
|
- ✅ Prevents casual extraction with `strings`
|
||||||
|
- ✅ Credentials appear as Base64 gibberish in binary
|
||||||
|
- ✅ Key changes on every build (unique per binary)
|
||||||
|
- ✅ De-obfuscation happens only in memory
|
||||||
|
|
||||||
|
**Weaknesses:**
|
||||||
|
- ⚠️ Key is embedded in binary
|
||||||
|
- ⚠️ De-obfuscation code is public
|
||||||
|
- ⚠️ Determined attacker with source could reverse it
|
||||||
|
|
||||||
|
**Result:** Good for preventing casual access, but not secure against determined reverse engineering.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Build with Obfuscation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd projects/nextcloud-integration
|
||||||
|
|
||||||
|
# Use obfuscated build script
|
||||||
|
./build-obfuscated.sh https://teamworkapps.com <username> <password>
|
||||||
|
```
|
||||||
|
|
||||||
|
The script will:
|
||||||
|
1. Generate a unique 256-bit obfuscation key
|
||||||
|
2. Encrypt all credentials with XOR cipher
|
||||||
|
3. Base64 encode encrypted data
|
||||||
|
4. Build all tools with embedded encrypted credentials + key
|
||||||
|
5. Display security information
|
||||||
|
|
||||||
|
### Verify Obfuscation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check that strings doesn't show plain credentials
|
||||||
|
strings ~/bin/nextcloud-client | grep -i "teamworkapps"
|
||||||
|
|
||||||
|
# Should NOT find plain server URL
|
||||||
|
# Instead, you'll see base64 gibberish
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run Tools
|
||||||
|
|
||||||
|
Tools work exactly the same - obfuscation is transparent:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
~/bin/nextcloud-client --op list --path "/"
|
||||||
|
~/bin/nextcloud-mail --op list-folders
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Comparison
|
||||||
|
|
||||||
|
| Method | Security | Complexity | Distribution |
|
||||||
|
|---------|----------|------------|--------------|
|
||||||
|
| Plain ldflags (current) | Low | Simple | ❌ Not recommended |
|
||||||
|
| XOR Obfuscation (this) | Medium | Medium | ✅ Better for sharing |
|
||||||
|
| Environment Variables | High | Low | ✅ Best for production |
|
||||||
|
| HSM/Key Management | Very High | High | ✅ Enterprise grade |
|
||||||
|
| Network Auth | Very High | High | ✅ No credentials in binary |
|
||||||
|
|
||||||
|
## Better Security Options (For Production)
|
||||||
|
|
||||||
|
### Option 1: Environment Variables (Recommended)
|
||||||
|
|
||||||
|
Remove credentials entirely from binary, require environment variables:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Read from environment at runtime
|
||||||
|
server := os.Getenv("NEXTCLOUD_SERVER")
|
||||||
|
user := os.Getenv("NEXTCLOUD_USER")
|
||||||
|
password := os.Getenv("NEXTCLOUD_PASSWORD")
|
||||||
|
|
||||||
|
if server == "" || user == "" || password == "" {
|
||||||
|
log.Fatal("NEXTCLOUD_SERVER, NEXTCLOUD_USER, NEXTCLOUD_PASSWORD required")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pros:** No credentials in binary at all
|
||||||
|
**Cons:** Requires runtime configuration
|
||||||
|
|
||||||
|
### Option 2: Interactive First Run
|
||||||
|
|
||||||
|
Prompt for credentials on first run, then cache encrypted:
|
||||||
|
|
||||||
|
```go
|
||||||
|
if !credentialsFile.Exists() {
|
||||||
|
username := prompt("Nextcloud username: ")
|
||||||
|
password := prompt("Nextcloud password: ")
|
||||||
|
|
||||||
|
// Encrypt and store locally
|
||||||
|
encrypted := encrypt(username, password, derivedKey)
|
||||||
|
credentialsFile.Write(encrypted)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pros:** Never in binary, user controls storage
|
||||||
|
**Cons:** One-time setup required
|
||||||
|
|
||||||
|
### Option 3: Network Authentication
|
||||||
|
|
||||||
|
Credentials stored on a secure server, binary authenticates to fetch them:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Authenticate to credential server
|
||||||
|
token := authenticateToServer(derivedMachineID)
|
||||||
|
credentials := fetchCredentials(token)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pros:** Most secure, can revoke access
|
||||||
|
**Cons:** Requires infrastructure, network dependency
|
||||||
|
|
||||||
|
### Option 4: Hardware Security Module (HSM)
|
||||||
|
|
||||||
|
Credentials stored in HSM, binary requests operations:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Never see actual credentials
|
||||||
|
result := hsm.Sign(data)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pros:** Enterprise-grade security
|
||||||
|
**Cons:** Expensive hardware, complex setup
|
||||||
|
|
||||||
|
## Implementation Status
|
||||||
|
|
||||||
|
### Completed
|
||||||
|
|
||||||
|
- ✅ `build-obfuscated.sh` - XOR encryption with random key
|
||||||
|
- ✅ `obfuscation/obfuscation.go` - Runtime de-obfuscation package
|
||||||
|
- ✅ Base64 encoding for safe embedding
|
||||||
|
- ✅ Unique key per build
|
||||||
|
|
||||||
|
### Pending Integration
|
||||||
|
|
||||||
|
To enable obfuscation in tools:
|
||||||
|
|
||||||
|
1. Import obfuscation package
|
||||||
|
2. Replace ldflags with obfuscated versions
|
||||||
|
3. Call `GetDeobfuscatedCredentials()` at runtime
|
||||||
|
4. Remove plain Build* variables
|
||||||
|
|
||||||
|
Example migration:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Old way (plain credentials)
|
||||||
|
config.URL = BuildServerURL
|
||||||
|
config.User = BuildUsername
|
||||||
|
|
||||||
|
// New way (obfuscated)
|
||||||
|
server, user, password, _ := GetDeobfuscatedCredentials()
|
||||||
|
config.URL = server
|
||||||
|
config.User = user
|
||||||
|
config.Token = password
|
||||||
|
```
|
||||||
|
|
||||||
|
## Recommendation
|
||||||
|
|
||||||
|
**For Personal Use:**
|
||||||
|
- Current XOR obfuscation is sufficient
|
||||||
|
- Prevents casual extraction
|
||||||
|
- Easy to use, no setup
|
||||||
|
|
||||||
|
**For Distribution/Production:**
|
||||||
|
- Use environment variables (most secure, simplest)
|
||||||
|
- Or implement network authentication for enterprise
|
||||||
|
- Never distribute binaries with embedded credentials
|
||||||
|
|
||||||
|
**For Open Source Projects:**
|
||||||
|
- Document credential requirements
|
||||||
|
- Provide setup guide
|
||||||
|
- Never commit credentials
|
||||||
|
- Use .env.example files
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Credential Obfuscation: 2026-02-20*
|
||||||
115
build-obfuscated.sh
Executable file
115
build-obfuscated.sh
Executable file
@@ -0,0 +1,115 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Credential Obfuscation Build Script
|
||||||
|
# Encrypts credentials and embeds them in Go binaries
|
||||||
|
# Usage: ./build-obfuscated.sh <server-url> <username> <password>
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ $# -ne 3 ]; then
|
||||||
|
echo "Usage: $0 <server-url> <username> <password>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
SERVER_URL="$1"
|
||||||
|
USERNAME="$2"
|
||||||
|
PASSWORD="$3"
|
||||||
|
|
||||||
|
# Generate a random obfuscation key (64 hex chars)
|
||||||
|
OBFUSCATION_KEY=$(openssl rand -hex 32)
|
||||||
|
|
||||||
|
echo "Building with obfuscated credentials..."
|
||||||
|
echo "Server: $SERVER_URL"
|
||||||
|
echo "User: $USERNAME"
|
||||||
|
echo "Password: ${PASSWORD:0:10}..."
|
||||||
|
echo "Obfuscation Key: ${OBFUSCATION_KEY:0:16}..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Function to obfuscate a string using XOR
|
||||||
|
obfuscate_string() {
|
||||||
|
local input="$1"
|
||||||
|
local key="$2"
|
||||||
|
local result=""
|
||||||
|
local key_len=${#key}
|
||||||
|
|
||||||
|
for ((i=0; i<${#input}; i++)); do
|
||||||
|
# Get character from input and key
|
||||||
|
input_char="${input:$i:1}"
|
||||||
|
key_char="${key:$((i % key_len)):1}"
|
||||||
|
|
||||||
|
# Get ASCII values
|
||||||
|
input_val=$(printf '%d' "'$input_char")
|
||||||
|
key_val=$(printf '%d' "'$key_char")
|
||||||
|
|
||||||
|
# XOR and convert back to char
|
||||||
|
xor_val=$((input_val ^ key_val))
|
||||||
|
xor_char=$(printf "\\$(printf '%03o' $xor_val)")
|
||||||
|
|
||||||
|
result+="$xor_char"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Base64 encode the result for safe embedding
|
||||||
|
echo -n "$result" | base64 -w0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Obfuscate credentials
|
||||||
|
OBFUSCATED_SERVER=$(obfuscate_string "$SERVER_URL" "$OBFUSCATION_KEY")
|
||||||
|
OBFUSCATED_USER=$(obfuscate_string "$USERNAME" "$OBFUSCATION_KEY")
|
||||||
|
OBFUSCATED_PASSWORD=$(obfuscate_string "$PASSWORD" "$OBFUSCATION_KEY")
|
||||||
|
|
||||||
|
# Build function
|
||||||
|
build_tool() {
|
||||||
|
local tool_name="$1"
|
||||||
|
local tool_dir="$2"
|
||||||
|
|
||||||
|
echo "Building $tool_name..."
|
||||||
|
|
||||||
|
cd "$tool_dir"
|
||||||
|
|
||||||
|
# Build with obfuscated credentials and de-obfuscation key
|
||||||
|
go build \
|
||||||
|
-ldflags="-X main.ObfuscatedServer=$OBFUSCATED_SERVER \
|
||||||
|
-X main.ObfuscatedUser=$OBFUSCATED_USER \
|
||||||
|
-X main.ObfuscatedPassword=$OBFUSCATED_PASSWORD \
|
||||||
|
-X main.ObfuscationKey=$OBFUSCATION_KEY" \
|
||||||
|
-o ~/bin/"$tool_name" .
|
||||||
|
|
||||||
|
echo "✓ $tool_name built successfully"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Build nextcloud-client
|
||||||
|
build_tool "nextcloud-client" "$SCRIPT_DIR/tools/go/nextcloud-client"
|
||||||
|
|
||||||
|
# Build nextcloud-contacts
|
||||||
|
build_tool "nextcloud-contacts" "$SCRIPT_DIR/tools/go/nextcloud-contacts"
|
||||||
|
|
||||||
|
# Build nextcloud-calendar
|
||||||
|
build_tool "nextcloud-calendar" "$SCRIPT_DIR/tools/go/nextcloud-calendar"
|
||||||
|
|
||||||
|
# Build nextcloud-mail
|
||||||
|
build_tool "nextcloud-mail" "$SCRIPT_DIR/tools/go/nextcloud-mail"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "All tools built with obfuscated credentials!"
|
||||||
|
echo ""
|
||||||
|
echo "Obfuscation Details:"
|
||||||
|
echo " Method: XOR cipher with random 256-bit key"
|
||||||
|
echo " Key: $OBFUSCATION_KEY"
|
||||||
|
echo " Encoded: Base64 for safe Go embedding"
|
||||||
|
echo ""
|
||||||
|
echo "Security Notes:"
|
||||||
|
echo " ✓ Credentials are XOR encrypted with unique key"
|
||||||
|
echo " ✓ Key changes on every build"
|
||||||
|
echo " ✓ strings command shows only base64 gibberish"
|
||||||
|
echo " ✓ Runtime de-obfuscation happens in memory"
|
||||||
|
echo ""
|
||||||
|
echo "Binaries installed at:"
|
||||||
|
echo " ~/bin/nextcloud-client"
|
||||||
|
echo " ~/bin/nextcloud-contacts"
|
||||||
|
echo " ~/bin/nextcloud-calendar"
|
||||||
|
echo " ~/bin/nextcloud-mail"
|
||||||
|
echo ""
|
||||||
|
echo "⚠️ Security Level: Medium"
|
||||||
|
echo " This prevents casual extraction, but a determined attacker"
|
||||||
|
echo " with knowledge of the de-obfuscation code could reverse it."
|
||||||
|
echo " For production use, consider stronger encryption or environment"
|
||||||
|
echo " variables for sensitive credentials."
|
||||||
55
tools/go/obfuscation/obfuscation.go
Normal file
55
tools/go/obfuscation/obfuscation.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Build-time obfuscated credentials
|
||||||
|
var (
|
||||||
|
ObfuscatedServer string
|
||||||
|
ObfuscatedUser string
|
||||||
|
ObfuscatedPassword string
|
||||||
|
ObfuscationKey string
|
||||||
|
)
|
||||||
|
|
||||||
|
// DeobfuscateString reverses the XOR obfuscation applied at build time
|
||||||
|
func DeobfuscateString(obfuscatedBase64, key string) (string, error) {
|
||||||
|
// Decode base64
|
||||||
|
obfuscated, err := base64.StdEncoding.DecodeString(obfuscatedBase64)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to decode obfuscated string: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// XOR de-obfuscation
|
||||||
|
result := make([]byte, len(obfuscated))
|
||||||
|
keyBytes := []byte(key)
|
||||||
|
keyLen := len(keyBytes)
|
||||||
|
|
||||||
|
for i := 0; i < len(obfuscated); i++ {
|
||||||
|
result[i] = obfuscated[i] ^ keyBytes[i%keyLen]
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(result), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDeobfuscatedCredentials returns the actual credentials
|
||||||
|
// This is called at runtime to retrieve and de-obfuscate credentials
|
||||||
|
func GetDeobfuscatedCredentials() (server, user, password string, err error) {
|
||||||
|
server, err := DeobfuscateString(ObfuscatedServer, ObfuscationKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", fmt.Errorf("failed to de-obfuscate server: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := DeobfuscateString(ObfuscatedUser, ObfuscationKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", fmt.Errorf("failed to de-obfuscate user: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
password, err := DeobfuscateString(ObfuscatedPassword, ObfuscationKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", fmt.Errorf("failed to de-obfuscate password: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return server, user, password, nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user