Skip to content

Latest commit

 

History

History
457 lines (350 loc) · 12.3 KB

File metadata and controls

457 lines (350 loc) · 12.3 KB

CLAUDE.md - Project Context

Project Overview

swift-ejson is a Swift implementation of Shopify's EJSON library for managing encrypted secrets in source control. The library module is named EJSONKit following Apple-style framework naming conventions.

Reference Implementation: https://github.com/Shopify/ejson

Before Starting a New Feature

Remember to bump the version! Before starting work on a new feature or bug fix, update the VERSION file at the project root with the new version number. This ensures the release workflow can create a new release when the changes are merged.

Purpose

Provide a native Swift library that can:

  • Encrypt and decrypt individual values using NaCl cryptography
  • Process JSON files with selective encryption of sensitive values
  • Maintain format compatibility with the Go EJSON implementation
  • Enable secure secret management in Swift-based projects

Core Architecture

Module Name: EJSONKit

import EJSONKit

// Key generation
let keyPair: EJSONKit.KeyPair = try EJSONKit.generateKeyPair()

// Value encryption/decryption
let encrypted = try EJSONKit.encrypt("secret", publicKey: key)
let decrypted = try EJSONKit.decrypt(encrypted, privateKey: key)

// File operations
let manager = EJSONKit.Manager()
try manager.encryptFile(at: path, publicKey: key)

Core Components

  1. Key Management

    • Generate keypairs (32-byte Curve25519 keys)
    • Format: 64-character hex strings
    • Functions: generateKeyPair() -> (publicKey: String, privateKey: String)
  2. Value Encryption/Decryption

    • Encrypt individual string values
    • Format: EJ[1:ephemeral_pk:nonce:ciphertext]
    • Base64 encoding for binary data
    • NaCl Box encryption (Curve25519 + Salsa20 + Poly1305)
  3. Recursive JSON Processing

    • Walk JSON tree and encrypt string values
    • Preserve structure (objects, arrays, types)
    • Special handling for _public_key field (never encrypt)
    • Support nested structures
  4. File Operations

    • Read/write EJSON files
    • Validate format
    • Extract public key from files

EJSON Format

File Structure

{
  "_public_key": "63ccf05a9492e68e12eeb1c705888aebdcc0080af7e594fc402beb24cce9d14f",
  "database_password": "EJ[1:yF4JKMR4RUJY0hcxKYKDOg==:Yw6rqhvtLx7Kdc1hGtxqPBnx9bxk8kAzTCGNZPwVU5c=:ZCaH/xShYQ==]",
  "nested": {
    "secret": "EJ[1:x7F9KMTR5RUJZ1ida9KDPh==:Zw7sqiwuMy8Ldc2iHuyqQCoy0cyl9lB0UDHOaQxWV6d=:ADcI/yTiZR==]"
  }
}

Encrypted Value Format

EJ[1:ephemeral_pk:nonce:ciphertext]

  • 1: Version number
  • ephemeral_pk: Base64-encoded ephemeral public key (32 bytes)
  • nonce: Base64-encoded nonce (24 bytes)
  • ciphertext: Base64-encoded encrypted data

Dependencies

swift-sodium (v0.9.1+)

Development Guidelines

Compatibility Requirements

  1. Must be compatible with Go EJSON

    • Encrypted files must decrypt with Go ejson
    • Files encrypted by Go ejson must decrypt in Swift
    • Identical format for all components
  2. Format Compliance

    • Exact EJ[1:...] format
    • Base64 encoding (standard, not URL-safe)
    • 64-character hex for public keys
    • Preserve _public_key field

Testing Strategy

  1. Start with single-value encryption/decryption
  2. Create test files with Go ejson, decrypt in Swift
  3. Create test files in Swift, decrypt with Go ejson
  4. Test recursive JSON processing
  5. Test edge cases: unicode, nested structures, arrays

Error Handling

  • Invalid key formats
  • Corrupted encrypted data
  • Format mismatches
  • Missing _public_key field
  • Type preservation errors

Key Design Decisions

Naming

  • Repository: swift-ejson (follows Swift package conventions)
  • Module: EJSONKit (Apple-style framework naming)
  • Rationale: Professional feel, clean imports, unlikely conflicts

API Design

public struct EJSON {
    private let sodium = Sodium()

