class WiKID::Auth

Constants

VERSION

Public Class Methods

new(host_or_file, port, keyfile, keypass, cafile = @@DEFAULT_CA_FILE) click to toggle source

This constructor allows the Auth_WiKID module to be initialized from either a properties file or via explicit arguments.

@param [string] host_or_file Either the IP address or hostname of

the wAuth server, or the path to a
properties file

@param [int] port The SSL listener port for the wAuth

daemon on the wAuth server

@param [string] keyfile The PKCS12 keystore generated for this

client by the wAuth server

@param [string] keypass The passphrase securing the keys in keyfile @param [string] cafile The certificate authority store for

validating the wAuth server certificate

The contents of the properties file should contain the following key-value pairs:

  • host - The IP address or hostname of the wAuth server

  • port - The SSL listener port for the wAuth daemon on the wAuth server

  • keyfile - The PKCS12 keystore generated for client by the wAuth server

  • pass - The passphrase securing the keys in keyfile

  • cafile - The PEM-encoded certificate file for validating the wAuth server certificate

end

# File lib/WiKID.rb, line 110
def initialize(host_or_file, port, keyfile, keypass, cafile = @@DEFAULT_CA_FILE)

  unless SSLEnabled
    raise RuntimeError.new('Ruby/OpenSSL module is required for WiKID authentication.')
  end

  if (File.exist?(host_or_file))
    # props = parse_ini_file(host_or_file)
    props = Hash.new

    @host = props['host']
    @port = props['port']
    @keyfile = props['keyfile']
    @keypass = props['pass']
    @cafile = props['cafile']
  else
    @host = host_or_file.untaint
    @port = port.untaint
    @keyfile = keyfile.untaint
    @keypass = keypass.untaint
    unless cafile.nil? || cafile.empty?
      @cafile = cafile.untaint
    end
  end
  if (!@port.is_a?(Integer))
    @port = 0
  end

  _dprint("WiKID.rb initialized: host=#{@host}, port=#{@port}, keyfile=#{@keyfile}, cafile=#{@cafile}")

  ## simple hack to allow for testing during gem installation (prevents security errors since keys may not yet be available)
  unless port == -1
    checkKeys()
  end

  return true
end

Public Instance Methods

chapVerify(username, domaincode, wikidChallenge = '', chapPassword = '', chapChallenge = '') click to toggle source

Verifies the credentials via challenge-response.

@note Not currently supported by the Open Source release of WiKID.

@return [boolean] 'true' indicates credentials were valid,

'false' if credentials were invalid or
an error occurred

end

# File lib/WiKID.rb, line 522
def chapVerify(username, domaincode, wikidChallenge = '', chapPassword = '', chapChallenge = '')

  _dprint('chapVerify() called ...')
  reconnect()
  validCredentials = false
  valid_tag = 'VERIFY:VALID'
  _dprint('Checking Chap Credentials')

  mesg = "CHAPOFFVERIFY:" + username + "\t" + "nil" + "\t" + domaincode + "\t" + wikidChallenge

  reconnect {

    $sslsocket.puts(chapPassword.length)
    $sslsocket.puts(chapPassword)
    $sslsocket.puts(chapChallenge.length)
    $sslsocket.puts(chapChallenge.length)
    $sslsocket.flush

    _dprint("Reading in...")

    inputLine = $sslsocket.gets.chomp
    if (inputLine[0, valid_tag.length] == valid_tag)
      validCredentials = true
    end
  }

  return validCredentials
end
checkCredentials(username, passcode, domaincode = '127000000001') click to toggle source

Verifies the credentials that are generated using the standard authentication method.

@param [string] username Users login ID in this authentication domain @param [string] passcode Passcode provided by the user @param [string] domaincode 12 digit code representing the

authentication domain

@return [boolean] 'true' indicates credentials were valid,

'false' if credentials were invalid or
an error occurred

end

