rcredstash 0.2.0 → 0.3.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
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