rcredstash 0.2.0 → 0.3.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
2
  SHA1:
3
- metadata.gz: 54924cbb3ef0b7e9e664afc6fe17bf2ed7f8b083
4
- data.tar.gz: d97592afae4bb20e1bc0550275ae35866ab35c46
3
+ metadata.gz: a910b2a8a3b77bc116c1466ce17a04bee4e0f54c
4
+ data.tar.gz: 1b6b12222a108f089ff5759e646b9381626c1b2f
5
5
  SHA512:
6
- metadata.gz: b07368c741370d224b0027fecf4dddd63d9dcf87785f0ff3c2eb5e2d9c819da7cfdc6b8c4f6178252379816f751e0d6a0022c091d1d005d1b4d911ada29c4bcd
7
- data.tar.gz: 259753ebbadb4e6e6c0c49819e68b5f78b250e1074b77dd459cf39788f6882a83e513cf95ace9ff9b764f338a848c43fa2a3b94e7b18663aa0a58068b803d45c
6
+ metadata.gz: 4e72395c9949808360df91414c3c0be3cf8d97a7addc8492ac72959a9fa4a7400afb328ac7d17009378e466ad2f86e18839a5f8d5de9c5ac3a9e307dbb676d1e
7
+ data.tar.gz: 0a0cd2c952afeabdfcefa06719aa0bde40f416b0032b245c2252e319464b8cfc7a13836ca3719384916083e00732ee1fa9d640f617cad68a5aba7a00a5f7b0e9
data/exe/rcredstash CHANGED
File without changes
@@ -0,0 +1,35 @@
1
+ require 'openssl'
2
+
3
+ class CredStash::Cipher
4
+ def initialize(key)
5
+ @key = key
6
+ end
7
+
8
+ def encrypt(value)
9
+ run(mode: :encrypt, value: value)
10
+ end
11
+
12
+ def decrypt(value)
13
+ run(mode: :decrypt, value: value).force_encoding("UTF-8")
14
+ end
15
+
16
+ private
17
+
18
+ def run(mode:, value:)
19
+ cipher = OpenSSL::Cipher::AES.new(256, "CTR")
20
+
21
+ case mode
22
+ when :encrypt
23
+ cipher.encrypt
24
+ when :decrypt
25
+ cipher.decrypt
26
+ else
27
+ raise ArgumentError, "Unknown mode: #{mode}"
28
+ end
29
+
30
+ cipher.key = @key
31
+ # FIXME It is better to generate and store initial counter
32
+ cipher.iv = %w(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1).map(&:hex).pack('C' * 16)
33
+ cipher.update(value) + cipher.final
34
+ end
35
+ end
@@ -0,0 +1,39 @@
1
+ class CredStash::CipherKey
2
+ attr_reader :data_key, :hmac_key, :wrapped_key
3
+
4
+ def self.generate(client: Aws::KMS::Client.new)
5
+ res = client.generate_data_key(key_id: 'alias/credstash', number_of_bytes: 64)
6
+ new(
7
+ data_key: res.plaintext[0...32],
8
+ hmac_key: res.plaintext[32..-1],
9
+ wrapped_key: res.ciphertext_blob
10
+ )
11
+ end
12
+
13
+ def self.decrypt(wrapped_key, client: Aws::KMS::Client.new)
14
+ res = client.decrypt(ciphertext_blob: wrapped_key)
15
+ new(
16
+ data_key: res.plaintext[0...32],
17
+ hmac_key: res.plaintext[32..-1],
18
+ wrapped_key: wrapped_key
19
+ )
20
+ end
21
+
22
+ def initialize(data_key:, hmac_key:, wrapped_key:)
23
+ @data_key = data_key
24
+ @hmac_key = hmac_key
25
+ @wrapped_key = wrapped_key
26
+ end
27
+
28
+ def hmac(message)
29
+ OpenSSL::HMAC.hexdigest("SHA256", hmac_key, message)
30
+ end
31
+
32
+ def encrypt(message)
33
+ CredStash::Cipher.new(data_key).encrypt(message)
34
+ end
35
+
36
+ def decrypt(message)
37
+ CredStash::Cipher.new(data_key).decrypt(message)
38
+ end
39
+ end
@@ -0,0 +1,5 @@
1
+ module CredStash
2
+ class Error < StandardError; end
3
+
4
+ class ItemNotFound < Error; end
5
+ end
@@ -0,0 +1,110 @@
1
+ class CredStash::Repository
2
+ class Item
3
+ attr_reader :key, :contents, :name, :version, :hmac
4
+
5
+ def initialize(key: nil, contents: nil, name: nil, version: nil, hmac: nil)
6
+ @key = key
7
+ @contents = contents
8
+ @name = name
9
+ @version = version
10
+ @hmac = hmac
11
+ end
12
+ end
13
+
14
+ class DynamoDB
15
+ def initialize(client: nil)
16
+ @client = client || Aws::DynamoDB::Client.new
17
+ end
18
+
19
+ def get(name)
20
+ select(name, limit: 1).first.tap do |item|
21
+ unless item
22
+ raise CredStash::ItemNotFound, "#{name} is not found"
23
+ end
24
+ end
25
+ end
26
+
27
+ def select(name, pluck: nil, limit: nil)
28
+ params = {
29
+ table_name: 'credential-store',
30
+ consistent_read: true,
31
+ key_condition_expression: "#name = :name",
32
+ expression_attribute_names: { "#name" => "name"},
33
+ expression_attribute_values: { ":name" => name }
34
+ }
35
+
36
+ if pluck
37
+ params[:projection_expression] = pluck
38
+ end
39
+
40
+ if limit
41
+ params[:limit] = limit
42
+ params[:scan_index_forward] = false
43
+ end
44
+
45
+ @client.query(params).items.map do |item|
46
+ Item.new(
47
+ key: item["key"],
48
+ contents: item["contents"],
49
+ name: item["name"],
50
+ version: item["version"]
51
+ )
52
+ end
53
+ end
54
+
55
+ def put(item)
56
+ @client.put_item(
57
+ table_name: 'credential-store',
58
+ item: {
59
+ name: item.name,
60
+ version: item.version,
61
+ key: item.key,
62
+ contents: item.contents,
63
+ hmac: item.hmac
64
+ },
65
+ condition_expression: "attribute_not_exists(#name)",
66
+ expression_attribute_names: { "#name" => "name" },
67
+ )
68
+ end
69
+
70
+ def list
71
+ @client.scan(
72
+ table_name: 'credential-store',
73
+ projection_expression: '#name, version',
74
+ expression_attribute_names: { "#name" => "name" },
75
+ ).items.map do |item|
76
+ Item.new(name: item['name'], version: item['version'])
77
+ end
78
+ end
79
+
80
+ def delete(item)
81
+ @client.delete_item(
82
+ table_name: 'credential-store',
83
+ key: {
84
+ name: item.name,
85
+ version: item.version
86
+ }
87
+ )
88
+ end
89
+ end
90
+
91
+ def self.default_storage
92
+ DynamoDB.new
93
+ end
94
+
95
+ def initialize(storage: CredStash::Repository.default_storage)
96
+ @storage = storage
97
+ end
98
+
99
+ def get(name)
100
+ @storage.get(name)
101
+ end
102
+
103
+ def put(item)
104
+ @storage.put(item)
105
+ end
106
+
107
+ def select(name, pluck: nil, limit: nil)
108
+ @storage.select(name, pluck: pluck, limit: limit)
109
+ end
110
+ end
@@ -0,0 +1,67 @@
1
+ class CredStash::Secret
2
+ attr_reader :name, :value, :key, :encrypted_value, :hmac
3
+
4
+ def initialize(name:, value: nil, key: nil, encrypted_value: nil, hmac: nil)
5
+ @name = name
6
+ @value = value
7
+ @key = key
8
+ @encrypted_value = encrypted_value
9
+ @hmac = hmac
10
+ end
11
+
12
+ def encrypt!
13
+ @key = CredStash::CipherKey.generate
14
+ @encrypted_value = @key.encrypt(@value)
15
+ @hmac = @key.hmac(@encrypted_value)
16
+ end
17
+
18
+ def save
19
+ self.class.repository.put(to_item)
20
+ end
21
+
22
+ def falsified?
23
+ @key.hmac(@encrypted_value) == @hmac
24
+ end
25
+
26
+ def decrypted_value
27
+ @key.decrypt(@encrypted_value)
28
+ end
29
+
30
+ class << self
31
+ def find(name)
32
+ item = repository.get(name)
33
+ new(
34
+ name: name,
35
+ key: CredStash::CipherKey.decrypt(Base64.decode64(item.key)),
36
+ encrypted_value: Base64.decode64(item.contents),
37
+ hmac: item.hmac
38
+ )
39
+ end
40
+
41
+ def repository
42
+ CredStash::Repository.new
43
+ end
44
+ end
45
+
46
+
47
+ private
48
+
49
+ def to_item
50
+ CredStash::Repository::Item.new(
51
+ name: name,
52
+ version: "%019d" % (current_version + 1),
53
+ key: Base64.encode64(key.wrapped_key),
54
+ contents: Base64.encode64(encrypted_value),
55
+ hmac: hmac
56
+ )
57
+ end
58
+
59
+ def current_version
60
+ item = CredStash::Repository.new.select(name, pluck: 'version', limit: 1).first
61
+ if item
62
+ item.version.to_i
63
+ else
64
+ 0
65
+ end
66
+ end
67
+ end
@@ -1,3 +1,3 @@
1
1
  module CredStash
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
data/lib/cred_stash.rb CHANGED
@@ -3,125 +3,50 @@ require 'aws-sdk'
3
3
  module CredStash
