porky_lib 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.
- checksums.yaml +7 -0
- data/.circleci/config.yml +58 -0
- data/.github/pull_request_template.md +19 -0
- data/.gitignore +16 -0
- data/.rspec +1 -0
- data/.rubocop.yml +46 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +109 -0
- data/README.md +103 -0
- data/Rakefile +8 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/porky_lib/aws/kms/client.rb +72 -0
- data/lib/porky_lib/config.rb +44 -0
- data/lib/porky_lib/symmetric.rb +98 -0
- data/lib/porky_lib/version.rb +5 -0
- data/lib/porky_lib.rb +7 -0
- data/porky_lib.gemspec +41 -0
- metadata +298 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: cc40805a9b711ba76325becb50acc00ed3bedb28
|
|
4
|
+
data.tar.gz: 728228c4441a2362bf8dbee387db06db13e1adb3
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: dbb89bca0d5f79bec39b326e148e589a499c95ae5a618755bf592bce71c53949161a1a2f06699e30a2ca33a7f439212f17e2a2453c039b3fb067602ec5c2ace2
|
|
7
|
+
data.tar.gz: aedfd5f8356dbf49994518e6ec2dd99ac7372c01183d0985e34084a68cbf62e7f446f3685fe8c927895697cc0864e0ae9ec18ef29d59375366e01c2f943353cc
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
jobs:
|
|
3
|
+
build:
|
|
4
|
+
parallelism: 2
|
|
5
|
+
working_directory: ~/porky_lib
|
|
6
|
+
environment:
|
|
7
|
+
- CODECOV_TOKEN: 4d03ca51-5054-4858-87c7-b37c376ffb6a
|
|
8
|
+
docker:
|
|
9
|
+
- image: zetatango/circle-ruby-2.3.7-openssl
|
|
10
|
+
|
|
11
|
+
steps:
|
|
12
|
+
- checkout
|
|
13
|
+
|
|
14
|
+
- restore_cache:
|
|
15
|
+
name: Restore bundle cache
|
|
16
|
+
keys:
|
|
17
|
+
- porky_lib-bundle-{{ checksum "Gemfile.lock" }}
|
|
18
|
+
- porky_lib-bundle-
|
|
19
|
+
|
|
20
|
+
- run:
|
|
21
|
+
name: Bundle Install
|
|
22
|
+
command: bundle install
|
|
23
|
+
|
|
24
|
+
- save_cache:
|
|
25
|
+
name: Store bundle cache
|
|
26
|
+
key: porky_lib-bundle-{{ checksum "Gemfile.lock" }}
|
|
27
|
+
paths:
|
|
28
|
+
- vendor/bundle
|
|
29
|
+
|
|
30
|
+
- run:
|
|
31
|
+
name: Run rubocop
|
|
32
|
+
command: |
|
|
33
|
+
bundle exec rubocop --out test_results/rubocop.txt --format fuubar --require rubocop-rspec --config .rubocop.yml
|
|
34
|
+
|
|
35
|
+
- run:
|
|
36
|
+
name: Run bundle-audit
|
|
37
|
+
command: |
|
|
38
|
+
bundle exec bundle-audit check --update
|
|
39
|
+
|
|
40
|
+
- run:
|
|
41
|
+
name: Run rspec tests
|
|
42
|
+
command: |
|
|
43
|
+
bundle exec rspec \
|
|
44
|
+
--profile 10 \
|
|
45
|
+
--format RspecJunitFormatter \
|
|
46
|
+
--out test_results/rspec.xml \
|
|
47
|
+
--format progress
|
|
48
|
+
|
|
49
|
+
- store_test_results:
|
|
50
|
+
path: test_results
|
|
51
|
+
|
|
52
|
+
- store_artifacts:
|
|
53
|
+
path: test_results
|
|
54
|
+
prefix: tests
|
|
55
|
+
|
|
56
|
+
- store_artifacts:
|
|
57
|
+
path: coverage
|
|
58
|
+
prefix: coverage
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
*Related Issue(s) or Task(s)*
|
|
2
|
+
|
|
3
|
+
List the issues or tasks that are related to this pull request.
|
|
4
|
+
|
|
5
|
+
*Why?*
|
|
6
|
+
|
|
7
|
+
Explain the high-level reason(s) why this change is required.
|
|
8
|
+
|
|
9
|
+
*How?*
|
|
10
|
+
|
|
11
|
+
Explain the development-level approach you are using in this solution.
|
|
12
|
+
|
|
13
|
+
*Risks*
|
|
14
|
+
|
|
15
|
+
Explain the risks that are involved with making this change, if any.
|
|
16
|
+
|
|
17
|
+
*Requested Reviewers*
|
|
18
|
+
|
|
19
|
+
List the developers that should review this pull request.
|
data/.gitignore
ADDED
data/.rspec
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
--require spec_helper
|
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
require: rubocop-rspec
|
|
2
|
+
|
|
3
|
+
AllCops:
|
|
4
|
+
Exclude:
|
|
5
|
+
- bin/*
|
|
6
|
+
- db/**/*.rb
|
|
7
|
+
- node_modules/**/*
|
|
8
|
+
- output/**/*
|
|
9
|
+
- vendor/**/*
|
|
10
|
+
TargetRubyVersion: 2.3.7
|
|
11
|
+
RSpec:
|
|
12
|
+
Patterns:
|
|
13
|
+
- _spec.rb
|
|
14
|
+
- "(?:^|/)spec/"
|
|
15
|
+
SpecTypes:
|
|
16
|
+
Feature: 'spec/features/**/*'
|
|
17
|
+
|
|
18
|
+
Documentation:
|
|
19
|
+
Enabled: false
|
|
20
|
+
|
|
21
|
+
Metrics/LineLength:
|
|
22
|
+
Max: 160
|
|
23
|
+
|
|
24
|
+
Metrics/ClassLength:
|
|
25
|
+
Enabled: false
|
|
26
|
+
|
|
27
|
+
Metrics/BlockLength:
|
|
28
|
+
Enabled: false
|
|
29
|
+
|
|
30
|
+
Metrics/AbcSize:
|
|
31
|
+
Max: 50
|
|
32
|
+
|
|
33
|
+
Metrics/MethodLength:
|
|
34
|
+
Max: 50
|
|
35
|
+
|
|
36
|
+
Style/StringLiterals:
|
|
37
|
+
Enabled: false
|
|
38
|
+
|
|
39
|
+
Style/ClassAndModuleChildren:
|
|
40
|
+
EnforcedStyle: compact
|
|
41
|
+
|
|
42
|
+
RSpec/MultipleExpectations:
|
|
43
|
+
Max: 5
|
|
44
|
+
|
|
45
|
+
RSpec/ExampleLength:
|
|
46
|
+
Max: 10
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
porky_lib (0.1.0)
|
|
5
|
+
aws-sdk-kms
|
|
6
|
+
msgpack
|
|
7
|
+
rbnacl-libsodium
|
|
8
|
+
|
|
9
|
+
GEM
|
|
10
|
+
remote: https://rubygems.org/
|
|
11
|
+
specs:
|
|
12
|
+
ast (2.4.0)
|
|
13
|
+
aws-eventstream (1.0.1)
|
|
14
|
+
aws-partitions (1.95.0)
|
|
15
|
+
aws-sdk-core (3.22.1)
|
|
16
|
+
aws-eventstream (~> 1.0)
|
|
17
|
+
aws-partitions (~> 1.0)
|
|
18
|
+
aws-sigv4 (~> 1.0)
|
|
19
|
+
jmespath (~> 1.0)
|
|
20
|
+
aws-sdk-kms (1.6.0)
|
|
21
|
+
aws-sdk-core (~> 3)
|
|
22
|
+
aws-sigv4 (~> 1.0)
|
|
23
|
+
aws-sigv4 (1.0.3)
|
|
24
|
+
bundler-audit (0.6.0)
|
|
25
|
+
bundler (~> 1.2)
|
|
26
|
+
thor (~> 0.18)
|
|
27
|
+
byebug (10.0.2)
|
|
28
|
+
codecov (0.1.10)
|
|
29
|
+
json
|
|
30
|
+
simplecov
|
|
31
|
+
url
|
|
32
|
+
diff-lcs (1.3)
|
|
33
|
+
docile (1.3.1)
|
|
34
|
+
ffi (1.9.25)
|
|
35
|
+
jaro_winkler (1.5.1)
|
|
36
|
+
jmespath (1.4.0)
|
|
37
|
+
json (2.1.0)
|
|
38
|
+
msgpack (1.2.4)
|
|
39
|
+
parallel (1.12.1)
|
|
40
|
+
parser (2.5.1.0)
|
|
41
|
+
ast (~> 2.4.0)
|
|
42
|
+
powerpack (0.1.2)
|
|
43
|
+
rainbow (3.0.0)
|
|
44
|
+
rake (12.3.1)
|
|
45
|
+
rbnacl (5.0.0)
|
|
46
|
+
ffi
|
|
47
|
+
rbnacl-libsodium (1.0.16)
|
|
48
|
+
rbnacl (>= 3.0.1)
|
|
49
|
+
rspec (3.7.0)
|
|
50
|
+
rspec-core (~> 3.7.0)
|
|
51
|
+
rspec-expectations (~> 3.7.0)
|
|
52
|
+
rspec-mocks (~> 3.7.0)
|
|
53
|
+
rspec-collection_matchers (1.1.3)
|
|
54
|
+
rspec-expectations (>= 2.99.0.beta1)
|
|
55
|
+
rspec-core (3.7.1)
|
|
56
|
+
rspec-support (~> 3.7.0)
|
|
57
|
+
rspec-expectations (3.7.0)
|
|
58
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
59
|
+
rspec-support (~> 3.7.0)
|
|
60
|
+
rspec-mocks (3.7.0)
|
|
61
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
62
|
+
rspec-support (~> 3.7.0)
|
|
63
|
+
rspec-support (3.7.1)
|
|
64
|
+
rspec_junit_formatter (0.4.1)
|
|
65
|
+
rspec-core (>= 2, < 4, != 2.12.0)
|
|
66
|
+
rubocop (0.57.2)
|
|
67
|
+
jaro_winkler (~> 1.5.1)
|
|
68
|
+
parallel (~> 1.10)
|
|
69
|
+
parser (>= 2.5)
|
|
70
|
+
powerpack (~> 0.1)
|
|
71
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
72
|
+
ruby-progressbar (~> 1.7)
|
|
73
|
+
unicode-display_width (~> 1.0, >= 1.0.1)
|
|
74
|
+
rubocop-rspec (1.27.0)
|
|
75
|
+
rubocop (>= 0.56.0)
|
|
76
|
+
rubocop_runner (2.1.0)
|
|
77
|
+
ruby-progressbar (1.9.0)
|
|
78
|
+
simplecov (0.16.1)
|
|
79
|
+
docile (~> 1.1)
|
|
80
|
+
json (>= 1.8, < 3)
|
|
81
|
+
simplecov-html (~> 0.10.0)
|
|
82
|
+
simplecov-html (0.10.2)
|
|
83
|
+
thor (0.20.0)
|
|
84
|
+
timecop (0.9.1)
|
|
85
|
+
unicode-display_width (1.4.0)
|
|
86
|
+
url (0.3.2)
|
|
87
|
+
|
|
88
|
+
PLATFORMS
|
|
89
|
+
ruby
|
|
90
|
+
|
|
91
|
+
DEPENDENCIES
|
|
92
|
+
bundler
|
|
93
|
+
bundler-audit
|
|
94
|
+
byebug
|
|
95
|
+
codecov
|
|
96
|
+
porky_lib!
|
|
97
|
+
rake
|
|
98
|
+
rspec
|
|
99
|
+
rspec-collection_matchers
|
|
100
|
+
rspec-mocks
|
|
101
|
+
rspec_junit_formatter
|
|
102
|
+
rubocop
|
|
103
|
+
rubocop-rspec
|
|
104
|
+
rubocop_runner
|
|
105
|
+
simplecov
|
|
106
|
+
timecop
|
|
107
|
+
|
|
108
|
+
BUNDLED WITH
|
|
109
|
+
1.16.1
|
data/README.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
[](https://circleci.com/gh/Zetatango/porky_lib) [](https://codecov.io/gh/Zetatango/porky_lib)
|
|
2
|
+
|
|
3
|
+
# PorkyLib
|
|
4
|
+
|
|
5
|
+
This gem is a cryptographic services library. PorkyLib uses AWS Key Management Service (KMS) for key management and RbNaCl for
|
|
6
|
+
performing cryptographic operations.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
Add this line to your application's Gemfile:
|
|
11
|
+
|
|
12
|
+
```ruby
|
|
13
|
+
gem 'porky_lib'
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
And then execute:
|
|
17
|
+
|
|
18
|
+
$ bundle install
|
|
19
|
+
|
|
20
|
+
Or install it yourself as:
|
|
21
|
+
|
|
22
|
+
$ gem install porky_lib
|
|
23
|
+
|
|
24
|
+
Inside of your Ruby program do:
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
require 'porky_lib'
|
|
28
|
+
```
|
|
29
|
+
... to pull it in as a dependency.
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
### Initialization
|
|
34
|
+
Something like the following should be included in an initializer in your Rails project:
|
|
35
|
+
```ruby
|
|
36
|
+
# Use PorkyLib's AWS KMS mock client except in production, for example
|
|
37
|
+
use_mock_client = !Rails.env.production?
|
|
38
|
+
PorkyLib::Config.configure(aws_region: ENV[AWS_REGION],
|
|
39
|
+
aws_key_id: ENV[AWS_KEY_ID],
|
|
40
|
+
aws_key_secret: ENV[AWS_KEY_SECRET],
|
|
41
|
+
aws_client_mock: use_mock_client)
|
|
42
|
+
PorkyLib::Config.initialize_aws
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Creating a New CMK
|
|
46
|
+
To create a new customer master key (CMK) within AWS:
|
|
47
|
+
```ruby
|
|
48
|
+
# Where tags is a list of key/value pairs (i.e [{ key1: 'value1' }])
|
|
49
|
+
# key_alias is an optional parameter, and if provided will create an alias with the provided value for the newly created key
|
|
50
|
+
# key_rotation_enabled is an optional parameter, and if true will enable automatic key rotation for the new created key. Default is true.
|
|
51
|
+
key_id = PorkyLib::Symmetric.instance.create_key(tags, key_alias, key_rotation_enabled)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Creating an Alias for an Existing CMK
|
|
55
|
+
To create a new alias for an existing customer master key (CMK) within AWS:
|
|
56
|
+
```ruby
|
|
57
|
+
# Where key_id is the AWS key ID or Amazon Resource Name (ARN)
|
|
58
|
+
# key_alias is the value of the alias to create
|
|
59
|
+
PorkyLib::Symmetric.instance.create_alias(key_id, key_alias)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Enabling Key Rotation for an Existing CMK
|
|
63
|
+
To create a new alias for an existing customer master key (CMK) within AWS:
|
|
64
|
+
```ruby
|
|
65
|
+
# Where key_id is the AWS key ID or Amazon Resource Name (ARN)
|
|
66
|
+
PorkyLib::Symmetric.instance.enable_key_rotation(key_id)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Encrypting Data
|
|
70
|
+
To encrypt data:
|
|
71
|
+
```ruby
|
|
72
|
+
# Where data is the data to encrypt
|
|
73
|
+
# cmk_key_id is the AWS key ID, Amazon Resource Name (ARN) or alias for the CMK to use to generate the data encryption key (DEK)
|
|
74
|
+
# encryption_context is an optional parameter to provide additional authentication data for encrypting the DEK. Default is nil.
|
|
75
|
+
[ciphertext_dek, ciphertext, nonce] = PorkyLib::Symmetric.instance.encrypt(data, cmk_key_id, encryption_context)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Decrypting Data
|
|
79
|
+
To decrypt data:
|
|
80
|
+
```ruby
|
|
81
|
+
# Where ciphertext_dek is the encrypted data encryption key (DEK)
|
|
82
|
+
# ciphertext is the encrypted data to be decrypted
|
|
83
|
+
# nonce is the nonce value associated with ciphertext
|
|
84
|
+
# encryption_context is an optional parameter to provide additional authentication data for decrypting the DEK. Default is nil. Note, this must match the value that was used to encrypt.
|
|
85
|
+
plaintext_data = PorkyLib::Symmetric.instance.decrypt(ciphertext_dek, ciphertext, nonce, encryption_context)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Development
|
|
89
|
+
|
|
90
|
+
Development on this project should occur on separate feature branches and pull requests should be submitted. When submitting a
|
|
91
|
+
pull request, the pull request comment template should be filled out as much as possible to ensure a quick review and increase
|
|
92
|
+
the likelihood of the pull request being accepted.
|
|
93
|
+
|
|
94
|
+
### Running Tests
|
|
95
|
+
|
|
96
|
+
```ruby
|
|
97
|
+
rspec # Without code coverage
|
|
98
|
+
COVERAGE=true rspec # with code coverage
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Contributing
|
|
102
|
+
|
|
103
|
+
Bug reports and pull requests are welcome on GitHub at [https://github.com/Zetatango/porky_lib](https://github.com/Zetatango/porky_lib)
|
data/Rakefile
ADDED
data/bin/console
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "porky_lib"
|
|
5
|
+
|
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
8
|
+
|
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
10
|
+
# require "pry"
|
|
11
|
+
# Pry.start
|
|
12
|
+
|
|
13
|
+
require "irb"
|
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'aws-sdk-kms'
|
|
4
|
+
require 'msgpack'
|
|
5
|
+
|
|
6
|
+
##
|
|
7
|
+
# This class is required for unit testing in order to mock response values from the AWS KMS SDK.
|
|
8
|
+
##
|
|
9
|
+
class Aws::KMS::Client
|
|
10
|
+
MOCK_ALIAS_NAME_ALREADY_EXISTS = 'alias/dup'
|
|
11
|
+
MOCK_INVALID_ALIAS_NAME = 'alias/aws'
|
|
12
|
+
MOCK_INVALID_TAG_VALUE = 'bad_value'
|
|
13
|
+
MOCK_NOT_FOUND_KEY_ID = 'bad_key'
|
|
14
|
+
MOCK_VALID_KEY_USAGE = 'AES_256'
|
|
15
|
+
PLAINTEXT_KEY_LENGTH = 32
|
|
16
|
+
|
|
17
|
+
def create_key(key_usage:, origin:, tags:)
|
|
18
|
+
raise Aws::KMS::Errors::TagException.new(nil, nil) if tags[0].value?(MOCK_INVALID_TAG_VALUE)
|
|
19
|
+
|
|
20
|
+
Aws::KMS::Types::CreateKeyResponse.new(
|
|
21
|
+
key_metadata: {
|
|
22
|
+
aws_account_id: '123',
|
|
23
|
+
creation_date: Time.now.utc.iso8601,
|
|
24
|
+
description: '',
|
|
25
|
+
enabled: true,
|
|
26
|
+
key_id: SecureRandom.uuid,
|
|
27
|
+
key_state: 'Enabled',
|
|
28
|
+
key_usage: key_usage,
|
|
29
|
+
origin: origin
|
|
30
|
+
}
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def enable_key_rotation(key_id:)
|
|
35
|
+
raise Aws::KMS::Errors::NotFoundException.new(nil, nil) if key_id.include?(MOCK_NOT_FOUND_KEY_ID)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def create_alias(target_key_id:, alias_name:)
|
|
39
|
+
raise Aws::KMS::Errors::InvalidAliasNameException.new(nil, nil) if alias_name == MOCK_INVALID_ALIAS_NAME
|
|
40
|
+
raise Aws::KMS::Errors::AlreadyExistsException.new(nil, nil) if alias_name == MOCK_ALIAS_NAME_ALREADY_EXISTS
|
|
41
|
+
raise Aws::KMS::Errors::NotFoundException.new(nil, nil) if target_key_id.include?(MOCK_NOT_FOUND_KEY_ID)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def generate_data_key(key_id:, key_spec:, encryption_context: nil)
|
|
45
|
+
raise Aws::KMS::Errors::InvalidKeyUsageException.new(nil, nil) unless key_spec == 'AES_256'
|
|
46
|
+
raise Aws::KMS::Errors::NotFoundException.new(nil, nil) if key_id.include?(MOCK_NOT_FOUND_KEY_ID)
|
|
47
|
+
|
|
48
|
+
plaintext = SecureRandom.random_bytes(PLAINTEXT_KEY_LENGTH)
|
|
49
|
+
Aws::KMS::Types::GenerateDataKeyResponse.new(
|
|
50
|
+
key_id: key_id,
|
|
51
|
+
plaintext: plaintext,
|
|
52
|
+
ciphertext_blob: [key_id, encryption_context, plaintext].to_msgpack.reverse
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def decrypt(ciphertext_blob:, encryption_context: nil)
|
|
57
|
+
key_id, decoded_context, plaintext = MessagePack.unpack(ciphertext_blob.reverse)
|
|
58
|
+
decoded_context = Hash[decoded_context.map { |k, v| [k.to_sym, v] }] if decoded_context
|
|
59
|
+
raise Aws::KMS::Errors::InvalidCiphertextException.new(nil, nil) unless decoded_context == encryption_context
|
|
60
|
+
|
|
61
|
+
Aws::KMS::Types::DecryptResponse.new(
|
|
62
|
+
key_id: key_id,
|
|
63
|
+
plaintext: plaintext
|
|
64
|
+
)
|
|
65
|
+
rescue MessagePack::MalformedFormatError
|
|
66
|
+
raise Aws::KMS::Errors::InvalidCiphertextException.new(nil, nil)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def inspect
|
|
70
|
+
'#<Aws::KMS::Client (mocked)>'
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class PorkyLib::Config
|
|
4
|
+
@aws_region = ''
|
|
5
|
+
@aws_key_id = ''
|
|
6
|
+
@aws_key_secret = ''
|
|
7
|
+
@aws_client_mock = false
|
|
8
|
+
|
|
9
|
+
@config = {
|
|
10
|
+
aws_region: @aws_region,
|
|
11
|
+
aws_key_id: @aws_key_id,
|
|
12
|
+
aws_key_secret: @aws_key_secret,
|
|
13
|
+
aws_client_mock: @aws_client_mock
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@allowed_config_keys = @config.keys
|
|
17
|
+
|
|
18
|
+
def self.configure(options = {})
|
|
19
|
+
options.each { |key, value| @config[key.to_sym] = value if @allowed_config_keys.include? key.to_sym }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.initialize_aws
|
|
23
|
+
Aws.config.update(
|
|
24
|
+
region: @config[:aws_region],
|
|
25
|
+
credentials: Aws::Credentials.new(
|
|
26
|
+
@config[:aws_key_id],
|
|
27
|
+
@config[:aws_key_secret]
|
|
28
|
+
)
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
class << self
|
|
33
|
+
attr_reader :config
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.logger
|
|
37
|
+
@logger ||= defined?(Rails) ? Rails.logger : Logger.new(STDOUT)
|
|
38
|
+
@logger
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
class << self
|
|
42
|
+
attr_writer :logger
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'aws-sdk-kms'
|
|
4
|
+
require 'rbnacl/libsodium'
|
|
5
|
+
require 'singleton'
|
|
6
|
+
|
|
7
|
+
class PorkyLib::Symmetric
|
|
8
|
+
include Singleton
|
|
9
|
+
|
|
10
|
+
CMK_KEY_ORIGIN = 'AWS_KMS'
|
|
11
|
+
CMK_KEY_USAGE = 'ENCRYPT_DECRYPT'
|
|
12
|
+
SYMMETRIC_KEY_SPEC = 'AES_256'
|
|
13
|
+
|
|
14
|
+
def client
|
|
15
|
+
require 'porky_lib/aws/kms/client' if PorkyLib::Config.config[:aws_client_mock]
|
|
16
|
+
@client ||= Aws::KMS::Client.new
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def create_key(tags, key_alias = nil, key_rotation_enabled = true)
|
|
20
|
+
PorkyLib::Config.logger.info("Creating a new master key")
|
|
21
|
+
resp = client.create_key(key_usage: CMK_KEY_USAGE, origin: CMK_KEY_ORIGIN, tags: tags)
|
|
22
|
+
key_id = resp.to_h[:key_metadata][:key_id]
|
|
23
|
+
|
|
24
|
+
# Enable automatic key rotation for the newly created CMK
|
|
25
|
+
enable_key_rotation(key_id) if key_rotation_enabled
|
|
26
|
+
|
|
27
|
+
# Create an alias for the newly created CMK
|
|
28
|
+
create_alias(key_id, key_alias) if key_alias
|
|
29
|
+
|
|
30
|
+
key_id
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def enable_key_rotation(key_id)
|
|
34
|
+
PorkyLib::Config.logger.info("Enabling automatic key rotation for master key: '#{key_id}'")
|
|
35
|
+
client.enable_key_rotation(key_id: key_id)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def create_alias(key_id, key_alias)
|
|
39
|
+
PorkyLib::Config.logger.info("Setting alias as '#{key_alias}' for master key: '#{key_id}'")
|
|
40
|
+
client.create_alias(target_key_id: key_id, alias_name: key_alias)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def encrypt(data, cmk_key_id, encryption_context = nil)
|
|
44
|
+
return if data.nil? || cmk_key_id.nil?
|
|
45
|
+
|
|
46
|
+
# Generate a new data encryption key
|
|
47
|
+
PorkyLib::Config.logger.info('Generating new data encryption key')
|
|
48
|
+
|
|
49
|
+
resp = {}
|
|
50
|
+
resp = client.generate_data_key(key_id: cmk_key_id, key_spec: SYMMETRIC_KEY_SPEC, encryption_context: encryption_context) if encryption_context
|
|
51
|
+
resp = client.generate_data_key(key_id: cmk_key_id, key_spec: SYMMETRIC_KEY_SPEC) unless encryption_context
|
|
52
|
+
|
|
53
|
+
plaintext_key = resp.to_h[:plaintext]
|
|
54
|
+
ciphertext_key = resp.to_h[:ciphertext_blob]
|
|
55
|
+
|
|
56
|
+
# Initialize the box
|
|
57
|
+
secret_box = RbNaCl::SecretBox.new(plaintext_key)
|
|
58
|
+
|
|
59
|
+
# rubocop:disable Lint/UselessAssignment
|
|
60
|
+
plaintext_key = "\0" * plaintext_key.bytesize
|
|
61
|
+
# rubocop:enable Lint/UselessAssignment
|
|
62
|
+
|
|
63
|
+
# First, make a nonce: A single-use value never repeated under the same key
|
|
64
|
+
# The nonce isn't secret, and can be sent with the ciphertext.
|
|
65
|
+
# The cipher instance has a nonce_bytes method for determining how many bytes should be in a nonce
|
|
66
|
+
nonce = RbNaCl::Random.random_bytes(secret_box.nonce_bytes)
|
|
67
|
+
|
|
68
|
+
# Encrypt a message with SecretBox
|
|
69
|
+
PorkyLib::Config.logger.info('Beginning encryption')
|
|
70
|
+
ciphertext = secret_box.encrypt(nonce, data)
|
|
71
|
+
PorkyLib::Config.logger.info('Encryption complete')
|
|
72
|
+
[ciphertext_key, ciphertext, nonce]
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def decrypt(ciphertext_dek, ciphertext, nonce, encryption_context = nil)
|
|
76
|
+
return if ciphertext.nil? || ciphertext_dek.nil? || nonce.nil?
|
|
77
|
+
|
|
78
|
+
# Decrypt the data encryption key
|
|
79
|
+
PorkyLib::Config.logger.info('Decrypting data encryption key')
|
|
80
|
+
|
|
81
|
+
resp = {}
|
|
82
|
+
resp = client.decrypt(ciphertext_blob: ciphertext_dek, encryption_context: encryption_context) if encryption_context
|
|
83
|
+
resp = client.decrypt(ciphertext_blob: ciphertext_dek) unless encryption_context
|
|
84
|
+
|
|
85
|
+
plaintext_key = resp.to_h[:plaintext]
|
|
86
|
+
|
|
87
|
+
secret_box = RbNaCl::SecretBox.new(plaintext_key)
|
|
88
|
+
|
|
89
|
+
# rubocop:disable Lint/UselessAssignment
|
|
90
|
+
plaintext_key = "\0" * plaintext_key.bytesize
|
|
91
|
+
# rubocop:enable Lint/UselessAssignment
|
|
92
|
+
|
|
93
|
+
PorkyLib::Config.logger.info('Beginning decryption')
|
|
94
|
+
result = secret_box.decrypt(nonce, ciphertext)
|
|
95
|
+
PorkyLib::Config.logger.info('Decryption complete')
|
|
96
|
+
result
|
|
97
|
+
end
|
|
98
|
+
end
|
data/lib/porky_lib.rb
ADDED
data/porky_lib.gemspec
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
5
|
+
require 'porky_lib/version'
|
|
6
|
+
|
|
7
|
+
Gem::Specification.new do |spec|
|
|
8
|
+
spec.name = "porky_lib"
|
|
9
|
+
spec.version = PorkyLib::VERSION
|
|
10
|
+
spec.authors = ["Greg Fletcher"]
|
|
11
|
+
spec.email = ["greg.fletcher@zetatango.com"]
|
|
12
|
+
|
|
13
|
+
spec.summary = 'A library for cryptographic services for the Zetatango platform'
|
|
14
|
+
spec.homepage = 'https://github.com/Zetatango/porky_lib'
|
|
15
|
+
|
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
|
17
|
+
f.match(%r{^(test|spec|features)/})
|
|
18
|
+
end
|
|
19
|
+
spec.bindir = 'exe'
|
|
20
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
21
|
+
spec.require_paths = ['lib']
|
|
22
|
+
|
|
23
|
+
spec.add_development_dependency 'bundler'
|
|
24
|
+
spec.add_development_dependency 'bundler-audit'
|
|
25
|
+
spec.add_development_dependency 'byebug'
|
|
26
|
+
spec.add_development_dependency 'codecov'
|
|
27
|
+
spec.add_development_dependency 'rake'
|
|
28
|
+
spec.add_development_dependency 'rspec'
|
|
29
|
+
spec.add_development_dependency 'rspec-collection_matchers'
|
|
30
|
+
spec.add_development_dependency 'rspec-mocks'
|
|
31
|
+
spec.add_development_dependency 'rspec_junit_formatter'
|
|
32
|
+
spec.add_development_dependency 'rubocop'
|
|
33
|
+
spec.add_development_dependency 'rubocop-rspec'
|
|
34
|
+
spec.add_development_dependency 'rubocop_runner'
|
|
35
|
+
spec.add_development_dependency 'simplecov'
|
|
36
|
+
spec.add_development_dependency 'timecop'
|
|
37
|
+
|
|
38
|
+
spec.add_dependency 'aws-sdk-kms'
|
|
39
|
+
spec.add_dependency 'msgpack'
|
|
40
|
+
spec.add_dependency 'rbnacl-libsodium'
|
|
41
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: porky_lib
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Greg Fletcher
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2018-07-13 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: bundler
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: bundler-audit
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: byebug
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: codecov
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: rake
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - ">="
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '0'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: rspec
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - ">="
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '0'
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - ">="
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '0'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: rspec-collection_matchers
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - ">="
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '0'
|
|
104
|
+
type: :development
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - ">="
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '0'
|
|
111
|
+
- !ruby/object:Gem::Dependency
|
|
112
|
+
name: rspec-mocks
|
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
|
114
|
+
requirements:
|
|
115
|
+
- - ">="
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: '0'
|
|
118
|
+
type: :development
|
|
119
|
+
prerelease: false
|
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - ">="
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: '0'
|
|
125
|
+
- !ruby/object:Gem::Dependency
|
|
126
|
+
name: rspec_junit_formatter
|
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
|
128
|
+
requirements:
|
|
129
|
+
- - ">="
|
|
130
|
+
- !ruby/object:Gem::Version
|
|
131
|
+
version: '0'
|
|
132
|
+
type: :development
|
|
133
|
+
prerelease: false
|
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
135
|
+
requirements:
|
|
136
|
+
- - ">="
|
|
137
|
+
- !ruby/object:Gem::Version
|
|
138
|
+
version: '0'
|
|
139
|
+
- !ruby/object:Gem::Dependency
|
|
140
|
+
name: rubocop
|
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
|
142
|
+
requirements:
|
|
143
|
+
- - ">="
|
|
144
|
+
- !ruby/object:Gem::Version
|
|
145
|
+
version: '0'
|
|
146
|
+
type: :development
|
|
147
|
+
prerelease: false
|
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
149
|
+
requirements:
|
|
150
|
+
- - ">="
|
|
151
|
+
- !ruby/object:Gem::Version
|
|
152
|
+
version: '0'
|
|
153
|
+
- !ruby/object:Gem::Dependency
|
|
154
|
+
name: rubocop-rspec
|
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
|
156
|
+
requirements:
|
|
157
|
+
- - ">="
|
|
158
|
+
- !ruby/object:Gem::Version
|
|
159
|
+
version: '0'
|
|
160
|
+
type: :development
|
|
161
|
+
prerelease: false
|
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
163
|
+
requirements:
|
|
164
|
+
- - ">="
|
|
165
|
+
- !ruby/object:Gem::Version
|
|
166
|
+
version: '0'
|
|
167
|
+
- !ruby/object:Gem::Dependency
|
|
168
|
+
name: rubocop_runner
|
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
|
170
|
+
requirements:
|
|
171
|
+
- - ">="
|
|
172
|
+
- !ruby/object:Gem::Version
|
|
173
|
+
version: '0'
|
|
174
|
+
type: :development
|
|
175
|
+
prerelease: false
|
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
177
|
+
requirements:
|
|
178
|
+
- - ">="
|
|
179
|
+
- !ruby/object:Gem::Version
|
|
180
|
+
version: '0'
|
|
181
|
+
- !ruby/object:Gem::Dependency
|
|
182
|
+
name: simplecov
|
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
|
184
|
+
requirements:
|
|
185
|
+
- - ">="
|
|
186
|
+
- !ruby/object:Gem::Version
|
|
187
|
+
version: '0'
|
|
188
|
+
type: :development
|
|
189
|
+
prerelease: false
|
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
191
|
+
requirements:
|
|
192
|
+
- - ">="
|
|
193
|
+
- !ruby/object:Gem::Version
|
|
194
|
+
version: '0'
|
|
195
|
+
- !ruby/object:Gem::Dependency
|
|
196
|
+
name: timecop
|
|
197
|
+
requirement: !ruby/object:Gem::Requirement
|
|
198
|
+
requirements:
|
|
199
|
+
- - ">="
|
|
200
|
+
- !ruby/object:Gem::Version
|
|
201
|
+
version: '0'
|
|
202
|
+
type: :development
|
|
203
|
+
prerelease: false
|
|
204
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
205
|
+
requirements:
|
|
206
|
+
- - ">="
|
|
207
|
+
- !ruby/object:Gem::Version
|
|
208
|
+
version: '0'
|
|
209
|
+
- !ruby/object:Gem::Dependency
|
|
210
|
+
name: aws-sdk-kms
|
|
211
|
+
requirement: !ruby/object:Gem::Requirement
|
|
212
|
+
requirements:
|
|
213
|
+
- - ">="
|
|
214
|
+
- !ruby/object:Gem::Version
|
|
215
|
+
version: '0'
|
|
216
|
+
type: :runtime
|
|
217
|
+
prerelease: false
|
|
218
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
219
|
+
requirements:
|
|
220
|
+
- - ">="
|
|
221
|
+
- !ruby/object:Gem::Version
|
|
222
|
+
version: '0'
|
|
223
|
+
- !ruby/object:Gem::Dependency
|
|
224
|
+
name: msgpack
|
|
225
|
+
requirement: !ruby/object:Gem::Requirement
|
|
226
|
+
requirements:
|
|
227
|
+
- - ">="
|
|
228
|
+
- !ruby/object:Gem::Version
|
|
229
|
+
version: '0'
|
|
230
|
+
type: :runtime
|
|
231
|
+
prerelease: false
|
|
232
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
233
|
+
requirements:
|
|
234
|
+
- - ">="
|
|
235
|
+
- !ruby/object:Gem::Version
|
|
236
|
+
version: '0'
|
|
237
|
+
- !ruby/object:Gem::Dependency
|
|
238
|
+
name: rbnacl-libsodium
|
|
239
|
+
requirement: !ruby/object:Gem::Requirement
|
|
240
|
+
requirements:
|
|
241
|
+
- - ">="
|
|
242
|
+
- !ruby/object:Gem::Version
|
|
243
|
+
version: '0'
|
|
244
|
+
type: :runtime
|
|
245
|
+
prerelease: false
|
|
246
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
247
|
+
requirements:
|
|
248
|
+
- - ">="
|
|
249
|
+
- !ruby/object:Gem::Version
|
|
250
|
+
version: '0'
|
|
251
|
+
description:
|
|
252
|
+
email:
|
|
253
|
+
- greg.fletcher@zetatango.com
|
|
254
|
+
executables: []
|
|
255
|
+
extensions: []
|
|
256
|
+
extra_rdoc_files: []
|
|
257
|
+
files:
|
|
258
|
+
- ".circleci/config.yml"
|
|
259
|
+
- ".github/pull_request_template.md"
|
|
260
|
+
- ".gitignore"
|
|
261
|
+
- ".rspec"
|
|
262
|
+
- ".rubocop.yml"
|
|
263
|
+
- Gemfile
|
|
264
|
+
- Gemfile.lock
|
|
265
|
+
- README.md
|
|
266
|
+
- Rakefile
|
|
267
|
+
- bin/console
|
|
268
|
+
- bin/setup
|
|
269
|
+
- lib/porky_lib.rb
|
|
270
|
+
- lib/porky_lib/aws/kms/client.rb
|
|
271
|
+
- lib/porky_lib/config.rb
|
|
272
|
+
- lib/porky_lib/symmetric.rb
|
|
273
|
+
- lib/porky_lib/version.rb
|
|
274
|
+
- porky_lib.gemspec
|
|
275
|
+
homepage: https://github.com/Zetatango/porky_lib
|
|
276
|
+
licenses: []
|
|
277
|
+
metadata: {}
|
|
278
|
+
post_install_message:
|
|
279
|
+
rdoc_options: []
|
|
280
|
+
require_paths:
|
|
281
|
+
- lib
|
|
282
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
283
|
+
requirements:
|
|
284
|
+
- - ">="
|
|
285
|
+
- !ruby/object:Gem::Version
|
|
286
|
+
version: '0'
|
|
287
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
288
|
+
requirements:
|
|
289
|
+
- - ">="
|
|
290
|
+
- !ruby/object:Gem::Version
|
|
291
|
+
version: '0'
|
|
292
|
+
requirements: []
|
|
293
|
+
rubyforge_project:
|
|
294
|
+
rubygems_version: 2.5.2
|
|
295
|
+
signing_key:
|
|
296
|
+
specification_version: 4
|
|
297
|
+
summary: A library for cryptographic services for the Zetatango platform
|
|
298
|
+
test_files: []
|