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.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/Gemfile +8 -8
- data/README.md +37 -0
- data/Rakefile +19 -1
- data/bin/pwn +2 -1
- data/bin/pwn_setup +1 -1
- data/bin/pwn_web_cache_deception +35 -7
- data/build_gem.sh +1 -1
- data/documentation/AI-Integration.md +7 -5
- data/documentation/Agent-Tool-Registry.md +2 -2
- data/documentation/Backup-Retention.md +82 -0
- data/documentation/Configuration.md +10 -6
- data/documentation/Contributing.md +1 -1
- data/documentation/Cron.md +64 -35
- data/documentation/Diagrams.md +2 -2
- data/documentation/General-PWN-Usage.md +4 -3
- data/documentation/Home.md +6 -5
- data/documentation/How-PWN-Works.md +7 -6
- data/documentation/Installation.md +254 -10
- data/documentation/Mission-Cancellation.md +134 -0
- data/documentation/Operability.md +17 -0
- data/documentation/Persistence.md +8 -6
- data/documentation/Plugin-Degradation.md +12 -0
- data/documentation/Plugins.md +3 -2
- data/documentation/Reinforcement-Learning.md +5 -6
- data/documentation/Runtime-Source-Protection.md +162 -0
- data/documentation/Transparent-Browser.md +11 -0
- data/documentation/Troubleshooting.md +3 -2
- data/documentation/What-is-PWN.md +2 -2
- data/documentation/YubiKey-Vaults.md +183 -0
- data/documentation/YubiKey.md +339 -0
- data/documentation/diagrams/cron-scheduling.svg +130 -128
- data/documentation/diagrams/dot/cron-scheduling.dot +5 -5
- data/documentation/diagrams/dot/persistence-filesystem.dot +5 -3
- data/documentation/diagrams/dot/plugin-ecosystem.dot +2 -2
- data/documentation/diagrams/persistence-filesystem.svg +172 -142
- data/documentation/diagrams/plugin-ecosystem.svg +64 -64
- data/documentation/pwn-ai-Agent.md +10 -4
- data/etc/default_cron/session_cleanup.rb +1245 -22
- data/etc/default_skills/pwn/ai/agent/mission_namespace/SKILL.md +21 -0
- data/etc/default_skills/pwn/ai/agent/tools/browser_act/SKILL.md +45 -0
- data/etc/default_skills/pwn/ai/agent/tools/loot_query/SKILL.md +27 -26
- data/etc/default_skills/pwn/ai/agent/tools/recon_run/SKILL.md +25 -15
- data/etc/default_skills/pwn/ai/agent/tools/workspace_tool/SKILL.md +24 -0
- data/etc/default_skills/pwn/ai/server_tools/SKILL.md +25 -0
- data/etc/default_skills/pwn/backups/SKILL.md +54 -0
- data/etc/default_skills/pwn/plugins/recon/SKILL.md +12 -1
- data/etc/default_skills/pwn/plugins/yubikey/SKILL.md +157 -0
- data/etc/default_skills/pwn/plugins/yubikey/fido/SKILL.md +189 -0
- data/etc/default_skills/pwn/plugins/yubikey/hsmauth/SKILL.md +83 -0
- data/etc/default_skills/pwn/plugins/yubikey/management/SKILL.md +80 -0
- data/etc/default_skills/pwn/plugins/yubikey/oath/SKILL.md +115 -0
- data/etc/default_skills/pwn/plugins/yubikey/openpgp/SKILL.md +144 -0
- data/etc/default_skills/pwn/plugins/yubikey/otp/SKILL.md +97 -0
- data/etc/default_skills/pwn/plugins/yubikey/piv/SKILL.md +110 -0
- data/etc/default_skills/pwn/plugins/yubikey/protocol/SKILL.md +21 -0
- data/etc/default_skills/pwn/plugins/yubikey/security_domain/SKILL.md +75 -0
- data/etc/default_skills/pwn/plugins/yubikey/security_domain/scp/SKILL.md +46 -0
- data/etc/default_skills/pwn/plugins/yubikey/transport/SKILL.md +98 -0
- data/etc/default_skills/pwn/redaction/SKILL.md +13 -1
- data/lib/pwn/ai/agent/dispatch.rb +26 -15
- data/lib/pwn/ai/agent/extrospection.rb +6 -7
- data/lib/pwn/ai/agent/learning.rb +62 -25
- data/lib/pwn/ai/agent/loop.rb +17 -1
- data/lib/pwn/ai/agent/mission_namespace.rb +331 -0
- data/lib/pwn/ai/agent/policy.rb +5 -0
- data/lib/pwn/ai/agent/policy_evaluation.rb +8 -8
- data/lib/pwn/ai/agent/prompt_builder.rb +13 -2
- data/lib/pwn/ai/agent/request_runtime.rb +122 -18
- data/lib/pwn/ai/agent/reward.rb +5 -3
- data/lib/pwn/ai/agent/skill_consolidation.rb +3 -2
- data/lib/pwn/ai/agent/skill_review.rb +9 -5
- data/lib/pwn/ai/agent/swarm.rb +35 -16
- data/lib/pwn/ai/agent/tools/browser_act.rb +32 -0
- data/lib/pwn/ai/agent/tools/loot_query.rb +11 -1
- data/lib/pwn/ai/agent/tools/recon_run.rb +11 -1
- data/lib/pwn/ai/agent/tools/workspace_tool.rb +29 -0
- data/lib/pwn/ai/agent/turn_finalizer.rb +6 -2
- data/lib/pwn/ai/agent.rb +1 -0
- data/lib/pwn/ai/anthropic.rb +8 -33
- data/lib/pwn/ai/cli.rb +34 -5
- data/lib/pwn/ai/gemini.rb +1 -0
- data/lib/pwn/ai/grok.rb +39 -28
- data/lib/pwn/ai/ollama.rb +4 -2
- data/lib/pwn/ai/open_ai.rb +11 -32
- data/lib/pwn/ai/open_web_ui.rb +62 -4
- data/lib/pwn/ai/router.rb +53 -3
- data/lib/pwn/ai/server_tools.rb +231 -0
- data/lib/pwn/ai.rb +1 -0
- data/lib/pwn/backups.rb +347 -0
- data/lib/pwn/config.rb +70 -39
- data/lib/pwn/cron.rb +120 -49
- data/lib/pwn/migrate.rb +150 -68
- data/lib/pwn/module_skills.rb +7 -0
- data/lib/pwn/plugins/ai_sandbox.rb +538 -1
- data/lib/pwn/plugins/authentication_helper.rb +74 -4
- data/lib/pwn/plugins/basic_auth.rb +2 -1
- data/lib/pwn/plugins/binary_parser.rb +5 -4
- data/lib/pwn/plugins/ghidra.rb +4 -3
- data/lib/pwn/plugins/google_workspace.rb +4 -21
- data/lib/pwn/plugins/jobs.rb +1 -1
- data/lib/pwn/plugins/log.rb +21 -3
- data/lib/pwn/plugins/preflight_checker.rb +34 -9
- data/lib/pwn/plugins/recon.rb +4 -1
- data/lib/pwn/plugins/repl/ai.rb +305 -27
- data/lib/pwn/plugins/repl/ai_console.rb +273 -89
- data/lib/pwn/plugins/repl/ai_swarm.rb +181 -62
- data/lib/pwn/plugins/repl/mesh.rb +3 -16
- data/lib/pwn/plugins/repl/vault.rb +4 -15
- data/lib/pwn/plugins/repl.rb +1 -216
- data/lib/pwn/plugins/sandbox/driver.rb +4 -0
- data/lib/pwn/plugins/transparent_browser.rb +215 -0
- data/lib/pwn/plugins/vault.rb +412 -135
- data/lib/pwn/plugins/yubikey/fido.rb +1288 -0
- data/lib/pwn/plugins/yubikey/hsmauth.rb +439 -0
- data/lib/pwn/plugins/yubikey/management.rb +686 -0
- data/lib/pwn/plugins/yubikey/oath.rb +766 -0
- data/lib/pwn/plugins/yubikey/openpgp.rb +1063 -0
- data/lib/pwn/plugins/yubikey/otp.rb +897 -0
- data/lib/pwn/plugins/yubikey/piv.rb +1315 -0
- data/lib/pwn/plugins/yubikey/protocol.rb +341 -0
- data/lib/pwn/plugins/yubikey/security_domain/scp.rb +482 -0
- data/lib/pwn/plugins/yubikey/security_domain.rb +464 -0
- data/lib/pwn/plugins/yubikey/transport.rb +1017 -0
- data/lib/pwn/plugins/yubikey.rb +844 -0
- data/lib/pwn/plugins.rb +1 -0
- data/lib/pwn/redaction.rb +69 -12
- data/lib/pwn/session_trace.rb +4 -0
- data/lib/pwn/sessions.rb +58 -16
- data/lib/pwn/setup.rb +123 -41
- data/lib/pwn/version.rb +1 -1
- data/lib/pwn/www/app_cobalt_io.rb +16 -5
- data/lib/pwn/www/bug_crowd.rb +16 -5
- data/lib/pwn/www/coinbase_pro.rb +16 -5
- data/lib/pwn/www/github.rb +30 -14
- data/lib/pwn/www/paypal.rb +15 -4
- data/lib/pwn/www/synack.rb +16 -5
- data/lib/pwn/www/twitter.rb +17 -6
- data/lib/pwn.rb +1 -0
- data/scripts/build_gate.rb +58 -0
- data/spec/documentation/installation_md_spec.rb +1 -1
- data/spec/fixtures/yubikey/apdu_frames.json +162 -0
- data/spec/integration/authentication_helper_oath_spec.rb +117 -0
- data/spec/integration/build_gate_spec.rb +73 -0
- data/spec/integration/controller_spill_spec.rb +48 -0
- data/spec/integration/harness_process_spec.rb +97 -0
- data/spec/integration/mission_cancel_spec.rb +79 -0
- data/spec/integration/mission_curses_pty_spec.rb +44 -0
- data/spec/integration/persistence_roundtrip_spec.rb +1 -7
- data/spec/integration/readme_security_spec.rb +47 -0
- data/spec/integration/web_cache_deception_yubikey_spec.rb +159 -0
- data/spec/lib/pwn/ai/agent/dispatch_spec.rb +6 -3
- data/spec/lib/pwn/ai/agent/dispatch_vault_safety_spec.rb +86 -0
- data/spec/lib/pwn/ai/agent/injection_guard_spec.rb +2 -0
- data/spec/lib/pwn/ai/agent/learning_spec.rb +4 -4
- data/spec/lib/pwn/ai/agent/loop_artifacts_spec.rb +5 -0
- data/spec/lib/pwn/ai/agent/loop_spec.rb +104 -29
- data/spec/lib/pwn/ai/agent/mission_namespace_spec.rb +138 -0
- data/spec/lib/pwn/ai/agent/policy_spec.rb +27 -0
- data/spec/lib/pwn/ai/agent/request_runtime_spec.rb +48 -0
- data/spec/lib/pwn/ai/agent/shell_search_effect_spec.rb +44 -0
- data/spec/lib/pwn/ai/agent/solve_spec.rb +6 -4
- data/spec/lib/pwn/ai/agent/tools/browser_act_spec.rb +44 -0
- data/spec/lib/pwn/ai/agent/tools/loot_query_yubikey_spec.rb +55 -0
- data/spec/lib/pwn/ai/agent/tools/recon_run_yubikey_spec.rb +43 -0
- data/spec/lib/pwn/ai/agent/tools/workspace_tool_spec.rb +20 -0
- data/spec/lib/pwn/ai/agent/turn_finalizer_spec.rb +7 -0
- data/spec/lib/pwn/ai/anthropic_vault_enrollment_spec.rb +37 -0
- data/spec/lib/pwn/ai/cli_spec.rb +6 -9
- data/spec/lib/pwn/ai/oauth_vault_persistence_spec.rb +195 -0
- data/spec/lib/pwn/ai/open_ai_spec.rb +2 -2
- data/spec/lib/pwn/ai/open_web_ui_workspace_spec.rb +36 -0
- data/spec/lib/pwn/ai/router_spec.rb +31 -3
- data/spec/lib/pwn/ai/server_tools_spec.rb +39 -0
- data/spec/lib/pwn/backups_spec.rb +114 -0
- data/spec/lib/pwn/config_redaction_yubikey_spec.rb +28 -0
- data/spec/lib/pwn/config_yubikey_spec.rb +92 -0
- data/spec/lib/pwn/cron_spec.rb +113 -6
- data/spec/lib/pwn/migrate_spec.rb +108 -5
- data/spec/lib/pwn/migrate_yubikey_spec.rb +128 -0
- data/spec/lib/pwn/module_skills_spec.rb +8 -0
- data/spec/lib/pwn/plugins/ai_sandbox_spec.rb +230 -0
- data/spec/lib/pwn/plugins/authentication_helper_native_yubikey_spec.rb +40 -0
- data/spec/lib/pwn/plugins/authentication_helper_yubikey_spec.rb +90 -0
- data/spec/lib/pwn/plugins/capability_broker/daemon_spec.rb +1 -1
- data/spec/lib/pwn/plugins/google_workspace_spec.rb +17 -35
- data/spec/lib/pwn/plugins/log_spec.rb +7 -0
- data/spec/lib/pwn/plugins/log_yubikey_spec.rb +47 -0
- data/spec/lib/pwn/plugins/preflight_checker_native_yubikey_spec.rb +19 -0
- data/spec/lib/pwn/plugins/preflight_checker_yubikey_spec.rb +55 -0
- data/spec/lib/pwn/plugins/recon_yubikey_spec.rb +43 -0
- data/spec/lib/pwn/plugins/repl/ai_console_spec.rb +125 -78
- data/spec/lib/pwn/plugins/repl/ai_legacy_containment_spec.rb +200 -0
- data/spec/lib/pwn/plugins/repl/ai_swarm_spec.rb +30 -16
- data/spec/lib/pwn/plugins/repl/ai_vault_edit_yubikey_spec.rb +103 -0
- data/spec/lib/pwn/plugins/repl/ai_yubikey_spec.rb +48 -0
- data/spec/lib/pwn/plugins/repl/mesh_vault_persistence_spec.rb +149 -0
- data/spec/lib/pwn/plugins/repl/vault_yaml_serialization_spec.rb +11 -0
- data/spec/lib/pwn/plugins/repl/vault_yubikey_spec.rb +39 -0
- data/spec/lib/pwn/plugins/repl_spec.rb +56 -5
- data/spec/lib/pwn/plugins/transparent_browser_jev_spec.rb +46 -0
- data/spec/lib/pwn/plugins/vault_native_yubikey_spec.rb +29 -0
- data/spec/lib/pwn/plugins/vault_publication_safety_spec.rb +258 -0
- data/spec/lib/pwn/plugins/vault_store_safety_spec.rb +241 -0
- data/spec/lib/pwn/plugins/vault_yubikey_safety_spec.rb +168 -0
- data/spec/lib/pwn/plugins/vault_yubikey_spec.rb +128 -0
- data/spec/lib/pwn/plugins/vault_yubikey_vector_spec.rb +45 -0
- data/spec/lib/pwn/plugins/yubikey/fido_spec.rb +854 -0
- data/spec/lib/pwn/plugins/yubikey/hsmauth_spec.rb +235 -0
- data/spec/lib/pwn/plugins/yubikey/management_spec.rb +277 -0
- data/spec/lib/pwn/plugins/yubikey/oath_spec.rb +349 -0
- data/spec/lib/pwn/plugins/yubikey/openpgp_spec.rb +810 -0
- data/spec/lib/pwn/plugins/yubikey/otp_spec.rb +337 -0
- data/spec/lib/pwn/plugins/yubikey/piv_spec.rb +646 -0
- data/spec/lib/pwn/plugins/yubikey/protocol_secure_spec.rb +28 -0
- data/spec/lib/pwn/plugins/yubikey/protocol_spec.rb +149 -0
- data/spec/lib/pwn/plugins/yubikey/protocol_vectors_spec.rb +18 -0
- data/spec/lib/pwn/plugins/yubikey/security_domain/scp_spec.rb +256 -0
- data/spec/lib/pwn/plugins/yubikey/security_domain/support.rb +153 -0
- data/spec/lib/pwn/plugins/yubikey/security_domain_spec.rb +296 -0
- data/spec/lib/pwn/plugins/yubikey/transport_spec.rb +760 -0
- data/spec/lib/pwn/plugins/yubikey_security_spec.rb +260 -0
- data/spec/lib/pwn/plugins/yubikey_spec.rb +209 -0
- data/spec/lib/pwn/plugins/yubikey_terminal_spec.rb +147 -0
- data/spec/lib/pwn/redaction_spec.rb +13 -10
- data/spec/lib/pwn/redaction_yubikey_spec.rb +61 -0
- data/spec/lib/pwn/session_cleanup_concurrency_spec.rb +201 -0
- data/spec/lib/pwn/session_cleanup_default_spec.rb +167 -2
- data/spec/lib/pwn/session_cleanup_legacy_spec.rb +101 -0
- data/spec/lib/pwn/session_cleanup_payload_spec.rb +335 -2
- data/spec/lib/pwn/session_cleanup_recovery_spec.rb +403 -0
- data/spec/lib/pwn/sessions_spec.rb +112 -0
- data/spec/lib/pwn/setup_native_yubikey_spec.rb +66 -0
- data/spec/lib/pwn/setup_spec.rb +3 -1
- data/spec/lib/pwn/setup_vault_yubikey_spec.rb +61 -0
- data/spec/lib/pwn/setup_yubikey_spec.rb +40 -0
- data/spec/lib/pwn/www/login_yubikey_spec.rb +273 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/support/gate_failure_formatter.rb +14 -0
- data/spec/support/harness/cli_faults.rb +13 -9
- data/spec/support/harness/controller_spill.rb +26 -0
- data/spec/support/harness/controller_state.rb +54 -0
- data/spec/support/harness/evaluate.rb +12 -1
- data/spec/support/harness/experiments.rb +1 -1
- data/spec/support/harness/fake_provider.rb +22 -6
- data/spec/support/harness/fault_preload.rb +3 -1
- data/spec/support/harness/mission_console.rb +54 -0
- data/spec/support/harness/mission_curses_pty.rb +41 -0
- data/spec/support/harness/mission_dispatch.rb +30 -0
- data/spec/support/harness/mission_fixture_preload.rb +24 -0
- data/spec/support/harness/mission_job_lifetime.rb +25 -0
- data/spec/support/harness/mission_legacy.rb +25 -0
- data/spec/support/harness/mission_normal_exit.rb +34 -0
- data/spec/support/harness/mission_swarm.rb +23 -0
- data/spec/support/harness/mission_unsupported.rb +29 -0
- data/spec/support/harness/package_acceptance.rb +33 -5
- data/spec/support/harness/session_acceptance.rb +4 -4
- data/spec/support/harness/swarm_acceptance.rb +1 -1
- data/spec/support/harness/swarm_control.rb +44 -0
- data/spec/support/mission_namespace.rb +14 -0
- data/spec/support/mission_pipe_queue.rb +33 -0
- data/third_party/pwn_rdoc.jsonl +599 -15
- metadata +140 -17
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'openssl'
|
|
4
|
+
require_relative 'protocol'
|
|
5
|
+
require_relative 'security_domain/scp'
|
|
6
|
+
|
|
7
|
+
module PWN
|
|
8
|
+
module Plugins
|
|
9
|
+
module YubiKey
|
|
10
|
+
# Native Security Domain administration. Reference: Yubico yubikit 5.9.1
|
|
11
|
+
# securitydomain.py; GlobalPlatform Card Specification 2.3, Amendments D/F.
|
|
12
|
+
module SecurityDomain
|
|
13
|
+
AID = ['a000000151000000'].pack('H*').freeze
|
|
14
|
+
OPERATIONS = ['info', 'reset', 'keys generate', 'keys import', 'keys export', 'keys delete', 'keys set-allowlist', 'keys set-certificate', 'keys set-ca-issuer'].freeze
|
|
15
|
+
SCP11_KIDS = [0x11, 0x13, 0x15].freeze
|
|
16
|
+
CA_KIDS = [0x10, *(0x20..0x2f)].freeze
|
|
17
|
+
CURVES = { 'prime256v1' => 0, 'secp384r1' => 1, 'secp521r1' => 2, 'brainpoolP256r1' => 3, 'brainpoolP384r1' => 5, 'brainpoolP512r1' => 7 }.freeze
|
|
18
|
+
|
|
19
|
+
# Parse nonsecret argv before selecting or authenticating a device.
|
|
20
|
+
class Request # rubocop:disable Metrics/ClassLength
|
|
21
|
+
def initialize(opts)
|
|
22
|
+
@opts = opts
|
|
23
|
+
@crypto = SCP::Crypto.new
|
|
24
|
+
parsed = Protocol.parse_args(args: opts.fetch(:args, []), flags: ['--force'], values: ['--replace-kvn'])
|
|
25
|
+
words = parsed[:positionals]
|
|
26
|
+
@operation = words.shift
|
|
27
|
+
@operation += " #{words.shift}" if @operation == 'keys'
|
|
28
|
+
raise ArgumentError, 'Unknown Security Domain operation' unless OPERATIONS.include?(@operation)
|
|
29
|
+
|
|
30
|
+
options = parsed[:options]
|
|
31
|
+
@params = {}
|
|
32
|
+
if %w[info reset].include?(@operation)
|
|
33
|
+
raise ArgumentError, 'Operation takes no positional arguments' unless words.empty?
|
|
34
|
+
raise ArgumentError, 'Unsupported option for operation' unless (options.keys - (@operation == 'reset' ? [:force] : [])).empty?
|
|
35
|
+
else
|
|
36
|
+
raise ArgumentError, 'Expected KID and KVN' if words.length < 2
|
|
37
|
+
|
|
38
|
+
@params = { kid: integer(words.shift), kvn: integer(words.shift) }
|
|
39
|
+
@params[:replace_kvn] = integer(options.fetch(:replace_kvn, opts.fetch(:replace_kvn, 0)))
|
|
40
|
+
allowed = if ['keys generate', 'keys import'].include?(@operation)
|
|
41
|
+
[:replace_kvn]
|
|
42
|
+
else
|
|
43
|
+
(@operation == 'keys delete' ? [:force] : [])
|
|
44
|
+
end
|
|
45
|
+
raise ArgumentError, 'Unsupported option for operation' unless (options.keys - allowed).empty?
|
|
46
|
+
raise ArgumentError, 'Replacement KVN must be in 0..127' unless @params[:replace_kvn].between?(0, 127)
|
|
47
|
+
|
|
48
|
+
# Binary export/generation returns data; '-' is an optional stdout
|
|
49
|
+
# compatibility marker, never an instruction to write a file.
|
|
50
|
+
words.pop if ['keys generate', 'keys export'].include?(@operation) && words == ['-']
|
|
51
|
+
if @operation == 'keys set-allowlist'
|
|
52
|
+
values = opts.fetch(:serials, words)
|
|
53
|
+
raise ArgumentError, 'Serials must be a bounded Array' unless values.is_a?(Array) && values.length <= 256
|
|
54
|
+
|
|
55
|
+
@params[:serials] = values.map { |value| integer(value, maximum: (1 << 160) - 1) }
|
|
56
|
+
words = []
|
|
57
|
+
end
|
|
58
|
+
raise ArgumentError, 'Unexpected positional argument; supply keys/certificates in memory' unless words.empty?
|
|
59
|
+
|
|
60
|
+
validate_reference
|
|
61
|
+
end
|
|
62
|
+
validate_consent
|
|
63
|
+
prepare_material
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def execute
|
|
67
|
+
session = Session.new(@opts)
|
|
68
|
+
case @operation
|
|
69
|
+
when 'info' then session.info
|
|
70
|
+
when 'reset' then session.reset
|
|
71
|
+
when 'keys generate' then session.generate(@params)
|
|
72
|
+
when 'keys import'
|
|
73
|
+
result = session.import_key(@params)
|
|
74
|
+
session.store_certificates(@params) if @params[:certificates]
|
|
75
|
+
session.store_ca(@params) if @params[:ski]
|
|
76
|
+
result
|
|
77
|
+
when 'keys export' then session.export(@params)
|
|
78
|
+
when 'keys delete' then session.delete(@params)
|
|
79
|
+
when 'keys set-allowlist' then session.allowlist(@params)
|
|
80
|
+
when 'keys set-certificate' then session.store_certificates(@params)
|
|
81
|
+
when 'keys set-ca-issuer' then session.store_ca(@params)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
def validate_reference
|
|
88
|
+
kid = @params[:kid]
|
|
89
|
+
kvn = @params[:kvn]
|
|
90
|
+
case @operation
|
|
91
|
+
when 'keys delete'
|
|
92
|
+
raise ArgumentError, 'Deleting all keys requires reset, not wildcard deletion' if kid.zero? && kvn.zero?
|
|
93
|
+
raise ArgumentError, 'Invalid key identifier' unless [0, 1, 2, 3, *SCP11_KIDS, *CA_KIDS].include?(kid)
|
|
94
|
+
raise ArgumentError, 'SCP03 keys must be deleted by a nonzero KVN' if [1, 2, 3].include?(kid) && kvn.zero?
|
|
95
|
+
when 'keys import'
|
|
96
|
+
raise ArgumentError, 'Invalid import KID' unless [1, *SCP11_KIDS, *CA_KIDS].include?(kid)
|
|
97
|
+
raise ArgumentError, 'Import KVN must be in 1..127' unless kvn.between?(1, 127)
|
|
98
|
+
when 'keys generate'
|
|
99
|
+
raise ArgumentError, 'Generation requires SCP11 KID and KVN in 1..127' unless SCP11_KIDS.include?(kid) && kvn.between?(1, 127)
|
|
100
|
+
when 'keys set-allowlist'
|
|
101
|
+
raise ArgumentError, 'Allowlist requires an OCE CA key reference' unless CA_KIDS.include?(kid) && kvn.positive?
|
|
102
|
+
else
|
|
103
|
+
raise ArgumentError, 'Invalid SCP11/CA key reference' unless [*SCP11_KIDS, *CA_KIDS].include?(kid) && kvn.positive?
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def validate_consent
|
|
108
|
+
return if ['info', 'keys export'].include?(@operation)
|
|
109
|
+
|
|
110
|
+
raise ConfirmationRequired, 'Security Domain mutation requires confirm: true' unless @opts[:confirm] == true
|
|
111
|
+
|
|
112
|
+
connection = @opts[:connection]
|
|
113
|
+
if @operation == 'reset'
|
|
114
|
+
raise ArgumentError, 'Reset requires a plain connection without an active secure channel' if connection.is_a?(SCP::Connection) || connection&.descriptor&.dig(:secure_channel)
|
|
115
|
+
else
|
|
116
|
+
raise SCP::ChannelError, 'SCP03 or SCP11a administration required' unless connection.is_a?(SCP::Connection) && connection.authenticated?
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def prepare_material
|
|
121
|
+
case @operation
|
|
122
|
+
when 'keys generate'
|
|
123
|
+
@params[:curve] = @opts.fetch(:curve, 'prime256v1')
|
|
124
|
+
raise ArgumentError, 'Unsupported EC curve' unless CURVES.key?(@params[:curve])
|
|
125
|
+
when 'keys import'
|
|
126
|
+
prepare_import
|
|
127
|
+
when 'keys set-certificate'
|
|
128
|
+
@params[:certificates] = @crypto.certificates(@opts.fetch(:certificates))
|
|
129
|
+
@crypto.validate_chain(@params[:certificates])
|
|
130
|
+
raise ArgumentError, 'Certificate bundle too large' if @params[:certificates].sum { |cert| cert.to_der.bytesize } > 60_000
|
|
131
|
+
when 'keys set-ca-issuer'
|
|
132
|
+
@params[:ski] = @opts[:ski]
|
|
133
|
+
raise ArgumentError, 'SKI must be a nonempty binary String of at most 64 bytes' unless @params[:ski].is_a?(String) && @params[:ski].bytesize.between?(1, 64)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def prepare_import
|
|
138
|
+
if @params[:kid] == 1
|
|
139
|
+
@params[:keys] = %i[key_enc key_mac key_dek].map do |name|
|
|
140
|
+
value = @opts[name]
|
|
141
|
+
value = @opts[:secret_provider].call(name) if value.nil? && @opts[:secret_provider].respond_to?(:call)
|
|
142
|
+
raise ArgumentError, 'Import requires three explicit 16-byte AES keys' unless value.is_a?(String) && value.bytesize == 16
|
|
143
|
+
|
|
144
|
+
value.b.dup
|
|
145
|
+
end
|
|
146
|
+
return
|
|
147
|
+
end
|
|
148
|
+
private_key = SCP11_KIDS.include?(@params[:kid])
|
|
149
|
+
value = @opts[:key]
|
|
150
|
+
if @opts[:certificate]
|
|
151
|
+
raise ArgumentError, 'CA certificate input cannot be combined with a key or private-key KID' if value || private_key
|
|
152
|
+
|
|
153
|
+
cert = @crypto.certificates([@opts[:certificate]]).first
|
|
154
|
+
constraints = cert.extensions.find { |ext| ext.oid == 'basicConstraints' }
|
|
155
|
+
usage = cert.extensions.find { |ext| ext.oid == 'keyUsage' }
|
|
156
|
+
raise ArgumentError, 'CA certificate requires CA:true and keyCertSign usage' unless constraints&.value&.include?('CA:TRUE') && usage&.value&.include?('Certificate Sign')
|
|
157
|
+
raise ArgumentError, 'CA certificate validity check failed' unless Time.now.between?(cert.not_before, cert.not_after)
|
|
158
|
+
|
|
159
|
+
value = cert.public_key
|
|
160
|
+
ski = cert.extensions.find { |ext| ext.oid == 'subjectKeyIdentifier' }
|
|
161
|
+
@params[:ski] = [ski.value.delete(':')].pack('H*') if ski
|
|
162
|
+
end
|
|
163
|
+
value = @opts[:secret_provider].call(:key) if value.nil? && @opts[:secret_provider].respond_to?(:call)
|
|
164
|
+
@params[:key] = @crypto.ec_key(value, private_key: private_key)
|
|
165
|
+
raise ArgumentError, 'CA imports must contain a public key, not private material' if !private_key && @params[:key].private?
|
|
166
|
+
|
|
167
|
+
@params[:curve] = @params[:key].group.curve_name
|
|
168
|
+
raise ArgumentError, 'Unsupported EC curve' unless CURVES.key?(@params[:curve])
|
|
169
|
+
raise ArgumentError, 'Non-block-aligned EC private scalars are unsupported by the YubiKey PUT KEY format' if private_key && (((@params[:key].group.degree + 7) / 8) % 16).nonzero?
|
|
170
|
+
|
|
171
|
+
if @opts[:certificates]
|
|
172
|
+
raise ArgumentError, 'Certificate bundles accompany SCP11 private keys only' unless private_key
|
|
173
|
+
|
|
174
|
+
@params[:certificates] = @crypto.certificates(@opts[:certificates])
|
|
175
|
+
leaf = @crypto.validate_chain(@params[:certificates])
|
|
176
|
+
raise ArgumentError, 'Imported key does not match certificate' unless @crypto.equal?(leaf.public_to_der, @params[:key].public_to_der)
|
|
177
|
+
raise ArgumentError, 'Certificate bundle too large' if @params[:certificates].sum { |cert| cert.to_der.bytesize } > 60_000
|
|
178
|
+
end
|
|
179
|
+
@params[:ski] = @opts[:ski] if @opts.key?(:ski)
|
|
180
|
+
return unless @params[:ski] && (!@params[:ski].is_a?(String) || !@params[:ski].bytesize.between?(1, 64))
|
|
181
|
+
|
|
182
|
+
raise ArgumentError, 'SKI must be a nonempty binary String of at most 64 bytes'
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def integer(value, maximum: 255)
|
|
186
|
+
return value if value.is_a?(Integer) && value.between?(0, maximum)
|
|
187
|
+
raise ArgumentError, 'Expected exact unsigned decimal or 0x-hex integer' unless value.is_a?(String) && value.match?(/\A(?:0x[0-9a-fA-F]+|[0-9]+)\z/)
|
|
188
|
+
|
|
189
|
+
number = Integer(value, value.start_with?('0x') ? 16 : 10)
|
|
190
|
+
raise ArgumentError, 'Integer outside permitted range' unless number.between?(0, maximum)
|
|
191
|
+
|
|
192
|
+
number
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Native administrative wire commands, after Request validation.
|
|
197
|
+
class Session # rubocop:disable Metrics/ClassLength
|
|
198
|
+
def initialize(opts)
|
|
199
|
+
@connection = opts.fetch(:connection)
|
|
200
|
+
@timeout = opts.fetch(:timeout, 30)
|
|
201
|
+
raise ArgumentError, 'An APDU connection is required' unless @connection.descriptor[:protocol] == :apdu
|
|
202
|
+
raise ArgumentError, 'Timeout must be finite and positive' unless @timeout.is_a?(Numeric) && @timeout.finite? && @timeout.positive?
|
|
203
|
+
|
|
204
|
+
Protocol.select(connection: @connection, aid: AID, timeout: @timeout) unless @connection.is_a?(SCP::Connection)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def info
|
|
208
|
+
keys = key_information
|
|
209
|
+
recognition = Protocol.untlv(data: get(0x66))
|
|
210
|
+
raise ResponseError, 'Invalid card recognition data' unless recognition.length == 1 && recognition.first[:tag] == 0x73
|
|
211
|
+
|
|
212
|
+
{ keys: keys, card_recognition_data: recognition.first[:value], ca_identifiers: ca_identifiers }
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def key_information
|
|
216
|
+
keys = Protocol.untlv(data: get(0xe0)).map do |row|
|
|
217
|
+
value = row[:value]
|
|
218
|
+
raise ResponseError, 'Invalid key-information template' unless row[:tag] == 0xc0 && value.bytesize >= 4 && value.bytesize.even?
|
|
219
|
+
|
|
220
|
+
pairs = value.byteslice(2..).bytes.each_slice(2).to_a
|
|
221
|
+
raise ResponseError, 'Duplicate key type in key-information template' unless pairs.map(&:first).uniq.length == pairs.length
|
|
222
|
+
|
|
223
|
+
{ kid: value.getbyte(0), kvn: value.getbyte(1), types: pairs.to_h }
|
|
224
|
+
end
|
|
225
|
+
raise ResponseError, 'Duplicate key reference' unless keys.map { |key| [key[:kid], key[:kvn]] }.uniq.length == keys.length
|
|
226
|
+
|
|
227
|
+
keys
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def ca_identifiers
|
|
231
|
+
[[0xff33, :kloc], [0xff34, :klcc]].flat_map do |tag, kind|
|
|
232
|
+
begin
|
|
233
|
+
rows = Protocol.untlv(data: get(tag))
|
|
234
|
+
rescue Protocol::APDUError => e
|
|
235
|
+
raise unless e.status == 0x6a88
|
|
236
|
+
|
|
237
|
+
next []
|
|
238
|
+
end
|
|
239
|
+
raise ResponseError, 'Invalid CA identifier sequence' unless rows.length.even?
|
|
240
|
+
|
|
241
|
+
rows.each_slice(2).map do |ski, ref|
|
|
242
|
+
raise ResponseError, 'Invalid CA identifier record' unless ski[:tag] == 0x42 && ref[:tag] == 0x83 && ref[:value].bytesize == 2 && !ski[:value].empty?
|
|
243
|
+
|
|
244
|
+
{ kid: ref[:value].getbyte(0), kvn: ref[:value].getbyte(1), ski: ski[:value], kind: kind }
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def import_key(params)
|
|
250
|
+
crypto = SCP::Crypto.new
|
|
251
|
+
data = [params[:kvn]].pack('C')
|
|
252
|
+
expected = data.dup
|
|
253
|
+
p2 = params[:kid]
|
|
254
|
+
if params[:keys]
|
|
255
|
+
p2 |= 0x80
|
|
256
|
+
params[:keys].each do |key|
|
|
257
|
+
check = crypto.aes(key, "\1".b * 16, initialization_vector: "\0".b * 16).byteslice(0, 3)
|
|
258
|
+
data += Protocol.tlv(tag: 0x88, value: @connection.wrap_key(key)) + "\3".b + check
|
|
259
|
+
expected += check
|
|
260
|
+
end
|
|
261
|
+
else
|
|
262
|
+
key = params[:key]
|
|
263
|
+
if key.private?
|
|
264
|
+
size = (key.group.degree + 7) / 8
|
|
265
|
+
scalar = key.private_key.to_s(2).rjust(size, "\0".b)
|
|
266
|
+
data += Protocol.tlv(tag: 0xb1, value: @connection.wrap_key(scalar))
|
|
267
|
+
else
|
|
268
|
+
data += Protocol.tlv(tag: 0xb0, value: key.public_key.to_octet_string(:uncompressed))
|
|
269
|
+
end
|
|
270
|
+
data += Protocol.tlv(tag: 0xf0, value: [CURVES.fetch(params[:curve])].pack('C')) + "\0".b
|
|
271
|
+
end
|
|
272
|
+
response = send_apdu(cla: 0x80, ins: 0xd8, p1: params[:replace_kvn], p2: p2, data: data)
|
|
273
|
+
raise ResponseError, 'Imported key check value mismatch; do not retry an uncertain write' unless crypto.equal?(expected, response)
|
|
274
|
+
|
|
275
|
+
{ imported: true, kid: params[:kid], kvn: params[:kvn] }
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def generate(params)
|
|
279
|
+
data = [params[:kvn]].pack('C') + Protocol.tlv(tag: 0xf0, value: [CURVES.fetch(params[:curve])].pack('C'))
|
|
280
|
+
response = Protocol.untlv(data: send_apdu(cla: 0x80, ins: 0xf1, p1: params[:replace_kvn], p2: params[:kid], data: data))
|
|
281
|
+
raise ResponseError, 'Invalid generated public key response' unless response.length == 1 && response.first[:tag] == 0xb0
|
|
282
|
+
|
|
283
|
+
SCP::Crypto.new.public_point(response.first[:value], params[:curve]).public_to_pem.b
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def export(params)
|
|
287
|
+
rows = Protocol.untlv(data: get(0xbf21, reference(params)))
|
|
288
|
+
raise ResponseError, 'No certificate chain stored for key' if rows.empty?
|
|
289
|
+
|
|
290
|
+
rows.reverse.map do |row|
|
|
291
|
+
raise ResponseError, 'Invalid certificate bundle' unless row[:tag] == 0x30
|
|
292
|
+
|
|
293
|
+
OpenSSL::X509::Certificate.new(Protocol.tlv(tag: 0x30, value: row[:value])).to_pem.b
|
|
294
|
+
end.join.b
|
|
295
|
+
rescue OpenSSL::OpenSSLError
|
|
296
|
+
raise ResponseError, 'Invalid stored certificate encoding', cause: nil
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def delete(params)
|
|
300
|
+
kid = [1, 2, 3].include?(params[:kid]) ? 0 : params[:kid]
|
|
301
|
+
data = kid.zero? ? ''.b : Protocol.tlv(tag: 0xd0, value: [kid].pack('C'))
|
|
302
|
+
data += Protocol.tlv(tag: 0xd2, value: [params[:kvn]].pack('C')) unless params[:kvn].zero?
|
|
303
|
+
send_apdu(cla: 0x80, ins: 0xe4, p1: 0, p2: 0, data: data)
|
|
304
|
+
{ deleted: true, kid: params[:kid], kvn: params[:kvn] }
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
def allowlist(params)
|
|
308
|
+
serials = params[:serials].map do |serial|
|
|
309
|
+
hex = serial.to_s(16)
|
|
310
|
+
hex = "0#{hex}" if hex.length.odd?
|
|
311
|
+
bytes = [hex].pack('H*')
|
|
312
|
+
bytes = "\0".b + bytes if bytes.getbyte(0).anybits?(0x80)
|
|
313
|
+
Protocol.tlv(tag: 0x93, value: bytes)
|
|
314
|
+
end.join.b
|
|
315
|
+
store(reference(params) + Protocol.tlv(tag: 0x70, value: serials))
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
def store_certificates(params)
|
|
319
|
+
store(reference(params) + Protocol.tlv(tag: 0xbf21, value: params[:certificates].map(&:to_der).join.b))
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def store_ca(params)
|
|
323
|
+
role = SCP11_KIDS.include?(params[:kid]) ? 1 : 0
|
|
324
|
+
store(Protocol.tlv(tag: 0xa6, value: Protocol.tlv(tag: 0x80, value: [role].pack('C')) + Protocol.tlv(tag: 0x42, value: params[:ski]) + Protocol.tlv(tag: 0x83, value: [params[:kid], params[:kvn]].pack('CC'))))
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def reset
|
|
328
|
+
keys = key_information
|
|
329
|
+
raise ResponseError, 'Unknown key type; refusing incomplete reset' unless keys.all? { |key| [1, 2, 3, *SCP11_KIDS, *CA_KIDS].include?(key[:kid]) }
|
|
330
|
+
|
|
331
|
+
keys.each do |key|
|
|
332
|
+
kid = key[:kid]
|
|
333
|
+
next if [2, 3].include?(kid)
|
|
334
|
+
|
|
335
|
+
kvn = key[:kvn]
|
|
336
|
+
ins = case kid
|
|
337
|
+
when 1
|
|
338
|
+
kid = kvn = 0
|
|
339
|
+
0x50
|
|
340
|
+
when 0x11, 0x15 then 0x82
|
|
341
|
+
when 0x13 then 0x88
|
|
342
|
+
else
|
|
343
|
+
raise ResponseError, 'Unknown key type; refusing incomplete reset' unless CA_KIDS.include?(kid)
|
|
344
|
+
|
|
345
|
+
0x2a
|
|
346
|
+
end
|
|
347
|
+
blocked = false
|
|
348
|
+
65.times do
|
|
349
|
+
send_apdu(cla: 0x80, ins: ins, p1: kvn, p2: kid, data: "\0".b * 8)
|
|
350
|
+
rescue Protocol::APDUError => e
|
|
351
|
+
if [0x6983, 0x6982].include?(e.status)
|
|
352
|
+
blocked = true
|
|
353
|
+
break
|
|
354
|
+
end
|
|
355
|
+
raise unless e.status == 0x6a80
|
|
356
|
+
end
|
|
357
|
+
raise ResponseError, 'Reset retry bound reached; state uncertain, do not retry automatically' unless blocked
|
|
358
|
+
end
|
|
359
|
+
after = key_information
|
|
360
|
+
default_present = after.any? { |key| key[:kid] == 1 && key[:kvn] == 0xff }
|
|
361
|
+
default_only = after.all? { |key| key[:kid] == 0x13 || ([1, 2, 3].include?(key[:kid]) && key[:kvn] == 0xff) }
|
|
362
|
+
raise ResponseError, 'Security Domain reset state could not be verified' unless default_present && default_only
|
|
363
|
+
|
|
364
|
+
{ reset: true, keys: after }
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
private
|
|
368
|
+
|
|
369
|
+
def reference(params)
|
|
370
|
+
Protocol.tlv(tag: 0xa6, value: Protocol.tlv(tag: 0x83, value: [params[:kid], params[:kvn]].pack('CC')))
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def store(data)
|
|
374
|
+
send_apdu(ins: 0xe2, p1: 0x90, p2: 0, data: data)
|
|
375
|
+
{ stored: true }
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
def get(tag, data = ''.b)
|
|
379
|
+
send_apdu(ins: 0xca, p1: tag >> 8, p2: tag & 0xff, data: data)
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
def send_apdu(opts)
|
|
383
|
+
Protocol.apdu(opts.merge(connection: @connection, timeout: @timeout, extended: true))
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
private_constant :Request, :Session
|
|
388
|
+
|
|
389
|
+
# Execute a native Security Domain management leaf operation.
|
|
390
|
+
public_class_method def self.execute(opts = {})
|
|
391
|
+
args = opts[:args] || []
|
|
392
|
+
raise ArgumentError, 'args must be an Array of Strings' unless args.is_a?(Array) && args.all?(String)
|
|
393
|
+
return { operations: operations } if args.empty? || args == ['keys']
|
|
394
|
+
|
|
395
|
+
Request.new(opts).execute
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
# Authenticate an APDU transport with explicit, memory-only keys.
|
|
399
|
+
public_class_method def self.secure_connection(opts = {})
|
|
400
|
+
raise ArgumentError, 'connection is required' unless opts[:connection]
|
|
401
|
+
|
|
402
|
+
SCP.open(opts)
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
# Enumerate implemented leaf operations without accessing a device.
|
|
406
|
+
public_class_method def self.operations
|
|
407
|
+
OPERATIONS.dup
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
# Return module attribution without device access.
|
|
411
|
+
public_class_method def self.authors
|
|
412
|
+
"AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n"
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
# Print callable API documentation without device access.
|
|
416
|
+
public_class_method def self.help
|
|
417
|
+
puts <<~HELP
|
|
418
|
+
Execute a native Security Domain management operation.
|
|
419
|
+
#{self}.execute(
|
|
420
|
+
args: 'optional - leaf command Array; empty returns local metadata',
|
|
421
|
+
connection: 'required - native APDU handle; SCP03/SCP11a wrapper for mutations except reset',
|
|
422
|
+
timeout: 'optional - finite positive per-command seconds, default 30',
|
|
423
|
+
confirm: 'optional - literal true required for mutations, including reset',
|
|
424
|
+
key_enc: 'optional - required 16-byte new K-ENC for SCP03 import',
|
|
425
|
+
key_mac: 'optional - required 16-byte new K-MAC for SCP03 import',
|
|
426
|
+
key_dek: 'optional - required 16-byte new K-DEK for SCP03 import',
|
|
427
|
+
key: 'optional - in-memory EC private key for SCP11 or public key for CA import; object/PEM/DER',
|
|
428
|
+
certificate: 'optional - in-memory CA X509 object/PEM/DER instead of key for CA import',
|
|
429
|
+
certificates: 'optional - leaf-last X509 bundle for set-certificate or private-key import',
|
|
430
|
+
ski: 'optional - raw 1..64 byte issuer identifier for set-ca-issuer or import',
|
|
431
|
+
curve: 'optional - OpenSSL named curve for generation; default prime256v1',
|
|
432
|
+
replace_kvn: 'optional - integer 0..127 replacing an existing KVN; default 0 creates',
|
|
433
|
+
serials: 'optional - Array of certificate serial integers for set-allowlist, instead of argv serials',
|
|
434
|
+
secret_provider: 'optional - callable accepting a missing import field Symbol; invoked only after consent'
|
|
435
|
+
)
|
|
436
|
+
Authenticate a native APDU transport using explicit in-memory keys.
|
|
437
|
+
#{self}.secure_connection(
|
|
438
|
+
connection: 'required - plain native APDU transport; owned by returned wrapper',
|
|
439
|
+
scp: 'required - Symbol scp03, scp11a, scp11b or scp11c',
|
|
440
|
+
kvn: 'required - exact integer card key version 0..255; zero explicitly selects any version',
|
|
441
|
+
kid: 'optional - exact integer KID; must match selected protocol',
|
|
442
|
+
confirm: 'required - literal true permits authentication attempts',
|
|
443
|
+
timeout: 'optional - finite positive handshake seconds, default 30',
|
|
444
|
+
key_enc: 'optional - required 16-byte K-ENC binary String for SCP03',
|
|
445
|
+
key_mac: 'optional - required 16-byte K-MAC binary String for SCP03',
|
|
446
|
+
key_dek: 'optional - 16-byte SCP03 K-DEK, required to import secret keys',
|
|
447
|
+
card_public_key: 'optional - SCP11 pinned EC public key object/PEM/DER; required unless CA anchors supplied',
|
|
448
|
+
ca_certificates: 'optional - explicit trusted CA X509 object/PEM/DER Array for SCP11 card verification',
|
|
449
|
+
card_certificates: 'optional - leaf-last card X509 bundle; otherwise fetched when CA anchors provided',
|
|
450
|
+
oce_private_key: 'optional - required EC private key object/PEM/DER for SCP11a/c',
|
|
451
|
+
oce_certificates: 'optional - required leaf-last OCE X509 bundle for SCP11a/c',
|
|
452
|
+
oce_kid: 'optional - OCE CA KID integer 0, 16 or 32..47; default zero selects a matching CA',
|
|
453
|
+
oce_kvn: 'optional - OCE CA key version integer 0..255; default zero selects any'
|
|
454
|
+
)
|
|
455
|
+
Enumerate implemented native leaf operations without device access.
|
|
456
|
+
#{self}.operations
|
|
457
|
+
Return module author information.
|
|
458
|
+
#{self}.authors
|
|
459
|
+
HELP
|
|
460
|
+
end
|
|
461
|
+
end
|
|
462
|
+
end
|
|
463
|
+
end
|
|
464
|
+
end
|