Access VBA and Smart Cards - what's the trick?

1

I'm trying to play with a smart card using Windows 10/Access 2016 and VBA, and I have come across just about every example I could possibly find of how to make this happen, including:

The issue that I'm having is that SCardEstablishContext is returning 0x0 - SCARD_S_SUCCESS, but when I pass the SCARDCONTEXT over to SCardIsValidContext, it's returning 0x6 - ERROR_INVALID_HANDLE.

Here's the relevant parts of the code that I'm using (ignore AuthDict and SCardAuthCode as those are just helper functions to decode whatever status the other functions return)

Public AuthDict As Scripting.Dictionary
Public Const SCARD_SCOPE_USER As Long = &H0
Public Const SCARD_SCOPE_SYSTEM As Long = &H2
Public Const SCARD_SHARE_SHARED As Long = &H2
Public Const SCARD_SHARE_EXCLUSIVE As Long = &H1
Public Const SCARD_SHARE_DIRECT As Long = &H3
Public Const SCARD_PROTOCOL_T0 As Long = &H1
Public Const SCARD_PROTOCOL_T1 As Long = &H2
Public Const SCARD_DEFAULT_READERS As String = "SCard$DefaultReaders\000"
Public Const SCARD_ALL_READERS As String = "SCard$AllReaders\000"

Public Type SCARDCONTEXT
    CardContext1 As Long
    ReaderName As String
End Type

Public Declare PtrSafe Function SCardEstablishContext Lib "winscard.dll" ( _
    ByVal dwScope As Long, _
    ByVal pvReserved1 As Long, _
    ByVal pvReserved2 As Long, _
    ByRef phContext As SCARDCONTEXT _
    ) As Long

Public Declare PtrSafe Function SCardIsValidContext Lib "winscard.dll" ( _
    ByRef hContext As SCARDCONTEXT _
) As Long

Public Sub GetContext()
    Dim lreturn As Long
    Dim RSVD1 As Long, RSVD2 As Long
    Dim myContext As SCARDCONTEXT
    Set AuthDict = New Scripting.Dictionary

    Debug.Print "-----------------------------------------------------------------------------"
    lreturn = SCardEstablishContext(SCARD_SCOPE_USER, RSVD1, RSVD2, myContext)
    SCardAuthCode lreturn
    Debug.Print "SCardEstablishContext:" & vbCrLf & _
                "   Return = " & AuthDict("hr") & vbCrLf & _
                "   Value = " & AuthDict("hc") & vbCrLf & _
                "   Description = " & AuthDict("hd") & vbCrLf & _
                "   myContext.CardContext1 = " & myContext.CardContext1 & vbCrLf & _
                "   myContext.ReaderName = " & Chr(34) & myContext.ReaderName & Chr(34) & vbCrLf
    lreturn = SCardIsValidContext(myContext)
    SCardAuthCode lreturn
    Debug.Print "SCardIsValidContext:" & vbCrLf & _
                "   Return = " & AuthDict("hr") & vbCrLf & _
                "   Value = " & AuthDict("hc") & vbCrLf & _
                "   Description = " & AuthDict("hd") & vbCrLf
    If lreturn <> 0 Then GoTo GetContextExit
GetContextExit:
    Debug.Print "-----------------------------------------------------------------------------" & vbCrLf
End Sub

Run the Sub, here's the output:

-----------------------------------------------------------------------------
SCardEstablishContext:
   Return = 0x00000000
   Value = SCARD_S_SUCCESS
   Description = No error was encountered.
   myContext.CardContext1 = -855572480
   myContext.ReaderName = ""

SCardIsValidContext:
   Return = 0x00000006
   Value = ERROR_INVALID_HANDLE
   Description = The handle is invalid.

-----------------------------------------------------------------------------

It seems that I'm not setting myContext correctly, but I'm at a loss for what it should actually look like.

Also, here's the code for SCardAuthCode if you want the pretty return:

'https://docs.microsoft.com/en-us/windows/win32/secauthn/authentication-return-values?redirectedfrom=MSDN#smart_card_return_values
'https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-?redirectedfrom=MSDN
Public Function SCardAuthCode(lreturn As Long)
    AuthDict.RemoveAll
    Dim hreturn As String, hc As String, hd As String
    hreturn = "0x" & Right("0000000" & Hex(lreturn), 8)
    Select Case hreturn
        Case "0x00000000": hc = "SCARD_S_SUCCESS": hd = "No error was encountered."
        Case "0x00000006": hc = "ERROR_INVALID_HANDLE": hd = "The handle is invalid."
        Case "0x00000109": hc = "ERROR_BROKEN_PIPE": hd = "The client attempted a smart card operation in a remote session, such as a client session running on a terminal server, and the operating system in use does not support smart card redirection."
        Case "0x80100001": hc = "SCARD_F_INTERNAL_ERROR": hd = "An internal consistency check failed."
        Case "0x80100002": hc = "SCARD_E_CANCELLED": hd = "The action was canceled by an SCardCancel request."
        Case "0x80100003": hc = "SCARD_E_INVALID_HANDLE": hd = "The supplied handle was not valid."
        Case "0x80100004": hc = "SCARD_E_INVALID_PARAMETER": hd = "One or more of the supplied parameters could not be properly interpreted."
        Case "0x80100005": hc = "SCARD_E_INVALID_TARGET": hd = "Registry startup information is missing or not valid."
        Case "0x80100006": hc = "SCARD_E_NO_MEMORY": hd = "Not enough memory available to complete this command."
        Case "0x80100007": hc = "SCARD_F_WAITED_TOO_LONG": hd = "An internal consistency timer has expired."
        Case "0x80100008": hc = "SCARD_E_INSUFFICIENT_BUFFER": hd = "The data buffer for returned data is too small for the returned data."
        Case "0x80100009": hc = "SCARD_E_UNKNOWN_READER": hd = "The specified reader name is not recognized."
        Case "0x8010000A": hc = "SCARD_E_TIMEOUT": hd = "The user-specified time-out value has expired."
        Case "0x8010000B": hc = "SCARD_E_SHARING_VIOLATION": hd = "The smart card cannot be accessed because of other outstanding connections."
        Case "0x8010000C": hc = "SCARD_E_NO_SMARTCARD": hd = "The operation requires a smart card, but no smart card is currently in the device."
        Case "0x8010000D": hc = "SCARD_E_UNKNOWN_CARD": hd = "The specified smart card name is not recognized."
        Case "0x8010000E": hc = "SCARD_E_CANT_DISPOSE": hd = "The system could not dispose of the media in the requested manner."
        Case "0x8010000F": hc = "SCARD_E_PROTO_MISMATCH": hd = "The requested protocols are incompatible with the protocol currently in use with the card."
        Case "0x80100010": hc = "SCARD_E_NOT_READY": hd = "The reader or card is not ready to accept commands."
        Case "0x80100011": hc = "SCARD_E_INVALID_VALUE": hd = "One or more of the supplied parameter values could not be properly interpreted."
        Case "0x80100012": hc = "SCARD_E_SYSTEM_CANCELLED": hd = "The action was canceled by the system, presumably to log off or shut down."
        Case "0x80100013": hc = "SCARD_F_COMM_ERROR": hd = "An internal communications error has been detected."
        Case "0x80100014": hc = "SCARD_F_UNKNOWN_ERROR": hd = "An internal error has been detected, but the source is unknown."
        Case "0x80100015": hc = "SCARD_E_INVALID_ATR": hd = "An ATR string obtained from the registry is not a valid ATR string."
        Case "0x80100016": hc = "SCARD_E_NOT_TRANSACTED": hd = "An attempt was made to end a nonexistent transaction."
        Case "0x80100017": hc = "SCARD_E_READER_UNAVAILABLE": hd = "The specified reader is not currently available for use."
        Case "0x80100018": hc = "SCARD_P_SHUTDOWN": hd = "The operation has been aborted to allow the server application to exit."
        Case "0x80100019": hc = "SCARD_E_PCI_TOO_SMALL": hd = "The PCI receive buffer was too small."
        Case "0x8010001A": hc = "SCARD_E_READER_UNSUPPORTED": hd = "The reader driver does not meet minimal requirements for support."
        Case "0x8010001B": hc = "SCARD_E_DUPLICATE_READER": hd = "The reader driver did not produce a unique reader name."
        Case "0x8010001C": hc = "SCARD_E_CARD_UNSUPPORTED": hd = "The smart card does not meet minimal requirements for support."
        Case "0x8010001D": hc = "SCARD_E_NO_SERVICE": hd = "The smart card resource manager is not running."
        Case "0x8010001E": hc = "SCARD_E_SERVICE_STOPPED": hd = "The smart card resource manager has shut down."
        Case "0x8010001F": hc = "SCARD_E_UNEXPECTED": hd = "An unexpected card error has occurred."
        Case "0x80100020": hc = "SCARD_E_ICC_INSTALLATION": hd = "No primary provider can be found for the smart card."
        Case "0x80100021": hc = "SCARD_E_ICC_CREATEORDER": hd = "The requested order of object creation is not supported."
        Case "0x80100022": hc = "SCARD_E_UNSUPPORTED_FEATURE": hd = "This smart card does not support the requested feature."
        Case "0x80100023": hc = "SCARD_E_DIR_NOT_FOUND": hd = "The specified directory does not exist in the smart card."
        Case "0x80100024": hc = "SCARD_E_FILE_NOT_FOUND": hd = "The specified file does not exist in the smart card."
        Case "0x80100025": hc = "SCARD_E_NO_DIR": hd = "The supplied path does not represent a smart card directory."
        Case "0x80100026": hc = "SCARD_E_NO_FILE": hd = "The supplied path does not represent a smart card file."
        Case "0x80100027": hc = "SCARD_E_NO_ACCESS": hd = "Access is denied to the file."
        Case "0x80100028": hc = "SCARD_E_WRITE_TOO_MANY": hd = "An attempt was made to write more data than would fit in the target object."
        Case "0x80100029": hc = "SCARD_E_BAD_SEEK": hd = "An error occurred in setting the smart card file object pointer."
        Case "0x8010002A": hc = "SCARD_E_INVALID_CHV": hd = "The supplied PIN is incorrect."
        Case "0x8010002B": hc = "SCARD_E_UNKNOWN_RES_MNG": hd = "An unrecognized error code was returned."
        Case "0x8010002C": hc = "SCARD_E_NO_SUCH_CERTIFICATE": hd = "The requested certificate does not exist."
        Case "0x8010002D": hc = "SCARD_E_CERTIFICATE_UNAVAILABLE": hd = "The requested certificate could not be obtained."
        Case "0x8010002E": hc = "SCARD_E_NO_READERS_AVAILABLE": hd = "No smart card reader is available."
        Case "0x8010002F": hc = "SCARD_E_COMM_DATA_LOST": hd = "A communications error with the smart card has been detected."
        Case "0x80100030": hc = "SCARD_E_NO_KEY_CONTAINER": hd = "The requested key container does not exist on the smart card."
        Case "0x80100031": hc = "SCARD_E_SERVER_TOO_BUSY": hd = "The smart card resource manager is too busy to complete this operation."
        Case "0x80100032": hc = "SCARD_E_PIN_CACHE_EXPIRED": hd = "The smart card PIN cache has expired."
        Case "0x80100033": hc = "SCARD_E_NO_PIN_CACHE": hd = "The smart card PIN cannot be cached."
        Case "0x80100034": hc = "SCARD_E_READ_ONLY_CARD": hd = "The smart card is read-only and cannot be written to."
        Case "0x80100065": hc = "SCARD_W_UNSUPPORTED_CARD": hd = "The reader cannot communicate with the card, due to ATR string configuration conflicts."
        Case "0x80100066": hc = "SCARD_W_UNRESPONSIVE_CARD": hd = "The smart card is not responding to a reset."
        Case "0x80100067": hc = "SCARD_W_UNPOWERED_CARD": hd = "Power has been removed from the smart card, so that further communication is not possible."
        Case "0x80100068": hc = "SCARD_W_RESET_CARD": hd = "The smart card was reset."
        Case "0x80100069": hc = "SCARD_W_REMOVED_CARD": hd = "The smart card has been removed, so further communication is not possible."
        Case "0x8010006A": hc = "SCARD_W_SECURITY_VIOLATION": hd = "Access was denied because of a security violation."
        Case "0x8010006B": hc = "SCARD_W_WRONG_CHV": hd = "The card cannot be accessed because the wrong PIN was presented."
        Case "0x8010006C": hc = "SCARD_W_CHV_BLOCKED": hd = "The card cannot be accessed because the maximum number of PIN entry attempts has been reached."
        Case "0x8010006D": hc = "SCARD_W_EOF": hd = "The end of the smart card file has been reached."
        Case "0x8010006E": hc = "SCARD_W_CANCELLED_BY_USER": hd = "The action was canceled by the user."
        Case "0x8010006F": hc = "SCARD_W_CARD_NOT_AUTHENTICATED": hd = "No PIN was presented to the smart card."
        Case "0x80100070": hc = "SCARD_W_CACHE_ITEM_NOT_FOUND": hd = "The requested item could not be found in the cache."
        Case "0x80100071": hc = "SCARD_W_CACHE_ITEM_STALE": hd = "The requested cache item is too old and was deleted from the cache."
        Case "0x80100072": hc = "SCARD_W_CACHE_ITEM_TOO_BIG": hd = "The new cache item exceeds the maximum per-item size defined for the cache."
        Case Else: hc = "UNKNOWN VALUE": hd = "Unknown value."
    End Select
    AuthDict.Add "hr", hreturn
    AuthDict.Add "hc", hc
    AuthDict.Add "hd", hd
End Function
vba
ms-access
vba7
asked on Stack Overflow Apr 21, 2021 by Chaosbydesign • edited Apr 21, 2021 by Chaosbydesign

1 Answer

4

SCARDCONTEXT is not a type, it's a handle. I don't know where that type comes from.

Use a LongPtr for that handle.

SCardEstablishContext uses a pointer to the handle, so ByRef

Public Declare PtrSafe Function SCardEstablishContext Lib "winscard.dll" ( _
    ByVal dwScope As Long, _
    ByVal pvReserved1 As LongPtr, _
    ByVal pvReserved2 As LongPtr, _
    ByRef phContext As LongPtr_
    ) As Long

Note that both pvReserveds are also pointers so LongPtr, not Long. You're only supposed to add PtrSafe after thoroughly checking which Longs should actually be a LongPtr.

While for SCardIsValidContext, the handle is passed directly, so ByVal:

Public Declare PtrSafe Function SCardIsValidContext Lib "winscard.dll" ( _
    ByVal hContext As LongPtr _
) As Long

Then, adjust your code accordingly, which should be trivial.

And of course, don't forget SCardReleaseContext when you're done, Windows can get fussy if you open contexts and never close them.

answered on Stack Overflow Apr 21, 2021 by Erik A

User contributions licensed under CC BY-SA 3.0