How to retrieve CAC/Smart Card info with reader?

1.9k views Asked by At

I'm in the Marines and am trying to make a database to manage an armory with MS Access and SharePoint Lists. I need to add new people when they check in with me so I can immediately assign them a weapon. Entering their information manually is cool, but they have a CAC/Smart Card with all this info on it and I want to see if I can automate this step.

There is an ActivClient Agent application I can open and see all of my info. There's a section called "My Personal Info" with two tabs that have the data I'm trying to get (first name, middle, last, branch, rank, EDIPI). I've recently learned that VBA can use Windows APIs (I also just learned that these exist). The issue I'm having is figuring how to use the resources that are installed on the government computer to access the CAC information. We cannot download any tools or libraries on these computers, it has to be already installed.

I've found winscard.h has some functions that seem like they would be useful, but I don't know how to use them. Can I use winscard.h functions to access and read the data from a second CAC connected to my computer? Should I find a different approach, like making my own .dll that has the functions to do everything I need with the card? I've been learning to program for a bit now, but when it comes to niche topics like this, I'm unsure how to approach the problem the best way.

The steps would generally be: user logs into computer using CAC, open the Access database, navigate to the check-in form, insert the new-join's card into the second card reader, somehow get data from the card, auto-fill the form with the fresh data, and finish doing whatever I make it do. The step I'm stuck on is strictly getting the data from the card.

OS: Windows 10 only

Software: MS Access 32 and 64 bit

Hardware: any Smart Card reader. I plan to share this software with a lot of people that won't have the same exact equipment except for the government computer. Most of the computers have a built in reader, and sometimes they might have standalone readers or keyboards with built in readers.

Alternatively, would it be easier to get a scanner and scan the barcode on the card? I believe the barcode will have the same information I need. Until I can get a scanner approved for use, I would still like to try to use the second Smart Card reader approach since that's what I have right now. Can anything be done with ActivClient or its resources? It uses ac.scapi.scmd.dll, acpkcs211.dll, acbsi21.dll, and acpivapi.dll. I can't find documentation for any of these things. It seems like one or all of them can somehow be used to get the data I want.

Edit:

I followed some of the links shared in the comments and have the following code:

Option Explicit
Option Compare Database

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 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

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

Public Declare PtrSafe Function SCardReleaseContext Lib "winscard.dll" ( _
    ByVal hContext As LongPtr) As Long
    
Public Declare PtrSafe Function SCardConnectA Lib "winscard.dll" ( _
    ByVal hContext As LongPtr, _
    szReader As LongPtr, _
    dwShareMode As LongPtr, _
    dwPreferredProtocols As LongPtr, _
    phCard As LongPtr, _
    pdwActiveProtocol) As LongPtr

Public Function SCardAuthCode(lReturn As Long)
'Credit: https://stackoverflow.com/questions/67199032/access-vba-and-smart-cards-whats-the-trick
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

Public Sub GetContext()
'https://learn.microsoft.com/en-us/windows/win32/secauthn/authentication-return-values?redirectedfrom=MSDN#smart_card_return_values
'https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-?redirectedfrom=MSDN
'Credit: https://stackoverflow.com/questions/67199032/access-vba-and-smart-cards-whats-the-trick

'TODO: Finish SCardConnectA

Dim lReturn As Long
Dim RSVD1 As Long, RSVD2 As Long
Dim myContext, cardCon As LongPtr
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
    
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
    SCardReleaseContext myContext
    Exit Sub
    
ErrorHandler:
    'TODO: handle the errors...
    Resume GetContextExit
    
End Sub

I'm not sure what the point of

Public Type SCARDCONTEXT
    CardContext1 As Long
    ReaderName As String
End Type

is in the code I was following. It was causing me errors, so I just removed it. The code outputs

-----------------------------------------------------------------------------
SCardEstablishContext:
   Return = 0x00000000
   Value = SCARD_S_SUCCESS
   Description = No error was encountered.

SCardIsValidContext:
   Return = 0x00000000
   Value = SCARD_S_SUCCESS
   Description = No error was encountered.

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

I'm not quite sure what I need to do next. It seems like now I need to identify the name of the CAC I want the data from (GetOpenCardNameA?), connect to it (SCardConnectA), and then use SCardTransmit to receive data from the card? I'm not sure if that's how these functions work.

1

There are 1 answers

3
Oxide1 On

Not 100% on sure if you can pull CAC information like that, but I do know that most Air Force CTK programs use TCMax which has a CAC function, and you can 100% set it up for armory use and generate excel status trackers and weapon assignments that way. I would suggest talk to your COMM about TCMax since it is program DOD uses anyway.