Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Vault

A lightweight Swift package for storing, reading, and deleting strings in the iOS and macOS keychain.

Requirements

  • Swift 6.0+
  • iOS 13+ / macOS 10.15+

Installation

Add Vault to your project via Swift Package Manager:

dependencies: [
    .package(url: "https://github.com/SparrowTek/Vault.git", from: "1.0.0")
]

Usage

Vault is an actor, so all calls require await.

Configure once, use everywhere

import Vault

let vault = Vault(configuration: KeychainConfiguration(
    serviceName: "com.myapp.service",
    accessGroup: nil,
    accountName: "userToken"
))

// Save
try await vault.save("my-secret-value")

// Read
let value = try await vault.read()

// Delete
try await vault.delete()

Configure later

let vault = Vault()

let config = KeychainConfiguration(
    serviceName: "com.myapp.service",
    accessGroup: nil,
    accountName: "userToken"
)

await vault.configure(config)

try await vault.save("my-secret-value")

Protection class and access control

KeychainConfiguration controls how the keychain protects an item. accessibility picks the kSecAttrAccessible* class (default .whenUnlocked, the system default); accessControl adds a user-presence gate (biometry / passcode) that the keychain evaluates on every read.

For key material — wallet seeds, private keys — use a ThisDeviceOnly class so the item is excluded from backups and can never be restored onto a different device:

let seedConfig = KeychainConfiguration(
    serviceName: "com.myapp.wallet",
    accessGroup: nil,
    accountName: "seed",
    accessibility: .afterFirstUnlockThisDeviceOnly
)

// Require Face ID / Touch ID (or passcode) on every read:
let gatedConfig = KeychainConfiguration(
    serviceName: "com.myapp.wallet",
    accessGroup: nil,
    accountName: "seed",
    accessibility: .whenUnlockedThisDeviceOnly,
    accessControl: .userPresence
)

Saving over an existing item migrates its protection class to the configured one (except for access-controlled items, which would require re-authentication to modify).

Service-wide operations

accounts() lists every account stored under the configuration's service, and deleteAll() wipes them all — useful for a complete "sign out" / "reset" flow:

let stored = try await vault.accounts()
try await vault.deleteAll()

Override configuration per call

You can pass a different configuration to any individual call, which takes precedence over the stored configuration:

let otherConfig = KeychainConfiguration(
    serviceName: "com.myapp.service",
    accessGroup: nil,
    accountName: "refreshToken"
)

try await vault.save("another-secret", configuration: otherConfig)
let token = try await vault.read(configuration: otherConfig)

Error Handling

All errors are thrown as VaultError:

do {
    let value = try await vault.read()
} catch let error as VaultError {
    switch error {
    case .noConfiguration:
        // No KeychainConfiguration was provided
    case .itemNotFound:
        // No matching item exists in the keychain
    case .unexpectedData:
        // The keychain returned data in an unexpected format
    case .encodingFailed:
        // The value could not be encoded as UTF-8
    case .accessControlCreationFailed:
        // The requested access control could not be constructed
    case .keychainFailure(let status):
        // A keychain operation failed with the given OSStatus
    }
}

About

A simple Swift Package to handle saving data to the iOS keychain

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages