Fountain Coach Gitowner-controlled · read only

swift-secretstore.git · README.md

swift-secretstore.git / README.md

revision 967af7d00a6478050bed4e86cad7aaadcc5a4ea6 · complete file

# swift-secretstore

`swift-secretstore` is a cross-platform secrets library that exposes a single `SecretStore`
protocol backed by platform-appropriate implementations. It ships with production-ready
stores for Apple platforms (Keychain), Linux desktops (Secret Service via `secret-tool`),
and headless Linux deployments (file-based keystore protected with ChaChaPoly and PBKDF2).

The project emphasises transparent, testable code: each backend is fully unit-tested and
keeps external interactions (like spawning `secret-tool`) behind small abstractions so
they can be mocked in tests.

## Features

- **Unified API** – A tiny `SecretStore` protocol with `store`, `retrieve`, and `delete`
  operations that work across backends.
- **Keychain backend** – `KeychainStore` integrates with the Apple Security framework when
  available and gracefully reports `unsupportedPlatform` on Linux builds.
- **Secret Service backend** – `SecretServiceStore` shells out to the `secret-tool`
  command, trimming trailing newlines from UTF-8 payloads by default while preserving
  binary data.
- **File keystore** – `FileKeystore` persists ChaChaPoly-encrypted secrets to disk using
  keys derived with PBKDF2-HMAC-SHA256 (provided by `swift-crypto`).
- **Process abstraction** – `ProcessRunner` centralises spawning external processes so the
  Linux backend can be tested without running real commands.
- **Extensive test suite** – Property-like checks cover success paths, error surfaces,
  tampering scenarios, and OS-specific behaviour. PBKDF2 is validated against known
  vectors.

## Installation

Add `swift-secretstore` to the dependency list in your `Package.swift`:

```swift
.package(url: "https://github.com/fountain-coach/swift-secretstore.git", from: "0.1.1")
```

Then depend on the `SecretStore` product from your target:

```swift
.target(
    name: "YourApp",
    dependencies: [
        .product(name: "SecretStore", package: "swift-secretstore")
    ]
)
```

## Usage

Select the backend that matches your deployment target and interact with it via the shared
protocol:

```swift
import SecretStore

let store: SecretStore
#if canImport(Security)
store = KeychainStore(service: "com.example.app")
#elseif os(Linux)
if ProcessInfo.processInfo.environment["USE_SECRET_SERVICE"] == "1" {
    store = SecretServiceStore(service: "com.example.app")
} else {
    let url = URL(fileURLWithPath: "/var/lib/example/keystore.json")
    store = try FileKeystore(storeURL: url, password: "change-me", iterations: 100_000)
}
#else
fatalError("No supported backend for this platform")
#endif

let secret = Data("super-secret-token".utf8)
try store.storeSecret(secret, for: "api-token")
let retrieved = try store.retrieveSecret(for: "api-token")
```

### Backend notes

- **KeychainStore**
  - Requires Apple platforms with the Security framework.
  - Allows configuring `service` and optional `accessibility` class (defaults to
    `kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly`).
- **SecretServiceStore**
  - Requires the `secret-tool` CLI (typically provided by `libsecret`) and access to a
    running Secret Service daemon.
  - Accepts a custom `ProcessRunning` implementation for dependency injection in tests.
  - Trims trailing `\n`/`\r\n` by default so secrets stored via the CLI round-trip cleanly.
- **FileKeystore**
  - Stores metadata and ciphertext in a JSON file alongside a random salt and iteration
    count.
  - Derives a 32-byte key from a user-supplied password using PBKDF2-HMAC-SHA256.
  - Uses ChaChaPoly for authenticated encryption and validates integrity before returning
    secrets.

## Platform support

| Backend             | macOS/iOS/tvOS/watchOS | Linux (Desktop) | Linux (Headless) |
|---------------------|------------------------|-----------------|------------------|
| KeychainStore       | ✅                     | ❌ (unsupported) | ❌                |
| SecretServiceStore  | ❌                     | ✅ (via secret-tool) | ⚠️ depends on D-Bus |
| FileKeystore        | ✅                     | ✅               | ✅                |

⚠️ `SecretServiceStore` assumes a running D-Bus session. For headless systems without D-Bus,
use `FileKeystore` instead.

## MCP server

`SecretStoreMCP` exposes the full SecretStore tool set over MCP stdio:

```bash
swift run SecretStoreMCP
```

Configuration is read from environment variables (or can be set later with
`secretstore.configure`):

- `SECRETSTORE_BACKEND` = `keychain`, `secret-service`, or `file`
- `SECRETSTORE_SERVICE` (keychain/secret-service)
- `SECRETSTORE_ACCESSIBILITY` (keychain)
- `SECRETSTORE_TRIM_NEWLINE` = `true`/`false` (secret-service)
- `SECRETSTORE_PATH` (file keystore)
- `SECRETSTORE_PASSWORD` (file keystore)
- `SECRETSTORE_ITERATIONS` (file keystore, default 100000)

## Development

Run the test suite locally before sending changes:

```bash
swift test --enable-code-coverage
```

The project strives to maintain complete test coverage; please ensure new behaviour is
accompanied by tests and keep coverage reports at 100% locally.

## Contributing

See [`AGENTS.md`](AGENTS.md) for details on the automation model, coverage expectations,
and security review requirements.

## License

Distributed under the terms of the [MIT License](LICENSE).