secure_conf 1.1.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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: []