@oxy.so/core 7.28.0 → 7.29.0
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/dist/cjs/crypto/index.js +19 -1
- package/dist/cjs/crypto/keyManager.js +106 -106
- package/dist/cjs/crypto/keychainGroupMigration.js +353 -0
- package/dist/cjs/crypto/keychainItems.js +70 -0
- package/dist/cjs/crypto/organizationKeys.js +80 -72
- package/dist/cjs/crypto/secureStoreOptions.js +2 -0
- package/dist/cjs/session/index.js +11 -1
- package/dist/cjs/utils/keychainAccessGroups.js +248 -0
- package/dist/esm/crypto/index.js +3 -0
- package/dist/esm/crypto/keyManager.js +63 -63
- package/dist/esm/crypto/keychainGroupMigration.js +348 -0
- package/dist/esm/crypto/keychainItems.js +61 -0
- package/dist/esm/crypto/organizationKeys.js +54 -46
- package/dist/esm/crypto/secureStoreOptions.js +1 -0
- package/dist/esm/session/index.js +3 -0
- package/dist/esm/utils/keychainAccessGroups.js +231 -0
- package/dist/types/crypto/index.d.ts +4 -0
- package/dist/types/crypto/keyManager.d.ts +9 -1
- package/dist/types/crypto/keychainGroupMigration.d.ts +27 -0
- package/dist/types/crypto/keychainItems.d.ts +41 -0
- package/dist/types/crypto/organizationKeys.d.ts +11 -9
- package/dist/types/crypto/secureStoreOptions.d.ts +15 -0
- package/dist/types/session/index.d.ts +2 -0
- package/dist/types/utils/keychainAccessGroups.d.ts +104 -0
- package/package.json +2 -2
- package/src/crypto/index.ts +29 -0
- package/src/crypto/keyManager.ts +84 -99
- package/src/crypto/keychainGroupMigration.ts +440 -0
- package/src/crypto/keychainItems.ts +73 -0
- package/src/crypto/organizationKeys.ts +72 -57
- package/src/crypto/secureStoreOptions.ts +32 -0
- package/src/session/index.ts +11 -0
- package/src/utils/keychainAccessGroups.ts +308 -0
- package/src/types/expo-secure-store.d.ts +0 -21
package/dist/cjs/crypto/index.js
CHANGED
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
* common chunk, which is the cost the lazy loading exists to avoid.
|
|
13
13
|
*/
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.
|
|
15
|
+
exports.solveRegistrationPow = exports.deriveIdentityLinkCode = exports.signIdentityProof = exports.digestIdentityPayload = exports.authChallengeDigest = exports.signSocialReceiveDigest = exports.deriveScopedSeedFromKey = exports.MAX_SOCIAL_RECEIVE_INDEX = exports.SOCIAL_RECEIVE_CHAIN_CODE_KEY = exports.SCOPED_SEED_KDF_SALT = exports.deriveSharedSecret = exports.AEAD_NONCE_LENGTH = exports.AEAD_KEY_LENGTH = exports.decryptAead = exports.encryptAead = exports.hkdfSha256 = exports.BACKUP_KDF_ENCRYPTION_INFO = exports.RecoveryPhraseService = exports.SignatureService = exports.ensureKeychainGroupsMigrated = exports.groupedKeychainStore = exports.deleteThroughPlan = exports.readThroughPlan = exports.ownKeychainItemPlan = exports.keychainWriteOption = exports.keychainDeletePlan = exports.keychainReadPlan = exports.keychainAccessGroupValue = exports.resolveKeychainAccess = exports.mayHoldKeychainItem = exports.keychainGroupFor = exports.KeychainAccessUnavailableError = exports.KEYCHAIN_ACCESS_GROUPS = exports.isHealthyOrganizationKeyPair = exports.isOrganizationAccountId = exports.syncOrganizationKeys = exports.deleteOrganizationKey = exports.listOrganizationKeys = exports.getOrganizationRecoveryPhrase = exports.getOrganizationPrivateKey = exports.getOrganizationPublicKey = exports.storeOrganizationKey = exports.organizationKeyFromPhrase = exports.generateOrganizationKey = exports.updateIdentityMarker = exports.readIdentityMarker = exports.IdentityUnavailableError = exports.IdentityPersistError = exports.IdentityAlreadyExistsError = exports.KeyManager = void 0;
|
|
16
|
+
exports.verifySignature = exports.signMessage = exports.loadCommonsIdentityBridge = exports.canonicalize = exports.createContentHasher = void 0;
|
|
16
17
|
// MUST stay the first import: the shim has to exist before any module below
|
|
17
18
|
// evaluates or draws randomness (see `./polyfill`).
|
|
18
19
|
require("./polyfill");
|
|
@@ -37,6 +38,23 @@ Object.defineProperty(exports, "deleteOrganizationKey", { enumerable: true, get:
|
|
|
37
38
|
Object.defineProperty(exports, "syncOrganizationKeys", { enumerable: true, get: function () { return organizationKeys_1.syncOrganizationKeys; } });
|
|
38
39
|
Object.defineProperty(exports, "isOrganizationAccountId", { enumerable: true, get: function () { return organizationKeys_1.isOrganizationAccountId; } });
|
|
39
40
|
Object.defineProperty(exports, "isHealthyOrganizationKeyPair", { enumerable: true, get: function () { return organizationKeys_1.isHealthyOrganizationKeyPair; } });
|
|
41
|
+
// iOS keychain access groups (docs/identity/ios-keychain-access-groups.md, ADR 0062)
|
|
42
|
+
var keychainAccessGroups_1 = require("../utils/keychainAccessGroups");
|
|
43
|
+
Object.defineProperty(exports, "KEYCHAIN_ACCESS_GROUPS", { enumerable: true, get: function () { return keychainAccessGroups_1.KEYCHAIN_ACCESS_GROUPS; } });
|
|
44
|
+
Object.defineProperty(exports, "KeychainAccessUnavailableError", { enumerable: true, get: function () { return keychainAccessGroups_1.KeychainAccessUnavailableError; } });
|
|
45
|
+
Object.defineProperty(exports, "keychainGroupFor", { enumerable: true, get: function () { return keychainAccessGroups_1.keychainGroupFor; } });
|
|
46
|
+
Object.defineProperty(exports, "mayHoldKeychainItem", { enumerable: true, get: function () { return keychainAccessGroups_1.mayHoldKeychainItem; } });
|
|
47
|
+
Object.defineProperty(exports, "resolveKeychainAccess", { enumerable: true, get: function () { return keychainAccessGroups_1.resolveKeychainAccess; } });
|
|
48
|
+
Object.defineProperty(exports, "keychainAccessGroupValue", { enumerable: true, get: function () { return keychainAccessGroups_1.keychainAccessGroupValue; } });
|
|
49
|
+
Object.defineProperty(exports, "keychainReadPlan", { enumerable: true, get: function () { return keychainAccessGroups_1.keychainReadPlan; } });
|
|
50
|
+
Object.defineProperty(exports, "keychainDeletePlan", { enumerable: true, get: function () { return keychainAccessGroups_1.keychainDeletePlan; } });
|
|
51
|
+
Object.defineProperty(exports, "keychainWriteOption", { enumerable: true, get: function () { return keychainAccessGroups_1.keychainWriteOption; } });
|
|
52
|
+
Object.defineProperty(exports, "ownKeychainItemPlan", { enumerable: true, get: function () { return keychainAccessGroups_1.ownKeychainItemPlan; } });
|
|
53
|
+
Object.defineProperty(exports, "readThroughPlan", { enumerable: true, get: function () { return keychainAccessGroups_1.readThroughPlan; } });
|
|
54
|
+
Object.defineProperty(exports, "deleteThroughPlan", { enumerable: true, get: function () { return keychainAccessGroups_1.deleteThroughPlan; } });
|
|
55
|
+
Object.defineProperty(exports, "groupedKeychainStore", { enumerable: true, get: function () { return keychainAccessGroups_1.groupedKeychainStore; } });
|
|
56
|
+
var keychainGroupMigration_1 = require("./keychainGroupMigration");
|
|
57
|
+
Object.defineProperty(exports, "ensureKeychainGroupsMigrated", { enumerable: true, get: function () { return keychainGroupMigration_1.ensureKeychainGroupsMigrated; } });
|
|
40
58
|
var signatureService_1 = require("./signatureService");
|
|
41
59
|
Object.defineProperty(exports, "SignatureService", { enumerable: true, get: function () { return signatureService_1.SignatureService; } });
|
|
42
60
|
var recoveryPhrase_1 = require("./recoveryPhrase");
|
|
@@ -47,6 +47,9 @@ const logger_1 = require("../logger");
|
|
|
47
47
|
const identityDerivations_1 = require("./identityDerivations");
|
|
48
48
|
const identitySigner_1 = require("./identitySigner");
|
|
49
49
|
const organizationKeys_1 = require("./organizationKeys");
|
|
50
|
+
const keychainAccessGroups_1 = require("../utils/keychainAccessGroups");
|
|
51
|
+
const keychainGroupMigration_1 = require("./keychainGroupMigration");
|
|
52
|
+
const keychainItems_1 = require("./keychainItems");
|
|
50
53
|
const identityMarker_1 = require("./identityMarker");
|
|
51
54
|
const deviceBackup_1 = require("./deviceBackup");
|
|
52
55
|
/**
|
|
@@ -107,16 +110,6 @@ class IdentityUnavailableError extends Error {
|
|
|
107
110
|
}
|
|
108
111
|
}
|
|
109
112
|
exports.IdentityUnavailableError = IdentityUnavailableError;
|
|
110
|
-
const STORAGE_KEYS = {
|
|
111
|
-
PRIVATE_KEY: 'oxy_identity_private_key',
|
|
112
|
-
PUBLIC_KEY: 'oxy_identity_public_key',
|
|
113
|
-
BACKUP_PRIVATE_KEY: 'oxy_identity_backup_private_key',
|
|
114
|
-
BACKUP_PUBLIC_KEY: 'oxy_identity_backup_public_key',
|
|
115
|
-
BACKUP_TIMESTAMP: 'oxy_identity_backup_timestamp',
|
|
116
|
-
// The cross-app identity in the iOS keychain access group (iOS only).
|
|
117
|
-
SHARED_PRIVATE_KEY: 'oxy_shared_identity_private_key',
|
|
118
|
-
SHARED_PUBLIC_KEY: 'oxy_shared_identity_public_key',
|
|
119
|
-
};
|
|
120
113
|
/**
|
|
121
114
|
* v2 identity slot layout — blast-radius isolation.
|
|
122
115
|
*
|
|
@@ -132,15 +125,8 @@ const STORAGE_KEYS = {
|
|
|
132
125
|
* Migration from the legacy layout is lazy + verify-before-delete — see
|
|
133
126
|
* {@link KeyManager._runSlotMigration}.
|
|
134
127
|
*/
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const V2_STORAGE_KEYS = {
|
|
138
|
-
PRIVATE_KEY: 'oxy_identity_private_key_v2',
|
|
139
|
-
PUBLIC_KEY: 'oxy_identity_public_key_v2',
|
|
140
|
-
BACKUP_PRIVATE_KEY: 'oxy_identity_backup_private_key_v2',
|
|
141
|
-
BACKUP_PUBLIC_KEY: 'oxy_identity_backup_public_key_v2',
|
|
142
|
-
BACKUP_TIMESTAMP: 'oxy_identity_backup_timestamp_v2',
|
|
143
|
-
};
|
|
128
|
+
// The names (V2_PRIMARY_KEYCHAIN_SERVICE, V2_BACKUP_KEYCHAIN_SERVICE,
|
|
129
|
+
// V2_STORAGE_KEYS) live in `./keychainItems`, beside every other identity item.
|
|
144
130
|
/**
|
|
145
131
|
* Dedicated keychain slot for the recovery mnemonic (the 12-word phrase).
|
|
146
132
|
*
|
|
@@ -157,8 +143,7 @@ const V2_STORAGE_KEYS = {
|
|
|
157
143
|
* lives ONLY in this slot: it is never mirrored into the identity marker,
|
|
158
144
|
* {@link KeyManager.getIdentityStatus}, logs, or any exported bundle.
|
|
159
145
|
*/
|
|
160
|
-
|
|
161
|
-
const RECOVERY_MNEMONIC_STORAGE_KEY = 'oxy_identity_mnemonic_v1';
|
|
146
|
+
// RECOVERY_MNEMONIC_KEYCHAIN_SERVICE / RECOVERY_MNEMONIC_STORAGE_KEY: `./keychainItems`.
|
|
162
147
|
/**
|
|
163
148
|
* Advisory AsyncStorage fast-path flag: set once the v2 slots own the identity.
|
|
164
149
|
* Re-derivable (its loss just re-runs the cheap slot check), so it lives in
|
|
@@ -176,29 +161,23 @@ const SLOTS_MIGRATED_FLAG_KEY = 'oxy_identity_slots_migrated_v2';
|
|
|
176
161
|
*/
|
|
177
162
|
const ROTATION_PENDING_KEY = 'oxy_identity_rotation_pending_v1';
|
|
178
163
|
const V2_SLOT_LAYOUT = {
|
|
179
|
-
primaryService: V2_PRIMARY_KEYCHAIN_SERVICE,
|
|
180
|
-
primaryPrivateKeyName: V2_STORAGE_KEYS.PRIVATE_KEY,
|
|
181
|
-
primaryPublicKeyName: V2_STORAGE_KEYS.PUBLIC_KEY,
|
|
182
|
-
backupService: V2_BACKUP_KEYCHAIN_SERVICE,
|
|
183
|
-
backupPrivateKeyName: V2_STORAGE_KEYS.BACKUP_PRIVATE_KEY,
|
|
184
|
-
backupPublicKeyName: V2_STORAGE_KEYS.BACKUP_PUBLIC_KEY,
|
|
185
|
-
backupTimestampName: V2_STORAGE_KEYS.BACKUP_TIMESTAMP,
|
|
164
|
+
primaryService: keychainItems_1.V2_PRIMARY_KEYCHAIN_SERVICE,
|
|
165
|
+
primaryPrivateKeyName: keychainItems_1.V2_STORAGE_KEYS.PRIVATE_KEY,
|
|
166
|
+
primaryPublicKeyName: keychainItems_1.V2_STORAGE_KEYS.PUBLIC_KEY,
|
|
167
|
+
backupService: keychainItems_1.V2_BACKUP_KEYCHAIN_SERVICE,
|
|
168
|
+
backupPrivateKeyName: keychainItems_1.V2_STORAGE_KEYS.BACKUP_PRIVATE_KEY,
|
|
169
|
+
backupPublicKeyName: keychainItems_1.V2_STORAGE_KEYS.BACKUP_PUBLIC_KEY,
|
|
170
|
+
backupTimestampName: keychainItems_1.V2_STORAGE_KEYS.BACKUP_TIMESTAMP,
|
|
186
171
|
};
|
|
187
172
|
const LEGACY_SLOT_LAYOUT = {
|
|
188
173
|
primaryService: undefined,
|
|
189
|
-
primaryPrivateKeyName: STORAGE_KEYS.PRIVATE_KEY,
|
|
190
|
-
primaryPublicKeyName: STORAGE_KEYS.PUBLIC_KEY,
|
|
174
|
+
primaryPrivateKeyName: keychainItems_1.STORAGE_KEYS.PRIVATE_KEY,
|
|
175
|
+
primaryPublicKeyName: keychainItems_1.STORAGE_KEYS.PUBLIC_KEY,
|
|
191
176
|
backupService: undefined,
|
|
192
|
-
backupPrivateKeyName: STORAGE_KEYS.BACKUP_PRIVATE_KEY,
|
|
193
|
-
backupPublicKeyName: STORAGE_KEYS.BACKUP_PUBLIC_KEY,
|
|
194
|
-
backupTimestampName: STORAGE_KEYS.BACKUP_TIMESTAMP,
|
|
177
|
+
backupPrivateKeyName: keychainItems_1.STORAGE_KEYS.BACKUP_PRIVATE_KEY,
|
|
178
|
+
backupPublicKeyName: keychainItems_1.STORAGE_KEYS.BACKUP_PUBLIC_KEY,
|
|
179
|
+
backupTimestampName: keychainItems_1.STORAGE_KEYS.BACKUP_TIMESTAMP,
|
|
195
180
|
};
|
|
196
|
-
/**
|
|
197
|
-
* iOS Keychain Access Group for sharing identities across Oxy apps
|
|
198
|
-
* All Oxy apps must have this access group enabled in their entitlements
|
|
199
|
-
* Format: [Team ID].so.oxy.shared or group.so.oxy.shared
|
|
200
|
-
*/
|
|
201
|
-
const IOS_KEYCHAIN_GROUP = 'group.so.oxy.shared';
|
|
202
181
|
/**
|
|
203
182
|
* Initialize React Native specific modules
|
|
204
183
|
*
|
|
@@ -208,7 +187,7 @@ const IOS_KEYCHAIN_GROUP = 'group.so.oxy.shared';
|
|
|
208
187
|
* `expo-secure-store`; off RN it throws (and is never called because every
|
|
209
188
|
* caller is gated by `isWebPlatform()` / native-only paths).
|
|
210
189
|
*/
|
|
211
|
-
async function
|
|
190
|
+
async function loadRawSecureStore() {
|
|
212
191
|
try {
|
|
213
192
|
return await (0, protocol_1.loadSecureStore)();
|
|
214
193
|
}
|
|
@@ -217,6 +196,29 @@ async function initSecureStore() {
|
|
|
217
196
|
throw new Error(`Failed to load expo-secure-store: ${errorMessage}. Make sure expo-secure-store is installed and properly configured.`);
|
|
218
197
|
}
|
|
219
198
|
}
|
|
199
|
+
/**
|
|
200
|
+
* The secure store routed to the keychain group of `item`
|
|
201
|
+
* (`../utils/keychainAccessGroups`): on iOS reads walk the item's group then
|
|
202
|
+
* the pre-split shared group, writes carry the item's group and THROW in an app
|
|
203
|
+
* that may not hold it, and in Commons every access first awaits the
|
|
204
|
+
* access-group migration. Android and web are unchanged (`ungrouped`).
|
|
205
|
+
*
|
|
206
|
+
* Throws when the store cannot load or the keychain cannot say which groups
|
|
207
|
+
* this app holds; every caller already treats a throw here as "storage
|
|
208
|
+
* unavailable".
|
|
209
|
+
*/
|
|
210
|
+
async function keychainItemStore(item) {
|
|
211
|
+
const raw = await loadRawSecureStore();
|
|
212
|
+
const access = await (0, keychainAccessGroups_1.resolveKeychainAccess)();
|
|
213
|
+
if (access.mode === 'grouped' && access.role === 'holder') {
|
|
214
|
+
await (0, keychainGroupMigration_1.ensureKeychainGroupsMigrated)();
|
|
215
|
+
}
|
|
216
|
+
return (0, keychainAccessGroups_1.groupedKeychainStore)(raw, access, item);
|
|
217
|
+
}
|
|
218
|
+
/** The store for the identity slots (primary, backup, legacy, phrase): Commons only on iOS. */
|
|
219
|
+
function initSecureStore() {
|
|
220
|
+
return keychainItemStore('identity-secret');
|
|
221
|
+
}
|
|
220
222
|
/**
|
|
221
223
|
* Check if we're on web platform
|
|
222
224
|
* Identity storage is only available on native platforms (iOS/Android)
|
|
@@ -466,8 +468,8 @@ class KeyManager {
|
|
|
466
468
|
let v2Private;
|
|
467
469
|
let v2Public;
|
|
468
470
|
try {
|
|
469
|
-
v2Private = await store.getItemAsync(V2_STORAGE_KEYS.PRIVATE_KEY, KeyManager._slotOpts(V2_PRIMARY_KEYCHAIN_SERVICE));
|
|
470
|
-
v2Public = await store.getItemAsync(V2_STORAGE_KEYS.PUBLIC_KEY, KeyManager._slotOpts(V2_PRIMARY_KEYCHAIN_SERVICE));
|
|
471
|
+
v2Private = await store.getItemAsync(keychainItems_1.V2_STORAGE_KEYS.PRIVATE_KEY, KeyManager._slotOpts(keychainItems_1.V2_PRIMARY_KEYCHAIN_SERVICE));
|
|
472
|
+
v2Public = await store.getItemAsync(keychainItems_1.V2_STORAGE_KEYS.PUBLIC_KEY, KeyManager._slotOpts(keychainItems_1.V2_PRIMARY_KEYCHAIN_SERVICE));
|
|
471
473
|
}
|
|
472
474
|
catch (error) {
|
|
473
475
|
return { mode: 'deferred', cause: error };
|
|
@@ -490,8 +492,8 @@ class KeyManager {
|
|
|
490
492
|
let legacyPrivate;
|
|
491
493
|
let legacyPublic;
|
|
492
494
|
try {
|
|
493
|
-
legacyPrivate = await store.getItemAsync(STORAGE_KEYS.PRIVATE_KEY);
|
|
494
|
-
legacyPublic = await store.getItemAsync(STORAGE_KEYS.PUBLIC_KEY);
|
|
495
|
+
legacyPrivate = await store.getItemAsync(keychainItems_1.STORAGE_KEYS.PRIVATE_KEY);
|
|
496
|
+
legacyPublic = await store.getItemAsync(keychainItems_1.STORAGE_KEYS.PUBLIC_KEY);
|
|
495
497
|
}
|
|
496
498
|
catch (error) {
|
|
497
499
|
return { mode: 'deferred', cause: error };
|
|
@@ -505,10 +507,10 @@ class KeyManager {
|
|
|
505
507
|
const canonicalPrivate = KeyManager.canonicalPrivateKey(legacyPrivate);
|
|
506
508
|
const canonicalPublic = legacyPublic.toLowerCase();
|
|
507
509
|
try {
|
|
508
|
-
await store.setItemAsync(V2_STORAGE_KEYS.PUBLIC_KEY, canonicalPublic, KeyManager._slotOpts(V2_PRIMARY_KEYCHAIN_SERVICE));
|
|
509
|
-
await store.setItemAsync(V2_STORAGE_KEYS.PRIVATE_KEY, canonicalPrivate, KeyManager._privateWriteOpts(store, V2_PRIMARY_KEYCHAIN_SERVICE));
|
|
510
|
-
const readBackPrivate = await store.getItemAsync(V2_STORAGE_KEYS.PRIVATE_KEY, KeyManager._slotOpts(V2_PRIMARY_KEYCHAIN_SERVICE));
|
|
511
|
-
const readBackPublic = await store.getItemAsync(V2_STORAGE_KEYS.PUBLIC_KEY, KeyManager._slotOpts(V2_PRIMARY_KEYCHAIN_SERVICE));
|
|
510
|
+
await store.setItemAsync(keychainItems_1.V2_STORAGE_KEYS.PUBLIC_KEY, canonicalPublic, KeyManager._slotOpts(keychainItems_1.V2_PRIMARY_KEYCHAIN_SERVICE));
|
|
511
|
+
await store.setItemAsync(keychainItems_1.V2_STORAGE_KEYS.PRIVATE_KEY, canonicalPrivate, KeyManager._privateWriteOpts(store, keychainItems_1.V2_PRIMARY_KEYCHAIN_SERVICE));
|
|
512
|
+
const readBackPrivate = await store.getItemAsync(keychainItems_1.V2_STORAGE_KEYS.PRIVATE_KEY, KeyManager._slotOpts(keychainItems_1.V2_PRIMARY_KEYCHAIN_SERVICE));
|
|
513
|
+
const readBackPublic = await store.getItemAsync(keychainItems_1.V2_STORAGE_KEYS.PUBLIC_KEY, KeyManager._slotOpts(keychainItems_1.V2_PRIMARY_KEYCHAIN_SERVICE));
|
|
512
514
|
const verified = readBackPrivate?.toLowerCase() === canonicalPrivate &&
|
|
513
515
|
readBackPublic?.toLowerCase() === canonicalPublic &&
|
|
514
516
|
KeyManager._isHealthyPair(readBackPrivate, readBackPublic);
|
|
@@ -543,8 +545,8 @@ class KeyManager {
|
|
|
543
545
|
let backupPrivate = null;
|
|
544
546
|
let backupPublic = null;
|
|
545
547
|
try {
|
|
546
|
-
backupPrivate = await store.getItemAsync(STORAGE_KEYS.BACKUP_PRIVATE_KEY);
|
|
547
|
-
backupPublic = await store.getItemAsync(STORAGE_KEYS.BACKUP_PUBLIC_KEY);
|
|
548
|
+
backupPrivate = await store.getItemAsync(keychainItems_1.STORAGE_KEYS.BACKUP_PRIVATE_KEY);
|
|
549
|
+
backupPublic = await store.getItemAsync(keychainItems_1.STORAGE_KEYS.BACKUP_PUBLIC_KEY);
|
|
548
550
|
}
|
|
549
551
|
catch (error) {
|
|
550
552
|
if ((0, logger_1.isDev)()) {
|
|
@@ -563,9 +565,9 @@ class KeyManager {
|
|
|
563
565
|
seedPrivate = primaryPrivate;
|
|
564
566
|
seedPublic = primaryPublic;
|
|
565
567
|
}
|
|
566
|
-
await store.setItemAsync(V2_STORAGE_KEYS.BACKUP_PUBLIC_KEY, seedPublic, KeyManager._slotOpts(V2_BACKUP_KEYCHAIN_SERVICE));
|
|
567
|
-
await store.setItemAsync(V2_STORAGE_KEYS.BACKUP_PRIVATE_KEY, seedPrivate, KeyManager._privateWriteOpts(store, V2_BACKUP_KEYCHAIN_SERVICE));
|
|
568
|
-
await store.setItemAsync(V2_STORAGE_KEYS.BACKUP_TIMESTAMP, Date.now().toString(), KeyManager._slotOpts(V2_BACKUP_KEYCHAIN_SERVICE));
|
|
568
|
+
await store.setItemAsync(keychainItems_1.V2_STORAGE_KEYS.BACKUP_PUBLIC_KEY, seedPublic, KeyManager._slotOpts(keychainItems_1.V2_BACKUP_KEYCHAIN_SERVICE));
|
|
569
|
+
await store.setItemAsync(keychainItems_1.V2_STORAGE_KEYS.BACKUP_PRIVATE_KEY, seedPrivate, KeyManager._privateWriteOpts(store, keychainItems_1.V2_BACKUP_KEYCHAIN_SERVICE));
|
|
570
|
+
await store.setItemAsync(keychainItems_1.V2_STORAGE_KEYS.BACKUP_TIMESTAMP, Date.now().toString(), KeyManager._slotOpts(keychainItems_1.V2_BACKUP_KEYCHAIN_SERVICE));
|
|
569
571
|
}
|
|
570
572
|
catch (error) {
|
|
571
573
|
logger_1.logger.warn('Failed to migrate identity backup slot to v2 (non-fatal)', { component: 'KeyManager' }, error);
|
|
@@ -583,23 +585,23 @@ class KeyManager {
|
|
|
583
585
|
}
|
|
584
586
|
}
|
|
585
587
|
static async _bestEffortDeleteV2Primary(store) {
|
|
586
|
-
await KeyManager._bestEffortDelete(store, V2_STORAGE_KEYS.PRIVATE_KEY, V2_PRIMARY_KEYCHAIN_SERVICE);
|
|
587
|
-
await KeyManager._bestEffortDelete(store, V2_STORAGE_KEYS.PUBLIC_KEY, V2_PRIMARY_KEYCHAIN_SERVICE);
|
|
588
|
+
await KeyManager._bestEffortDelete(store, keychainItems_1.V2_STORAGE_KEYS.PRIVATE_KEY, keychainItems_1.V2_PRIMARY_KEYCHAIN_SERVICE);
|
|
589
|
+
await KeyManager._bestEffortDelete(store, keychainItems_1.V2_STORAGE_KEYS.PUBLIC_KEY, keychainItems_1.V2_PRIMARY_KEYCHAIN_SERVICE);
|
|
588
590
|
}
|
|
589
591
|
static async _bestEffortDeleteLegacyPrimaryAndBackup(store) {
|
|
590
|
-
await KeyManager._bestEffortDelete(store, STORAGE_KEYS.PRIVATE_KEY);
|
|
591
|
-
await KeyManager._bestEffortDelete(store, STORAGE_KEYS.PUBLIC_KEY);
|
|
592
|
-
await KeyManager._bestEffortDelete(store, STORAGE_KEYS.BACKUP_PRIVATE_KEY);
|
|
593
|
-
await KeyManager._bestEffortDelete(store, STORAGE_KEYS.BACKUP_PUBLIC_KEY);
|
|
594
|
-
await KeyManager._bestEffortDelete(store, STORAGE_KEYS.BACKUP_TIMESTAMP);
|
|
592
|
+
await KeyManager._bestEffortDelete(store, keychainItems_1.STORAGE_KEYS.PRIVATE_KEY);
|
|
593
|
+
await KeyManager._bestEffortDelete(store, keychainItems_1.STORAGE_KEYS.PUBLIC_KEY);
|
|
594
|
+
await KeyManager._bestEffortDelete(store, keychainItems_1.STORAGE_KEYS.BACKUP_PRIVATE_KEY);
|
|
595
|
+
await KeyManager._bestEffortDelete(store, keychainItems_1.STORAGE_KEYS.BACKUP_PUBLIC_KEY);
|
|
596
|
+
await KeyManager._bestEffortDelete(store, keychainItems_1.STORAGE_KEYS.BACKUP_TIMESTAMP);
|
|
595
597
|
}
|
|
596
598
|
static async _bestEffortDeleteBackupsAllGenerations(store) {
|
|
597
|
-
await KeyManager._bestEffortDelete(store, V2_STORAGE_KEYS.BACKUP_PRIVATE_KEY, V2_BACKUP_KEYCHAIN_SERVICE);
|
|
598
|
-
await KeyManager._bestEffortDelete(store, V2_STORAGE_KEYS.BACKUP_PUBLIC_KEY, V2_BACKUP_KEYCHAIN_SERVICE);
|
|
599
|
-
await KeyManager._bestEffortDelete(store, V2_STORAGE_KEYS.BACKUP_TIMESTAMP, V2_BACKUP_KEYCHAIN_SERVICE);
|
|
600
|
-
await KeyManager._bestEffortDelete(store, STORAGE_KEYS.BACKUP_PRIVATE_KEY);
|
|
601
|
-
await KeyManager._bestEffortDelete(store, STORAGE_KEYS.BACKUP_PUBLIC_KEY);
|
|
602
|
-
await KeyManager._bestEffortDelete(store, STORAGE_KEYS.BACKUP_TIMESTAMP);
|
|
599
|
+
await KeyManager._bestEffortDelete(store, keychainItems_1.V2_STORAGE_KEYS.BACKUP_PRIVATE_KEY, keychainItems_1.V2_BACKUP_KEYCHAIN_SERVICE);
|
|
600
|
+
await KeyManager._bestEffortDelete(store, keychainItems_1.V2_STORAGE_KEYS.BACKUP_PUBLIC_KEY, keychainItems_1.V2_BACKUP_KEYCHAIN_SERVICE);
|
|
601
|
+
await KeyManager._bestEffortDelete(store, keychainItems_1.V2_STORAGE_KEYS.BACKUP_TIMESTAMP, keychainItems_1.V2_BACKUP_KEYCHAIN_SERVICE);
|
|
602
|
+
await KeyManager._bestEffortDelete(store, keychainItems_1.STORAGE_KEYS.BACKUP_PRIVATE_KEY);
|
|
603
|
+
await KeyManager._bestEffortDelete(store, keychainItems_1.STORAGE_KEYS.BACKUP_PUBLIC_KEY);
|
|
604
|
+
await KeyManager._bestEffortDelete(store, keychainItems_1.STORAGE_KEYS.BACKUP_TIMESTAMP);
|
|
603
605
|
}
|
|
604
606
|
/**
|
|
605
607
|
* Clear the iOS keychain-group copy of the identity (force-delete only) so a
|
|
@@ -607,12 +609,12 @@ class KeyManager {
|
|
|
607
609
|
* rung. Best-effort — the shared slot is a redundant convenience copy. The
|
|
608
610
|
* Android signer store is cleared on every delete, in {@link deleteIdentity}.
|
|
609
611
|
*/
|
|
610
|
-
static async _clearSharedSlot(
|
|
612
|
+
static async _clearSharedSlot(_identityStore) {
|
|
611
613
|
try {
|
|
612
614
|
if ((0, platform_1.isIOS)()) {
|
|
613
|
-
|
|
614
|
-
await
|
|
615
|
-
await
|
|
615
|
+
// The shared slot lives in two groups of its own, not the identity slots'.
|
|
616
|
+
await (await keychainItemStore('spending-key')).deleteItemAsync(keychainItems_1.STORAGE_KEYS.SHARED_PRIVATE_KEY);
|
|
617
|
+
await (await keychainItemStore('shared')).deleteItemAsync(keychainItems_1.STORAGE_KEYS.SHARED_PUBLIC_KEY);
|
|
616
618
|
}
|
|
617
619
|
KeyManager.invalidateSharedCache();
|
|
618
620
|
}
|
|
@@ -679,6 +681,20 @@ class KeyManager {
|
|
|
679
681
|
KeyManager.cachedSharedPublicKey = null;
|
|
680
682
|
KeyManager.cachedHasSharedIdentity = null;
|
|
681
683
|
}
|
|
684
|
+
/**
|
|
685
|
+
* iOS: write the personal key's cross-app slot — the private key to the
|
|
686
|
+
* wallet group (Commons and the Peable wallet), the public key to the shared
|
|
687
|
+
* group (every Oxy app). Throws in any app that may not hold the private key.
|
|
688
|
+
* @internal
|
|
689
|
+
*/
|
|
690
|
+
static async _writeIosSharedSlot(privateKey, publicKey) {
|
|
691
|
+
const privateStore = await keychainItemStore('spending-key');
|
|
692
|
+
const privateOpts = {
|
|
693
|
+
keychainAccessible: privateStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
|
|
694
|
+
};
|
|
695
|
+
await privateStore.setItemAsync(keychainItems_1.STORAGE_KEYS.SHARED_PRIVATE_KEY, privateKey, privateOpts);
|
|
696
|
+
await (await keychainItemStore('shared')).setItemAsync(keychainItems_1.STORAGE_KEYS.SHARED_PUBLIC_KEY, publicKey, {});
|
|
697
|
+
}
|
|
682
698
|
/**
|
|
683
699
|
* Lowercase and pad to canonical 64-hex-char form.
|
|
684
700
|
*
|
|
@@ -714,8 +730,10 @@ class KeyManager {
|
|
|
714
730
|
// ==================== SHARED IDENTITY METHODS ====================
|
|
715
731
|
// One Oxy identity across every Oxy app on a device.
|
|
716
732
|
//
|
|
717
|
-
// iOS:
|
|
718
|
-
//
|
|
733
|
+
// iOS: keychain access groups (docs/identity/ios-keychain-access-groups.md).
|
|
734
|
+
// The private key is in `<TEAM>.so.oxy.wallet` (Commons and the Peable
|
|
735
|
+
// wallet only), the public key in `<TEAM>.group.so.oxy.shared`, which every
|
|
736
|
+
// Oxy app reads. Every other app signs in through Commons (ADR 0029).
|
|
719
737
|
// Android: every app has its own UID, so nothing is shared through storage.
|
|
720
738
|
// Commons is the ONLY holder of the private key. It keeps the identity
|
|
721
739
|
// signer store (`setIdentitySignerStore`) for its identity host, and every
|
|
@@ -740,21 +758,11 @@ class KeyManager {
|
|
|
740
758
|
}
|
|
741
759
|
const { privateKey, publicKey } = await KeyManager.generateKeyPair();
|
|
742
760
|
if ((0, platform_1.isIOS)()) {
|
|
743
|
-
const store = await initSecureStore();
|
|
744
|
-
// Note: keychainAccessGroup requires Keychain Sharing capability in Xcode
|
|
745
761
|
try {
|
|
746
|
-
|
|
747
|
-
keychainAccessible: store.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
|
|
748
|
-
keychainAccessGroup: IOS_KEYCHAIN_GROUP, // Enables sharing across apps
|
|
749
|
-
};
|
|
750
|
-
await store.setItemAsync(STORAGE_KEYS.SHARED_PRIVATE_KEY, privateKey, privateOpts);
|
|
751
|
-
const publicOpts = {
|
|
752
|
-
keychainAccessGroup: IOS_KEYCHAIN_GROUP,
|
|
753
|
-
};
|
|
754
|
-
await store.setItemAsync(STORAGE_KEYS.SHARED_PUBLIC_KEY, publicKey, publicOpts);
|
|
762
|
+
await KeyManager._writeIosSharedSlot(privateKey, publicKey);
|
|
755
763
|
}
|
|
756
764
|
catch (error) {
|
|
757
|
-
throw new Error(`Failed to create shared identity on iOS.
|
|
765
|
+
throw new Error(`Failed to create shared identity on iOS. Only Commons may write it, with the keychain access groups of docs/identity/ios-keychain-access-groups.md. Error: ${error}`);
|
|
758
766
|
}
|
|
759
767
|
}
|
|
760
768
|
else if ((0, platform_1.isAndroid)()) {
|
|
@@ -794,9 +802,8 @@ class KeyManager {
|
|
|
794
802
|
try {
|
|
795
803
|
let publicKey = null;
|
|
796
804
|
if ((0, platform_1.isIOS)()) {
|
|
797
|
-
const store = await
|
|
798
|
-
|
|
799
|
-
publicKey = await store.getItemAsync(STORAGE_KEYS.SHARED_PUBLIC_KEY, opts);
|
|
805
|
+
const store = await keychainItemStore('shared');
|
|
806
|
+
publicKey = await store.getItemAsync(keychainItems_1.STORAGE_KEYS.SHARED_PUBLIC_KEY, {});
|
|
800
807
|
}
|
|
801
808
|
else if ((0, platform_1.isAndroid)()) {
|
|
802
809
|
const signer = KeyManager.identitySignerStore;
|
|
@@ -836,9 +843,10 @@ class KeyManager {
|
|
|
836
843
|
return null;
|
|
837
844
|
}
|
|
838
845
|
try {
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
846
|
+
// The wallet group: Commons and the Peable wallet. Every other app's plan
|
|
847
|
+
// is empty, so this is `null` there without touching the keychain.
|
|
848
|
+
const store = await keychainItemStore('spending-key');
|
|
849
|
+
return await store.getItemAsync(keychainItems_1.STORAGE_KEYS.SHARED_PRIVATE_KEY, {});
|
|
842
850
|
}
|
|
843
851
|
catch (error) {
|
|
844
852
|
if ((0, logger_1.isDev)()) {
|
|
@@ -932,16 +940,7 @@ class KeyManager {
|
|
|
932
940
|
const canonicalPrivate = KeyManager.canonicalPrivateKey(privateKey);
|
|
933
941
|
const publicKey = (0, secp256k1_1.deriveSecp256k1PublicKey)(canonicalPrivate);
|
|
934
942
|
if ((0, platform_1.isIOS)()) {
|
|
935
|
-
|
|
936
|
-
const privateOpts = {
|
|
937
|
-
keychainAccessible: store.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
|
|
938
|
-
keychainAccessGroup: IOS_KEYCHAIN_GROUP,
|
|
939
|
-
};
|
|
940
|
-
await store.setItemAsync(STORAGE_KEYS.SHARED_PRIVATE_KEY, canonicalPrivate, privateOpts);
|
|
941
|
-
const publicOpts = {
|
|
942
|
-
keychainAccessGroup: IOS_KEYCHAIN_GROUP,
|
|
943
|
-
};
|
|
944
|
-
await store.setItemAsync(STORAGE_KEYS.SHARED_PUBLIC_KEY, publicKey, publicOpts);
|
|
943
|
+
await KeyManager._writeIosSharedSlot(canonicalPrivate, publicKey);
|
|
945
944
|
}
|
|
946
945
|
else if ((0, platform_1.isAndroid)()) {
|
|
947
946
|
await KeyManager._writeIdentitySigner(canonicalPrivate, publicKey);
|
|
@@ -1778,7 +1777,7 @@ class KeyManager {
|
|
|
1778
1777
|
}
|
|
1779
1778
|
try {
|
|
1780
1779
|
const store = await initSecureStore();
|
|
1781
|
-
await store.setItemAsync(RECOVERY_MNEMONIC_STORAGE_KEY, mnemonic, KeyManager._privateWriteOpts(store, RECOVERY_MNEMONIC_KEYCHAIN_SERVICE));
|
|
1780
|
+
await store.setItemAsync(keychainItems_1.RECOVERY_MNEMONIC_STORAGE_KEY, mnemonic, KeyManager._privateWriteOpts(store, keychainItems_1.RECOVERY_MNEMONIC_KEYCHAIN_SERVICE));
|
|
1782
1781
|
}
|
|
1783
1782
|
catch (error) {
|
|
1784
1783
|
if ((0, logger_1.isDev)()) {
|
|
@@ -1813,7 +1812,7 @@ class KeyManager {
|
|
|
1813
1812
|
}
|
|
1814
1813
|
try {
|
|
1815
1814
|
const store = await initSecureStore();
|
|
1816
|
-
return await store.getItemAsync(RECOVERY_MNEMONIC_STORAGE_KEY, KeyManager._slotOpts(RECOVERY_MNEMONIC_KEYCHAIN_SERVICE));
|
|
1815
|
+
return await store.getItemAsync(keychainItems_1.RECOVERY_MNEMONIC_STORAGE_KEY, KeyManager._slotOpts(keychainItems_1.RECOVERY_MNEMONIC_KEYCHAIN_SERVICE));
|
|
1817
1816
|
}
|
|
1818
1817
|
catch (error) {
|
|
1819
1818
|
if ((0, logger_1.isDev)()) {
|
|
@@ -1859,7 +1858,7 @@ class KeyManager {
|
|
|
1859
1858
|
return; // Identity storage is only available on native platforms
|
|
1860
1859
|
}
|
|
1861
1860
|
const store = await initSecureStore();
|
|
1862
|
-
await KeyManager._bestEffortDelete(store, RECOVERY_MNEMONIC_STORAGE_KEY, RECOVERY_MNEMONIC_KEYCHAIN_SERVICE);
|
|
1861
|
+
await KeyManager._bestEffortDelete(store, keychainItems_1.RECOVERY_MNEMONIC_STORAGE_KEY, keychainItems_1.RECOVERY_MNEMONIC_KEYCHAIN_SERVICE);
|
|
1863
1862
|
}
|
|
1864
1863
|
/**
|
|
1865
1864
|
* Check if a complete, parseable identity exists on this device.
|
|
@@ -2078,8 +2077,8 @@ class KeyManager {
|
|
|
2078
2077
|
await store.deleteItemAsync(layout.primaryPublicKeyName, readOpts);
|
|
2079
2078
|
}
|
|
2080
2079
|
await KeyManager._bestEffortDeleteV2Primary(store);
|
|
2081
|
-
await KeyManager._bestEffortDelete(store, STORAGE_KEYS.PRIVATE_KEY);
|
|
2082
|
-
await KeyManager._bestEffortDelete(store, STORAGE_KEYS.PUBLIC_KEY);
|
|
2080
|
+
await KeyManager._bestEffortDelete(store, keychainItems_1.STORAGE_KEYS.PRIVATE_KEY);
|
|
2081
|
+
await KeyManager._bestEffortDelete(store, keychainItems_1.STORAGE_KEYS.PUBLIC_KEY);
|
|
2083
2082
|
// Always drop the stored recovery mnemonic — it is scoped to the identity
|
|
2084
2083
|
// being deleted, so a leftover would let Settings reveal a stale phrase for
|
|
2085
2084
|
// an identity that no longer exists (or a DIFFERENT one after re-onboarding).
|
|
@@ -2606,7 +2605,8 @@ class KeyManager {
|
|
|
2606
2605
|
* `HDKey.fromMasterSeed` and never touches the identity key.
|
|
2607
2606
|
*
|
|
2608
2607
|
* Key source (native only):
|
|
2609
|
-
* - iOS: the shared
|
|
2608
|
+
* - iOS: the shared slot's private key in the wallet keychain group (Commons
|
|
2609
|
+
* and the Peable wallet only; `null` in every other app), then this device's
|
|
2610
2610
|
* primary identity.
|
|
2611
2611
|
* - Android: Commons (the one app with a signer store) uses its own primary
|
|
2612
2612
|
* identity; every other app asks Commons over IPC and never reads a local
|