Class: Autosign::Token

Inherits:
Object
  • Object
show all
Defined in:
lib/autosign/token.rb

Overview

Class modeling JSON Web Tokens as credentials for certificate auto signing. See jwt.io for more information about JSON web tokens in general.

Returns:

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(certname, reusable = false, validfor = 7200, requester, secret) ⇒ Autosign::Config

Create a token instance to model an individual JWT token

Parameters:

  • certname (String)

    common name or regex of common names for which this token is valid

  • reusable (True, False) (defaults to: false)

    true if the token can be used multiple times, false if the token is intended as a one-time credential

  • validfor (Integer, String) (defaults to: 7200)

    seconds that the token will be valid for, starting at the current time

  • requester (String)

    arbitrary string identifying the person or machine that generated the token initially

  • secret (String)

    shared HMAC secret used to sign or validate tokens



34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/autosign/token.rb', line 34

def initialize(certname, reusable=false, validfor=7200, requester, secret)
  # set up logging
  @log = Logging.logger[self.class]
  @log.debug "initializing #{self.class.name}"

  @validfor  = validfor
  @certname  = certname
  @reusable  = reusable
  @requester = requester
  @secret    = secret
  @uuid      = SecureRandom.uuid # UUID is needed to allow token regeneration with the same settings
  @validto   = Time.now.to_i + self.validfor
end

Instance Attribute Details

#certnameString

Returns common name or regex of common names for which this token is valid.

Returns:

  • (String)

    common name or regex of common names for which this token is valid



14
15
16
# File 'lib/autosign/token.rb', line 14

def certname
  @certname
end

#requesterString

Returns arbitrary string identifying the person or machine that generated the token initially.

Returns:

  • (String)

    arbitrary string identifying the person or machine that generated the token initially



18
19
20
# File 'lib/autosign/token.rb', line 18

def requester
  @requester
end

#reusableTrue, False

Returns true if the token can be used multiple times, false if the token is intended as a one-time credential.

Returns:

  • (True, False)

    true if the token can be used multiple times, false if the token is intended as a one-time credential



16
17
18
# File 'lib/autosign/token.rb', line 16

def reusable
  @reusable
end

#secretString

Returns shared HMAC secret used to sign or validate tokens.

Returns:

  • (String)

    shared HMAC secret used to sign or validate tokens



20
21
22
# File 'lib/autosign/token.rb', line 20

def secret
  @secret
end

#uuidString

Returns RFC4122 v4 UUID functioning as unique identifier for the token.

Returns:

  • (String)

    RFC4122 v4 UUID functioning as unique identifier for the token



24
25
26
# File 'lib/autosign/token.rb', line 24

def uuid
  @uuid
end

#validforInteger, String (readonly)

Returns seconds that the token will be valid for after being issued.

Returns:

  • (Integer, String)

    seconds that the token will be valid for after being issued



12
13
14
# File 'lib/autosign/token.rb', line 12

def validfor
  @validfor
end

#validtoInteger, String

Returns POSIX seconds since epoch that the token is valid until.

Returns:

  • (Integer, String)

    POSIX seconds since epoch that the token is valid until



22
23
24
# File 'lib/autosign/token.rb', line 22

def validto
  @validto
end

Class Method Details

.from_token(token, hmac_secret) ⇒ Autosign::Token

Create an Autosign::Token object from a serialized token after validating the signature and expiration time validity.

Parameters:

  • token (String)

    JSON Web Token coming from the certificate signing request

  • secret (String)

    shared HMAC secret used to sign or validate tokens

Returns:

  • (Autosign::Token)

    instance of Autosign::Token with the settings from the serialized token



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/autosign/token.rb', line 137

def self.from_token(token, hmac_secret)
  begin
    decoded = JWT.decode(token, hmac_secret)[0]
  rescue JWT::ExpiredSignature
    raise Autosign::Token::ExpiredToken
  rescue
    raise Autosign::Token::Invalid
  end
  cert_data = MultiJson.load(decoded["data"])
  new_token = self.new(cert_data["certname"], cert_data["reusable"], cert_data["validfor"],
                       cert_data["requester"], hmac_secret)

  new_token.validto = self.token_validto(token, hmac_secret)
  new_token.uuid = cert_data["uuid"]

  new_token
