autosign 0.0.1

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