s7n 0.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.
Files changed (47) hide show
  1. data/.gitignore +7 -0
  2. data/Gemfile +4 -0
  3. data/Gemfile.lock +28 -0
  4. data/LICENCE +24 -0
  5. data/README.rdoc +54 -0
  6. data/Rakefile +67 -0
  7. data/bin/s7ncli +23 -0
  8. data/lib/s7n.rb +12 -0
  9. data/lib/s7n/action.rb +7 -0
  10. data/lib/s7n/attribute.rb +226 -0
  11. data/lib/s7n/cipher.rb +110 -0
  12. data/lib/s7n/configuration.rb +41 -0
  13. data/lib/s7n/entry.rb +106 -0
  14. data/lib/s7n/entry_collection.rb +44 -0
  15. data/lib/s7n/entry_template.rb +10 -0
  16. data/lib/s7n/exception.rb +116 -0
  17. data/lib/s7n/file.rb +77 -0
  18. data/lib/s7n/gpass_file.rb +203 -0
  19. data/lib/s7n/key.rb +83 -0
  20. data/lib/s7n/message_catalog.rb +5 -0
  21. data/lib/s7n/s7n_file.rb +47 -0
  22. data/lib/s7n/s7ncli.rb +226 -0
  23. data/lib/s7n/s7ncli/attribute_command.rb +83 -0
  24. data/lib/s7n/s7ncli/command.rb +215 -0
  25. data/lib/s7n/s7ncli/entry_collection_command.rb +63 -0
  26. data/lib/s7n/s7ncli/entry_command.rb +728 -0
  27. data/lib/s7n/s7ncli/option.rb +101 -0
  28. data/lib/s7n/secret_generator.rb +30 -0
  29. data/lib/s7n/undo_stack.rb +7 -0
  30. data/lib/s7n/unicode_data.rb +29 -0
  31. data/lib/s7n/utils.rb +37 -0
  32. data/lib/s7n/version.rb +3 -0
  33. data/lib/s7n/world.rb +158 -0
  34. data/po/ja/s7n.po +533 -0
  35. data/po/s7n.pot +533 -0
  36. data/s7n.gemspec +30 -0
  37. data/test/s7n/attribute_test.rb +50 -0
  38. data/test/s7n/gpass_file_test.rb +169 -0
  39. data/test/s7n/gpass_file_test/passwords.gps.empty +1 -0
  40. data/test/s7n/gpass_file_test/passwords.gps.one_entry +2 -0
  41. data/test/s7n/gpass_file_test/passwords.gps.three_entries +3 -0
  42. data/test/s7n/gpass_file_test/passwords.gps.with_folders +0 -0
  43. data/test/s7n/secret_generator_test.rb +29 -0
  44. data/test/s7n/unicode_data_test.rb +28 -0
  45. data/test/s7n/world_test.rb +35 -0
  46. data/test/test_helper.rb +11 -0
  47. metadata +153 -0
