secure_conf 1.1.0 → 2.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 2e3c76b854e1a1f9928b858a30e35323dd7d149d
4
- data.tar.gz: 565f358aeaf3fce3a0102a3f734861769824daaa
2
+ SHA256:
3
+ metadata.gz: 977af3cdc7abe1fce0989ac7ed1e8cfc9d91c4008458638888ec71a4b0129152
4
+ data.tar.gz: 22e451c7ab83a730c57011b0423e079c6634fd92ab4770ca570287220fca4d91
5
5
  SHA512:
6
- metadata.gz: 6633e4d0d35351ef51838d2c4cc6bd31c25ce94c2ddb29c53c87a21125f22b1598c9271b50c50b60e1e73e9bc27daceb0d930497b49d7fe6dd352afaa316c105
7
- data.tar.gz: c724cd2b2dc2416914b46871667386c042b0f5f23e9af7c4395d6f80ccdd91f771c61b6277240169189323e79670f1aa821bc3b4b81a63de29105b67bdd14a0b
6
+ metadata.gz: 55ee00fef2d53f1a27fc6a7e5ffbed08522701bdf6250665d9b8a5dce65fadaf252b0fd3a02a1817055c168e53a43f6b46775a7c2a92d722a83d9e45ba3dc428
7
+ data.tar.gz: 5b43c21c28fdedd803a80ab73dd37de6fda28af5b6e0c6226819c43948dd8ef53816af63c212896a206112b0e7c1f183db416ac8c9bb274ecb0909103cf4b9f8
data/README.md CHANGED
@@ -20,7 +20,7 @@ Or install it yourself as:
20
20
 
21
21
  ## Usage
22
22
 
23
- path = File.expand_path("config.yml", File.dirname(__FILE__))
23
+ path = File.expand_path("secure.yml", File.dirname(__FILE__))
24
24
  config = SecureConf::Config.new(path)
25
25
 
26
26
  config["enc:id"] = "yoshida-eth0"
data/exe/secure_conf.rb CHANGED
@@ -3,35 +3,33 @@
3
3
  $LOAD_PATH << File.expand_path("../lib", File.dirname(__FILE__))
4
4
 
5
5
  require 'secure_conf'
6
-
7
- method = (ARGV[0]||"").to_sym
8
- path = ARGV[1]
9
- key = ARGV[2]
10
- value = ARGV[3]
6
+ require 'optparse'
11
7
 
12
8
  class SecureConfCmd
13
9
 
14
- def initialize(path)
15
- @path = path
10
+ def initialize(storage_path, privatekey_path=nil)
11
+ @storage_path = File.expand_path(storage_path)
12
+ @privatekey_path = File.expand_path(privatekey_path)
16
13
  end
17
14
 
18
15
  def config
19
16
  unless @config
20
- unless @path
21
- raise "path is required."
22
- end
23
- #unless File.file?(@path)
24
- # raise "path is not found."
17
+ #unless File.file?(@storage_path)
18
+ # raise "storage_path is not exist: #{@storage_path}"
25
19
  #end
26
20
 
27
- @config = SecureConf::Config.new(@path, auto_commit: true)
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, encripter: SecureConf::Encrypter.new(pkey), auto_commit: true)
28
28
  end
29
29
  @config
30
30
  end
31
31
 
32
32
  def read(key)
33
- raise "key is required." unless key
34
-
35
33
  val = config[key]
36
34
 
37
35
  puts "read"
@@ -40,9 +38,6 @@ class SecureConfCmd
40
38
  end
41
39
 
42
40
  def write(key, value)
43
- raise "key is required." unless key
44
- raise "value is required." unless value
45
-
46
41
  puts "write"
47
42
  puts " key: #{key}"
48
43
  puts " val: #{value}"
@@ -51,32 +46,76 @@ class SecureConfCmd
51
46
  end
52
47
 
53
48
  def delete(key)
54
- raise "key is required." unless key
55
-
56
49
  puts "delete"
57
50
  puts " key: #{key}"
58
51
  config.delete(key)
59
52
  puts "delete ok."
60
53
  end
54
+ end
55
+
61
56
 
62
- def usage
63
- lines = []
64
- lines << "Usage:"
65
- lines << " #{File.basename($0)} method path [key [value]]"
66
- lines << ""
67
- lines << " method:"
68
- lines << " read write delete"
69
- lines << ""
70
- lines << " required args"
71
- lines << " read : path key"
72
- lines << " write : path key value"
73
- lines << " delete: path key"
74
- lines.join("\n")
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
75
111
  end
112
+ puts parser.help
113
+ exit(1)
76
114
  end
77
115
 
78
116
 
79
- cmd = SecureConfCmd.new(path)
117
+ # execute
118
+ cmd = SecureConfCmd.new(storage_path, privatekey_path)
80
119
 
81
120
  begin
82
121
  case method
@@ -86,13 +125,11 @@ begin
86
125
  cmd.write(key, value)
87
126
  when :delete
88
127
  cmd.delete(key)
89
- else
90
- puts cmd.usage
91
128
  end
92
129
  rescue => e
93
130
  puts e
94
131
  puts
95
- puts cmd.usage
132
+ puts parser.help
96
133
 
97
134
  #puts e.backtrace.join("\n")
98
135
  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
- pk2 = File.expand_path(pk)
19
- if File.file?(pk2) && File.readable?(pk2)
20
- pk = File.read(pk2)
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
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
+ sequence = 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
@@ -1,3 +1,3 @@
1
1
  module SecureConf
2
- VERSION = "1.1.0"
2
+ VERSION = "2.0.0"
3
3
  end
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", "~> 1.12"
23
- spec.add_development_dependency "rake", "~> 10.0"
22
+ spec.add_development_dependency "bundler", "~> 2.0"
23
+ spec.add_development_dependency "rake", "~> 12.0"
24
24
  spec.add_development_dependency "rspec", "~> 3.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.1.0
4
+ version: 2.0.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: 2017-07-13 00:00:00.000000000 Z
11
+ date: 2022-04-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -16,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.12'
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: '1.12'
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: '10.0'
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: '10.0'
40
+ version: '12.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -75,6 +75,7 @@ files:
75
75
  - lib/secure_conf/core_ext.rb
76
76
  - lib/secure_conf/core_ext/string.rb
77
77
  - lib/secure_conf/encrypter.rb
78
+ - lib/secure_conf/openssh.rb
78
79
  - lib/secure_conf/serializer.rb
79
80
  - lib/secure_conf/serializer/json.rb
80
81
  - lib/secure_conf/serializer/marshal.rb
@@ -86,7 +87,7 @@ homepage: https://github.com/yoshida-eth0/ruby-secure_conf
86
87
  licenses:
87
88
  - MIT
88
89
  metadata: {}
89
- post_install_message:
90
+ post_install_message:
90
91
  rdoc_options: []
91
92
  require_paths:
92
93
  - lib
@@ -101,9 +102,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
101
102
  - !ruby/object:Gem::Version
102
103
  version: '0'
103
104
  requirements: []
104
- rubyforge_project:
105
- rubygems_version: 2.5.1
106
- signing_key:
105
+ rubygems_version: 3.3.3
106
+ signing_key:
107
107
  specification_version: 4
108
108
  summary: To encrypt the configuration value.
109
109
  test_files: []