@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.
Files changed (264) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/LICENSE +0 -10
  3. package/docs/facts.json +1 -1
  4. package/manifest.json +266 -267
  5. package/package.json +2 -2
  6. package/pipeline/scripts/_notices.mjs +1 -1
  7. package/pipeline/skills/.skill-manifest.json +68 -68
  8. package/pipeline/skills/shared/README.md +70 -70
  9. package/pipeline/skills/shared/external/alarmkit/SKILL.md +373 -381
  10. package/pipeline/skills/shared/external/alarmkit/evals/evals.json +23 -18
  11. package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +328 -378
  12. package/pipeline/skills/shared/external/app-clips/SKILL.md +260 -160
  13. package/pipeline/skills/shared/external/app-clips/evals/evals.json +27 -27
  14. package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +150 -83
  15. package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +135 -83
  16. package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +143 -85
  17. package/pipeline/skills/shared/external/app-intents/SKILL.md +302 -304
  18. package/pipeline/skills/shared/external/app-intents/evals/evals.json +21 -21
  19. package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +594 -894
  20. package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +339 -277
  21. package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +27 -23
  22. package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +105 -122
  23. package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +143 -166
  24. package/pipeline/skills/shared/external/app-store-review/SKILL.md +307 -326
  25. package/pipeline/skills/shared/external/app-store-review/evals/evals.json +21 -21
  26. package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +105 -67
  27. package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +114 -101
  28. package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +333 -360
  29. package/pipeline/skills/shared/external/apple-on-device-ai/evals/evals.json +24 -27
  30. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +215 -322
  31. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +161 -256
  32. package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +277 -387
  33. package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +196 -210
  34. package/pipeline/skills/shared/external/authentication/SKILL.md +265 -381
  35. package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
  36. package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +133 -178
  37. package/pipeline/skills/shared/external/authentication/references/passkeys.md +111 -147
  38. package/pipeline/skills/shared/external/avkit/SKILL.md +267 -364
  39. package/pipeline/skills/shared/external/avkit/evals/evals.json +26 -26
  40. package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +375 -493
  41. package/pipeline/skills/shared/external/background-processing/SKILL.md +270 -382
  42. package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
  43. package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +169 -317
  44. package/pipeline/skills/shared/external/callkit-voip/SKILL.md +290 -371
  45. package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +24 -24
  46. package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +175 -343
  47. package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +292 -381
  48. package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +33 -30
  49. package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +227 -355
  50. package/pipeline/skills/shared/external/contacts-framework/SKILL.md +197 -346
  51. package/pipeline/skills/shared/external/contacts-framework/evals/evals.json +19 -21
  52. package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +169 -308
  53. package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +226 -376
  54. package/pipeline/skills/shared/external/core-bluetooth/evals/evals.json +25 -22
  55. package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +257 -337
  56. package/pipeline/skills/shared/external/core-data/SKILL.md +292 -368
  57. package/pipeline/skills/shared/external/core-data/evals/evals.json +30 -27
  58. package/pipeline/skills/shared/external/core-motion/SKILL.md +235 -324
  59. package/pipeline/skills/shared/external/core-motion/evals/evals.json +31 -27
  60. package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +210 -310
  61. package/pipeline/skills/shared/external/core-nfc/SKILL.md +292 -366
  62. package/pipeline/skills/shared/external/core-nfc/evals/evals.json +22 -24
  63. package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +233 -329
  64. package/pipeline/skills/shared/external/coreml/SKILL.md +323 -367
  65. package/pipeline/skills/shared/external/coreml/evals/evals.json +24 -21
  66. package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +562 -565
  67. package/pipeline/skills/shared/external/cryptokit/SKILL.md +253 -394
  68. package/pipeline/skills/shared/external/cryptokit/evals/evals.json +20 -18
  69. package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +299 -488
  70. package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +270 -323
  71. package/pipeline/skills/shared/external/debugging-instruments/evals/evals.json +27 -30
  72. package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +167 -315
  73. package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +140 -193
  74. package/pipeline/skills/shared/external/device-integrity/SKILL.md +230 -353
  75. package/pipeline/skills/shared/external/device-integrity/evals/evals.json +25 -21
  76. package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +159 -197
  77. package/pipeline/skills/shared/external/energykit/SKILL.md +225 -392
  78. package/pipeline/skills/shared/external/energykit/evals/evals.json +29 -28
  79. package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +174 -470
  80. package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +261 -383
  81. package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +25 -22
  82. package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +165 -268
  83. package/pipeline/skills/shared/external/healthkit/SKILL.md +252 -303
  84. package/pipeline/skills/shared/external/healthkit/evals/evals.json +24 -23
  85. package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +369 -523
  86. package/pipeline/skills/shared/external/homekit-matter/SKILL.md +233 -348
  87. package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +27 -22
  88. package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +199 -305
  89. package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +368 -340
  90. package/pipeline/skills/shared/external/ios-accessibility/evals/evals.json +28 -27
  91. package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +314 -260
  92. package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +97 -67
  93. package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +165 -101
  94. package/pipeline/skills/shared/external/ios-localization/SKILL.md +258 -371
  95. package/pipeline/skills/shared/external/ios-localization/evals/evals.json +23 -23
  96. package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +283 -491
  97. package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +313 -440
  98. package/pipeline/skills/shared/external/ios-networking/SKILL.md +265 -341
  99. package/pipeline/skills/shared/external/ios-networking/evals/evals.json +24 -24
  100. package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +425 -652
  101. package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +143 -285
  102. package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -53
  103. package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +231 -456
  104. package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +517 -784
  105. package/pipeline/skills/shared/external/ios-simulator/SKILL.md +265 -393
  106. package/pipeline/skills/shared/external/ios-simulator/evals/evals.json +21 -21
  107. package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +177 -270
  108. package/pipeline/skills/shared/external/live-activities/SKILL.md +318 -360
  109. package/pipeline/skills/shared/external/live-activities/evals/evals.json +21 -21
  110. package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +478 -710
  111. package/pipeline/skills/shared/external/mapkit-location/SKILL.md +295 -267
  112. package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +28 -24
  113. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +378 -532
  114. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +397 -499
  115. package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +165 -348
  116. package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +26 -23
  117. package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +123 -130
  118. package/pipeline/skills/shared/external/musickit-audio/SKILL.md +189 -315
  119. package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +22 -21
  120. package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +181 -270
  121. package/pipeline/skills/shared/external/natural-language/SKILL.md +188 -340
  122. package/pipeline/skills/shared/external/natural-language/evals/evals.json +21 -21
  123. package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +171 -225
  124. package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +258 -392
  125. package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +30 -29
  126. package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +164 -231
  127. package/pipeline/skills/shared/external/pdfkit/SKILL.md +312 -344
  128. package/pipeline/skills/shared/external/pdfkit/evals/evals.json +19 -19
  129. package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +413 -624
  130. package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +242 -358
  131. package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +25 -21
  132. package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +161 -226
  133. package/pipeline/skills/shared/external/permissionkit/SKILL.md +282 -400
  134. package/pipeline/skills/shared/external/permissionkit/evals/evals.json +27 -30
  135. package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +237 -350
  136. package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +276 -325
  137. package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +299 -545
  138. package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +344 -588
  139. package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +316 -660
  140. package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +270 -416
  141. package/pipeline/skills/shared/external/push-notifications/SKILL.md +312 -340
  142. package/pipeline/skills/shared/external/push-notifications/evals/evals.json +27 -26
  143. package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +328 -485
  144. package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +327 -560
  145. package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +218 -410
  146. package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +24 -27
  147. package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +221 -348
  148. package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +222 -393
  149. package/pipeline/skills/shared/external/shareplay-activities/evals/evals.json +23 -24
  150. package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +280 -420
  151. package/pipeline/skills/shared/external/speech-recognition/SKILL.md +217 -421
  152. package/pipeline/skills/shared/external/speech-recognition/evals/evals.json +23 -26
  153. package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +133 -125
  154. package/pipeline/skills/shared/external/storekit/SKILL.md +228 -204
  155. package/pipeline/skills/shared/external/storekit/evals/evals.json +27 -24
  156. package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +98 -109
  157. package/pipeline/skills/shared/external/storekit/references/core-patterns.md +298 -242
  158. package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +356 -649
  159. package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +274 -399
  160. package/pipeline/skills/shared/external/swift-api-design-guidelines/evals/evals.json +22 -24
  161. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +107 -108
  162. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +93 -165
  163. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +99 -137
  164. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +77 -120
  165. package/pipeline/skills/shared/external/swift-architecture/SKILL.md +334 -350
  166. package/pipeline/skills/shared/external/swift-architecture/evals/evals.json +22 -22
  167. package/pipeline/skills/shared/external/swift-charts/SKILL.md +208 -394
  168. package/pipeline/skills/shared/external/swift-charts/evals/evals.json +27 -30
  169. package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +351 -762
  170. package/pipeline/skills/shared/external/swift-codable/SKILL.md +339 -343
  171. package/pipeline/skills/shared/external/swift-codable/evals/evals.json +20 -20
  172. package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +303 -351
  173. package/pipeline/skills/shared/external/swift-concurrency/evals/evals.json +27 -24
  174. package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +65 -80
  175. package/pipeline/skills/shared/external/swift-concurrency/references/async-algorithms.md +48 -84
  176. package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +134 -79
  177. package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +145 -167
  178. package/pipeline/skills/shared/external/swift-concurrency/references/diagnostics.md +62 -50
  179. package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +92 -121
  180. package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +177 -241
  181. package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +258 -234
  182. package/pipeline/skills/shared/external/swift-language/SKILL.md +342 -382
  183. package/pipeline/skills/shared/external/swift-language/evals/evals.json +24 -27
  184. package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +79 -56
  185. package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +297 -340
  186. package/pipeline/skills/shared/external/swift-security/SKILL.md +180 -161
  187. package/pipeline/skills/shared/external/swift-security/evals/evals.json +25 -25
  188. package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +314 -469
  189. package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +408 -476
  190. package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +260 -530
  191. package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +270 -477
  192. package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +573 -571
  193. package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +370 -441
  194. package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +332 -433
  195. package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +346 -468
  196. package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +352 -472
  197. package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +431 -432
  198. package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +328 -425
  199. package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +341 -579
  200. package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +396 -457
  201. package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
  202. package/pipeline/skills/shared/external/swift-testing/SKILL.md +188 -175
  203. package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
  204. package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +80 -84
  205. package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
  206. package/pipeline/skills/shared/external/swiftdata/SKILL.md +392 -256
  207. package/pipeline/skills/shared/external/swiftdata/evals/evals.json +24 -24
  208. package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +206 -402
  209. package/pipeline/skills/shared/external/swiftdata/references/indexing.md +59 -52
  210. package/pipeline/skills/shared/external/swiftdata/references/predicate-pitfalls.md +57 -33
  211. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +354 -747
  212. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +300 -508
  213. package/pipeline/skills/shared/external/swiftlint/SKILL.md +175 -226
  214. package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +141 -208
  215. package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +100 -109
  216. package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +159 -179
  217. package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +383 -18
  218. package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +143 -229
  219. package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +283 -366
  220. package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +396 -608
  221. package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +336 -385
  222. package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +239 -349
  223. package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +228 -310
  224. package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +260 -249
  225. package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +92 -74
  226. package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +112 -177
  227. package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +61 -64
  228. package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +94 -134
  229. package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +193 -225
  230. package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +173 -327
  231. package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +193 -168
  232. package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
  233. package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +132 -133
  234. package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
  235. package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +106 -140
  236. package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +316 -252
  237. package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +341 -332
  238. package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +547 -854
  239. package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +485 -537
  240. package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +417 -499
  241. package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +213 -376
  242. package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +86 -175
  243. package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +89 -195
  244. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +95 -182
  245. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +71 -149
  246. package/pipeline/skills/shared/external/swiftui-performance/references/wwdc-session-sources.md +21 -27
  247. package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +303 -295
  248. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +204 -387
  249. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +469 -683
  250. package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +140 -186
  251. package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +75 -86
  252. package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +63 -60
  253. package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +69 -137
  254. package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +95 -67
  255. package/pipeline/skills/shared/external/tipkit/SKILL.md +220 -335
  256. package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
  257. package/pipeline/skills/shared/external/vision-framework/SKILL.md +260 -375
  258. package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +393 -515
  259. package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +363 -539
  260. package/pipeline/skills/shared/external/weatherkit/SKILL.md +152 -310
  261. package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
  262. package/pipeline/skills/shared/external/widgetkit/SKILL.md +216 -288
  263. package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +414 -719
  264. package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
