Commit a1fe6794 authored by qwelp's avatar qwelp

add output format options

parent 5a59e1ae
# nfcUID
nfcUID - terminal program for reading NFC tag UID by NFC reader and write it as keyboard output.
Console application for reading NFC tag UID by NFC reader and write it as keyboard output.
## Tech
Program used Go bindings to the PC/SC API.
https://github.com/ebfe/scard
It is works with ACS readers through their PC/SC driver. So driver should be installed first.
You can find driver in manufacturer site https://www.acs.com.hk/
## Overview
Application read NFC tag UID using PC/SC API.
PC/SC is a standard to interface computers with smartcards, available on most operating systems, including Windows, MacOS, Linux.
UID writed in active text input field by generating keystrokes on the keyboard.
So cursor should be in some text input field.
UID output format options are available.
## Supported readers
Program tested with readers:
Application should work with any PC/SC compatible reader. It is tested with ACS readers:
- ACR122U
- ACR1281U-C1
- ACR1252U-M1
## Supported NFC tags
Program tested with tags:
Application should work with any NFC tag with UID. It is tested with NXP tags:
- Mifare Classic
- Mifare Ultralight
- NTAG203
- NTAG213
- NTAG216
## Download/Install
1. Intall PC/SC driver for your reader (https://www.acs.com.hk/)
2. Download archive from release page and extract it. No installation needed.
3. Connect reader to PC.
4. In terminal windows launch program binary from extracted archive.
5. Open text field where you want to write tag UID (e.g. Notepad window, Excel cell etc)
6. Touch NFC tag to reader. Tag UID will be writted to current cursor postion in text field.
7. Release tag from reader.
## Install
## Build
To build your own binary just clone repo and use golang build command
```golang
go get github.com/taglme/nfcuid
```
## Build
```golang
go build
```
## Demo
You can see how it works in this video
https://www.youtube.com/watch?v=38UCCXbQPL0
## Flags
There are options for application that should be specified as flags.
- '-device' (integer) - device number to use. Set to 0 if your want to select it manually or set to the desired device number to auto-select.
- '-caps-lock' (boolean) - UID output with caps lock
- '-decimal' (boolean) - UID output in decimal format
- '-reverse' (boolean) - UID output in reverse order
- '-end-char' (string) - character at the end of UID. Options: 'hyphen', 'enter', 'semicolon', 'colon', 'comma', 'none', 'space', 'tab', (default "none")
- '-int-char' (string) - character between bytes of UID. Options: 'hyphen', 'enter', 'semicolon', 'colon', 'comma', 'none', 'space', 'tab', (default "none")
Run with '-h' flag to check usage
```
nfcUID -h
```
## Examples
```golang
//This will auto-select first available PC/SC device in system
//Output will be in direct order
//Bytes of UID will be in hex format
//Between bytes of UID will be hyphen ("-") printed
//At end of UID newline will be printed
nfcUID -end-char=enter -in-char=hyphen -caps-lock=false -reverse=false -decimal=false -device=1
//Output
04-ae-65-ca-82-49-80
```
\ No newline at end of file
package main
type CharFlag int
const (
CharFlagNone CharFlag = iota
CharFlagSpace
CharFlagTab
CharFlagHyphen
CharFlagEnter
CharFlagSemiColon
CharFlagColon
CharFlagComma
)
type CharFlagDef struct {
name string
output string
}
var charFlagStrings = map[CharFlag]CharFlagDef{
CharFlagNone: CharFlagDef{"none", ""},
CharFlagSpace: CharFlagDef{"space", " "},
CharFlagTab: CharFlagDef{"tab", "\\t"},
CharFlagHyphen: CharFlagDef{"hyphen", "-"},
CharFlagEnter: CharFlagDef{"enter", "\\n"},
CharFlagSemiColon: CharFlagDef{"semicolon", ";"},
CharFlagColon: CharFlagDef{"colon", ":"},
CharFlagComma: CharFlagDef{"comma", ","},
}
func StringToCharFlag(s string) (CharFlag, bool) {
for k, v := range charFlagStrings {
if v.name == s {
return k, true
}
}
return 0, false
}
func (charFlag CharFlag) Name() string {
return charFlagStrings[charFlag].name
}
func (charFlag CharFlag) Output() string {
return charFlagStrings[charFlag].output
}
func CharFlagOptions() string {
var s string
for _, v := range charFlagStrings {
s = s + "'" + v.name + "', "
}
return s
}
package main
import (
"fmt"
"os"
"github.com/ebfe/scard"
"github.com/taglme/string2keyboard"
"errors"
"flag"
)
func errorExit(err error) {
fmt.Println(err)
os.Exit(1)
}
func waitUntilCardPresent(ctx *scard.Context, readers []string) (int, error) {
rs := make([]scard.ReaderState, len(readers))
for i := range rs {
rs[i].Reader = readers[i]
rs[i].CurrentState = scard.StateUnaware
}
for {
for i := range rs {
if rs[i].EventState&scard.StatePresent != 0 {
return i, nil
}
rs[i].CurrentState = rs[i].EventState
}
err := ctx.GetStatusChange(rs, -1)
if err != nil {
return -1, err
}
}
}
func waitUntilCardRelease(ctx *scard.Context, readers []string, index int) error {
rs := make([]scard.ReaderState, 1)
rs[0].Reader = readers[index]
rs[0].CurrentState = scard.StatePresent
for {
if rs[0].EventState&scard.StateEmpty != 0 {
return nil
}
rs[0].CurrentState = rs[0].EventState
err := ctx.GetStatusChange(rs, -1)
if err != nil {
return err
}
}
}
func main() {
// Establish a context
ctx, err := scard.EstablishContext()
if err != nil {
errorExit(err)
var appFlags Flags
var endChar, inChar string
var ok bool
//Read application flags
flag.StringVar(&endChar, "end-char", "none", "Character at the end of UID. Options: "+CharFlagOptions())
flag.StringVar(&inChar, "in-char", "none", "Сharacter between bytes of UID. Options: "+CharFlagOptions())
flag.BoolVar(&appFlags.CapsLock, "caps-lock", false, "UID with Caps Lock")
flag.BoolVar(&appFlags.Reverse, "reverse", false, "UID reverse order")
flag.BoolVar(&appFlags.Decimal, "decimal", false, "UID in decimal format")
flag.IntVar(&appFlags.Device, "device", 0, "Device number to use")
flag.Parse()
//Check flags
appFlags.EndChar, ok = StringToCharFlag(endChar)
if !ok {
errorExit(errors.New("Unknown end character flag. Run with '-h' flag to check options"))
return
}
defer ctx.Release()
// List available readers
readers, err := ctx.ListReaders()
if err != nil {
errorExit(err)
appFlags.InChar, ok = StringToCharFlag(inChar)
if !ok {
errorExit(errors.New("Unknown in character flag. Run with '-h' flag to check options"))
return
}
fmt.Printf("Found %d readers:\n", len(readers))
for i, reader := range readers {
fmt.Printf("[%d] %s\n", i, reader)
}
if len(readers) > 0 {
for {
fmt.Println("Waiting for a Card")
index, err := waitUntilCardPresent(ctx, readers)
if err != nil {
errorExit(err)
}
// Connect to card
fmt.Println("Connecting to card in ", readers[index])
card, err := ctx.Connect(readers[index], scard.ShareExclusive, scard.ProtocolAny)
if err != nil {
errorExit(err)
}
defer card.Disconnect(scard.ResetCard)
var cmd = []byte{0xFF, 0xCA, 0x00, 0x00, 0x00}
rsp, err := card.Transmit(cmd)
if err != nil {
errorExit(err)
}
uid := string(rsp[0:7])
uidS := fmt.Sprintf("%x", uid)
fmt.Printf("Tag UID is: %s\n", uidS)
fmt.Printf("Writting as keyboard input...")
string2keyboard.KeyboardWrite(uidS)
fmt.Printf("Done.\n")
card.Disconnect(scard.ResetCard)
//Wait while card will be released
fmt.Print("Waiting for card release...")
err = waitUntilCardRelease(ctx, readers, index)
fmt.Println("Card released.")
}
}
service := NewService(appFlags)
service.Start()
}
package main
import (
"bufio"
"bytes"
"errors"
"fmt"
"os"
"runtime"
"strconv"
"strings"
"github.com/ebfe/scard"
"github.com/taglme/string2keyboard"
)
type Service interface {
Start()
Flags() Flags
}
func NewService(flags Flags) Service {
return &service{flags}
}
type Flags struct {
CapsLock bool
Reverse bool
Decimal bool
EndChar CharFlag
InChar CharFlag
Device int
}
type service struct {
flags Flags
}
func (s *service) Start() {
//Establish a context
ctx, err := scard.EstablishContext()
if err != nil {
errorExit(err)
}
defer ctx.Release()
//List available readers
readers, err := ctx.ListReaders()
if err != nil {
errorExit(err)
}
if len(readers) < 1 {
errorExit(errors.New("Devices not found. Try to plug-in new device and restart"))
}
fmt.Printf("Found %d device:\n", len(readers))
for i, reader := range readers {
fmt.Printf("[%d] %s\n", i+1, reader)
}
if s.flags.Device == 0 {
//Device should be selected by user input
for {
fmt.Print("Enter device number to start: ")
inputReader := bufio.NewReader(os.Stdin)
deviceStr, _ := inputReader.ReadString('\n')
if runtime.GOOS == "windows" {
deviceStr = strings.Replace(deviceStr, "\r\n", "", -1)
} else {
deviceStr = strings.Replace(deviceStr, "\n", "", -1)
}
deviceInt, err := strconv.Atoi(deviceStr)
if err != nil {
fmt.Println("Please input integer value")
continue
}
if deviceInt < 0 {
fmt.Println("Please input positive integer value")
continue
}
if deviceInt > len(readers) {
fmt.Printf("Value should be less than or equal to %d\n", len(readers))
continue
}
s.flags.Device = deviceInt
break
}
} else if s.flags.Device < 0 {
errorExit(errors.New("Device flag should positive integer"))
return
} else if s.flags.Device > len(readers) {
errorExit(errors.New("Device flag should not exceed the number of available devices"))
return
}
fmt.Println("Selected device:")
fmt.Printf("[%d] %s\n", s.flags.Device, readers[s.flags.Device-1])
selectedReaders := []string{readers[s.flags.Device-1]}
for {
fmt.Println("Waiting for a Card")
index, err := waitUntilCardPresent(ctx, selectedReaders)
if err != nil {
errorExit(err)
}
//Connect to card
fmt.Println("Connecting to card...")
card, err := ctx.Connect(selectedReaders[index], scard.ShareExclusive, scard.ProtocolAny)
if err != nil {
errorExit(err)
}
defer card.Disconnect(scard.ResetCard)
//GET DATA command
var cmd = []byte{0xFF, 0xCA, 0x00, 0x00, 0x00}
rsp, err := card.Transmit(cmd)
if err != nil {
errorExit(err)
}
if len(rsp) < 2 {
fmt.Println("Not enough bytes in answer. Try again")
card.Disconnect(scard.ResetCard)
continue
}
//Check response code - two last bytes of response
rspCodeBytes := rsp[len(rsp)-2 : len(rsp)]
successResponseCode := []byte{0x90, 0x00}
if !bytes.Equal(rspCodeBytes, successResponseCode) {
fmt.Printf("Operation failed to complete. Error code % x\n", rspCodeBytes)
card.Disconnect(scard.ResetCard)
continue
}
uidBytes := rsp[0 : len(rsp)-2]
fmt.Printf("UID is: % x\n", uidBytes)
fmt.Printf("Writting as keyboard input...")
string2keyboard.KeyboardWrite(s.formatOutput(uidBytes))
fmt.Printf("Done\n")
card.Disconnect(scard.ResetCard)
//Wait while card will be released
fmt.Print("Waiting for card release...")
err = waitUntilCardRelease(ctx, selectedReaders, index)
fmt.Println("Card released")
}
}
func (s *service) Flags() Flags {
return s.flags
}
func errorExit(err error) {
fmt.Println(err)
os.Exit(1)
}
func (s *service) formatOutput(rx []byte) string {
var output string
//Reverse UID in flag set
if s.flags.Reverse {
for i, j := 0, len(rx)-1; i < j; i, j = i+1, j-1 {
rx[i], rx[j] = rx[j], rx[i]
}
}
for i, rxByte := range rx {
var byteStr string
if s.flags.Decimal {
byteStr = fmt.Sprintf("%03d", rxByte)
} else {
if s.flags.CapsLock {
byteStr = fmt.Sprintf("%02X", rxByte)
} else {
byteStr = fmt.Sprintf("%02x", rxByte)
}
}
output = output + byteStr
if i < len(rx)-1 {
output = output + s.flags.InChar.Output()
}
}
output = output + s.flags.EndChar.Output()
return output
}
func waitUntilCardPresent(ctx *scard.Context, readers []string) (int, error) {
rs := make([]scard.ReaderState, len(readers))
for i := range rs {
rs[i].Reader = readers[i]
rs[i].CurrentState = scard.StateUnaware
}
for {
for i := range rs {
if rs[i].EventState&scard.StatePresent != 0 {
return i, nil
}
rs[i].CurrentState = rs[i].EventState
}
err := ctx.GetStatusChange(rs, -1)
if err != nil {
return -1, err
}
}
}
func waitUntilCardRelease(ctx *scard.Context, readers []string, index int) error {
rs := make([]scard.ReaderState, 1)
rs[0].Reader = readers[index]
rs[0].CurrentState = scard.StatePresent
for {
if rs[0].EventState&scard.StateEmpty != 0 {
return nil
}
rs[0].CurrentState = rs[0].EventState
err := ctx.GetStatusChange(rs, -1)
if err != nil {
return err
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment