ootp/basiccard/HOTPC.BAS

876 lines
20 KiB
QBasic
Raw Normal View History

2017-01-03 11:10:10 +00:00
'
' Copyright (c) 2005,2006 Mark Fullmer
' Copyright (c) 2009 Mark Fullmer and the Ohio State University
' All rights reserved.
'
' Redistribution and use in source and binary forms, with or without
' modification, are permitted provided that the following conditions
' are met:
' 1. Redistributions of source code must retain the above copyright
' notice, this list of conditions and the following disclaimer.
' 2. Redistributions in binary form must reproduce the above copyright
' notice, this list of conditions and the following disclaimer in the
' documentation and/or other materials provided with the distribution.
'
' THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
' ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
' IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
' ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
' FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
' DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
' OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
' HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
' LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
' OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
' SUCH DAMAGE.
'
' $Id: HOTPC.BAS 13 2009-11-26 16:37:03Z maf $
'
#include "sha-1.def"
#include "preader.def"
#include "AlgID.DEF"
#include "commands.def"
#include "hotp.def"
Option Explicit
' number of keys/hosts - max 254. 255 is reserved. Index is 8 bits.
Const HOTPNum = 85
' HOTPCodeVersion notes:
' Rev 1 - first production version - feb 2006
' Rev 2 - disable AdminMode on first usage - safety net
' if left on by mistake.
' Rev 3 - Count32 commands
' - conditional compile commands
' - Get/Set/Test naming standardize
' Rev 4 - release of Rev3
' Rev 5 - renumber commands, readerKey, getCapabilities
' - ClearALL, checkReaderKey
' Rev 6 - release Rev5
' Code version
Const HOTPCodeVersion = 6
' Capabilities (conditionally compiled in functions)
eeprom Capabilities as Long = CAPSETHOST + CAPGETHOST + CAPGETHOSTNAME + _
CAPGETHOTP + CAPSETADMINMODE + _
CAPSETBALANCECARDINDEX + CAPSETPIN + _
CAPTESTPIN + CAPGETVERSION + _
CAPSETADMINKEY + CAPSETHOST32 + _
CAPGETHOST32 + CAPGETHOTPCOUNT32 + _
CAPGETHOTPHOST + CAPGETHOTPHOSTCOUNT32 + _
CAPPRDISPLAY + CAPCLEARALL + CAPSETREADERKEY
Const DefaultPIN = "28165"
' Default Admin Key
eeprom AdminKey as String*20 = "00000000000000000000"
' HOTPK and its derivatives
' default Key "00000000000000000000"
' KeyI is Key XOR input pad (0x36)
' IPAD44 is 0x36 repeated 44 times
' KeyO is Key XOR output pad (0x5c)
' OPAD44 is 0x5c repeated 44 times.
' Note that only the I and O versions of the keys
' are stored -- which is all that's needed.
' An alternative implementation could choose to store
' the key and compute the I and O versions at run time
' this would use less EEPROM space at the cost of more
' CPU cycles (and less battery life) to compute a HOTP
eeprom HOTPKeyI(HOTPNum) as string*20 = &H06,&H06,&H06,&H06,&H06,&H06,&H06, _
&H06,&H06,&H06,&H06,&H06,&H06,&H06, _
&H06,&H06,&H06,&H06,&H06,&H06
eeprom HOTPKeyO(HOTPNum) as string*20 = &H6C,&H6C,&H6C,&H6C,&H6C,&H6C,&H6C, _
&H6C,&H6C,&H6C,&H6C,&H6C,&H6C,&H6C, _
&H6C,&H6C,&H6C,&H6C,&H6C,&H6C
eeprom HOTPHost(HOTPNum) as string*12
eeprom HOTPCount32(HOTPNum) as Long
' Input pad
eeprom IPAD44 as string*44 = &H36,&H36,&H36,&H36,&H36,&H36,&H36,&H36,&H36, _
&H36,&H36,&H36,&H36,&H36,&H36,&H36,&H36,&H36, _
&H36,&H36,&H36,&H36,&H36,&H36,&H36,&H36,&H36, _
&H36,&H36,&H36,&H36,&H36,&H36,&H36,&H36,&H36, _
&H36,&H36,&H36,&H36,&H36,&H36,&H36,&H36
' Output pad
eeprom OPAD44 as string*44 = &H5C,&H5C,&H5C,&H5C,&H5C,&H5C,&H5C,&H5C,&H5C, _
&H5C,&H5C,&H5C,&H5C,&H5C,&H5C,&H5C,&H5C,&H5C, _
&H5C,&H5C,&H5C,&H5C,&H5C,&H5C,&H5C,&H5C,&H5C, _
&H5C,&H5C,&H5C,&H5C,&H5C,&H5C,&H5C,&H5C,&H5C, _
&H5C,&H5C,&H5C,&H5C,&H5C,&H5C,&H5C,&H5C
' Temporary message digest (inner)
eeprom md1 as string*20
' PIN - used to deter unauthorized use
eeprom PIN as string*5 = DefaultPIN
' Reader Key. Weak authentication for reader
eeprom readerKey as string*5 = "00000"
' The balance card can only use one of the host definitions.
eeprom BalanceCardIndex = 255 ' Disabled
' if AdminMode is not set some commands require a valid PIN to work.
eeprom AdminMode as Byte = 1
' Keep track of PIN failures
eeprom PINFailCount as Byte = 0
eeprom readerKeyFailCount as Byte = 0
Const MaxPINFail = 10
Const MaxReaderKeyFail = 2
Function CheckPIN(myPIN as string*5) as Byte
if AdminMode = 1 then
CheckPIN = 0 ' success
PINFailCount = 0 ' reset
else
if PINFailCount >= MaxPINFail then
CheckPIN = 2 ' fail
else
if myPIN <> PIN then
CheckPIN = 1 ' fail
PINFailCount = PINFailCount + 1
else
CheckPIN = 0 ' Success
PINFailCount = 0
end if
end if
end if
end Function
Function CheckReaderKey(idx as Byte, myKey as String*5) as Byte
if (Asc(HOTPHost(idx)(2)) AND &H80) then
if myKey <> readerKey then
readerKeyFailCount = readerKeyFailCount + 1
if (readerKeyFailCount >= MAXReaderKeyFail) then
PINFailCount = MaxPINFail ' Lock card
end if
CheckReaderKey = 1 ' Fail
else
readerKeyFailCount = 0
CheckReaderKey = 0 ' Success
end if
else
CheckReaderKey = 0 'Success
end if
end Function
Function CheckAdmin() as Byte
if AdminMode <> 1 then
CheckAdmin = 1 ' fail
else
CheckAdmin = 0 ' success
end if
end Function
Function CheckIndex(Idx as Byte) as Byte
if Idx > HOTPNum then
CheckIndex = 1
else
CheckIndex = 0
end if
end function
'
' The balance reader doesn't wait long for a reponse so the HMAC must be
' broken up into steps
'
' HMAC as defined in RFC2104 is H(K XOR opad, H(K XOR ipad, text))
' where K is the key
' ipad=0x36
' opad=0x5c
' H=SHA-160
' text=Count
'
#ifdef ENABLEPRDISPLAY
Command &HC8 &H00 PRDisplay(RecordNumber as Byte, DataFormat as Byte, _
DigitCount as Byte,DecimalPoint as Byte, _
Delay as Byte, MoreData as Byte, _
Data as String)
private str11 as string*11
private str8 as string*8
private str4 as string*4 at str8+4
private low as long at str8+4
private high as long at str8
if BalanceCardIndex = 255 then
data = "Not Enabled"
dataFormat = PRAlpha
exit command
end if
select case RecordNumber
case 0
DataFormat = PRAlpha
DigitCount = 0
DecimalPoint = 0
Delay = 1
MoreData = PRMoreData
' disable admin mode on first use.
if (AdminMode = 1) then
AdminMode = 0
end if
low = HOTPCount32(0)
' Display VER1-count
data = "VER1-" + hex$(low)
' start inner hash of HMAC SHA-160(K XOR ipad,text)
call ShaStart()
call ShaAppend(HOTPKeyI(0))
case 1
DataFormat = PRAlpha
DigitCount = 0
DecimalPoint = 6
Delay = 1
MoreData = PRMoreData
data = "HOTP"
low = HOTPcount(0)
' inner hash still working
call ShaAppend(IPAD44)
call ShaAppend(str8)
case 2
DataFormat = PRAlpha
DigitCount = 0
DecimalPoint = 5
Delay = 1
MoreData = PRMoreData
Data = "HOTP"
' done with inner hash. Store temp result in md1
md1 = ShaEnd()
case 3
DataFormat = PRAlpha
DigitCount = 0
DecimalPoint = 4
Delay = 1
MoreData = PRMoreData
Data = "HOTP"
' start outer hash H(K XOR opad, inner)
call ShaStart()
call ShaAppend(HOTPKeyO(0))
case 4
DataFormat = PRAlpha
DigitCount = 0
DecimalPoint = 3
Delay = 1
MoreData = PRMoreData
data = "HOTP"
' outer still working.
call ShaAppend(OPAD44)
case 5
DataFormat = PRAlpha
DigitCount = 0
DecimalPoint = 2
Delay = 1
MoreData = PRMoreData
data = "HOTP"
' outer still working...
call ShaAppend(md1)
' Increment Count just before displaying result.
Disable OverflowCheck
HOTPCount32(0) = HOTPCount32(0) + 1
Enable OverflowCheck
case 6
DataFormat = PRHex
DigitCount = 10
DecimalPoint = 0
Delay = 10000 / PRDelayUnits ' display 10 seconds
MoreData = PRNoMoreData
' finish with outer, display top 40 bits in hex (no leading 0's).
str11 = "000" + left$(ShaEnd(),8)
data = left$(str11,8)
case else
' should not happen
SW1SW2=swDataNotFound
end select
End Command
#endif ' ENABLECPRDISPLAY
#ifdef ENABLECSETHOST
command &H80 &H40 SetHost(Idx as Byte, Count as Integer, _
HostName as String*12, HOTPKey as String*20)
private tmp as string*20
private tmpb1 as long at tmp
private tmpb2 as long at tmp+4
private tmpb3 as long at tmp+8
private tmpb4 as long at tmp+12
private tmpb5 as long at tmp+16
if CheckAdmin() <> 0 then
SW1SW2 = swAccessDenied
Exit
end if
if CheckIndex(Idx) <> 0 then
SW1SW2 = swDataNotFound
Exit
End if
' store K
' HOTPKey(n) = K
' store K XOR IPAD
tmp = HOTPKey
tmpb1 = tmpb1 xor &H36363636
tmpb2 = tmpb2 xor &H36363636
tmpb3 = tmpb3 xor &H36363636
tmpb4 = tmpb4 xor &H36363636
tmpb5 = tmpb5 xor &H36363636
HOTPKeyI(idx) = tmp
' store K XOR OPAD
tmp = HOTPKey
tmpb1 = tmpb1 xor &H5C5C5C5C
tmpb2 = tmpb2 xor &H5C5C5C5C
tmpb3 = tmpb3 xor &H5C5C5C5C
tmpb4 = tmpb4 xor &H5C5C5C5C
tmpb5 = tmpb5 xor &H5C5C5C5C
HOTPKeyO(idx) = tmp
HOTPCount32(Idx) = Count
HOTPHost(Idx) = HostName
end command
#endif 'ENABLECSETHOST
#ifdef ENABLECGETHOST
' GetHost
command &H80 &H42 GetHost(Idx as Byte, Count as Integer, _
HostName as String*12, HOTPKey as String*20)
private tmp as string*20
private tmpb1 as long at tmp
private tmpb2 as long at tmp+4
private tmpb3 as long at tmp+8
private tmpb4 as long at tmp+12
private tmpb5 as long at tmp+16
if CheckAdmin() <> 0 then
sw1sw2 = swAccessDenied
Exit
end if
if CheckIndex(Idx) <> 0 then
sw1sw2 = swDataNotFound
Exit
end if
Count = HOTPCount32(Idx)
HostName = HOTPHost(Idx)
' load K XOR IPAD
' could also do this with OPAD...
tmp = HOTPKeyI(Idx)
tmpb1 = tmpb1 xor &H36363636
tmpb2 = tmpb2 xor &H36363636
tmpb3 = tmpb3 xor &H36363636
tmpb4 = tmpb4 xor &H36363636
tmpb5 = tmpb5 xor &H36363636
HOTPKey = tmp
end command
#endif 'ENABLECGETHOST
#ifdef ENABLECGETHOSTNAME
command &H80 &H44 GetHostName(Idx as Byte, myPIN as String*5,_
HostName as String*12)
if CheckPIN(myPIN) <> 0 then
SW1SW2 = swAccessDenied
Exit
end if
if CheckIndex(Idx) <> 0 then
SW1SW2 = swDataNotFound
Exit
end if
HostName = HOTPHost(idx)
end command
#endif 'ENABLECGETHOSTNAME
#ifdef ENABLECGETHOTP
command &H80 &H46 GetHOTP(Idx as Byte, myPIN as String*5, HOTP as String*5)
private str8 as string*8
private low as long at str8+4
if CheckPIN(myPIN) <> 0 then
SW1SW2 = swAccessDenied
Exit
end if
' disable admin mode on first use.
if (AdminMode = 1) then
AdminMode = 0
end if
' don't allow operations with default pin
if myPIN = DefaultPIN then
SW1SW2 = swAccessDenied
Exit
end if
' Check reader access
if CheckReaderKey(Idx, HOTP) <> 0 then
SW1SW2 = swAccessDenied
Exit
end if
if CheckIndex(Idx) <> 0 then
SW1SW2 = swDataNotFound
Exit
end if
low = HOTPCount32(idx)
call ShaStart()
call ShaAppend(HOTPKeyI(idx))
call ShaAppend(IPAD44)
call ShaAppend(str8)
md1 = ShaEnd()
call ShaStart()
call ShaAppend(HOTPKeyO(idx))
call ShaAppend(OPAD44)
call ShaAppend(md1)
Disable OverflowCheck
HOTPCount32(idx) = low + 1
Enable OverflowCheck
HOTP = left$(ShaEnd(),5)
end command
#endif 'ENABLECGETHOTP
#ifdef ENABLECSETADMINMODE
command &H80 &H48 SetAdminMode(Mode as Byte, K as String*20)
if K <> AdminKey then
SW1SW2 = swAccessDenied
else
AdminMode = Mode
end if
end command
#endif 'ENABLECSETADMINMODE
#ifdef ENABLECSETBALANCECARDINDEX
command &H80 &H4A SetBalanceCardIndex(Idx as Byte)
if CheckAdmin() <> 0 then
SW1SW2 = swAccessDenied
exit
else
BalanceCardIndex = Idx
end if
end command
#endif 'ENABLECSETBALANCECARDINDEX
#ifdef ENABLECSETPIN
command &H80 &H4C SetPIN(myPIN as String*5, newPIN as String*5)
if CheckPIN(myPIN) <> 0 then
SW1SW2 = swAccessDenied
else
PIN = newPIN
end if
end command
#endif 'ENABLECSETPIN
#ifdef ENABLECTESTPIN
command &H80 &H4E TestPIN(myPIN as String*5)
private t as Byte
t = CheckPIN(myPIN)
if t <> 0 then
if t = 2 then
SW1SW2 = swBadAuthenticate ' too many tries
else
SW1SW2 = swAccessDenied
end if
end if
end command
#endif 'ENABLECTESTPIN
#ifdef ENABLECGETVERSION
command &H80 &H50 GetVersion(V as Byte)
V = HOTPCodeVersion
end command
#endif 'ENABLECGETVERSION
#ifdef ENABLECSETADMINKEY
command &H80 &H52 SetAdminKey(K as String*20)
if CheckAdmin() <> 0 then
sw1sw2 = swAccessDenied
Exit
end if
AdminKey = K
end command
#endif 'ENABLECSETADMINKEY
#ifdef ENABLECSETHOST32
command &H80 &H54 SetHost32(Idx as Byte, Count32 as Long,_
HostName as String*12, HOTPKey as String*20)
private tmp as string*20
private tmpb1 as long at tmp
private tmpb2 as long at tmp+4
private tmpb3 as long at tmp+8
private tmpb4 as long at tmp+12
private tmpb5 as long at tmp+16
if CheckAdmin() <> 0 then
SW1SW2 = swAccessDenied
Exit
end if
if CheckIndex(Idx) <> 0 then
SW1SW2 = swDataNotFound
Exit
End if
HOTPCount32(Idx) = Count32
HOTPHost(Idx) = HostName
' store K
' HOTPKey(n) = K
' store K XOR IPAD
tmp = HOTPKey
tmpb1 = tmpb1 xor &H36363636
tmpb2 = tmpb2 xor &H36363636
tmpb3 = tmpb3 xor &H36363636
tmpb4 = tmpb4 xor &H36363636
tmpb5 = tmpb5 xor &H36363636
HOTPKeyI(idx) = tmp
' store K XOR OPAD
tmp = HOTPKey
tmpb1 = tmpb1 xor &H5C5C5C5C
tmpb2 = tmpb2 xor &H5C5C5C5C
tmpb3 = tmpb3 xor &H5C5C5C5C
tmpb4 = tmpb4 xor &H5C5C5C5C
tmpb5 = tmpb5 xor &H5C5C5C5C
HOTPKeyO(idx) = tmp
end command
#endif 'ENABLECSETHOST32
#ifdef ENABLECGETHOST32
command &H80 &H56 GetHost32(Idx as Byte, Count32 as Long,_
HostName as String*12, HOTPKey as String*20)
private tmp as string*20
private tmpb1 as long at tmp
private tmpb2 as long at tmp+4
private tmpb3 as long at tmp+8
private tmpb4 as long at tmp+12
private tmpb5 as long at tmp+16
if CheckAdmin() <> 0 then
sw1sw2 = swAccessDenied
Exit
end if
if CheckIndex(Idx) <> 0 then
sw1sw2 = swDataNotFound
Exit
end if
Count32 = HOTPCount32(Idx)
HostName = HOTPHost(Idx)
' load K XOR IPAD
' could also do this with OPAD...
tmp = HOTPKeyI(Idx)
tmpb1 = tmpb1 xor &H36363636
tmpb2 = tmpb2 xor &H36363636
tmpb3 = tmpb3 xor &H36363636
tmpb4 = tmpb4 xor &H36363636
tmpb5 = tmpb5 xor &H36363636
HOTPKey = tmp
end command
#endif 'ENABLECGETHOST32
#ifdef ENABLECGETHOTPCOUNT32
command &H80 &H58 GetHOTPCount32(Idx as Byte, myPIN as String*5,_
Count32 as Long, HOTP as String*5)
private str8 as string*8
private low as long at str8+4
if CheckPIN(myPIN) <> 0 then
SW1SW2 = swAccessDenied
Exit
end if
' disable admin mode on first use.
if (AdminMode = 1) then
AdminMode = 0
end if
' don't allow operations with default pin
if myPIN = DefaultPIN then
SW1SW2 = swAccessDenied
Exit
end if
' Check reader access
if CheckReaderKey(Idx, HOTP) <> 0 then
SW1SW2 = swAccessDenied
Exit
end if
if CheckIndex(Idx) <> 0 then
SW1SW2 = swDataNotFound
Exit
end if
' when Count32 == 0, use stored count
if Count32 <> 0 then
low = Count32
else
low = HOTPCount32(idx)
end if
call ShaStart()
call ShaAppend(HOTPKeyI(idx))
call ShaAppend(IPAD44)
call ShaAppend(str8)
md1 = ShaEnd()
call ShaStart()
call ShaAppend(HOTPKeyO(idx))
call ShaAppend(OPAD44)
call ShaAppend(md1)
Disable OverflowCheck
HOTPCount32(idx) = low + 1
Enable OverflowCheck
HOTP = left$(ShaEnd(),5)
end command
#endif 'ENABE_GETHOTPCOUNT32
#ifdef ENABLECGETHOTPHOST
command &H80 &H5A GetHOTPHost(Idx as Byte, myPIN as String*5,_
HOTP as String*5, HostName as String*12)
private str8 as string*8
private low as long at str8+4
if CheckPIN(myPIN) <> 0 then
SW1SW2 = swAccessDenied
Exit
end if
' disable admin mode on first use.
if (AdminMode = 1) then
AdminMode = 0
end if
' don't allow operations with default pin
if myPIN = DefaultPIN then
SW1SW2 = swAccessDenied
Exit
end if
' Check reader access
if CheckReaderKey(Idx, HOTP) <> 0 then
SW1SW2 = swAccessDenied
Exit
end if
if CheckIndex(Idx) <> 0 then
SW1SW2 = swDataNotFound
Exit
end if
low = HOTPCount32(idx)
call ShaStart()
call ShaAppend(HOTPKeyI(idx))
call ShaAppend(IPAD44)
call ShaAppend(str8)
md1 = ShaEnd()
call ShaStart()
call ShaAppend(HOTPKeyO(idx))
call ShaAppend(OPAD44)
call ShaAppend(md1)
Disable OverflowCheck
HOTPCount32(idx) = low + 1
Enable OverflowCheck
HOTP = left$(ShaEnd(),5)
HostName = HOTPHost(Idx)
end command
#endif 'ENABLECGETHOTPHOST
#ifdef ENABLECGETHOTPHOSTCOUNT32
command &H80 &H5C GetHOTPHostCount32(Idx as Byte, myPIN as String*5,_
Count32 as Long, HOTP as String*5, HostName as String*12)
private str8 as string*8
private low as long at str8+4
if CheckPIN(myPIN) <> 0 then
SW1SW2 = swAccessDenied
Exit
end if
' disable admin mode on first use.
if (AdminMode = 1) then
AdminMode = 0
end if
' don't allow operations with default pin
if myPIN = DefaultPIN then
SW1SW2 = swAccessDenied
Exit
end if
' Check reader access
if CheckReaderKey(Idx, HOTP) <> 0 then
SW1SW2 = swAccessDenied
Exit
end if
if CheckIndex(Idx) <> 0 then
SW1SW2 = swDataNotFound
Exit
end if
' when Count32 == 0, use stored count
if Count32 <> 0 then
low = Count32
else
low = HOTPCount32(idx)
end if
call ShaStart()
call ShaAppend(HOTPKeyI(idx))
call ShaAppend(IPAD44)
call ShaAppend(str8)
md1 = ShaEnd()
call ShaStart()
call ShaAppend(HOTPKeyO(idx))
call ShaAppend(OPAD44)
call ShaAppend(md1)
Disable OverflowCheck
HOTPCount32(idx) = low + 1
Enable OverflowCheck
HOTP = left$(ShaEnd(),5)
HostName = HOTPHost(Idx)
end command
#endif 'ENABLECGETHOTPHOSTCOUNT32
#ifdef ENABLECCLEARALL
command &H80 &H5E ClearAll()
private i,j as Integer
if CheckAdmin() <> 0 then
SW1SW2 = swAccessDenied
Exit
end if
for i = 0 to HOTPNum
HOTPKeyI(i) = Chr$(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)
HOTPKeyO(i) = Chr$(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)
HOTPCount32(i) = 0
HOTPHost(i) = Chr$(0,0,0,0,0,0,0,0,0,0,0,0)
next i
BalanceCardIndex = 255
PINFailCount = 0
AdminMode = 1
AdminKey = "00000000000000000000"
PIN = DefaultPIN
end command
#endif 'ENABLECCLEARALL
#ifdef ENABLESETREADERKEY
command &H80 &H60 SetReaderKey(myKey as String*5)
if CheckAdmin() <> 0 then
SW1SW2 = swAccessDenied
Exit
end if
readerKey = myKey
end command
#endif 'ENABLECSETREADERKEY
command &H80 &H90 GetCapabilities(C as Long)
C = Capabilities
end command