# File lib/WiKID.rb, line 464
    def checkCredentials(username, passcode, domaincode = '127000000001')

      _dprint("checkCredentials(#{username}, #{passcode}, #{domaincode}) called ...")

      validCredentials = false
      offline_challenge = ''
      offline_response = ''
      chap_password = ''
      chap_challenge = ''
      valid_tag = 'VERIFY:VALID'

      _dprint('Checking Credentials...')

      mesg = "VERIFY:" + username + "\t" + passcode + "\t" + domaincode
      mesg = <<XML
    <transaction>
        <type format="base">2</type>
        <data>
            <user-id>#{username}</user-id>
            <passcode>#{passcode}</passcode>
            <domaincode>#{domaincode}</domaincode>
            <offline-challenge encoding="none">#{offline_challenge}</offline-challenge>
            <offline-response encoding="none">#{offline_response}</offline-response>
            <chap-password encoding="none">#{chap_password}</chap-password>
            <chap-challenge encoding="none">#{chap_challenge}</chap-challenge>
            <result>null</result>
        </data>
    </transaction>
XML

      reconnect {

        xml = _request(mesg)
        response = XPath.first(xml, '//data/result')

        if response =~ /VALID/
          validCredentials = true
        else
          validCredentials = false
        end
        _dprint('Read response: verdict = ' + validCredentials.to_s)
      }

      _dprint('Returning Results...')
      return validCredentials
    end
checkKeys() click to toggle source
This method checks that the certificates are readable and accessible.

end

# File lib/WiKID.rb, line 179
def checkKeys()

  data = nil
  if (@cafile.nil? || @cafile.empty? || !File.exists?(@cafile) || OpenSSL::X509::Certificate.new(File.read(@cafile)).nil?)
    warn 'CA certificate NOT OK, running without peer verification'
  else
    _dprint('CA certificate OK')
  end

  if (@keyfile.nil? || @keyfile.empty? || !File.exists?(@keyfile) || OpenSSL::X509::Certificate.new(File.read(@keyfile)).nil?)
    raise SecurityError, 'Public key NOT OK!'
  else
    _dprint('Public key OK')
  end

  if (!File.exists?(@keyfile) || OpenSSL::PKey::RSA.new(File.read(@keyfile), @keypass).nil?)
    raise SecurityError, 'Private key NOT OK!'
  else
    _dprint('Private key OK')
  end

end
close() click to toggle source
This method simply closes the connection to the wAuth service.

end

# File lib/WiKID.rb, line 160
def close()
  _dprint('Closing Auth_WiKID connection ...')
  unless $sslsocket.nil?
    unless $sslsocket.closed?
      $sslsocket.puts('QUIT');
      $sslsocket.flush
      $sslsocket.close
    end
    $sslsocket = nil
    @socket.shutdown
  end
  @isConnected = false
end
deleteUser(user_id, domaincode = ' 127000000001 ') click to toggle source

Delete a user by userid

@param [string] user_id The userid on the server, or a user object as returned

by a call to findUser()

@param [string] domaincode 12 digit code representing the authentication domain if first argument is a userid (string), not necessary

if first argument is the user object, which will already have this

@return [boolean] ' true ' indicates deletion was successful

end

# File lib/WiKID.rb, line 720
    def deleteUser(user_id, domaincode = ' 127000000001 ')

      successful = false

      _dprint("deleteUser(#{user_id},#{domaincode})")

      if (user_id.is_a?(String))
        user_xml = findUser(user_id, domaincode)
      end

      if user_xml.nil?
        return false
      end

      _dprint("user: #{user_id}")

      mesg = <<XML
        <transaction>
          <type>7</type>
          <data>
            <domaincode>#{domaincode}</domaincode>
            <user-id>#{user_id}</user-id>
            #{user_xml}
          <result>null</result>
          <return-code>-2147483648</return-code>
        </data>
        </transaction>
XML


      reconnect {
        _dprint('deleting user ...')

        xml = _request(mesg)

        unless xml.nil?
          response = XPath.first(xml, '//data/result')
          _dprint("response: '#{response}'")

          if response.to_s =~ /SUCC?ESS/
            successful = true
          else
            successful = false
          end
        end

        return successful
      }

    end
findUser(username, domaincode = '127000000001') click to toggle source

