secure_conf 1.0.0 → 2.1.0
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 +5 -5
- data/README.md +25 -4
- data/Rakefile +7 -3
- data/exe/secure_conf.rb +135 -0
- data/lib/secure_conf/config.rb +33 -13
- data/lib/secure_conf/encrypter.rb +14 -6
- data/lib/secure_conf/openssh.rb +302 -0
- data/lib/secure_conf/storage/yaml.rb +3 -6
- data/lib/secure_conf/version.rb +1 -1
- data/secure_conf.gemspec +3 -3
- metadata +17 -15
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 | 
            -
             | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 2 | 
            +
            SHA256:
         | 
| 3 | 
            +
              metadata.gz: abe2a25e6dbe9aac46965807a20fcdcf507db1635961c19f86c9f08b1c68c2e2
         | 
| 4 | 
            +
              data.tar.gz: fcbfd1b8c33ab8b0c3a0708a522b1c49ca4ee07009c9af2596ebefe8e58608ed
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: a9e1b112d53d48f6e2eaf445f1faea965e4df0d9b092c8022353bcd53b8b212d9df4fc2883ff496c385e5dcdc2296a9aa898b215e7a41cb982793c7d7131abe4
         | 
| 7 | 
            +
              data.tar.gz: 90e342f51a1698cad0b71e905f1a481b597fb25c5cdd9aeb281a8d7bee28c0d1fe930abc357ecb0194afbcae5f23366ae6ab986d5a5d1b4a3945b563bf8101f6
         | 
    
        data/README.md
    CHANGED
    
    | @@ -20,12 +20,33 @@ Or install it yourself as: | |
| 20 20 |  | 
| 21 21 | 
             
            ## Usage
         | 
