From 461ef65ab61a7b73c51228ae4196446e7a73c69d Mon Sep 17 00:00:00 2001 From: Denis Semenenko Date: Sun, 18 Jul 2021 00:59:23 +0200 Subject: [PATCH] Initialize --- .gitignore | 2 + .rubocop.yml | 2 + .ruby-version | 1 + Gemfile | 13 + Gemfile.lock | 51 ++++ LICENSE | 21 ++ README.md | 49 ++++ bin/icsp | 55 +++++ config/config.rb | 48 ++++ config/environment.rb | 11 + src/commands/base_command.rb | 19 ++ src/commands/certificate/delete.rb | 25 ++ src/commands/certificate/install.rb | 48 ++++ src/commands/certificate/list.rb | 71 ++++++ src/commands/certificate/view.rb | 16 ++ src/commands/container/change_pin_code.rb | 17 ++ src/commands/container/check.rb | 24 ++ src/commands/container/copy.rb | 17 ++ src/commands/container/delete.rb | 17 ++ .../container/forget_all_pin_codes.rb | 17 ++ src/commands/container/list.rb | 28 +++ src/commands/container/view.rb | 17 ++ src/commands/create_hash.rb | 19 ++ src/commands/create_signature.rb | 26 ++ src/commands/decrypt_file.rb | 22 ++ src/commands/encrypt_file.rb | 22 ++ src/commands/hardware/list.rb | 21 ++ src/commands/license/set.rb | 20 ++ src/commands/license/view.rb | 20 ++ src/commands/verify_hash.rb | 19 ++ src/commands/verify_signature.rb | 26 ++ src/csp_option_parser.rb | 225 ++++++++++++++++++ src/shell.rb | 46 ++++ src/shell_result.rb | 20 ++ 34 files changed, 1055 insertions(+) create mode 100644 .gitignore create mode 100644 .rubocop.yml create mode 100644 .ruby-version create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 LICENSE create mode 100644 README.md create mode 100755 bin/icsp create mode 100644 config/config.rb create mode 100644 config/environment.rb create mode 100644 src/commands/base_command.rb create mode 100644 src/commands/certificate/delete.rb create mode 100644 src/commands/certificate/install.rb create mode 100644 src/commands/certificate/list.rb create mode 100644 src/commands/certificate/view.rb create mode 100644 src/commands/container/change_pin_code.rb create mode 100644 src/commands/container/check.rb create mode 100644 src/commands/container/copy.rb create mode 100644 src/commands/container/delete.rb create mode 100644 src/commands/container/forget_all_pin_codes.rb create mode 100644 src/commands/container/list.rb create mode 100644 src/commands/container/view.rb create mode 100644 src/commands/create_hash.rb create mode 100644 src/commands/create_signature.rb create mode 100644 src/commands/decrypt_file.rb create mode 100644 src/commands/encrypt_file.rb create mode 100644 src/commands/hardware/list.rb create mode 100644 src/commands/license/set.rb create mode 100644 src/commands/license/view.rb create mode 100644 src/commands/verify_hash.rb create mode 100644 src/commands/verify_signature.rb create mode 100644 src/csp_option_parser.rb create mode 100644 src/shell.rb create mode 100644 src/shell_result.rb diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3bf780b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea +.env \ No newline at end of file diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..c11ffec --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,2 @@ +Style/Documentation: + Enabled: false \ No newline at end of file diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..e2bdf6e --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +2.7.3 \ No newline at end of file diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..d48f5e0 --- /dev/null +++ b/Gemfile @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' + +ruby '2.7.3' + +gem 'os', '~> 1.1' + +gem 'tty-prompt', '~> 0.23.1' + +group :development, :test do + gem 'rubocop', '~> 1.18', require: false +end diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..ef9ac05 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,51 @@ +GEM + remote: https://rubygems.org/ + specs: + ast (2.4.2) + os (1.1.1) + parallel (1.20.1) + parser (3.0.2.0) + ast (~> 2.4.1) + pastel (0.8.0) + tty-color (~> 0.5) + rainbow (3.0.0) + regexp_parser (2.1.1) + rexml (3.2.5) + rubocop (1.18.3) + parallel (~> 1.10) + parser (>= 3.0.0.0) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 1.8, < 3.0) + rexml + rubocop-ast (>= 1.7.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 1.4.0, < 3.0) + rubocop-ast (1.8.0) + parser (>= 3.0.1.1) + ruby-progressbar (1.11.0) + tty-color (0.6.0) + tty-cursor (0.7.1) + tty-prompt (0.23.1) + pastel (~> 0.8) + tty-reader (~> 0.8) + tty-reader (0.9.0) + tty-cursor (~> 0.7) + tty-screen (~> 0.8) + wisper (~> 2.0) + tty-screen (0.8.1) + unicode-display_width (2.0.0) + wisper (2.0.1) + +PLATFORMS + ruby + +DEPENDENCIES + os (~> 1.1) + rubocop (~> 1.18) + tty-prompt (~> 0.23.1) + +RUBY VERSION + ruby 2.7.3p183 + +BUNDLED WITH + 2.1.4 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..bd56e25 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Denis Semenenko + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..4f6714d --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ +# iCSP + +An interactive CryptoPro CSP shell that tries to imitate its GUI counterpart on the Windows platform. +Built for macOS and Linux daily use by wrapping a set of CryptoPro CLI tools: cryptcp, certmgr, csptest, cpconfig, etc. + +The project goal is only to simplify my daily life with CryptoPro CSP on *nix systems. +Not everything is polished. Feel free to submit a PR if you need to add extra commands/options or fix bugs. + +See: +* https://www.cryptopro.ru/products/other/cryptcp +* https://www.cryptopro.ru/sites/default/files/products/cryptcp/cryptcp_5.0.x.pdf +* https://www.cryptopro.ru/sites/default/files/docs/certmgr.pdf + +## Usage + +Management commands: + +* certificate — Manage certificates +* container — Manage containers +* hardware — Manage hardware +* license — Manage licenses + +CSP commands: + +* create_signature +* verify_signature +* create_hash +* verify_hash +* encrypt_file +* decrypt_file + + +## Examples + +List all key containers + +```bash +./bin/icsp container list +``` + +Create a signature for the file + +```bash +./bin/icsp create_signature document.txt +``` + +## License + +[MIT](./LICENSE) \ No newline at end of file diff --git a/bin/icsp b/bin/icsp new file mode 100755 index 0000000..725a61f --- /dev/null +++ b/bin/icsp @@ -0,0 +1,55 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require 'logger' +require 'optionparser' +require 'ostruct' +require 'yaml' + +require 'os' +require 'tty-prompt' + +require_relative '../config/config' +require_relative '../config/environment' +require_relative '../src/csp_option_parser' +require_relative '../src/commands/base_command' +require_relative '../src/shell' +require_relative '../src/shell_result' + +require_relative '../src/commands/certificate/delete' +require_relative '../src/commands/certificate/install' +require_relative '../src/commands/certificate/list' +require_relative '../src/commands/certificate/view' +require_relative '../src/commands/container/check' +require_relative '../src/commands/container/list' +require_relative '../src/commands/hardware/list' +require_relative '../src/commands/license/view' +require_relative '../src/commands/license/set' +require_relative '../src/commands/create_hash' +require_relative '../src/commands/verify_hash' +require_relative '../src/commands/encrypt_file' +require_relative '../src/commands/decrypt_file' +require_relative '../src/commands/create_signature' +require_relative '../src/commands/verify_signature' + +# CryptoPro CSP +module CSP + def self.to_camel_case(string) + string.to_s.split('_').map(&:capitalize).join + end + + def self.run + parsed = ::CSP::Services::OptionParserService.new.parse + command_parts = %w[::CSP Commands] + command_parts << to_camel_case(parsed.command) if parsed.command + command_parts << to_camel_case(parsed.subcommand) if parsed.subcommand + command = Object.const_get(command_parts.join('::')) + .new(config: config, options: parsed.options, arguments: parsed.arguments) + command.respond_to?(:print) ? command.print : command.execute + rescue StandardError => e + puts 'Application aborted with error:' + raise e + end + + run +end diff --git a/config/config.rb b/config/config.rb new file mode 100644 index 0000000..b1cc43f --- /dev/null +++ b/config/config.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +class Config + def initialize + @bin = %i[certmgr cryptcp csptest].freeze + @sbin = [:cpconfig].freeze + end + + def csp_path + @csp_path ||= ENV.fetch('CSP_PATH', '/opt/cprocsp') + end + + def build_path(binary_name) + File.join(csp_path, category_path(binary_name), arch_path, binary_name.to_s) + end + + def category_path(binary_name) + @sbin.include?(binary_name) ? 'sbin' : 'bin' + end + + def arch_path + return '' if OS.mac? + + OS.bits == 64 ? 'amd64' : 'ia32' + end + + def log_level + @log_level ||= ENV.fetch('LOG_LEVEL', 'warn').upcase + end + + private + + def method_missing(symbol, *args) + if @bin.include?(symbol) || @sbin.include?(symbol) + build_path(symbol) + else + super + end + end + + def respond_to_missing?(symbol, *args) + if @bin.include?(symbol) || @sbin.include?(symbol) + true + else + super + end + end +end diff --git a/config/environment.rb b/config/environment.rb new file mode 100644 index 0000000..cb02c18 --- /dev/null +++ b/config/environment.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module CSP + def self.config + @config ||= Config.new + end + + def self.logger + @logger ||= Logger.new($stdout, level: Object.const_get("Logger::Severity::#{config.log_level}")) + end +end diff --git a/src/commands/base_command.rb b/src/commands/base_command.rb new file mode 100644 index 0000000..9bf5587 --- /dev/null +++ b/src/commands/base_command.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module CSP + module Commands + class BaseCommand + def initialize(config:, options:, arguments:) + @config = config + @options = options + @arguments = arguments + @prompt = TTY::Prompt.new + ::CSP.logger.debug('Command initialized with:') + ::CSP.logger.debug("arguments: #{@arguments.inspect}") + ::CSP.logger.debug("options: #{@options.inspect}") + end + + attr_reader :config, :options, :arguments, :prompt + end + end +end diff --git a/src/commands/certificate/delete.rb b/src/commands/certificate/delete.rb new file mode 100644 index 0000000..5e7e8fd --- /dev/null +++ b/src/commands/certificate/delete.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module CSP + module Commands + module Certificate + class Delete < ::CSP::Commands::BaseCommand + def certmgr + @certmgr ||= @config.certmgr + end + + def execute + store = prompt.select('Select store:', available_stores) + result = ::CSP::Shell.new("#{certmgr} -delete -store #{store}", fork: false).execute + exit(result.exit_code) unless result.ok + + puts result + end + + def available_stores + %w[uMy root ca] + end + end + end + end +end diff --git a/src/commands/certificate/install.rb b/src/commands/certificate/install.rb new file mode 100644 index 0000000..0bd27d7 --- /dev/null +++ b/src/commands/certificate/install.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module CSP + module Commands + module Certificate + class Install < ::CSP::Commands::BaseCommand + def certmgr + @certmgr ||= @config.certmgr + end + + def execute + unless options[:file] + install_from_container + return + end + + install_from_file + end + + def install_from_container + result = ::CSP::Shell.new("#{certmgr} -inst -cont '#{selected_container}'", convert_to_utf8: false).execute + exit(result.exit_code) unless result.ok + + puts result + end + + def install_from_file + store = prompt.select('Select store:', available_stores) + not_crl = prompt.no?('Is it CRL?') + result = ::CSP::Shell.new( + "#{certmgr} -inst -file '#{options[:file]}' -store #{store} #{not_crl ? '' : '-crl'}", convert_to_utf8: false + ).execute + exit(result.exit_code) unless result.ok + + puts result + end + + def selected_container + ::CSP::Commands::Container::List.new(config: config, options: options, arguments: arguments).select + end + + def available_stores + %w[uMy root ca] + end + end + end + end +end diff --git a/src/commands/certificate/list.rb b/src/commands/certificate/list.rb new file mode 100644 index 0000000..c41d11e --- /dev/null +++ b/src/commands/certificate/list.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +module CSP + module Commands + module Certificate + class List < ::CSP::Commands::BaseCommand + def certmgr + @certmgr ||= @config.certmgr + end + + def execute + store = prompt.select('Select store:', available_stores) + result = ::CSP::Shell.new("#{certmgr} -list -store #{store}", convert_to_utf8: false).execute + exit(result.exit_code) unless result.ok + + result + end + + def select + list_divider = '=============================================================================' + i = 1 + certificate_hash = {} + certificates = [] + divider_count = 0 + execute.output_lines.each do |line| + index_line = "#{i}-------" + next_index_line = "#{i + 1}-------" + if line == index_line || next_index_line || line == list_divider + if line == "#{i}-------" + certificate_hash = {} + certificate_hash['index'] = i + next + end + + if line == "#{i + 1}-------" + certificates << OpenStruct.new(certificate_hash.dup) + i += 1 + certificate_hash = {} + certificate_hash['index'] = i + end + + if line == list_divider + divider_count += 1 + break if divider_count == 2 + end + end + + next unless certificate_hash['index'] + + attribute = line.split(' : ').map(&:strip) + key = attribute.first + value = attribute[1..].join(' : ') + + certificate_hash[key] = value if key + end + + choices = certificates.map { |certificate| [certificate['Subject'], certificate['SHA1 Hash']] }.to_h + @prompt.select('Choose certificate:', choices) + end + + def print + puts execute + end + + def available_stores + %w[uMy root ca] + end + end + end + end +end diff --git a/src/commands/certificate/view.rb b/src/commands/certificate/view.rb new file mode 100644 index 0000000..1c0598d --- /dev/null +++ b/src/commands/certificate/view.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module CSP + module Commands + module Certificate + class View < ::CSP::Commands::BaseCommand + def execute + result = ::CSP::Shell.new("openssl x509 -in #{arguments.first} -text -noout -nameopt multiline,-esc_msb,utf8").execute + exit(result.exit_code) unless result.ok + + puts result + end + end + end + end +end diff --git a/src/commands/container/change_pin_code.rb b/src/commands/container/change_pin_code.rb new file mode 100644 index 0000000..5816355 --- /dev/null +++ b/src/commands/container/change_pin_code.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module CSP + module Commands + module Container + class ChangePinCode < ::CSP::Commands::BaseCommand + def csptest + @csptest ||= @config.csptest + end + + def execute + raise 'Not implemented' + end + end + end + end +end diff --git a/src/commands/container/check.rb b/src/commands/container/check.rb new file mode 100644 index 0000000..c5ee223 --- /dev/null +++ b/src/commands/container/check.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module CSP + module Commands + module Container + class Check < ::CSP::Commands::BaseCommand + def csptest + @csptest ||= @config.csptest + end + + def execute + result = ::CSP::Shell.new("#{csptest} -keyset -check -cont '#{selected_container}'").execute + exit(result.exit_code) unless result.ok + + puts result + end + + def selected_container + ::CSP::Commands::Container::List.new(config: config, options: options, arguments: arguments).select + end + end + end + end +end diff --git a/src/commands/container/copy.rb b/src/commands/container/copy.rb new file mode 100644 index 0000000..8ee5d35 --- /dev/null +++ b/src/commands/container/copy.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module CSP + module Commands + module Container + class Copy < ::CSP::Commands::BaseCommand + def csptest + @csptest ||= @config.csptest + end + + def execute + raise 'Not implemented' + end + end + end + end +end diff --git a/src/commands/container/delete.rb b/src/commands/container/delete.rb new file mode 100644 index 0000000..b8e77d6 --- /dev/null +++ b/src/commands/container/delete.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module CSP + module Commands + module Container + class Delete < ::CSP::Commands::BaseCommand + def csptest + @csptest ||= @config.csptest + end + + def execute + raise 'Not implemented' + end + end + end + end +end diff --git a/src/commands/container/forget_all_pin_codes.rb b/src/commands/container/forget_all_pin_codes.rb new file mode 100644 index 0000000..eb9ac8c --- /dev/null +++ b/src/commands/container/forget_all_pin_codes.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module CSP + module Commands + module Container + class ForgetAllPinCodes < ::CSP::Commands::BaseCommand + def csptest + @csptest ||= @config.csptest + end + + def execute + raise 'Not implemented' + end + end + end + end +end diff --git a/src/commands/container/list.rb b/src/commands/container/list.rb new file mode 100644 index 0000000..f9121b2 --- /dev/null +++ b/src/commands/container/list.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module CSP + module Commands + module Container + class List < ::CSP::Commands::BaseCommand + def csptest + @csptest ||= @config.csptest + end + + def execute + result = ::CSP::Shell.new("#{csptest} -keyset -enum_containers -verifycontext -fqcn").execute + exit(result.exit_code) unless result.ok + + result.output_lines.filter { |l| l.start_with?('\\') } + end + + def print + puts execute + end + + def select + @prompt.select('Choose container:', execute) + end + end + end + end +end diff --git a/src/commands/container/view.rb b/src/commands/container/view.rb new file mode 100644 index 0000000..d1d3b5e --- /dev/null +++ b/src/commands/container/view.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module CSP + module Commands + module Container + class View < ::CSP::Commands::BaseCommand + def csptest + @csptest ||= @config.csptest + end + + def execute + raise 'Not implemented' + end + end + end + end +end diff --git a/src/commands/create_hash.rb b/src/commands/create_hash.rb new file mode 100644 index 0000000..7d4b292 --- /dev/null +++ b/src/commands/create_hash.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module CSP + module Commands + class CreateHash < ::CSP::Commands::BaseCommand + def cryptcp + @cryptcp ||= @config.cryptcp + end + + def execute + input_file = arguments.first + result = ::CSP::Shell.new("#{cryptcp} -hash -hex #{input_file}", convert_to_utf8: false).execute + exit(result.exit_code) unless result.ok + + puts result + end + end + end +end diff --git a/src/commands/create_signature.rb b/src/commands/create_signature.rb new file mode 100644 index 0000000..51d0c78 --- /dev/null +++ b/src/commands/create_signature.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module CSP + module Commands + class CreateSignature < ::CSP::Commands::BaseCommand + def cryptcp + @cryptcp ||= @config.cryptcp + end + + def execute + input_file = arguments.first + thumbprint = selected_certificate + + result = ::CSP::Shell.new("#{cryptcp} -signf -thumbprint '#{thumbprint}' #{input_file}", + convert_to_utf8: false, fork: false).execute + exit(result.exit_code) unless result.ok + + puts result + end + + def selected_certificate + ::CSP::Commands::Certificate::List.new(config: config, options: options, arguments: arguments).select + end + end + end +end diff --git a/src/commands/decrypt_file.rb b/src/commands/decrypt_file.rb new file mode 100644 index 0000000..b9601c5 --- /dev/null +++ b/src/commands/decrypt_file.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module CSP + module Commands + class DecryptFile < ::CSP::Commands::BaseCommand + def cryptcp + @cryptcp ||= @config.cryptcp + end + + def execute + input_file = arguments.first + output_file = arguments.last + result = ::CSP::Shell.new( + "#{cryptcp} -decr -f '#{options[:certificate_file]}' -start #{input_file} #{output_file}", convert_to_utf8: false + ).execute + exit(result.exit_code) unless result.ok + + puts result + end + end + end +end diff --git a/src/commands/encrypt_file.rb b/src/commands/encrypt_file.rb new file mode 100644 index 0000000..481df13 --- /dev/null +++ b/src/commands/encrypt_file.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module CSP + module Commands + class EncryptFile < ::CSP::Commands::BaseCommand + def cryptcp + @cryptcp ||= @config.cryptcp + end + + def execute + input_file = arguments.first + output_file = arguments.last + result = ::CSP::Shell.new( + "#{cryptcp} -encr -thumbprint '#{options[:certificate_file]}' #{input_file} #{output_file}", convert_to_utf8: false + ).execute + exit(result.exit_code) unless result.ok + + puts result + end + end + end +end diff --git a/src/commands/hardware/list.rb b/src/commands/hardware/list.rb new file mode 100644 index 0000000..eaca1d8 --- /dev/null +++ b/src/commands/hardware/list.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module CSP + module Commands + module Hardware + class List < ::CSP::Commands::BaseCommand + def cpconfig + @cpconfig ||= @config.cpconfig + end + + def execute + type = options[:type] || 'reader' + result = ::CSP::Shell.new("#{cpconfig} -hardware #{type} -view").execute + exit(result.exit_code) unless result.ok + + puts result + end + end + end + end +end diff --git a/src/commands/license/set.rb b/src/commands/license/set.rb new file mode 100644 index 0000000..47179d1 --- /dev/null +++ b/src/commands/license/set.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module CSP + module Commands + module License + class Set < ::CSP::Commands::BaseCommand + def cpconfig + @cpconfig ||= @config.cpconfig + end + + def execute + result = ::CSP::Shell.new("#{cpconfig} -license -set #{arguments.first}").execute + exit(result.exit_code) unless result.ok + + puts result + end + end + end + end +end diff --git a/src/commands/license/view.rb b/src/commands/license/view.rb new file mode 100644 index 0000000..610deba --- /dev/null +++ b/src/commands/license/view.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module CSP + module Commands + module License + class View < ::CSP::Commands::BaseCommand + def cpconfig + @cpconfig ||= @config.cpconfig + end + + def execute + result = ::CSP::Shell.new("#{cpconfig} -license -view").execute + exit(result.exit_code) unless result.ok + + puts result + end + end + end + end +end diff --git a/src/commands/verify_hash.rb b/src/commands/verify_hash.rb new file mode 100644 index 0000000..d26e748 --- /dev/null +++ b/src/commands/verify_hash.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module CSP + module Commands + class VerifyHash < ::CSP::Commands::BaseCommand + def cryptcp + @cryptcp ||= @config.cryptcp + end + + def execute + input_file = arguments.first + result = ::CSP::Shell.new("#{cryptcp} -vhash -hex #{input_file}", convert_to_utf8: false).execute + exit(result.exit_code) unless result.ok + + puts result + end + end + end +end diff --git a/src/commands/verify_signature.rb b/src/commands/verify_signature.rb new file mode 100644 index 0000000..9f818a1 --- /dev/null +++ b/src/commands/verify_signature.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module CSP + module Commands + class VerifySignature < ::CSP::Commands::BaseCommand + def cryptcp + @cryptcp ||= @config.cryptcp + end + + def execute + input_file = arguments.first + thumbprint = selected_certificate + + result = ::CSP::Shell.new("#{cryptcp} -vsignf -thumbprint '#{thumbprint}' #{input_file}", + convert_to_utf8: false, fork: false).execute + exit(result.exit_code) unless result.ok + + puts result + end + + def selected_certificate + ::CSP::Commands::Certificate::List.new(config: config, options: options, arguments: arguments).select + end + end + end +end diff --git a/src/csp_option_parser.rb b/src/csp_option_parser.rb new file mode 100644 index 0000000..bdb8100 --- /dev/null +++ b/src/csp_option_parser.rb @@ -0,0 +1,225 @@ +# frozen_string_literal: true + +module CSP + module Services + # OptionParserService + class OptionParserService + APP_HELP = <<~HELP + Available commands are: + certificate Manage certificates + container Manage containers + hardware Manage hardware + license Manage license + ----- + sign_file#{' '} + verify_signature + encrypt_file#{' '} + decrypt_file + #{' '} + #{' '} + See 'csp COMMAND --help' for more information on a specific command. + For full documentation, see: https://github.com/denblackstache/cryptopro-cli#readme + HELP + + def parse + command = parse_command + unless command + puts 'No command passed' + exit + end + + subcommand_result = parse_subcommand(command) + OpenStruct.new( + command: command, + subcommand: subcommand_result[:subcommand], + arguments: subcommand_result[:arguments], + options: subcommand_result[:options] + ) + end + + private + + def parse_command + global = OptionParser.new do |opts| + opts.banner = 'Usage: csp [command] [subcommand] [options]' + opts.separator('') + opts.separator(APP_HELP) + end + global.order! + ARGV.shift.to_sym unless ARGV.empty? + end + + def parse_subcommand(command) + options = {} + subcommands = { + encrypt_file: encrypt_file_option_parser(options), + decrypt_file: decrypt_file_option_parser(options), + create_hash: create_hash_option_parser(options), + verify_hash: verify_hash_option_parser(options), + create_signature: create_signature_option_parser(options), + verify_signature: verify_signature_option_parser(options), + certificate: { + delete: certificate_delete_option_parser(options), + install: certificate_install_option_parser(options), + list: certificate_list_option_parser(options), + view: certificate_list_option_parser(options) + }, + container: { + check: container_check_option_parser(options), + list: container_list_option_parser(options) + }, + hardware: { + list: hardware_list_option_parser(options) + }, + license: { + view: license_view_option_parser(options), + set: license_set_option_parser(options) + } + } + + is_management_command = subcommands[command].is_a?(Hash) + subcommand = ARGV.shift.to_sym if is_management_command && !ARGV.empty? + + subcommand_option_parser = is_management_command ? subcommands[command][subcommand] : subcommands[command] + subcommand_option_parser.parse! + + { subcommand: subcommand, arguments: ARGV.dup, options: options } + end + + def encrypt_file_option_parser(options) + OptionParser.new do |opts| + opts.banner = 'Usage: csp encrypt_file [options]' + opts.on('-f certificate_file', '--file certificate_file', String, + 'certificate file path') do |certificate_file| + options[:certificate_file] = certificate_file + end + opts.separator('') + opts.separator('description') + end + end + + def decrypt_file_option_parser(options) + OptionParser.new do |opts| + opts.banner = 'Usage: csp decrypt_file [options]' + opts.on('-f certificate_file', '--file certificate_file', String, + 'certificate file path') do |certificate_file| + options[:certificate_file] = certificate_file + end + opts.separator('') + opts.separator('description') + end + end + + def create_hash_option_parser(_options) + OptionParser.new do |opts| + opts.banner = 'Usage: csp create_hash [options]' + opts.separator('') + opts.separator('description') + end + end + + def verify_hash_option_parser(_options) + OptionParser.new do |opts| + opts.banner = 'Usage: csp verify_hash [options]' + opts.separator('') + opts.separator('description') + end + end + + def create_signature_option_parser(_options) + OptionParser.new do |opts| + opts.banner = 'Usage: csp create_signature [options]' + opts.separator('') + opts.separator('description') + end + end + + def verify_signature_option_parser(_options) + OptionParser.new do |opts| + opts.banner = 'Usage: csp verify_signature [options]' + opts.separator('') + opts.separator('description') + end + end + + def certificate_delete_option_parser(_options) + OptionParser.new do |opts| + opts.banner = 'Usage: csp certificate delete [options]' + opts.separator('') + opts.separator('description') + end + end + + def certificate_install_option_parser(options) + OptionParser.new do |opts| + opts.banner = 'Usage: csp certificate install [options]' + opts.on('-f file', '--file file', String, 'file path') do |file| + options[:file] = file + end + opts.separator('') + opts.separator('description') + end + end + + def certificate_list_option_parser(_options) + OptionParser.new do |opts| + opts.banner = 'Usage: csp certificate list [options]' + opts.separator('') + opts.separator('description') + end + end + + def certificate_view_option_parser(_options) + OptionParser.new do |opts| + opts.banner = 'Usage: csp certificate view [options]' + opts.separator('') + opts.separator('description') + end + end + + def container_check_option_parser(_options) + OptionParser.new do |opts| + opts.banner = 'Usage: csp container check [options]' + opts.separator('') + opts.separator('description') + end + end + + def container_list_option_parser(_options) + OptionParser.new do |opts| + opts.banner = 'Usage: csp container list [options]' + opts.separator('') + opts.separator('description') + end + end + + def hardware_list_option_parser(options) + OptionParser.new do |opts| + opts.banner = 'Usage: csp hardware list [options]' + opts.on('-t hardware_type', '--type hardware_type', String, + 'hardware_type - reader (default) | rndm | media') do |hardware_type| + options[:type] = hardware_type + end + opts.separator('') + opts.separator('Review of the installed readers, generators of random numbers and media') + end + end + + def license_view_option_parser(_options) + OptionParser.new do |opts| + opts.banner = 'Usage: csp license view' + opts.separator('') + opts.separator('description') + end + end + + def license_set_option_parser(_options) + OptionParser.new do |opts| + opts.banner = 'Usage: csp license set' + opts.separator('') + opts.separator('description') + end + end + end + end +end diff --git a/src/shell.rb b/src/shell.rb new file mode 100644 index 0000000..0d06a30 --- /dev/null +++ b/src/shell.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module CSP + # Shell + class Shell + def initialize(command, split: true, convert_to_utf8: true, fork: true) + @command = command + @split = split + @convert_to_utf8 = convert_to_utf8 + @fork = fork + end + + def execute + ::CSP.logger.info("command: #{@command}") + unless @fork + exec(@command) + return + end + + raw_output = `#{@command}` + exec_success = $CHILD_STATUS.success? + exit_code = $CHILD_STATUS.exitstatus + + output = @convert_to_utf8 ? raw_output.force_encoding('cp1251').encode('utf-8', undef: :replace) : raw_output + csp_error_code = '' + output_lines = [] + + if @split + output_lines = output.split("\n") + csp_error_code_line = output_lines.find { |l| l.include?('ErrorCode') } + csp_error_code = /0x0*[1-9a-fA-F][0-9a-fA-F]*/.match(csp_error_code_line).to_s + end + + shell_result = ::CSP::ShellResult.new( + output: output, + output_lines: output_lines, + csp_error_code: csp_error_code, + ok: exec_success && csp_error_code == '', + exit_code: exit_code + ) + + ::CSP.logger.debug("ok: #{shell_result.ok}") + shell_result + end + end +end diff --git a/src/shell_result.rb b/src/shell_result.rb new file mode 100644 index 0000000..8a22630 --- /dev/null +++ b/src/shell_result.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module CSP + # ShellResult + class ShellResult + def initialize(ok:, exit_code:, output:, output_lines:, csp_error_code:) + @ok = ok + @exit_code = exit_code + @output = output + @output_lines = output_lines + @csp_error_code = csp_error_code + end + + attr_reader :ok, :exit_code, :output, :output_lines, :csp_error_code + + def to_s + output + end + end +end