Find a user by username

@param [string] username the textual id of the user to search for @param [string] domaincode the 12 digit code representating the authentication domain

@return [String] The XML representing the user object, if successful; nil if unsuccesful

end

# File lib/WiKID.rb, line 615
    def findUser(username, domaincode = '127000000001')

      _dprint("findUser() ...");

      user = nil

      mesg = <<XML
      <transaction>
        <type>5</type>
        <data>
          <domaincode>#{domaincode}</domaincode>
          <user-id>#{username}</user-id>
          <result>null</result>
          <return-code>-2147483648</return-code>
        </data>
      </transaction>
XML

      reconnect {
        _dprint("Looking up user ...");
        xml = _request(mesg)

        user_xml = XPath.first(xml, '//data/user')

        return user_xml
      }

    end
getDomains() click to toggle source

Fetches a list of domains served by the currently connected server code.

@note Not currently supported by the Open Source release of WiKID.

@return [string] ' true ' indicates credentials were valid,

' false ' if credentials were invalid or
an error occurred

end

# File lib/WiKID.rb, line 784
    def getDomains()

      _dprint("getDomains() called ...")

      valid_tag = "DOMAINLIST"
      _dprint("Getting Domains")

      mesg = <<XML
    <transaction>
        <type>3</type>
        <data>
            <domain-list>null</domain-list>
        </data>
    </transaction>
XML
      reconnect {
        xml = _request(mesg)
        domains = XPath.match(xml, '//data/domain-list')
      }
      _dprint("Returning Results...")
      return domains
    end
isConnected() click to toggle source

Is the socket connected?

@return [boolean] Status of handle: true indicates connection is active

end

# File lib/WiKID.rb, line 369
def isConnected()
  return @isConnected
end
ping() click to toggle source

Send a big ping transaction to the server to verify the connection is good.

@note This method should not be necessary in typical implementations, but is available nonetheless.

@return [string] the raw response from the server

end

# File lib/WiKID.rb, line 249
def ping()
  mesg = '<transaction> <type>1</type> <data> <value>TX</value> </data> </transaction>'
  xml = _request(mesg)
  return xml
end
preRegister(tokenCode, preRegCode, domaincode = '127000000001') click to toggle source

This method supports user pre-registration. You may upload a list of userids and pre-registration codes into the server via the WiKIDAdmin interface. Users can then use the pre-registration code provided to them securely by the administrator in conjunction with the registration code provided by the WiKID token to register in an expedited manner.

@param [string] tokenCode the registration code provided by the token @param [string] preRegCode the code associated with the username that was uploaded to the server @param [string] domaincode 12 digit code representing the authentication server/domain

@return [boolean] 'true' indicates that the pre-registration was successful;

'false' if not

end

# File lib/WiKID.rb, line 567
    def preRegister(tokenCode, preRegCode, domaincode = '127000000001')

      _dprint('preRegister() called ...')
      successful = false;

      mesg = <<XML
    <transaction>
      <type>10</type>
      <data>
        <token-registration-code>#{tokenCode}</token-registration-code>
        <pre-registration-code>#{preRegCode}</pre-registration-code>
        <domaincode>#{domaincode}</domaincode>
        <error-code>null</error-code>
        <result>null</result>
      </data>
    </transaction>
XML

      reconnect {
        _dprint('Pre-registering ...')
        xml = _request(mesg)

        response = XPath.first(xml, '//data/result')
        _dprint("response: '#{response}'")

        if response.to_s =~ /SUC?CESS/
          successful = true
        else
          successful = false
        end
      }

      _dprint("Read response: verdict = #{successful}")
      return successful

    end
reconnect() { || ... } click to toggle source

This method reconnects to the wAuth server, if the socket handle is dead.

@return [boolean] Whether the socket is connected

end

