lib/yubikey/otp_verify.rb in yubikey-1.2.1 vs lib/yubikey/otp_verify.rb in yubikey-1.3.0
- old
+ new
@@ -1,18 +1,28 @@
+require 'base64'
+require 'securerandom'
+
module Yubikey
- API_URL = 'https://api.yubico.com/wsapi/'
- API_ID = 2549
- API_KEY = 'e928a7d3076516a8c8c879f42c3ea0388f3b19f'
-
+ API_URL = 'https://api.yubico.com/wsapi/2.0/'
+
class OTP::Verify
-
# The raw status from the Yubico server
attr_reader :status
-
- def initialize(otp)
- verify("id=#{API_ID}&otp=#{otp}")
+
+ def initialize(args)
+ raise(ArgumentError, "Must supply API ID") if args[:api_id].nil?
+ raise(ArgumentError, "Must supply API Key") if args[:api_key].nil?
+ raise(ArgumentError, "Must supply OTP") if args[:otp].nil?
+
+ @api_key = args[:api_key]
+ @api_id = args[:api_id]
+
+ @url = args[:url] || API_URL
+ @nonce = args[:nonce] || OTP::Verify.generate_nonce(32)
+
+ verify(args)
end
def valid?
@status == 'OK'
end
@@ -21,24 +31,66 @@
@status == 'REPLAYED_OTP'
end
private
- def verify(query)
- uri = URI.parse(API_URL) + 'verify'
+ def verify(args)
+ query = "id=#{@api_id}&otp=#{args[:otp]}&nonce=#{@nonce}"
+
+ uri = URI.parse(@url) + 'verify'
uri.query = query
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
req = Net::HTTP::Get.new(uri.request_uri)
result = http.request(req).body
-
+
@status = result[/status=(.*)$/,1].strip
if @status == 'BAD_OTP' || @status == 'BACKEND_ERROR'
raise OTP::InvalidOTPError, "Received error: #{@status}"
end
+
+ if ! verify_response(result)
+ @status = 'BAD_RESPONSE'
+ return
+ end
+ end
+
+ def verify_response(result)
+
+ signature = result[/^h=(.+)$/, 1].strip
+ returned_nonce = result[/nonce=(.+)$/, 1]
+ returned_nonce.strip! unless returned_nonce.nil?
+
+ if @nonce != returned_nonce
+ return false
+ end
+
+ generated_signature = OTP::Verify.generate_hmac(result, @api_key)
+
+ return signature == generated_signature
+ end
+
+
+ def self.generate_nonce(length)
+ return SecureRandom.hex length/2
+ end
+
+
+ def self.generate_hmac(response, api_key)
+ response_params = response.split(' ')
+ response_params.reject! do |p|
+ p =~ /^h=(.+)$/
+ end
+
+ response_string = response_params.sort.join('&')
+ response_string.strip!
+
+ hmac = OpenSSL::HMAC.digest('sha1', Base64.decode64(api_key), response_string)
+
+ return Base64.encode64(hmac).strip
end
end # OTP::Verify
end # Yubikey