module Duo

A Ruby implementation of the Duo WebSDK

Constants

AKEY_LEN
APP_EXPIRE
APP_PREFIX
AUTH_PREFIX
DUO_EXPIRE
DUO_PREFIX
ERR_AKEY
ERR_IKEY
ERR_SKEY
ERR_USER
IKEY_LEN
SKEY_LEN

Public Instance Methods

sign_request(ikey, skey, akey, username) click to toggle source

Sign a Duo 2FA request @param ikey [String] The Duo IKEY @param skey [String] The Duo SKEY @param akey [String] The Duo AKEY @param username [String] Username to authenticate as

# File lib/duo_web.rb, line 29
def sign_request(ikey, skey, akey, username)
  return Duo::ERR_USER if !username || username.empty?
  return Duo::ERR_USER if username.include? '|'
  return Duo::ERR_IKEY if !ikey || ikey.to_s.length != Duo::IKEY_LEN
  return Duo::ERR_SKEY if !skey || skey.to_s.length != Duo::SKEY_LEN
  return Duo::ERR_AKEY if !akey || akey.to_s.length < Duo::AKEY_LEN

  vals = [username, ikey]

  duo_sig = sign_vals(skey, vals, Duo::DUO_PREFIX, Duo::DUO_EXPIRE)
  app_sig = sign_vals(akey, vals, Duo::APP_PREFIX, Duo::APP_EXPIRE)

  return [duo_sig, app_sig].join(':')
end
verify_response(ikey, skey, akey, sig_response) click to toggle source

Verify a Duo 2FA request @param ikey [String] The Duo IKEY @param skey [String] The Duo SKEY @param akey [String] The Duo AKEY @param sig_response [String] Response from Duo service

# File lib/duo_web.rb, line 49
def verify_response(ikey, skey, akey, sig_response)
  begin
    auth_sig, app_sig = sig_response.to_s.split(':')
    auth_user = parse_vals(skey, auth_sig, Duo::AUTH_PREFIX, ikey)
    app_user = parse_vals(akey, app_sig, Duo::APP_PREFIX, ikey)
  rescue
    return nil
  end

  return nil if auth_user != app_user

  return auth_user
end

Private Instance Methods

hmac_sha1(key, data) click to toggle source
# File lib/duo_web.rb, line 65
def hmac_sha1(key, data)
  OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha1'), key, data.to_s)
end
parse_vals(key, val, prefix, ikey) click to toggle source
# File lib/duo_web.rb, line 82
def parse_vals(key, val, prefix, ikey)
  ts = Time.now.to_i

  parts = val.to_s.split('|')
  return nil if parts.length != 3
  u_prefix, u_b64, u_sig = parts

  sig = hmac_sha1(key, [u_prefix, u_b64].join('|'))

  return nil if hmac_sha1(key, sig) != hmac_sha1(key, u_sig)

  return nil if u_prefix != prefix

  cookie_parts = Base64.decode64(u_b64).to_s.split('|')
  return nil if cookie_parts.length != 3
  user, u_ikey, exp = cookie_parts

  return nil if u_ikey != ikey

  return nil if ts >= exp.to_i

  return user
end
sign_vals(key, vals, prefix, expire) click to toggle source
# File lib/duo_web.rb, line 69
def sign_vals(key, vals, prefix, expire)
  exp = Time.now.to_i + expire

  val_list = vals + [exp]
  val = val_list.join('|')

  b64 = Base64.encode64(val).delete("\n")
  cookie = prefix + '|' + b64

  sig = hmac_sha1(key, cookie)
  return [cookie, sig].join('|')
end