| 22 22 |  | 
| 23 | 
            -
                path = File.expand_path(" | 
| 23 | 
            +
                path = File.expand_path("secure.yml", File.dirname(__FILE__))
         | 
| 24 24 | 
             
                config = SecureConf::Config.new(path)
         | 
| 25 | 
            -
                 | 
| 26 | 
            -
                config["enc: | 
| 25 | 
            +
                
         | 
| 26 | 
            +
                config["enc:id"] = "yoshida-eth0"
         | 
| 27 | 
            +
                config["enc:pass"] = "himitsu"
         | 
| 28 | 
            +
                config["last_access"] = Time.now.to_s
         | 
| 27 29 | 
             
                config.save
         | 
| 28 | 
            -
             | 
| 30 | 
            +
                
         | 
| 31 | 
            +
                p config["enc:id"]
         | 
| 32 | 
            +
                p config["enc:pass"]
         | 
| 33 | 
            +
                p config["last_access"]
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            ## Usage cli
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                % secure_conf.rb read enc:pass
         | 
| 38 | 
            +
                read
         | 
| 39 | 
            +
                  key: enc:pass
         | 
| 40 | 
            +
                  val: himitsu
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                % secure_conf.rb --help
         | 
| 43 | 
            +
                Usage: secure_conf.rb [options] method [arguments]...
         | 
| 44 | 
            +
                        --pkey privatekey_path       PrivateKey file path (default: ~/.ssh/id_rsa)
         | 
| 45 | 
            +
                        --storage storage_path       Storage file path (default: ./secure.yml)
         | 
| 46 | 
            +
                  methods usage:
         | 
| 47 | 
            +
                    secure_conf.rb [options] read key
         | 
| 48 | 
            +
                    secure_conf.rb [options] write key value
         | 
| 49 | 
            +
                    secure_conf.rb [options] delete key
         | 
| 29 50 |  | 
| 30 51 | 
             
            ## Development
         | 
| 31 52 |  | 
    
        data/Rakefile
    CHANGED
    
    | @@ -1,6 +1,10 @@ | |
| 1 1 | 
             
            require "bundler/gem_tasks"
         | 
| 2 | 
            -
            require " | 
| 2 | 
            +
            require "rake/testtask"
         | 
| 3 3 |  | 
| 4 | 
            -
             | 
| 4 | 
            +
            Rake::TestTask.new(:test) do |t|
         | 
| 5 | 
            +
              t.libs << "test"
         | 
| 6 | 
            +
              t.libs << "lib"
         | 
| 7 | 
            +
              t.test_files = FileList["test/**/*_test.rb"]
         | 
| 8 | 
            +
            end
         | 
| 5 9 |  | 
| 6 | 
            -
            task :default => : | 
| 10 | 
            +
            task :default => :test
         | 
    
        data/exe/secure_conf.rb
    ADDED
    
    | @@ -0,0 +1,135 @@ | |
| 1 | 
            +
            #!/bin/ruby
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            $LOAD_PATH << File.expand_path("../lib", File.dirname(__FILE__))
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            require 'secure_conf'
         | 
| 6 | 
            +
            require 'optparse'
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            class SecureConfCmd
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              def initialize(storage_path, privatekey_path=nil)
         | 
| 11 | 
            +
                @storage_path = File.expand_path(storage_path)
         | 
| 12 | 
            +
                @privatekey_path = File.expand_path(privatekey_path)
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              def config
         | 
| 16 | 
            +
                unless @config
         | 
| 17 | 
            +
                  #unless File.file?(@storage_path)
         | 
| 18 | 
            +
                  #  raise "storage_path is not exist: #{@storage_path}"
         | 
| 19 | 
            +
                  #end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  unless File.file?(@privatekey_path)
         | 
| 22 | 
            +
                    raise "privatekey_path is not exist: #{@privatekey_path}"
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  pkey = File.open(@privatekey_path, "r") {|f| f.read}
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  @config = SecureConf::Config.new(@storage_path, encrypter: SecureConf::Encrypter.new(pkey), auto_commit: true)
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
                @config
         | 
| 30 | 
            +
              end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
              def read(key)
         | 
| 33 | 
            +
                val = config[key]
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                puts "read"
         | 
| 36 | 
            +
                puts "  key: #{key}"
         | 
| 37 | 
            +
                puts "  val: #{val}"
         | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
              def write(key, value)
         | 
| 41 | 
            +
                puts "write"
         | 
| 42 | 
            +
                puts "  key: #{key}"
         | 
| 43 | 
            +
                puts "  val: #{value}"
         | 
| 44 | 
            +
                config[key] = value
         | 
| 45 | 
            +
                puts "write ok."
         | 
| 46 | 
            +
              end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
              def delete(key)
         | 
| 49 | 
            +
                puts "delete"
         | 
| 50 | 
            +
                puts "  key: #{key}"
         | 
| 51 | 
            +
                config.delete(key)
         | 
| 52 | 
            +
                puts "delete ok."
         | 
| 53 | 
            +
              end
         | 
| 54 | 
            +
            end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
             | 
| 57 | 
            +
            # default value
         | 
| 58 | 
            +
            privatekey_path = "~/.ssh/id_rsa"
         | 
| 59 | 
            +
            storage_path = "./secure.yml"
         | 
| 60 | 
            +
            method = nil
         | 
| 61 | 
            +
            key = nil
         | 
| 62 | 
            +
            value = nil
         | 
| 63 | 
            +
             | 
| 64 | 
            +
            # parser
         | 
| 65 | 
            +
            parser = OptionParser.new {|o|
         | 
| 66 | 
            +
              o.banner = "Usage: #{File.basename($0)} [options] method [arguments]..."
         | 
| 67 | 
            +
              o.on('--pkey privatekey_path', "PrivateKey file path (default: #{privatekey_path})") {|v| privatekey_path = v }
         | 
| 68 | 
            +
              o.on('--storage storage_path', "Storage file path (default: #{storage_path})") {|v| storage_path = v }
         | 
| 69 | 
            +
              o.on_tail(
         | 
| 70 | 
            +
                "  methods usage:",
         | 
| 71 | 
            +
                "    #{File.basename($0)} [options] read key",
         | 
| 72 | 
            +
                "    #{File.basename($0)} [options] write key value",
         | 
| 73 | 
            +
                "    #{File.basename($0)} [options] delete key",
         | 
| 74 | 
            +
              )
         | 
| 75 | 
            +
              o.version = SecureConf::VERSION
         | 
| 76 | 
            +
            }
         | 
| 77 | 
            +
             | 
| 78 | 
            +
            methods = {
         | 
| 79 | 
            +
              read: 1,
         | 
| 80 | 
            +
              write: 2,
         | 
| 81 | 
            +
              delete: 1,
         | 
| 82 | 
            +
            }
         | 
| 83 | 
            +
             | 
| 84 | 
            +
            # parse argv
         | 
| 85 | 
            +
            begin
         | 
| 86 | 
            +
              parser.parse!(ARGV)
         | 
| 87 | 
            +
             | 
| 88 | 
            +
              # method
         | 
| 89 | 
            +
              method = ARGV.shift
         | 
| 90 | 
            +
              unless method
         | 
| 91 | 
            +
                raise ""
         | 
| 92 | 
            +
              end
         | 
| 93 | 
            +
              method = method.to_sym
         | 
| 94 | 
            +
              unless methods.include?(method)
         | 
| 95 | 
            +
                raise "not found method: #{method}"
         | 
| 96 | 
            +
              end
         | 
| 97 | 
            +
             | 
| 98 | 
            +
              # method argc
         | 
| 99 | 
            +
              unless ARGV.length==methods[method]
         | 
| 100 | 
            +
                raise "wrong number of method arguments: method=#{method} given=#{ARGV.length} expected=#{methods[method]}"
         | 
| 101 | 
            +
              end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
              # key value
         | 
| 104 | 
            +
              key = ARGV[0]
         | 
| 105 | 
            +
              value = ARGV[1]
         | 
| 106 | 
            +
             | 
| 107 | 
            +
            rescue => e
         | 
| 108 | 
            +
              if 0<e.message.to_s.length
         | 
| 109 | 
            +
                puts e.message
         | 
| 110 | 
            +
                puts
         | 
| 111 | 
            +
              end
         | 
| 112 | 
            +
              puts parser.help
         | 
| 113 | 
            +
              exit(1)
         | 
| 114 | 
            +
            end
         | 
| 115 | 
            +
             | 
| 116 | 
            +
             | 
| 117 | 
            +
            # execute
         | 
| 118 | 
            +
            cmd = SecureConfCmd.new(storage_path, privatekey_path)
         | 
| 119 | 
            +
             | 
| 120 | 
            +
            begin
         | 
| 121 | 
            +
              case method
         | 
| 122 | 
            +
              when :read
         | 
| 123 | 
            +
                cmd.read(key)
         | 
| 124 | 
            +
              when :write
         | 
| 125 | 
            +
                cmd.write(key, value)
         | 
| 126 | 
            +
              when :delete
         | 
| 127 | 
            +
                cmd.delete(key)
         | 
| 128 | 
            +
              end
         | 
| 129 | 
            +
            rescue => e
         | 
| 130 | 
            +
              puts e
         | 
| 131 | 
            +
              puts
         | 
| 132 | 
            +
              puts parser.help
         | 
| 133 | 
            +
             | 
| 134 | 
            +
              #puts e.backtrace.join("\n")
         | 
| 135 | 
            +
            end
         | 
    
        data/lib/secure_conf/config.rb
    CHANGED
    
    | @@ -1,21 +1,33 @@ | |
| 1 | 
            +
            require 'delegate'
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module SecureConf
         | 
| 2 | 
            -
              class Config <  | 
| 4 | 
            +
              class Config < SimpleDelegator
         | 
| 3 5 | 
             
                attr_reader :path
         | 
| 4 | 
            -
                attr_reader : | 
| 6 | 
            +
                attr_reader :encrypter
         | 
| 5 7 | 
             
                attr_reader :serializer
         | 
| 6 8 | 
             
                attr_reader :storage
         | 
| 7 9 | 
             
                attr_accessor :auto_commit
         | 
| 8 10 |  | 
| 9 | 
            -
                def initialize(path,  | 
| 11 | 
            +
                def initialize(path, encrypter: nil, serializer: nil, storage: nil, auto_commit: false)
         | 
| 10 12 | 
             
                  @path = path
         | 
| 11 | 
            -
                  @ | 
| 13 | 
            +
                  @encrypter = encrypter || SecureConf.default
         | 
| 12 14 | 
             
                  @serializer = serializer || Serializer::Marshal
         | 
| 13 15 | 
             
                  @storage = storage || Storage.fetch(path)
         | 
| 14 16 | 
             
                  @auto_commit = auto_commit
         | 
| 15 | 
            -
                   | 
| 17 | 
            +
                  super(@storage.load(path))
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                # store
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                def plain_store(key, value)
         | 
| 23 | 
            +
                  __getobj__.store(key, value)
         | 
| 24 | 
            +
                  save if @auto_commit
         | 
| 16 25 | 
             
                end
         | 
| 17 26 |  | 
| 18 | 
            -
                 | 
| 27 | 
            +
                def secure_store(key, value)
         | 
| 28 | 
            +
                  value = @serializer.dump(value)
         | 
| 29 | 
            +
                  plain_store(key, @encrypter.encrypt(value))
         | 
| 30 | 
            +
                end
         | 
| 19 31 |  | 
| 20 32 | 
             
                def store(key, value)
         | 
| 21 33 | 
             
                  if key.to_s.start_with?("enc:")
         | 
| @@ -26,24 +38,32 @@ module SecureConf | |
| 26 38 | 
             
                end
         | 
| 27 39 | 
             
                alias_method :[]=, :store
         | 
| 28 40 |  | 
| 29 | 
            -
                 | 
| 30 | 
            -
                  value = @serializer.dump(value)
         | 
| 31 | 
            -
                  plain_store(key, @encripter.encrypt(value))
         | 
| 32 | 
            -
                end
         | 
| 41 | 
            +
                # get
         | 
| 33 42 |  | 
| 34 | 
            -
                 | 
| 43 | 
            +
                def plain_get(key)
         | 
| 44 | 
            +
                  __getobj__[key]
         | 
| 45 | 
            +
                end
         | 
| 35 46 |  | 
| 36 47 | 
             
                def [](key)
         | 
| 37 48 | 
             
                  value = plain_get(key)
         | 
| 38 49 | 
             
                  if value && key.to_s.start_with?("enc:")
         | 
| 39 | 
            -
                    value = @ | 
| 50 | 
            +
                    value = @encrypter.decrypt(value)
         | 
| 40 51 | 
             
                    value = @serializer.load(value)
         | 
| 41 52 | 
             
                  end
         | 
| 42 53 | 
             
                  value
         | 
| 43 54 | 
             
                end
         | 
| 44 55 |  | 
| 56 | 
            +
                # delete
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                def delete(key)
         | 
| 59 | 
            +
                  __getobj__.delete(key)
         | 
| 60 | 
            +
                  save if @auto_commit
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                # save
         | 
| 64 | 
            +
             | 
| 45 65 | 
             
                def save
         | 
| 46 | 
            -
                  @storage.save(path,  | 
| 66 | 
            +
                  @storage.save(path, __getobj__)
         | 
| 47 67 | 
             
                end
         | 
| 48 68 | 
             
              end
         | 
| 49 69 | 
             
            end
         | 
| @@ -1,10 +1,11 @@ | |
| 1 1 | 
             
            require 'openssl'
         | 
| 2 2 | 
             
            require 'base64'
         | 
| 3 | 
            +
            require 'secure_conf/openssh'
         | 
| 3 4 |  | 
| 4 5 | 
             
            module SecureConf
         | 
| 5 6 | 
             
              class Encrypter
         | 
| 6 7 | 
             
                def initialize(pkey=nil, pass=nil)
         | 
| 7 | 
            -
                  pkey ||= "~/.ssh/id_rsa"
         | 
| 8 | 
            +
                  pkey ||= File.open(File.expand_path("~/.ssh/id_rsa"), "r") {|f| f.read }
         | 
| 8 9 | 
             
                  self.pkey = [pkey, pass]
         | 
| 9 10 | 
             
                end
         | 
| 10 11 |  | 
| @@ -13,15 +14,22 @@ module SecureConf | |
| 13 14 |  | 
| 14 15 | 
             
                  case pk
         | 
| 15 16 | 
             
                  when OpenSSL::PKey::RSA
         | 
| 17 | 
            +
                    # OpenSSL private key object
         | 
| 16 18 | 
             
                    @pkey = pk
         | 
| 19 | 
            +
                  when OpenSSH::PKey
         | 
| 20 | 
            +
                    # OpenSSH private key object
         | 
| 21 | 
            +
                    @pkey = pk.to_openssl
         | 
| 17 22 | 
             
                  when String
         | 
| 18 | 
            -
                     | 
| 19 | 
            -
                    if  | 
| 20 | 
            -
                       | 
| 23 | 
            +
                    pk = pk.strip
         | 
| 24 | 
            +
                    if pk.start_with?("-----BEGIN OPENSSH PRIVATE KEY-----") && pk.end_with?("-----END OPENSSH PRIVATE KEY-----")
         | 
| 25 | 
            +
                      # OpenSSH private pem string
         | 
| 26 | 
            +
                      @pkey = OpenSSH::PKey.new(pk).to_openssl
         | 
| 27 | 
            +
                    else
         | 
| 28 | 
            +
                      # OpenSSL private pem string
         | 
| 29 | 
            +
                      @pkey = OpenSSL::PKey::RSA.new(pk, pass)
         | 
| 21 30 | 
             
                    end
         | 
| 22 | 
            -
             | 
| 23 | 
            -
                    @pkey = OpenSSL::PKey::RSA.new(pk, pass)
         | 
| 24 31 | 
             
                  when Integer
         | 
| 32 | 
            +
                    # generate
         | 
| 25 33 | 
             
                    @pkey = OpenSSL::PKey::RSA.new(pk)
         | 
| 26 34 | 
             
                  end
         | 
| 27 35 | 
             
                end
         | 
| @@ -0,0 +1,302 @@ | |
| 1 | 
            +
            require 'stringio'
         | 
| 2 | 
            +
            require 'base64'
         | 
| 3 | 
            +
            require 'singleton'
         | 
| 4 | 
            +
            require 'openssl'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module SecureConf
         | 
| 7 | 
            +
              module OpenSSH
         | 
| 8 | 
            +
                class PKey
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  def initialize(source)
         | 
| 11 | 
            +
                    if String===source
         | 
| 12 | 
            +
                      # pem string
         | 
| 13 | 
            +
                      @h = parse_pem(source)
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                    elsif IO===source || source.respond_to?(:read)
         | 
| 16 | 
            +
                      # pem io
         | 
| 17 | 
            +
                      source = source.read
         | 
| 18 | 
            +
                      @h = parse_pem(source)
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                    elsif source.respond_to?(:to_der)
         | 
| 21 | 
            +
                      # call to_der method
         | 
| 22 | 
            +
                      der = source.to_der
         | 
| 23 | 
            +
                      @h = parse_der(der)
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                    else
         | 
| 26 | 
            +
                      # other
         | 
| 27 | 
            +
                      raise ArgumentError, "unsupported argument type: #{source.class.name}"
         | 
| 28 | 
            +
                    end
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  def parse_pem(pem)
         | 
| 32 | 
            +
                    pem = pem.strip
         | 
| 33 | 
            +
                    if !pem.start_with?("-----BEGIN OPENSSH PRIVATE KEY-----") || !pem.end_with?("-----END OPENSSH PRIVATE KEY-----")
         | 
| 34 | 
            +
                      raise FormatError, "invalid pem string"
         | 
| 35 | 
            +
                    end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                    pem = pem.gsub(/-----(?:BEGIN|END) OPENSSH PRIVATE KEY-----/, "").gsub(/[\r\n]/, "")
         | 
| 38 | 
            +
                    der = Base64::strict_decode64(pem)
         | 
| 39 | 
            +
                    parse_der(der)
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                  # sshkey_private_to_blob2
         | 
| 43 | 
            +
                  # https://github.com/openssh/openssh-portable/blob/master/sshkey.c#L3910
         | 
| 44 | 
            +
                  def parse_der(der)
         | 
| 45 | 
            +
                    bio = StringIO.new(der, "rb")
         | 
| 46 | 
            +
                    h = {}
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                    # header
         | 
| 49 | 
            +
                    h[:header] = bio.read(15)
         | 
| 50 | 
            +
                    if h[:header]!="openssh-key-v1\0"
         | 
| 51 | 
            +
                      raise FormatError, "openssh header not found"
         | 
| 52 | 
            +
                    end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                    # ciphername
         | 
| 55 | 
            +
                    length = bio.read(4).unpack("N")[0]
         | 
| 56 | 
            +
                    h[:ciphername] = bio.read(length)
         | 
| 57 | 
            +
                    if h[:ciphername]!="none"
         | 
| 58 | 
            +
                      raise FormatError, "ciphername is not 'none': #{h[:ciphername]}"
         | 
| 59 | 
            +
                    end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                    # kdfname
         | 
| 62 | 
            +
                    length = bio.read(4).unpack("N")[0]
         | 
| 63 | 
            +
                    h[:kdfname] = bio.read(length)
         | 
| 64 | 
            +
                    if h[:kdfname]!="none"
         | 
| 65 | 
            +
                      raise FormatError, "kdfname is not 'none': #{h[:kdfname]}"
         | 
| 66 | 
            +
                    end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                    # kdf
         | 
| 69 | 
            +
                    length = bio.read(4).unpack("N")[0]
         | 
| 70 | 
            +
                    h[:kdf] = bio.read(length)
         | 
| 71 | 
            +
                    if h[:kdf]!=""
         | 
| 72 | 
            +
                      raise FormatError, "kdf is not empty: #{h[:kdfname]}"
         | 
| 73 | 
            +
                    end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                    # number of keys
         | 
| 76 | 
            +
                    h[:keys_num] = bio.read(4).unpack("N")[0]
         | 
| 77 | 
            +
                    if h[:keys_num]!=1
         | 
| 78 | 
            +
                      raise FormatError, "number of keys is not 1: #{h[:keys_num]}"
         | 
| 79 | 
            +
                    end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                    # public key
         | 
| 82 | 
            +
                    length = bio.read(4).unpack("N")[0]
         | 
| 83 | 
            +
                    publickey = bio.read(length)
         | 
| 84 | 
            +
                    h[:publickey] = parse_der_publickey(publickey)
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                    # rnd+prv+comment+pad
         | 
| 87 | 
            +
                    length = bio.read(4).unpack("N")[0]
         | 
| 88 | 
            +
                    privatekey = bio.read(length)
         | 
| 89 | 
            +
                    h[:privatekey] = parse_der_privatekey(privatekey)
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                    # check eof
         | 
| 92 | 
            +
                    if !bio.eof?
         | 
| 93 | 
            +
                      raise FormatError, "no eof"
         | 
| 94 | 
            +
                    end
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                    h
         | 
| 97 | 
            +
                  end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                  # to_blob_buf
         | 
| 100 | 
            +
                  # https://github.com/openssh/openssh-portable/blob/master/sshkey.c#L828
         | 
| 101 | 
            +
                  def parse_der_publickey(publickey)
         | 
| 102 | 
            +
                    bio = StringIO.new(publickey, "rb")
         | 
| 103 | 
            +
                    h = {}
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                    # keytype
         | 
| 106 | 
            +
                    length = bio.read(4).unpack("N")[0]
         | 
| 107 | 
            +
                    h[:keytype] = bio.read(length)
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                    # content
         | 
| 110 | 
            +
                    kt = Keytype.fetch(h[:keytype])
         | 
| 111 | 
            +
                    if !kt
         | 
| 112 | 
            +
                      raise UnsupportedError, "unsupported keytype: #{h[:keytype]}"
         | 
| 113 | 
            +
                    end
         | 
| 114 | 
            +
                    kt.parse_der_public_key_contents(h, bio)
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                    # check eof
         | 
| 117 | 
            +
                    if !bio.eof?
         | 
| 118 | 
            +
                      raise FormatError, "publickey no eof"
         | 
| 119 | 
            +
                    end
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                    h
         | 
| 122 | 
            +
                  end
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                  # sshkey_private_serialize_opt
         | 
| 125 | 
            +
                  # https://github.com/openssh/openssh-portable/blob/master/sshkey.c#L3230
         | 
| 126 | 
            +
                  def parse_der_privatekey(privatekey)
         | 
| 127 | 
            +
                    bio = StringIO.new(privatekey, "rb")
         | 
| 128 | 
            +
                    h = {}
         | 
| 129 | 
            +
             | 
| 130 | 
            +
                    # checksum
         | 
| 131 | 
            +
                    h[:checksum] = bio.read(8).unpack("NN")
         | 
| 132 | 
            +
             | 
| 133 | 
            +
                    # keytype
         | 
| 134 | 
            +
                    length = bio.read(4).unpack("N")[0]
         | 
| 135 | 
            +
                    h[:keytype] = bio.read(length)
         | 
| 136 | 
            +
             | 
| 137 | 
            +
                    # content
         | 
| 138 | 
            +
                    kt = Keytype.fetch(h[:keytype])
         | 
| 139 | 
            +
                    if !kt
         | 
| 140 | 
            +
                      raise UnsupportedError, "unsupported keytype: #{h[:keytype]}"
         | 
| 141 | 
            +
                    end
         | 
| 142 | 
            +
                    kt.parse_der_private_key_contents(h, bio)
         | 
| 143 | 
            +
             | 
| 144 | 
            +
                    # comment
         | 
| 145 | 
            +
                    length = bio.read(4).unpack("N")[0]
         | 
| 146 | 
            +
                    h[:comment] = bio.read(length)
         | 
| 147 | 
            +
             | 
| 148 | 
            +
                    # padding
         | 
| 149 | 
            +
                    h[:padding] = bio.read
         | 
| 150 | 
            +
             | 
| 151 | 
            +
                    # check eof
         | 
| 152 | 
            +
                    if !bio.eof?
         | 
| 153 | 
            +
                      raise FormatError, "privatekey no eof"
         | 
| 154 | 
            +
                    end
         | 
| 155 | 
            +
             | 
| 156 | 
            +
                    h
         | 
| 157 | 
            +
                  end
         | 
| 158 | 
            +
             | 
| 159 | 
            +
                  def keytype
         | 
| 160 | 
            +
                    Keytype.fetch(@h[:privatekey][:keytype])
         | 
| 161 | 
            +
                  end
         | 
| 162 | 
            +
             | 
| 163 | 
            +
                  def to_openssl
         | 
| 164 | 
            +
                    keytype.to_openssl(@h)
         | 
| 165 | 
            +
                  end
         | 
| 166 | 
            +
             | 
| 167 | 
            +
                  def to_openssl_pem
         | 
| 168 | 
            +
                    keytype.to_openssl_pem(@h)
         | 
| 169 | 
            +
                  end
         | 
| 170 | 
            +
             | 
| 171 | 
            +
                  def to_openssl_der
         | 
| 172 | 
            +
                    keytype.to_openssl_der(@h)
         | 
| 173 | 
            +
                  end
         | 
| 174 | 
            +
                end
         | 
| 175 | 
            +
             | 
| 176 | 
            +
                module Keytype
         | 
| 177 | 
            +
                  module Base
         | 
| 178 | 
            +
                    def support?(keytype)
         | 
| 179 | 
            +
                      raise Error, "not implemented error: #{self.class.name}.support?(keytype)"
         | 
| 180 | 
            +
                    end
         | 
| 181 | 
            +
             | 
| 182 | 
            +
                    def parse_der_public_key_contents(h, bio)
         | 
| 183 | 
            +
                      raise Error, "not implemented error: #{self.class.name}.parse_der_public_key_contents(h, bio)"
         | 
| 184 | 
            +
                    end
         | 
| 185 | 
            +
             | 
| 186 | 
            +
                    def parse_der_private_key_contents(h, bio)
         | 
| 187 | 
            +
                      raise Error, "not implemented error: #{self.class.name}.parse_der_private_key_contents(h, bio)"
         | 
| 188 | 
            +
                    end
         | 
| 189 | 
            +
             | 
| 190 | 
            +
                    def to_openssl(h)
         | 
| 191 | 
            +
                      raise Error, "not implemented error: #{self.class.name}.to_openssl(h)"
         | 
| 192 | 
            +
                    end
         | 
| 193 | 
            +
             | 
| 194 | 
            +
                    def to_openssl_pem(h)
         | 
| 195 | 
            +
                      raise Error, "not implemented error: #{self.class.name}.to_openssl_pem(h)"
         | 
| 196 | 
            +
                    end
         | 
| 197 | 
            +
             | 
| 198 | 
            +
                    def to_openssl_der(h)
         | 
| 199 | 
            +
                      raise Error, "not implemented error: #{self.class.name}.to_openssl_der(h)"
         | 
| 200 | 
            +
                    end
         | 
| 201 | 
            +
                  end
         | 
| 202 | 
            +
             | 
| 203 | 
            +
                  class RSA
         | 
| 204 | 
            +
                    include Singleton
         | 
| 205 | 
            +
                    include Base
         | 
| 206 | 
            +
             | 
| 207 | 
            +
                    def support?(keytype)
         | 
| 208 | 
            +
                      keytype=="ssh-rsa"
         | 
| 209 | 
            +
                    end
         | 
| 210 | 
            +
             | 
| 211 | 
            +
                    def parse_der_public_key_contents(h, bio)
         | 
| 212 | 
            +
                      # e pub0
         | 
| 213 | 
            +
                      length = bio.read(4).unpack("N")[0]
         | 
| 214 | 
            +
                      h[:e] = bio.read(length)
         | 
| 215 | 
            +
             | 
| 216 | 
            +
                      # n pub1
         | 
| 217 | 
            +
                      length = bio.read(4).unpack("N")[0]
         | 
| 218 | 
            +
                      h[:n] = bio.read(length)
         | 
| 219 | 
            +
                    end
         | 
| 220 | 
            +
             | 
| 221 | 
            +
                    def parse_der_private_key_contents(h, bio)
         | 
| 222 | 
            +
                      # n pub0
         | 
| 223 | 
            +
                      length = bio.read(4).unpack("N")[0]
         | 
| 224 | 
            +
                      h[:n] = bio.read(length)
         | 
| 225 | 
            +
             | 
| 226 | 
            +
                      # e pub1
         | 
| 227 | 
            +
                      length = bio.read(4).unpack("N")[0]
         | 
| 228 | 
            +
                      h[:e] = bio.read(length)
         | 
| 229 | 
            +
             | 
| 230 | 
            +
                      # d pri0
         | 
| 231 | 
            +
                      length = bio.read(4).unpack("N")[0]
         | 
| 232 | 
            +
                      h[:d] = bio.read(length)
         | 
| 233 | 
            +
             | 
| 234 | 
            +
                      # iqmp
         | 
| 235 | 
            +
                      length = bio.read(4).unpack("N")[0]
         | 
| 236 | 
            +
                      h[:iqmp] = bio.read(length)
         | 
| 237 | 
            +
             | 
| 238 | 
            +
                      # p
         | 
| 239 | 
            +
                      length = bio.read(4).unpack("N")[0]
         | 
| 240 | 
            +
                      h[:p] = bio.read(length)
         | 
| 241 | 
            +
             | 
| 242 | 
            +
                      # q
         | 
| 243 | 
            +
                      length = bio.read(4).unpack("N")[0]
         | 
| 244 | 
            +
                      h[:q] = bio.read(length)
         | 
| 245 | 
            +
                    end
         | 
| 246 | 
            +
             | 
| 247 | 
            +
                    def to_openssl(h)
         | 
| 248 | 
            +
                      pem = to_openssl_pem(h)
         | 
| 249 | 
            +
                      OpenSSL::PKey::RSA.new(pem)
         | 
| 250 | 
            +
                    end
         | 
| 251 | 
            +
             | 
| 252 | 
            +
                    def to_openssl_pem(h)
         | 
| 253 | 
            +
                      der = to_openssl_der(h)
         | 
| 254 | 
            +
                      b64 = Base64::strict_encode64(der)
         | 
| 255 | 
            +
                      lines = b64.scan(/.{1,64}/)
         | 
| 256 | 
            +
             | 
| 257 | 
            +
                      [
         | 
| 258 | 
            +
                        "-----BEGIN RSA PRIVATE KEY-----",
         | 
| 259 | 
            +
                        lines,
         | 
| 260 | 
            +
                        "-----END RSA PRIVATE KEY-----",
         | 
| 261 | 
            +
                      ].flatten.join("\n")
         | 
| 262 | 
            +
                    end
         | 
| 263 | 
            +
             | 
| 264 | 
            +
                    def to_openssl_der(h)
         | 
| 265 | 
            +
                      d = h[:privatekey][:d].unpack("H*")[0].to_i(16)
         | 
| 266 | 
            +
                      p = h[:privatekey][:p].unpack("H*")[0].to_i(16)
         | 
| 267 | 
            +
                      q = h[:privatekey][:q].unpack("H*")[0].to_i(16)
         | 
| 268 | 
            +
             | 
| 269 | 
            +
                      exponent1 = d % (p - 1)
         | 
| 270 | 
            +
                      exponent2 = d % (q - 1)
         | 
| 271 | 
            +
             | 
| 272 | 
            +
                      OpenSSL::ASN1::Sequence.new([
         | 
| 273 | 
            +
                        OpenSSL::ASN1::Integer.new(0),
         | 
| 274 | 
            +
                        OpenSSL::ASN1::Integer.new(h[:privatekey][:n].unpack("H*")[0].to_i(16)),
         | 
| 275 | 
            +
                        OpenSSL::ASN1::Integer.new(h[:privatekey][:e].unpack("H*")[0].to_i(16)),
         | 
| 276 | 
            +
                        OpenSSL::ASN1::Integer.new(h[:privatekey][:d].unpack("H*")[0].to_i(16)),
         | 
| 277 | 
            +
                        OpenSSL::ASN1::Integer.new(p),
         | 
| 278 | 
            +
                        OpenSSL::ASN1::Integer.new(q),
         | 
| 279 | 
            +
                        OpenSSL::ASN1::Integer.new(exponent1),
         | 
| 280 | 
            +
                        OpenSSL::ASN1::Integer.new(exponent2),
         | 
| 281 | 
            +
                        OpenSSL::ASN1::Integer.new(h[:privatekey][:iqmp].unpack("H*")[0].to_i(16)),
         | 
| 282 | 
            +
                      ]).to_der
         | 
| 283 | 
            +
                    end
         | 
| 284 | 
            +
                  end
         | 
| 285 | 
            +
             | 
| 286 | 
            +
                  KEYTYPES = [RSA.instance]
         | 
| 287 | 
            +
             | 
| 288 | 
            +
                  def self.fetch(keytype)
         | 
| 289 | 
            +
                    KEYTYPES.find {|kt| kt.support?(keytype)}
         | 
| 290 | 
            +
                  end
         | 
| 291 | 
            +
                end
         | 
| 292 | 
            +
             | 
| 293 | 
            +
                class Error < StandardError
         | 
| 294 | 
            +
                end
         | 
| 295 | 
            +
             | 
| 296 | 
            +
                class FormatError < Error
         | 
| 297 | 
            +
                end
         | 
| 298 | 
            +
             | 
| 299 | 
            +
                class UnsupportedError < Error
         | 
| 300 | 
            +
                end
         | 
| 301 | 
            +
              end
         | 
| 302 | 
            +
            end
         | 
| @@ -2,11 +2,6 @@ require "yaml" | |
| 2 2 |  | 
| 3 3 | 
             
            module SecureConf
         | 
| 4 4 | 
             
              module Storage
         | 
| 5 | 
            -
                def self.fetch(path)
         | 
| 6 | 
            -
                  ext = File.extname(path).sub(".", "").capitalize
         | 
| 7 | 
            -
                  const_get(ext)
         | 
| 8 | 
            -
                end
         | 
| 9 | 
            -
             | 
| 10 5 | 
             
                module Yaml
         | 
| 11 6 | 
             
                  def self.load(path)
         | 
| 12 7 | 
             
                    if File.file?(path)
         | 
| @@ -19,7 +14,9 @@ module SecureConf | |
| 19 14 | 
             
                  def self.save(path, obj)
         | 
| 20 15 | 
             
                    h = {}
         | 
| 21 16 | 
             
                    h.replace(obj)
         | 
| 22 | 
            -
                     | 
| 17 | 
            +
                    File.open(path, "w") {|f|
         | 
| 18 | 
            +
                      YAML.dump(h, f)
         | 
| 19 | 
            +
                    }
         | 
| 23 20 | 
             
                  end
         | 
| 24 21 | 
             
                end
         | 
| 25 22 | 
             
                Yml = Yaml
         | 
    
        data/lib/secure_conf/version.rb
    CHANGED
    
    
    
        data/secure_conf.gemspec
    CHANGED
    
    | @@ -19,7 +19,7 @@ Gem::Specification.new do |spec| | |
| 19 19 | 
             
              spec.executables   = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
         | 
| 20 20 | 
             
              spec.require_paths = ["lib"]
         | 
| 21 21 |  | 
| 22 | 
            -
              spec.add_development_dependency "bundler", "~>  | 
| 23 | 
            -
              spec.add_development_dependency "rake", "~>  | 
| 24 | 
            -
              spec.add_development_dependency " | 
| 22 | 
            +
              spec.add_development_dependency "bundler", "~> 2.0"
         | 
| 23 | 
            +
              spec.add_development_dependency "rake", "~> 12.0"
         | 
| 24 | 
            +
              spec.add_development_dependency "minitest", "~> 5.0"
         | 
| 25 25 | 
             
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: secure_conf
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 1.0 | 
| 4 | 
            +
              version: 2.1.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - yoshida
         | 
| 8 | 
            -
            autorequire: | 
| 8 | 
            +
            autorequire:
         | 
| 9 9 | 
             
            bindir: exe
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date:  | 
| 11 | 
            +
            date: 2022-04-15 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: bundler
         | 
| @@ -16,46 +16,47 @@ dependencies: | |
| 16 16 | 
             
                requirements:
         | 
| 17 17 | 
             
                - - "~>"
         | 
| 18 18 | 
             
                  - !ruby/object:Gem::Version
         | 
| 19 | 
            -
                    version: ' | 
| 19 | 
            +
                    version: '2.0'
         | 
| 20 20 | 
             
              type: :development
         | 
| 21 21 | 
             
              prerelease: false
         | 
| 22 22 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 23 23 | 
             
                requirements:
         | 
| 24 24 | 
             
                - - "~>"
         | 
| 25 25 | 
             
                  - !ruby/object:Gem::Version
         | 
| 26 | 
            -
                    version: ' | 
| 26 | 
            +
                    version: '2.0'
         | 
| 27 27 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 28 28 | 
             
              name: rake
         | 
| 29 29 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 30 30 | 
             
                requirements:
         | 
| 31 31 | 
             
                - - "~>"
         | 
| 32 32 | 
             
                  - !ruby/object:Gem::Version
         | 
| 33 | 
            -
                    version: ' | 
| 33 | 
            +
                    version: '12.0'
         | 
| 34 34 | 
             
              type: :development
         | 
| 35 35 | 
             
              prerelease: false
         | 
| 36 36 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 37 37 | 
             
                requirements:
         | 
| 38 38 | 
             
                - - "~>"
         | 
| 39 39 | 
             
                  - !ruby/object:Gem::Version
         | 
| 40 | 
            -
                    version: ' | 
| 40 | 
            +
                    version: '12.0'
         | 
| 41 41 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 42 | 
            -
              name:  | 
| 42 | 
            +
              name: minitest
         | 
| 43 43 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 44 44 | 
             
                requirements:
         | 
| 45 45 | 
             
                - - "~>"
         | 
| 46 46 | 
             
                  - !ruby/object:Gem::Version
         | 
| 47 | 
            -
                    version: ' | 
| 47 | 
            +
                    version: '5.0'
         | 
| 48 48 | 
             
              type: :development
         | 
| 49 49 | 
             
              prerelease: false
         | 
| 50 50 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 51 51 | 
             
                requirements:
         | 
| 52 52 | 
             
                - - "~>"
         | 
| 53 53 | 
             
                  - !ruby/object:Gem::Version
         | 
| 54 | 
            -
                    version: ' | 
| 54 | 
            +
                    version: '5.0'
         | 
| 55 55 | 
             
            description: To encrypt the configuration value.
         | 
| 56 56 | 
             
            email:
         | 
| 57 57 | 
             
            - yoshida.eth0@gmail.com
         | 
| 58 | 
            -
            executables: | 
| 58 | 
            +
            executables:
         | 
| 59 | 
            +
            - secure_conf.rb
         | 
| 59 60 | 
             
            extensions: []
         | 
| 60 61 | 
             
            extra_rdoc_files: []
         | 
| 61 62 | 
             
            files:
         | 
| @@ -68,11 +69,13 @@ files: | |
| 68 69 | 
             
            - Rakefile
         | 
| 69 70 | 
             
            - bin/console
         | 
| 70 71 | 
             
            - bin/setup
         | 
| 72 | 
            +
            - exe/secure_conf.rb
         | 
| 71 73 | 
             
            - lib/secure_conf.rb
         | 
| 72 74 | 
             
            - lib/secure_conf/config.rb
         | 
| 73 75 | 
             
            - lib/secure_conf/core_ext.rb
         | 
| 74 76 | 
             
            - lib/secure_conf/core_ext/string.rb
         | 
| 75 77 | 
             
            - lib/secure_conf/encrypter.rb
         | 
| 78 | 
            +
            - lib/secure_conf/openssh.rb
         | 
| 76 79 | 
             
            - lib/secure_conf/serializer.rb
         | 
| 77 80 | 
             
            - lib/secure_conf/serializer/json.rb
         | 
| 78 81 | 
             
            - lib/secure_conf/serializer/marshal.rb
         | 
| @@ -84,7 +87,7 @@ homepage: https://github.com/yoshida-eth0/ruby-secure_conf | |
| 84 87 | 
             
            licenses:
         | 
| 85 88 | 
             
            - MIT
         | 
| 86 89 | 
             
            metadata: {}
         | 
| 87 | 
            -
            post_install_message: | 
| 90 | 
            +
            post_install_message:
         | 
| 88 91 | 
             
            rdoc_options: []
         | 
| 89 92 | 
             
            require_paths:
         | 
| 90 93 | 
             
            - lib
         | 
| @@ -99,9 +102,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 99 102 | 
             
                - !ruby/object:Gem::Version
         | 
| 100 103 | 
             
                  version: '0'
         | 
| 101 104 | 
             
            requirements: []
         | 
| 102 | 
            -
             | 
| 103 | 
            -
             | 
| 104 | 
            -
            signing_key: 
         | 
| 105 | 
            +
            rubygems_version: 3.3.3
         | 
| 106 | 
            +
            signing_key:
         | 
| 105 107 | 
             
            specification_version: 4
         | 
| 106 108 | 
             
            summary: To encrypt the configuration value.
         | 
| 107 109 | 
             
            test_files: []
         |