| Class | Yubico |
| In: |
lib/yubico.rb
|
| Parent: | Object |
| E_OK | = | "OK" | Returned if all is well with the request. | |
| E_REPLAYED_OTP | = | "REPLAYED_OTP" | Returned if the server has seen that OTP for that key before. | |
| E_BAD_OTP | = | "BAD_OTP" | Returned if the OTP is invalid or misformed. | |
| E_NO_SUCH_CLIENT | = | "NO_SUCH_CLIENT" | Returned if such a client doesn‘t exist | |
| E_POLICY_VIOLATION | = | "POLICY_VIOLATION" | Returned if a specified request violates the rules for the verifier. Typically this happens when you send an unsigned request but the verifier is set up to only use signed requests. | |
| E_BAD_SIGNATURE | = | "BAD_SIGNATURE" | Returned if the signature on the request was invalid. | |
| E_OPERATION_NOT_ALLOWED | = | "OPERATION_NOT_ALLOWED" | Returned if the operation is not allowed, or otherwise denied. | |
| E_INCORRECT_PARAMETER | = | "INCORRECT_PARAMETER" | Returned if a parameter is bad or missing, respectively. (Also returns an "info" field — needs to be parsed (1) ) | |
| E_MISSING_PARAMETER | = | "MISSING_PARAMETER" | ||
| E_BACKEND_ERROR | = | "BACKEND_ERROR" | Returned on a Backend Error | |
| E_MISSING_SECRET | = | "MISSING_SECRET" | Returned when the object is constructed without a shared key, on requests that require the shared key. | |
| E_UNKNOWN_ERROR | = | "UNKNOWN_ERROR" | Returned on an unknown error server side. |
| _id | [RW] | The requested client or verifier ID for the instance of the object. |
| _key | [RW] | The shared key, if required, for the client or verifier. |
| _res | [RW] | The response from the server. |
Initialization is used to create the instance variables at creation. Yubikey.new requires in the least an ID, and also takes a shared key.
# File lib/yubico.rb, line 125
125: def initialize(id, key='')
126: @_id = id
127: @_key = key
128: end
Requests add_key information from the server and returns all the information required to encode a Yubikey. This command will create a new key on request, so standard practice should be very secure and sure before this is called.
if( user.created? )
user.keyflag = yk.add_key( @_id, timestamp )
user.keydata = yk.last_response
end
# =>
# File lib/yubico.rb, line 195
195: def add_key( id, nonce )
196: if not @_key
197: return E_MISSING_SECRET
198: end
199:
200: operation = "add_key"
201: mesg = "id=#{id}&nonce=#{nonce}&operation=#{operation}"
202: key = self.hmac( @_id, mesg, 'sha1' )
203: url = URI.parse("http://api.yubico.com/wsapi/add_key?operation=#{operation}&id=#{id}&nonce=#{nonce}&h=#{key}")
204: http = Net::HTTP.new(url.host, 443)
205: http.use_ssl = true
206: res = http.get(url.path + "?" + url.query)
207: if( ( res.body ) && ( /status=([\w_]+)[\s]/.match(res.body) ) )
208: @_res = res.body
209: return res.body.scan(/status=([\w_]+)[\s]/).first
210: else
211: return E_UNKNOWN_ERROR
212: end
213: end
Signed request that deletes key key_id from the server, with the client ID id and passes nonce as the request nonce.
# File lib/yubico.rb, line 217
217: def delete_key( id, key_id, nonce )
218: if not @_key
219: return E_MISSING_SECRET
220: end
221: operation = "delete_key"
222: mesg = "key_id=#{key_id}&id=#{id}&nonce=#{nonce}&operation=#{operation}"
223: key = self.hmac( @_id, mesg, 'sha1' )
224: url = URI.parse("http://api.yubico.com/wsapi/delete_key?#{mesg}&h=#{key}")
225: http = Net::HTTP.new(url.host, 443)
226: http.use_ssl = true
227: res = http.get(url.path + "?" + url.query)
228: if( (res.body) && ( /status=([\w_]+)[\s]/.match(res.body) ) )
229: @_res = res.body
230: return res.body.scan(/status=([\w_]+)[\s]/).first
231: else
232: return E_UNKNOWN_ERROR
233: end
234: end
Takes the OTP, shared key, and ID, creates the RFC3339 timestamp, and returns the hash.
hash = yubikey_object.signKey( @otp, @shared, @id )
# => "39ccb32d95edfdbcd882f2b01809724ec640ea16"
# File lib/yubico.rb, line 253
253: def hmac( key, msg, algorithm )
254: OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new(algorithm), key, msg)
255: end
Return the last available Yubikey response body, in full, for parsing.
req = yk.verify( @token, @_id )
res = req.last_response
# => "h=...\nstatus=OK"
On an error, there will be many fields returned by this function you can sift out manually using regular expressions (to be implemented: last_response returns an associative array of the response.) for various variables.
req = yk.verify( @token, @_id )
# => "h=Z/D42NucC1nSlIVZqFPR3RO5dG4=\r\nt=2007-10-23T14:26:48Z\r\nstatus=REPLAYED_OTP\r\n\r\n"
res = yk.last_response
res["h"]
# => "h=Z/D42NucC1nSlIVZqFPR3RO5dG4="
res["t"]
# => "2007-10-23T14:26:48Z"
res["status"]
# => "REPLAYED_OTP"
res["info"]
# => nil
# File lib/yubico.rb, line 177
177: def last_response
178: hash = @_res.scan(/h=([\w_\=\/]+)[\s]/).first
179: timestamp = @_res.scan(/t=([\w\-:]+)[\s]/).first
180: status = @_res.scan(/status=([a-zA-Z0-9_]+)[\s]/).first
181: info = @_res.scan(/info=([\w\t\S\W]+)/).first
182:
183: return { "h" => hash, "t" => timestamp, "status" => status, "info" => info }
184: end
Verify an Yubikey OTP Token. Returns the value of the response variable "status"
req = yk.verify( @token, @id )
# => STATUS_VARIABLE
Upon an error, you should use the Yubikey.last_response method for a more detailed collection of variables, including the request hash, info and further.
# File lib/yubico.rb, line 137
137: def verify( otp, id = @_id )
138: @_id = id if @_id.nil?
139: # Set up the full URL for the request, then pass it through the +URI+ gauntlet.
140: fullurl = "https://api.yubico.com/wsapi/verify?id="+ (@_id.to_s) +"&otp="+ otp
141: url = URI.parse(fullurl)
142: http = Net::HTTP.new(url.host, 443)
143: http.use_ssl = true
144: # Make the call to the server and return the full response, not just the body (to be used later)
145: res = http.get(url.path + "?" + url.query)
146: # Parse and find the status within the response's body, to verify this is, in fact, what we're looking for.
147: if ( !(/status=([a-zA-Z0-9_]+)/.match(res.body)) )
148: # If it's not, let's raise an error.
149: raise "Response Error: "+ res.body
150: else
151: @_res = res.body
152: end
153: # Finally, return the response value.
154: @_res.scan(/status=([a-zA-Z0-9_]+)[\s]/).first.to_s
155: end