signed_config 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|