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,439 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'openssl'
|
|
4
|
+
require 'securerandom'
|
|
5
|
+
|
|
6
|
+
module PWN
|
|
7
|
+
module Plugins
|
|
8
|
+
module YubiKey
|
|
9
|
+
# Native YubiHSM Auth APDUs. Protocol reference: yubikey-manager 5.9.1
|
|
10
|
+
# yubikit/hsmauth.py and ykman/_cli/hsmauth.py. Independent Ruby encoding.
|
|
11
|
+
module HSMAuth
|
|
12
|
+
AID = ['a000000527210701'].pack('H*').freeze
|
|
13
|
+
ALGORITHMS = { symmetric: 0x26, asymmetric: 0x27 }.freeze
|
|
14
|
+
|
|
15
|
+
# Retain the device's ephemeral state; do not reselect another application.
|
|
16
|
+
class Session
|
|
17
|
+
def initialize(connection, timeout)
|
|
18
|
+
raise ArgumentError, 'HSM Auth requires an APDU connection' unless connection.respond_to?(:exchange) && connection.respond_to?(:descriptor) && connection.descriptor[:protocol] == :apdu
|
|
19
|
+
raise ArgumentError, 'timeout must be finite and positive' unless timeout.is_a?(Numeric) && timeout.finite? && timeout.positive?
|
|
20
|
+
|
|
21
|
+
selected = Protocol.select(connection: connection, aid: AID, timeout: timeout)
|
|
22
|
+
version = Protocol.tlv_map(data: selected)[0x79]
|
|
23
|
+
raise ResponseError, 'Malformed HSM Auth SELECT version' unless version&.bytesize == 3
|
|
24
|
+
|
|
25
|
+
@state = { connection: connection, timeout: timeout, version: version.bytes.freeze }.freeze
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def [](key)
|
|
29
|
+
@state.fetch(key)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
public_class_method def self.open_session(opts = {})
|
|
34
|
+
Session.new(opts[:connection], opts.fetch(:timeout, 30))
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
public_class_method def self.execute(opts = {})
|
|
38
|
+
args = opts.fetch(:args, [])
|
|
39
|
+
raise ArgumentError, 'args must be an Array of Strings' unless args.is_a?(Array) && args.all?(String)
|
|
40
|
+
return { application: :hsmauth, operations: operations } if args.empty? || [%w[credentials], %w[access]].include?(args)
|
|
41
|
+
|
|
42
|
+
size = %w[credentials access].include?(args.first) ? 2 : 1
|
|
43
|
+
operation = args.first(size).join(' ')
|
|
44
|
+
raise ArgumentError, 'Unknown HSM Auth operation' unless operations.include?(operation)
|
|
45
|
+
|
|
46
|
+
flags = case operation
|
|
47
|
+
when 'credentials symmetric' then %w[--touch --generate]
|
|
48
|
+
when 'credentials generate', 'credentials derive', 'credentials import' then %w[--touch]
|
|
49
|
+
when 'reset', 'credentials delete' then %w[--force]
|
|
50
|
+
when 'access change-management-key' then %w[--generate]
|
|
51
|
+
else []
|
|
52
|
+
end
|
|
53
|
+
parsed = Protocol.parse_args(args: args.drop(size), flags: flags, values: operation == 'credentials export' ? %w[--format] : [])
|
|
54
|
+
positional = parsed[:positionals]
|
|
55
|
+
count = operation.start_with?('credentials ') && operation != 'credentials list' ? 1 : 0
|
|
56
|
+
raise ArgumentError, 'Unexpected HSM Auth positional arguments' unless positional.length == count
|
|
57
|
+
|
|
58
|
+
options = opts.merge(parsed[:options]).merge(label: positional.first, operation: operation)
|
|
59
|
+
%i[generate touch force].each do |control|
|
|
60
|
+
raise ArgumentError, "#{control} must be a boolean" if options.key?(control) && ![true, false].include?(options[control])
|
|
61
|
+
end
|
|
62
|
+
return list(session: session(options)) if operation == 'credentials list'
|
|
63
|
+
return put(options) if ['credentials symmetric', 'credentials derive', 'credentials generate', 'credentials import'].include?(operation)
|
|
64
|
+
return delete(options) if operation == 'credentials delete'
|
|
65
|
+
return export(options) if operation == 'credentials export'
|
|
66
|
+
return change_password(options) if operation == 'credentials change-password'
|
|
67
|
+
return change_management(options) if operation.start_with?('access ')
|
|
68
|
+
return reset(options) if operation == 'reset'
|
|
69
|
+
|
|
70
|
+
session = session(options)
|
|
71
|
+
{ version: session[:version], management_key_retries: retries(session: session) }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
public_class_method def self.operations
|
|
75
|
+
['info', 'reset', 'credentials list', 'credentials symmetric', 'credentials derive', 'credentials delete',
|
|
76
|
+
'credentials generate', 'credentials import', 'credentials export', 'credentials change-password',
|
|
77
|
+
'access change-management-key', 'access change-management-password']
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
public_class_method def self.calculate(opts = {})
|
|
81
|
+
label = label(value: opts[:label])
|
|
82
|
+
algorithm = opts.fetch(:algorithm, :symmetric)
|
|
83
|
+
raise ArgumentError, 'algorithm must be :symmetric or :asymmetric' unless ALGORITHMS.key?(algorithm)
|
|
84
|
+
|
|
85
|
+
context = bytes(value: opts[:context], size: algorithm == :symmetric ? 16 : 130)
|
|
86
|
+
crypto = opts[:card_crypto]
|
|
87
|
+
if algorithm == :asymmetric
|
|
88
|
+
public_key = public_point(value: opts[:public_key])
|
|
89
|
+
public_point(value: context.byteslice(0, 65))
|
|
90
|
+
public_point(value: context.byteslice(65, 65))
|
|
91
|
+
bytes(value: crypto, size: 16)
|
|
92
|
+
else
|
|
93
|
+
bytes(value: crypto, size: 8) if crypto
|
|
94
|
+
raise ArgumentError, 'public_key is only valid for asymmetric credentials' if opts[:public_key]
|
|
95
|
+
end
|
|
96
|
+
Protocol.require_confirm(opts)
|
|
97
|
+
password = password(opts.merge(name: :credential_password))
|
|
98
|
+
session = session(opts)
|
|
99
|
+
require_version(session: session, minimum: [5, 6, 0]) if algorithm == :asymmetric
|
|
100
|
+
payload = tlv(tag: 0x71, value: label) + tlv(tag: 0x77, value: context)
|
|
101
|
+
payload += tlv(tag: 0x7c, value: public_key) if public_key
|
|
102
|
+
payload += tlv(tag: 0x78, value: crypto) if crypto
|
|
103
|
+
payload += tlv(tag: 0x73, value: password)
|
|
104
|
+
response = send_apdu(session: session, ins: 3, data: payload)
|
|
105
|
+
raise ResponseError, 'HSM Auth must return exactly three 16-byte session keys' unless response.bytesize == 48
|
|
106
|
+
|
|
107
|
+
{ key_senc: response.byteslice(0, 16), key_smac: response.byteslice(16, 16), key_srmac: response.byteslice(32, 16) }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
private_class_method def self.session(opts = {})
|
|
111
|
+
supplied = opts[:session]
|
|
112
|
+
return open_session(opts) if supplied.nil?
|
|
113
|
+
raise ArgumentError, 'session must be an HSMAuth.open_session handle' unless supplied.is_a?(Session)
|
|
114
|
+
raise ArgumentError, 'connection differs from supplied session' if opts[:connection] && !supplied[:connection].equal?(opts[:connection])
|
|
115
|
+
|
|
116
|
+
supplied
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
public_class_method def self.challenge(opts = {})
|
|
120
|
+
label = label(value: opts[:label])
|
|
121
|
+
algorithm = opts.fetch(:algorithm, :symmetric)
|
|
122
|
+
raise ArgumentError, 'algorithm must be :symmetric or :asymmetric' unless ALGORITHMS.key?(algorithm)
|
|
123
|
+
|
|
124
|
+
Protocol.require_confirm(opts)
|
|
125
|
+
password = password(opts.merge(name: :credential_password)) if algorithm == :asymmetric || opts.key?(:credential_password)
|
|
126
|
+
session = session(opts)
|
|
127
|
+
require_version(session: session, minimum: [5, 6, 0])
|
|
128
|
+
payload = tlv(tag: 0x71, value: label)
|
|
129
|
+
payload += tlv(tag: 0x73, value: password) if password && Protocol.version_at_least?(version: session[:version], minimum: [5, 7, 1])
|
|
130
|
+
response = send_apdu(session: session, ins: 4, data: payload)
|
|
131
|
+
if algorithm == :asymmetric
|
|
132
|
+
public_point(value: response)
|
|
133
|
+
else
|
|
134
|
+
raise ResponseError, 'Malformed HSM Auth host challenge' unless response.bytesize == 8
|
|
135
|
+
|
|
136
|
+
response
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
private_class_method def self.require_version(opts = {})
|
|
141
|
+
raise Error, 'HSM Auth operation is unsupported by the selected application version' unless Protocol.version_at_least?(version: opts.fetch(:session)[:version], minimum: opts.fetch(:minimum))
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
private_class_method def self.public_point(opts = {})
|
|
145
|
+
value = opts[:value]
|
|
146
|
+
if value.is_a?(OpenSSL::PKey::EC)
|
|
147
|
+
raise ArgumentError, 'Only P-256 public keys are supported' unless value.group.curve_name == 'prime256v1'
|
|
148
|
+
|
|
149
|
+
value = value.public_key.to_octet_string(:uncompressed)
|
|
150
|
+
end
|
|
151
|
+
bytes(value: value, size: 65)
|
|
152
|
+
raise ArgumentError, 'Expected an uncompressed P-256 point' unless value.getbyte(0) == 4
|
|
153
|
+
|
|
154
|
+
group = OpenSSL::PKey::EC::Group.new('prime256v1')
|
|
155
|
+
point = OpenSSL::PKey::EC::Point.new(group, OpenSSL::BN.new(value, 2))
|
|
156
|
+
raise ArgumentError, 'Invalid P-256 public key' unless point.on_curve? && !point.infinity?
|
|
157
|
+
|
|
158
|
+
value.b
|
|
159
|
+
rescue OpenSSL::OpenSSLError
|
|
160
|
+
raise ArgumentError, 'Invalid P-256 public key', cause: nil
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
private_class_method def self.private_scalar(opts = {})
|
|
164
|
+
value = opts[:private_key]
|
|
165
|
+
value = Protocol.secret(secret_provider: opts[:secret_provider], name: :private_key) if value.nil?
|
|
166
|
+
if value.is_a?(String) && value.bytesize == 32
|
|
167
|
+
scalar = value.b
|
|
168
|
+
else
|
|
169
|
+
if value.is_a?(String)
|
|
170
|
+
raise ArgumentError, 'Private key input is too large' if value.bytesize > 65_536
|
|
171
|
+
|
|
172
|
+
key_password = opts[:key_password]
|
|
173
|
+
key_password = Protocol.secret(secret_provider: opts[:secret_provider], name: :key_password) if value.include?('ENCRYPTED') && key_password.nil?
|
|
174
|
+
value = OpenSSL::PKey.read(value, key_password || '')
|
|
175
|
+
end
|
|
176
|
+
raise ArgumentError, 'Expected a private P-256 key' unless value.is_a?(OpenSSL::PKey::EC) && value.private? && value.group.curve_name == 'prime256v1'
|
|
177
|
+
|
|
178
|
+
scalar = value.private_key.to_s(2).rjust(32, "\x00")
|
|
179
|
+
end
|
|
180
|
+
number = OpenSSL::BN.new(scalar, 2)
|
|
181
|
+
order = OpenSSL::PKey::EC::Group.new('prime256v1').order
|
|
182
|
+
raise ArgumentError, 'Invalid P-256 private scalar' unless number.to_i.positive? && number < order
|
|
183
|
+
|
|
184
|
+
scalar
|
|
185
|
+
rescue OpenSSL::OpenSSLError
|
|
186
|
+
raise ArgumentError, 'Invalid or encrypted private key; supply the correct key_password once', cause: nil
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
private_class_method def self.export(opts = {})
|
|
190
|
+
label = label(value: opts[:label])
|
|
191
|
+
format = opts.fetch(:format, 'PEM').to_s.upcase
|
|
192
|
+
raise ArgumentError, 'format must be RAW, PEM or DER' unless %w[RAW PEM DER].include?(format)
|
|
193
|
+
|
|
194
|
+
session = session(opts)
|
|
195
|
+
require_version(session: session, minimum: [5, 6, 0])
|
|
196
|
+
point = public_point(value: send_apdu(session: session, ins: 10, data: tlv(tag: 0x71, value: label)))
|
|
197
|
+
return point if format == 'RAW'
|
|
198
|
+
|
|
199
|
+
algorithm = OpenSSL::ASN1::Sequence.new([OpenSSL::ASN1::ObjectId.new('id-ecPublicKey'), OpenSSL::ASN1::ObjectId.new('prime256v1')])
|
|
200
|
+
der = OpenSSL::ASN1::Sequence.new([algorithm, OpenSSL::ASN1::BitString.new(point)]).to_der
|
|
201
|
+
format == 'DER' ? der : OpenSSL::PKey.read(der).public_to_pem
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
private_class_method def self.change_password(opts = {})
|
|
205
|
+
label = label(value: opts[:label])
|
|
206
|
+
admin = opts.key?(:management_key) || opts.key?(:management_password) || opts[:auth_method] == :management
|
|
207
|
+
raise ArgumentError, 'Management credential conflicts with explicit credential auth_method' if admin && opts[:auth_method] == :credential
|
|
208
|
+
raise ArgumentError, 'Cannot combine credential and management authentication' if admin && opts.key?(:credential_password)
|
|
209
|
+
raise ArgumentError, 'auth_method must be :management or :credential' if opts[:auth_method] && !%i[management credential].include?(opts[:auth_method])
|
|
210
|
+
|
|
211
|
+
Protocol.require_confirm(opts)
|
|
212
|
+
new_password = password(opts.merge(name: :new_credential_password))
|
|
213
|
+
auth = admin ? management_key(opts) : password(opts.merge(name: :credential_password))
|
|
214
|
+
session = session(opts)
|
|
215
|
+
require_version(session: session, minimum: [5, 8, 0])
|
|
216
|
+
payload = tlv(tag: 0x71, value: label) + tlv(tag: admin ? 0x7b : 0x73, value: auth) + tlv(tag: 0x73, value: new_password)
|
|
217
|
+
send_apdu(session: session, ins: 11, p1: admin ? 1 : 0, data: payload)
|
|
218
|
+
{ status: :acknowledged, verification: :not_readable }
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
private_class_method def self.change_management(opts = {})
|
|
222
|
+
raise ArgumentError, 'generate conflicts with supplied new management key' if opts[:generate] && (opts[:new_management_key] || opts[:new_management_password])
|
|
223
|
+
raise ArgumentError, 'Specify only one new management credential' if opts.key?(:new_management_key) && opts.key?(:new_management_password)
|
|
224
|
+
|
|
225
|
+
Protocol.require_confirm(opts)
|
|
226
|
+
new_key = if opts[:generate]
|
|
227
|
+
SecureRandom.random_bytes(16)
|
|
228
|
+
elsif opts[:operation] == 'access change-management-password'
|
|
229
|
+
password(opts.merge(name: :new_management_password))
|
|
230
|
+
else
|
|
231
|
+
bytes(value: Protocol.secret(value: opts[:new_management_key], secret_provider: opts[:secret_provider], name: :new_management_key), size: 16)
|
|
232
|
+
end
|
|
233
|
+
current = management_key(opts)
|
|
234
|
+
session = session(opts)
|
|
235
|
+
raise Error, 'Random management key generation is unsupported on HSM Auth 5.8 and later' if opts[:generate] && Protocol.version_at_least?(version: session[:version], minimum: [5, 8, 0])
|
|
236
|
+
|
|
237
|
+
send_apdu(session: session, ins: 8, data: tlv(tag: 0x7b, value: current) + tlv(tag: 0x7b, value: new_key))
|
|
238
|
+
result = { status: :acknowledged, verification: :not_readable }
|
|
239
|
+
result[:generated_management_key] = new_key if opts[:generate]
|
|
240
|
+
result
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
private_class_method def self.retries(opts = {})
|
|
244
|
+
value = send_apdu(session: opts.fetch(:session), ins: 9)
|
|
245
|
+
raise ResponseError, 'Malformed HSM Auth management retry counter' unless value.bytesize == 1 && value.getbyte(0) <= 8
|
|
246
|
+
|
|
247
|
+
value.getbyte(0)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
private_class_method def self.reset(opts = {})
|
|
251
|
+
Protocol.require_confirm(opts)
|
|
252
|
+
selected = session(opts)
|
|
253
|
+
send_apdu(session: selected, ins: 6, p1: 0xde, p2: 0xad)
|
|
254
|
+
fresh = open_session(connection: selected[:connection], timeout: selected[:timeout])
|
|
255
|
+
raise ResponseError, 'HSM Auth reset readback failed' unless list(session: fresh).empty? && retries(session: fresh) == 8
|
|
256
|
+
|
|
257
|
+
{ reset: true, verified: true, verification: :empty_inventory_and_retries }
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
private_class_method def self.send_apdu(opts = {})
|
|
261
|
+
session = opts.fetch(:session)
|
|
262
|
+
Protocol.apdu(connection: session[:connection], timeout: session[:timeout], ins: opts.fetch(:ins), p1: opts.fetch(:p1, 0), p2: opts.fetch(:p2, 0), data: opts.fetch(:data, ''.b))
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
private_class_method def self.tlv(opts = {})
|
|
266
|
+
Protocol.tlv(tag: opts.fetch(:tag), value: opts.fetch(:value))
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
private_class_method def self.label(opts = {})
|
|
270
|
+
value = opts[:value]
|
|
271
|
+
raise ArgumentError, 'label must contain 1 to 64 exact bytes' unless value.is_a?(String) && value.bytesize.between?(1, 64)
|
|
272
|
+
|
|
273
|
+
value.b
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
private_class_method def self.bytes(opts = {})
|
|
277
|
+
value = opts[:value]
|
|
278
|
+
raise ArgumentError, "Expected exactly #{opts[:size]} raw bytes" unless value.is_a?(String) && value.bytesize == opts[:size]
|
|
279
|
+
|
|
280
|
+
value.b
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
private_class_method def self.password(opts = {})
|
|
284
|
+
name = opts.fetch(:name)
|
|
285
|
+
value = opts[name]
|
|
286
|
+
value = Protocol.secret(secret_provider: opts[:secret_provider], name: name) if value.nil?
|
|
287
|
+
raise ArgumentError, 'HSM Auth password must be a String of at most 16 bytes' unless value.is_a?(String) && value.bytesize <= 16
|
|
288
|
+
|
|
289
|
+
value.b.ljust(16, "\x00")
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
private_class_method def self.management_key(opts = {})
|
|
293
|
+
raise ArgumentError, 'Choose management_key or management_password, not both' if opts.key?(:management_key) && opts.key?(:management_password)
|
|
294
|
+
return password(opts.merge(name: :management_password)) if opts.key?(:management_password)
|
|
295
|
+
|
|
296
|
+
value = Protocol.secret(value: opts[:management_key], secret_provider: opts[:secret_provider], name: :management_key)
|
|
297
|
+
bytes(value: value, size: 16)
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
private_class_method def self.list(opts = {})
|
|
301
|
+
result = Protocol.untlv(data: send_apdu(session: opts.fetch(:session), ins: 5)).map do |entry|
|
|
302
|
+
value = entry[:value]
|
|
303
|
+
raise ResponseError, 'Malformed HSM Auth LIST entry' unless entry[:tag] == 0x72 && value.bytesize.between?(4, 67) && [0, 1].include?(value.getbyte(1)) && value.getbyte(-1) <= 8
|
|
304
|
+
|
|
305
|
+
algorithm = ALGORITHMS.key(value.getbyte(0))
|
|
306
|
+
raise ResponseError, 'Unsupported HSM Auth credential algorithm' unless algorithm
|
|
307
|
+
|
|
308
|
+
{ label: value.byteslice(2, value.bytesize - 3), algorithm: algorithm, touch: value.getbyte(1) == 1, retries: value.getbyte(-1) }
|
|
309
|
+
end
|
|
310
|
+
raise ResponseError, 'Duplicate HSM Auth labels' unless result.map { |entry| entry[:label] }.uniq.length == result.length
|
|
311
|
+
|
|
312
|
+
result
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
private_class_method def self.put(opts = {})
|
|
316
|
+
label = label(value: opts[:label])
|
|
317
|
+
touch = opts.fetch(:touch, false)
|
|
318
|
+
raise ArgumentError, 'touch must be a boolean' unless [true, false].include?(touch)
|
|
319
|
+
raise ArgumentError, 'generate conflicts with explicit key material' if opts[:generate] && (opts[:key_enc] || opts[:key_mac])
|
|
320
|
+
|
|
321
|
+
Protocol.require_confirm(opts)
|
|
322
|
+
asymmetric = ['credentials generate', 'credentials import'].include?(opts[:operation])
|
|
323
|
+
if asymmetric
|
|
324
|
+
scalar = opts[:operation] == 'credentials generate' ? ''.b : private_scalar(opts)
|
|
325
|
+
elsif opts[:operation] == 'credentials derive'
|
|
326
|
+
value = Protocol.secret(value: opts[:derivation_password], secret_provider: opts[:secret_provider], name: :derivation_password)
|
|
327
|
+
key = OpenSSL::PKCS5.pbkdf2_hmac(value, 'Yubico', 10_000, 32, 'SHA256')
|
|
328
|
+
key_enc = key.byteslice(0, 16)
|
|
329
|
+
key_mac = key.byteslice(16, 16)
|
|
330
|
+
elsif opts[:generate]
|
|
331
|
+
key_enc = SecureRandom.random_bytes(16)
|
|
332
|
+
key_mac = SecureRandom.random_bytes(16)
|
|
333
|
+
else
|
|
334
|
+
key_enc = bytes(value: Protocol.secret(value: opts[:key_enc], secret_provider: opts[:secret_provider], name: :key_enc), size: 16)
|
|
335
|
+
key_mac = bytes(value: Protocol.secret(value: opts[:key_mac], secret_provider: opts[:secret_provider], name: :key_mac), size: 16)
|
|
336
|
+
end
|
|
337
|
+
password = password(opts.merge(name: :credential_password))
|
|
338
|
+
management = management_key(opts)
|
|
339
|
+
session = session(opts)
|
|
340
|
+
require_version(session: session, minimum: [5, 6, 0]) if asymmetric
|
|
341
|
+
raise ArgumentError, 'HSM Auth label already exists; delete explicitly before replacing it' if list(session: session).any? { |entry| entry[:label] == label }
|
|
342
|
+
|
|
343
|
+
algorithm = asymmetric ? :asymmetric : :symmetric
|
|
344
|
+
payload = tlv(tag: 0x7b, value: management) + tlv(tag: 0x71, value: label) + tlv(tag: 0x74, value: [ALGORITHMS.fetch(algorithm)].pack('C'))
|
|
345
|
+
payload += asymmetric ? tlv(tag: 0x7d, value: scalar) : tlv(tag: 0x75, value: key_enc) + tlv(tag: 0x76, value: key_mac)
|
|
346
|
+
payload += tlv(tag: 0x73, value: password) + tlv(tag: 0x7a, value: [touch ? 1 : 0].pack('C'))
|
|
347
|
+
send_apdu(session: session, ins: 1, data: payload)
|
|
348
|
+
result = list(session: session).find { |entry| entry[:label] == label }
|
|
349
|
+
raise ResponseError, 'HSM Auth import readback did not match metadata' unless result && result[:algorithm] == algorithm && result[:touch] == touch
|
|
350
|
+
|
|
351
|
+
result = result.merge(verified: true, verification: :metadata_only)
|
|
352
|
+
result[:generated_keys] = { key_enc: key_enc, key_mac: key_mac } if opts[:generate]
|
|
353
|
+
result
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
private_class_method def self.delete(opts = {})
|
|
357
|
+
label = label(value: opts[:label])
|
|
358
|
+
Protocol.require_confirm(opts)
|
|
359
|
+
management = management_key(opts)
|
|
360
|
+
session = session(opts)
|
|
361
|
+
raise ResponseError, 'No exact HSM Auth credential label' unless list(session: session).any? { |entry| entry[:label] == label }
|
|
362
|
+
|
|
363
|
+
send_apdu(session: session, ins: 2, data: tlv(tag: 0x7b, value: management) + tlv(tag: 0x71, value: label))
|
|
364
|
+
raise ResponseError, 'HSM Auth delete readback failed' if list(session: session).any? { |entry| entry[:label] == label }
|
|
365
|
+
|
|
366
|
+
{ deleted: label, verified: true }
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
public_class_method def self.authors
|
|
370
|
+
"AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n"
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
public_class_method def self.help
|
|
374
|
+
puts <<~HELP
|
|
375
|
+
Execute native credential management without a subprocess.
|
|
376
|
+
#{self}.execute(
|
|
377
|
+
args: 'optional - operation argv after hsmauth; empty returns local metadata',
|
|
378
|
+
connection: 'required - selected APDU connection except local help',
|
|
379
|
+
session: 'optional - existing open_session handle instead of reselecting connection',
|
|
380
|
+
confirm: 'required - literal true for mutations and authentication',
|
|
381
|
+
credential_password: 'optional - current in-memory password up to sixteen bytes; explicit empty is allowed',
|
|
382
|
+
new_credential_password: 'optional - new in-memory credential password up to sixteen bytes',
|
|
383
|
+
management_key: 'optional - exactly sixteen raw authorization key bytes, never guessed',
|
|
384
|
+
management_password: 'optional - at most sixteen password bytes instead of management_key',
|
|
385
|
+
new_management_key: 'optional - exactly sixteen raw bytes for change-management-key',
|
|
386
|
+
new_management_password: 'optional - at most sixteen password bytes for change-management-password',
|
|
387
|
+
key_enc: 'optional - sixteen raw AES encryption key bytes for symmetric import',
|
|
388
|
+
key_mac: 'optional - sixteen raw AES authentication key bytes for symmetric import',
|
|
389
|
+
derivation_password: 'optional - in-memory password for PBKDF2 symmetric derivation',
|
|
390
|
+
private_key: 'optional - raw thirty-two byte P-256 scalar, PEM/DER String or private OpenSSL EC object',
|
|
391
|
+
key_password: 'optional - in-memory PEM/DER decryption password; never retried',
|
|
392
|
+
auth_method: 'optional - credential or management Symbol for password-change authorization',
|
|
393
|
+
generate: 'optional - boolean symmetric key generation or pre-5.8 management-key generation',
|
|
394
|
+
touch: 'optional - boolean touch requirement on credential creation',
|
|
395
|
+
format: 'optional - RAW point, PEM or DER for public-key export, returned in memory',
|
|
396
|
+
secret_provider: 'optional - callable receiving a missing secret field Symbol after consent',
|
|
397
|
+
timeout: 'optional - finite positive I/O timeout seconds'
|
|
398
|
+
)
|
|
399
|
+
Enumerate implemented operation leaves only.
|
|
400
|
+
#{self}.operations
|
|
401
|
+
Select the application once for a multi-command cryptographic session.
|
|
402
|
+
#{self}.open_session(
|
|
403
|
+
connection: 'required - caller-owned raw APDU transport handle',
|
|
404
|
+
timeout: 'optional - finite positive I/O timeout seconds'
|
|
405
|
+
)
|
|
406
|
+
Obtain a host challenge without losing ephemeral session state.
|
|
407
|
+
#{self}.challenge(
|
|
408
|
+
session: 'required - open_session handle shared with the following calculate call',
|
|
409
|
+
label: 'required - full exact credential label bytes',
|
|
410
|
+
algorithm: 'optional - symmetric or asymmetric Symbol, default symmetric',
|
|
411
|
+
credential_password: 'optional - in-memory credential password; required for asymmetric mode',
|
|
412
|
+
confirm: 'required - literal true for challenge generation or authentication'
|
|
413
|
+
)
|
|
414
|
+
Calculate three in-memory session keys using one credential.
|
|
415
|
+
#{self}.calculate(
|
|
416
|
+
connection: 'optional - raw APDU handle when no existing session is supplied',
|
|
417
|
+
session: 'optional - shared open_session handle retaining an earlier challenge',
|
|
418
|
+
label: 'required - full exact credential label bytes',
|
|
419
|
+
algorithm: 'optional - symmetric or asymmetric Symbol, default symmetric',
|
|
420
|
+
context: 'required - sixteen symmetric challenge bytes or two uncompressed P-256 points (130 bytes)',
|
|
421
|
+
public_key: 'optional - required asymmetric HSM public key, raw 65-byte point or OpenSSL EC object',
|
|
422
|
+
card_crypto: 'optional - eight-byte symmetric or required sixteen-byte asymmetric card cryptogram',
|
|
423
|
+
credential_password: 'required - in-memory password, at most sixteen raw bytes',
|
|
424
|
+
confirm: 'required - literal true for one authentication attempt',
|
|
425
|
+
timeout: 'optional - finite positive I/O timeout seconds'
|
|
426
|
+
)
|
|
427
|
+
Import/export use data in memory, not filenames. No credential or default key is guessed.
|
|
428
|
+
Reuse the same session between challenge and calculate; do not reselect another application.
|
|
429
|
+
Creation/deletion read back metadata. Password/key changes return acknowledged/not_readable;
|
|
430
|
+
they do not perform a second authentication attempt to pretend secret contents were read back.
|
|
431
|
+
Asymmetric operations require 5.6; credential-password changes require 5.8.
|
|
432
|
+
Display module author information.
|
|
433
|
+
#{self}.authors
|
|
434
|
+
HELP
|
|
435
|
+
end
|
|
436
|
+
end
|
|
437
|
+
end
|
|
438
|
+
end
|
|
439
|
+
end
|