pwn 0.5.783 → 0.5.785

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. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/Gemfile +8 -8
  4. data/README.md +37 -0
  5. data/Rakefile +19 -1
  6. data/bin/pwn +2 -1
  7. data/bin/pwn_setup +1 -1
  8. data/bin/pwn_web_cache_deception +35 -7
  9. data/build_gem.sh +1 -1
  10. data/documentation/AI-Integration.md +7 -5
  11. data/documentation/Agent-Tool-Registry.md +2 -2
  12. data/documentation/Backup-Retention.md +82 -0
  13. data/documentation/Configuration.md +10 -6
  14. data/documentation/Contributing.md +1 -1
  15. data/documentation/Cron.md +64 -35
  16. data/documentation/Diagrams.md +2 -2
  17. data/documentation/General-PWN-Usage.md +4 -3
  18. data/documentation/Home.md +6 -5
  19. data/documentation/How-PWN-Works.md +7 -6
  20. data/documentation/Installation.md +254 -10
  21. data/documentation/Mission-Cancellation.md +134 -0
  22. data/documentation/Operability.md +17 -0
  23. data/documentation/Persistence.md +8 -6
  24. data/documentation/Plugin-Degradation.md +12 -0
  25. data/documentation/Plugins.md +3 -2
  26. data/documentation/Reinforcement-Learning.md +5 -6
  27. data/documentation/Runtime-Source-Protection.md +162 -0
  28. data/documentation/Transparent-Browser.md +11 -0
  29. data/documentation/Troubleshooting.md +3 -2
  30. data/documentation/What-is-PWN.md +2 -2
  31. data/documentation/YubiKey-Vaults.md +183 -0
  32. data/documentation/YubiKey.md +339 -0
  33. data/documentation/diagrams/cron-scheduling.svg +130 -128
  34. data/documentation/diagrams/dot/cron-scheduling.dot +5 -5
  35. data/documentation/diagrams/dot/persistence-filesystem.dot +5 -3
  36. data/documentation/diagrams/dot/plugin-ecosystem.dot +2 -2
  37. data/documentation/diagrams/persistence-filesystem.svg +172 -142
  38. data/documentation/diagrams/plugin-ecosystem.svg +64 -64
  39. data/documentation/pwn-ai-Agent.md +10 -4
  40. data/etc/default_cron/session_cleanup.rb +1245 -22
  41. data/etc/default_skills/pwn/ai/agent/mission_namespace/SKILL.md +21 -0
  42. data/etc/default_skills/pwn/ai/agent/tools/browser_act/SKILL.md +45 -0
  43. data/etc/default_skills/pwn/ai/agent/tools/loot_query/SKILL.md +27 -26
  44. data/etc/default_skills/pwn/ai/agent/tools/recon_run/SKILL.md +25 -15
  45. data/etc/default_skills/pwn/ai/agent/tools/workspace_tool/SKILL.md +24 -0
  46. data/etc/default_skills/pwn/ai/server_tools/SKILL.md +25 -0
  47. data/etc/default_skills/pwn/backups/SKILL.md +54 -0
  48. data/etc/default_skills/pwn/plugins/recon/SKILL.md +12 -1
  49. data/etc/default_skills/pwn/plugins/yubikey/SKILL.md +157 -0
  50. data/etc/default_skills/pwn/plugins/yubikey/fido/SKILL.md +189 -0
  51. data/etc/default_skills/pwn/plugins/yubikey/hsmauth/SKILL.md +83 -0
  52. data/etc/default_skills/pwn/plugins/yubikey/management/SKILL.md +80 -0
  53. data/etc/default_skills/pwn/plugins/yubikey/oath/SKILL.md +115 -0
  54. data/etc/default_skills/pwn/plugins/yubikey/openpgp/SKILL.md +144 -0
  55. data/etc/default_skills/pwn/plugins/yubikey/otp/SKILL.md +97 -0
  56. data/etc/default_skills/pwn/plugins/yubikey/piv/SKILL.md +110 -0
  57. data/etc/default_skills/pwn/plugins/yubikey/protocol/SKILL.md +21 -0
  58. data/etc/default_skills/pwn/plugins/yubikey/security_domain/SKILL.md +75 -0
  59. data/etc/default_skills/pwn/plugins/yubikey/security_domain/scp/SKILL.md +46 -0
  60. data/etc/default_skills/pwn/plugins/yubikey/transport/SKILL.md +98 -0
  61. data/etc/default_skills/pwn/redaction/SKILL.md +13 -1
  62. data/lib/pwn/ai/agent/dispatch.rb +26 -15
  63. data/lib/pwn/ai/agent/extrospection.rb +6 -7
  64. data/lib/pwn/ai/agent/learning.rb +62 -25
  65. data/lib/pwn/ai/agent/loop.rb +17 -1
  66. data/lib/pwn/ai/agent/mission_namespace.rb +331 -0
  67. data/lib/pwn/ai/agent/policy.rb +5 -0
  68. data/lib/pwn/ai/agent/policy_evaluation.rb +8 -8
  69. data/lib/pwn/ai/agent/prompt_builder.rb +13 -2
  70. data/lib/pwn/ai/agent/request_runtime.rb +122 -18
  71. data/lib/pwn/ai/agent/reward.rb +5 -3
  72. data/lib/pwn/ai/agent/skill_consolidation.rb +3 -2
  73. data/lib/pwn/ai/agent/skill_review.rb +9 -5
  74. data/lib/pwn/ai/agent/swarm.rb +35 -16
  75. data/lib/pwn/ai/agent/tools/browser_act.rb +32 -0
  76. data/lib/pwn/ai/agent/tools/loot_query.rb +11 -1
  77. data/lib/pwn/ai/agent/tools/recon_run.rb +11 -1
  78. data/lib/pwn/ai/agent/tools/workspace_tool.rb +29 -0
  79. data/lib/pwn/ai/agent/turn_finalizer.rb +6 -2
  80. data/lib/pwn/ai/agent.rb +1 -0
  81. data/lib/pwn/ai/anthropic.rb +8 -33
  82. data/lib/pwn/ai/cli.rb +34 -5
  83. data/lib/pwn/ai/gemini.rb +1 -0
  84. data/lib/pwn/ai/grok.rb +39 -28
  85. data/lib/pwn/ai/ollama.rb +4 -2
  86. data/lib/pwn/ai/open_ai.rb +11 -32
  87. data/lib/pwn/ai/open_web_ui.rb +62 -4
  88. data/lib/pwn/ai/router.rb +53 -3
  89. data/lib/pwn/ai/server_tools.rb +231 -0
  90. data/lib/pwn/ai.rb +1 -0
  91. data/lib/pwn/backups.rb +347 -0
  92. data/lib/pwn/config.rb +70 -39
  93. data/lib/pwn/cron.rb +120 -49
  94. data/lib/pwn/migrate.rb +150 -68
  95. data/lib/pwn/module_skills.rb +7 -0
  96. data/lib/pwn/plugins/ai_sandbox.rb +538 -1
  97. data/lib/pwn/plugins/authentication_helper.rb +74 -4
  98. data/lib/pwn/plugins/basic_auth.rb +2 -1
  99. data/lib/pwn/plugins/binary_parser.rb +5 -4
  100. data/lib/pwn/plugins/ghidra.rb +4 -3
  101. data/lib/pwn/plugins/google_workspace.rb +4 -21
  102. data/lib/pwn/plugins/jobs.rb +1 -1
  103. data/lib/pwn/plugins/log.rb +21 -3
  104. data/lib/pwn/plugins/preflight_checker.rb +34 -9
  105. data/lib/pwn/plugins/recon.rb +4 -1
  106. data/lib/pwn/plugins/repl/ai.rb +305 -27
  107. data/lib/pwn/plugins/repl/ai_console.rb +273 -89
  108. data/lib/pwn/plugins/repl/ai_swarm.rb +181 -62
  109. data/lib/pwn/plugins/repl/mesh.rb +3 -16
  110. data/lib/pwn/plugins/repl/vault.rb +4 -15
  111. data/lib/pwn/plugins/repl.rb +1 -216
  112. data/lib/pwn/plugins/sandbox/driver.rb +4 -0
  113. data/lib/pwn/plugins/transparent_browser.rb +215 -0
  114. data/lib/pwn/plugins/vault.rb +412 -135
  115. data/lib/pwn/plugins/yubikey/fido.rb +1288 -0
  116. data/lib/pwn/plugins/yubikey/hsmauth.rb +439 -0
  117. data/lib/pwn/plugins/yubikey/management.rb +686 -0
  118. data/lib/pwn/plugins/yubikey/oath.rb +766 -0
  119. data/lib/pwn/plugins/yubikey/openpgp.rb +1063 -0
  120. data/lib/pwn/plugins/yubikey/otp.rb +897 -0
  121. data/lib/pwn/plugins/yubikey/piv.rb +1315 -0
  122. data/lib/pwn/plugins/yubikey/protocol.rb +341 -0
  123. data/lib/pwn/plugins/yubikey/security_domain/scp.rb +482 -0
  124. data/lib/pwn/plugins/yubikey/security_domain.rb +464 -0
  125. data/lib/pwn/plugins/yubikey/transport.rb +1017 -0
  126. data/lib/pwn/plugins/yubikey.rb +844 -0
  127. data/lib/pwn/plugins.rb +1 -0
  128. data/lib/pwn/redaction.rb +69 -12
  129. data/lib/pwn/session_trace.rb +4 -0
  130. data/lib/pwn/sessions.rb +58 -16
  131. data/lib/pwn/setup.rb +123 -41
  132. data/lib/pwn/version.rb +1 -1
  133. data/lib/pwn/www/app_cobalt_io.rb +16 -5
  134. data/lib/pwn/www/bug_crowd.rb +16 -5
  135. data/lib/pwn/www/coinbase_pro.rb +16 -5
  136. data/lib/pwn/www/github.rb +30 -14
  137. data/lib/pwn/www/paypal.rb +15 -4
  138. data/lib/pwn/www/synack.rb +16 -5
  139. data/lib/pwn/www/twitter.rb +17 -6
  140. data/lib/pwn.rb +1 -0
  141. data/scripts/build_gate.rb +58 -0
  142. data/spec/documentation/installation_md_spec.rb +1 -1
  143. data/spec/fixtures/yubikey/apdu_frames.json +162 -0
  144. data/spec/integration/authentication_helper_oath_spec.rb +117 -0
  145. data/spec/integration/build_gate_spec.rb +73 -0
  146. data/spec/integration/controller_spill_spec.rb +48 -0
  147. data/spec/integration/harness_process_spec.rb +97 -0
  148. data/spec/integration/mission_cancel_spec.rb +79 -0
  149. data/spec/integration/mission_curses_pty_spec.rb +44 -0
  150. data/spec/integration/persistence_roundtrip_spec.rb +1 -7
  151. data/spec/integration/readme_security_spec.rb +47 -0
  152. data/spec/integration/web_cache_deception_yubikey_spec.rb +159 -0
  153. data/spec/lib/pwn/ai/agent/dispatch_spec.rb +6 -3
  154. data/spec/lib/pwn/ai/agent/dispatch_vault_safety_spec.rb +86 -0
  155. data/spec/lib/pwn/ai/agent/injection_guard_spec.rb +2 -0
  156. data/spec/lib/pwn/ai/agent/learning_spec.rb +4 -4
  157. data/spec/lib/pwn/ai/agent/loop_artifacts_spec.rb +5 -0
  158. data/spec/lib/pwn/ai/agent/loop_spec.rb +104 -29
  159. data/spec/lib/pwn/ai/agent/mission_namespace_spec.rb +138 -0
  160. data/spec/lib/pwn/ai/agent/policy_spec.rb +27 -0
  161. data/spec/lib/pwn/ai/agent/request_runtime_spec.rb +48 -0
  162. data/spec/lib/pwn/ai/agent/shell_search_effect_spec.rb +44 -0
  163. data/spec/lib/pwn/ai/agent/solve_spec.rb +6 -4
  164. data/spec/lib/pwn/ai/agent/tools/browser_act_spec.rb +44 -0
  165. data/spec/lib/pwn/ai/agent/tools/loot_query_yubikey_spec.rb +55 -0
  166. data/spec/lib/pwn/ai/agent/tools/recon_run_yubikey_spec.rb +43 -0
  167. data/spec/lib/pwn/ai/agent/tools/workspace_tool_spec.rb +20 -0
  168. data/spec/lib/pwn/ai/agent/turn_finalizer_spec.rb +7 -0
  169. data/spec/lib/pwn/ai/anthropic_vault_enrollment_spec.rb +37 -0
  170. data/spec/lib/pwn/ai/cli_spec.rb +6 -9
  171. data/spec/lib/pwn/ai/oauth_vault_persistence_spec.rb +195 -0
  172. data/spec/lib/pwn/ai/open_ai_spec.rb +2 -2
  173. data/spec/lib/pwn/ai/open_web_ui_workspace_spec.rb +36 -0
  174. data/spec/lib/pwn/ai/router_spec.rb +31 -3
  175. data/spec/lib/pwn/ai/server_tools_spec.rb +39 -0
  176. data/spec/lib/pwn/backups_spec.rb +114 -0
  177. data/spec/lib/pwn/config_redaction_yubikey_spec.rb +28 -0
  178. data/spec/lib/pwn/config_yubikey_spec.rb +92 -0
  179. data/spec/lib/pwn/cron_spec.rb +113 -6
  180. data/spec/lib/pwn/migrate_spec.rb +108 -5
  181. data/spec/lib/pwn/migrate_yubikey_spec.rb +128 -0
  182. data/spec/lib/pwn/module_skills_spec.rb +8 -0
  183. data/spec/lib/pwn/plugins/ai_sandbox_spec.rb +230 -0
  184. data/spec/lib/pwn/plugins/authentication_helper_native_yubikey_spec.rb +40 -0
  185. data/spec/lib/pwn/plugins/authentication_helper_yubikey_spec.rb +90 -0
  186. data/spec/lib/pwn/plugins/capability_broker/daemon_spec.rb +1 -1
  187. data/spec/lib/pwn/plugins/google_workspace_spec.rb +17 -35
  188. data/spec/lib/pwn/plugins/log_spec.rb +7 -0
  189. data/spec/lib/pwn/plugins/log_yubikey_spec.rb +47 -0
  190. data/spec/lib/pwn/plugins/preflight_checker_native_yubikey_spec.rb +19 -0
  191. data/spec/lib/pwn/plugins/preflight_checker_yubikey_spec.rb +55 -0
  192. data/spec/lib/pwn/plugins/recon_yubikey_spec.rb +43 -0
  193. data/spec/lib/pwn/plugins/repl/ai_console_spec.rb +125 -78
  194. data/spec/lib/pwn/plugins/repl/ai_legacy_containment_spec.rb +200 -0
  195. data/spec/lib/pwn/plugins/repl/ai_swarm_spec.rb +30 -16
  196. data/spec/lib/pwn/plugins/repl/ai_vault_edit_yubikey_spec.rb +103 -0
  197. data/spec/lib/pwn/plugins/repl/ai_yubikey_spec.rb +48 -0
  198. data/spec/lib/pwn/plugins/repl/mesh_vault_persistence_spec.rb +149 -0
  199. data/spec/lib/pwn/plugins/repl/vault_yaml_serialization_spec.rb +11 -0
  200. data/spec/lib/pwn/plugins/repl/vault_yubikey_spec.rb +39 -0
  201. data/spec/lib/pwn/plugins/repl_spec.rb +56 -5
  202. data/spec/lib/pwn/plugins/transparent_browser_jev_spec.rb +46 -0
  203. data/spec/lib/pwn/plugins/vault_native_yubikey_spec.rb +29 -0
  204. data/spec/lib/pwn/plugins/vault_publication_safety_spec.rb +258 -0
  205. data/spec/lib/pwn/plugins/vault_store_safety_spec.rb +241 -0
  206. data/spec/lib/pwn/plugins/vault_yubikey_safety_spec.rb +168 -0
  207. data/spec/lib/pwn/plugins/vault_yubikey_spec.rb +128 -0
  208. data/spec/lib/pwn/plugins/vault_yubikey_vector_spec.rb +45 -0
  209. data/spec/lib/pwn/plugins/yubikey/fido_spec.rb +854 -0
  210. data/spec/lib/pwn/plugins/yubikey/hsmauth_spec.rb +235 -0
  211. data/spec/lib/pwn/plugins/yubikey/management_spec.rb +277 -0
  212. data/spec/lib/pwn/plugins/yubikey/oath_spec.rb +349 -0
  213. data/spec/lib/pwn/plugins/yubikey/openpgp_spec.rb +810 -0
  214. data/spec/lib/pwn/plugins/yubikey/otp_spec.rb +337 -0
  215. data/spec/lib/pwn/plugins/yubikey/piv_spec.rb +646 -0
  216. data/spec/lib/pwn/plugins/yubikey/protocol_secure_spec.rb +28 -0
  217. data/spec/lib/pwn/plugins/yubikey/protocol_spec.rb +149 -0
  218. data/spec/lib/pwn/plugins/yubikey/protocol_vectors_spec.rb +18 -0
  219. data/spec/lib/pwn/plugins/yubikey/security_domain/scp_spec.rb +256 -0
  220. data/spec/lib/pwn/plugins/yubikey/security_domain/support.rb +153 -0
  221. data/spec/lib/pwn/plugins/yubikey/security_domain_spec.rb +296 -0
  222. data/spec/lib/pwn/plugins/yubikey/transport_spec.rb +760 -0
  223. data/spec/lib/pwn/plugins/yubikey_security_spec.rb +260 -0
  224. data/spec/lib/pwn/plugins/yubikey_spec.rb +209 -0
  225. data/spec/lib/pwn/plugins/yubikey_terminal_spec.rb +147 -0
  226. data/spec/lib/pwn/redaction_spec.rb +13 -10
  227. data/spec/lib/pwn/redaction_yubikey_spec.rb +61 -0
  228. data/spec/lib/pwn/session_cleanup_concurrency_spec.rb +201 -0
  229. data/spec/lib/pwn/session_cleanup_default_spec.rb +167 -2
  230. data/spec/lib/pwn/session_cleanup_legacy_spec.rb +101 -0
  231. data/spec/lib/pwn/session_cleanup_payload_spec.rb +335 -2
  232. data/spec/lib/pwn/session_cleanup_recovery_spec.rb +403 -0
  233. data/spec/lib/pwn/sessions_spec.rb +112 -0
  234. data/spec/lib/pwn/setup_native_yubikey_spec.rb +66 -0
  235. data/spec/lib/pwn/setup_spec.rb +3 -1
  236. data/spec/lib/pwn/setup_vault_yubikey_spec.rb +61 -0
  237. data/spec/lib/pwn/setup_yubikey_spec.rb +40 -0
  238. data/spec/lib/pwn/www/login_yubikey_spec.rb +273 -0
  239. data/spec/spec_helper.rb +15 -0
  240. data/spec/support/gate_failure_formatter.rb +14 -0
  241. data/spec/support/harness/cli_faults.rb +13 -9
  242. data/spec/support/harness/controller_spill.rb +26 -0
  243. data/spec/support/harness/controller_state.rb +54 -0
  244. data/spec/support/harness/evaluate.rb +12 -1
  245. data/spec/support/harness/experiments.rb +1 -1
  246. data/spec/support/harness/fake_provider.rb +22 -6
  247. data/spec/support/harness/fault_preload.rb +3 -1
  248. data/spec/support/harness/mission_console.rb +54 -0
  249. data/spec/support/harness/mission_curses_pty.rb +41 -0
  250. data/spec/support/harness/mission_dispatch.rb +30 -0
  251. data/spec/support/harness/mission_fixture_preload.rb +24 -0
  252. data/spec/support/harness/mission_job_lifetime.rb +25 -0
  253. data/spec/support/harness/mission_legacy.rb +25 -0
  254. data/spec/support/harness/mission_normal_exit.rb +34 -0
  255. data/spec/support/harness/mission_swarm.rb +23 -0
  256. data/spec/support/harness/mission_unsupported.rb +29 -0
  257. data/spec/support/harness/package_acceptance.rb +33 -5
  258. data/spec/support/harness/session_acceptance.rb +4 -4
  259. data/spec/support/harness/swarm_acceptance.rb +1 -1
  260. data/spec/support/harness/swarm_control.rb +44 -0
  261. data/spec/support/mission_namespace.rb +14 -0
  262. data/spec/support/mission_pipe_queue.rb +33 -0
  263. data/third_party/pwn_rdoc.jsonl +599 -15
  264. metadata +140 -17
