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.
- data/.gitignore +7 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +28 -0
- data/LICENCE +24 -0
- data/README.rdoc +54 -0
- data/Rakefile +67 -0
- data/bin/s7ncli +23 -0
- data/lib/s7n.rb +12 -0
- data/lib/s7n/action.rb +7 -0
- data/lib/s7n/attribute.rb +226 -0
- data/lib/s7n/cipher.rb +110 -0
- data/lib/s7n/configuration.rb +41 -0
- data/lib/s7n/entry.rb +106 -0
- data/lib/s7n/entry_collection.rb +44 -0
- data/lib/s7n/entry_template.rb +10 -0
- data/lib/s7n/exception.rb +116 -0
- data/lib/s7n/file.rb +77 -0
- data/lib/s7n/gpass_file.rb +203 -0
- data/lib/s7n/key.rb +83 -0
- data/lib/s7n/message_catalog.rb +5 -0
- data/lib/s7n/s7n_file.rb +47 -0
- data/lib/s7n/s7ncli.rb +226 -0
- data/lib/s7n/s7ncli/attribute_command.rb +83 -0
- data/lib/s7n/s7ncli/command.rb +215 -0
- data/lib/s7n/s7ncli/entry_collection_command.rb +63 -0
- data/lib/s7n/s7ncli/entry_command.rb +728 -0
- data/lib/s7n/s7ncli/option.rb +101 -0
- data/lib/s7n/secret_generator.rb +30 -0
- data/lib/s7n/undo_stack.rb +7 -0
- data/lib/s7n/unicode_data.rb +29 -0
- data/lib/s7n/utils.rb +37 -0
- data/lib/s7n/version.rb +3 -0
- data/lib/s7n/world.rb +158 -0
- data/po/ja/s7n.po +533 -0
- data/po/s7n.pot +533 -0
- data/s7n.gemspec +30 -0
- data/test/s7n/attribute_test.rb +50 -0
- data/test/s7n/gpass_file_test.rb +169 -0
- data/test/s7n/gpass_file_test/passwords.gps.empty +1 -0
- data/test/s7n/gpass_file_test/passwords.gps.one_entry +2 -0
- data/test/s7n/gpass_file_test/passwords.gps.three_entries +3 -0
- data/test/s7n/gpass_file_test/passwords.gps.with_folders +0 -0
- data/test/s7n/secret_generator_test.rb +29 -0
- data/test/s7n/unicode_data_test.rb +28 -0
- data/test/s7n/world_test.rb +35 -0
- data/test/test_helper.rb +11 -0
- metadata +153 -0
data/lib/s7n/cipher.rb
ADDED
@@ -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
|
data/lib/s7n/entry.rb
ADDED
@@ -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,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
|
data/lib/s7n/file.rb
ADDED
@@ -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
|