Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ permissions:
jobs:
call-workflow:
uses: skiptools/actions/.github/workflows/skip-framework.yml@v1
with:
runs-on: "['macos-15-intel', 'ubuntu-24.04']"
32 changes: 32 additions & 0 deletions Sources/SkipKeychain/SkipKeychain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
#if !SKIP_BRIDGE
import Foundation
#if !SKIP
// Platforms without the Security framework (e.g. the Linux host pass of
// `skip export`) compile a throwing stub so the package can build there.
#if canImport(Security)
import Security
#endif
#else
import android.content.Context
import android.content.SharedPreferences
Expand All @@ -23,6 +27,7 @@ public struct Keychain {
/// Retrieve a value.
public func string(forKey key: String) throws -> String? {
#if !SKIP
#if canImport(Security)
guard let data = try data(forKey: key) else {
return nil
}
Expand All @@ -31,6 +36,9 @@ public struct Keychain {
}
return string
#else
throw KeychainError(message: "Keychain is not supported on this platform")
#endif
#else
do {
let prefs = try initializePreferences()
return prefs.getString(key, nil)
Expand Down Expand Up @@ -67,11 +75,15 @@ public struct Keychain {
/// Store a key value pair.
public func set(_ string: String, forKey key: String, access: KeychainAccess = .unlocked) throws {
#if !SKIP
#if canImport(Security)
guard let data = string.data(using: .utf8) else {
throw KeychainError(invalidValue: true)
}
try set(data, forKey: key, access: access)
#else
throw KeychainError(message: "Keychain is not supported on this platform")
#endif
#else
do {
let prefs = try initializePreferences()
let editor = prefs.edit()
Expand Down Expand Up @@ -99,6 +111,7 @@ public struct Keychain {
}

#if !SKIP
#if canImport(Security)
private func data(forKey key: String) throws -> Data? {
lock.lock()
defer { lock.unlock() }
Expand Down Expand Up @@ -135,6 +148,7 @@ public struct Keychain {
throw KeychainError(code: code)
}
}
#endif
#else
private var preferences: SharedPreferences?

Expand Down Expand Up @@ -167,10 +181,14 @@ public struct Keychain {
/// Delete the value stored for the given key.
public func removeValue(forKey key: String) throws {
#if !SKIP
#if canImport(Security)
lock.lock()
defer { lock.unlock() }
try removeHoldingLock(forKey: key)
#else
throw KeychainError(message: "Keychain is not supported on this platform")
#endif
#else
do {
let prefs = try initializePreferences()
let editor = prefs.edit()
Expand All @@ -183,6 +201,7 @@ public struct Keychain {
}

#if !SKIP
#if canImport(Security)
private func removeHoldingLock(forKey key: String) throws {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
Expand All @@ -194,10 +213,12 @@ public struct Keychain {
}
}
#endif
#endif

/// Return the set of all stored keys.
public func keys() throws -> [String] {
#if !SKIP
#if canImport(Security)
lock.lock()
defer { lock.unlock() }

Expand All @@ -219,6 +240,9 @@ public struct Keychain {
}
return dicts.compactMap { $0[kSecAttrAccount as String] as? String }
#else
throw KeychainError(message: "Keychain is not supported on this platform")
#endif
#else
do {
return Array(initializePreferences().getAll().keys)
} catch {
Expand All @@ -230,6 +254,7 @@ public struct Keychain {
/// Remove all stored key value pairs.
public func removeAll() throws {
#if !SKIP
#if canImport(Security)
lock.lock()
defer { lock.unlock() }

Expand All @@ -239,6 +264,9 @@ public struct Keychain {
throw KeychainError(code: code)
}
#else
throw KeychainError(message: "Keychain is not supported on this platform")
#endif
#else
do {
let editor = initializePreferences().edit()
editor.clear()
Expand All @@ -264,6 +292,7 @@ public enum KeychainAccess {
case passcodeSetThisDeviceOnly

#if !SKIP
#if canImport(Security)
var value: String {
switch self {
case .unlocked:
Expand All @@ -279,6 +308,7 @@ public enum KeychainAccess {
}
}
#endif
#endif
}

/// Thrown on keychain error.
Expand All @@ -294,10 +324,12 @@ public struct KeychainError: Error, CustomStringConvertible {
}

#if !SKIP
#if canImport(Security)
init(code: OSStatus) {
self.message = SecCopyErrorMessageString(code, nil) as? String ?? "Unknown error"
}
#endif
#endif

public var description: String {
return message
Expand Down
Loading