end

.token_validto(token, hmac_secret) ⇒ Integer

Extract the expiration time, in seconds since epoch, from a signed token. Uses HMAC secret to validate the expiration time.

Parameters:

  • token (String)

    Serialized JSON web token

  • hmac_secret (String)

    Password that the token was (hopefully) originally signed with.

Returns:

  • (Integer)

    POSIX time (seconds since epoch) that the token is valid until



159
160
161
162
163
164
165
166
167
168
# File 'lib/autosign/token.rb', line 159

def self.token_validto(token, hmac_secret)
  begin
    decoded = JWT.decode(token, hmac_secret)[0]
  rescue JWT::ExpiredSignature
    raise Autosign::Token::ExpiredToken
  rescue
    raise Autosign::Token::Invalid
  end
  return decoded['exp'].to_i
end

.validate(requested_certname, token, hmac_secret) ⇒ True, False

Validate an existing JSON Web Token.

  1. Use the HMAC secret to validate the data in the token

  2. Compare the expiration time in the token to the current time to determine if it’s valid

  3. compare the certname or regex of certnames in the token to the requested common name from the certificate signing request

Parameters:

  • requested_certname (String)

    common name coming from the certificate signing request. This is the common name the requester wants.

  • token (String)

    JSON Web Token coming from the certificate signing request

  • hmac_secret (String)

    Password that the token was (hopefully) originally signed with.

Returns:

  • (True, False)

    returns true if the token can be validated, or false if the token cannot be validated.

Raises:

  • (Autosign::Token::ValidationError)


58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/autosign/token.rb', line 58

def self.validate(requested_certname, token, hmac_secret)
  @log = Logging.logger[self.class]
  @log.debug "attempting to validate token"
  @log.info "attempting to validate token for: #{requested_certname.to_s}"
  errors = []
  begin
    @log.debug "Decoding and parsing token"
    data = MultiJson.load(JWT.decode(token, hmac_secret)[0]["data"])
  rescue JWT::ExpiredSignature
    @log.warn "Token has an expired signature"
    errors << "Expired Signature"
  rescue
    @log.warn "Unable to validate token successfully"
    errors << "Invalid Token"
  end
  @log.warn "validation failed with: #{errors.join(', ')}" unless errors.count == 0

  if data.nil?
    @log.error "token is nil; this probably means the token failed to validate"
    return false
  end

  certname_is_regex = (data["certname"] =~ /\/[^\/].*\//) ? true : false

  if certname_is_regex
    @log.debug "validating certname as regular expression"
    regexp = Regexp.new(/\/([^\/].*)\//.match(data["certname"])[1])
    unless regexp.match(requested_certname)
      errors << "certname: '#{requested_certname}' does not match validation regex: '#{regexp.to_s}'"
    end
  else
    unless data["certname"] == requested_certname
      errors << "certname: '#{requested_certname}' does not match certname '#{data["certname"]}' in token"
    end
  end

  unless errors.count == 0
    @log.warn "validation failed with: #{errors.join(', ')}"
    return false
  else
    @log.info "validated token successfully"
    return true
  end

  # we should never get here, but if we do we should break instead of returning anything
  @log.error "unexpectedly reached end of validation method"
  raise Autosign::Token::ValidationError
end

Instance Method Details

#reusable?True, False

check if the token is reusable or a one-time use token

Returns:

  • (True, False)

    return true if the token can be used multiple times, false if the token can only be used once



109
110
111
# File 'lib/autosign/token.rb', line 109

def reusable?
  !!@reusable
end

#signObject

Sign the token with HMAC using a SHA-512 hash



126
127
128
129
# File 'lib/autosign/token.rb', line 126

def sign()
  exp_payload = { :data => to_json, :exp => validto.to_s}
  JWT.encode exp_payload, secret, 'HS512'
end

#to_hashHash{String => String}

convert the token to a hash

Returns:

  • (Hash{String => String})


115
116
117
118
119
120
121
122
123
# File 'lib/autosign/token.rb', line 115

def to_hash
  {
    "certname"  => certname,
    "requester" => requester,
    "reusable"  => reusable?,
    "validfor"  => validfor,
    "uuid"      => uuid
  }
end