@@ -0,0 +1,110 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require "openssl"
4
+
5
+ module S7n
6
+ # 秘密鍵暗号化方式を用いた暗号化処理を表現する。
7
+ class Cipher
8
+ @@cipher_classes = {}
9
+
10
+ class << self
11
+ def create_instance(type, options)
12
+ return @@cipher_classes[type].new(options)
13
+ end
14
+
15
+ private
16
+
17
+ def cipher_type(name)
18
+ if @@cipher_classes.nil?
19
+ @@cipher_classes = {}
20
+ end
21
+ @@cipher_classes[name] = self
22
+ self.const_set("CIPHER_TYPE", name)
23
+ end
24
+ end
25
+
26
+ attr_accessor :options
27
+
28
+ def initialize(options)
29
+ self.options = options
30
+ end
31
+
32
+ # io で指定した IO からデータを読み込み、復号する。
33
+ def decrypt(io)
34
+ begin
35
+ cipher = create_cipher
36
+ cipher.decrypt
37
+ parse_options(cipher, options)
38
+ s = ""
39
+ while d = io.read(cipher.block_size)
40
+ s << cipher.update(d)
41
+ end
42
+ s << cipher.final
43
+ rescue OpenSSL::Cipher::CipherError
44
+ raise InvalidPassphrase
45
+ end
46
+ return s
47
+ end
48
+
49
+ # io で指定した IO からデータを読み込み、暗号化する。
50
+ def encrypt(io)
51
+ cipher = create_cipher
52
+ cipher.encrypt
53
+ parse_options(cipher, options)
54
+ s = ""
55
+ while d = io.read(cipher.block_size)
56
+ s << cipher.update(d)
57
+ end
58
+ s << cipher.final
59
+ return s
60
+ end
61
+
62
+ private
63
+
64
+ def create_cipher
65
+ cipher_type = self.class.const_get("CIPHER_TYPE")
66
+ return OpenSSL::Cipher::Cipher.new(cipher_type)
67
+ end
68
+
69
+ def parse_options(cipher, options)
70
+ if options.key?(:passphrase)
71
+ cipher.pkcs5_keyivgen(options[:passphrase])
72
+ elsif options.key?(:key)
73
+ cipher.key_len = options[:key].length
74
+ cipher.key = options[:key].key
75
+ if options.key?(:iv)
76
+ cipher.iv = options[:iv]
77
+ end
78
+ else
79
+ raise ArgumentError, "too few options: :passphrase or :key"
80
+ end
81
+ if options.key?(:padding)
82
+ cipher.padding = options[:padding]
83
+ end
84
+ end
85
+ end
86
+
87
+ # 秘密鍵方式の一つ AES を用いて暗号化処理を行うクラスを表現する。
88
+ class Aes256CbcCipher < Cipher
89
+ cipher_type "AES-256-CBC"
90
+ end
91
+
92
+ # 秘密鍵方式の一つ Blowfish を用いて暗号化処理を行うクラスを表現する。
93
+ class BfCbcCipher < Cipher
94
+ cipher_type "BF-CBC"
95
+ end
96
+
97
+ # Cipher クラスと同じインタフェースを持つが、暗号化しないクラスを表現
98
+ # する。
99
+ class PlainCipher < Cipher
100
+ cipher_type "plain"
101
+
102
+ def encrypt(io)
103
+ return io.read
104
+ end
105
+
106
+ def decrypt(io)
107
+ return io.read
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,41 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require "fileutils"
4
+
5
+ module S7n
6
+ # 設定を表現する。
7
+ class Configuration
8
+ # デフォルトの秘密鍵暗号方式。
9
+ DEFAULT_CIPHER_TYPE = "AES-256-CBC"
10
+
11
+ # World オブジェクト。
12
+ attr_accessor :world
13
+
14
+ # 秘密鍵暗号方式。
15
+ attr_accessor :cipher_type
16
+
17
+ def initialize(world)
18
+ @world = world
19
+ @cipher_type = DEFAULT_CIPHER_TYPE
20
+ end
21
+
22
+ # 設定をファイルに書き出す。
23
+ def save(path)
24
+ hash = {
25
+ "cipher_type" => @cipher_type,
26
+ }
27
+ FileUtils.touch(path)
28
+ File.open(path, "wb") do |f|
29
+ f.write(hash.to_yaml)
30
+ end
31
+ end
32
+
33
+ # 設定をファイルから読み込む。
34
+ def load(path)
35
+ File.open(path, "rb") do |f|
36
+ hash = YAML.load(f.read)
37
+ @cipher_type = hash["cipher_type"]
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,106 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require "gettext"
4
+
5
+ module S7n
6
+ # 機密情報を表現する。
7
+ class Entry
8
+ include GetText
9
+
10
+ # タグ名の配列。
11
+ attr_reader :tags
12
+
13
+ # 属性の配列。
14
+ attr_reader :attributes
15
+
16
+ def initialize
17
+ @attributes = []
18
+ attr = NumericAttribute.new("name" => "id", "editabled" => false,
19
+ "protected" => true)
20
+ @attributes.push(attr)
21
+ attr = TextAttribute.new("name" => "name", "protected" => true)
22
+ @attributes.push(attr)
23
+ attr = NumericAttribute.new("name" => "rate", "protected" => true,
24
+ "value" => 1)
25
+ @attributes.push(attr)
26
+
27
+ @tags = []
28
+ end
29
+
30
+ # ID を取得する。
31
+ def id
32
+ return get_attribute("id").value
33
+ end
34
+
35
+ # ID を設定する。
36
+ def id=(val)
37
+ get_attribute("id").value = val
38
+ end
39
+
40
+ # name で指定した名前の属性を取得する。
41
+ # 存在しない場合、nil を返す。
42
+ def get_attribute(name)
43
+ return @attributes.find { |attr| attr.name == name }
44
+ end
45
+
46
+ # attributes で指定した属性を追加する。
47
+ # 名前が同じ属性が存在する場合、値を書き換える。
48
+ # 名前が同じ属性が存在し、なおかつ型が異なる場合、ApplicationError
49
+ # 例外を発生させる。
50
+ def add_attributes(*attributes)
51
+ [attributes].flatten.each do |attribute|
52
+ attr = @attributes.find { |a|
53
+ a.name == attribute.name
54
+ }
55
+ if attr
56
+ if attr.class == attribute.class
57
+ attr.value = attribute.value
58
+ else
59
+ raise ApplicationError, _("not same attribute type: name=<%s> expected=<%s> actual=<%s>") % [attr.name, attr.class, attribute.class]
60
+ end
61
+ else
62
+ @attributes.push(attribute)
63
+ end
64
+ end
65
+ end
66
+
67
+ # 全ての属性の複製を取得する。
68
+ def duplicate_attributes(exclude_uneditable = true)
69
+ if exclude_uneditable
70
+ attrs = @attributes
71
+ else
72
+ attrs = @attributes.select { |attr|
73
+ attr.editable?
74
+ }
75
+ end
76
+ return attrs.collect { |attr|
77
+ a = attr.dup
78
+ begin
79
+ a.value = attr.value.dup
80
+ rescue TypeError
81
+ end
82
+ a
83
+ }
84
+ end
85
+
86
+ def method_missing(symbol, *args)
87
+ attr_name = symbol.to_s
88
+ if attr_name[-1] == "="
89
+ attr_name = attr_name[0..-2]
90
+ setter = true
91
+ end
92
+ attr = @attributes.find { |a|
93
+ a.name == attr_name
94
+ }
95
+ if attr
96
+ if setter
97
+ attr.value = args.first
98
+ else
99
+ return attr.value
100
+ end
101
+ else
102
+ super
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,44 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require "s7n/entry"
4
+
5
+ module S7n
6
+ # 機密情報の集合を表現する。
7
+ class EntryCollection
8
+ # 機密情報の配列。
9
+ attr_reader :entries
10
+
11
+ def initialize
12
+ @entries = []
13
+ @max_id = 0
14
+ end
15
+
16
+ # 機密情報を追加する。
17
+ def add_entries(*entries)
18
+ [entries].flatten.each do |entry|
19
+ @max_id += 1
20
+ entry.id = @max_id
21
+ @entries.push(entry)
22
+ end
23
+ end
24
+
25
+ # 機密情報を削除し、削除した機密情報を返す。
26
+ def delete_entries(*entries)
27
+ entries = [entries].flatten
28
+ return @entries.delete_if { |entry|
29
+ entries.include?(entry)
30
+ }
31
+ end
32
+
33
+ # other で指定した EntryCollection オブジェクトの entries をマージ
34
+ # する。
35
+ def merge(other)
36
+ add_entries(other.entries)
37
+ end
38
+
39
+ # 指定した id にマッチする Entry オブジェクトを entries から探す。
40
+ def find(id)
41
+ return @entries.find { |entry| entry.id == id }
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,10 @@
1
+ module S7n
2
+ # 機密情報の新規作成時に利用するテンプレートを表現する。
3
+ class EntryTemplate
4
+ # 名前。
5
+ attr_accessor :name
6
+
7
+ # 属性の配列。
8
+ attr_accessor :attributes
9
+ end
10
+ end
@@ -0,0 +1,116 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require "gettext"
4
+
5
+ module S7n
6
+ # s7nで定義する例外クラスの親クラス。
7
+ class ApplicationError < StandardError
8
+ include GetText
9
+ end
10
+
11
+ # 不正なパスフレーズを表現する例外クラス。
12
+ class InvalidPassphrase < ApplicationError
13
+ def to_s
14
+ return _("Invlaid passphrase.")
15
+ end
16
+ end
17
+
18
+ # 不正なパスを表現する例外クラス。
19
+ class InvalidPath < ApplicationError
20
+ def initialize(path)
21
+ @path = path
22
+ end
23
+
24
+ def to_s
25
+ return _("Invalid path: path=<%s>") % @path
26
+ end
27
+ end
28
+
29
+ # ファイルやディレクトリが存在しないことを表現する例外クラス。
30
+ class NotExist < ApplicationError
31
+ def initialize(path)
32
+ @path = path
33
+ end
34
+
35
+ def to_s
36
+ return _("Does not exit: path=<%s>") % @path
37
+ end
38
+ end
39
+
40
+ # 機密情報が存在しないことを表現する例外クラス。
41
+ class NoSuchEntry < ApplicationError
42
+ def initialize(options = {})
43
+ @options = options
44
+ end
45
+
46
+ def to_s
47
+ if !@options.empty?
48
+ s = ""
49
+ @options.each do |key, value|
50
+ s << " #{key}=<#{value}>"
51
+ end
52
+ end
53
+ if s.empty?
54
+ return _("No such entry.")
55
+ else
56
+ return _("No such entry:%s") % s
57
+ end
58
+ end
59
+ end
60
+
61
+ # 機密情報の属性が存在しないことを表現する例外クラス。
62
+ class NoSuchAttribute < ApplicationError
63
+ def initialize(options = {})
64
+ @options = options
65
+ end
66
+
67
+ def to_s
68
+ if !@options.empty?
69
+ s = ""
70
+ @options.each do |key, value|
71
+ s << " #{key}=<#{value}>"
72
+ end
73
+ end
74
+ if s.empty?
75
+ return _("No such attribute.")
76
+ else
77
+ return _("No such attribute:%s") % s
78
+ end
79
+ end
80
+ end
81
+
82
+ # データ長が不正であることを表現する例外クラス。
83
+ class DataLengthError < ApplicationError
84
+ def initialize(actual, needed)
85
+ @actual = actual
86
+ @needed = needed
87
+ end
88
+
89
+ def to_s
90
+ return _("Invalid data length: actual=<%s> needed=<%s>") % [@actual, @needed]
91
+ end
92
+ end
93
+
94
+ # キャンセルを表現する例外クラス。
95
+ class Canceled < ApplicationError
96
+ MESSAGE = _("Canceled.")
97
+
98
+ def to_s
99
+ return MESSAGE
100
+ end
101
+ end
102
+
103
+ # OS のコマンドの実行に失敗したことを表現する例外クラス。
104
+ class CommandFailed < ApplicationError
105
+ # command:: 失敗したコマンド
106
+ # status:: コマンドの終了ステータス
107
+ def initialize(command, status)
108
+ @command = command
109
+ @status = status
110
+ end
111
+
112
+ def to_s
113
+ return _("Failed the command: command=<%s> status=<%d>") % [@command, @status]
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,77 @@
1
+ require "s7n/exception"
2
+
3
+ module S7n
4
+ class File
5
+ # パスワードコレクションのファイルのバージョン。
6
+ VERSION = "1.1.0"
7
+
8
+ # パスワードコレクションの先頭記述するマジック文字列。
9
+ MAGIC = "GPassFile version #{VERSION}"
10
+
11
+ # Cipherで使用するIV(Initial Vector)
12
+ IV = [5, 23, 1, 123, 12, 3, 54, 94].pack("C8")
13
+
14
+ # パスワードコレクションを格納するファイルを作成する。
15
+ # +path+:: 作成するファイルのパスを文字列で指定する。
16
+ # +passphrase+:: ファイルを暗号化するマスターパスフレーズを
17
+ # 文字列で指定する。
18
+ #
19
+ # すでにファイルが存在した場合、例外を発生させる。
20
+ def self.create(path, passphrase)
21
+ path = ::File.expand_path(path)
22
+ if ::File.exist?(path)
23
+ raise S7::Exception, "already exist file or directory: #{path}"
24
+ end
25
+ ::File.open(path, "w") do |f|
26
+ key = S7::Key.create_instance("SHA-1", passphrase)
27
+ cipher =
28
+ S7::Cipher.create_instance("BF-CBC", :key => key, :iv => S7::File::IV)
29
+ magic = cipher.encrypt(StringIO.new(S7::File::MAGIC))
30
+ f.write(magic)
31
+ end
32
+ end
33
+
34
+ def self.open(path, passphrase)
35
+ path = ::File.expand_path(path)
36
+ if !::File.exist?(path)
37
+ raise S7::Exception, "no such file or directory: #{path}"
38
+ end
39
+ if !block_given?
40
+ raise ArgumentError, "no block given"
41
+ end
42
+ begin
43
+ f = self.new(path, passphrase)
44
+ yield(f)
45
+ ensure
46
+ f.close
47
+ end
48
+ end
49
+
50
+ # パスワードコレクションのパス。
51
+ attr_accessor :path
52
+
53
+ # パスフレーズから生成したCipher用のキー。
54
+ attr_accessor :key
55
+
56
+ # キーとIVから生成したCipher。
57
+ attr_accessor :cipher
58
+
59
+ # 操作対象のファイル。
60
+ attr_accessor :file
61
+
62
+ def initialize(path, passphrase)
63
+ self.path = ::File.expand_path(path)
64
+ self.key = S7::Key.create_instance("SHA-1", passphrase)
65
+ self.cipher = S7::Cipher.create_instance("BF-CBC", :key => key,
66
+ :iv => S7::File::IV)
67
+ self.file = nil
68
+ end
69
+
70
+ # ファイルを閉じる。
71
+ def close
72
+ if self.file
73
+ self.file.close
74
+ end
75
+ end
76
+ end
77
+ end