@@ -0,0 +1,189 @@
1
+ ---
2
+ name: pwn-plugins-yubikey-fido
3
+ description: Use when managing native FIDO and U2F operations.
4
+ license: MIT
5
+ allowed-tools: [pwn, pwn_eval]
6
+ metadata:
7
+ bundled: true
8
+ preserve: true
9
+ module: PWN::Plugins::YubiKey::FIDO
10
+ source: pwn/plugins/yubikey/fido.rb
11
+ ---
12
+
13
+ # Native FIDO/U2F
14
+
15
+ Use `PWN::Plugins::YubiKey::FIDO` for CTAP2 management and caller-controlled
16
+ credential operations. Application logic and CBOR are Ruby; cryptography uses
17
+ Ruby OpenSSL and host entropy. No executable, interpreter worker or CBOR gem is
18
+ required. CTAPHID framing and shared-library device I/O belong to `Transport`.
19
+
20
+ ## When to use
21
+
22
+ - Read authenticator capabilities, manage PINs, discoverable credentials,
23
+ fingerprints, always-UV and enterprise-attestation configuration.
24
+ - Run low-level makeCredential/getAssertion or U2F registration/authentication
25
+ with an RP/application and client-data hash supplied by the caller.
26
+ - Do not treat this module as a browser or WebAuthn relying-party server. It does
27
+ not invent origins, approve RP IDs or verify server challenges/signatures.
28
+
29
+ ## Prerequisites and safety
30
+
31
+ Obtain an explicitly selected FIDO connection from `YubiKey::Transport.open`
32
+ with `application: :fido`. Serial-less Security Keys are valid; select them by
33
+ exact device path/reader or unambiguous inventory, never an OTP serial guess.
34
+ HID supports CTAP/U2F; APDU supports FIDO AID selection, NFC CTAP, command chaining,
35
+ GET RESPONSE, keepalive and cancellation. Read getInfo to negotiate actual
36
+ options and PIN protocol 2 or 1; do not infer support from the model name.
37
+
38
+ Every authentication, signature/counter operation and mutation requires literal
39
+ `confirm: true`. `--force` is only an accepted compatibility flag; it grants no
40
+ consent. `info`, retry counters and fingerprint sensor information do not require
41
+ consent. Help groups are local and do not need a connection.
42
+
43
+ Supply `pin:` and `new_pin:` as in-memory Strings, or an explicitly provided
44
+ `secret_provider:` callable receiving `:pin`/`:new_pin`. Never put these in args,
45
+ process arguments, environment variables, files or transcripts. There is no
46
+ factory PIN, default prompt, automatic PIN retry or automatic reset retry.
47
+ Sessions erase owned secret copies on completion, but Ruby/OpenSSL cannot promise
48
+ that every temporary allocation is zeroized. Caller-owned Strings are untouched.
49
+
50
+ ## Management calls
51
+
52
+ ```ruby
53
+ fido = PWN::Plugins::YubiKey::FIDO
54
+ fido.execute(args: []) # local leaf catalog
55
+ fido.execute(args: ['access']) # local group help
56
+ fido.execute(connection: connection, args: ['info'])
57
+ fido.perform(connection: connection, operation: :pin_retries)
58
+ # secret_provider below is an operator-supplied private callable, not a prompt default.
59
+ fido.execute(connection: connection, args: %w[access verify-pin],
60
+ secret_provider: secret_provider, confirm: true)
61
+ ```
62
+
63
+ Implemented leaf paths (args exclude the `fido` prefix):
64
+
65
+ | Path | Additional in-memory inputs / behavior |
66
+ |---|---|
67
+ | `info` | Actual CTAP getInfo, or U2F version/capabilities |
68
+ | `reset` | One reset command; firmware power-up/touch window still applies |
69
+ | `access change-pin` | `new_pin:`, plus `pin:` when already configured; initializes only if getInfo reports no PIN |
70
+ | `access change-pin --u2f` | YubiKey 4 FIPS vendor U2F PIN command, capability-probed |
71
+ | `access verify-pin` | `pin:`; verifies by obtaining and discarding a token, no fabricated RP |
72
+ | `access force-change` | Config permission; requires existing PIN and setMinPINLength |
73
+ | `access set-min-length LENGTH` | Optional repeatable `--rp-id RPID`; minimum cannot decrease |
74
+ | `credentials list` | PIN/UV permission 0x04; exhausts every RP and credential; checks totals and duplicates |
75
+ | `credentials list --csv` | Returns CSV in memory; no output file is opened |
76
+ | `credentials delete HEX_ID` | Complete exact credential ID; no ambiguous prefix matching |
77
+ | `fingerprints list` | Permission 0x08; returns template IDs and names |
78
+ | `fingerprints add [NAME]` | Bounded capture loop and optional friendly name |
79
+ | `fingerprints rename HEX_ID NAME` | Exact template ID and UTF-8 friendly name |
80
+ | `fingerprints delete HEX_ID` | Exact template ID |
81
+ | `config toggle-always-uv` | Permission 0x20 if a PIN/UV token is required |
82
+ | `config enable-ep-attestation` | Requires advertised enterprise-attestation capability |
83
+
84
+ Local groups: root, `access`, `credentials`, `fingerprints`, `config`.
85
+ `operations` lists leaf paths only; variants above are not extra leaf operations.
86
+ PIN verification selects a supported non-RP management permission when scoped
87
+ tokens are advertised, or scopes getAssertion permission to an explicitly supplied
88
+ `rp_id:`. Without either it uses legacy getPINToken, never an invented website.
89
+ An authenticator disabling legacy tokens and exposing no non-RP permission needs
90
+ the caller's `rp_id:`. Built-in UV is not accepted as proof of PIN verification.
91
+ `perform(operation: :update_credential, credential_id: binary_id, user: user_hash,
92
+ ...)` implements CTAP2.1 updateUserInformation; `user['id']` must be the existing
93
+ user ID. Preview credential management supports list/delete, not user update.
94
+
95
+ All native IDs/hashes are **binary Strings** (`.b`); text is valid UTF-8.
96
+ Hex CLI selectors are decoded only after strict even-length hex validation.
97
+ PINs are not normalized or silently truncated. Standard CTAP PINs require at
98
+ least the advertised minimum and at most the advertised byte limit (63 by
99
+ default), with no NUL. Old FIPS U2F requires at least 6 characters and permits
100
+ at most 32 encoded bytes. A newer advertised PIN limit does not waive the CTAP
101
+ 255-byte bound on the block-padded PIN plaintext.
102
+
103
+ ## Credential operations
104
+
105
+ ```ruby
106
+ # client_data_hash is a 32-byte SHA-256 digest produced by the caller's ceremony.
107
+ attestation = fido.make_credential(
108
+ connection: connection, confirm: true, client_data_hash: client_data_hash,
109
+ rp: { 'id' => relying_party_id, 'name' => relying_party_name },
110
+ user: { 'id' => user_id.b, 'name' => user_name },
111
+ secret_provider: secret_provider
112
+ )
113
+ assertions = fido.get_assertion(
114
+ connection: connection, confirm: true, rp_id: relying_party_id,
115
+ client_data_hash: client_data_hash,
116
+ allow_list: [{ 'type' => 'public-key', 'id' => credential_id.b }]
117
+ )
118
+ ```
119
+
120
+ makeCredential accepts `key_params:` (default ES256), `exclude_list:`,
121
+ `extensions:`, `authenticator_options:` and `enterprise_attestation:` (1 or 2).
122
+ getAssertion accepts `allow_list:`, `extensions:` and `authenticator_options:`;
123
+ it returns an Array containing all advertised assertions, using getNextAssertion.
124
+ Use `pin:`/`secret_provider:` for PIN verification, or literal `uv: true` for
125
+ configured built-in UV and a permission-scoped token. Permission tokens bind
126
+ makeCredential/getAssertion to the exact caller RP ID. No-PIN/non-UV requests
127
+ remain possible where the authenticator permits them; the device enforces policy.
128
+ CTAP2.0 preview commands use the legacy PIN token when scoped tokens are absent.
129
+
130
+ `u2f_register(connection:, confirm: true, client_data_hash:, app_id_hash:)`
131
+ returns the raw public key, key handle, attestation certificate and signature.
132
+ `u2f_authenticate(..., key_handle:)` returns presence, counter and signature.
133
+ Literal `check_only: true` interprets only defined U2F recognition statuses.
134
+ These return cryptographic material, **not a server authentication verdict**.
135
+ The caller must verify challenge, RP/application hash, origin policy, flags,
136
+ algorithm, signature, attestation trust and counter semantics before accepting it.
137
+
138
+ ## Bounds, cancellation and recovery
139
+
140
+ - `timeout:` is one finite positive operation budget, default 30 seconds.
141
+ `cancelled:` is a callable, forwarded to Transport as well as checked between
142
+ exchanges; `on_keepalive:` receives status 1 or 2. Callback exceptions propagate.
143
+ - `max_records:` defaults to 1024, maximum 16384. Counts are assertions, not hints:
144
+ excessive, duplicate, inconsistent or prematurely ended lists fail explicitly.
145
+ - Enrollment uses `capture_timeout_ms:` (default 10000), `max_samples:` (default
146
+ 128), and optional `on_sample:` callable receiving template ID, status and
147
+ remaining samples. Intermediate bad captures are reported; PIN attempts are not
148
+ repeated. Failure attempts one separately bounded enrollment cancel, without
149
+ deleting any credential. A failed cleanup cannot replace the original error.
150
+ - `perform(operation: :bio_sensor_info)` is unauthenticated. Explicit
151
+ `perform(operation: :cancel_enrollment, confirm: true)` cancels enrollment.
152
+ - If enrollment completes but naming fails, `EnrollmentError#template_id` names
153
+ the already-created template. Rename that template; do not enroll again or
154
+ assume a rollback. A transport timeout can leave device state uncertain.
155
+ - A connection is caller-owned. Use it exclusively for the whole operation; do
156
+ not interleave concurrent enumeration, PIN or enrollment workflows. Close it in
157
+ the caller's ensure block. A closed/error transport cannot promise cleanup.
158
+ - U2F-only authenticators report U2F capabilities. CTAP2-only management fails
159
+ explicitly. FIPS-only PIN/reset commands are implemented, not substituted for
160
+ ordinary U2F devices. Physical touch/reset timing is never simulated.
161
+
162
+ ## CBOR and crypto verification
163
+
164
+ `FIDO::CBOR.encode(value:)` / `.decode(data:)` use CTAP canonical major-type
165
+ ordering, minimal integer/length encodings, 32-level depth, 16384-item and 1 MiB
166
+ bounds. Map keys are integer/text; byte strings use ASCII-8BIT encoding. Reject
167
+ indefinite lengths, duplicate keys, noncanonical maps, invalid UTF-8, trailing
168
+ bytes, tags and floating-point values rather than coercing them.
169
+
170
+ Protocol 1 derives SHA256(ECDH-x), zero-IV AES-256-CBC and 16-byte HMAC outputs.
171
+ Protocol 2 derives independent HKDF-SHA256 HMAC/AES keys using the CTAP labels,
172
+ uses a fresh host-random IV prefix and 32-byte HMAC. Validate COSE EC2/P-256/-25
173
+ key agreement points before ECDH. No device OTP output supplies entropy.
174
+
175
+ Run the paired RSpec offline with an isolated HOME and the repository's normal
176
+ Ruby bundle; only Connection or native I/O fixtures may be faked. Keep independent
177
+ RFC8949/NIST/RFC4231/P-256/KDF vectors, both PIN protocols, complete public
178
+ management paths, response errors and cancellation tests. Passing fixtures prove
179
+ host protocol behavior, not physical-device or platform interoperability.
180
+
181
+ References used for the protocol contract:
182
+
183
+ - [FIDO CTAP 2.1 proposed standard, 2021-06-15](https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html)
184
+ - [U2F raw message formats 1.2](https://fidoalliance.org/specs/u2f-specs-1.2-ps-20170411/fido-u2f-raw-message-formats-v1.2-ps-20170411.html)
185
+ - [RFC 8949](https://www.rfc-editor.org/rfc/rfc8949) and [RFC 4231](https://www.rfc-editor.org/rfc/rfc4231)
186
+ - Official `python-fido2` 2.2.1: `cbor.py`, `ctap1.py`, `pcsc.py`,
187
+ `ctap2/{base,pin,credman,bio,config}.py`; YubiKey Manager 5.9.1 `ykman/fido.py`
188
+ and its FIDO command module. These were read-only interoperability references,
189
+ not runtime dependencies; the Ruby implementation is independently written.
@@ -0,0 +1,83 @@
1
+ ---
2
+ name: pwn-plugins-yubikey-hsmauth
3
+ description: Manage native HSM Auth credentials and sessions.
4
+ license: MIT
5
+ allowed-tools: [pwn, pwn_eval]
6
+ metadata:
7
+ bundled: true
8
+ preserve: true
9
+ module: PWN::Plugins::YubiKey::HSMAuth
10
+ source: pwn/plugins/yubikey/hsmauth.rb
11
+ ---
12
+
13
+ # Native YubiHSM Auth
14
+
15
+ ## When to use
16
+
17
+ Use `PWN::Plugins::YubiKey::HSMAuth` for YubiKey YubiHSM Auth credentials and
18
+ session-key derivation. This is not the YubiHSM2 management protocol or a browser
19
+ WebAuthn ceremony. The root YubiKey router owns transport lifetime.
20
+
21
+ ## Public API
22
+
23
+ - `execute(args:, connection:, ...)` handles `info`, `reset`, `credentials list`,
24
+ `generate`, `import`, `export`, `symmetric`, `derive`, `delete`, `change-password`,
25
+ and `access change-management-key` / `change-management-password`.
26
+ - `operations`, `help`, `authors` are local metadata.
27
+ - `open_session(connection:, timeout:)` selects once and returns a session handle.
28
+ - `challenge(session:, label:, algorithm:, credential_password:, confirm: true)`
29
+ returns the host challenge or ephemeral uncompressed P-256 public point.
30
+ - `calculate(session:, label:, algorithm:, context:, credential_password:,
31
+ card_crypto:, public_key:, confirm: true)` returns a Hash containing exactly
32
+ `key_senc`, `key_smac`, `key_srmac`, each sixteen raw bytes.
33
+
34
+ ## Procedure
35
+
36
+ 1. Select the intended APDU connection; never guess a device or factory key.
37
+ 2. Read `.help`. Supply passwords/keys only in memory or through a
38
+ `secret_provider:` receiving a field Symbol after literal `confirm: true`.
39
+ Wrong authentication is attempted once; APDU errors preserve `status` without
40
+ logging payloads. Status 63Cx encodes remaining attempts in its low nibble.
41
+ 3. Use exact complete label bytes. Management changes do not substring-match.
42
+ 4. Symmetric import uses raw `key_enc:` and `key_mac:` (sixteen bytes each).
43
+ `derive` uses `derivation_password:` with PBKDF2-HMAC-SHA256, salt `Yubico`,
44
+ 10,000 iterations. `generate` creates a P-256 private key on device; symmetric
45
+ `--generate` returns the generated keys in memory for explicit caller storage.
46
+ 5. Asymmetric import accepts `private_key:` as a raw 32-byte scalar, an in-memory
47
+ PEM/DER String or private OpenSSL EC object on prime256v1. Encrypted PEM/DER
48
+ takes `key_password:`; it never prompts implicitly or retries decryption.
49
+ Export returns public key bytes in `RAW`, `PEM` (default) or `DER` format;
50
+ filenames and secret argv are not accepted by the family API.
51
+ 6. For a live HSM challenge exchange, retain the same `open_session` handle for
52
+ `challenge` and `calculate`. Do not select another application between them.
53
+ Symmetric context is host challenge + card challenge (sixteen bytes), optional
54
+ card cryptogram eight bytes. Asymmetric context is two uncompressed P-256
55
+ points (130 bytes), `public_key:` is the HSM long-term public point, and
56
+ `card_crypto:` contains sixteen bytes. The caller manages the HSM side.
57
+ 7. Check the returned verification scope. Create/delete/reset verify inventory
58
+ metadata, not private key contents. Password and management-key writes return
59
+ `status: :acknowledged, verification: :not_readable`; no second authentication
60
+ attempt is made merely to simulate readback.
61
+
62
+ ## Pitfalls
63
+
64
+ - No shell, executable, network lookup, hidden cache or filesystem credential
65
+ lookup is performed. Raw keys are not auto-decoded from hexadecimal Strings.
66
+ - Credential/management passwords are at most sixteen raw bytes and zero-padded
67
+ per protocol. An explicitly supplied empty String is allowed, but nil never
68
+ means the factory credential. Ruby memory is not guaranteed zeroizable.
69
+ - Asymmetric operations and challenges require application version 5.6.0;
70
+ credential-password changes require 5.8.0. Random management-key generation is
71
+ rejected on 5.8 or later. SELECT failure is not permission to fall back.
72
+ - Device timeout, cancellation or failure must propagate; never replay a mutation.
73
+ - Session keys are secret return values. Do not print or put them in transcripts.
74
+
75
+ ## Verification
76
+
77
+ Run `bundle exec rspec spec/lib/pwn/plugins/yubikey/hsmauth_spec.rb` and scoped
78
+ RuboCop. The specs exercise production Protocol APDUs with a fake raw exchange
79
+ boundary and independent PBKDF2 / P-256 wire vectors. No hardware interoperability
80
+ or secure-channel transport is claimed by these offline tests.
81
+
82
+ References: pinned yubikey-manager 5.9.1 `yubikit/hsmauth.py` and
83
+ `ykman/_cli/hsmauth.py`; https://developers.yubico.com/yubihsm-auth/ .
@@ -0,0 +1,80 @@
1
+ ---
2
+ name: pwn-plugins-yubikey-management
3
+ description: Configure native device capabilities with exact readback.
4
+ license: MIT
5
+ allowed-tools: [pwn, pwn_eval]
6
+ metadata:
7
+ bundled: true
8
+ preserve: true
9
+ module: PWN::Plugins::YubiKey::Management
10
+ source: pwn/plugins/yubikey/management.rb
11
+ ---
12
+
13
+ # PWN::Plugins::YubiKey::Management
14
+
15
+ Native device information/configuration over OTP HID, management smartcard or
16
+ CTAP vendor commands. No executable, subprocess, interpreter or network backend.
17
+
18
+ ## API and routing
19
+
20
+ `execute(args: [...], connection: ...)` takes argv after `config`.
21
+ `operations` lists five prior leaves: `usb`, `nfc`, `mode`, `set-lock-code`, `reset`.
22
+ Empty args return local metadata without device I/O. `--force` is not consent.
23
+ Typed APIs: `info`, `configure_usb`, `configure_nfc`, `set_mode`, `set_lock_code`,
24
+ `reset`, `reboot`, `help`, `authors`. See runtime help for exact options.
25
+
26
+ ## Procedure
27
+
28
+ 1. Select the exact device explicitly. `info` reads version, serial, transport
29
+ capability masks, lock state, flags/timeouts, NFC restrictions, FIPS masks and
30
+ reset metadata. Retain unknown raw TLVs; reject malformed/duplicate records and
31
+ bound pagination. Do not infer all apps from a model or firmware version.
32
+ 2. On pre-4.1 firmware, report unavailable capability metadata explicitly and
33
+ only expose observed USB product/interface information. On 4.x, do not trust
34
+ the broken USB-enabled tag; 4.2.4 has a documented supported-mask correction.
35
+ 3. Application toggles and lock configuration require firmware 5.x plus observed
36
+ capabilities. `--enable`/`--disable` may repeat. Unsupported apps, conflicting
37
+ options, disabling all USB apps and missing lock metadata fail before a write.
38
+ `--list` is read-only and cannot be combined with changes.
39
+ 4. Every write/reboot/reset requires literal `confirm: true`. Lock material is
40
+ sixteen raw in-memory bytes via `lock_code:`/`new_lock_code:` or an explicit
41
+ `secret_provider.call(field)`. Never guess a credential. Generation requires
42
+ `secret_sink.call(:new_lock_code, bytes)`; never print the generated value.
43
+ 5. Write once, then read actual device information and compare requested fields.
44
+ Any lost acknowledgment, mismatching readback or identity change raises an
45
+ uncertainty error; never automatically resend. A lock flag verifies protection
46
+ state, not export/equality of the lock secret.
47
+ 6. USB interface changes normally reboot and need an explicit `reconnect`
48
+ callable before writing. It receives nonsecret prior identity metadata and
49
+ must return a fresh handle to the exact device. The returned handle is closed
50
+ after readback; the original connection remains caller-owned. Never rediscover
51
+ or switch to the first available key. Serial-less reconnection must preserve
52
+ the same observed explicit selector; missing/changed selectors fail uncertain.
53
+ Use `reboot: false` only to intentionally
54
+ defer activation; stored configuration readback is not active-interface proof.
55
+ 7. Legacy NEO/4 `mode` emits the four-byte little-endian mode/timeouts record.
56
+ Reconnect after physical reinsertion and verify the observed USB product ID
57
+ maps to exactly the requested interfaces. A CCID reader that provides no USB
58
+ product ID cannot prove full legacy interface mode; fail uncertain rather than
59
+ claim success. Legacy timeout values are not readable and are labeled so.
60
+ 8. Full-device reset is gated by Bio multi-protocol/PIV or reset-blocked capability
61
+ metadata and smartcard transport, not assumed available on all YubiKeys. Verify
62
+ the fresh reset-state field is cleared. `data_erasure_verified: false` is
63
+ explicit: configuration metadata does not independently prove forensic erasure
64
+ of every application credential. Use application-specific resets otherwise.
65
+ 9. NFC restriction is available from firmware 5.7 and lasts until USB power.
66
+ Disabling the currently used NFC path needs a separately usable readback path;
67
+ absence or lost connectivity is uncertainty, never verified completion.
68
+
69
+ ## Evidence boundaries
70
+
71
+ Same-path specs use synthetic HID/APDU/CTAP wire frames and the real shared
72
+ Protocol codec. They cover field packing, pagination, mutations/readback,
73
+ capability/version refusals, secrets, stale responses and legacy modes. No real
74
+ hardware, credential, network service or global reset is exercised by default.
75
+ Physical compatibility across firmware/models still requires explicit operator
76
+ hardware validation.
77
+
78
+ Protocol provenance: official yubikey-manager **5.9.1** source,
79
+ `yubikit/management.py`, `yubikit/core/otp.py`, `ykman/_cli/config.py` and
80
+ <https://docs.yubico.com/yesdk/yubikey-api/Yubico.YubiKey.Management.Commands.html>.
@@ -0,0 +1,115 @@
1
+ ---
2
+ name: pwn-plugins-yubikey-oath
3
+ description: Use native OATH with exact account identities.
4
+ license: MIT
5
+ allowed-tools: [pwn, pwn_eval]
6
+ metadata:
7
+ bundled: true
8
+ preserve: true
9
+ module: PWN::Plugins::YubiKey::OATH
10
+ source: pwn/plugins/yubikey/oath.rb
11
+ ---
12
+
13
+ # Native OATH
14
+
15
+ ## When to use
16
+
17
+ Use `PWN::Plugins::YubiKey::OATH` for OATH application APDUs, not software-only OTP
18
+ or browser WebAuthn. The root YubiKey router owns transport lifetime.
19
+
20
+ ## Public API
21
+
22
+ - `execute(args:, connection:, ...)` implements `info`, `reset`, `access change`,
23
+ `remember`, `forget`, and `accounts add`, `uri`, `import`, `list`, `code`,
24
+ `rename`, `delete`. Empty/group args return local metadata.
25
+ - `operations`, `help`, `authors` are local metadata.
26
+ - `code(connection:, account:, confirm: true, ...)` returns one numeric String.
27
+ - `calculate(connection:, account:, challenge:, confirm: true, ...)` returns a
28
+ raw full HMAC, not a formatted OTP.
29
+ - `calculate_all(connection:, timestamp:, confirm: true, ...)` returns credential
30
+ records with `code` value/validity Hashes; HOTP and touch records have nil codes.
31
+ - `parse_uri(uri:)` validates and parses an in-memory otpauth URI locally.
32
+
33
+ ## Procedure
34
+
35
+ 1. Use a caller-selected APDU connection. Never select the first device implicitly.
36
+ 2. Pass the exact complete ID returned by LIST, including period/issuer prefixes,
37
+ whitespace and UTF-8 bytes. No substring, case folding or Unicode normalization
38
+ is performed when identifying an account.
39
+ 3. Use `code` for one numeric OTP String; leading zeroes are significant. Optional
40
+ `timestamp:` is nonnegative Integer epoch seconds. TOTP periods come from the
41
+ credential ID, while HOTP uses an empty challenge and increments its counter.
42
+ 4. Supply `password:` or sixteen raw bytes in `access_key:` in memory, or an
43
+ explicit `secret_provider:` callback. Authentication is single-attempt with
44
+ mutual HMAC-SHA1 verification after SELECT. Missing or wrong credentials never
45
+ trigger default guesses, repair, cached-key deletion, fallback or retry.
46
+ 5. For account creation supply `secret:` (raw bytes) or strict `secret_base32:`;
47
+ `--generate` returns the generated seed in memory. Metadata options include
48
+ oath-type, algorithm (SHA1/SHA256/SHA512), digits (6–8), period, issuer, initial
49
+ HOTP counter and touch. Invalid values fail before authentication. Existing
50
+ IDs require explicit `overwrite: true` or `--force` in addition to consent.
51
+ 6. URI input goes in `uri:`, not argv. Conflicting issuer fields, noncanonical
52
+ Base32, duplicate URI parameters and invalid periods are rejected, not repaired.
53
+ 7. Bulk import accepts `credentials:` (complete Hash records) or `pskc:` (in-memory
54
+ PSKC 1.0 XML). Validate the complete batch before any authentication/write;
55
+ never silently skip a bad entry. Each successful PUT is checked with LIST.
56
+ Partial device failure is not rolled back or retried.
57
+ 8. Use `access change` with `new_password:` / `new_access_key:`, or `--clear`.
58
+ Changes read back protection state only, not secret contents. Reset uses DEAD
59
+ confirmation bytes and verifies regenerated salt, unlocked state and empty
60
+ inventory. Rename/delete verify exact IDs and never delete as a repair step.
61
+
62
+ ## Explicit encrypted-cache handoff
63
+
64
+ There is no built-in host cache or implicit lookup. `access remember`, `--remember`
65
+ and `use_cache: true` require an explicit `encrypted_cache:` adapter supplied by
66
+ the caller. It must expose `encrypted? == true` and these methods:
67
+
68
+ ```ruby
69
+ cache.store(device_id: id, access_key: raw_key)
70
+ cache.fetch(device_id: id) # raw bytes or nil
71
+ cache.delete(device_id: id)
72
+ # access forget --all additionally requires:
73
+ cache.clear
74
+ cache.empty?
75
+ ```
76
+
77
+ The adapter owns encryption-at-rest and unlock policy; the assertion is the
78
+ caller's contract, not a cryptographic audit by this module. This is a transfer to
79
+ an explicit encrypted storage API, **not a plaintext file store**. Mutations are
80
+ read back through the adapter. Stored keys are never deleted after failed auth,
81
+ or automatically removed on password change/reset; explicitly forget stale
82
+ records. `use_cache:` never falls back to a password after failure. Do not supply
83
+ a plaintext Hash/file adapter in production.
84
+
85
+ ## Pitfalls and limits
86
+
87
+ - `confirm: true` is mandatory for auth, code calculation, mutations and cache
88
+ writes. `--force` does not supply confirmation. Unprotected inventory is read-only.
89
+ - Native PSKC import supports plain values, AES-128/192/256 key-wrap and
90
+ MAC-authenticated AES-CBC, raw `pskc_key:` or embedded PBKDF2 with
91
+ `pskc_password:`. Unsupported algorithms, XML signatures and DTD/entities are
92
+ rejected. PSKC export-on-add, legacy file-path argv, arbitrary XML encryption
93
+ algorithms and Steam-specific five-character output are not implemented.
94
+ - LIST returns all exact IDs, including hidden-named records. Display-only legacy
95
+ flags do not change the structured result. Exact numeric MFA does not silently
96
+ switch to Steam formatting.
97
+ - Touch requires version 4.2.6, SHA512 4.3.1, rename 5.3.1. Known pre-4.3.5 prefix
98
+ overwrite defects fail closed. FIPS policy is ultimately enforced by the
99
+ selected application; no host override or plaintext secure-channel fallback exists.
100
+ - No CLI, subprocess, corrupt-account repair, silent credential deletion or disk
101
+ access exists. Ruby does not guarantee memory zeroization.
102
+ - A successful APDU or inventory readback is not physical interoperability or
103
+ proof of the imported secret. Timeouts leave mutation state uncertain.
104
+
105
+ ## Verification
106
+
107
+ Run `bundle exec rspec spec/lib/pwn/plugins/yubikey/oath_spec.rb` and scoped
108
+ RuboCop. Tests exercise production Protocol framing and a raw exchange boundary,
109
+ including application-specific A5 SEND REMAINING, mutual auth, RFC 6238 vectors,
110
+ strict identities, mutation readback, cache handoff, and authenticated PSKC import.
111
+ No real hardware, home credentials or network is accessed.
112
+
113
+ References: Yubico YKOATH protocol and pinned yubikey-manager 5.9.1
114
+ `yubikit/oath.py` / `ykman/_cli/oath.py`; RFC 4226, RFC 6238, RFC 6030 and RFC 3394.
115
+ https://developers.yubico.com/OATH/YKOATH_Protocol.html
@@ -0,0 +1,144 @@
1
+ ---
2
+ name: pwn-plugins-yubikey-openpgp
3
+ description: Use native Ruby OpenPGP card operations through YubiKey APDUs.
4
+ ---
5
+
6
+ # PWN::Plugins::YubiKey::OpenPGP
7
+
8
+ Native Ruby application logic using the shared `YubiKey::Protocol` APDU/TLV codec
9
+ and a caller-owned `Connection`. It does not run a CLI, start a subprocess, invoke
10
+ another interpreter, discover hardware at load time, or invent factory credentials.
11
+ Use `PWN::Plugins::YubiKey::OpenPGP.help` for all options.
12
+
13
+ ## Safety and connection ownership
14
+
15
+ - Supply `connection:` with `descriptor[:protocol] == :apdu`. The caller owns
16
+ connection selection, exclusive access and closing. Use a secure-connection
17
+ wrapper when the selected device/interface requires SCP; never fall back to
18
+ plaintext automatically.
19
+ - Pass literal `confirm: true` for **every** mutation, authentication attempt and
20
+ private-key operation. A compatibility `--force` argument is not consent.
21
+ - Supply secrets as `pin:`, `admin_pin:`, `new_pin:`, `new_admin_pin:`, `reset_code:`
22
+ or `key_password:`. A `secret_provider:` callable receives only the field Symbol
23
+ for a required missing secret. Never put credentials in argv, logs or examples.
24
+ - Validate input, key type, algorithm, curve, firmware and card-advertised
25
+ capabilities before authentication. Unsupported operations raise explicitly.
26
+ - Do not replay a mutation or crypto request after timeout, failed authentication,
27
+ or a readback mismatch. A timeout can leave uncertain card state. Reset alone
28
+ deliberately exhausts PIN counters, bounded by observed counters or the
29
+ one-byte counter range during an explicitly requested corruption recovery.
30
+ - Replacing keys or changing algorithm attributes may erase existing keys.
31
+ `fixed` and `cached-fixed` touch policy cannot be reversed without replacing
32
+ the key. Factory reset does **not** restore the attestation key/certificate.
33
+ - PIN changes return freshly read retry counters and `credential_verified: false`:
34
+ secret values cannot be read back. Use a separate explicitly confirmed
35
+ `verify_pin` call if verifying the new credential is wanted.
36
+ - Secrets are not persisted by this module. Ruby/OpenSSL allocation and garbage
37
+ collection do not guarantee zeroization of all temporary copies.
38
+
39
+ ## Management leaf operations
40
+
41
+ `execute(args: [...], connection: connection, ...)` accepts the words **after**
42
+ `openpgp`. Empty args and the `access`, `keys`, `certificates` groups return local
43
+ help metadata without touching a connection. `operations` lists leaves only.
44
+
45
+ | Leaf | Native operation and evidence |
46
+ |---|---|
47
+ | `info` | SELECT, GET VERSION, application DO 6E, signature-counter DO 7A, KDF DO F9 when advertised |
48
+ | `reset` | Deliberate bounded PIN blocking, TERMINATE E6, ACTIVATE 44, fresh empty-key metadata readback |
49
+ | `access set-retries USER RESET ADMIN` | Admin VERIFY 83, F2, fresh C4 readback; NEO PIN-reset side effect reported |
50
+ | `access change-pin` | CHANGE REFERENCE DATA 24/81, fresh C4; requires `pin`, `new_pin` |
51
+ | `access change-admin-pin` | CHANGE REFERENCE DATA 24/83, fresh C4; requires `admin_pin`, `new_admin_pin` |
52
+ | `access change-reset-code` | Admin VERIFY, PUT DATA D3, fresh C4; requires `reset_code` |
53
+ | `access unblock-pin` | RESET RETRY COUNTER 2C, P1=0 with reset code or P1=2 with admin authorization |
54
+ | `access set-signature-policy always|once` | PUT DATA C4 with readback |
55
+ | `keys info SLOT` | Parsed algorithm, origin, fingerprint, generation time and UIF metadata |
56
+ | `keys set-touch SLOT POLICY` | GET/PUT DATA D6–D9, button byte 20, readback, fixed-policy protection |
57
+ | `keys import SLOT FILE|-` | Native OpenSSL key parsing, import template 4D/7F48/5F48, PUT DATA ODD DB/3FFF, public-key readback |
58
+ | `keys attest SLOT OUTPUT|-` | User VERIFY 81, proprietary 80/FB, certificate readback; SIG/DEC/AUT only |
59
+ | `certificates export SLOT OUTPUT|-` | SELECT DATA A5 and GET DATA 7F21, or attestation DO FC |
60
+ | `certificates import SLOT FILE|-` | Single PEM/DER X.509 parsing, certificate-limit validation, PUT DATA and exact DER readback |
61
+ | `certificates delete SLOT` | Empty PUT DATA, empty-slot readback |
62
+
63
+ Slots: `sig`, `dec`, `aut`, `att`; `enc` is an alias for `dec`. Touch policies:
64
+ `off`, `on`, `fixed`, `cached`, `cached-fixed`. Export/attest accept `--format PEM`
65
+ or `--format DER`; PEM is the default. Input `-` requires `key:` or `certificate:`
66
+ in memory, not stdin. Output `-` returns bytes, not stdout. Named output files are
67
+ created exclusively with mode 0600; existing paths are not overwritten.
68
+
69
+ ## Typed cryptographic APIs
70
+
71
+ ```ruby
72
+ pgp = PWN::Plugins::YubiKey::OpenPGP
73
+
74
+ # Read-only capability discovery and native OpenSSL public-key export.
75
+ supported = pgp.algorithms(connection: connection)
76
+ public_key = pgp.public_key(connection: connection, slot: :sig)
77
+
78
+ # Explicitly destructive on-card generation; returns an OpenSSL public key.
79
+ public_key = pgp.generate_key(
80
+ connection: connection, slot: :sig, algorithm: :secp256r1,
81
+ admin_pin: admin_pin, confirm: true
82
+ )
83
+
84
+ signature = pgp.sign(
85
+ connection: connection, message: message, hash_algorithm: :sha256,
86
+ pin: user_pin, confirm: true
87
+ )
88
+ authentication_signature = pgp.authenticate(
89
+ connection: connection, message: challenge, pin: user_pin, confirm: true
90
+ )
91
+ plaintext_or_shared_secret = pgp.decipher(
92
+ connection: connection, value: ciphertext_or_peer_public_key,
93
+ pin: user_pin, confirm: true
94
+ )
95
+ # decrypt is an alias to the same decipher implementation.
96
+ ```
97
+
98
+ - RSA signing sends DigestInfo through PSO 2A/9E9A; authentication uses INTERNAL
99
+ AUTHENTICATE 88. RSA signatures are raw modulus-length bytes.
100
+ - ECDSA signs a SHA digest; card `r || s` is checked and converted to DER.
101
+ - Ed25519 sends the original message and returns raw 64-byte signatures.
102
+ `prehashed: true` is rejected for Ed25519. Other signing methods can accept
103
+ prehashed SHA-1/224/256/384/512 input of exactly the digest length.
104
+ - RSA decipher sends the zero padding indicator before a modulus-length
105
+ PKCS1-v1_5 ciphertext. EC/X25519 decipher sends the A6/7F49/86 peer template
106
+ and returns the **raw shared secret**, not a decrypted OpenPGP message.
107
+ - `verify_pin(mode: :signature|:extended|:admin, ...)` chooses PW1/81, PW1/82 or
108
+ PW3/83. `unverify_pin` clears that reference on firmware 5.6 or newer.
109
+ - RSA2048/3072/4096, NIST P-256/384/521, secp256k1, Brainpool P256/P384/P512,
110
+ Ed25519 and X25519 are recognized. Actual generation/import support comes
111
+ from firmware restrictions and advertised FA entries, not merely host support.
112
+ - NEO RSA imports use CRT components; 4.4 FIPS firmware is limited to RSA2048.
113
+ RSA generation is disabled on 4.2.0–4.3.4 vulnerable firmware.
114
+ - PKCS8 Curve25519 import extracts the inner 32-byte seed/scalar natively;
115
+ X25519 reverses scalar bytes for the card. P-521 scalars use 66 bytes.
116
+ - PIN KDF supports card-advertised iterated-and-salted S2K with SHA256/SHA512,
117
+ role-specific salts and bounded byte counts. Unknown KDFs never send plaintext.
118
+
119
+ ## Verification and boundaries
120
+
121
+ Run the paired offline suite:
122
+
123
+ ```text
124
+ bundle exec rspec spec/lib/pwn/plugins/yubikey/openpgp_spec.rb
125
+ bundle exec rubocop lib/pwn/plugins/yubikey/openpgp.rb spec/lib/pwn/plugins/yubikey/openpgp_spec.rb
126
+ ```
127
+
128
+ Tests use the actual shared Protocol plus a strict synthetic Connection, literal
129
+ APDU fixtures, native OpenSSL keys, RFC seed material, host signature verification,
130
+ certificate parsing, public-key equality and ECDH equality. They cover command and
131
+ response chaining, version quirks, readback mismatch, malformed input, consent,
132
+ missing secrets, incorrect PIN and timeout without replay. They do not establish
133
+ USB/NFC interoperability or on-device behavior on every firmware/FIPS variant.
134
+
135
+ This is the card application layer, not a keyring, OpenPGP packet/ASCII-armor
136
+ encoder or signature/attestation trust-chain validator. RSA OAEP and symmetric
137
+ OpenPGP message decryption are not implied by the card PKCS1/ECDH primitives.
138
+
139
+ Protocol provenance: OpenPGP Card specification 3.4
140
+ (<https://gnupg.org/ftp/specs/OpenPGP-smart-card-application-3.4.pdf>),
141
+ Yubico YubiKey Manager 5.9.1 `yubikit/openpgp.py` and
142
+ `ykman/_cli/openpgp.py` (<https://github.com/Yubico/yubikey-manager>).
143
+ The cached official sources were consulted read-only, never used as runtime
144
+ backends. Source attribution and the reference BSD license are retained in Ruby.