signed_config 0.0.2

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 02a530752f0bb7d45a71a7e5b5c63a48c82ba866
4
+ data.tar.gz: 04ff0c5fccc77cefde01ff9d3e932e270d8bde02
5
+ SHA512:
6
+ metadata.gz: 2368c9dcad07fc216d00d046f5259cb9fcca43000347d5b286ad558d190b6eefcf2f4092da28daaebe52748ee9e0b723ee910655735a7d3f0f887fb5d77b2257
7
+ data.tar.gz: ba14d2d8a428d11406156136c19b4d4779a696e90c9bf1284f8e4f1a9054c6bfff45e18c6da1f2605b993efae9de52104119baca9a335929784ef2af6322f317
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2015 Kevin Trowbridge
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,95 @@
1
+ # SignedConfig
2
+
3
+ SignedConfig has two modes:
4
+
5
+ Writer mode : intended to be used by a sort of "licensing console", takes a config hash and a private key and generates
6
+ signed "license text." The licensed text contains a YML serialized "human readable" version of the config hash and
7
+ a matching signature.
8
+
9
+ Reader mode: intended to be used by the "licensed application" takes a public key (which should be distributed with the
10
+ application) and the "license text." It confirms the signature matches the config hash, and deserializes the license text
11
+ from YML back into a Ruby hash.
12
+
13
+ This gem was extracted from an enterprise Rails webapp. It's unusual for a Rails application in that, it's installed onsite
14
+ on clients' servers. (This is not unusual for an "enterprise application," however.)
15
+
16
+
17
+ ## Writing licenses
18
+
19
+ config_hash = {
20
+ :client_name: "Acme Inc."
21
+ :license_level: "high"
22
+ :licensed_features: ["widget_management", "foobar_creation"],
23
+ :expiration_date: 2099-01-01 00:00:00 UTC
24
+ :num_superusers: 1
25
+ :num_users: 20
26
+ }
27
+ org_name = "BRANDED"
28
+
29
+ writer = SignedConfig::Writer.new(SIGNED_CONFIG_PRIVATE_KEY, config_hash, org_name)
30
+ self.signed_config = writer.signed_config
31
+
32
+
33
+ ## Reading licenses
34
+
35
+ def verify
36
+ begin
37
+ reader = SignedConfig::Reader.new(SIGNED_CONFIG_PUBLIC_KEY, params[:license])
38
+ @config = reader.config
39
+ flash[:success] = 'License is valid.'
40
+ rescue SignedConfig::SignatureDoesNotMatch
41
+ flash[:error] = 'License is invalid.'
42
+ flash.discard
43
+ render :new
44
+ end
45
+ end
46
+
47
+
48
+
49
+ ## Example of generated license:
50
+ ```
51
+
52
+ =========================BEGIN BRANDED LICENSE=========================
53
+ ---
54
+ :config: |
55
+ ---
56
+ :client_name: Acme Inc.
57
+ :license_level: high
58
+ :licensed_features:
59
+ - widget_management
60
+ - foobar_creation
61
+ :expiration_date: 2099-01-01 00:00:00 UTC
62
+ :num_superusers: 1
63
+ :num_users: 20
64
+ :signature: |
65
+ VUiNY22N9cIs+RUJ1+w+T7pXEkBwOhGx2heeo3ekxybWjm+gEBXkt8n3Y8+s
66
+ pUyK0RfLM6NK9LMP6/Ke5IXL199hDLKXJnfy/nuZ+uIkNmoL1Bd7FhMqXCmd
67
+ HyRuuSkHTsQG4hk1VCS/mn/KaZWn1P+dyng4HWYWCAJYt2dtJqf/ZbLmIXdI
68
+ xX9GplIG5wCto7Dss7ksBR2x97QFe5P3OGQAjX0qw/jFWY8pOy+JCaJqB4b4
69
+ U/xy9p/+VWPhdmQPY8/hIowx9WC2UqbV5QKTEJzqWebG7xbvwmJfywE60+qO
70
+ rhEqh8fza8jagTbmzBtpsXyyTd4ZAusXuwc7uMTtFQ==
71
+
72
+ =========================END BRANDED LICENSE=========================
73
+ ```
74
+
75
+ ## How "secure" is this?
76
+
77
+ Since Ruby is not compiled and trivially easy to modify, it's very unsuited for being "hacker proof." Normally this doesn't
78
+ matter as, enforcing license compliance is trivial for a classic consumer webapp where users only interact with the app via
79
+ their web browsers.
80
+
81
+ However, for enterprise clients, it makes sense to just make it "slightly difficult" to modify the license to add users,
82
+ enable additional features, or extend the expiration date. The security that this gem provides raises the bar just from
83
+ the point where anyone could read and alter a plain text configuration file, to the point where any "programmer" type could
84
+ figure out modifications in an hour or two.
85
+
86
+ However, I believe this is sufficient to enforce 95% of business agreements.
87
+
88
+ Additionally SignedConfig only uses a SHA1 hashing algorithm which is not hacker-proof anymore.
89
+
90
+
91
+ ## Development
92
+
93
+ ### Running the tests
94
+
95
+ rake
@@ -0,0 +1,10 @@
1
+ require 'yaml'
2
+
3
+ require 'signed_config/crypto'
4
+ require 'signed_config/errors'
5
+ require 'signed_config/reader'
6
+ require 'signed_config/writer'
7
+
8
+ module SignedConfig
9
+ VERSION = "0.0.2"
10
+ end
@@ -0,0 +1,28 @@
1
+ # Adapted from: https://blog.atechmedia.com/encrypting-signing-data-ruby/
2
+
3
+ require 'openssl'
4
+ require 'base64'
5
+
6
+ module SignedConfig
7
+ module Crypto
8
+
9
+ class << self
10
+
11
+ # Return a signature for the string
12
+ def sign(key, string)
13
+ Base64.encode64(rsa_key(key).sign(OpenSSL::Digest::SHA1.new, string))
14
+ end
15
+
16
+ # Verify the string and signature
17
+ def verify(key, signature, string)
18
+ rsa_key(key).verify(OpenSSL::Digest::SHA1.new, Base64.decode64(signature), string)
19
+ end
20
+
21
+ private
22
+
23
+ def rsa_key(key)
24
+ OpenSSL::PKey::RSA.new(key)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,5 @@
1
+ module SignedConfig
2
+
3
+ class SignatureDoesNotMatch < StandardError
4
+ end
5
+ end
@@ -0,0 +1,18 @@
1
+ module SignedConfig
2
+ class Reader
3
+ def initialize(public_key, signed_config)
4
+ yml = signed_config.split("\n")[1..-2].join("\n")
5
+ hash = YAML.load(yml)
6
+ signature = hash[:signature]
7
+ @config_string = hash[:config]
8
+
9
+ unless Crypto.verify(public_key, signature, @config_string)
10
+ raise SignatureDoesNotMatch
11
+ end
12
+ end
13
+
14
+ def config
15
+ YAML.load(@config_string)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,20 @@
1
+ module SignedConfig
2
+ class Writer
3
+ def initialize(private_key, config_hash, org_name = nil)
4
+ config_string = YAML::dump(config_hash)
5
+ signature = Crypto.sign(private_key, config_string)
6
+ @org_name = org_name
7
+ @signed_config = YAML::dump({:config => config_string,
8
+ :signature => signature})
9
+ end
10
+
11
+ def signed_config
12
+ dramatic = '=' * 25
13
+ brand_txt = @org_name ? @org_name + " " : nil
14
+
15
+ "#{dramatic}BEGIN #{brand_txt}LICENSE#{dramatic}\n" +
16
+ @signed_config + "\n" +
17
+ "#{dramatic}END #{brand_txt}LICENSE#{dramatic}"
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,27 @@
1
+ -----BEGIN RSA PRIVATE KEY-----
2
+ MIIEpQIBAAKCAQEAvLGrsJK6IobzhJ288tFHLae6hqrRzeftQIBsJ7htIp8fmHbn
3
+ OfPdf9p+e4WJMQkM5YX8bdv/TJLmsd8qCKdZ/5FTJB4RsH7gm9ef/4edXuuH7tUH
4
+ QZuz+J87sq6UNGfpth7IsXkWcPqjmzZlpYAXL4kRZ1TtoXNt5WUMCyWRltGBnHuE
5
+ TZs4D+RBXNYLL1wb98/FBWUQUfUI2KPOnVkTcysGLhfpEEVGgsAWdwi2+Jn3KKZz
6
+ 6BInElRBrGYXIK5wPLigLebDUUGmNj8PL6LaJe7o2TO6cJXWLOSTIr1x3+giL5kX
7
+ 2w++kJcWyCmnffW/CscTX3em+qJX67q3p84vXQIDAQABAoIBAQCZMM0ofxaqbVFK
8
+ ex0pLQpSYHeoWQoX1pDg6uHjpXDEyNbH2tCCVh+fau2Arrrgmm5j8NEtB4xOyHyO
9
+ L5VajTMdrwgGrHrEBV2oZ/g3Zgw3QZSMK1rGwvfrgqret6kOmsY82uUoYBv+AR3O
10
+ Ju2C3Wj1aJw+fc6mYqX3tH+AlTGLdImO+wq7g40A1EDISHd0kaE4jTlljUiTf4Wn
11
+ 6lCSE+ie5z+s6pIYopnlwSX99X1VIClVXhD54rpngRF6ZrFgLtgIrfR4KOHfOuUa
12
+ euaa1Mnwjn/DV9KfS2q5wRytJhF+bJTf0fxBHC7gCwYXXj2meKVNC8HfvIGdOWsJ
13
+ LIIGmmkBAoGBAO1VwuMGu567IsUHkNv+GpHtiOSvQK9VrC3fT8/APS8uwmEXNcY2
14
+ eWlfjl1Eockf1jx79M8HjXfEjGfjdtFzgxwaf5v9lBGhzw0YMuVDRhXNkOsucclj
15
+ H/liNAmoAw4tSo9OUvqs3G5n0gcvZmeJRVAG/qTTXe+sJ0opcRwIeOKhAoGBAMuI
16
+ oEaO8roYcUyeqxVSJGHzMG+PpU6NwS9jrm4QnlFvm1/Z7iQHUbxv2Id/nost7ioQ
17
+ EsHo5r3S42CYgFvc4P/vWJpiM7ey/Db014fOayjDmn0ui9TwQSXQm8c4GsoQhpjz
18
+ PGALlaGRi5KXp4pWhkk+/2TSJO4QBclzxCkLwM89AoGBAOZaW/qv2UCaD4g+7Kmp
19
+ ey5x53FWbCkBtUkcusFAq+H0t3M27NmCm9rbhTkfWQv126D/CsA6a2N4oHJhAz0N
20
+ qY6IZZ0IxUNL0sO53gRJhGe7CtJJVOWLUUhiuXE54iVLblejRMTLLHP4TpDsv670
21
+ PBMNhvCBumXaqJPBn2f/DR2hAoGBAI/eSpS1dMOwhV3IhlwyzP7jvOgO3KWTM+wA
22
+ hQrN+mrL/kMZs+iJt/AAC0l0Hyh4VfIrnGau73Ncf1cHVSwaDH08vR+brLz76qYa
23
+ GcOoWDzfTvOPlE33acWGGKcQdHdCiTHSNWoapjEnxUXgpw/1K0TZIAQvOuWAM+b7
24
+ SrxMz4RtAoGANI/eHiSeMlRmRBAlpTXERqtHxcookrWkxMEpeqNsiBi0iiicfi4S
25
+ ECSu3rqmpFnwOx4rxPuXmP8+zCEdAg8CvlN/GOG5j1cAfQ5W8HhmGdlnWXhqxDvN
26
+ 2vrfCkomVTICJSn4tpIBfffwJzuIKXzJEZk23HmN5HcS0IsaCbQwGFk=
27
+ -----END RSA PRIVATE KEY-----
@@ -0,0 +1,9 @@
1
+ -----BEGIN PUBLIC KEY-----
2
+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvLGrsJK6IobzhJ288tFH
3
+ Lae6hqrRzeftQIBsJ7htIp8fmHbnOfPdf9p+e4WJMQkM5YX8bdv/TJLmsd8qCKdZ
4
+ /5FTJB4RsH7gm9ef/4edXuuH7tUHQZuz+J87sq6UNGfpth7IsXkWcPqjmzZlpYAX
5
+ L4kRZ1TtoXNt5WUMCyWRltGBnHuETZs4D+RBXNYLL1wb98/FBWUQUfUI2KPOnVkT
6
+ cysGLhfpEEVGgsAWdwi2+Jn3KKZz6BInElRBrGYXIK5wPLigLebDUUGmNj8PL6La
7
+ Je7o2TO6cJXWLOSTIr1x3+giL5kX2w++kJcWyCmnffW/CscTX3em+qJX67q3p84v
8
+ XQIDAQAB
9
+ -----END PUBLIC KEY-----
@@ -0,0 +1,27 @@
1
+ require 'minitest/autorun'
2
+ require 'signed_config'
3
+
4
+ class SignedConfigTest < Minitest::Test
5
+
6
+ def test_flow
7
+ private_key_path = File.expand_path("../support/test_private.key", __FILE__)
8
+ private_key = File.open(private_key_path, 'rb') { |f| f.read }
9
+ config_hash = {
10
+ :num_users => '1',
11
+ :functionality_enabled => false
12
+ }
13
+
14
+ writer = SignedConfig::Writer.new(private_key, config_hash)
15
+ signed_config = writer.signed_config
16
+
17
+ assert signed_config
18
+
19
+ public_key_path = File.expand_path("../support/test_private.key", __FILE__)
20
+ public_key = File.open(public_key_path, 'rb') { |f| f.read }
21
+ reader = SignedConfig::Reader.new(public_key, signed_config)
22
+
23
+ config = reader.config
24
+ assert config[:num_users] == '1'
25
+ assert config[:functionality_enabled] == false
26
+ end
27
+ end
metadata ADDED
@@ -0,0 +1,58 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: signed_config
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Kevin Trowbridge
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-12-07 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description:
14
+ email:
15
+ - kevinmtrowbridge@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - MIT_LICENSE
21
+ - README.md
22
+ - lib/signed_config.rb
23
+ - lib/signed_config/crypto.rb
24
+ - lib/signed_config/errors.rb
25
+ - lib/signed_config/reader.rb
26
+ - lib/signed_config/writer.rb
27
+ - test/support/test_private.key
28
+ - test/support/test_public.key
29
+ - test/test_signed_config.rb
30
+ homepage: https://github.com/kevinmtrowbridge/signed_config
31
+ licenses:
32
+ - MIT
33
+ metadata: {}
34
+ post_install_message:
35
+ rdoc_options: []
36
+ require_paths:
37
+ - lib
38
+ required_ruby_version: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ required_rubygems_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ requirements: []
49
+ rubyforge_project:
50
+ rubygems_version: 2.5.0
51
+ signing_key:
52
+ specification_version: 4
53
+ summary: Cryptograpically sign a configuration hash. A way to enforce configuration
54
+ / licensing of an installed Ruby application.
55
+ test_files:
56
+ - test/support/test_private.key
57
+ - test/support/test_public.key
58
+ - test/test_signed_config.rb