# File lib/WiKID.rb, line 285
def reconnect()

  _dprint("reconnect() called.")

  begin

    if ($sslsocket.nil? || $sslsocket.closed?)
      _dprint("Socket inactive.  Reconnecting...")

      #puts "Setting up SSL context ..."
      ctx = OpenSSL::SSL::SSLContext.new()

      # Options:
      #    "cert", "key", "client_ca", "ca_file", "ca_path",
      #    "timeout", "verify_mode", "verify_depth",
      #    "verify_callback", "options", "cert_store", "extra_chain_cert"

      ctx.cert = OpenSSL::X509::Certificate.new(File.read(@keyfile))
      ctx.key = OpenSSL::PKey::RSA.new(File.read(@keyfile), @keypass)

      if @cafile.nil? || @cafile.empty?
        ctx.ca_file = nil # @cafile
        ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
      else

        ctx.ca_file = @cafile

        # this next bit might be redundant?
        ctx.cert_store = OpenSSL::X509::Store.new
        ctx.cert_store.set_default_paths
        ctx.cert_store.add_file(@cafile)

        ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
      end
      # ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE

      ctx.timeout = @@timeout

      if ctx.cert.nil?
        _dprint("warning: peer certificate won't be verified this session.")
        ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
      end

      _dprint("Opening socket to #{@host}:#{@port}...")
      @socket = TCPSocket.open(@host, @port)
      _dprint("socket open")
      #@socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, @@timeout)

      #@socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, @@timeout)

      $sslsocket = OpenSSL::SSL::SSLSocket.new(@socket, ctx)
      _dprint("socket created")
      #$sslsocket.sync_close = true

      # $sslsocket should be good now
      _dprint("Connecting SSL socket ...")
      $sslsocket.connect

      _startConnection()

    end

    if block_given?
      #puts "Connecting SSL socket in block ..."
      $sslsocket.connect if $sslsocket.closed?
      yield
      #puts "SSL connection block finished."
    else
      #puts "SSL connection wanting to do something else ..."
      # do something non-OO
    end
  rescue Exception => ex
    warn "Error reading from server: #{ex}"
  end

end
registerUsername(username, regcode, domaincode, groupname = '', passcode = '') click to toggle source

Creates an association between the userid and the device registered by the user.

@param [string] username Users login ID in this authentication domain @param [string] regcode Registration code provided to user when

setting up this domain on users device

@param [string] domaincode 12 digit code representing this

authentication domain

@param [string] passcode Optional passcode provided by the user, to

link this device to an existing registration

@return [int] Result code from the registration attempt

end

# File lib/WiKID.rb, line 390
    def registerUsername(username, regcode, domaincode, groupname = '', passcode = '')

      _dprint('registerUsername() called ...')
      valid_tag = 'REGUSER:SUCESS'

      if (!passcode.nil? && passcode.length > 0)
        _dprint('Adding new device ...')
        command = 'ADDREGUSER'
        type = 4;
        passcodeline = "<passcode>#{passcode}</passcode>";
        format = 'add';
      else
        _dprint('Registering user ...')
        command = 'REGUSER'
        type = 4;
        passcodeline = '<passcode>null</passcode>';
        format = 'new';
      end

      if (!groupname.nil? && groupname.length>0)
        groupnameline="<groupName>#{groupname}</groupName>"
      else
        groupnameline='<groupName>null</groupName>'
      end

      #mesg = "#{command}:#{username}\t#{regcode}\t#{domaincode}\t#{passcode}"
      mesg = <<XML
    <transaction>
        <type format="#{format}">#{type}</type>
        <data>
        <user-id>#{username}</user-id>
        <registration-code>#{regcode}</registration-code>
        <domaincode>#{domaincode}</domaincode>
        #{passcodeline}
      #{groupnameline}
        <error-code>null</error-code>
        <result>null</result>
        </data>
    </transaction>
XML

      #puts mesg
      reconnect {

        _dprint("registerUsername() sending '#{mesg}' ...")

        xml = _request(mesg)
        response = XPath.first(xml, '//data/result')
        _dprint("response: '#{response}'")
        if response.to_s =~ /SUCC?ESS/
          _dprint('Registered!')
          return 0
        else
          err = XPath.first(xml, '//data/error-code/text()')
          _dprint("Failed to register!  Error: #{err}")
          return err
        end
      }

    end