4
4
  class << self
5
5
  def get(name)
6
- dynamodb = Aws::DynamoDB::Client.new
7
- res = dynamodb.query(
8
- table_name: 'credential-store',
9
- limit: 1,
10
- consistent_read: true,
11
- scan_index_forward: false,
12
- key_condition_expression: "#name = :name",
13
- expression_attribute_names: { "#name" => "name"},
14
- expression_attribute_values: { ":name" => name }
15
- )
16
- material = res.items.first
17
- data = Base64.decode64(material["key"])
18
- contents = Base64.decode64(material["contents"])
6
+ secret = Secret.find(name)
19
7
 
20
- kms = Aws::KMS::Client.new
21
- kms_res = kms.decrypt(ciphertext_blob: data)
22
-
23
- key = kms_res.plaintext[0..32]
24
- hmackey = kms_res.plaintext[32..-1]
25
-
26
- unless OpenSSL::HMAC.hexdigest("sha256", hmackey, contents) == material["hmac"]
27
- raise "invalid"
8
+ if secret.falsified?
9
+ raise "Invalid secret. #{name} has falsified"
28
10
  end
29
11
 
30
- cipher = OpenSSL::Cipher::AES.new(256, "CTR")
31
- cipher.decrypt
32
- cipher.key = key
33
- # FIXME It is better to generate and store initial counter
34
- cipher.iv = %w(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1).map(&:hex).pack('C' * 16)
35
- value = cipher.update(contents) + cipher.final
36
- value.force_encoding("UTF-8")
12
+ secret.decrypted_value
13
+
14
+ rescue CredStash::ItemNotFound
15
+ nil
37
16
  end
38
17
 
39
18
  def put(name, value)
40
- kms = Aws::KMS::Client.new
41
- kms_res = kms.generate_data_key(key_id: 'alias/credstash', number_of_bytes: 64)
42
- data_key = kms_res.plaintext[0..32]
43
- hmac_key = kms_res.plaintext[32..-1]
44
- wrapped_key = kms_res.ciphertext_blob
45
-
46
- cipher = OpenSSL::Cipher::AES.new(256, "CTR")
47
- cipher.encrypt
48
- cipher.key = data_key
49
- # FIXME It is better to generate and store initial counter
50
- cipher.iv = %w(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1).map(&:hex).pack('C' * 16)
51
- contents = cipher.update(value) + cipher.final
52
-
53
- hmac = OpenSSL::HMAC.hexdigest("sha256", hmac_key, contents)
54
-
55
- version = get_highest_version(name) + 1
56
-
57
- dynamodb = Aws::DynamoDB::Client.new
58
- dynamodb.put_item(
59
- table_name: 'credential-store',
60
- item: {
61
- name: name,
62
- version: "%019d" % version,
63
- key: Base64.encode64(wrapped_key),
64
- contents: Base64.encode64(contents),
65
- hmac: hmac
66
- },
67
- condition_expression: "attribute_not_exists(#name)",
68
- expression_attribute_names: { "#name" => "name" },
69
- )
19
+ secret = Secret.new(name: name, value: value)
20
+ secret.encrypt!
21
+ secret.save
70
22
  end
71
23
 
72
24
  def list
73
- dynamodb = Aws::DynamoDB::Client.new
74
- res = dynamodb.scan(
75
- table_name: 'credential-store',
76
- projection_expression: '#name, version',
77
- expression_attribute_names: { "#name" => "name" },
78
- )
79
- res.items.inject({}) {|h, i| h[i['name']] = i['version']; h }
25
+ Repository.new.list.inject({}) {|h, item| h[item.name] = item.version; h }
80
26
  end
81
27
 
82
28
  def delete(name)
83
- dynamodb = Aws::DynamoDB::Client.new
84
- res = dynamodb.query(
85
- table_name: 'credential-store',
86
- consistent_read: true,
87
- key_condition_expression: "#name = :name",
88
- expression_attribute_names: { "#name" => "name"},
89
- expression_attribute_values: { ":name" => name }
90
- )
91
29
  # TODO needs delete target version option
92
-
93
- item = res.items.first
94
- dynamodb.delete_item(
95
- table_name: 'credential-store',
96
- key: {
97
- name: item['name'],
98
- version: item['version'],
99
- }
100
- )
30
+ repository = Repository.new
31
+ item = repository.select(name).first
32
+ repository.delete(item)
101
33
  end
102
34
 
103
35
  private
104
36
 
105
37
  def get_highest_version(name)
106
- dynamodb = Aws::DynamoDB::Client.new
107
- res = dynamodb.query(
108
- table_name: 'credential-store',
109
- limit: 1,
110
- consistent_read: true,
111
- scan_index_forward: false,
112
- key_condition_expression: "#name = :name",
113
- expression_attribute_names: { "#name" => "name"},
114
- expression_attribute_values: { ":name" => name },
115
- projection_expression: 'version',
116
- )
117
-
118
- item = res.items.first
119
-
38
+ item = Repository.new.select(name, pluck: 'version', limit: 1).first
120
39
  if item
121
- item['version'].to_i
40
+ item.version.to_i
122
41
  else
123
42
  0
124
43
  end
125
44
  end
126
45
  end
127
46
  end
47
+
48
+ require 'cred_stash/cipher_key'
49
+ require 'cred_stash/cipher'
50
+ require 'cred_stash/error'
51
+ require 'cred_stash/repository'
52
+ require 'cred_stash/secret'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rcredstash
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - adorechic
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-06-16 00:00:00.000000000 Z
11
+ date: 2016-06-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk
@@ -84,9 +84,13 @@ files:
84
84
  - bin/setup
85
85
  - exe/rcredstash
86
86
  - lib/cred_stash.rb
87
+ - lib/cred_stash/cipher.rb
88
+ - lib/cred_stash/cipher_key.rb
89
+ - lib/cred_stash/error.rb
90
+ - lib/cred_stash/repository.rb
91
+ - lib/cred_stash/secret.rb
87
92
  - lib/cred_stash/version.rb
88
93
  - lib/rcredstash.rb
89
- - lib/rcredstash/version.rb
90
94
  - rcredstash.gemspec
91
95
  homepage: https://github.com/adorechic/rcredstash
92
96
  licenses:
@@ -113,4 +117,3 @@ signing_key:
113
117
  specification_version: 4
114
118
  summary: A Ruby port of CredStash
115
119
  test_files: []
116
- has_rdoc:
@@ -1,3 +0,0 @@
1
- module Rcredstash
2
- VERSION = "0.1.0"
3
- end