    public func generateKeyPair() throws -> (publicKey: String, privateKey: String)
    public func encrypt(_ plaintext: String, publicKey: String) throws -> String
    public func decrypt(_ ciphertext: String, privateKey: String) throws -> String
    public func encryptFile(at path: String, publicKey: String) throws
    public func decryptFile(at path: String, privateKey: String) throws -> [String: Any]
}

Implementation Approach

  1. Start small: Basic encrypt/decrypt of single values
  2. Verify compatibility: Cross-test with Go ejson
  3. Build incrementally: Add recursive JSON support
  4. Test extensively: Use Go ejson as reference

Main Challenges

  1. Format compatibility - Must match Go implementation exactly
  2. Recursive encryption - Walk JSON tree correctly
  3. Type preservation - Distinguish encrypted vs. plain values
  4. Error handling - Graceful handling of invalid data
  5. Public key handling - Never encrypt _public_key field

References

Project Status

Implementation Complete - Branch: claude/implement-ejson-swift-assessment-01GMRgL5Ckidt55G3FKEUSic

The library has been fully implemented with:

  • Complete EJSON encryption/decryption functionality
  • Comprehensive test suite (25+ test cases)
  • Full documentation and examples
  • Format compatibility with Go EJSON

Installing Swift for Testing

This project requires Swift 6.2 or later.

Prerequisites

Linux only: Install libsodium-dev (required for EJSONKit cryptography on Linux):

# Ubuntu/Debian
sudo apt-get update
sudo apt-get install -y libsodium-dev

macOS: No additional dependencies needed. The swift-sodium package includes bundled libsodium (Clibsodium.xcframework) for Apple platforms.

Option 1: Direct Download from swift.org (Recommended)

This is the most reliable method for getting Swift 6.2.1.

For Ubuntu 22.04/24.04 (x86_64):

# Download Swift 6.2.1
cd /tmp
wget https://download.swift.org/swift-6.2.1-release/ubuntu2204/swift-6.2.1-RELEASE/swift-6.2.1-RELEASE-ubuntu22.04.tar.gz

# Extract
tar xzf swift-6.2.1-RELEASE-ubuntu22.04.tar.gz

# Option A: Install system-wide (requires sudo)
sudo mv swift-6.2.1-RELEASE-ubuntu22.04 /opt/swift
echo 'export PATH=/opt/swift/usr/bin:$PATH' >> ~/.bashrc

# Option B: Install to user directory (no sudo required)
mkdir -p ~/.local/swift
mv swift-6.2.1-RELEASE-ubuntu22.04 ~/.local/swift/6.2.1
echo 'export PATH=$HOME/.local/swift/6.2.1/usr/bin:$PATH' >> ~/.bashrc

# Reload shell configuration
source ~/.bashrc

# Verify installation
swift --version
# Should output: Swift version 6.2.1 (swift-6.2.1-RELEASE)

For macOS:

# Install Xcode Command Line Tools (includes Swift 6.2+)
xcode-select --install

# Or download the latest Swift toolchain from:
# https://www.swift.org/install/macos/
# and follow the installer instructions

Option 2: Using Swiftly

Swiftly is the official Swift toolchain manager for Linux. Note that it requires network access and may have connectivity issues in some environments.

# Download and install Swiftly
curl -O https://download.swift.org/swiftly/linux/swiftly-$(uname -m).tar.gz
tar zxf swiftly-$(uname -m).tar.gz
echo "Y" | ./swiftly init --skip-install --quiet-shell-followup

# Load Swiftly environment
source "${SWIFTLY_HOME_DIR:-$HOME/.local/share/swiftly}/env.sh"
hash -r

# Install Swift 6.2 (requires network access)
swiftly install latest

# Verify installation
swift --version

Option 3: Using swiftenv

# Install swiftenv
git clone https://github.com/kylef/swiftenv.git ~/.swiftenv

# Add to PATH
echo 'export SWIFTENV_ROOT="$HOME/.swiftenv"' >> ~/.bashrc
echo 'export PATH="$SWIFTENV_ROOT/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(swiftenv init -)"' >> ~/.bashrc
source ~/.bashrc

# Install Swift 6.2
swiftenv install 6.2
swiftenv global 6.2

Additional System Dependencies

Swift requires several system libraries on Linux:

Ubuntu/Debian:

sudo apt-get update
sudo apt-get install -y \
  binutils \
  git \
  gnupg2 \
  libc6-dev \
  libcurl4-openssl-dev \
  libedit2 \
  libpython3.8 \
  libsqlite3-0 \
  libxml2-dev \
  libz3-dev \
  pkg-config \
  tzdata \
  unzip \
  zlib1g-dev

Building and Testing the Library

Once Swift and libsodium are installed:

Build the library

cd /home/user/swift-ejson
swift build

Expected output:

Fetching https://github.com/jedisct1/swift-sodium.git
Fetched https://github.com/jedisct1/swift-sodium.git from cache
Computing version for https://github.com/jedisct1/swift-sodium.git
Computed https://github.com/jedisct1/swift-sodium.git at 0.9.1
Building for debugging...
[2/3] Emitting module EJSONKit
warning: Using a system installation of libsodium - This is unsupported.
[3/3] Compiling EJSONKit EJSON.swift
Build complete! (2.00s)

Note: On Linux, you may see a warning "Using a system installation of libsodium - This is unsupported." This is expected and safe to ignore. The library will work correctly with the system-installed libsodium. On macOS, the bundled Clibsodium.xcframework is used automatically with no warnings.

Run the test suite

swift test

Expected output:

Test Suite 'All tests' started at...
Test Suite 'EJSONKitTests' started at...
[... 30 test cases ...]
Test Suite 'All tests' passed at...
	 Executed 30 tests, with 0 failures (0 unexpected) in 0.294 seconds

All 30 tests should pass, including:

  • Key generation and validation tests
  • Encryption/decryption tests
  • JSON processing tests
  • File operations tests
  • Unicode and special character handling
  • Performance benchmarks

Run specific tests

# Run only basic encryption tests
swift test --filter testBasicEncryptDecrypt

# Run with verbose output
swift test -v

Build in release mode

swift build -c release

Generate Xcode project (macOS only)

swift package generate-xcodeproj
open swift-ejson.xcodeproj

Cross-Compatibility Testing with Go EJSON

To verify format compatibility with the Go implementation:

1. Install Go EJSON

go install github.com/Shopify/ejson/cmd/ejson@latest

2. Generate keys with Go EJSON

ejson keygen
# Output:
# Public Key:  abc123...
# Private Key: def456...

3. Create a test file and encrypt with Go

cat > test.json <<EOF
{
  "_public_key": "YOUR_PUBLIC_KEY_HERE",
  "password": "secret123",
  "api_key": "my_api_key"
}
EOF

ejson encrypt test.json

4. Decrypt with Swift EJSONKit

import EJSONKit

let ejson = EJSON()
let secrets = try ejson.decryptFile(
    at: "test.json",
    privateKey: "YOUR_PRIVATE_KEY_HERE"
)
print(secrets["password"]) // Should print: secret123

5. Encrypt with Swift and decrypt with Go

let keyPair = try EJSON.generateKeyPair()
// Create file with keyPair.publicKey
try ejson.encryptFile(at: "swift_encrypted.json", publicKey: keyPair.publicKey)
# Use the private key from Swift
ejson decrypt swift_encrypted.json

Troubleshooting

Build Errors

Error: error: failed to clone https://github.com/jedisct1/swift-sodium.git

  • Solution: Check internet connectivity and GitHub access

Error: cannot find 'sodium_init' in scope

  • Solution: The dependency didn't download correctly. Try:
    rm -rf .build
    swift package resolve
    swift build

Test Failures

Error: sodiumInitializationFailed

  • Solution: Libsodium isn't properly linked. Reinstall swift-sodium dependency.

Error: Network-related test failures

  • Some tests create temporary files. Ensure /tmp is writable:
    ls -ld /tmp
    chmod 1777 /tmp  # If needed

Performance Benchmarks

Run performance tests:

swift test --filter testPerformance

Typical results on modern hardware:

  • Key generation: ~0.5ms per keypair
  • Value encryption: ~0.1ms per value
  • Value decryption: ~0.1ms per value
  • File encryption (1KB): ~5ms
  • File decryption (1KB): ~5ms

Integration Target

This library will be integrated with Xproject's secret management system (EJSONService) to provide standardized EJSON encryption/decryption capabilities.