Ok, we found the unique constraint to query SecIdentity. Although kSecAttrPublicKeyHash doesn't work, but kSecAttrApplicationLabel with public key hash gets exactly SecIdentity that we want.
Post
Replies
Boosts
Views
Activity
Hi Quinn,
We have tried query with uniqueness constraint to get exactly matched identity, but no luck. Since we have cached public key hash, so we retrieved certificate data and use it to query as kSecClass set to kSecClassIdentity, kSecMatchItemList set to cert data array, kSecReturnRef set to true. It returns the identity, but not the one matched. We also tried to query with kSecClass set to kSecClassIdentity, kSecAttrPublicKeyHash set to public key hash, kSecReturnRef set to true, it returns -25291. We used to query exact identity with kSecValuePersistentRef in the other project. But for this project, we only cached public key hash, so not sure how to get matched identity. To figure out what we can use, we queried identities with attributes and got returned dictionary list. Below is the attributes dictionary for the identity that we want to get:
UUID = "F970C2F0-2FB8-419B-924E-141BD593D700";
accc = "<SecAccessControlRef: dk>";
agrp = "JBF29L28EJ.com.example.keychainTest";
asen = 0;
atag = "";
bsiz = 256;
cdat = "2024-03-21 19:13:11 +0000";
cenc = 3;
certdata = {length = 1190, bytes = 0x308204a2 30820428 a0030201 02021466 ... fa248377 6a0d670c };
class = idnt;
crtr = 0;
ctyp = 3;
decr = 1;
drve = 1;
edat = "2001-01-01 00:00:00 +0000";
encr = 0;
esiz = 256;
extr = 1;
issr = {length = 73, bytes = 0x31243022 06035504 030c1b50 524f4420 ... 0a0c0543 6973636f };
kcls = 1;
klbl = {length = 20, bytes = 0x0d08990422f219443228a0149948b76477070a7a};
labl = "urn::deviceid:0ace7954238d7747";
mdat = "2024-03-21 19:13:11 +0000";
modi = 1;
musr = {length = 0, bytes = 0x};
next = 0;
pdmn = dk;
perm = 1;
pkhh = {length = 20, bytes = 0x0d08990422f219443228a0149948b76477070a7a};
priv = 1;
sdat = "2001-01-01 00:00:00 +0000";
sens = 0;
sha1 = {length = 20, bytes = 0xe10d689ebac24f5f90e64d94f83eef53e76281bb};
sign = 1;
skid = {length = 20, bytes = 0x0d08990422f219443228a0149948b76477070a7a};
slnr = {length = 20, bytes = 0x660cd07546ddf3e095a1b0c1bbee50efe44eddb4};
snrc = 0;
subj = {length = 55, bytes = 0x31353033 06035504 030c2c75 726e3a63 ... 32333864 37373437 };
sync = 0;
tomb = 0;
type = 73;
unwp = 1;
"v_Ref" = "<SecIdentityRef: 0x283484f80>";
vrfy = 0;
vyrc = 0;
wrap = 0;
The labl = "urn::deviceid:0ace7954238d7747" is not unique for certificate, it is only unique for the device. Looks like the unique constraints are certdata and pkhh/public key hash, but we tried both with no luck. Any idea?
Thanks,
Ying
Hi Quinn,
You are right, it is related to previously generated key. There is legacy key that was generated without setting any access flag, so by default it can only be accessible when device is locked. When we enumerate all identities on locked device, it caused the problem. After delete all previous identities, the problem goes away.
Thank you so much for the help!
Ying
Hi Quinn,
I read your post Investigate hard-to-reproduce keychain problems
In our code we log the OSStatus when SecItem calls fail, that tells the error -25308 is from SecItemCopyMathing that try to query all SecIdentity with their attributes. The logs shows below including our extension (NSExtension) logs.
Our extension is notification service extension, when push notification arrives, the extension needs to access keychain to get identity. The reason we query all identities because we need to get identity that certificate matched the cached public key hash. On MacOS, we actually query kSecClassCertificate with kSecAttrPublicKeyHash to get exactly matched cert and then use SecIdentityCreateWithCertificate to get identity. But SecIdentityCreateWithCertificate API only available on MacOS and we don't know how to get SecIdentity with SecCertificate on iOS. So we made code to query all SecIdentity with their attributes and then compare the public key hash to find the matched one.
Do you think the failure is due to query attributes? I ask this because sysdiag logs -25308 with "cannot decrypt metadata key".
It is also very confused, because the working device always work, and non-working device always failed with -25308. Plus, if we keep the non-working device unlocked, the problem goes away. So it tells the problem is due to keychain access control when device is locked. But even using kSecAttrAccessibleAlwaysThisDeviceOnly during import won't solve the problem.
default NSExtension 2024-03-14 10:49:13.843947 -0400 2222 0x110111 System Keychain Always Supported set via feature flag to disabled
default NSExtension 2024-03-14 10:49:13.843955 -0400 2222 0x110111 [0x11de3a710] activating connection: mach=true listener=false peer=false name=com.apple.securityd
default NSExtension 2024-03-14 10:49:13.844064 -0400 2222 0x110111 Adding securityd connection to pool, total now 1
default securityd 2024-03-14 10:49:13.844114 -0400 129 0x110091 [0x7d8a0c1c0] activating connection: mach=false listener=false peer=true name=com.apple.securityd.peer.0x7d8a0c1c0
default securityd 2024-03-14 10:49:13.844996 -0400 129 0x110115 SecDbKeychainItemV7: cannot decrypt metadata key because the keychain is locked (-25308)
default securityd 2024-03-14 10:49:13.845036 -0400 129 0x110091 NSExtension[2222]/1#7 LF=0 copy_matching Error Domain=NSOSStatusErrorDomain Code=-25308 "ks_crypt: e00002e2 failed to 'od' item (class 6, bag: 0) Access to item attempted while keychain is locked." UserInfo={numberOfErrorsDeep=0, NSDescription=ks_crypt: e00002e2 failed to 'od' item (class 6, bag: 0) Access to item attempted while keychain is locked.}
error NSExtension 2024-03-14 10:49:13.846383 -0400 2222 0x110111 E/ KeyChainKeyStore.swift:1580 getSecIdentity() SecItemCopyMatching error: User interaction is not allowed.
Hi Quinn,
On the devices affected by this problem, it always happen. I don’t have access to the devices but i can try to borrow them and i can send Testflight build to the user.
i actually already asked user to try a build with change to use kSecAttrAccessibleAlwaysThisDeviceOnly when generating key pair & importing cert, but the problem still exist.
Thanks @endecotp, it is good to know. We found the root cause is flag bFileLockAcquired is being reset somewhere else, so it cause timer starts even after successfully locked. Then fileLock.lock called twice.
I found more info in sysdiagnose:
[app<com.example.myApp(B7AB7300-8A17-4C71-88BC-BA3D55AF6666)>:1522] check if suspended process is holding locks
[app<com.example.myApp(B7AB7300-8A17-4C71-88BC-BA3D55AF6666)>:1522] suspended with locked system files:
/var/mobile/Containers/Shared/AppGroup/A4A5C452-2871-4AEF-8737-D0436AE7F9F4/.lock
[app<com.example.myApp(B7AB7300-8A17-4C71-88BC-BA3D55AF6666)>:1522] locked files not in allowed directories: /var/mobile/Containers/Data/Application/C9C92475-09F8-445B-B244-F2EF514DC72F/tmp
/var/mobile/Containers/Data/Application/C9C92475-09F8-445B-B244-F2EF514DC72F
[app<com.example.myApp(B7AB7300-8A17-4C71-88BC-BA3D55AF6666)>:1522] Terminating with context: <RBSTerminateContext| domain:15 code:0xDEAD10CC explanation:[app<com.example.myApp(B7AB7300-8A17-4C71-88BC-BA3D55AF6666)>:1522] was suspended with locked system files:
/var/mobile/Containers/Shared/AppGroup/A4A5C452-2871-4AEF-8737-D0436AE7F9F4/.lock
not in allowed directories:
/var/mobile/Containers/Data/Application/C9C92475-09F8-445B-B244-F2EF514DC72F/tmp
/var/mobile/Containers/Data/Application/C9C92475-09F8-445B-B244-F2EF514DC72F reportType:CrashLog maxTerminationResistance:Absolute>
Should I delete the lock file after unlock it? Or it just complaints that lock file cannot stay in share app group folder?
Never mind. I just realized that by default keychain items can only be accessed when the device is unlocked and I should set kSecAttrAccessible to kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly.
I have seen the same issue in my app. Did you solve the problem?