@@ -1,515 +1,514 @@
1
1
  # Keychain Item Classes
2
2
 
3
- > Scope: Correct class selection and attribute usage for all five `kSecClass` types, with emphasis on uniqueness rules, AutoFill behavior, and migration safety.
4
-
5
- The five `kSecClass` types - GenericPassword, InternetPassword, Key, Certificate, and Identity - each serve distinct roles with unique attribute requirements that AI code generators routinely get wrong. **Choosing the wrong class** causes silent AutoFill failures, query collisions, and subtle security degradation. This reference covers every class with its composite primary key, required and optional attributes, correct Swift patterns, and the specific mistakes to watch for.
6
-
7
- The keychain is an encrypted SQLite database optimized for small secrets. Each item class defines a set of attributes forming a **composite primary key** - adding an item whose primary key matches an existing item returns `errSecDuplicateItem` (-25299). Understanding these primary keys is the single most important concept for correct keychain usage.
8
-
9
- **Sources:** Apple Keychain Services documentation, TN3137 ("On Mac keychain APIs and implementations"), Quinn "The Eskimo!" DTS posts ("SecItem: Fundamentals," "SecItem: Pitfalls and Best Practices"), Apple Platform Security Guide, WWDC 2022-2024 passkey sessions, OWASP MASVS/MASTG.
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
- - [Composite Primary Keys by Class](#composite-primary-keys-by-class)
16
- - [kSecClassGenericPassword - App-Specific Secrets](#ksecclassgenericpassword-app-specific-secrets)
17
- - [The kSecAttrGeneric trap](#the-ksecattrgeneric-trap)
18
- - [kSecClassInternetPassword - AutoFill and Credential Sharing](#ksecclassinternetpassword-autofill-and-credential-sharing)
19
- - [How AutoFill Integration Works](#how-autofill-integration-works)
20
- - [Protocol and Authentication Constants](#protocol-and-authentication-constants)
21
- - [Credential Provider Extensions (iOS 12+)](#credential-provider-extensions-ios-12)
22
- - [kSecClassKey - Cryptographic Key Management](#ksecclasskey-cryptographic-key-management)
23
- - [ApplicationTag vs ApplicationLabel - The Critical Distinction](#applicationtag-vs-applicationlabel-the-critical-distinction)
24
- - [SecKeyCreateRandomKey - The Preferred API](#seckeycreaterandomkey-the-preferred-api)
25
- - [CryptoKit Key Storage Mapping](#cryptokit-key-storage-mapping)
26
- - [kSecClassCertificate - DER-Encoded Certificate Storage](#ksecclasscertificate-der-encoded-certificate-storage)
27
- - [kSecClassIdentity - The Virtual Join](#ksecclassidentity-the-virtual-join)
28
- - [Why SecItemAdd with kSecClassIdentity Fails](#why-secitemadd-with-ksecclassidentity-fails)
29
- - [Three Correct Approaches to Creating an Identity](#three-correct-approaches-to-creating-an-identity)
30
- - [Correctness Issues AI Generators Get Wrong](#correctness-issues-ai-generators-get-wrong)
31
- - [1. GenericPassword for Everything](#1-genericpassword-for-everything)
32
- - [2. Direct Identity Creation](#2-direct-identity-creation)
33
- - [3. Missing Key Type Attributes](#3-missing-key-type-attributes)
34
- - [4. ApplicationTag / ApplicationLabel Confusion](#4-applicationtag-applicationlabel-confusion)
35
- - [5. No Duplicate Handling](#5-no-duplicate-handling)
36
- - [6. Missing kSecUseDataProtectionKeychain on macOS](#6-missing-ksecusedataprotectionkeychain-on-macos)
37
- - [7. Storing Large Blobs in Keychain](#7-storing-large-blobs-in-keychain)
38
- - [Modern API Patterns and Platform Differences](#modern-api-patterns-and-platform-differences)
39
- - [kSecUseDataProtectionKeychain Unifies Cross-Platform Behavior](#ksecusedataprotectionkeychain-unifies-cross-platform-behavior)
40
- - [TN3137 Key Takeaways](#tn3137-key-takeaways)
41
- - [iCloud Keychain Sync Restrictions](#icloud-keychain-sync-restrictions)
42
- - [Size Limits, Performance, and When to Use Files](#size-limits-performance-and-when-to-use-files)
43
- - [Performance Architecture](#performance-architecture)
44
- - [Envelope Encryption for Large Data](#envelope-encryption-for-large-data)
45
- - [Querying with kSecReturnAttributes for Metadata Inspection](#querying-with-ksecreturnattributes-for-metadata-inspection)
46
- - [Testing Matrix for Class Correctness](#testing-matrix-for-class-correctness)
47
- - [Migration: GenericPassword to InternetPassword](#migration-genericpassword-to-internetpassword)
48
- - [Cross-Reference Index](#cross-reference-index)
49
- - [Conclusion](#conclusion)
50
- - [Summary Checklist](#summary-checklist)
51
-
52
- ## Composite Primary Keys by Class
53
-
54
- Every `kSecClass` defines a specific attribute set forming its uniqueness constraint. `kSecAttrAccessGroup` and `kSecAttrSynchronizable` participate in the primary key for all classes.
55
-
56
- | Class | Primary Key Attributes | Typical Use |
57
- | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- |
58
- | **GenericPassword** | `kSecAttrService` + `kSecAttrAccount` | App-specific secrets, API tokens, encryption keys |
59
- | **InternetPassword** | `kSecAttrServer` + `kSecAttrProtocol` + `kSecAttrPort` + `kSecAttrPath` + `kSecAttrAccount` + `kSecAttrSecurityDomain` + `kSecAttrAuthenticationType` | Web credentials, server passwords, AutoFill |
60
- | **Key** | `kSecAttrApplicationLabel` + `kSecAttrApplicationTag` + `kSecAttrKeyClass` + `kSecAttrKeyType` + `kSecAttrKeySizeInBits` + `kSecAttrEffectiveKeySize` | RSA/EC keys, symmetric keys |
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
- // ✅ GenericPassword for an app-specific API token
82
- let addQuery: [String: Any] = [
83
- kSecClass as String: kSecClassGenericPassword,
84
- kSecAttrService as String: "com.myapp.api",
85
- kSecAttrAccount as String: "oauth-refresh-token",
86
- kSecValueData as String: tokenString.data(using: .utf8)!,
87
- kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
88
- kSecUseDataProtectionKeychain as String: true
89
- ]
90
- let status = SecItemAdd(addQuery as CFDictionary, nil)
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
- **When to choose GenericPassword vs InternetPassword:** If the credential belongs to a web domain and you want AutoFill, use InternetPassword. GenericPassword's `kSecAttrService` is an opaque string with no semantic meaning to the system - it cannot trigger Password AutoFill.
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
- InternetPassword exists specifically for credentials associated with network services. Its **7-attribute composite primary key** enables the system to match credentials to domains for Password AutoFill, Safari integration, and cross-device sync.
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
- Primary key attributes: `kSecAttrServer` (hostname), `kSecAttrProtocol` (e.g., `kSecAttrProtocolHTTPS`), `kSecAttrPort` (CFNumber), `kSecAttrPath` (URL path), `kSecAttrAccount` (username), `kSecAttrSecurityDomain` (HTTP realm), `kSecAttrAuthenticationType` (e.g., `kSecAttrAuthenticationTypeHTMLForm`).
87
+ ## Internet Password
102
88
 
103
- **Notable:** `kSecAttrGeneric` and `kSecAttrService` are **NOT available** for InternetPassword - the server/protocol/path attributes serve the equivalent purpose.
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
- ### How AutoFill Integration Works
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
- Password AutoFill matches credentials to apps and websites by comparing `kSecAttrServer` against associated domains. Full integration requires three pieces:
102
+ `kSecAttrService` and `kSecAttrGeneric` do not exist on this class.
108
103
 
109
- 1. **Store credentials as InternetPassword** with `kSecAttrServer` set to the website domain
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
- When all three are in place, the iOS QuickType bar automatically suggests matching credentials. Safari stores all web passwords as `kSecClassInternetPassword` - using GenericPassword for web credentials means the system can never suggest them for AutoFill.
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
- ```swift
116
- // ✅ InternetPassword enabling AutoFill
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
- // ❌ GenericPassword for web credentials - AutoFill will never find these
133
- let badQuery: [String: Any] = [
134
- kSecClass as String: kSecClassGenericPassword, // WRONG CLASS
135
- kSecAttrService as String: "example.com", // Opaque to AutoFill
136
- kSecAttrAccount as String: "user@example.com",
137
- kSecValueData as String: "password123".data(using: .utf8)!
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
- // ✅ EC key creation with SecKeyCreateRandomKey
169
- let tag = "com.myapp.keys.signing".data(using: .utf8)!
170
- let attributes: [String: Any] = [
171
- kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
172
- kSecAttrKeySizeInBits as String: 256,
173
- kSecPrivateKeyAttrs as String: [
174
- kSecAttrIsPermanent as String: true,
175
- kSecAttrApplicationTag as String: tag
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
- // ✅ Secure Enclave key with biometric protection
187
- var accessError: Unmanaged<CFError>?
188
- guard let accessControl = SecAccessControlCreateWithFlags(
189
- kCFAllocatorDefault,
190
- kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
191
- [.privateKeyUsage, .userPresence],
192
- &accessError
193
- ) else { throw accessError!.takeRetainedValue() as Error }
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
- var genError: Unmanaged<CFError>?
207
- guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &genError) else {
208
- throw genError!.takeRetainedValue() as Error
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
- Direct `SecItemAdd` for keys is appropriate only when importing - Apple's "import, then add" pattern via `SecKeyCreateWithData`. Always create a `SecKey` object first; avoid adding raw key data directly.
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
- // ✅ Adding a certificate
228
- guard let certificate = SecCertificateCreateWithData(nil, derData as CFData) else {
229
- throw CertificateError.invalidDER // Only accepts DER, not PEM
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
- When you pass a `SecCertificate` via `kSecValueRef`, the system **automatically extracts** `kSecAttrIssuer`, `kSecAttrSerialNumber`, `kSecAttrSubject`, `kSecAttrPublicKeyHash`, `kSecAttrCertificateType`, and `kSecAttrCertificateEncoding` from the certificate data. The critical attribute for identity formation is **`kSecAttrPublicKeyHash`** - the hash of the certificate's public key that must match a private key's `kSecAttrApplicationLabel`.
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
- **The keychain does not store digital identities as discrete items.** An identity is a logical join of a certificate and its matching private key, synthesized at query time when `kSecAttrPublicKeyHash` of a certificate matches `kSecAttrApplicationLabel` of a private key - both values being the SHA-1 hash of the public key.
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
- ### Why SecItemAdd with kSecClassIdentity Fails
232
+ Mapping CryptoKit keys to storage:
250
233
 
251
- Attempting `SecItemAdd` with `kSecClass: kSecClassIdentity` will fail with `errSecParam` (-50). The system has no "identity table." Identity items appear only when the matching relationship exists between separately stored certificate and key items.
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
- Cascading implications:
254
-
255
- - Adding a **certificate** can implicitly create an identity if a matching private key already exists
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
- ### Three Correct Approaches to Creating an Identity
244
+ ## Certificate
271
245
 
272
- **Method 1: PKCS#12 import** (most common for server-issued certificates):
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
- // ✅ Import identity from .p12 file
276
- let options: [String: Any] = [kSecImportExportPassphrase as String: "p12password"]
277
- var items: CFArray?
278
- let status = SecPKCS12Import(p12Data as CFData, options as CFDictionary, &items)
279
- guard status == errSecSuccess,
280
- let results = items as? [[String: Any]],
281
- let identity = results.first?[kSecImportItemIdentity as String] as? SecIdentity else {
282
- throw IdentityError.importFailed
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
- **Method 2: Add certificate and key separately** with matching public key hashes. Both items must be in the same keychain implementation - on macOS, both must use `kSecUseDataProtectionKeychain: true` or both must be in the file-based keychain.
292
-
293
- **Method 3: macOS-only** - `SecIdentityCreateWithCertificate` searches keychains for a matching private key given a certificate reference.
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
- ## Correctness Issues AI Generators Get Wrong
279
+ ## Identity
298
280
 
299
- ### 1. GenericPassword for Everything
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
- AI code almost universally uses `kSecClassGenericPassword` with `kSecAttrService` for web credentials, completely missing AutoFill integration. The correct pattern uses `kSecClassInternetPassword` with `kSecAttrServer` and associated domains.
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
- ### 2. Direct Identity Creation
289
+ Three ways to get one:
304
290
 
305
- Generated code attempts `SecItemAdd` with `kSecClassIdentity` as if it were a storable item class. Identities must be created through PKCS#12 import or by adding matching certificate and key items separately.
306
-
307
- ### 3. Missing Key Type Attributes
308
-
309
- Code omits `kSecAttrKeyType` from key queries, producing ambiguous matches across RSA and EC keys. Since key type is part of the composite primary key, this is a correctness bug, not a style issue.
310
-
311
- ### 4. ApplicationTag / ApplicationLabel Confusion
312
-
313
- Generated code sets `kSecAttrApplicationLabel` as a human-readable string, not understanding it is an auto-generated public key hash used for identity matching. The developer-set tag for lookup is `kSecAttrApplicationTag`.
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
- // ✅ Update-first pattern (preferred)
321
- var status = SecItemUpdate(searchQuery as CFDictionary, updateAttrs as CFDictionary)
322
- if status == errSecItemNotFound {
323
- var addQuery = searchQuery
324
- addQuery.merge(updateAttrs) { _, new in new }
325
- status = SecItemAdd(addQuery as CFDictionary, nil)
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
- ### 6. Missing kSecUseDataProtectionKeychain on macOS
330
-
331
- Without this flag, macOS defaults to the legacy file-based keychain, causing `kSecAttrAccessible`, `kSecAttrAccessGroup`, and biometric access controls to be silently ignored or behave unexpectedly.
332
-
333
- ### 7. Storing Large Blobs in Keychain
334
-
335
- The keychain is designed for small secrets. Storing large data degrades performance due to Secure Enclave decryption latency per item retrieval. Use envelope encryption for anything beyond a few KB.
336
-
337
- ---
338
-
339
- ## Modern API Patterns and Platform Differences
340
-
341
- ### kSecUseDataProtectionKeychain Unifies Cross-Platform Behavior
342
-
343
- On **iOS/tvOS/watchOS**, the data protection keychain is the only implementation - this flag is ignored. On **macOS native apps**, `SecItem` defaults to the legacy file-based keychain. Setting `kSecUseDataProtectionKeychain: true` switches to the data protection keychain, giving iOS-identical behavior. On **Mac Catalyst** apps, data protection is the default.
344
-
345
- Apple explicitly recommends setting this flag to `true` for all keychain operations. The file-based keychain is on the path to deprecation (`SecKeychainCreate` deprecated in macOS 12).
346
-
347
- ### TN3137 Key Takeaways
348
-
349
- TN3137 documents macOS's **three keychain APIs** (legacy Keychain, SecKeychain, and the recommended SecItem) and **two implementations** (file-based and data protection). Critical routing rule: only `SecItem` can target either implementation; `SecKeychain` always targets file-based. The data protection keychain requires code signing and a provisioning profile on macOS.
350
-
351
- Subtle behavioral difference: `SecItemDelete` defaults to `kSecMatchLimitAll` on the data protection keychain (deleting ALL matching items) but `kSecMatchLimitOne` on the file-based keychain - an inconsistency documented in Apple's bug tracker (r. 105800863).
352
-
353
- ### iCloud Keychain Sync Restrictions
354
-
355
- `kSecAttrSynchronizable` must be explicitly set to `true` - items never sync by default. **All five classes** support sync on iOS 14+ / macOS 11+ / watchOS 7+; earlier versions only sync password classes.
356
-
357
- Key restrictions:
358
-
359
- - Items with "ThisDeviceOnly" accessibility **cannot** sync - combining `kSecAttrSynchronizable: true` with any `ThisDeviceOnly` accessibility returns `errSecParam` (-50)
360
- - Persistent references are unsupported for synchronizable items
361
- - tvOS accepts the attribute but never actually syncs
362
- - Updates and deletes propagate to all copies across devices
363
-
364
- ---
365
-
366
- ## Size Limits, Performance, and When to Use Files
367
-
368
- Apple describes the keychain as storing **"small secrets"** without publishing a hard size limit. The underlying SQLite supports items up to approximately **16 MB** (`SQLITE_MAX_LENGTH`), but this is emphatically not the intended use.
369
-
370
- ### Performance Architecture
371
-
372
- The keychain has a direct performance implication: **metadata is encrypted with a cached Application Processor key** (enabling fast attribute queries), while **secret values require a Secure Enclave round trip** per access. This means `kSecReturnAttributes`-only queries are substantially faster than `kSecReturnData` queries. Always request only what you need.
373
-
374
- Query optimization rules:
375
-
376
- 1. **Use full composite keys** - broad queries force `securityd` to perform slow database scans
377
- 2. **Limit matches** - use `kSecMatchLimitOne` when expecting a single item to terminate search early
378
- 3. **Fetch metadata first** - if you only need to check existence, do not request `kSecReturnData`
379
-
380
- ### Envelope Encryption for Large Data
381
-
382
- For data beyond a few kilobytes, use the **DEK/KEK pattern**: store a 32-byte AES-256 Data Encryption Key (DEK) in the keychain, encrypt the actual data with that key, and write the ciphertext to a file protected by `NSFileProtection`. OWASP MASTG recommends this pattern for MASVS L2 compliance.
383
-
384
- Accessibility-to-file-protection mapping:
385
-
386
- - `kSecAttrAccessibleWhenUnlocked` → `NSFileProtectionComplete`
387
- - `kSecAttrAccessibleAfterFirstUnlock` → `NSFileProtectionCompleteUntilFirstUserAuthentication`
388
-
389
- ### Querying with kSecReturnAttributes for Metadata Inspection
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
- // ✅ Inspect all metadata for a GenericPassword item
393
- let query: [String: Any] = [
394
- kSecClass as String: kSecClassGenericPassword,
395
- kSecAttrService as String: "com.myapp.api",
396
- kSecReturnAttributes as String: true,
397
- kSecReturnData as String: true,
398
- kSecMatchLimit as String: kSecMatchLimitOne
399
- ]
400
- var item: CFTypeRef?
401
- let status = SecItemCopyMatching(query as CFDictionary, &item)
402
- guard status == errSecSuccess, let attrs = item as? [String: Any] else { return }
403
-
404
- // Available metadata in the returned dictionary:
405
- let account = attrs[kSecAttrAccount as String] as? String
406
- let service = attrs[kSecAttrService as String] as? String
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
- When both `kSecReturnAttributes` and `kSecReturnData` are true with `kSecMatchLimitOne`, the result is a single dictionary containing all metadata plus the secret data under `kSecValueData`. With `kSecMatchLimitAll`, it is an array of such dictionaries.
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
- ## Testing Matrix for Class Correctness
430
+ ## Tests That Prove the Class Is Right
419
431
 
420
- | Test Scenario | Expected OSStatus | Rationale |
421
- | -------------------------------------------------------- | ------------------------------ | ------------------------------------------------- |
422
- | Add `kSecClassIdentity` via `SecItemAdd` | `errSecParam` (-50) | Identities must be imported, not created directly |
423
- | Add `kSecClassKey` without `kSecAttrKeyType` | `errSecParam` (-50) | Crypto metadata is strictly required |
424
- | Add item with identical composite primary key | `errSecDuplicateItem` (-25299) | Requires `SecItemUpdate` or delete-then-add |
425
- | Sync `true` + `ThisDeviceOnly` accessibility | `errSecParam` (-50) | Contradictory constraints |
426
- | Use `kSecAttrApplicationTag` with `kSecClassCertificate` | `errSecParam` (-50) | Tag is only valid for key items |
427
- | Query `kSecClassKey` without `kSecAttrKeyClass` | May return wrong key class | Ambiguous match across public/private/symmetric |
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
- ## Migration: GenericPassword to InternetPassword
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
- // ✅ Migration pattern: GenericPassword → InternetPassword
437
- func migrateWebCredentials() throws {
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: "www.example.com",
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
- var items: CFTypeRef?
448
- let fetchStatus = SecItemCopyMatching(oldQuery as CFDictionary, &items)
449
- guard fetchStatus == errSecSuccess,
450
- let results = items as? [[String: Any]] else { return }
451
-
452
- for item in results {
453
- guard let account = item[kSecAttrAccount as String] as? String,
454
- let data = item[kSecValueData as String] as? Data else { continue }
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: "www.example.com",
466
+ kSecAttrServer as String: host,
460
467
  kSecAttrProtocol as String: kSecAttrProtocolHTTPS,
461
- kSecAttrAccount as String: account,
462
- kSecValueData as String: data,
468
+ kSecAttrAccount as String: user,
463
469
  kSecUseDataProtectionKeychain as String: true
464
470
  ]
465
- let addStatus = SecItemAdd(newQuery as CFDictionary, nil)
466
- guard addStatus == errSecSuccess || addStatus == errSecDuplicateItem else { continue }
467
-
468
- // 3. Delete old GenericPassword item
469
- let deleteQuery: [String: Any] = [
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: "www.example.com",
472
- kSecAttrAccount as String: account
477
+ kSecAttrService as String: oldService,
478
+ kSecAttrAccount as String: user
473
479
  ]
474
- SecItemDelete(deleteQuery as CFDictionary)
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
- ## Cross-Reference Index
482
-
483
- - **SecItem CRUD operations, query dictionaries, error handling** → `keychain-fundamentals.md`
484
- - **Accessibility constants, SecAccessControl flags** → `keychain-access-control.md`
485
- - **Biometric protection for keys and passwords** → `biometric-authentication.md`
486
- - **Secure Enclave key generation and constraints** → `secure-enclave.md`
487
- - **CryptoKit key types and keychain storage mapping** → `cryptokit-symmetric.md`, `cryptokit-public-key.md`
488
- - **OAuth tokens, API keys, credential lifecycle** → `credential-storage-patterns.md`
489
- - **Access groups, app extensions, sharing** → `keychain-sharing.md`
490
- - **SecCertificate, SecTrust, trust evaluation** → `certificate-trust.md`
491
- - **Legacy migration patterns** → `migration-legacy-stores.md`
492
- - **AI-generated code anti-patterns** → `common-anti-patterns.md`
493
- - **OWASP MASVS/MASTG compliance** → `compliance-owasp-mapping.md`
494
-
495
- ---
496
-
497
- ## Conclusion
498
-
499
- The keychain's five classes form a precise taxonomy: GenericPassword for app-local secrets, InternetPassword for domain-associated credentials enabling AutoFill, Key for cryptographic material with its tag/label distinction, Certificate for public X.509 data, and Identity as a virtual construct emerging from matching certificate and key items. The most impactful decisions are choosing InternetPassword over GenericPassword for web credentials, always setting `kSecUseDataProtectionKeychain` for cross-platform consistency, understanding that identities cannot be directly created, and recognizing `kSecAttrApplicationTag` (developer-set, for lookup) versus `kSecAttrApplicationLabel` (system-generated, for identity matching) as fundamentally different attributes despite their similar names.
500
-
501
- ---
502
-
503
- ## Summary Checklist
504
-
505
- 1. **Correct class selection** - Use `kSecClassInternetPassword` (not GenericPassword) for any credential associated with a web domain to enable AutoFill
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).