@mmerterden/multi-agent-pipeline 20.7.0 → 20.8.1
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 +21 -0
- package/LICENSE +0 -10
- package/docs/facts.json +1 -1
- package/manifest.json +285 -285
- package/package.json +3 -3
- package/pipeline/lib/redact.mjs +3 -2
- package/pipeline/scripts/_notices.mjs +1 -1
- package/pipeline/scripts/gen-skills-index.mjs +13 -1
- package/pipeline/scripts/pre-commit-check.sh +4 -0
- package/pipeline/skills/.skill-manifest.json +69 -69
- 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 +345 -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 +107 -121
- package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +145 -165
- package/pipeline/skills/shared/external/app-store-review/SKILL.md +306 -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 +335 -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 +277 -381
- package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +135 -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 +274 -384
- package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +173 -321
- 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 +228 -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 +302 -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 +236 -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/localization-reuse-map/SKILL.md +3 -3
- package/pipeline/skills/shared/external/localization-reuse-map/example-mapping.json +1 -1
- package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +8 -7
- package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md +2 -2
- package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +5 -2
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/_shared.py +100 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-artifact.py +45 -26
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-spreadsheet.py +14 -16
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-annotations.py +12 -5
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-legacy-labels.py +2 -1
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/publish-confluence.py +6 -5
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-overlay.py +44 -18
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-legacy-values.py +5 -2
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +10 -11
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/scan-screen-keys.py +4 -33
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/verify-map.py +12 -59
- package/pipeline/skills/shared/external/mapkit-location/SKILL.md +297 -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/skill-creator/template.md +7 -1
- 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 +302 -241
- 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 +304 -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 +183 -162
- 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 +411 -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 +375 -491
- 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 +397 -457
- package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
- package/pipeline/skills/shared/external/swift-testing/SKILL.md +191 -175
- package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
- package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +81 -84
- package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
- package/pipeline/skills/shared/external/swiftdata/SKILL.md +394 -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 +201 -168
- package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
- package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +134 -133
- package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
- package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +111 -138
- 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 +160 -315
- package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
- package/pipeline/skills/shared/external/widgetkit/SKILL.md +224 -288
- package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +416 -719
- package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
|
@@ -1,538 +1,467 @@
|
|
|
1
|
-
# CryptoKit
|
|
1
|
+
# CryptoKit: Signatures, Key Agreement, HPKE and Post-Quantum
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Topics: digital signatures (ECDSA, EdDSA), Diffie-Hellman over elliptic curves,
|
|
4
|
+
hybrid public-key encryption from iOS 17,
|
|
5
|
+
ML-KEM and ML-DSA with hybrid migration (iOS 26+), key serialization, and where the
|
|
6
|
+
Secure Enclave fits.
|
|
6
7
|
|
|
7
|
-
CryptoKit's
|
|
8
|
+
CryptoKit's types carry several rules for you: a signing key cannot perform key
|
|
9
|
+
agreement, a shared secret cannot be used as a key until it is run through a KDF,
|
|
10
|
+
and the only classical curve the Secure Enclave accepts is P-256.
|
|
8
11
|
|
|
9
|
-
CryptoKit
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
Background: CryptoKit arrived at WWDC 2019 (session 709) as a Swift replacement for
|
|
13
|
+
the C-level `SecKey` API. It sits on corecrypto and zeroes private key memory on
|
|
14
|
+
deallocation. iOS 14 added PEM/DER import and export plus standalone HKDF. iOS 17
|
|
15
|
+
added HPKE (RFC 9180). iOS 26 (WWDC 2025 session 314) added formally verified
|
|
16
|
+
post-quantum algorithms and turned on post-quantum TLS by default.
|
|
12
17
|
|
|
13
18
|
## Contents
|
|
14
19
|
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
|
73
|
-
|
|
|
74
|
-
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
| Algorithm | Security | iOS | Secure Enclave | Pub Key Size | Best For |
|
|
84
|
-
| ----------- | -------- | --- | -------------- | ------------ | ------------------------------- |
|
|
85
|
-
| P256 | ~128-bit | 13+ | ✅ Yes | 64 bytes | Hardware keys, NIST compliance |
|
|
86
|
-
| P384 | ~192-bit | 13+ | ❌ No | 96 bytes | Government/compliance |
|
|
87
|
-
| P521 | ~256-bit | 13+ | ❌ No | 132 bytes | Maximum classical security |
|
|
88
|
-
| Curve25519 | ~128-bit | 13+ | ❌ No | 32 bytes | Modern protocols, software keys |
|
|
89
|
-
| ML-KEM-768 | ~AES-128 | 26+ | ✅ Yes | 1,184 bytes | Key encapsulation |
|
|
90
|
-
| ML-KEM-1024 | ~AES-192 | 26+ | ✅ Yes | 1,568 bytes | Higher-security KEM |
|
|
91
|
-
| ML-DSA-65 | ~AES-128 | 26+ | ✅ Yes | 1,952 bytes | Post-quantum signatures |
|
|
92
|
-
| ML-DSA-87 | ~AES-192 | 26+ | ✅ Yes | 2,592 bytes | Higher-security signatures |
|
|
93
|
-
| X-Wing | Hybrid | 26+ | ✅ Yes | 1,216 bytes | Hybrid PQC KEM |
|
|
94
|
-
|
|
95
|
-
On Apple Silicon, both P256 and Curve25519 are heavily optimized in corecrypto with hand-tuned assembly. Performance differences are negligible for most applications - Apple's NISTZ256 optimization closes the gap that Curve25519 holds in non-Apple benchmarks.
|
|
96
|
-
|
|
97
|
-
---
|
|
98
|
-
|
|
99
|
-
## Signing and Key Agreement Are Separate Type Hierarchies
|
|
100
|
-
|
|
101
|
-
CryptoKit's most important design decision is splitting each curve into two non-interchangeable type families: `Signing` and `KeyAgreement`. A `P256.Signing.PrivateKey` cannot perform key agreement. A `Curve25519.KeyAgreement.PrivateKey` cannot sign. The compiler enforces this at build time. AI generators frequently conflate these, producing code that fails to compile.
|
|
102
|
-
|
|
103
|
-
### ✅ Correct: P256 key generation, signing, and verification
|
|
20
|
+
- [Picking a curve or algorithm](#picking-a-curve-or-algorithm)
|
|
21
|
+
- [Signing and key agreement are different types](#signing-and-key-agreement-are-different-types)
|
|
22
|
+
- [Key agreement always goes through a KDF](#key-agreement-always-goes-through-a-kdf)
|
|
23
|
+
- [HPKE (iOS 17+)](#hpke-ios-17)
|
|
24
|
+
- [Post-quantum cryptography (iOS 26+)](#post-quantum-cryptography-ios-26)
|
|
25
|
+
- [PEM and DER (iOS 14+)](#pem-and-der-ios-14)
|
|
26
|
+
- [The Secure Enclave, briefly](#the-secure-enclave-briefly)
|
|
27
|
+
- [RSA](#rsa)
|
|
28
|
+
- [Frequent mistakes](#frequent-mistakes)
|
|
29
|
+
- [Availability](#availability)
|
|
30
|
+
- [Performance and threads](#performance-and-threads)
|
|
31
|
+
- [Sources](#sources)
|
|
32
|
+
- [Summary](#summary)
|
|
33
|
+
- [Checklist](#checklist)
|
|
34
|
+
|
|
35
|
+
## Picking a curve or algorithm
|
|
36
|
+
|
|
37
|
+
Two errors show up often: suggesting Curve25519 when the key must live in the
|
|
38
|
+
Secure Enclave, and suggesting P-256 when the goal is simple constant-time software
|
|
39
|
+
keys.
|
|
40
|
+
|
|
41
|
+
- **P256 (secp256r1).** The only classical curve the Secure Enclave supports, so
|
|
42
|
+
required for hardware keys with a biometric access control. NIST FIPS 186-5, the
|
|
43
|
+
widest TLS, X.509 and server interop. Raw public key 64 bytes (uncompressed x||y),
|
|
44
|
+
raw signature 64 bytes (r||s). PEM/DER from iOS 14.
|
|
45
|
+
- **Curve25519** (X25519 for agreement, Ed25519 for signing). The default for
|
|
46
|
+
software-only keys: fixed, rigid parameters, constant time by construction, no
|
|
47
|
+
point validation to forget, 32-byte public keys. Only `rawRepresentation` exists
|
|
48
|
+
(no PEM, DER or x963). Not available in the Secure Enclave.
|
|
49
|
+
- **P384** and **P521**, roughly 192-bit and 256-bit strength (NIST levels 3 and 5).
|
|
50
|
+
They mirror the P256 API. Pick them only because an external standard or auditor
|
|
51
|
+
insists.
|
|
52
|
+
- **ML-KEM-768** (roughly AES-128 strength) and **ML-KEM-1024** (roughly AES-192).
|
|
53
|
+
Lattice KEM from FIPS 203. Secure Enclave capable on iOS 26+.
|
|
54
|
+
- **ML-DSA-65** (roughly AES-128) and **ML-DSA-87** (roughly AES-192). Lattice
|
|
55
|
+
signatures from FIPS 204. Secure Enclave capable on iOS 26+.
|
|
56
|
+
- **X-Wing** (`XWingMLKEM768X25519`). A hybrid of ML-KEM-768 and X25519; an attacker
|
|
57
|
+
must break both. Apple's recommended path for migrating custom protocols, used
|
|
58
|
+
through HPKE.
|
|
59
|
+
|
|
60
|
+
| Need | Choose |
|
|
61
|
+
| --- | --- |
|
|
62
|
+
| Key must never leave hardware | `SecureEnclave.P256.*` |
|
|
63
|
+
| Software signing or agreement | `Curve25519.*` |
|
|
64
|
+
| FIPS or enterprise interop | `P256` or `P384` |
|
|
65
|
+
| End-to-end encryption, iOS 17+ | HPKE `Curve25519_SHA256_ChachaPoly` |
|
|
66
|
+
| End-to-end encryption resistant to harvest-now-decrypt-later, iOS 26+ | HPKE `XWingMLKEM768X25519_SHA256_AES_GCM_256` |
|
|
67
|
+
| Highest classical level | `P521`, only if mandated |
|
|
68
|
+
|
|
69
|
+
| Algorithm | Public key | Secure Enclave |
|
|
70
|
+
| --- | --- | --- |
|
|
71
|
+
| P256 | 64 B | yes |
|
|
72
|
+
| P384 | 96 B | no |
|
|
73
|
+
| P521 | 132 B | no |
|
|
74
|
+
| Curve25519 | 32 B | no |
|
|
75
|
+
| ML-KEM-768 | 1,184 B | yes (iOS 26) |
|
|
76
|
+
| ML-KEM-1024 | 1,568 B | yes (iOS 26) |
|
|
77
|
+
| ML-DSA-65 | 1,952 B | yes (iOS 26) |
|
|
78
|
+
| ML-DSA-87 | 2,592 B | yes (iOS 26) |
|
|
79
|
+
| X-Wing | 1,216 B | yes (iOS 26) |
|
|
80
|
+
|
|
81
|
+
Speed is not a reason to prefer one of P256 and Curve25519 on Apple Silicon;
|
|
82
|
+
corecrypto's NISTZ256 code makes the difference negligible.
|
|
83
|
+
|
|
84
|
+
## Signing and key agreement are different types
|
|
85
|
+
|
|
86
|
+
Each curve has two separate families, `Signing` and `KeyAgreement`, and the compiler
|
|
87
|
+
will not let you mix them.
|
|
104
88
|
|
|
105
89
|
```swift
|
|
106
90
|
import CryptoKit
|
|
91
|
+
import Foundation
|
|
107
92
|
|
|
108
|
-
|
|
109
|
-
let
|
|
110
|
-
let verifyingKey = signingKey.publicKey // P256.Signing.PublicKey
|
|
111
|
-
|
|
112
|
-
// Sign data (CryptoKit hashes internally with SHA-256)
|
|
113
|
-
let message = Data("Transfer $100 to Alice".utf8)
|
|
114
|
-
let signature = try signingKey.signature(for: message)
|
|
115
|
-
// signature is P256.Signing.ECDSASignature
|
|
116
|
-
|
|
117
|
-
// Verify
|
|
118
|
-
let isValid = verifyingKey.isValidSignature(signature, for: message)
|
|
119
|
-
|
|
120
|
-
// Signature serialization
|
|
121
|
-
let derSig = signature.derRepresentation // ASN.1 DER (interoperable)
|
|
122
|
-
let rawSig = signature.rawRepresentation // Raw r‖s concatenation (64 bytes)
|
|
123
|
-
let restored = try P256.Signing.ECDSASignature(derRepresentation: derSig)
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
For pre-hashed data (when the digest is computed externally), use `signature(for:)` with a `Digest` parameter or the `SHA256Digest` directly.
|
|
127
|
-
|
|
128
|
-
### ❌ Wrong: Mixing signing and key agreement key types
|
|
93
|
+
let releaseSigner = P256.Signing.PrivateKey()
|
|
94
|
+
let verifierKey: P256.Signing.PublicKey = releaseSigner.publicKey
|
|
129
95
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
let key = P256.Signing.PrivateKey()
|
|
133
|
-
let shared = try key.sharedSecretFromKeyAgreement(with: otherPublicKey)
|
|
134
|
-
// Error: P256.Signing.PrivateKey has no member 'sharedSecretFromKeyAgreement'
|
|
96
|
+
let artifact = Data("build 5120 checksum".utf8)
|
|
97
|
+
let signature = try releaseSigner.signature(for: artifact)
|
|
135
98
|
|
|
136
|
-
|
|
99
|
+
let valid = verifierKey.isValidSignature(signature, for: artifact)
|
|
100
|
+
let asn1 = signature.derRepresentation
|
|
101
|
+
let compact = signature.rawRepresentation
|
|
102
|
+
let decoded = try P256.Signing.ECDSASignature(derRepresentation: asn1)
|
|
137
103
|
```
|
|
138
104
|
|
|
139
|
-
|
|
105
|
+
`signature(for:)` hashes `Data` with SHA-256 internally and returns a
|
|
106
|
+
`P256.Signing.ECDSASignature`. `derRepresentation` is the ASN.1 form most servers
|
|
107
|
+
expect; `rawRepresentation` is the 64-byte r||s form. If you already have a digest,
|
|
108
|
+
pass the `SHA256Digest` (any `Digest`) to `signature(for:)` instead.
|
|
140
109
|
|
|
141
|
-
|
|
110
|
+
Calling `sharedSecretFromKeyAgreement` on a `P256.Signing.PrivateKey`, or
|
|
111
|
+
`signature(for:)` on a `Curve25519.KeyAgreement.PrivateKey`, is a compile error.
|
|
112
|
+
Generate one key per purpose.
|
|
142
113
|
|
|
143
|
-
|
|
114
|
+
## Key agreement always goes through a KDF
|
|
144
115
|
|
|
145
|
-
|
|
116
|
+
An ECDH `SharedSecret` is not uniformly random and cannot be turned into a
|
|
117
|
+
`SymmetricKey` directly. Apple's documentation says it is not suitable as a key on
|
|
118
|
+
its own; the two sanctioned routes are `hkdfDerivedSymmetricKey` and
|
|
119
|
+
`x963DerivedSymmetricKey`.
|
|
146
120
|
|
|
147
121
|
```swift
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
// Both parties generate key agreement keys (NOT signing keys)
|
|
151
|
-
let aliceKey = Curve25519.KeyAgreement.PrivateKey()
|
|
152
|
-
let bobKey = Curve25519.KeyAgreement.PrivateKey()
|
|
122
|
+
let deviceKey = Curve25519.KeyAgreement.PrivateKey()
|
|
123
|
+
let serverKey = Curve25519.KeyAgreement.PrivateKey()
|
|
153
124
|
|
|
154
|
-
|
|
155
|
-
let
|
|
156
|
-
with: bobKey.publicKey
|
|
157
|
-
)
|
|
158
|
-
|
|
159
|
-
// CRITICAL: Derive a symmetric key via HKDF - never use SharedSecret directly
|
|
160
|
-
let symmetricKey = sharedSecret.hkdfDerivedSymmetricKey(
|
|
125
|
+
let secret = try deviceKey.sharedSecretFromKeyAgreement(with: serverKey.publicKey)
|
|
126
|
+
let channelKey = secret.hkdfDerivedSymmetricKey(
|
|
161
127
|
using: SHA256.self,
|
|
162
|
-
salt: Data("
|
|
163
|
-
sharedInfo: Data("
|
|
164
|
-
outputByteCount: 32
|
|
128
|
+
salt: Data("pairing-2026".utf8),
|
|
129
|
+
sharedInfo: Data("telemetry-channel/v1/encrypt".utf8),
|
|
130
|
+
outputByteCount: 32
|
|
165
131
|
)
|
|
166
|
-
|
|
167
|
-
// Now use the derived key for authenticated encryption
|
|
168
|
-
let sealed = try ChaChaPoly.seal(plaintext, using: symmetricKey)
|
|
132
|
+
let packet = try ChaChaPoly.seal(Data("temp=21.5".utf8), using: channelKey)
|
|
169
133
|
```
|
|
170
134
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
### ❌ Wrong: Using SharedSecret directly as an encryption key
|
|
135
|
+
`sharedInfo` ties the key to your protocol and role. Derive separate keys with
|
|
136
|
+
different `sharedInfo` for encryption and for authentication.
|
|
174
137
|
|
|
175
138
|
```swift
|
|
176
|
-
//
|
|
177
|
-
let
|
|
178
|
-
|
|
179
|
-
// SharedSecret is NOT a SymmetricKey and cannot be used as one directly.
|
|
180
|
-
// Its byte distribution is non-uniform (only ~2^255 of 2^256 values are
|
|
181
|
-
// valid P-256 x-coordinates). Skipping HKDF also prevents protocol binding
|
|
182
|
-
// and removes the salt's entropy-concentration benefit.
|
|
183
|
-
|
|
184
|
-
// This forced extraction is dangerous:
|
|
185
|
-
let insecureKey = SymmetricKey(data: sharedSecret.withUnsafeBytes { Data($0) })
|
|
186
|
-
// ⚠️ Non-uniform key material, no domain separation, no salt
|
|
139
|
+
// Broken: raw secret bytes used as a key
|
|
140
|
+
let bad = SymmetricKey(data: secret.withUnsafeBytes { Data($0) })
|
|
187
141
|
```
|
|
188
142
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
Before iOS 17, encrypting data for a recipient's public key required manually implementing ECIES: perform ECDH, derive a key via HKDF, encrypt with AES-GCM, and transmit the ephemeral public key alongside the ciphertext. HPKE (RFC 9180) packages this entire flow into a single API. CryptoKit supports all four RFC modes - Base, Auth, PSK, and AuthPSK - with five built-in cipher suites.
|
|
143
|
+
This skips extraction over non-uniform material (on P-256 roughly half of all 256-bit strings can
|
|
144
|
+
occur as an x-coordinate, so the bits are biased), and has no salt and no domain
|
|
145
|
+
separation.
|
|
194
146
|
|
|
195
|
-
|
|
147
|
+
## HPKE (iOS 17+)
|
|
196
148
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
| `.P384_SHA384_AES_GCM_256` | P-384 | HKDF-SHA384 | AES-GCM-256 | 17+ |
|
|
202
|
-
| `.P521_SHA512_AES_GCM_256` | P-521 | HKDF-SHA512 | AES-GCM-256 | 17+ |
|
|
203
|
-
| `.XWingMLKEM768X25519_SHA256_AES_GCM_256` | X-Wing hybrid | HKDF-SHA256 | AES-GCM-256 | 26+ |
|
|
149
|
+
Before iOS 17, encrypting to a public key meant building ECIES by hand: ECDH, HKDF,
|
|
150
|
+
AES-GCM, and shipping the ephemeral public key alongside. HPKE (RFC 9180) packages
|
|
151
|
+
that. Every mode the RFC defines is available (base, sender-authenticated, pre-shared key,
|
|
152
|
+
and the combination of the last two), and five suites come predefined:
|
|
204
153
|
|
|
205
|
-
|
|
154
|
+
| Suite name | KEM | KDF | AEAD | Since |
|
|
155
|
+
| --- | --- | --- | --- | --- |
|
|
156
|
+
| `.Curve25519_SHA256_ChachaPoly` | X25519 | SHA-256 HKDF | ChaCha20 with Poly1305 | 17 |
|
|
157
|
+
| `.P256_SHA256_AES_GCM_256` | NIST P-256 | SHA-256 HKDF | AES-GCM, 256-bit key | 17 |
|
|
158
|
+
| `.P384_SHA384_AES_GCM_256` | NIST P-384 | SHA-384 HKDF | AES-GCM, 256-bit key | 17 |
|
|
159
|
+
| `.P521_SHA512_AES_GCM_256` | NIST P-521 | SHA-512 HKDF | AES-GCM, 256-bit key | 17 |
|
|
160
|
+
| `.XWingMLKEM768X25519_SHA256_AES_GCM_256` | X-Wing hybrid | SHA-256 HKDF | AES-GCM, 256-bit key | 26 |
|
|
206
161
|
|
|
207
|
-
|
|
162
|
+
Other combinations are built with `HPKE.Ciphersuite(kem:kdf:aead:)`, for example a
|
|
163
|
+
P-384 KEM with the SHA-384 KDF and ChaChaPoly:
|
|
164
|
+
`HPKE.Ciphersuite(kem: .P384_HKDF_SHA384, kdf: .HKDF_SHA384, aead: .chaChaPoly)`.
|
|
208
165
|
|
|
209
166
|
```swift
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
let
|
|
213
|
-
let
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
167
|
+
@available(iOS 17.0, macOS 14.0, *)
|
|
168
|
+
func exchangeMedicalNote() throws -> Data {
|
|
169
|
+
let suite = HPKE.Ciphersuite.Curve25519_SHA256_ChachaPoly
|
|
170
|
+
let clinicKey = Curve25519.KeyAgreement.PrivateKey()
|
|
171
|
+
let context = Data("clinic-inbox/v3".utf8)
|
|
172
|
+
let header = Data("patient=5521".utf8)
|
|
173
|
+
|
|
174
|
+
var outbound = try HPKE.Sender(
|
|
175
|
+
recipientKey: clinicKey.publicKey, ciphersuite: suite, info: context
|
|
176
|
+
)
|
|
177
|
+
let ciphertext = try outbound.seal(Data("allergy: penicillin".utf8), authenticating: header)
|
|
178
|
+
let encapsulated = outbound.encapsulatedKey
|
|
218
179
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
)
|
|
226
|
-
let ciphertext = try sender.seal(
|
|
227
|
-
Data("Confidential document".utf8),
|
|
228
|
-
authenticating: Data("metadata".utf8) // optional AAD
|
|
229
|
-
)
|
|
230
|
-
let encapsulatedKey = sender.encapsulatedKey // MUST be sent with ciphertext
|
|
231
|
-
|
|
232
|
-
// === RECIPIENT ===
|
|
233
|
-
var recipient = try HPKE.Recipient(
|
|
234
|
-
privateKey: recipientPrivateKey,
|
|
235
|
-
ciphersuite: ciphersuite,
|
|
236
|
-
info: info,
|
|
237
|
-
encapsulatedKey: encapsulatedKey // from sender
|
|
238
|
-
)
|
|
239
|
-
let plaintext = try recipient.open(
|
|
240
|
-
ciphertext,
|
|
241
|
-
authenticating: Data("metadata".utf8) // same AAD
|
|
242
|
-
)
|
|
180
|
+
var inbound = try HPKE.Recipient(
|
|
181
|
+
privateKey: clinicKey, ciphersuite: suite, info: context,
|
|
182
|
+
encapsulatedKey: encapsulated
|
|
183
|
+
)
|
|
184
|
+
return try inbound.open(ciphertext, authenticating: header)
|
|
185
|
+
}
|
|
243
186
|
```
|
|
244
187
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
1. **The encapsulated key is not embedded in the ciphertext.** Your protocol must transmit `encapsulatedKey` alongside the ciphertext. Losing it means permanent decryption failure.
|
|
248
|
-
|
|
249
|
-
2. **`HPKE.Sender` and `HPKE.Recipient` are stateful structs that must be declared with `var`** because `seal()` and `open()` are mutating methods - they increment an internal nonce counter. Using `let` causes a compiler error.
|
|
188
|
+
Three things break HPKE code:
|
|
250
189
|
|
|
251
|
-
|
|
190
|
+
1. `encapsulatedKey` is not part of the ciphertext. You must send it with the
|
|
191
|
+
message. Without it nothing can be decrypted, ever.
|
|
192
|
+
2. `Sender` and `Recipient` are structs with state (an internal nonce counter), and
|
|
193
|
+
`seal` / `open` are `mutating`. Declaring them with `let` does not compile.
|
|
194
|
+
3. Messages must be opened in the order they were sealed; out-of-order opens fail
|
|
195
|
+
because the counters disagree.
|
|
252
196
|
|
|
253
|
-
|
|
197
|
+
## Post-quantum cryptography (iOS 26+)
|
|
254
198
|
|
|
255
|
-
|
|
199
|
+
The threat is harvest now, decrypt later: traffic recorded today is decrypted once
|
|
200
|
+
a large quantum computer exists. From iOS 26, `URLSession` and Network.framework
|
|
201
|
+
offer the hybrid `X25519MLKEM768` group in the TLS ClientHello by default.
|
|
256
202
|
|
|
257
|
-
|
|
203
|
+
CryptoKit adds five types, formally verified against the FIPS specifications, all
|
|
204
|
+
usable in the Secure Enclave:
|
|
258
205
|
|
|
259
|
-
|
|
206
|
+
| Type | Public key | Output |
|
|
207
|
+
| --- | --- | --- |
|
|
208
|
+
| `MLKEM768` | 1,184 B | ciphertext 1,088 B |
|
|
209
|
+
| `MLKEM1024` | 1,568 B | |
|
|
210
|
+
| `XWingMLKEM768X25519` (draft-connolly-cfrg-xwing-kem) | 1,216 B | encapsulation 1,120 B |
|
|
211
|
+
| `MLDSA65` | 1,952 B | signature 3,309 B |
|
|
212
|
+
| `MLDSA87` | 2,592 B | signature 4,627 B |
|
|
260
213
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
| `MLKEM1024` | ML-KEM-1024 | FIPS 203 | Key encapsulation | ✅ | 1,568 B pub |
|
|
265
|
-
| `XWingMLKEM768X25519` | X-Wing hybrid | draft-connolly-cfrg-xwing-kem | Key encapsulation | ✅ | 1,216 B pub / 1,120 B encap |
|
|
266
|
-
| `MLDSA65` | ML-DSA-65 | FIPS 204 | Digital signatures | ✅ | 1,952 B pub / 3,309 B sig |
|
|
267
|
-
| `MLDSA87` | ML-DSA-87 | FIPS 204 | Digital signatures | ✅ | 2,592 B pub / 4,627 B sig |
|
|
214
|
+
What you pay is bytes on the wire, not CPU. Compare 3,309 bytes per ML-DSA-65
|
|
215
|
+
signature with 64 per Ed25519 signature, and 1,184 bytes of ML-KEM-768 public key
|
|
216
|
+
with 32 for X25519. Timing is in the same range as the classical algorithms.
|
|
268
217
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
Key encapsulation differs fundamentally from Diffie-Hellman key agreement. In ECDH, both parties contribute public keys. In KEM, only the recipient has a key pair - the sender calls `encapsulate()` on the public key, which produces both a shared secret and an opaque ciphertext that only the private key can decapsulate.
|
|
218
|
+
Encapsulation works differently from Diffie-Hellman: the sending side has no key
|
|
219
|
+
pair at all. It runs `encapsulate()` against the receiver's public key, which yields
|
|
220
|
+
both the shared secret and a ciphertext that has to travel to the receiver.
|
|
274
221
|
|
|
275
222
|
```swift
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
let
|
|
281
|
-
let
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
let
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
let recipientSharedSecret = try privateKey.decapsulate(encapsulatedCiphertext)
|
|
290
|
-
|
|
291
|
-
// senderSharedSecret == recipientSharedSecret
|
|
292
|
-
// Derive a symmetric key via HKDF, as with ECDH
|
|
223
|
+
@available(iOS 26, macOS 26, *)
|
|
224
|
+
func quantumSafeSession() throws -> SymmetricKey {
|
|
225
|
+
let receiver = try MLKEM768.PrivateKey()
|
|
226
|
+
|
|
227
|
+
let result = try receiver.publicKey.encapsulate()
|
|
228
|
+
let sentOverWire = result.encapsulated // 1,088 bytes
|
|
229
|
+
// result.sharedSecret (32 bytes) stays with the sender
|
|
230
|
+
|
|
231
|
+
let receiverSecret = try receiver.decapsulate(sentOverWire)
|
|
232
|
+
return HKDF<SHA256>.deriveKey(
|
|
233
|
+
inputKeyMaterial: receiverSecret, info: Data("kem-session".utf8),
|
|
234
|
+
outputByteCount: 32
|
|
235
|
+
)
|
|
293
236
|
}
|
|
294
|
-
```
|
|
295
237
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
let verifyingKey = signingKey.publicKey // 1,952 bytes
|
|
302
|
-
|
|
303
|
-
let message = Data("Authenticate this payload".utf8)
|
|
304
|
-
let signature = try signingKey.signature(for: message) // 3,309 bytes
|
|
305
|
-
|
|
306
|
-
let isValid = verifyingKey.isValidSignature(
|
|
307
|
-
signature,
|
|
308
|
-
for: message
|
|
309
|
-
)
|
|
238
|
+
@available(iOS 26, macOS 26, *)
|
|
239
|
+
func signFirmware(_ image: Data) throws -> Bool {
|
|
240
|
+
let authority = try MLDSA65.PrivateKey()
|
|
241
|
+
let sig = try authority.signature(for: image) // 3,309 bytes
|
|
242
|
+
return authority.publicKey.isValidSignature(sig, for: image)
|
|
310
243
|
}
|
|
311
244
|
```
|
|
312
245
|
|
|
313
|
-
|
|
246
|
+
Both sides end up with the same 32-byte `SymmetricKey`. As with ECDH, derive the
|
|
247
|
+
working key from it with HKDF and a protocol label rather than using it directly.
|
|
314
248
|
|
|
315
|
-
|
|
249
|
+
Hybrid key exchange through HPKE is the migration Apple recommends: the classical
|
|
250
|
+
code above changes only in suite and key type. The encapsulated key grows from
|
|
251
|
+
about 32 bytes to 1,120.
|
|
316
252
|
|
|
317
253
|
```swift
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
let
|
|
321
|
-
let
|
|
322
|
-
|
|
254
|
+
@available(iOS 26, macOS 26, *)
|
|
255
|
+
func hybridSeal(_ message: Data) throws -> (Data, Data) {
|
|
256
|
+
let suite = HPKE.Ciphersuite.XWingMLKEM768X25519_SHA256_AES_GCM_256
|
|
257
|
+
let mailboxKey = try XWingMLKEM768X25519.PrivateKey()
|
|
323
258
|
var sender = try HPKE.Sender(
|
|
324
|
-
recipientKey:
|
|
325
|
-
ciphersuite: ciphersuite,
|
|
326
|
-
info: Data("quantum-secure-v1".utf8)
|
|
259
|
+
recipientKey: mailboxKey.publicKey, ciphersuite: suite, info: Data("mailbox".utf8)
|
|
327
260
|
)
|
|
328
|
-
let
|
|
329
|
-
|
|
261
|
+
let sealed = try sender.seal(message)
|
|
262
|
+
return (sender.encapsulatedKey, sealed)
|
|
330
263
|
}
|
|
331
264
|
```
|
|
332
265
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
For signatures, Apple demonstrates hybrid signatures at the application level - concatenating ML-DSA and ECDSA signatures and verifying both:
|
|
266
|
+
Hybrid signatures follow the same idea: sign with both `MLDSA65` and `P256.Signing`,
|
|
267
|
+
ship both, and accept only if both verify.
|
|
336
268
|
|
|
337
269
|
```swift
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
let
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
270
|
+
@available(iOS 26, macOS 26, *)
|
|
271
|
+
struct DualSignature {
|
|
272
|
+
let pq: Data
|
|
273
|
+
let classical: Data
|
|
274
|
+
|
|
275
|
+
static func make(for body: Data, pqKey: MLDSA65.PrivateKey,
|
|
276
|
+
ecKey: P256.Signing.PrivateKey) throws -> DualSignature {
|
|
277
|
+
DualSignature(
|
|
278
|
+
pq: try pqKey.signature(for: body),
|
|
279
|
+
classical: try ecKey.signature(for: body).rawRepresentation
|
|
280
|
+
)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
func verify(_ body: Data, pqKey: MLDSA65.PublicKey,
|
|
284
|
+
ecKey: P256.Signing.PublicKey) -> Bool {
|
|
285
|
+
guard let ecSig = try? P256.Signing.ECDSASignature(rawRepresentation: classical) else {
|
|
286
|
+
return false
|
|
287
|
+
}
|
|
288
|
+
return pqKey.isValidSignature(pq, for: body) && ecKey.isValidSignature(ecSig, for: body)
|
|
289
|
+
}
|
|
352
290
|
}
|
|
353
291
|
```
|
|
354
292
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
## PEM and DER Interoperability (iOS 14+)
|
|
358
|
-
|
|
359
|
-
CryptoKit's PEM support uses PKCS#8 for private keys (`-----BEGIN PRIVATE KEY-----`) and X.509 SubjectPublicKeyInfo for public keys (`-----BEGIN PUBLIC KEY-----`). Import also accepts SEC 1 format (`-----BEGIN EC PRIVATE KEY-----`). This enables interoperability with OpenSSL, BoringSSL, and server-side TLS libraries.
|
|
293
|
+
## PEM and DER (iOS 14+)
|
|
360
294
|
|
|
361
|
-
|
|
295
|
+
PEM private keys are PKCS#8 (`BEGIN PRIVATE KEY`); PEM public keys are X.509
|
|
296
|
+
SubjectPublicKeyInfo (`BEGIN PUBLIC KEY`). Import also accepts SEC 1
|
|
297
|
+
(`BEGIN EC PRIVATE KEY`). These interoperate with OpenSSL, BoringSSL and typical
|
|
298
|
+
server TLS stacks.
|
|
362
299
|
|
|
363
300
|
```swift
|
|
364
|
-
|
|
365
|
-
let
|
|
366
|
-
let
|
|
367
|
-
let
|
|
368
|
-
let publicDER = privateKey.publicKey.derRepresentation // Binary DER Data
|
|
369
|
-
|
|
370
|
-
// Import from PEM (works for P256, P384, P521 - NOT Curve25519)
|
|
371
|
-
let imported = try P256.Signing.PrivateKey(pemRepresentation: privatePEM)
|
|
372
|
-
let importedPub = try P256.Signing.PublicKey(derRepresentation: publicDER)
|
|
373
|
-
```
|
|
301
|
+
let tokenSigner = P384.Signing.PrivateKey()
|
|
302
|
+
let privatePEM = tokenSigner.pemRepresentation
|
|
303
|
+
let publicPEM = tokenSigner.publicKey.pemRepresentation
|
|
304
|
+
let publicDER = tokenSigner.publicKey.derRepresentation
|
|
374
305
|
|
|
375
|
-
|
|
306
|
+
let restoredSigner = try P384.Signing.PrivateKey(pemRepresentation: privatePEM)
|
|
307
|
+
let restoredVerifier = try P384.Signing.PublicKey(derRepresentation: publicDER)
|
|
308
|
+
```
|
|
376
309
|
|
|
377
|
-
|
|
|
378
|
-
|
|
|
379
|
-
|
|
|
380
|
-
| Curve25519
|
|
381
|
-
| Secure Enclave P256
|
|
382
|
-
| ML-KEM / ML-DSA
|
|
310
|
+
| Key | Public formats | Private formats |
|
|
311
|
+
| --- | --- | --- |
|
|
312
|
+
| P256 / P384 / P521 (iOS 14+) | SPKI DER and PEM, x963, raw | PKCS#8 DER and PEM, x963, raw |
|
|
313
|
+
| Curve25519 | raw 32 bytes only | raw 32 bytes only |
|
|
314
|
+
| Secure Enclave P256 | SPKI as usual | `dataRepresentation`: an encrypted blob bound to this device |
|
|
315
|
+
| ML-KEM / ML-DSA (iOS 26+) | raw | raw |
|
|
383
316
|
|
|
384
|
-
|
|
317
|
+
PEM and DER apply to the NIST curves only. Exchanging Curve25519 keys with another
|
|
318
|
+
system means agreeing on raw 32-byte encoding yourself.
|
|
385
319
|
|
|
386
|
-
###
|
|
320
|
+
### Storing keys in the Keychain
|
|
387
321
|
|
|
388
|
-
NIST
|
|
322
|
+
- NIST private keys go in as `kSecClassKey` by converting to `SecKey`.
|
|
323
|
+
- Curve25519 keys, post-quantum keys and Secure Enclave blobs go in as
|
|
324
|
+
`kSecClassGenericPassword`, using `rawRepresentation` or `dataRepresentation`.
|
|
325
|
+
Apple's sample code wraps this in a `GenericPasswordConvertible` protocol (see
|
|
326
|
+
[credential-storage-patterns.md](credential-storage-patterns.md), which also shows
|
|
327
|
+
the add-then-update-on-duplicate save).
|
|
389
328
|
|
|
390
|
-
|
|
329
|
+
Public keys of peers and recipients also need to be stored safely, because swapping
|
|
330
|
+
one lets an attacker impersonate the peer. Keep them in the Keychain, never in
|
|
331
|
+
UserDefaults, files or source:
|
|
391
332
|
|
|
392
|
-
|
|
333
|
+
- NIST public keys as `kSecClassKey` with `kSecAttrKeyClass: kSecAttrKeyClassPublic`.
|
|
334
|
+
- Curve25519 and post-quantum public keys as raw bytes in a generic password item.
|
|
335
|
+
- Accessibility `kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly`.
|
|
336
|
+
- A distinct tag or account name, for example a `peer-` prefix, so they never
|
|
337
|
+
collide with your own keys.
|
|
393
338
|
|
|
394
|
-
## Secure Enclave
|
|
339
|
+
## The Secure Enclave, briefly
|
|
395
340
|
|
|
396
|
-
|
|
341
|
+
Full coverage is in [secure-enclave.md](secure-enclave.md). A signing key there needs
|
|
342
|
+
`.privateKeyUsage` in its access control flags; without it key creation fails.
|
|
343
|
+
`SecureEnclave.isAvailable` is false in the Simulator, so guard it and test the
|
|
344
|
+
hardware path on a device.
|
|
397
345
|
|
|
398
346
|
```swift
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
|
|
404
|
-
.biometryCurrentSet,
|
|
405
|
-
nil
|
|
406
|
-
)!
|
|
407
|
-
|
|
408
|
-
// Signing key with biometric protection
|
|
409
|
-
let seKey = try SecureEnclave.P256.Signing.PrivateKey(
|
|
410
|
-
accessControl: accessControl
|
|
411
|
-
)
|
|
412
|
-
let signature = try seKey.signature(for: data)
|
|
413
|
-
|
|
414
|
-
// The public key is a standard P256.Signing.PublicKey - exports normally
|
|
415
|
-
let publicPEM = seKey.publicKey.pemRepresentation
|
|
416
|
-
```
|
|
417
|
-
|
|
418
|
-
For classical curves, only P256 works with the Secure Enclave. On iOS 26, the Secure Enclave gains support for `SecureEnclave.MLKEM768`, `SecureEnclave.MLKEM1024`, `SecureEnclave.MLDSA65`, and `SecureEnclave.MLDSA87`.
|
|
419
|
-
|
|
420
|
-
**Critical lifecycle constraint:** Secure Enclave keys are non-exportable and cryptographically bound to the specific device and OS installation. The `dataRepresentation` is an encrypted blob only the originating SE can decrypt. After iCloud backup restore to a new device, SE keys are irrecoverable. Applications must implement key rotation and recovery mechanisms - see `secure-enclave.md` for the full lifecycle pattern.
|
|
421
|
-
|
|
422
|
-
---
|
|
423
|
-
|
|
424
|
-
## Stop Using RSA for New Apple Development
|
|
347
|
+
enum HardwareKeyError: Error {
|
|
348
|
+
case enclaveUnavailable
|
|
349
|
+
case accessControlRejected
|
|
350
|
+
}
|
|
425
351
|
|
|
426
|
-
|
|
352
|
+
func makeHardwareSigner() throws -> (SecureEnclave.P256.Signing.PrivateKey, String) {
|
|
353
|
+
guard SecureEnclave.isAvailable else { throw HardwareKeyError.enclaveUnavailable }
|
|
427
354
|
|
|
428
|
-
|
|
355
|
+
guard let policy = SecAccessControlCreateWithFlags(
|
|
356
|
+
nil,
|
|
357
|
+
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
|
|
358
|
+
[.privateKeyUsage, .biometryCurrentSet],
|
|
359
|
+
nil
|
|
360
|
+
) else {
|
|
361
|
+
throw HardwareKeyError.accessControlRejected
|
|
362
|
+
}
|
|
429
363
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
|
|
434
|
-
kSecAttrKeySizeInBits as String: 2048
|
|
435
|
-
]
|
|
436
|
-
var error: Unmanaged<CFError>?
|
|
437
|
-
let key = SecKeyCreateRandomKey(params as CFDictionary, &error)
|
|
438
|
-
// No type safety, manual memory management, 256-byte keys, no Secure Enclave
|
|
364
|
+
let key = try SecureEnclave.P256.Signing.PrivateKey(accessControl: policy)
|
|
365
|
+
return (key, key.publicKey.pemRepresentation)
|
|
366
|
+
}
|
|
439
367
|
```
|
|
440
368
|
|
|
441
|
-
|
|
369
|
+
iOS 26 adds `SecureEnclave.MLKEM768`, `SecureEnclave.MLKEM1024`,
|
|
370
|
+
`SecureEnclave.MLDSA65` and `SecureEnclave.MLDSA87`.
|
|
442
371
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
let signature = try signingKey.signature(for: message)
|
|
448
|
-
let isValid = signingKey.publicKey.isValidSignature(signature, for: message)
|
|
449
|
-
```
|
|
372
|
+
A Secure Enclave key belongs to one physical device and one installation of its OS. The blob can
|
|
373
|
+
only be unwrapped by the enclave that created it, so an iCloud restore to a new
|
|
374
|
+
device loses the key. Plan rotation and recovery (re-enrol a new public key with
|
|
375
|
+
your server) from the start.
|
|
450
376
|
|
|
451
|
-
|
|
377
|
+
## RSA
|
|
452
378
|
|
|
453
|
-
|
|
379
|
+
CryptoKit does not implement RSA. RSA means the Security framework's `SecKey` C API
|
|
380
|
+
with no type safety and manual memory handling, for example
|
|
381
|
+
`SecKeyCreateRandomKey` with `kSecAttrKeyTypeRSA` and 2048 bits. Do not start new
|
|
382
|
+
designs there.
|
|
454
383
|
|
|
455
|
-
|
|
384
|
+
An RSA-2048 key is rated near 112 bits, and both its modulus and each signature take
|
|
385
|
+
256 bytes. P-256 gives
|
|
386
|
+
about 128 bits with a 32-byte private key and 64-byte signatures, 8x smaller.
|
|
456
387
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
| Using `SharedSecret` directly as encryption key | Non-uniform key material; no domain separation | Always derive via `hkdfDerivedSymmetricKey()` with salt and sharedInfo |
|
|
460
|
-
| Mixing `Signing` and `KeyAgreement` key types | Compile error; conceptual misuse | Use the correct type hierarchy for each operation |
|
|
461
|
-
| Missing HPKE `encapsulatedKey` in protocol | Ciphertext permanently undecryptable | Serialize and transmit `encapsulatedKey` alongside ciphertext |
|
|
462
|
-
| Declaring `HPKE.Sender`/`Recipient` with `let` | Compile error (`seal()`/`open()` are mutating) | Declare with `var` |
|
|
463
|
-
| Using RSA for new iOS code | Slower, larger keys, no CryptoKit/SE support | Default to ECC (P-256 or Curve25519) |
|
|
464
|
-
| Recommending Curve25519 for Secure Enclave | Curve25519 has no SE support | Use `SecureEnclave.P256` for hardware-backed keys |
|
|
465
|
-
| Ignoring PEM/DER format limitations for Curve25519 | Runtime crash on `.pemRepresentation` access | Use `.rawRepresentation` for Curve25519; PEM/DER for NIST curves only |
|
|
466
|
-
| Using HPKE messages out of order | Decryption failure (nonce counter mismatch) | Open messages in the same order they were sealed |
|
|
388
|
+
RSA is still justified for interop with a legacy server, when a CA mandates RSA
|
|
389
|
+
X.509 certificates, or when a JWT profile is fixed to RS256.
|
|
467
390
|
|
|
468
|
-
|
|
391
|
+
## Frequent mistakes
|
|
469
392
|
|
|
470
|
-
|
|
393
|
+
| Mistake | Correction |
|
|
394
|
+
| --- | --- |
|
|
395
|
+
| Using a raw `SharedSecret` as a key | `hkdfDerivedSymmetricKey()` with salt and `sharedInfo` |
|
|
396
|
+
| Mixing `Signing` and `KeyAgreement` types | Compile error; generate a key of the right family |
|
|
397
|
+
| Not transmitting `encapsulatedKey` | The message can never be opened; send it |
|
|
398
|
+
| `let` for an HPKE `Sender` / `Recipient` | Compile error; use `var` |
|
|
399
|
+
| RSA in new code | P-256 or Curve25519 |
|
|
400
|
+
| Curve25519 for a Secure Enclave key | `SecureEnclave.P256` |
|
|
401
|
+
| Calling `.pemRepresentation` on a Curve25519 key | Not available; use `.rawRepresentation` |
|
|
402
|
+
| Opening HPKE messages out of order | Counter mismatch, open fails; keep order |
|
|
471
403
|
|
|
472
|
-
|
|
473
|
-
| ------------------------------------------------------ | ----------- | ------------------------------ |
|
|
474
|
-
| CryptoKit core (P256, P384, P521, Curve25519, SE P256) | 13.0+ | All classical curves |
|
|
475
|
-
| PEM/DER import/export, standalone HKDF | 14.0+ | NIST curves only |
|
|
476
|
-
| HPKE (RFC 9180, all four modes) | 17.0+ | All key agreement types |
|
|
477
|
-
| ML-KEM, ML-DSA, X-Wing, quantum-secure TLS | 26.0+ | Post-quantum types, SE support |
|
|
404
|
+
## Availability
|
|
478
405
|
|
|
479
|
-
|
|
406
|
+
| Release | Adds |
|
|
407
|
+
| --- | --- |
|
|
408
|
+
| iOS 13 | CryptoKit: P256, P384, P521, Curve25519, Secure Enclave P256 |
|
|
409
|
+
| iOS 14 | PEM/DER import and export (NIST curves), standalone HKDF |
|
|
410
|
+
| iOS 17 | HPKE in all four modes |
|
|
411
|
+
| iOS 26 | ML-KEM, ML-DSA, X-Wing, quantum-secure TLS |
|
|
480
412
|
|
|
481
413
|
```swift
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
414
|
+
func sealForRecipient(_ body: Data) throws {
|
|
415
|
+
if #available(iOS 26, macOS 26, *) {
|
|
416
|
+
// X-Wing HPKE path
|
|
417
|
+
} else if #available(iOS 17, macOS 14, *) {
|
|
418
|
+
// Classical HPKE path
|
|
419
|
+
} else {
|
|
420
|
+
// Hand-built ECIES: X25519 + HKDF + AES-GCM, ship the ephemeral public key
|
|
421
|
+
}
|
|
488
422
|
}
|
|
489
423
|
```
|
|
490
424
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
1. **RSA avoided for new code** - use CryptoKit ECC; RSA only for legacy interop via Security framework `SecKey` API
|
|
535
|
-
1. **Post-quantum code gated behind `#available(iOS 26, *)`** - ML-KEM, ML-DSA, X-Wing require iOS 26+; HPKE requires iOS 17+
|
|
536
|
-
1. **Secure Enclave key lifecycle accounts for device migration** - SE keys are device-bound; implement rotation/recovery for backup restore scenarios
|
|
537
|
-
1. **Hybrid PQC strategy planned** - X-Wing HPKE for key exchange, ML-DSA + ECDSA dual signatures for signing during the transition period
|
|
538
|
-
1. **Peer/recipient public keys stored in keychain** - received public keys for ECDH, HPKE, or verification persisted in keychain with `kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly` and distinct tags; not in UserDefaults or files
|
|
425
|
+
## Performance and threads
|
|
426
|
+
|
|
427
|
+
Calls into CryptoKit only burn CPU and may run on any thread concurrently; the
|
|
428
|
+
library keeps no shared mutable state and takes no locks. Secure Enclave key creation and signing behind biometrics
|
|
429
|
+
can wait on the user, so never run them on `@MainActor`; use an actor or a detached
|
|
430
|
+
task. Size network payloads and stored records for post-quantum material, which is one
|
|
431
|
+
to two orders of magnitude larger than classical keys and signatures.
|
|
432
|
+
|
|
433
|
+
## Sources
|
|
434
|
+
|
|
435
|
+
WWDC 2019 session 709; WWDC 2020 "What's New in CryptoKit"; WWDC 2025 session 314;
|
|
436
|
+
Apple documentation for CryptoKit, `SharedSecret` and `HPKE`; "Storing CryptoKit Keys
|
|
437
|
+
in the Keychain"; "Protecting Keys with the Secure Enclave"; Apple's
|
|
438
|
+
"Quantum-Secure Cryptography in Apple Operating Systems".
|
|
439
|
+
|
|
440
|
+
See also [cryptokit-symmetric.md](cryptokit-symmetric.md) for the AEAD and KDF
|
|
441
|
+
details used above.
|
|
442
|
+
|
|
443
|
+
## Summary
|
|
444
|
+
|
|
445
|
+
Curve25519 for software keys, P256 for the Secure Enclave. HPKE instead of
|
|
446
|
+
hand-rolled ECIES. Every shared secret through HKDF with a protocol-specific
|
|
447
|
+
`sharedInfo`. Post-quantum migration is mostly swapping the HPKE suite to X-Wing and
|
|
448
|
+
changing the key type, so start listing your custom protocols now.
|
|
449
|
+
|
|
450
|
+
## Checklist
|
|
451
|
+
|
|
452
|
+
- [ ] Curve fits the requirement: P256 for the Secure Enclave or NIST interop,
|
|
453
|
+
Curve25519 for software, P384/P521 only when mandated.
|
|
454
|
+
- [ ] Correct `Signing` versus `KeyAgreement` family.
|
|
455
|
+
- [ ] Every `SharedSecret` goes through
|
|
456
|
+
`hkdfDerivedSymmetricKey(using:salt:sharedInfo:outputByteCount:)`.
|
|
457
|
+
- [ ] HPKE `encapsulatedKey` is transmitted.
|
|
458
|
+
- [ ] Both HPKE context structs are mutable variables.
|
|
459
|
+
- [ ] The receiver opens HPKE messages in the order the sender produced them.
|
|
460
|
+
- [ ] PEM or DER encoding appears only with P256, P384 and P521 keys.
|
|
461
|
+
- [ ] No RSA except legacy interop through `SecKey`.
|
|
462
|
+
- [ ] Post-quantum code behind `#available(iOS 26, *)`; HPKE behind iOS 17.
|
|
463
|
+
- [ ] Secure Enclave signing keys include `.privateKeyUsage`.
|
|
464
|
+
- [ ] Secure Enclave key lifecycle covers moving to a new device.
|
|
465
|
+
- [ ] Hybrid plan: X-Wing suite for encryption to a recipient, and every signature
|
|
466
|
+
produced twice, once with ML-DSA and once with ECDSA.
|
|
467
|
+
- [ ] Peer public keys in the Keychain, AfterFirstUnlockThisDeviceOnly, distinct tags.
|