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.
- checksums.yaml +7 -0
- data/MIT_LICENSE +19 -0
- data/README.md +95 -0
- data/lib/signed_config.rb +10 -0
- data/lib/signed_config/crypto.rb +28 -0
- data/lib/signed_config/errors.rb +5 -0
- data/lib/signed_config/reader.rb +18 -0
- data/lib/signed_config/writer.rb +20 -0
- data/test/support/test_private.key +27 -0
- data/test/support/test_public.key +9 -0
- data/test/test_signed_config.rb +27 -0
- metadata +58 -0
checksums.yaml
ADDED
@@ -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
|
data/MIT_LICENSE
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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,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,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
|