autosign 0.0.1

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,44 @@
1
+ When(/^I get help for "([^"]*)"$/) do |app_name|
2
+ @app_name = app_name
3
+ step %(I run `#{app_name} help`)
4
+ end
5
+
6
+ Given(/^a pre\-shared key of "([^"]*)"$/) do |presharedkey|
7
+ @psk = presharedkey
8
+ end
9
+
10
+ Given(/^a hostname of "([^"]*)"$/) do |host|
11
+ @hostname = host
12
+ end
13
+
14
+ Given(/^the current time is (\d+)$/) do |time|
15
+ @current_time = time
16
+ end
17
+
18
+ Given(/^a static token file containing:$/) do |multiline|
19
+ @static_token_file = multiline
20
+ end
21
+
22
+ Given(/^a mocked "\/(\S*)" directory$/)do |directory|
23
+ dir_name = File.join(File.expand_path(current_dir), "etc")
24
+ FileUtils.mkdir_p dir_name
25
+ set_env 'ETCROOT', dir_name
26
+ # create_dir("etc")
27
+ end
28
+
29
+ Then(/^a "\/(\S*)" (?:file|directory) should exist$/) do |file|
30
+ #expect(File.exist?(File.join(File.expand_path(current_dir), file))).to be true
31
+ fullpath = File.join(File.expand_path(current_dir), file)
32
+ FileUtils.mkdir_p fullpath
33
+ $world.puts "path: " + fullpath
34
+ expect(File.exist?(file)).to be true
35
+ end
36
+
37
+ #When(/^I pipe in the file "(.*?)"$/) do |file|
38
+ # in_current_dir do
39
+ # File.open(file, 'r').each_line do |line|
40
+ # _write_interactive(line)
41
+ # end
42
+ # end
43
+ # @interactive.stdin.close()
44
+ #end
@@ -0,0 +1,17 @@
1
+ require 'aruba/cucumber'
2
+
3
+
4
+ ENV['PATH'] = "#{File.expand_path(File.dirname(__FILE__) + '/../../bin')}#{File::PATH_SEPARATOR}#{ENV['PATH']}"
5
+ LIB_DIR = File.join(File.expand_path(File.dirname(__FILE__)),'..','..','lib')
6
+
7
+ Before do
8
+ # Using "announce" causes massive warnings on 1.9.2
9
+ @puts = true
10
+ @original_rubylib = ENV['RUBYLIB']
11
+ ENV['RUBYLIB'] = LIB_DIR + File::PATH_SEPARATOR + ENV['RUBYLIB'].to_s
12
+ $world = self
13
+ end
14
+
15
+ After do
16
+ ENV['RUBYLIB'] = @original_rubylib
17
+ end
@@ -0,0 +1,20 @@
1
+ Feature: Validate autosign key
2
+ In order to sign puppet certificates automatically
3
+ I want to validate autosign keys programatically
4
+ So that I only grant access to allowed systems without needing manual authorization
5
+
6
+ Scenario: Validate a certificate signing request
7
+ Given I set the environment variables to:
8
+ | variable | value |
9
+ | AUTOSIGN_TESTMODE | true |
10
+ | AUTOSIGN_TEST_SECRET | secret |
11
+ | AUTOSIGN_TEST_LOGLEVEL | info |
12
+ When I run `autosign-validator i-7672fe81` interactively
13
+ And I pipe in the file "../../fixtures/i-7672fe81.pem"
14
+ Then the output should contain "token validated successfully"
15
+ Then the exit status should be 0
16
+
17
+ Scenario: Do not validate a certificate signing request whose certname does not match the certificate
18
+ When I run `autosign-validator wrong-certname.example.com` interactively
19
+ And I pipe in the file "../../fixtures/i-7672fe81.pem"
20
+ Then the exit status should be 1
@@ -0,0 +1,34 @@
1
+ -----BEGIN CERTIFICATE REQUEST-----
2
+ MIIF8jCCA9oCAQAwFTETMBEGA1UEAwwKaS03NjcyZmU4MTCCAiIwDQYJKoZIhvcN
3
+ AQEBBQADggIPADCCAgoCggIBAKy9i/q94RycncCxUgoGexiHY/El/q5mV+fB6DlB
4
+ 60tPpAxUUDpdEFmSS63/uKc5bGIE7R+YD64etHHY6WeX/JK1OIE17YCIMT+J82tC
5
+ MCTiXHgD52uViuYC0a3Yr7PrrAYALrVzSds3hbfsQNmC/9BfwLD1FB5g8PVPb82J
6
+ paahRAh4dBzjgbNVrgI0s54dlDca9LvO+RDORqJVBM27jy1rScdg4rxcy30Fw9DI
7
+ 1NhrhfXrNTnUb6T7GOpKZjJO0mbhItZRQr28S1Rx3Lyqk43HkIJQ66/7tUhrBXmG
8
+ 787hHhqYbh0dvxR8t61Xu7ITIX3U164kEE70nVNxelTFuedYUA7os85WDriCoD9n
9
+ O2eNatMdWPpw7kcvoPZzIc2kfu4UQxP9UTYLKz2sNnjan2ZSoTdQ6K4tc0ee5hVv
10
+ AF3klg+M9LpoLEnBu51hhxhfEiaY/cx2EdQvsJOJH9U5Q11jvnn6McxDfoLHeGeW
11
+ 7Cp6qjmVysirE/tQU9Ds2v+NEkmOF1JLMe95lxISvwz4tuoRlgGRLD6vJhyDyUtg
12
+ Zg505eiWxjWJ6h4591Ll0yjVgYuOf4I5aePXsOsh9zS4cchzbktWdAoBgv/p6sVH
13
+ 67Gfd1EzdkByT8RGBOCV9T4jX4NwBqFL7HczHO0GnUqvZ0034THnqCyPSxTqOy6n
14
+ QogDAgMBAAGgggGWMIIBkgYJKoZIhvcNAQkHMYIBgxOCAX9leUowZVhBaU9pSktW
15
+ MVFpTENKaGJHY2lPaUpJVXpVeE1pSjkuZXlKa1lYUmhJam9pZTF3aVkyVnlkRzVo
16
+ YldWY0lqcGNJbWt0TnpZM01tWmxPREZjSWl4Y0luSmxjWFZsYzNSbGNsd2lPbHdp
17
+ UkdGdWFXVnNjeTFOWVdOQ2IyOXJMVkJ5YnkweUxteHZZMkZzWENJc1hDSnlaWFZ6
18
+ WVdKc1pWd2lPbVpoYkhObExGd2lkbUZzYVdSbWIzSmNJam81T1RrNU9Ua3NYQ0ox
19
+ ZFdsa1hDSTZYQ0kwWVRNMlpqQTBOUzFqTm1ObExUUmlaall0WW1Fell5MDJaak5s
20
+ TnpobE5tSTNNV05jSW4waUxDSmxlSEFpT2lJeE5ETTNORGN3TVRrMkluMC5PWlFk
21
+ ZW5Wekl4eS1JczI3MVRLMHFxaEttUmZxa0IyTGhzY3N6LWtJSzRIUWFlbTNBd3g3
22
+ elZraUNwajBfZUZja2dhS1lOQk1BZGhVZklNcVMzSU1tdzANBgkqhkiG9w0BAQsF
23
+ AAOCAgEADY7/yRoEeXziUo8bwIp4E+0FPfpP9+uVbhHJE+5TIgBBR1B4qzN0/vRG
24
+ Rv6Vo9dwjMadxxWmXQbKjzkC+t2DuIY2u9bgoVr54IMzllwBvfhvDzj9qO6gRlko
25
+ cNyWzoz/Bc9/KmO7jCttI7QjkJWPE8JvlId5TEzgNPmo7L1QXQXNSlp1tc3EDwdp
26
+ xWpfx7NXzZJUpgCueywYwjWB/ckK4Mu5wvXuWyHw2J/4hMbxhJzl++DKkHxxtDIa
27
+ UKfTlEp/9InQUUo6HluN4GqMguu9wcvcRQIKf8SiWiQmH3LnMaCl+yWQ8rYS7w7O
28
+ oIIlw9IrQMrKwt14hEDDORdSBiFcRHkmdegW0Z+OWTNVkeSA3IS0YRTNnItnDg24
29
+ GJcx8ooaj9upbfixnnhBWZwfQwf1ngWN192hXlORkTSeav9/1nSTJujy6X5wp6b/
30
+ 3WtYGy6c9Gknx26dQB6JLTxJyGUQbgoNMG3toCaISLJUamDcT03eTx6yvpm2eC4l
31
+ uwRPdegUj0sMNQivJ34elXeJtCT70RcQGU3dhspXI1AsYVmNNGqj20Kkzd971WAq
32
+ lxkG9yhW9TapxTe1G1BPETYXtLK8a0Y4evdnFVE1w9MRWfmWO2UMMC2bOVlV83pN
33
+ x8ykrmy6WP7cubIIH7/PUD8jI2ekBEjvIDdft2dZeYEWZIs5DcI=
34
+ -----END CERTIFICATE REQUEST-----
data/lib/autosign.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'autosign/version.rb'
2
+ require 'autosign/token.rb'
3
+ require 'autosign/config.rb'
4
+ require 'autosign/decoder.rb'
5
+ require 'autosign/journal.rb'
6
+ require 'autosign/validator.rb'
7
+
8
+ # Add requires for other files you add to your project here, so
9
+ # you just need to require this one file in your bin file
@@ -0,0 +1,131 @@
1
+ module Autosign
2
+ module Exceptions
3
+ class Validation < Exception
4
+ end
5
+ class NotFound < Exception
6
+ end
7
+ class Permissions < Exception
8
+ end
9
+ class Error < Exception
10
+ end
11
+ end
12
+ require 'iniparse'
13
+ require 'rbconfig'
14
+ require 'securerandom'
15
+ require 'deep_merge'
16
+ class Config
17
+ attr_accessor :location
18
+ attr_accessor :config_file_paths
19
+ def initialize(settings = {})
20
+ # set up logging
21
+ @log = Logging.logger['Autosign::Config']
22
+ @log.debug "initializing Autosign::Config"
23
+
24
+ # validate parameter
25
+ raise 'settings is not a hash' unless settings.is_a?(Hash)
26
+
27
+ # look in the following places for a config file
28
+ @config_file_paths = ['/etc/autosign.conf', '/usr/local/etc/autosign.conf', File.join(Dir.home, '.autosign.conf')]
29
+ @config_file_paths = [ settings['config_file'] ] unless settings['config_file'].nil?
30
+
31
+ @settings = settings
32
+ @log.debug "Using merged settings hash: " + @settings.to_s
33
+ end
34
+
35
+ def settings
36
+ @log.debug "merging settings"
37
+ setting_sources = [default_settings, configfile, @settings]
38
+ setting_sources.inject({}) { |merged, hash| merged.deep_merge(hash) }
39
+ end
40
+
41
+ private
42
+
43
+ def default_settings
44
+ { 'general' =>
45
+ {
46
+ 'loglevel' => 'INFO',
47
+ 'token_validity' => 7200,
48
+ 'logfile' => '/var/log/autosign.log',
49
+ 'journalfile' => '/var/log/autosign.journal',
50
+ },
51
+ 'jwt_token' => {
52
+ 'validity' => 7200
53
+ }
54
+ }
55
+ end
56
+
57
+ def configfile
58
+ @log.debug "Finding config file"
59
+ @config_file_paths.each { |file|
60
+ @log.debug "Checking if file '#{file}' exists"
61
+ if File.file?(file)
62
+ @log.debug "Reading config file from: " + file
63
+ config_file = File.read(file)
64
+ parsed_config_file = IniParse.parse(config_file).to_hash
65
+ @log.debug "configuration read from config file: " + parsed_config_file.to_s
66
+ return parsed_config_file if parsed_config_file.is_a?(Hash)
67
+ else
68
+ @log.debug "Configuration file '#{file}' not found"
69
+ end
70
+ }
71
+ return {}
72
+ end
73
+
74
+ def validate_config_file(configfile = location)
75
+ @log.debug "validating config file"
76
+ unless File.file?(configfile)
77
+ @log.error "configuration file not found at: #{configfile}"
78
+ raise Autosign::Exceptions::NotFound
79
+ end
80
+
81
+ # check if file is world-readable
82
+ if File.world_readable?(configfile) or File.world_writable?(configfile)
83
+ @log.error "configuration file #{configfile} is world-readable or world-writable, which is a security risk"
84
+ raise Autosign::Exceptions::Permissions
85
+ end
86
+
87
+ configfile
88
+ end
89
+
90
+ def self.generate_default()
91
+ os_defaults = (
92
+ case RbConfig::CONFIG['host_os']
93
+ when /darwin|mac os/
94
+ {
95
+ 'logpath' => File.join(Dir.home, 'autosign.log'),
96
+ 'confpath' => File.join(Dir.home, '.autosign.conf'),
97
+ 'journalfile' => File.join(Dir.home, '.autosign.journal')
98
+ }
99
+ when /linux/
100
+ {
101
+ 'logpath' => '/var/log/autosign.log',
102
+ 'confpath' => '/etc/autosign.conf',
103
+ 'journalfile' => File.join(Dir.home, '/var/log/autosign.journal')
104
+ }
105
+ when /bsd/
106
+ {
107
+ 'logpath' => '/var/log/autosign.log',
108
+ 'confpath' => '/usr/local/etc/autosign.conf',
109
+ 'journalfile' => File.join(Dir.home, '/var/log/autosign.journal')
110
+ }
111
+ else
112
+ raise Autosign::Exceptions::Error, "unsupported os: #{host_os.inspect}"
113
+ end
114
+ )
115
+
116
+ config = IniParse.gen do |doc|
117
+ doc.section("general") do |general|
118
+ general.option("loglevel", "warn")
119
+ general.option("logfile", os_defaults['logpath'])
120
+ general.option("journalfile", os_defaults['journalfile'])
121
+ end
122
+ doc.section("jwt_token") do |jwt_token|
123
+ jwt_token.option("secret", SecureRandom.base64(15))
124
+ jwt_token.option("validity", 7200)
125
+ end
126
+ end.to_ini
127
+ raise Autosign::Exceptions::Error, "file #{os_defaults['confpath']} already exists, aborting" if File.file?(os_defaults['confpath'])
128
+ File.write(os_defaults['confpath'], config)
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,32 @@
1
+ require 'logging'
2
+
3
+ module Autosign
4
+ class Decoder
5
+ def self.decode_csr(csr)
6
+ @log = Logging.logger['Autosign::Decoder']
7
+ @log.debug "decoding CSR"
8
+
9
+ begin
10
+ csr = OpenSSL::X509::Request.new(csr)
11
+ rescue OpenSSL::X509::RequestError
12
+ @log.error "Rescued OpenSSL::X509::RequestError; unable to decode CSR"
13
+ return nil
14
+ end
15
+
16
+ # extract challenge password
17
+ challenge_password = csr.attributes.select { |a| a.oid == 'challengePassword' }.first.value.value.first.value
18
+
19
+ # extract common name
20
+ common_name = /^\/CN=(\S*)$/.match(csr.subject.to_s)[1]
21
+
22
+ output = {
23
+ :challenge_password => challenge_password,
24
+ :common_name => common_name
25
+ }
26
+
27
+ @log.info "Decoded CSR for CN: " + output[:common_name].to_s
28
+ @log.debug "Decoded CSR as: " + output.to_s
29
+ return output
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,123 @@
1
+ require 'logging'
2
+ require 'yaml/store'
3
+
4
+ # Autosign::Journal checks for one-time keys that have been used before
5
+
6
+ module Autosign
7
+ class Journal
8
+ attr_accessor :settings
9
+ def initialize(settings = {})
10
+ @log = Logging.logger['Autosign::Journal']
11
+ @log.debug "initializing Autosign::Journal"
12
+ @settings = settings
13
+ fail unless self.setup
14
+ end
15
+
16
+ def setup
17
+ journalfile = self.settings['journalfile']
18
+ store = YAML::Store.new(journalfile, true)
19
+ store.ultra_safe = true
20
+ return store
21
+ end
22
+
23
+ # Check whether a UUID already exists in the journal
24
+ #
25
+ # ==== Attributes
26
+ #
27
+ # * +uuid+ - Unique journal entry identifier
28
+ #
29
+ # ==== Examples
30
+ #
31
+ # To exit if a token has already been used:
32
+ #
33
+ # journal = Autosign::Journal.new({journalfile = '/etc/autosign/journal')
34
+ # exit 1 if journal.check('d2e601c8-93df-4459-be18-1877eaf00920')
35
+ def check(uuid)
36
+ fail unless validate_uuid(uuid)
37
+ true
38
+ end
39
+
40
+ def delete(uuid)
41
+ fail unless validate_uuid(uuid)
42
+ true
43
+ end
44
+
45
+
46
+ # Add a new token to the journal
47
+ #
48
+ # ==== Attributes
49
+ #
50
+ # * +uuid+ - Unique journal entry identifier
51
+ # * +validto+ - Integer seconds unix timestamp that the token will be valid until
52
+ # * +data+ - Arbitrary hash that will be serialized and stored in the journal for auditing purposes
53
+ #
54
+ # ==== Examples
55
+ #
56
+ # To attempt adding a token to the journal:
57
+ #
58
+ # journal = Autosign::Journal.new({journalfile = '/etc/autosign/journal')
59
+ # exit 1 if journal.add('d2e601c8-93df-4459-be18-1877eaf00920')
60
+ #
61
+ # This will only succeed if the token has not previously been added
62
+ # This is the primary way this class is expected to be used
63
+ def add(uuid, validto, data = {})
64
+ @log.debug "attempting to add UUID: '#{uuid.to_s}' which is valid to '#{Time.at(validto.to_i)}' with data #{data.to_s}"
65
+ puts validate_uuid(uuid).to_s
66
+ #fail unless validate_uuid(uuid)
67
+ #fail unless validate_timestamp(validto)
68
+ #fail unless validate_data(data)
69
+
70
+ store = self.setup
71
+ # wrap the change in a transaction because multiple autosign instances
72
+ # may try to run simultaneously. This will block until another process
73
+ # releases the transaction lock.
74
+ result = store.transaction do
75
+ # check whether the UUID is already in the store
76
+ if store.root?(uuid)
77
+ @log.warn "Token with UUID '#{uuid}' is already saved in the journal, will not add'"
78
+ store.abort
79
+ else
80
+ # save the token identified by UUID
81
+ store[uuid.to_s] = {:validto => validto.to_s, :data => data}
82
+ end
83
+ end
84
+
85
+ # return true if the transaction went through
86
+ return !!result
87
+ end
88
+
89
+ def validate_uuid(uuid)
90
+ unless uuid.is_a?(String)
91
+ @log.error "UUID is not a string"
92
+ return false
93
+ end
94
+
95
+ unless !!/^\S{8}-\S{4}-4\S{3}-[89abAB]\S{3}-\S{12}$/.match(uuid.to_s)
96
+ @log.error "UUID is not a valid V4 UUID"
97
+ return false
98
+ end
99
+ end
100
+
101
+ def validate_data(data)
102
+ unless data.is_a?(Hash)
103
+ @log.error "data is not a hash"
104
+ return false
105
+ end
106
+ end
107
+
108
+ def validate_timestamp(time)
109
+ unless time.is_a?(Integer)
110
+ @log.error "timestamp is not an integer"
111
+ return false
112
+ end
113
+
114
+ if Time.at(time) > Time.now
115
+ @log.debug "validated timestamp: " + time
116
+ return true
117
+ else
118
+ @log.error "invalid timestamp: " + time
119
+ return false
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,7 @@
1
+ module Autosign
2
+ require 'logging'
3
+ @logger = Logging.logger['autosign_logger']
4
+ @logger.level = :warn
5
+
6
+
7
+ end
@@ -0,0 +1,143 @@
1
+ module Autosign
2
+ require 'jwt'
3
+ require 'JSON'
4
+ require 'securerandom'
5
+
6
+ class Token
7
+ attr_reader :validfor
8
+ attr_reader :certname
9
+ attr_reader :reusable
10
+ attr_reader :requester
11
+ attr_reader :secret
12
+ attr_accessor :validto
13
+ attr_accessor :uuid
14
+
15
+ def initialize(certname, reusable=false, validfor=7200, requester, secret)
16
+ # set up logging
17
+ @log = Logging.logger['Autosign::Token']
18
+ @log.debug "initializing"
19
+
20
+ @validfor = validfor
21
+ @certname = certname
22
+ @reusable = reusable
23
+ @requester = requester
24
+ @secret = secret
25
+ @uuid = SecureRandom.uuid # UUID is needed to allow token regeneration with the same settings
26
+ @validto = Time.now.to_i + self.validfor
27
+ end
28
+
29
+ def self.validate(requested_certname, token, hmac_secret)
30
+ @log = Logging.logger['Autosign::Token.validate']
31
+ @log.debug "attempting to validate token"
32
+ @log.info "attempting to validate token for: #{requested_certname.to_s}"
33
+ errors = []
34
+ begin
35
+ @log.debug "Decoding and parsing token"
36
+ data = JSON.parse(JWT.decode(token, hmac_secret)[0]["data"])
37
+ rescue JWT::ExpiredSignature
38
+ @log.warn "Token has an expired signature"
39
+ errors << "Expired Signature"
40
+ rescue
41
+ @log.warn "Unable to validate token successfully"
42
+ errors << "Invalid Token"
43
+ end
44
+ @log.warn "validation failed with: #{errors.join(', ')}" unless errors.count == 0
45
+ certname_is_regex = (data["certname"] =~ /\/[^\/].*\//) ? true : false
46
+
47
+ if certname_is_regex
48
+ @log.debug "validating certname as regular expression"
49
+ regexp = Regexp.new(/\/([^\/].*)\//.match(data["certname"])[1])
50
+ unless regexp.match(requested_certname)
51
+ errors << "certname: '#{requested_certname}' does not match validation regex: '#{regexp.to_s}'"
52
+ end
53
+ else
54
+ unless data["certname"] == requested_certname
55
+ errors << "certname: '#{requested_certname}' does not match certname '#{data["certname"]}' in token"
56
+ end
57
+ end
58
+
59
+ unless errors.count == 0
60
+ @log.warn "validation failed with: #{errors.join(', ')}"
61
+ return false
62
+ else
63
+ @log.info "validated token successfully"
64
+ return true
65
+ end
66
+
67
+ # we should never get here, but if we do we should break instead of returning anything
68
+ @log.error "unexpectedly reached end of validation method"
69
+ raise Autosign::Token::ValidationError
70
+ end
71
+
72
+ def self.token_validto(token, hmac_secret)
73
+ begin
74
+ decoded = JWT.decode(token, hmac_secret)[0]
75
+ rescue JWT::ExpiredSignature
76
+ raise Autosign::Token::ExpiredToken
77
+ rescue
78
+ raise Autosign::Token::Invalid
79
+ end
80
+ return decoded['exp'].to_i
81
+ end
82
+
83
+ def self.from_token(token, hmac_secret)
84
+ begin
85
+ decoded = JWT.decode(token, hmac_secret)[0]
86
+ rescue JWT::ExpiredSignature
87
+ raise Autosign::Token::ExpiredToken
88
+ rescue
89
+ raise Autosign::Token::Invalid
90
+ end
91
+ certname = JSON.parse(decoded["data"])["certname"]
92
+ requester = JSON.parse(decoded["data"])["requester"]
93
+ reusable = JSON.parse(decoded["data"])["reusable"]
94
+ validfor = JSON.parse(decoded["data"])["validfor"]
95
+
96
+ new_token = self.new(certname, reusable, validfor, requester, hmac_secret)
97
+ new_token.validto = self.token_validto(token, hmac_secret)
98
+ new_token.uuid = JSON.parse(decoded["data"])["uuid"]
99
+
100
+ return new_token
101
+ end
102
+
103
+ def certname=(str)
104
+ @name = str
105
+ end
106
+
107
+ def reusable=(bool)
108
+ @reusable = !!bool
109
+ end
110
+
111
+ def reusable
112
+ !!@reusable
113
+ end
114
+
115
+ def requester=(str)
116
+ @requester = str
117
+ end
118
+
119
+ def secret=(str)
120
+ @secret = str
121
+ end
122
+
123
+ def to_hash
124
+ {
125
+ "certname" => self.certname,
126
+ "requester" => self.requester,
127
+ "reusable" => self.reusable,
128
+ "validfor" => self.validfor,
129
+ "uuid" => self.uuid
130
+ }
131
+ end
132
+
133
+ def to_json
134
+ JSON.generate self.to_hash
135
+ end
136
+
137
+ def sign()
138
+ exp_payload = { :data => self.to_json, :exp => self.validto.to_s}
139
+ JWT.encode exp_payload, self.secret, 'HS512'
140
+ end
141
+
142
+ end
143
+ end