@tokamakdev/plugin-secure-storage 0.1.0-beta.107 → 0.1.0-beta.114
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -1
- package/android/Bindings.kt +356 -0
- package/android/SecureStoragePlugin.kt +27 -21
- package/android/bindings.ts +1166 -0
- package/apple/SecureStoragePlugin.swift +27 -16
- package/package.json +4 -3
- package/src/index.ts +4 -4
- package/tokamak-plugin.json +45 -4
|
@@ -18,6 +18,10 @@ final class TokamakSecureStoragePlugin: TokamakPlugin {
|
|
|
18
18
|
self.host = host
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
func executor(for method: String) -> DispatchQueue {
|
|
22
|
+
["get", "replace", "set"].contains(method) ? .main : queue
|
|
23
|
+
}
|
|
24
|
+
|
|
21
25
|
#if os(macOS)
|
|
22
26
|
/// macOS grants the data protection keychain only to apps whose provisioning
|
|
23
27
|
/// profile provides an application identifier.
|
|
@@ -32,16 +36,19 @@ final class TokamakSecureStoragePlugin: TokamakPlugin {
|
|
|
32
36
|
method: String,
|
|
33
37
|
arguments: Any,
|
|
34
38
|
reply: @escaping TokamakPluginReply
|
|
35
|
-
) {
|
|
39
|
+
) -> (() -> Void)? {
|
|
36
40
|
let arguments = arguments as? [String: Any] ?? [:]
|
|
37
41
|
let prompting =
|
|
38
|
-
|
|
42
|
+
["get", "replace", "set"].contains(method) && isUnlocked
|
|
43
|
+
? Result { () throws(TokamakPluginError) in try host.requireUI() } : nil
|
|
44
|
+
let authentication = LAContext()
|
|
39
45
|
queue.async {
|
|
40
46
|
reply(
|
|
41
47
|
Result { () throws(TokamakPluginError) in
|
|
42
|
-
try self.perform(method, arguments, prompting: prompting)
|
|
48
|
+
try self.perform(method, arguments, prompting: prompting, authentication: authentication)
|
|
43
49
|
})
|
|
44
50
|
}
|
|
51
|
+
return { authentication.invalidate() }
|
|
45
52
|
}
|
|
46
53
|
|
|
47
54
|
/// Whether the device's protected data is available, which it is not while
|
|
@@ -60,7 +67,8 @@ final class TokamakSecureStoragePlugin: TokamakPlugin {
|
|
|
60
67
|
private func perform(
|
|
61
68
|
_ method: String,
|
|
62
69
|
_ arguments: [String: Any],
|
|
63
|
-
prompting: Result<Void, TokamakPluginError
|
|
70
|
+
prompting: Result<Void, TokamakPluginError>?,
|
|
71
|
+
authentication: LAContext
|
|
64
72
|
) throws(TokamakPluginError) -> Any? {
|
|
65
73
|
#if os(macOS)
|
|
66
74
|
// Unentitled reads report errSecItemNotFound rather than a missing entitlement.
|
|
@@ -80,7 +88,8 @@ final class TokamakSecureStoragePlugin: TokamakPlugin {
|
|
|
80
88
|
group: group,
|
|
81
89
|
stored(arguments),
|
|
82
90
|
prompt: optionalString(arguments, "prompt"),
|
|
83
|
-
prompting: prompting
|
|
91
|
+
prompting: prompting,
|
|
92
|
+
authentication: authentication
|
|
84
93
|
))
|
|
85
94
|
return nil
|
|
86
95
|
case "set":
|
|
@@ -91,7 +100,8 @@ final class TokamakSecureStoragePlugin: TokamakPlugin {
|
|
|
91
100
|
group: group,
|
|
92
101
|
stored,
|
|
93
102
|
prompt: optionalString(arguments, "prompt"),
|
|
94
|
-
prompting: prompting
|
|
103
|
+
prompting: prompting,
|
|
104
|
+
authentication: authentication
|
|
95
105
|
)
|
|
96
106
|
try check(status == errSecItemNotFound ? add(name, group: group, stored) : status)
|
|
97
107
|
return nil
|
|
@@ -100,7 +110,8 @@ final class TokamakSecureStoragePlugin: TokamakPlugin {
|
|
|
100
110
|
requiredString(arguments, "name"),
|
|
101
111
|
group: group,
|
|
102
112
|
prompt: optionalString(arguments, "prompt"),
|
|
103
|
-
prompting: prompting
|
|
113
|
+
prompting: prompting,
|
|
114
|
+
authentication: authentication
|
|
104
115
|
)
|
|
105
116
|
case "delete":
|
|
106
117
|
let name = try requiredString(arguments, "name")
|
|
@@ -141,14 +152,15 @@ final class TokamakSecureStoragePlugin: TokamakPlugin {
|
|
|
141
152
|
group: String?,
|
|
142
153
|
_ stored: [CFString: Any],
|
|
143
154
|
prompt: String?,
|
|
144
|
-
prompting: Result<Void, TokamakPluginError
|
|
155
|
+
prompting: Result<Void, TokamakPluginError>?,
|
|
156
|
+
authentication: LAContext
|
|
145
157
|
) throws(TokamakPluginError) -> OSStatus {
|
|
146
158
|
var item = query(name, group: group)
|
|
147
159
|
item[kSecUseAuthenticationContext] = Self.contextWithoutUI()
|
|
148
160
|
let status = SecItemUpdate(item as CFDictionary, stored as CFDictionary)
|
|
149
161
|
guard status == errSecInteractionNotAllowed, let prompting else { return status }
|
|
150
162
|
try prompting.get()
|
|
151
|
-
item[kSecUseAuthenticationContext] = Self.context(prompt: prompt)
|
|
163
|
+
item[kSecUseAuthenticationContext] = Self.context(authentication, prompt: prompt)
|
|
152
164
|
return SecItemUpdate(item as CFDictionary, stored as CFDictionary)
|
|
153
165
|
}
|
|
154
166
|
|
|
@@ -158,7 +170,8 @@ final class TokamakSecureStoragePlugin: TokamakPlugin {
|
|
|
158
170
|
_ name: String,
|
|
159
171
|
group: String?,
|
|
160
172
|
prompt: String?,
|
|
161
|
-
prompting: Result<Void, TokamakPluginError
|
|
173
|
+
prompting: Result<Void, TokamakPluginError>?,
|
|
174
|
+
authentication: LAContext
|
|
162
175
|
) throws(TokamakPluginError) -> String {
|
|
163
176
|
var item = query(name, group: group)
|
|
164
177
|
item[kSecReturnData] = true
|
|
@@ -167,7 +180,7 @@ final class TokamakSecureStoragePlugin: TokamakPlugin {
|
|
|
167
180
|
var status = SecItemCopyMatching(item as CFDictionary, &data)
|
|
168
181
|
if status == errSecInteractionNotAllowed, let prompting {
|
|
169
182
|
try prompting.get()
|
|
170
|
-
item[kSecUseAuthenticationContext] = Self.context(prompt: prompt)
|
|
183
|
+
item[kSecUseAuthenticationContext] = Self.context(authentication, prompt: prompt)
|
|
171
184
|
status = SecItemCopyMatching(item as CFDictionary, &data)
|
|
172
185
|
// The first read found the value, so it exists but the system can no longer decrypt it.
|
|
173
186
|
if status == errSecItemNotFound {
|
|
@@ -192,11 +205,9 @@ final class TokamakSecureStoragePlugin: TokamakPlugin {
|
|
|
192
205
|
return context
|
|
193
206
|
}
|
|
194
207
|
|
|
195
|
-
///
|
|
196
|
-
private static func context(prompt: String?) -> LAContext
|
|
197
|
-
|
|
198
|
-
let context = LAContext()
|
|
199
|
-
context.localizedReason = prompt
|
|
208
|
+
/// The invocation's authentication context, with the requested prompt.
|
|
209
|
+
private static func context(_ context: LAContext, prompt: String?) -> LAContext {
|
|
210
|
+
if let prompt, !prompt.isEmpty { context.localizedReason = prompt }
|
|
200
211
|
return context
|
|
201
212
|
}
|
|
202
213
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokamakdev/plugin-secure-storage",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.114",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/mantty/tokamak.git",
|
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
},
|
|
9
9
|
"type": "module",
|
|
10
10
|
"exports": {
|
|
11
|
-
".": "./src/index.ts"
|
|
11
|
+
".": "./src/index.ts",
|
|
12
|
+
"./android": "./android/bindings.ts"
|
|
12
13
|
},
|
|
13
14
|
"peerDependencies": {
|
|
14
|
-
"@tokamakdev/plugin": "0.1.0-beta.
|
|
15
|
+
"@tokamakdev/plugin": "0.1.0-beta.114"
|
|
15
16
|
},
|
|
16
17
|
"files": [
|
|
17
18
|
"android",
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Plugin } from "@tokamakdev/plugin";
|
|
1
|
+
import { Plugin, type TokamakContext } from "@tokamakdev/plugin";
|
|
2
2
|
|
|
3
3
|
/** When a stored value can be read. */
|
|
4
4
|
export type Readable = "whenUnlocked" | "afterFirstUnlock";
|
|
@@ -45,8 +45,8 @@ export interface DeleteOptions {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
class SecureStorage extends Plugin {
|
|
48
|
-
constructor() {
|
|
49
|
-
super("secure-storage");
|
|
48
|
+
constructor(ctx: TokamakContext) {
|
|
49
|
+
super(ctx, "secure-storage");
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
/** Stores a new value; rejects with `DuplicateItemError` when `name` has one. */
|
|
@@ -91,4 +91,4 @@ function stored(name: string, value: string, options: CreateOptions) {
|
|
|
91
91
|
};
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
export
|
|
94
|
+
export function secureStorage(ctx: TokamakContext): SecureStorage { return new SecureStorage(ctx); }
|
package/tokamak-plugin.json
CHANGED
|
@@ -1,20 +1,61 @@
|
|
|
1
1
|
{
|
|
2
|
-
"schemaVersion":
|
|
2
|
+
"schemaVersion": 2,
|
|
3
3
|
"id": "secure-storage",
|
|
4
|
+
"bindings": {
|
|
5
|
+
"android": {
|
|
6
|
+
"sdk": 35,
|
|
7
|
+
"modules": [
|
|
8
|
+
"android.security.keystore",
|
|
9
|
+
"java.security.KeyStore",
|
|
10
|
+
"javax.crypto.Cipher",
|
|
11
|
+
"javax.crypto.KeyGenerator"
|
|
12
|
+
]
|
|
13
|
+
}
|
|
14
|
+
},
|
|
4
15
|
"platforms": {
|
|
5
16
|
"macos": {
|
|
6
17
|
"class": "TokamakSecureStoragePlugin",
|
|
7
|
-
"sources": [
|
|
18
|
+
"sources": [
|
|
19
|
+
"apple/SecureStoragePlugin.swift"
|
|
20
|
+
]
|
|
8
21
|
},
|
|
9
22
|
"ios": {
|
|
10
23
|
"class": "TokamakSecureStoragePlugin",
|
|
11
|
-
"sources": [
|
|
24
|
+
"sources": [
|
|
25
|
+
"apple/SecureStoragePlugin.swift"
|
|
26
|
+
],
|
|
12
27
|
"plist": "apple/Info.plist"
|
|
13
28
|
},
|
|
14
29
|
"android": {
|
|
15
30
|
"class": "com.tokamak.plugins.securestorage.TokamakSecureStoragePlugin",
|
|
16
|
-
"sources": [
|
|
31
|
+
"sources": [
|
|
32
|
+
"android/SecureStoragePlugin.kt"
|
|
33
|
+
],
|
|
17
34
|
"manifest": "android/AndroidManifest.xml"
|
|
18
35
|
}
|
|
36
|
+
},
|
|
37
|
+
"api": {
|
|
38
|
+
"module": "src/index.ts",
|
|
39
|
+
"export": "secureStorage"
|
|
40
|
+
},
|
|
41
|
+
"operations": {
|
|
42
|
+
"get": {
|
|
43
|
+
"foreground": [
|
|
44
|
+
"ios",
|
|
45
|
+
"android"
|
|
46
|
+
]
|
|
47
|
+
},
|
|
48
|
+
"replace": {
|
|
49
|
+
"foreground": [
|
|
50
|
+
"ios",
|
|
51
|
+
"android"
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
"set": {
|
|
55
|
+
"foreground": [
|
|
56
|
+
"ios",
|
|
57
|
+
"android"
|
|
58
|
+
]
|
|
59
|
+
}
|
|
19
60
|
}
|
|
20
61
|
}
|