@mmerterden/multi-agent-pipeline 20.7.0 → 20.8.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/CHANGELOG.md +9 -0
- package/LICENSE +0 -10
- package/docs/facts.json +1 -1
- package/manifest.json +266 -267
- package/package.json +2 -2
- package/pipeline/scripts/_notices.mjs +1 -1
- package/pipeline/skills/.skill-manifest.json +68 -68
- package/pipeline/skills/shared/README.md +70 -70
- package/pipeline/skills/shared/external/alarmkit/SKILL.md +373 -381
- package/pipeline/skills/shared/external/alarmkit/evals/evals.json +23 -18
- package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +328 -378
- package/pipeline/skills/shared/external/app-clips/SKILL.md +260 -160
- package/pipeline/skills/shared/external/app-clips/evals/evals.json +27 -27
- package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +150 -83
- package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +135 -83
- package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +143 -85
- package/pipeline/skills/shared/external/app-intents/SKILL.md +302 -304
- package/pipeline/skills/shared/external/app-intents/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +594 -894
- package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +339 -277
- package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +27 -23
- package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +105 -122
- package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +143 -166
- package/pipeline/skills/shared/external/app-store-review/SKILL.md +307 -326
- package/pipeline/skills/shared/external/app-store-review/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +105 -67
- package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +114 -101
- package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +333 -360
- package/pipeline/skills/shared/external/apple-on-device-ai/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +215 -322
- package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +161 -256
- package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +277 -387
- package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +196 -210
- package/pipeline/skills/shared/external/authentication/SKILL.md +265 -381
- package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +133 -178
- package/pipeline/skills/shared/external/authentication/references/passkeys.md +111 -147
- package/pipeline/skills/shared/external/avkit/SKILL.md +267 -364
- package/pipeline/skills/shared/external/avkit/evals/evals.json +26 -26
- package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +375 -493
- package/pipeline/skills/shared/external/background-processing/SKILL.md +270 -382
- package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +169 -317
- package/pipeline/skills/shared/external/callkit-voip/SKILL.md +290 -371
- package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +175 -343
- package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +292 -381
- package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +33 -30
- package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +227 -355
- package/pipeline/skills/shared/external/contacts-framework/SKILL.md +197 -346
- package/pipeline/skills/shared/external/contacts-framework/evals/evals.json +19 -21
- package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +169 -308
- package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +226 -376
- package/pipeline/skills/shared/external/core-bluetooth/evals/evals.json +25 -22
- package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +257 -337
- package/pipeline/skills/shared/external/core-data/SKILL.md +292 -368
- package/pipeline/skills/shared/external/core-data/evals/evals.json +30 -27
- package/pipeline/skills/shared/external/core-motion/SKILL.md +235 -324
- package/pipeline/skills/shared/external/core-motion/evals/evals.json +31 -27
- package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +210 -310
- package/pipeline/skills/shared/external/core-nfc/SKILL.md +292 -366
- package/pipeline/skills/shared/external/core-nfc/evals/evals.json +22 -24
- package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +233 -329
- package/pipeline/skills/shared/external/coreml/SKILL.md +323 -367
- package/pipeline/skills/shared/external/coreml/evals/evals.json +24 -21
- package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +562 -565
- package/pipeline/skills/shared/external/cryptokit/SKILL.md +253 -394
- package/pipeline/skills/shared/external/cryptokit/evals/evals.json +20 -18
- package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +299 -488
- package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +270 -323
- package/pipeline/skills/shared/external/debugging-instruments/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +167 -315
- package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +140 -193
- package/pipeline/skills/shared/external/device-integrity/SKILL.md +230 -353
- package/pipeline/skills/shared/external/device-integrity/evals/evals.json +25 -21
- package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +159 -197
- package/pipeline/skills/shared/external/energykit/SKILL.md +225 -392
- package/pipeline/skills/shared/external/energykit/evals/evals.json +29 -28
- package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +174 -470
- package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +261 -383
- package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +25 -22
- package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +165 -268
- package/pipeline/skills/shared/external/healthkit/SKILL.md +252 -303
- package/pipeline/skills/shared/external/healthkit/evals/evals.json +24 -23
- package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +369 -523
- package/pipeline/skills/shared/external/homekit-matter/SKILL.md +233 -348
- package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +27 -22
- package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +199 -305
- package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +368 -340
- package/pipeline/skills/shared/external/ios-accessibility/evals/evals.json +28 -27
- package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +314 -260
- package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +97 -67
- package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +165 -101
- package/pipeline/skills/shared/external/ios-localization/SKILL.md +258 -371
- package/pipeline/skills/shared/external/ios-localization/evals/evals.json +23 -23
- package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +283 -491
- package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +313 -440
- package/pipeline/skills/shared/external/ios-networking/SKILL.md +265 -341
- package/pipeline/skills/shared/external/ios-networking/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +425 -652
- package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +143 -285
- package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -53
- package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +231 -456
- package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +517 -784
- package/pipeline/skills/shared/external/ios-simulator/SKILL.md +265 -393
- package/pipeline/skills/shared/external/ios-simulator/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +177 -270
- package/pipeline/skills/shared/external/live-activities/SKILL.md +318 -360
- package/pipeline/skills/shared/external/live-activities/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +478 -710
- package/pipeline/skills/shared/external/mapkit-location/SKILL.md +295 -267
- package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +28 -24
- package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +378 -532
- package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +397 -499
- package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +165 -348
- package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +26 -23
- package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +123 -130
- package/pipeline/skills/shared/external/musickit-audio/SKILL.md +189 -315
- package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +22 -21
- package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +181 -270
- package/pipeline/skills/shared/external/natural-language/SKILL.md +188 -340
- package/pipeline/skills/shared/external/natural-language/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +171 -225
- package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +258 -392
- package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +30 -29
- package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +164 -231
- package/pipeline/skills/shared/external/pdfkit/SKILL.md +312 -344
- package/pipeline/skills/shared/external/pdfkit/evals/evals.json +19 -19
- package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +413 -624
- package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +242 -358
- package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +25 -21
- package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +161 -226
- package/pipeline/skills/shared/external/permissionkit/SKILL.md +282 -400
- package/pipeline/skills/shared/external/permissionkit/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +237 -350
- package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +276 -325
- package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +299 -545
- package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +344 -588
- package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +316 -660
- package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +270 -416
- package/pipeline/skills/shared/external/push-notifications/SKILL.md +312 -340
- package/pipeline/skills/shared/external/push-notifications/evals/evals.json +27 -26
- package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +328 -485
- package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +327 -560
- package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +218 -410
- package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +221 -348
- package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +222 -393
- package/pipeline/skills/shared/external/shareplay-activities/evals/evals.json +23 -24
- package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +280 -420
- package/pipeline/skills/shared/external/speech-recognition/SKILL.md +217 -421
- package/pipeline/skills/shared/external/speech-recognition/evals/evals.json +23 -26
- package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +133 -125
- package/pipeline/skills/shared/external/storekit/SKILL.md +228 -204
- package/pipeline/skills/shared/external/storekit/evals/evals.json +27 -24
- package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +98 -109
- package/pipeline/skills/shared/external/storekit/references/core-patterns.md +298 -242
- package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +356 -649
- package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +274 -399
- package/pipeline/skills/shared/external/swift-api-design-guidelines/evals/evals.json +22 -24
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +107 -108
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +93 -165
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +99 -137
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +77 -120
- package/pipeline/skills/shared/external/swift-architecture/SKILL.md +334 -350
- package/pipeline/skills/shared/external/swift-architecture/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/swift-charts/SKILL.md +208 -394
- package/pipeline/skills/shared/external/swift-charts/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +351 -762
- package/pipeline/skills/shared/external/swift-codable/SKILL.md +339 -343
- package/pipeline/skills/shared/external/swift-codable/evals/evals.json +20 -20
- package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +303 -351
- package/pipeline/skills/shared/external/swift-concurrency/evals/evals.json +27 -24
- package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +65 -80
- package/pipeline/skills/shared/external/swift-concurrency/references/async-algorithms.md +48 -84
- package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +134 -79
- package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +145 -167
- package/pipeline/skills/shared/external/swift-concurrency/references/diagnostics.md +62 -50
- package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +92 -121
- package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +177 -241
- package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +258 -234
- package/pipeline/skills/shared/external/swift-language/SKILL.md +342 -382
- package/pipeline/skills/shared/external/swift-language/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +79 -56
- package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +297 -340
- package/pipeline/skills/shared/external/swift-security/SKILL.md +180 -161
- package/pipeline/skills/shared/external/swift-security/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +314 -469
- package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +408 -476
- package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +260 -530
- package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +270 -477
- package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +573 -571
- package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +370 -441
- package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +332 -433
- package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +346 -468
- package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +352 -472
- package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +431 -432
- package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +328 -425
- package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +341 -579
- package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +396 -457
- package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
- package/pipeline/skills/shared/external/swift-testing/SKILL.md +188 -175
- package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
- package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +80 -84
- package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
- package/pipeline/skills/shared/external/swiftdata/SKILL.md +392 -256
- package/pipeline/skills/shared/external/swiftdata/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +206 -402
- package/pipeline/skills/shared/external/swiftdata/references/indexing.md +59 -52
- package/pipeline/skills/shared/external/swiftdata/references/predicate-pitfalls.md +57 -33
- package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +354 -747
- package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +300 -508
- package/pipeline/skills/shared/external/swiftlint/SKILL.md +175 -226
- package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +141 -208
- package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +100 -109
- package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +159 -179
- package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +383 -18
- package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +143 -229
- package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +283 -366
- package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +396 -608
- package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +336 -385
- package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +239 -349
- package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +228 -310
- package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +260 -249
- package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +92 -74
- package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +112 -177
- package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +61 -64
- package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +94 -134
- package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +193 -225
- package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +173 -327
- package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +193 -168
- package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
- package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +132 -133
- package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
- package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +106 -140
- package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +316 -252
- package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +341 -332
- package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +547 -854
- package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +485 -537
- package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +417 -499
- package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +213 -376
- package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +86 -175
- package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +89 -195
- package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +95 -182
- package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +71 -149
- package/pipeline/skills/shared/external/swiftui-performance/references/wwdc-session-sources.md +21 -27
- package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +303 -295
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +204 -387
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +469 -683
- package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +140 -186
- package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +75 -86
- package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +63 -60
- package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +69 -137
- package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +95 -67
- package/pipeline/skills/shared/external/tipkit/SKILL.md +220 -335
- package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
- package/pipeline/skills/shared/external/vision-framework/SKILL.md +260 -375
- package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +393 -515
- package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +363 -539
- package/pipeline/skills/shared/external/weatherkit/SKILL.md +152 -310
- package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
- package/pipeline/skills/shared/external/widgetkit/SKILL.md +216 -288
- package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +414 -719
- package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
|
@@ -1,515 +1,514 @@
|
|
|
1
1
|
# Keychain Item Classes
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
The keychain stores five kinds of item: `kSecClassGenericPassword`,
|
|
4
|
+
`kSecClassInternetPassword`, `kSecClassKey`, `kSecClassCertificate` and
|
|
5
|
+
`kSecClassIdentity`. Choosing the wrong one does not crash anything. It shows up
|
|
6
|
+
later as Password AutoFill that never offers the credential, queries that
|
|
7
|
+
collide with unrelated items, or keys stored with weaker handling than they
|
|
8
|
+
should get.
|
|
9
|
+
|
|
10
|
+
Each class has its own composite primary key. A second add whose primary-key
|
|
11
|
+
attributes equal an existing item's fails with `errSecDuplicateItem` (-25299).
|
|
12
|
+
The save pattern (add, then update on duplicate) is in
|
|
13
|
+
[keychain-fundamentals.md](keychain-fundamentals.md).
|
|
12
14
|
|
|
13
15
|
## Contents
|
|
14
16
|
|
|
15
|
-
- [
|
|
16
|
-
- [
|
|
17
|
-
|
|
18
|
-
- [
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
- [
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
- [
|
|
27
|
-
- [
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
| **Certificate** | `kSecAttrCertificateType` + `kSecAttrIssuer` + `kSecAttrSerialNumber` | X.509 certificates |
|
|
62
|
-
| **Identity** | Same as Certificate (virtual join, not a stored item) | TLS client auth, code signing |
|
|
63
|
-
|
|
64
|
-
Omitting a primary key attribute does not cause an error - the system uses a nil/empty default. But a second add with the same defaults produces `errSecDuplicateItem`, which is a frequent source of confusion.
|
|
65
|
-
|
|
66
|
-
---
|
|
67
|
-
|
|
68
|
-
## kSecClassGenericPassword - App-Specific Secrets
|
|
69
|
-
|
|
70
|
-
GenericPassword is the correct choice for API tokens, OAuth refresh tokens, encryption keys, and any secret that does not represent a web login credential. Its primary key is effectively **`kSecAttrService` + `kSecAttrAccount`**.
|
|
71
|
-
|
|
72
|
-
**Required for meaningful usage** (not enforced by the API, but omitting them causes collisions): `kSecAttrService` (CFString - typically bundle ID or service identifier) and `kSecAttrAccount` (CFString - the account or key name). The actual secret goes in `kSecValueData` as `Data`.
|
|
73
|
-
|
|
74
|
-
**Optional metadata attributes:** `kSecAttrLabel` (human-readable name), `kSecAttrDescription` (item kind), `kSecAttrComment` (user-editable note), `kSecAttrCreator` and `kSecAttrType` (FourCharCode as CFNumber), `kSecAttrGeneric` (arbitrary CFData for custom metadata), `kSecAttrIsInvisible`/`kSecAttrIsNegative` (boolean flags). System-managed read-only: `kSecAttrCreationDate`, `kSecAttrModificationDate`.
|
|
75
|
-
|
|
76
|
-
### The kSecAttrGeneric trap
|
|
77
|
-
|
|
78
|
-
`kSecAttrGeneric` is **NOT part of the primary key** despite its name suggesting otherwise. Two items with identical `kSecAttrService` + `kSecAttrAccount` but different `kSecAttrGeneric` values still collide - the second add fails with `errSecDuplicateItem`. Yet querying by `kSecAttrGeneric` that does not match returns `errSecItemNotFound` even though an item with that service+account exists. This inconsistency is a major source of bugs.
|
|
17
|
+
- [Primary Keys per Class](#primary-keys-per-class)
|
|
18
|
+
- [Generic Password](#generic-password)
|
|
19
|
+
- [Internet Password](#internet-password)
|
|
20
|
+
- [Key](#key)
|
|
21
|
+
- [Certificate](#certificate)
|
|
22
|
+
- [Identity](#identity)
|
|
23
|
+
- [Mistakes Generated Code Makes](#mistakes-generated-code-makes)
|
|
24
|
+
- [Platform Differences](#platform-differences)
|
|
25
|
+
- [Size and When to Use a File](#size-and-when-to-use-a-file)
|
|
26
|
+
- [Tests That Prove the Class Is Right](#tests-that-prove-the-class-is-right)
|
|
27
|
+
- [Moving Web Logins to Internet Passwords](#moving-web-logins-to-internet-passwords)
|
|
28
|
+
- [Checklist](#checklist)
|
|
29
|
+
- [Related Files](#related-files)
|
|
30
|
+
|
|
31
|
+
## Primary Keys per Class
|
|
32
|
+
|
|
33
|
+
For every class, `kSecAttrAccessGroup` and `kSecAttrSynchronizable` are also
|
|
34
|
+
part of the key.
|
|
35
|
+
|
|
36
|
+
| Class | Primary-key attributes | Use it for |
|
|
37
|
+
| --- | --- | --- |
|
|
38
|
+
| Generic password | `kSecAttrService`, `kSecAttrAccount` | app secrets, API tokens, raw key bytes |
|
|
39
|
+
| Internet password | `kSecAttrAccount`, `kSecAttrServer`, `kSecAttrPath`, `kSecAttrPort`, `kSecAttrProtocol`, `kSecAttrAuthenticationType`, `kSecAttrSecurityDomain` | website logins, AutoFill |
|
|
40
|
+
| Key | `kSecAttrApplicationTag`, `kSecAttrApplicationLabel`, `kSecAttrKeyType`, `kSecAttrKeyClass`, `kSecAttrEffectiveKeySize`, `kSecAttrKeySizeInBits` | `SecKey` objects |
|
|
41
|
+
| Certificate | `kSecAttrIssuer`, `kSecAttrSerialNumber`, `kSecAttrCertificateType` | X.509 certificates |
|
|
42
|
+
| Identity | same as certificate | not stored; a join of certificate and private key |
|
|
43
|
+
|
|
44
|
+
Leaving a primary-key attribute out is legal. The attribute then takes an empty
|
|
45
|
+
default, and the next add that also leaves it out collides with the first.
|
|
46
|
+
|
|
47
|
+
## Generic Password
|
|
48
|
+
|
|
49
|
+
Pick this class for OAuth refresh tokens, API tokens, symmetric key bytes and
|
|
50
|
+
any secret that is not a website login.
|
|
51
|
+
|
|
52
|
+
- Set `kSecAttrService` (the bundle identifier or a service name) and
|
|
53
|
+
`kSecAttrAccount`. The API will not insist on them, but without them unrelated
|
|
54
|
+
items collide. Put the secret itself, as `Data`, under `kSecValueData`.
|
|
55
|
+
- Optional descriptive attributes: `kSecAttrLabel`, `kSecAttrDescription`,
|
|
56
|
+
`kSecAttrComment`, `kSecAttrCreator` and `kSecAttrType` (FourCharCode as
|
|
57
|
+
`CFNumber`), `kSecAttrGeneric` (`CFData`), `kSecAttrIsInvisible`,
|
|
58
|
+
`kSecAttrIsNegative`. The system maintains `kSecAttrCreationDate` and
|
|
59
|
+
`kSecAttrModificationDate`; they are read-only.
|
|
60
|
+
- `kSecAttrGeneric` does not count toward uniqueness. Two adds that differ only in
|
|
61
|
+
`kSecAttrGeneric` still collide, yet a query that filters on a different
|
|
62
|
+
`kSecAttrGeneric` value reports the item as missing.
|
|
79
63
|
|
|
80
64
|
```swift
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
65
|
+
func addLicenseKey(_ bytes: Data) -> OSStatus {
|
|
66
|
+
var entry: [String: Any] = [
|
|
67
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
68
|
+
kSecAttrService as String: "com.example.editor",
|
|
69
|
+
kSecAttrAccount as String: "license",
|
|
70
|
+
kSecValueData as String: bytes,
|
|
71
|
+
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
|
|
72
|
+
]
|
|
73
|
+
#if os(macOS)
|
|
74
|
+
entry[kSecUseDataProtectionKeychain as String] = true
|
|
75
|
+
#endif
|
|
76
|
+
return SecItemAdd(entry as CFDictionary, nil)
|
|
77
|
+
}
|
|
91
78
|
```
|
|
92
79
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
---
|
|
96
|
-
|
|
97
|
-
## kSecClassInternetPassword - AutoFill and Credential Sharing
|
|
80
|
+
The caller switches on the returned status and updates on
|
|
81
|
+
`errSecDuplicateItem`.
|
|
98
82
|
|
|
99
|
-
|
|
83
|
+
Decision rule: a credential for a web domain that should appear in Password
|
|
84
|
+
AutoFill belongs in an internet password. To the system, a generic password's
|
|
85
|
+
service string is an opaque label, so AutoFill never offers it.
|
|
100
86
|
|
|
101
|
-
|
|
87
|
+
## Internet Password
|
|
102
88
|
|
|
103
|
-
|
|
89
|
+
The seven-attribute key is what lets the system connect a credential to a
|
|
90
|
+
domain for AutoFill, Safari and syncing across devices.
|
|
104
91
|
|
|
105
|
-
|
|
92
|
+
| Attribute | Meaning |
|
|
93
|
+
| --- | --- |
|
|
94
|
+
| `kSecAttrServer` | host name |
|
|
95
|
+
| `kSecAttrProtocol` | for example `kSecAttrProtocolHTTPS` |
|
|
96
|
+
| `kSecAttrPort` | `CFNumber` |
|
|
97
|
+
| `kSecAttrPath` | path on the server |
|
|
98
|
+
| `kSecAttrAccount` | the user name |
|
|
99
|
+
| `kSecAttrSecurityDomain` | the HTTP realm |
|
|
100
|
+
| `kSecAttrAuthenticationType` | for example `kSecAttrAuthenticationTypeHTMLForm` |
|
|
106
101
|
|
|
107
|
-
|
|
102
|
+
`kSecAttrService` and `kSecAttrGeneric` do not exist on this class.
|
|
108
103
|
|
|
109
|
-
|
|
110
|
-
2. **Configure Associated Domains** by adding `webcredentials:<domain>` to the app's entitlements
|
|
111
|
-
3. **Host an apple-app-site-association file** at `https://<domain>/.well-known/apple-app-site-association` containing `{"webcredentials": {"apps": ["TEAMID.com.example.app"]}}`
|
|
104
|
+
AutoFill needs three things together:
|
|
112
105
|
|
|
113
|
-
|
|
106
|
+
1. the credential stored as an internet password with `kSecAttrServer` set;
|
|
107
|
+
2. the Associated Domains entitlement containing `webcredentials:<domain>`;
|
|
108
|
+
3. the domain's association file, published over HTTPS under the
|
|
109
|
+
`.well-known` directory, with a `webcredentials.apps` array that lists
|
|
110
|
+
`TEAMID.bundle.identifier`.
|
|
114
111
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
let addQuery: [String: Any] = [
|
|
118
|
-
kSecClass as String: kSecClassInternetPassword,
|
|
119
|
-
kSecAttrServer as String: "example.com",
|
|
120
|
-
kSecAttrProtocol as String: kSecAttrProtocolHTTPS,
|
|
121
|
-
kSecAttrPort as String: 443,
|
|
122
|
-
kSecAttrPath as String: "/login",
|
|
123
|
-
kSecAttrAccount as String: "user@example.com",
|
|
124
|
-
kSecAttrAuthenticationType as String: kSecAttrAuthenticationTypeHTMLForm,
|
|
125
|
-
kSecValueData as String: "password123".data(using: .utf8)!,
|
|
126
|
-
kSecUseDataProtectionKeychain as String: true
|
|
127
|
-
]
|
|
128
|
-
let status = SecItemAdd(addQuery as CFDictionary, nil)
|
|
129
|
-
```
|
|
112
|
+
Safari itself keeps every web password as an internet password, and a web
|
|
113
|
+
login stored as a generic password is never suggested.
|
|
130
114
|
|
|
131
115
|
```swift
|
|
132
|
-
//
|
|
133
|
-
let
|
|
134
|
-
kSecClass as String: kSecClassGenericPassword,
|
|
135
|
-
kSecAttrService as String: "example.
|
|
136
|
-
kSecAttrAccount as String: "
|
|
137
|
-
kSecValueData as String: "
|
|
116
|
+
// Wrong: the domain is hidden in an opaque service string; AutoFill ignores it.
|
|
117
|
+
let hidden: [String: Any] = [
|
|
118
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
119
|
+
kSecAttrService as String: "forum.example.org",
|
|
120
|
+
kSecAttrAccount as String: "rhea",
|
|
121
|
+
kSecValueData as String: Data("pw".utf8)
|
|
138
122
|
]
|
|
139
123
|
```
|
|
140
124
|
|
|
141
|
-
### Protocol and Authentication Constants
|
|
142
|
-
|
|
143
|
-
Protocol constants span **30+ values** including `kSecAttrProtocolHTTPS`, `kSecAttrProtocolHTTP`, `kSecAttrProtocolSSH`, `kSecAttrProtocolFTP`, `kSecAttrProtocolIMAPS`, `kSecAttrProtocolSMTP`, and many more. Authentication types include `kSecAttrAuthenticationTypeHTMLForm`, `kSecAttrAuthenticationTypeHTTPBasic`, `kSecAttrAuthenticationTypeHTTPDigest`, and `kSecAttrAuthenticationTypeDefault`.
|
|
144
|
-
|
|
145
|
-
### Credential Provider Extensions (iOS 12+)
|
|
146
|
-
|
|
147
|
-
For third-party password managers, `ASCredentialProviderViewController` enables credential provider extensions. Apps subclass this controller, populate `ASCredentialIdentityStore` with `ASPasswordCredentialIdentity` instances, and override `provideCredentialWithoutUserInteraction(for:)` for tap-to-fill behavior.
|
|
148
|
-
|
|
149
|
-
---
|
|
150
|
-
|
|
151
|
-
## kSecClassKey - Cryptographic Key Management
|
|
152
|
-
|
|
153
|
-
Key items require **`kSecAttrKeyType`** and **`kSecAttrKeySizeInBits`** for generation - omitting either from `SecKeyCreateRandomKey` returns `errSecParam` (-50). The `kSecAttrKeyClass` attribute (`kSecAttrKeyClassPublic`, `kSecAttrKeyClassPrivate`, `kSecAttrKeyClassSymmetric`) is set automatically during generation but must be specified when querying.
|
|
154
|
-
|
|
155
|
-
### ApplicationTag vs ApplicationLabel - The Critical Distinction
|
|
156
|
-
|
|
157
|
-
This confusion is the single most common keychain mistake for cryptographic keys, and AI generators get it wrong constantly:
|
|
158
|
-
|
|
159
|
-
- **`kSecAttrApplicationTag`** (CFData): **Developer-set** binary tag for finding and organizing keys. You choose its content - typically a reverse-DNS string encoded as Data. Part of the primary key. **Use this for lookup.**
|
|
160
|
-
- **`kSecAttrApplicationLabel`** (CFData): **System-generated** SHA-1 hash of the public key bytes (the `subjectPublicKey` element per RFC 5280 section4.1). Part of the primary key. **Used internally for identity formation** - it must match a certificate's `kSecAttrPublicKeyHash` to synthesize a `SecIdentity`. Never set this manually for asymmetric keys.
|
|
161
|
-
- **`kSecAttrLabel`** (CFString): Human-readable display name. NOT part of the primary key. Shows in Keychain Access on macOS.
|
|
162
|
-
|
|
163
|
-
### SecKeyCreateRandomKey - The Preferred API
|
|
164
|
-
|
|
165
|
-
`SecKeyCreateRandomKey` (iOS 10+, macOS 10.12+) generates keys atomically, returns a `SecKey` reference directly, auto-computes `kSecAttrApplicationLabel`, and supports Secure Enclave generation via `kSecAttrTokenID: kSecAttrTokenIDSecureEnclave`. Apple recommends storing **only the private key** and deriving the public key via `SecKeyCopyPublicKey()`.
|
|
166
|
-
|
|
167
125
|
```swift
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
126
|
+
func webLogin(user: String, secret: Data) -> [String: Any] {
|
|
127
|
+
[
|
|
128
|
+
kSecClass as String: kSecClassInternetPassword,
|
|
129
|
+
kSecAttrServer as String: "forum.example.org",
|
|
130
|
+
kSecAttrProtocol as String: kSecAttrProtocolHTTPS,
|
|
131
|
+
kSecAttrPort as String: 443,
|
|
132
|
+
kSecAttrPath as String: "/login",
|
|
133
|
+
kSecAttrAccount as String: user,
|
|
134
|
+
kSecAttrAuthenticationType as String: kSecAttrAuthenticationTypeHTMLForm,
|
|
135
|
+
kSecValueData as String: secret,
|
|
136
|
+
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked,
|
|
137
|
+
kSecUseDataProtectionKeychain as String: true
|
|
176
138
|
]
|
|
177
|
-
]
|
|
178
|
-
var error: Unmanaged<CFError>?
|
|
179
|
-
guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else {
|
|
180
|
-
throw error!.takeRetainedValue() as Error
|
|
181
139
|
}
|
|
182
|
-
let publicKey = SecKeyCopyPublicKey(privateKey)!
|
|
183
140
|
```
|
|
184
141
|
|
|
142
|
+
There are more than thirty protocol constants (`kSecAttrProtocolHTTPS`,
|
|
143
|
+
`kSecAttrProtocolHTTP`, `kSecAttrProtocolSSH`, `kSecAttrProtocolFTP`,
|
|
144
|
+
`kSecAttrProtocolIMAPS`, `kSecAttrProtocolSMTP`, ...). Authentication types
|
|
145
|
+
include `kSecAttrAuthenticationTypeHTMLForm`, `kSecAttrAuthenticationTypeHTTPBasic`,
|
|
146
|
+
`kSecAttrAuthenticationTypeHTTPDigest` and `kSecAttrAuthenticationTypeDefault`.
|
|
147
|
+
|
|
148
|
+
A password manager that wants to supply credentials system-wide builds a
|
|
149
|
+
credential provider extension (iOS 12+): subclass
|
|
150
|
+
`ASCredentialProviderViewController`, publish `ASPasswordCredentialIdentity`
|
|
151
|
+
values to `ASCredentialIdentityStore`, and override
|
|
152
|
+
`provideCredentialWithoutUserInteraction(for:)`.
|
|
153
|
+
|
|
154
|
+
## Key
|
|
155
|
+
|
|
156
|
+
- `kSecAttrKeyType` and `kSecAttrKeySizeInBits` are required to generate a key.
|
|
157
|
+
`SecKeyCreateRandomKey` without either returns `errSecParam`.
|
|
158
|
+
- `kSecAttrKeyClass` (`kSecAttrKeyClassPublic`, `kSecAttrKeyClassPrivate`,
|
|
159
|
+
`kSecAttrKeyClassSymmetric`) is filled in on generation, but queries must name
|
|
160
|
+
it.
|
|
161
|
+
- `kSecAttrApplicationTag` (`CFData`) is yours to set, is part of the key, and
|
|
162
|
+
is the attribute to look keys up by. Encode a reverse-DNS name to `Data` for
|
|
163
|
+
it.
|
|
164
|
+
- `kSecAttrApplicationLabel` (`CFData`) is computed by the system as the SHA-1
|
|
165
|
+
of the public key (the `subjectPublicKey` bit string from RFC 5280 section
|
|
166
|
+
4.1). It is part of the key, and it has to equal a certificate's
|
|
167
|
+
`kSecAttrPublicKeyHash` for the pair to form a `SecIdentity`. Do not set it
|
|
168
|
+
yourself on asymmetric keys.
|
|
169
|
+
- `kSecAttrLabel` (`CFString`) is only a display name. It is not part of the key
|
|
170
|
+
and is what Keychain Access shows on macOS.
|
|
171
|
+
|
|
172
|
+
Prefer `SecKeyCreateRandomKey` (available from iOS 10 and macOS 10.12) as the way to make a
|
|
173
|
+
key: it generates and stores in one step, returns a `SecKey`, computes the
|
|
174
|
+
application label, and can target the Secure Enclave with
|
|
175
|
+
`kSecAttrTokenID: kSecAttrTokenIDSecureEnclave`. Apple suggests persisting only
|
|
176
|
+
the private key and obtaining the public half with `SecKeyCopyPublicKey()`.
|
|
177
|
+
|
|
185
178
|
```swift
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
let tag = "com.myapp.keys.se-signing".data(using: .utf8)!
|
|
196
|
-
let attributes: [String: Any] = [
|
|
197
|
-
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
|
|
198
|
-
kSecAttrKeySizeInBits as String: 256,
|
|
199
|
-
kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
|
|
200
|
-
kSecPrivateKeyAttrs as String: [
|
|
201
|
-
kSecAttrIsPermanent as String: true,
|
|
202
|
-
kSecAttrApplicationTag as String: tag,
|
|
203
|
-
kSecAttrAccessControl as String: accessControl
|
|
179
|
+
func makeReceiptSigningKey() throws -> SecKey {
|
|
180
|
+
let tag = Data("com.example.receipts.signing".utf8)
|
|
181
|
+
let spec: [String: Any] = [
|
|
182
|
+
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
|
|
183
|
+
kSecAttrKeySizeInBits as String: 256,
|
|
184
|
+
kSecPrivateKeyAttrs as String: [
|
|
185
|
+
kSecAttrIsPermanent as String: true,
|
|
186
|
+
kSecAttrApplicationTag as String: tag
|
|
187
|
+
]
|
|
204
188
|
]
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
189
|
+
var failure: Unmanaged<CFError>?
|
|
190
|
+
guard let key = SecKeyCreateRandomKey(spec as CFDictionary, &failure) else {
|
|
191
|
+
throw (failure?.takeRetainedValue() as Error?) ?? KeychainError(status: errSecParam)
|
|
192
|
+
}
|
|
193
|
+
return key
|
|
209
194
|
}
|
|
210
195
|
```
|
|
211
196
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
### CryptoKit Key Storage Mapping
|
|
215
|
-
|
|
216
|
-
CryptoKit's NIST keys (P256, P384, P521) map to `SecKey` via `SecKeyCreateWithData` with `kSecAttrKeyTypeECSECPrimeRandom`, stored as `kSecClassKey`. **Non-NIST keys** (Curve25519, SymmetricKey) have no `SecKey` equivalent and must be stored as **`kSecClassGenericPassword`** items with raw key data in `kSecValueData`. Secure Enclave keys (`SecureEnclave.P256.Signing.PrivateKey`) export an encrypted blob that only the originating SE can restore - this blob is also stored as a generic password, not a key item.
|
|
217
|
-
|
|
218
|
-
---
|
|
219
|
-
|
|
220
|
-
## kSecClassCertificate - DER-Encoded Certificate Storage
|
|
221
|
-
|
|
222
|
-
Certificates are **not encrypted** by the keychain (they are public data). The primary key is `kSecAttrCertificateType` + `kSecAttrIssuer` + `kSecAttrSerialNumber`.
|
|
223
|
-
|
|
224
|
-
Creation follows a two-step pattern: first create a `SecCertificate` from DER data, then add via `kSecValueRef`:
|
|
197
|
+
For a Secure Enclave key, create the access control first and put it inside the
|
|
198
|
+
private-key attributes. The flags always include `.privateKeyUsage`:
|
|
225
199
|
|
|
226
200
|
```swift
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
201
|
+
func makeEnclaveKey() throws -> SecKey {
|
|
202
|
+
var failure: Unmanaged<CFError>?
|
|
203
|
+
guard let policy = SecAccessControlCreateWithFlags(
|
|
204
|
+
nil, kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
|
|
205
|
+
[.privateKeyUsage, .userPresence], &failure) else {
|
|
206
|
+
throw (failure?.takeRetainedValue() as Error?) ?? KeychainError(status: errSecParam)
|
|
207
|
+
}
|
|
208
|
+
let spec: [String: Any] = [
|
|
209
|
+
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
|
|
210
|
+
kSecAttrKeySizeInBits as String: 256,
|
|
211
|
+
kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
|
|
212
|
+
kSecPrivateKeyAttrs as String: [
|
|
213
|
+
kSecAttrIsPermanent as String: true,
|
|
214
|
+
kSecAttrApplicationTag as String: Data("com.example.enclave.auth".utf8),
|
|
215
|
+
kSecAttrAccessControl as String: policy
|
|
216
|
+
]
|
|
217
|
+
]
|
|
218
|
+
guard let key = SecKeyCreateRandomKey(spec as CFDictionary, &failure) else {
|
|
219
|
+
throw (failure?.takeRetainedValue() as Error?) ?? KeychainError(status: errSecParam)
|
|
220
|
+
}
|
|
221
|
+
return key
|
|
230
222
|
}
|
|
231
|
-
let addQuery: [String: Any] = [
|
|
232
|
-
kSecClass as String: kSecClassCertificate,
|
|
233
|
-
kSecValueRef as String: certificate,
|
|
234
|
-
kSecUseDataProtectionKeychain as String: true
|
|
235
|
-
]
|
|
236
|
-
let status = SecItemAdd(addQuery as CFDictionary, nil)
|
|
237
223
|
```
|
|
238
224
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
**Common pitfall (Apple DTS):** `kSecAttrApplicationTag` is **NOT a valid attribute for certificates** (only for keys). Using it with `kSecClassCertificate` causes `errSecParam` (-50) on the data protection keychain or mysterious silent failures on the file-based keychain.
|
|
242
|
-
|
|
243
|
-
---
|
|
244
|
-
|
|
245
|
-
## kSecClassIdentity - The Virtual Join
|
|
225
|
+
`KeychainError` is the status wrapper from
|
|
226
|
+
[keychain-fundamentals.md](keychain-fundamentals.md).
|
|
246
227
|
|
|
247
|
-
|
|
228
|
+
Call `SecItemAdd` for a key yourself only when importing one: build a `SecKey`
|
|
229
|
+
from external bytes with `SecKeyCreateWithData`, then add it through
|
|
230
|
+
`kSecValueRef`. Never add raw key bytes as a key item.
|
|
248
231
|
|
|
249
|
-
|
|
232
|
+
Mapping CryptoKit keys to storage:
|
|
250
233
|
|
|
251
|
-
|
|
234
|
+
| CryptoKit type | How to store |
|
|
235
|
+
| --- | --- |
|
|
236
|
+
| `P256`, `P384`, `P521` private keys | `SecKeyCreateWithData` with `kSecAttrKeyTypeECSECPrimeRandom`, then a `kSecClassKey` item |
|
|
237
|
+
| `Curve25519` keys, `SymmetricKey` | no `SecKey` form; store the raw bytes as a generic password |
|
|
238
|
+
| `SecureEnclave.P256` keys | keep the encrypted `dataRepresentation` blob in a generic password item |
|
|
252
239
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
-
|
|
256
|
-
- Adding a **private key** can implicitly create an identity if a matching certificate exists
|
|
257
|
-
- Deleting a certificate or key can implicitly destroy an identity
|
|
258
|
-
- The identity "set" changes without any explicit identity operations
|
|
259
|
-
|
|
260
|
-
```swift
|
|
261
|
-
// ❌ Attempting to create an identity directly - fails with errSecParam (-50)
|
|
262
|
-
let attributes: [String: Any] = [
|
|
263
|
-
kSecClass as String: kSecClassIdentity,
|
|
264
|
-
kSecAttrLabel as String: "MyInvalidIdentity"
|
|
265
|
-
]
|
|
266
|
-
let status = SecItemAdd(attributes as CFDictionary, nil)
|
|
267
|
-
// status == errSecParam (-50)
|
|
268
|
-
```
|
|
240
|
+
Details for the enclave case are in [secure-enclave.md](secure-enclave.md);
|
|
241
|
+
the CryptoKit side is in [cryptokit-symmetric.md](cryptokit-symmetric.md) and
|
|
242
|
+
[cryptokit-public-key.md](cryptokit-public-key.md).
|
|
269
243
|
|
|
270
|
-
|
|
244
|
+
## Certificate
|
|
271
245
|
|
|
272
|
-
|
|
246
|
+
- Certificates are public, so the keychain does not encrypt them.
|
|
247
|
+
- Adding one takes two steps: `SecCertificateCreateWithData(nil, der)` (DER
|
|
248
|
+
bytes only; strip PEM armor first), then `SecItemAdd` with `kSecValueRef`.
|
|
249
|
+
- Passing the certificate by `kSecValueRef` lets the keychain extract issuer,
|
|
250
|
+
serial number, subject, `kSecAttrPublicKeyHash`, certificate type and
|
|
251
|
+
encoding for you.
|
|
252
|
+
- `kSecAttrPublicKeyHash` has to match a private key's
|
|
253
|
+
`kSecAttrApplicationLabel` for an identity to appear.
|
|
254
|
+
- `kSecAttrApplicationTag` is not valid on certificates: the data protection
|
|
255
|
+
keychain returns `errSecParam`, and the file-based keychain fails without
|
|
256
|
+
telling you.
|
|
273
257
|
|
|
274
258
|
```swift
|
|
275
|
-
|
|
276
|
-
let
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
259
|
+
func storeServerRoot(der: Data) throws {
|
|
260
|
+
guard let cert = SecCertificateCreateWithData(nil, der as CFData) else {
|
|
261
|
+
throw KeychainError(status: errSecParam)
|
|
262
|
+
}
|
|
263
|
+
let entry: [String: Any] = [
|
|
264
|
+
kSecClass as String: kSecClassCertificate,
|
|
265
|
+
kSecValueRef as String: cert,
|
|
266
|
+
kSecAttrLabel as String: "Internal root"
|
|
267
|
+
]
|
|
268
|
+
let status = SecItemAdd(entry as CFDictionary, nil)
|
|
269
|
+
guard status == errSecSuccess || status == errSecDuplicateItem else {
|
|
270
|
+
throw KeychainError(status: status)
|
|
271
|
+
}
|
|
283
272
|
}
|
|
284
|
-
// Extract components
|
|
285
|
-
var certificate: SecCertificate?
|
|
286
|
-
SecIdentityCopyCertificate(identity, &certificate)
|
|
287
|
-
var privateKey: SecKey?
|
|
288
|
-
SecIdentityCopyPrivateKey(identity, &privateKey)
|
|
289
273
|
```
|
|
290
274
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
---
|
|
275
|
+
A duplicate certificate is by definition the same certificate, so there is
|
|
276
|
+
nothing to update. Trust evaluation and pinning are in
|
|
277
|
+
[certificate-trust.md](certificate-trust.md).
|
|
296
278
|
|
|
297
|
-
##
|
|
279
|
+
## Identity
|
|
298
280
|
|
|
299
|
-
|
|
281
|
+
There is no identity table. The keychain presents an identity whenever a
|
|
282
|
+
certificate's `kSecAttrPublicKeyHash` equals a private key's
|
|
283
|
+
`kSecAttrApplicationLabel` (both the SHA-1 of the public key).
|
|
300
284
|
|
|
301
|
-
|
|
285
|
+
- `SecItemAdd` with `kSecClassIdentity` returns `errSecParam` (-50).
|
|
286
|
+
- Adding a certificate or a key can make an identity appear; deleting either
|
|
287
|
+
makes it disappear.
|
|
302
288
|
|
|
303
|
-
|
|
289
|
+
Three ways to get one:
|
|
304
290
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
### 5. No Duplicate Handling
|
|
316
|
-
|
|
317
|
-
AI code calls `SecItemAdd` without handling `errSecDuplicateItem`. The correct pattern either attempts update-first-then-add, or catches the duplicate error and calls `SecItemUpdate`:
|
|
291
|
+
1. Import a PKCS#12 file with `SecPKCS12Import`, passing the password under
|
|
292
|
+
`kSecImportExportPassphrase`. Take `kSecImportItemIdentity` from the result,
|
|
293
|
+
and split it with `SecIdentityCopyCertificate` and
|
|
294
|
+
`SecIdentityCopyPrivateKey` when you need the parts.
|
|
295
|
+
2. Add the certificate and the private key separately so their hashes match.
|
|
296
|
+
On macOS both must live in the same implementation (both data protection or
|
|
297
|
+
both file-based).
|
|
298
|
+
3. On macOS only, `SecIdentityCreateWithCertificate` finds the private key that
|
|
299
|
+
belongs to a certificate.
|
|
318
300
|
|
|
319
301
|
```swift
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
302
|
+
func importClientIdentity(p12: Data, password: String) throws -> SecIdentity {
|
|
303
|
+
let options = [kSecImportExportPassphrase as String: password]
|
|
304
|
+
var items: CFArray?
|
|
305
|
+
let status = SecPKCS12Import(p12 as CFData, options as CFDictionary, &items)
|
|
306
|
+
guard status == errSecSuccess,
|
|
307
|
+
let first = (items as? [[String: Any]])?.first,
|
|
308
|
+
let identity = first[kSecImportItemIdentity as String] else {
|
|
309
|
+
throw KeychainError(status: status)
|
|
310
|
+
}
|
|
311
|
+
return identity as! SecIdentity
|
|
326
312
|
}
|
|
327
313
|
```
|
|
328
314
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
315
|
+
(`SecIdentity` is a CF type, so the conditional cast always succeeds once the
|
|
316
|
+
value is present; the forced cast is the idiomatic way to bridge it.)
|
|
317
|
+
|
|
318
|
+
## Mistakes Generated Code Makes
|
|
319
|
+
|
|
320
|
+
1. A web login saved as a generic password with the domain in the service.
|
|
321
|
+
AutoFill never offers it. Use an internet password with `kSecAttrServer` and
|
|
322
|
+
associated domains.
|
|
323
|
+
2. Adding an identity directly. Import PKCS#12, or add certificate and key
|
|
324
|
+
separately.
|
|
325
|
+
3. Key queries without `kSecAttrKeyType`. Type is part of the key, so an RSA
|
|
326
|
+
key and an EC key with the same tag are both candidates, and the query can
|
|
327
|
+
return the wrong one.
|
|
328
|
+
4. Writing a human-readable string into `kSecAttrApplicationLabel`. The lookup
|
|
329
|
+
attribute is `kSecAttrApplicationTag`.
|
|
330
|
+
5. No duplicate handling. Save with add-or-update:
|
|
331
|
+
|
|
332
|
+
```swift
|
|
333
|
+
func upsert(match: [String: Any], payload: [String: Any]) throws {
|
|
334
|
+
let added = SecItemAdd(match.merging(payload) { _, new in new } as CFDictionary, nil)
|
|
335
|
+
switch added {
|
|
336
|
+
case errSecSuccess:
|
|
337
|
+
return
|
|
338
|
+
case errSecDuplicateItem:
|
|
339
|
+
let changed = SecItemUpdate(match as CFDictionary, payload as CFDictionary)
|
|
340
|
+
guard changed == errSecSuccess else { throw KeychainError(status: changed) }
|
|
341
|
+
default:
|
|
342
|
+
throw KeychainError(status: added)
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
`match` holds the class and primary-key attributes; `payload` holds the
|
|
348
|
+
value and the accessibility (accessibility is accepted in an update).
|
|
349
|
+
6. macOS code without `kSecUseDataProtectionKeychain`. It lands in the
|
|
350
|
+
file-based keychain, which ignores or mishandles `kSecAttrAccessible`,
|
|
351
|
+
`kSecAttrAccessGroup` and biometric access control.
|
|
352
|
+
7. Large blobs. Every read of `kSecValueData` pays for a Secure Enclave
|
|
353
|
+
decrypt. Past a few kilobytes, switch to envelope encryption (below).
|
|
354
|
+
|
|
355
|
+
## Platform Differences
|
|
356
|
+
|
|
357
|
+
- iOS, tvOS and watchOS have only the data protection keychain;
|
|
358
|
+
`kSecUseDataProtectionKeychain` is ignored there.
|
|
359
|
+
- A native macOS app defaults to the file-based keychain. Mac Catalyst defaults
|
|
360
|
+
to the data protection keychain.
|
|
361
|
+
- Apple recommends `kSecUseDataProtectionKeychain: true` on every operation.
|
|
362
|
+
`SecKeychainCreate` is deprecated as of macOS 12.
|
|
363
|
+
- TN3137 describes three macOS keychain APIs (the legacy Keychain API,
|
|
364
|
+
`SecKeychain`, and `SecItem`) over two implementations. Only `SecItem` can
|
|
365
|
+
reach either implementation; `SecKeychain` always works on the file-based one.
|
|
366
|
+
- On a Mac, reaching the data protection keychain needs a code-signed app
|
|
367
|
+
that carries a provisioning profile.
|
|
368
|
+
- The match limit a delete assumes differs by backend: all matching items on
|
|
369
|
+
the data protection keychain, only one on the file-based keychain
|
|
370
|
+
(reported to Apple as bug 105800863). Write delete queries that are precise
|
|
371
|
+
enough for either.
|
|
372
|
+
|
|
373
|
+
### Synchronization
|
|
374
|
+
|
|
375
|
+
- Items sync only when `kSecAttrSynchronizable` is set to `true`. Nothing syncs
|
|
376
|
+
by default.
|
|
377
|
+
- Every one of the five classes can sync from iOS 14, macOS 11 and watchOS 7; before
|
|
378
|
+
that, only the two password classes.
|
|
379
|
+
- A synchronizable item cannot use an accessibility value ending in
|
|
380
|
+
`ThisDeviceOnly`. Apple documents the combination as not allowed, and the add
|
|
381
|
+
fails with `errSecParam` (-50).
|
|
382
|
+
- Synchronizable items cannot be fetched through persistent references.
|
|
383
|
+
- tvOS accepts the attribute and never syncs.
|
|
384
|
+
- Updating or deleting a synced item changes it on every device.
|
|
385
|
+
|
|
386
|
+
Access groups and iCloud Keychain are covered in
|
|
387
|
+
[keychain-sharing.md](keychain-sharing.md).
|
|
388
|
+
|
|
389
|
+
## Size and When to Use a File
|
|
390
|
+
|
|
391
|
+
Apple publishes no hard limit and describes the keychain as a place for small
|
|
392
|
+
secrets. The SQLite column limit (`SQLITE_MAX_LENGTH`, about 16 MB) is not an
|
|
393
|
+
invitation.
|
|
394
|
+
|
|
395
|
+
- Query with the full composite key, use `kSecMatchLimitOne` when you expect
|
|
396
|
+
one item, and skip `kSecReturnData` when you only need to know an item exists.
|
|
397
|
+
- For bulk data, use envelope encryption: keep a data encryption key (AES-256,
|
|
398
|
+
32 bytes) in the keychain, encrypt the payload with it, and write the
|
|
399
|
+
ciphertext to a file that also has `NSFileProtection` set. OWASP MASTG
|
|
400
|
+
recommends this layout for MASVS L2.
|
|
401
|
+
- File protection counterparts: `kSecAttrAccessibleWhenUnlocked` matches
|
|
402
|
+
`NSFileProtectionComplete`; `kSecAttrAccessibleAfterFirstUnlock` matches
|
|
403
|
+
`NSFileProtectionCompleteUntilFirstUserAuthentication`.
|
|
404
|
+
|
|
405
|
+
Requesting data and attributes together returns a dictionary:
|
|
390
406
|
|
|
391
407
|
```swift
|
|
392
|
-
|
|
393
|
-
let query: [String: Any] = [
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
let created = attrs[kSecAttrCreationDate as String] as? Date
|
|
408
|
-
let modified = attrs[kSecAttrModificationDate as String] as? Date
|
|
409
|
-
let accessible = attrs[kSecAttrAccessible as String] as? String
|
|
410
|
-
let syncable = attrs[kSecAttrSynchronizable as String] as? Bool
|
|
411
|
-
let secretData = attrs[kSecValueData as String] as? Data
|
|
408
|
+
func describeLicense() throws -> [String: Any]? {
|
|
409
|
+
let query: [String: Any] = [
|
|
410
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
411
|
+
kSecAttrService as String: "com.example.editor",
|
|
412
|
+
kSecAttrAccount as String: "license",
|
|
413
|
+
kSecReturnAttributes as String: true,
|
|
414
|
+
kSecReturnData as String: true,
|
|
415
|
+
kSecMatchLimit as String: kSecMatchLimitOne
|
|
416
|
+
]
|
|
417
|
+
var out: CFTypeRef?
|
|
418
|
+
let status = SecItemCopyMatching(query as CFDictionary, &out)
|
|
419
|
+
if status == errSecItemNotFound { return nil }
|
|
420
|
+
guard status == errSecSuccess else { throw KeychainError(status: status) }
|
|
421
|
+
return out as? [String: Any]
|
|
422
|
+
}
|
|
412
423
|
```
|
|
413
424
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
425
|
+
The dictionary includes the account (`acct`), service (`svce`), creation and
|
|
426
|
+
modification dates, accessibility, the synchronizable flag, and the secret
|
|
427
|
+
under `kSecValueData`. Read them with the `kSec...` constants, not the short
|
|
428
|
+
strings. Switch to `kSecMatchLimitAll` and you get back a list of them.
|
|
417
429
|
|
|
418
|
-
##
|
|
430
|
+
## Tests That Prove the Class Is Right
|
|
419
431
|
|
|
420
|
-
|
|
|
421
|
-
|
|
|
422
|
-
| Add `kSecClassIdentity`
|
|
423
|
-
| Add
|
|
424
|
-
| Add
|
|
425
|
-
|
|
|
426
|
-
|
|
|
427
|
-
| Query
|
|
432
|
+
| Action | Expected status |
|
|
433
|
+
| --- | --- |
|
|
434
|
+
| Add with `kSecClassIdentity` | `errSecParam` |
|
|
435
|
+
| Add a key without `kSecAttrKeyType` | `errSecParam` |
|
|
436
|
+
| Add twice with the same primary key | `errSecDuplicateItem` |
|
|
437
|
+
| Add with synchronizable `true` and a `ThisDeviceOnly` value | `errSecParam` |
|
|
438
|
+
| Add a certificate with `kSecAttrApplicationTag` | `errSecParam` |
|
|
439
|
+
| Query a key without `kSecAttrKeyClass` | may match the wrong class |
|
|
428
440
|
|
|
429
|
-
|
|
441
|
+
The harness for these tests is in
|
|
442
|
+
[testing-security-code.md](testing-security-code.md).
|
|
430
443
|
|
|
431
|
-
##
|
|
432
|
-
|
|
433
|
-
If your application currently stores web credentials as `kSecClassGenericPassword`, migrate to `kSecClassInternetPassword` to enable AutoFill:
|
|
444
|
+
## Moving Web Logins to Internet Passwords
|
|
434
445
|
|
|
435
446
|
```swift
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
// 1. Query existing GenericPassword items for web credentials
|
|
439
|
-
let oldQuery: [String: Any] = [
|
|
447
|
+
func promoteWebLogins(oldService: String, host: String) throws {
|
|
448
|
+
let find: [String: Any] = [
|
|
440
449
|
kSecClass as String: kSecClassGenericPassword,
|
|
441
|
-
kSecAttrService as String:
|
|
450
|
+
kSecAttrService as String: oldService,
|
|
442
451
|
kSecReturnAttributes as String: true,
|
|
443
452
|
kSecReturnData as String: true,
|
|
444
453
|
kSecMatchLimit as String: kSecMatchLimitAll
|
|
445
454
|
]
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
guard
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
for
|
|
453
|
-
guard let
|
|
454
|
-
let
|
|
455
|
-
|
|
456
|
-
// 2. Add as InternetPassword with proper attributes
|
|
457
|
-
let newQuery: [String: Any] = [
|
|
455
|
+
var out: CFTypeRef?
|
|
456
|
+
let status = SecItemCopyMatching(find as CFDictionary, &out)
|
|
457
|
+
if status == errSecItemNotFound { return }
|
|
458
|
+
guard status == errSecSuccess, let rows = out as? [[String: Any]] else {
|
|
459
|
+
throw KeychainError(status: status)
|
|
460
|
+
}
|
|
461
|
+
for row in rows {
|
|
462
|
+
guard let user = row[kSecAttrAccount as String] as? String,
|
|
463
|
+
let secret = row[kSecValueData as String] as? Data else { continue }
|
|
464
|
+
let match: [String: Any] = [
|
|
458
465
|
kSecClass as String: kSecClassInternetPassword,
|
|
459
|
-
kSecAttrServer as String:
|
|
466
|
+
kSecAttrServer as String: host,
|
|
460
467
|
kSecAttrProtocol as String: kSecAttrProtocolHTTPS,
|
|
461
|
-
kSecAttrAccount as String:
|
|
462
|
-
kSecValueData as String: data,
|
|
468
|
+
kSecAttrAccount as String: user,
|
|
463
469
|
kSecUseDataProtectionKeychain as String: true
|
|
464
470
|
]
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
let
|
|
471
|
+
try upsert(match: match, payload: [
|
|
472
|
+
kSecValueData as String: secret,
|
|
473
|
+
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked
|
|
474
|
+
])
|
|
475
|
+
let removeOld: [String: Any] = [
|
|
470
476
|
kSecClass as String: kSecClassGenericPassword,
|
|
471
|
-
kSecAttrService as String:
|
|
472
|
-
kSecAttrAccount as String:
|
|
477
|
+
kSecAttrService as String: oldService,
|
|
478
|
+
kSecAttrAccount as String: user
|
|
473
479
|
]
|
|
474
|
-
SecItemDelete(
|
|
480
|
+
let removed = SecItemDelete(removeOld as CFDictionary)
|
|
481
|
+
guard removed == errSecSuccess || removed == errSecItemNotFound else {
|
|
482
|
+
throw KeychainError(status: removed)
|
|
483
|
+
}
|
|
475
484
|
}
|
|
476
485
|
}
|
|
477
486
|
```
|
|
478
487
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
-
|
|
485
|
-
-
|
|
486
|
-
-
|
|
487
|
-
-
|
|
488
|
-
|
|
489
|
-
-
|
|
490
|
-
-
|
|
491
|
-
-
|
|
492
|
-
-
|
|
493
|
-
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
2. **Composite primary key completeness** - Include all primary key attributes for the chosen class to avoid `errSecDuplicateItem` collisions and query misses
|
|
507
|
-
3. **kSecAttrGeneric is NOT a primary key** - Do not rely on it for uniqueness in GenericPassword items; it causes asymmetric add/query behavior
|
|
508
|
-
4. **ApplicationTag vs ApplicationLabel** - Use `kSecAttrApplicationTag` (developer-set, CFData) for key lookup; never manually set `kSecAttrApplicationLabel` (system-generated hash for identity formation)
|
|
509
|
-
5. **Identity creation via import** - Never `SecItemAdd` with `kSecClassIdentity`; use `SecPKCS12Import` or add matching certificate + key pairs separately
|
|
510
|
-
6. **Key type and size required** - Always specify `kSecAttrKeyType` and `kSecAttrKeySizeInBits` when creating or querying `kSecClassKey` items
|
|
511
|
-
7. **kSecUseDataProtectionKeychain on macOS** - Set to `true` for all operations to get iOS-identical behavior and avoid silent legacy keychain fallback
|
|
512
|
-
8. **Sync and accessibility agreement** - Never combine `kSecAttrSynchronizable: true` with "ThisDeviceOnly" accessibility values
|
|
513
|
-
9. **Small secrets only** - Use envelope encryption (DEK in keychain, ciphertext in `NSFileProtection`-guarded file) for data beyond a few KB
|
|
514
|
-
10. **Certificate attributes** - Never use `kSecAttrApplicationTag` with `kSecClassCertificate`; let the system extract metadata via `kSecValueRef`
|
|
515
|
-
11. **Duplicate handling** - Always handle `errSecDuplicateItem` with an update-first-then-add or delete-then-add pattern
|
|
488
|
+
The old item is removed only after the new one was written. Broader migration
|
|
489
|
+
guidance is in [migration-legacy-stores.md](migration-legacy-stores.md).
|
|
490
|
+
|
|
491
|
+
## Checklist
|
|
492
|
+
|
|
493
|
+
- Web-domain credentials are internet passwords.
|
|
494
|
+
- Every primary-key attribute for the class is set.
|
|
495
|
+
- Nothing depends on `kSecAttrGeneric` for uniqueness.
|
|
496
|
+
- Keys are found by `kSecAttrApplicationTag`; `kSecAttrApplicationLabel` is left
|
|
497
|
+
to the system.
|
|
498
|
+
- Identities come from `SecPKCS12Import` or a matching certificate and key.
|
|
499
|
+
- Key items always state type and size.
|
|
500
|
+
- macOS code sets `kSecUseDataProtectionKeychain: true`.
|
|
501
|
+
- Synchronizable items never use a `ThisDeviceOnly` accessibility value.
|
|
502
|
+
- Only small secrets go in the keychain; larger data uses envelope encryption.
|
|
503
|
+
- Certificates carry no application tag and get their metadata from
|
|
504
|
+
`kSecValueRef`.
|
|
505
|
+
- `errSecDuplicateItem` leads to `SecItemUpdate` (add, then update).
|
|
506
|
+
|
|
507
|
+
## Related Files
|
|
508
|
+
|
|
509
|
+
[keychain-fundamentals.md](keychain-fundamentals.md),
|
|
510
|
+
[keychain-access-control.md](keychain-access-control.md),
|
|
511
|
+
[biometric-authentication.md](biometric-authentication.md),
|
|
512
|
+
[credential-storage-patterns.md](credential-storage-patterns.md),
|
|
513
|
+
[common-anti-patterns.md](common-anti-patterns.md),
|
|
514
|
+
[compliance-owasp-mapping.md](compliance-owasp-mapping.md).
|