setDebug(newStatus) click to toggle source

Modify the debug state of the current connection

@param [boolean] newStatus

end

# File lib/WiKID.rb, line 813
def setDebug(newStatus)
  @@DEBUG = (newStatus == true) ? true : false
end
updateUser(user_id, domaincode = '127000000001', updateUserXml = '') click to toggle source

Update the previously “found” user

This method is used to update a user object on the server. The network client certificate that was used to establish the wClient connection must be authorized to perform this action.

@param [string] user_id The userid on the server, or a user object as returned

by a call to findUser()

@param [string] domaincode 12 digit code representing the authentication domain if first argument is a userid (string), not necessary

if first argument is the user object, which will already have this

@return [boolean] 'true' indicates the update was successful

end

# File lib/WiKID.rb, line 659
    def updateUser(user_id, domaincode = '127000000001', updateUserXml = '')

      successful = false

      _dprint("updateUser(#{user_id},#{domaincode})")

      if (user_id.is_a?(String))
        user_xml = findUser(user_id, domaincode)
      end

      if user_xml.nil?
        return false
      end

      _dprint("user_xml: #{user_xml}, updating to #{updateUserXml}")

      mesg = <<XML
        <transaction>
          <type>6</type>
          <data>
            <domaincode>#{domaincode}</domaincode>
            <user-id>#{user_id}</user-id>
            #{updateUserXml}
          <result>null</result>
          <return-code>-2147483648</return-code>
        </data>
        </transaction>
XML

      reconnect {
        _dprint('updating user ...')

        xml = _request(mesg)

        response = XPath.first(xml, '//data/result')
        _dprint("response: '#{response}'")

        if response.to_s =~ /SUCC?ESS/
          successful = true
        else
          successful = false
        end

      }

      return successful
    end

Private Instance Methods

_WiKID() click to toggle source

@note Class destructor, which just calls close(). @api private

# File lib/WiKID.rb, line 150
def _WiKID()
  close()
end
_dprint(msg) click to toggle source

Prints a message if the debug flag is true, time-stamped since the epoch.

@param [string] msg Message to print out @api private @private

end

# File lib/WiKID.rb, line 825
def _dprint(msg)

  if (@@DEBUG)
    show = Time.now.to_s + ' : ' + msg
    show += '<br />' if !ENV['REQUEST_URI'].nil?

    puts show
    #STDERR.puts show
    #STDERR.flush()
  end
  return true
end
_request(mesg) click to toggle source

Send the request and get the response back from the server.

@param [string] mesg The message to send to the server

@return [string] The response from the server

@api private @private _request

end

# File lib/WiKID.rb, line 214
def _request(mesg)
  mesg.gsub!(/\n/, '')
  _dprint("send.request is: #{mesg.inspect}")
  #puts "---------------------------------"
  $sslsocket.puts(mesg)
  $sslsocket.flush

  _dprint("checking response...")
  raw_response = $sslsocket.gets
  _dprint("send.raw_response is: #{raw_response.inspect}")
  response = raw_response.chomp unless raw_response.nil?
  _dprint("send.response is: #{response.inspect}")
  unless response.nil?
    #puts "creating xml"
    xml = Document.new response
    #puts xml.inspect
  else
    #puts 'No response received.'
    xml = nil
  end
  #puts "returning XML"
  return xml
end
_startConnection() click to toggle source

This method initiates the connection to the wAuth server.

@return [boolean] Whether the socket is connected @api private

end

# File lib/WiKID.rb, line 262
def _startConnection()
  _dprint("startConnection() called.")
  valid_tag = "ACCEPT";
  # The client initiates the transaction
  mesg = "CONNECT: WiKID Ruby Client v#{VERSION}"
  mesg = "<transaction> <type>1</type> <data> <client-string>wClient Ruby #{VERSION}</client-string> <server-string>null</server-string> <result>null</result> </data> </transaction>
    "

  xml = _request(mesg);
  result = XPath.first(xml, '//data/result')
  @isConnected = (result == 'ACCEPT')

  return @isConnected
end