API

Zebra VAS SDK 4.1

Overview

The Zebra VAS SDK is an Android library built on native Android NFC APIs and third-party Value-Added Services (VAS) protocols, including Apple VAS, Google Smart Tap, and Samsung Wallet. It simplifies configuring and managing the workflows required to access and process data from NFC passes, such as loyalty cards, coupons, and tickets.

The main SDK components are:

  1. ZebraNfcVas - The main class that manages all interaction with NFC passes. It configures the NFC reader and reads the data from passes, regardless of whether they originate from a mobile wallet.
  2. VasConfig - A base class that provides an interface for storing and accessing merchant parameters required to access an NFC pass. Three types of configuration objects are automatically generated when creating a VasConfig object:
    • AppleVasConfig - A VASConfig object that handles configuration parameters specific to Apple VAS, used for accessing and processing passes stored in Apple Wallet.
    • SmartTapVasConfig - A VASConfig object that handles configuration parameters specific to Google Smart Tap, used for accessing and processing passes stored in Google Wallet.
    • SamsungVasConfig - A VASConfig object that handles configuration parameters specific to Samsung Wallet, used for accessing and processing passes stored in Samsung Wallet.
  3. Pass object - Encapsulates the extracted NFC pass information, including the payload data and supplementary metadata. To comply with security standards (including Apple requirements), this object enables developers to validate the pass type and perform replay-attack prevention by verifying thaat the transmission timestamp is within 10 seconds of the current time.

This section offers guidance on developing an application to read NFC passes using the Zebra VAS SDK. A single application can be built to seamlessly interact with Apple Wallet, Google Wallet, Samsung Wallet, and other NFC passes.


Install SDK

Perform the steps below to install the SDK and prepare to run the developed application on the device:

  1. Download the Zebra VAS SDK from the Zebra Support Portal and extract the downloaded files.
  2. Import the Android .AAR file into the "libs" folder of the Android application project.
  3. Install the Zebra VAS Service .APK on the supported Zebra mobile computer. This service is required to establish communication between the SDK and device.

Note: This SDK requires a minimum Android API Level of 31.

Xamarin Support

To use Zebra VAS SDK in Xamarin, convert "zebranfcvas-release.aar" to a .DLL file. Refer to instructions from Microsoft to bind an .AAR. If .DLL creation fails with errors during the build, resolve the issue by removing TerminalMode with the following change to Metadata.xml, then binding the library:

    <remove-node
        path="/api/package[@name='com.zebra.nfcvas']/class[@name='ZebraNfcVas.TerminalMode']">
    </remove-node>

Terminal Mode is reserved for further implementation and future expansion. Removing it does not cause any harm. Zebra mobile computers are VAS_Only terminals; payment is not supported.


Developer Guide

Zebra VAS SDK is capable of reading Apple Wallet (Apple VAS), Google Wallet (Google Smart Tap), and Samsung Wallet passes. This section provides guidance for developers to build apps based on the type of pass being read. Refer to the sample demo app and corresponding source code.

Apple VAS

This section provides guidance on how to develop an application capable of reading an NFC pass from Apple Wallet.

Developers can select one of the following approaches:

  • Explicitly pass IZebraServiceConnection Interface to ZebraNfcVas object - Directly pass the IZebraServiceConnection interface to the ZebraNfcVas object to acquire callback methods from the Zebra NFC VAS service concerning service connection/disconnection and NFC pass object. This streamlined approach benefits developers using application frameworks like MVC, MVP, or MVVM to manage the service callback, removing the necessity of implementing the IZebraServecConnection within the Activity. Start with step 1 and then skip steps 2 and 3 in the guide provided below.
  • Implement the IZebraServiceConnection interface within the Activity - Implement the IZebraServiceConnection interface within the Activity to receive callback methods from the Zebra NFC VAS service, specifically concerning the service connection/disconnection and NFC pass object. Skip step 1 and proceed with step 2 in the guide below.

Step-by-step guide to creating an application that can read an NFC pass from Apple Wallet:

  1. Explicitly pass IZebraServiceConnection Interface to ZebraNfcVas object - The three callback methods provided by the Zebra NFC VAS service include: onServiceConnected, onServiceDisconnected, and onPassDetected.

    ZebraNfcVas vasSDK = new ZebraNfcVas(this, new IZebraServiceConnection() {
        @Override
        public void onServiceConnection() {
            ...
        }
    
    
    @Override
    public void onServiceDisconnection() {
        ...
    }
    
    @Override
    public void onPassDetected(Pass pass) {
        ...
    }
    
    }); vasSDK.init();

    To explicitly pass IZebraServiceConnection Interface to ZebraNfcVas object, continue to step 4.

  2. Implement IZebraServiceConnection Interface - As an alternative to the approach from step 1, proceed with these next 2 steps to implement the IZebraServiceConnection interface within the Activity. The three callback methods provided by the Zebra NFC VAS service include: onServiceConnected, onServiceDisconnected, and onPassDetected.

    public class AppleVASActivity extends AppCompatActivity implements IZebraServiceConnection {
        ...
    }
    

    Replace "AppleVASActivity" with the name of the class implemented within your application.

  3. Create a ZebraNFCVAS object and intialize the object - ZebraNfcVas object should be declared in onCreate() and initialized by calling init().

    private ZebraNfcVas vasSDK;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init(); //UI Initialization
    
    
    vasSdkInit();
    
    } private void vasSdkInit() { Log.e(TAG, "vasSdkInit"); vasSDK = new ZebraNfcVas(this); vasSDK.init(); }
  4. After initialization, a callback to onServiceConnection is available. Proceed with the subsequent steps for implementation of this method.

    @Override
    public void onServiceConnection() {
        Log.e(TAG,"onServiceConnection");
        //Start with the app implemenation
    }
    

    a. Create an AppleVasConfig object - This is the object that the SDK uses to interact with a presented NFC pass.

    AppleVasConfig av_cfg = new AppleVasConfig();
    

    b. Set the appropriate configuration values in the AppleVasConfig object by using the following APIs:

    • av_cfg.setPassTypeIds(String[] ptis)
    • az_cfg.setPrivateKey(PrivateKeyStr)
    • av_cfg.setFilter(byte[] filter, ZebraNfcVas.FilterType)
    • av_cfg.setMerchantURL(String url)

    Sample code (Note: Replace Merchant URL, Pass Type ID, and private key with your specific values):

    az_cfg.setPrivateKey(ZebraPrivateKeyStr);
    az_cfg.setFilter(new byte[]{(byte) 0xaa, 0x55, (byte) 0xaa, 0x55}, ZebraNfcVas.FilterType.ExactMatch);
    az_cfg.setMerchantURL("https://www.zebra.com/vas-signup");
    az_cfg.setPassTypeIds(new String[]{"pass.com.pronto.zebra-wallet-pass.demo"});
    

    c. Create the ZebraNFCVAS object. This holds the instance of a VasConfig object for pass detection operation.

    vasSDK.createZebraNfcVasConfig();
    

    d. Set the AppleVasConfig object into the ZebraNFCVAS object - This informs the SDK about the configuration data to use when reading the NFC pass. Multiple VasConfigs or a single VasConfig can be provided.

    Multiple VasConfigs:

    vasSDK.setVasConfigs(List<VasConfig>);
    

    Single VasConfig:

    vasSDK.setVasConfig(av_cfg);
    

    e. Call connectToReader - This establishes the connection with the NFC reader.

    vasSDK.connectToReader();
    

    f. Perform one of the following based on your requirement. Only read capability is enabled and polling is configured to read NfcA, NfcB, NfcV, and IsoDep tags:

    • If no additional tags are required to be read, call enableReadPassMode():

      vasSDK.enableReadPassMode();
      
    • If NfcF or Ndef tags are additionally required to be read, call enableReadPassMode() with parameters:

      enableReadPassMode(boolean doSkipNdef, boolean doEnableNFC_F);
      

      For NfcF tags, set "doEnableNFC_F" to "true."
      For Ndef tags, set "doSkipNdef" to "false." Note: Performance could be affected since an NDEF check is performed along with additional checks from the OS after initialzing the tag communication. This affects the processing time of the NFC pass. The advantage of this is the OS reads the NDEF message and it is be enumerated in the Tag object, which can be easily be accessed.

    g. When an NFC pass is read, onPassDetected() is called - This method receives the Pass object when a pass is read. Process the data read from the pass based on business needs.

    public void onPassDetected(Pass pass) {
        Log.v(TAG, ">>> PASS DETECTED <<<");
        // Process data read from the pass
    }
    

    h. Call disableReadPassMode before the app goes to the background or when the activity ends - This returns the device to the default NFC Forum polling mode.

    @Override
    public void onPause() {
        super.onPause();
        Log.i(TAG, "onPause called & Reader Mode Deactivated .....");
        vasSDK.disableReadPassMode();
    }
    

Google Smart Tap

This section provides guidance on how to develop an application capable of reading an NFC pass from Google Wallet.

Developers can select one of the following approaches:

  • Explicitly pass IZebraServiceConnection Interface to ZebraNfcVas object - Directly pass the IZebraServiceConnection interface to the ZebraNfcVas object to acquire callback methods from the Zebra NFC VAS service concerning service connection/disconnection and NFC pass object. This streamlined approach benefits developers using application frameworks like MVC, MVP, or MVVM to manage the service callback, removing the necessity of implementing the IZebraServecConnection within the Activity. Start with step 1 and then skip steps 2 and 3 in the guide provided below.
  • Implement the IZebraServiceConnection interface within the Activity - Implement the IZebraServiceConnection interface within the Activity to receive callback methods from the Zebra NFC VAS service, specifically concerning the service connection/disconnection and NFC pass object. Skip step 1 and proceed with step 2 in the guide below.

Step-by-step guide to creating an application that can read an NFC pass from Google Wallet:

  1. Explicitly pass IZebraServiceConnection Interface to ZebraNfcVas object - The three callback methods provided by the Zebra NFC VAS service include: onServiceConnected, onServiceDisconnected, and onPassDetected.

    ZebraNfcVas vasSDK = new ZebraNfcVas(this, new IZebraServiceConnection() {
        @Override
        public void onServiceConnection() {
            ...
        }
    
    
    @Override
    public void onServiceDisconnection() {
        ...
    }
    
    @Override
    public void onPassDetected(Pass pass) {
        ...
    }
    
    }); vasSDK.init();

    To explicitly pass IZebraServiceConnection Interface to ZebraNfcVas object, continue to step 4.

  2. Implement IZebraServiceConnection Interface - As an alternative to the approach from step 1, proceed with these next 2 steps to implement the IZebraServiceConnection interface within the Activity. The three callback methods provided by the Zebra NFC VAS service include: onServiceConnected, onServiceDisconnected, and onPassDetected.

    public class AppleVASActivity extends AppCompatActivity implements IZebraServiceConnection {
        ...
    }
    

    Replace "AppleVASActivity" with the name of the class implemented within your application.

  3. Create a ZebraNFCVAS object and intialize the object - ZebraNfcVas object should be declared in onCreate() and initialized by calling init().

    private ZebraNfcVas vasSDK;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init(); //UI Initialization
    
    
    vasSdkInit();
    
    } private void vasSdkInit() { Log.e(TAG, "vasSdkInit"); vasSDK = new ZebraNfcVas(this); vasSDK.init(); }
  4. After initialization, a callback to onServiceConnection is available. Proceed with the subsequent steps for implementation of this method.

    @Override
    public void onServiceConnection() {
        Log.e(TAG,"onServiceConnection");
        //Start with the app implemenation
    }
    

    a. Create an SmartTapVasConfig object - This is the object that the SDK uses to know how to interact with a presented NFC pass.

    SmartTapVasConfig g_cfg = new SmartTapVasConfig();
    

    b. Set the appropriate configuration values in the AppleVasConfig object by using the following APIs:

    • g_cfg.setCollectorID(String collID)
    • g_cfg.setPrivateKey(String PrivateKeyStr, int PrivateKeyVersion);

    Optionally, additional values can be configured:

    • g_cfg.setMerchantNameNdefRecord(NdefRecord record)
    • g_cfg.setStoreLocationIdNdefRecord(NdefRecord record)
    • g_cfg.setServiceTypes(ServiceType[] type)

    Sample code (Note: Replace Collector ID, NDEF record, store location ID NDEF record, and private key with your specific values):

    g_cfg.setCollectorID("12703788");    // decimal value
    g_cfg.setPrivateKey(DemoPrivateKeyStr, DemoPrivateKeyVersion);//smartPrivateKey
    g_cfg.setMerchantNameNdefRecord(rec);
    g_cfg.setStoreLocationIdNdefRecord(rec);
    g_cfg.setServiceTypes(new SmartTapVasConfig.ServiceType[]{SmartTapVasConfig.ServiceType.AllExceptPPSE});
    g_cfg.setPrivateKey(ZebraPrivateKeyStr, ZebraPrivateKeyVersion);
    

    c. Create the ZebraNFCVAS object. This holds the instance of a VasConfig object for pass detection operation.

    vasSDK.createZebraNfcVasConfig();
    

    d. Set the AppleVasConfig object into the ZebraNFCVAS object - This informs the SDK about the configuration data to use when reading the NFC pass. For Google, only a single configuration is supported at a time.

    vasSDK.setVasConfig(VasConfig);
    
    • Sample code to configure only Google Smart Tap:

      vasSDK.setVasConfig(g_cfg);

    Since Apple supports multiple configurations, the following applies if configuring both Apple VAS and Google Smart Tap at the same time:

    vasSDK.setVasConfigs(List<VasConfig>);
    
    • Sample code to configure both Apple VAS and Smart Tap configurations at the same time:

      vasSDK.setVasConfigs(Arrays.asList(new VasConfig[]{az_cfg, g_cfg}));

    e. Call connectToReader - This establishes the connection with the NFC reader.

    vasSDK.connectToReader();
    

    f. Perform one of the following based on your requirement. Only read capability is enabled and polling is configured to read NfcA, NfcB, NfcV, IsoDep, and GOOGLE_SMART_TAP tags:

    • If no additional tags are required to be read, call enableReadPassMode():

      vasSDK.enableReadPassMode();
      
    • If NfcF or Ndef tags are additionally required to be read, call enableReadPassMode() with parameters:

      enableReadPassMode(boolean doSkipNdef, boolean doEnableNFC_F);
      

      For NfcF tags, set "doEnableNFC_F" to "true."
      For Ndef tags, set "doSkipNdef" to "false." Note: Performance could be affected since an NDEF check is performed along with additional checks from the OS after initialzing the tag communication. This affects the processing time of the NFC pass. The advantage of this is the OS reads the NDEF message and it is be enumerated in the Tag object, which can be easily be accessed.

    g. When an NFC pass is read, onPassDetected() is called - This method receives the Pass object when a pass is read. Process the data read from the pass based on business needs.

    public void onPassDetected(Pass pass) {
        Log.v(TAG, ">>> PASS DETECTED <<<");
        // Process data read from the pass
    }
    

    h. Call disableReadPassMode before the app goes to the background or when the activity ends - This returns the device to the default NFC Forum polling mode.

    @Override
    public void onPause() {
        super.onPause();
        Log.i(TAG, "onPause called & Reader Mode Deactivated .....");
        vasSDK.disableReadPassMode();
    }
    

Samsung Wallet

This section provides guidance on how to develop an application capable of reading an NFC pass from Samsung Wallet using the Zebra VAS SDK.

Step-by-step guide:

  1. Initialize the ZebraNfcVas SDK: In the Activity's onCreate() method, create an instance of ZebraNfcVas and initialize it. Either implement the IZebraServiceConnection interface in the Activity or pass an anonymous implementation to the constructor.

    public class SamsungVasActivity extends AppCompatActivity implements IZebraServiceConnection {
        private static final String TAG = "SamsungVasActivity";
        private ZebraNfcVas vasSDK;
    
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    
        // Initialize the Zebra VAS SDK
        vasSDK = new ZebraNfcVas(this);
        vasSDK.init();
    }
    // ... other activity lifecycle methods
    
    }
  2. Configure the Samsung Wallet Transaction: Once the SDK service is connected, the onServiceConnection() callback is invoked. In this method, create and configure the SamsungVasConfig object.

    @Override
    public void onServiceConnection() {
        Log.d(TAG, "VAS Service Connected.");
        // a. Create a SamsungVasConfig object
        SamsungVasConfig samsungConfig = new SamsungVasConfig();
    
    
    // b. Set the Private Key (replace with your actual key)
    // IMPORTANT: For production, use a secure key management system.
    String samsung_PrivateKeyStr = "-----BEGIN EC PRIVATE KEY-----\n...";
    samsungConfig.setPrivateKey(samsung_PrivateKeyStr);
    
    // c. Generate and set a random nonce (Recommended)
    byte[] randomNonce = new byte[16];
    new SecureRandom().nextBytes(randomNonce);
    samsungConfig.setRandomNonce(randomNonce);
    
    // d. Set the Service Type (e.g., TICKET, LOYALTY, etc.)
    samsungConfig.setServiceTypes(SamsungVasConfig.ServiceType.TICKET);
    
    // e. Create the ZebraNfcVasConfig object
    vasSDK.createZebraNfcVasConfig();
    
    // f. Set the SamsungVasConfig into the ZebraNFCVAS object
    vasSDK.setVasConfig(samsungConfig);
    
    // g. Connect to the NFC reader
    int result = vasSDK.connectToReader();
    if (result == ZebraNfcVas.ERROR_SUCCESS) {
        Log.d(TAG, "Connected to reader successfully.");
    
        // h. Enable read pass mode to start listening for taps
        vasSDK.enableReadPassMode();
    } 
    else {
        Log.e(TAG, "Failed to connect to reader. Error code: " + result);
    }
    
    }
  3. Handle the Detected Pass: When an NFC pass from Samsung Wallet is tapped, the onPassDetected() callback is invoked. This method receives a Pass object containing the data read from the wallet.

    @Override
    public void onPassDetected(Pass pass) {
        Log.v(TAG, ">>> SAMSUNG WALLET PASS DETECTED <<<");
    
    
    // Process the data read from the pass based on your business needs
    if (pass.isDecrypted()) {
        String payload = new String(pass.getPayloadMessage());
        Log.d(TAG, "Decrypted Payload: " + payload);
        String rawPayload = new
        String(pass.getPayloadNDEFMessage());
        Log.d(TAG, "Raw NDEF Payload: " + rawPayload);
    } 
    else {
        Log.w(TAG, "Payload is encrypted. Please check your
        private key configuration.");
    }
    // Other details can also be retrieved from the pass object
    Log.d(TAG, "Pass Type: " + pass.getPassType());
    
    }
  4. Manage SDK Lifecycle – Disable Read Mode: To conserve battery and release NFC resources, disable read mode when the app goes into the background, and disconnect from the reader when the activity is destroyed.

    @Override
    protected void onPause() {
        super.onPause();
        if (vasSDK != null) {
            Log.i(TAG, "onPause called. Disabling read pass mode.");
            vasSDK.disableReadPassMode();
        }
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (vasSDK != null) {
            vasSDK.disconnectFromReader();
        }
    }
    
  5. Handle Service Disconnection: Implement the onServiceDisconnection method to handle cases where the connection to the VAS service is lost unexpectedly.

    @Override
    public void onServiceDisconnection() {
        Log.e(TAG, "VAS Service Disconnected.");
        // Handle disconnection, e.g., by attempting to re-initialize or notifying the user.
    }
    

ZebraNfcVAS

ZebraNfcVAS is the main class of the SDK that establishes a connection with the Zebra NFC VAS service, handles all the communication with the NFC passes, configures the NFC reader, and reads the pass from Apple, Google, or Samsung Wallet. This object must be explicitly created and initialized. After the pass is successfully read, ZebraNfcVas decrypts the data (given that a private key is provided and all information is correct and intact) and returns the Pass object back to the caller. If a private key is not provided, the SDK returns the encrypted data.

When developing an app to read an NFC pass from Apple or Google Wallet, developers have 2 options:

  • Explicitly pass IZebraServiceConnection Interface to ZebraNfcVas object
  • Implement the IZebraServiceConnection interface within the Activity.

See Developer Guide for more information.

The subsequent subsections provides the methods available from ZebraNfcVAS.

init

    public void init()

Initializes and binds to the Zebra NFC VAS Service.

Parameter: None

Return value: None

Throws: RuntimeException if the service is not available or the security/signature does not match.

onServiceConnected

This is a callback method invoked when Zebra NFC VAS service is connected. IZebraServiceConnection interface must be implemented. When init() is called, it tries to connect to the Zebra NFC VAS service. If successful, it calls onServiceConnected.

onServiceDisconnected

This is a callback method invoked when Zebra NFC VAS service is disconnected. IZebraServiceConnection interface must be implemented. When init() is called, it tries to connect to the Zebra NFC VAS service. If unsuccessful, it calls onServiceDisconnected.

setVasConfig

    public void setVASConfig()

Specifies the VASConfig object to be used for interacting with the passes. Prior to using this method, createAppleVasConfig() must be called to create the VasConfig object. To retrieve or pass data, call createZebraVasConfig().

Parameter: None

Return value: None

Throws: InvalidParameterException or RuntimeException if VasConfig object is null.

setVasConfigs

    public void setVASConfigs()

Specifies the VASConfig objects to be used for interacting with the passes. Prior to using this method, createAppleVasConfig() must be called to create the VasConfig object. To retrieve or pass data, call createZebraVasConfig().

Parameter: None

Return value: None

Throws: InvalidParameterException or RuntimeException if VasConfig object is null.

clearConfigs

    public void clearConfigs()

Clears the VASConfig object that is used for interacting with passes. After clearing the object, data from the NFC passes cannot be retrieved until calling: setPrivateKey(), setFilter(), setPassIds(), and setVasConfig().

Parameter: None

Return value: None

Throws: RuntimeException if VasConfig object is null.

createZebraVasConfig

    public void createZebraVasConfig()

Creates the ZebraConfig object and holds the instance of a VasConfig object for pass detection operation.

Parameter: None

Return: None

connectToReader

    pubilc void connectToReader()

Obtains a connection to the NFC adapter.

Parameter: None

Return value: Returns error code indicating success or failure:

  • ERROR_NFC_IS_DISABLED - the NFC adapter is disabled
  • ERROR_NO_NFC_SUPPORT - the hardware does not have NFC support
  • ERROR_SUCCESS - connection is successful

enableReadPassMode

    pubilc void enableReadPassMode()

Limits the NFC controller to reader mode, disabling card emulation. Polling is configured for the Zebra NFC reader to read NFC Type A and Type B cards.

IMPORTANT NOTE: Since the NFC resources are fully allocated, no other application (running on the foreground or background) can request for NFC resources.

Parameter: None

Return value: None

disableReadPassMode

    public void disableReadPassMode()

Returns to default NFC Forum polling mode. This method should be called whenever the app moves to the background or when the activity is terminated.

Parameter: None

Return value: None

onPassDetected

    public void onPassDetected(Pass pass)

The interface method onPassDetected is a callback that gets invoked when an NFC pass is read. The SDK passes the NFC pass and identifies whether the pass is encrypted or decrypted. Pass is a class where all Pass data can be accessed via getter methods:

  • public String getStatusWord() – Returns the StatusWord of the NFC pass detected
  • public Result getResultCode() - Returns the ResultCode of the NFC pass detected
  • public byte[] getMerchantId() - Returns the MerchantID (Pass Type Identifier converted to SHA256) of the NFC pass detected
  • public String getPassTypeId() - Returns the PassTypeID of the NFC pass detected
  • public Type getPassType() - Returns the PassType of the NFC pass detected
  • public byte[] getPayloadMessage() - Returns the PayloadMessage of the NFC pass detected
  • public Date getPayloadDate() - Returns the PayloadDate of the NFC pass detected
  • public boolean isDecrypted() – Returns the status of the payload message: decrypted or encrypted
  • public byte[] getPayloadNDEFMessage() - Returns the raw byte array representing the complete Smart Tap payload NDEF message. Developers can use this byte array to instantiate an NDEF message, which can then be parsed by their application, offering enhanced flexibility.

Parameter: Pass object

Return value: None

getSdkVersion

    public String getSdkVersion()

Returns the version of the SDK formatted as MAJ.MIN.BUG.

Parameter: Activity activity - This is the activity that registered for the NFC intent Response resp, the class that has implemented the response interface and receives the pass data.

Return value: String - the SDK version in MAJOR.MINOR.BUG_FIX format.


VasConfig

This is the abstract base class (interface) for a VAS configuration object and is not instantiated by itself. It contains information for accessing and interpreting a NFC pass. The configuration object contains one or more Pass Type Identifier(s), which identifies the pass to be returned, and may contain an associated private key, used to decrypt data from the pass. The following subsections detail the available methods.

NOTE: Google Smart Tap supports only 1 configuration object. Apple VAS supports 1 or more configuration objects.

enum VasType 

Identifies the type of VasConfig object as one of two possible values:

  • APPLE_VAS - Apple VAS configuration object
  • GOOGLE_SMART_TAP - Google Smart Tap configuration object
  • SAMSUNG_VAS - Samsung Wallet configuration object
  • UNKNOWN - Other NFC-type configuration object, including ISO or NDEF-formatted

getType

    public VasType getType() 

Returns the type of the VASConfig object.

Return value: One of the following:

  • VasType.APPLE_VAS - Apple VAS configuration object
  • VasType.GOOGLE_SMART_TAP - Google Smart Tap configuration object
  • VasType.SAMSUNG_VAS - Samsung Wallet configuration object
  • UNKNOWN - Other NFC-type configuration object, including ISO or NDEF-formatted

setPrivateKey (Apple VAS)

    public void setPrivateKey(String key) 

This method only applies to Apple VAS. Sets the EC private key in PEM format. The private key must be properly formatted or decrypted and cannot be null or an empty string. For example:

    -----BEGIN EC PARAMETERS-----\n 
    alkidnklde==\n 
    -----END EC PARAMETERS-----\n 
    -----BEGIN EC PRIVATE KEY——\n 
    aslkdflaksdfjfkk328klxld…\n 
    ak7ujvbn9b8o3bv9hae98fn2…\n 
    9asjh90fuh34n2uh33==\n 
    -----END EC PRIVATE KEY-----\n 

Parameters: String key

Return values: None

Throws: InvalidParameterException if the parameter is null or the string is empty.


setPrivateKey (Google Smart Tap)

     public void setPrivateKey(String key, int Version) 

This method only applies to Google Smart Tap. Sets the EC private key in PEM format. The private key must be properly formatted or decrypted and cannot be null or an empty string. For example:

    -----BEGIN EC PARAMETERS-----\n 
    alkidnklde==\n 
    -----END EC PARAMETERS-----\n 
    -----BEGIN EC PRIVATE KEY——\n 
    aslkdflaksdfjfkk328klxld…\n 
    ak7ujvbn9b8o3bv9hae98fn2…\n 
    9asjh90fuh34n2uh33==\n 
    -----END EC PRIVATE KEY-----\n 

Parameters:

  • String key
  • Int Version 

Return values: None

Throws: InvalidParameterException if the parameter is null or the empty string.

See best practices for Google Smart Tap Private Key Management.


AppleVASConfig

AppleVASConfig is the configuration class object for Apple NFC passes. It is inherited from VasConfig and contains parameters specific to Apple VAS for accessing and processing Apple Wallet passes. This configures the criteria used to choose which passes to access and read. The passes that are read must match the configuration parameters specified by the AppleVASConfig object. The following subsections detail the available methods.

constructor

    public void AppleVASConfig()

Creates and initializes a new AppleVasConfig object containing details specific to Apple VAS.

setPassTypeIds

    public void setPassTypeIds(String[] passTypeIds)

Sets one or more Pass Type Identifiers in the app prior to reading the NFC pass. This overwrites any pre-existing values. The Pass Type Identifier is provided by Apple, Google, or the issuer of the NFC pass. The app uses these unique string values as identifiers to ensure that when it reads the NFC pass, it can verify whether the pass type identifier matches the one that was used during the NFC pass creation process. These identifiers are typically in the form 'com.companyname.pass' or other similar reverse domain string. When an NFC reader specifies one or more Pass Type Identifiers, only passes that match those identifiers are retrieved.

Parameters: String[] passTypeIds
This is an array of one or more Pass Type IDs. For example, {“pass.com.zebra”} or {“pass.com.zebra”, “pass.com.xyz.somethingspecific”}. When attempting to read a pass, the pass being read is checked against each Pass Type ID in the array.

Return value: None

Throws:

  • InvalidParameterException when there are null or empty strings in the array, invalid characters, or the array is empty in any of the Pass Type IDs.
  • UnsupportedEncodingException when a UTF-8 conversion encoding error occurs.
  • NoSuchAlgorithmException when a SHA256 conversion error occurs.

setFilter

    public void setFilter(byte[] filter, FilterType type)

Optional: Sets the filter information to be read for the NFC pass. The filter is supplied by the merchant of the NFC pass. This overwrites any pre-existing value. A company may issue passes for different reasons and/or uses. The filter offers the ability to issue numerous similar passes with distinguiahsable variations.

A sample use case could involve a company issuing a season of tickets to a sporting event. In this case, all the tickets originate from the same company, with each ticket sharing similarities except for access date, which serves as the sole distinguishing factor. This distinguishing factor is used as the filter to identify any combination of dates. For example, consider a scenario where there are 4 games taking place on different dates. The developer could assign each game a specific within the date filter. The Zebra VAS SDK SDK can support all 4 games by determining which bit is set. When using the NFC reader, a filter can be specified with one bit set for the current game, and it can be designated as a bit-mask. By doing so, any pass with the corresponding bit set for the present date can be read, while passes without the bit set can be skipped.

Parameters:

  • byte[] filter - This is a 4-byte array that is the filter value used during the pass retrieval process.
  • FilterType type - This is one of two constants:
    • FILTER_TYPE_EXACT_MATCH - When the specified filter value exactly matches the merchant pass filter value, the match is successful.
    • FILTER_TYPE_BIT_MASK - When the specified filter value is bitwise ANDed with the merchant pass filter value and the result equals the merchant pass filter value, the match is successful.

Return value: None

Throws: InvalidParameterException if the filter value is not exactly 4 bytes in size.

setFilterNone

    public void setFilterNone()

Clears the filter for any filter value in a pass (including no filter value) to be accepted.

setMerchantURL

    public void setMerchantURL(String url)

Optional: Sets the VAS Merchant URL where the Apple device should be directed if no pass is currently available on the device. This overwrites any pre-existing value.

Passes are often used as loyalty cards. In situations where a customer is not yet a loyalty member, the user could simply tap the reader, and the Merchant URL can be transmitted to the device, guiding the user to a sign-up page. This implementation requires specific server APIs and processes that are beyond the scope of this document.

Parameter: String url
This is the merchant URL string for the associated pass, which is generally used to sign-up for a pass. It must include "https://". The maximum string length is 64 bytes.

Return value: None

Throws: InvalidParameterException if the length is too long or does not contain a valid "https://" header.

getPassTypeIds

    public String[] getPassTypeIds()

Gets the list of currently configured Pass Type IDs in string format.

Parameters: None

Return values:

  • String[] of Pass Type Ids.
  • null, if no passTypeIds have been set.

getMerchantIds

    public byte[][] getMerchantIds()

Get the list of currently configured Merchant IDs as byte arrays, where the merchant ID is the the Pass Type ID converted to SHA256 format.

Parameters: None

Return values:

  • byte[][] of Merchant Ids
  • null, if no passTypeIds have been set.

getFilter

    public byte[] getFilter()

Get the currently configured filter

Parameters: None

Return values:

  • byte[] of the filter in exactly 5 bytes
  • null, if no filter has been set

The filter[5] contains the filter type (0x01 is exact match, 0x02 is bit_mask).

getMerchantURL

    public String getMerchantURL()

Gets the Merchant URL string which the Apple device should be directed if no pass is currently available on the device. Generally this is provided for the user to sign-up for a pass.

Parameter: None

Return value:

  • If successful, returns the string of the merchant URL.
  • If unsuccessful and no merchant URL has been set, returns null.

getTerminalMode

    public AppleVasConfig.TerminalMode getTerminalMode()

Returns the private key currently configured, in PEM format.

Parameters: None

Return values: This always returns TerminalMode.VasOnly

getProtocol

    public AppleVasConfig.VasProtocol getProtocol()

Returns the protocol currently configured, in PEM format

Parameters: None

Return values: This always returns VasProtocol.FullVASProtocol


SmartTapVasConfig

SmartTapVasConfig is the configuration class object for Google Wallet passes. It is inherited from VASConfig and contains the parameters specific to Google Smart Tap for accessing and processing a Google Wallet passes. This configures the criteria used to choose which passes to access and read. The passes that are read must match the configuration parameters specific by the SmartTapVasConfig object. See Google Wallet documentation for reference. The following subsections detail the available methods.

constructor

    public void SmartTapVasConfig()

Creates and initializes a new VAS config object containing details specific to Google Smart Tap.

setCollectorID

    public void setCollectorID(String collId)

Specifies the single Collector ID associated with the pass(es) to be read. This is mandatory and serves as the primary value that is used to match with a NFC pass on the Android device. The pass on the Android device must contain a matching Collector ID. Decimal number identifiers are assigned to each pass issuer for identification purposes. Only passes that match the Collector ID specified are accepted. This overwrites any pre-existing values.

Parameters: String collectorId This is one Collector ID.  Only one Collector ID is allowed

Return values: None

Throws:

  • InvalidParameterException if the array is empty, there are null or empty strings in the array, or invalid characters in any of the pass Type Ids.
  • UnsupportedEncodingException if the UTF-8 conversion encoding is not available. UTF-8 conversion encoding is required to convert the PassTypeID string into byte encoding for the MerchantID calculation.
  • NoSuchAlgorithmException if the SHA-256 algorithm is not available.  This is required to automatically convert the PassTypeID to the equivalent MerchantID.

setServiceTypes

    public void setServiceTypes(ServiceType[] types)

Optional: Specifies an array of pass types that shall be accepted. One or more types may be specified, for example: Loyalty and/or GiftCard. The default value is "AllExceptPPSE". This overwrites any pre-existing values. See enum ServiceType for all possible values.

Parameters: ServiceType[] types Array of one or more ServiceTypes. This must not be null and or empty.

Return values: None

Throws: InvalidParameterException if the array is empty, there are null or empty strings in the array, or there are invalid characters in any of the pass Type Ids.

Sample code:

    // Create a GoogleSmartTap Config object
    SmartTapVasConfig z_cfg = new SmartTapVasConfig();    
          
    // Set the service type. This is optional - if not specified, AllExceptPPSE is used (method example for this setServiceTypes)
    z_cfg.setServiceTypes(new SmartTapVasConfig.ServiceType[]{SmartTapVasConfig.ServiceType.AllExceptPPSE});

enum ServiceType

    public enum ServiceType{
        AllServices("All Services", 0x00),
        AllExceptPPSE("All Excpet PPSE", 0x01),
        Loyalty("Loyalty", 0x03),
        Offer("Offer", 0x04),
        GiftCard("Gift Card", 0x05),
        PrivateLabelCard("Private Label Card", 0x06),
        EventTicket("Event Ticket", 0x07),
        Flight("Flight", 0x08),
        Transit("Transit", 0x09),
        CloudBasedWallet("Cloud Based Wallet", 0x10),
        MobileMarketingPlatform("Mobile Marketing Platform", 0x11),
        Generic("Generic", 0x12),
        GenericPrivatePass("Generic Private Pass", 0x13),
        WalletCustomer("Wallet Customer", 0x40);

    }

setPosCapabilitiesArray

    public void setPosCapabilitiesArray(byte[] posCapabilities)

Optional: Specifies a 5-byte array that indicates the POS capabilities. Data read from the NFC pass must match this data for validation. This overwrites any pre-existing values.

Parameters: byte[] posCapabilities (must be 5 bytes)

Return values: None

Throws: InvalidParameterException if the array is null or not 5 bytes long.

setStoreLocationIdNdefRecord

    public void setStoreLocationIdNdefRecord(NdefRecord storeLocationIdRecord)

Optional: Specifies the store location ID NDEF record to be placed into the Merchant data based on Google specifications. Data read from the NFC pass must match this data for validation. This overwrites any pre-existing values.

Parameters: NdefRecord storeLocationIdRecord

Return values: None

Throws: InvalidParameterException if the NDEF record is NULL.

Sample code:

    // Create a GoogleSmartTap Config object
    SmartTapVasConfig z_cfg = new SmartTapVasConfig();
    z_cfg.setCollectorID("95011179");

    // EXAMPLE - OPTIONAL FIELD - create an NDEF Record for the location ID in the Merchant NDEF Record
    ByteBuffer bb = ByteBuffer.allocate(4);
    byte [] locId = bb.putInt(12345).array();
    rec = new NdefRecord(NdefRecord.TNF_EXTERNAL_TYPE, new byte[]{(byte) 0x6c, (byte) 0x69, (byte) 0x64}, // 'lid' in byte form
        null, concatenateByteArrays(new byte[]{(byte) 0x04}, // payload format byte
        locId ));
    z_cfg.setStoreLocationIdNdefRecord(rec);

setTerminalIdNdefRecord

    public void setTerminalIdNdefRecord(NdefRecord terminalIdRecord)

Optional: Specifies the terminal ID NDEF record to be placed into the Merchant data based on Google specifications. Data read from the NFC pass must match this data for validation. This overwrites any pre-existing values.

Parameters: NdefRecord terminalIdRecord

Return values: None

Throws: InvalidParameterException if the NDEF record is NULL.

setMerchantNameNdefRecord

    public void setMerchantNameNdefRecord(NdefRecord merchantNameRecord)

Optional: Specifies the merchant name NDEF record to be placed into the Merchant data based on Google specifications. Data read from the NFC pass must match this data for validation. This overwrites any pre-existing values.

Parameters: NdefRecord merchantNameRecord

Return values: None

Throws: InvalidParameterException if the NDEF record is NULL.

Sample code:

    // Create a GoogleSmartTap Config object
    SmartTapVasConfig z_cfg = new SmartTapVasConfig();
    z_cfg.setCollectorID("95011179");

    // EXAMPLE - OPTIONAL FIELD - create an NDEF Record for the Merchant name in the Merchant NDEF Record
    byte[] name = new String("Sample Merchant").getBytes(StandardCharsets.UTF_8);
    byte[] strHeader = new byte[] {(byte) 0x02, (byte) 0x65, (byte) 0x6E};
    NdefRecord rec = new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, new byte[]{(byte) 0x6d,(byte) 0x6e, (byte) 0x72}, // 'mnr' in byte form
    concatenateByteArrays(strHeader, name));
    z_cfg.setMerchantNameNdefRecord(rec);

setMerchantCategoryNdefRecord

    public void setMerchantCategoryIdNdefRecord(NdefRecord merchantCategoryRecord)

Optional: Specifies the merchant category NDEF record to be placed into the Merchant data based on Google specifications. Data read from the NFC pass must match this data for validation. This overwrites any pre-existing values.

Parameters: NdefRecord merchantCategoryRecord Return values: None Throws: InvalidParameterException if the NDEF record is NULL.

getCollectorIDHexStr

    public String getCollectorIDHexStr()

Returns the Collector ID as a string in hex format.

Parameters: None

Return values:

  • String collectorID as a hex string.
  • null, if no Collector IDs have been set.

getCollectorIDInt

    public int getCollectorIDInt()

Returns the Collector ID in integer format.

Parameters: None

Return values: Integer format of Collector ID. Null if no Collector IDs have been set.

getPosCapabilitiesArray

    public byte[] getPosCapabilitiesArray()

Optional: Returns the 5-byte array that indicates the POS capabilities. This is set from setPosCapabilitiesArray() method.

Parameters: none

Return values: byte[] - 5 byte array with the POS capability masks

getStoreLocationIdNdefRecord

    public NdefRecord getStoreLocationIdNdefRecord()

Optional: Returns the store location ID NDEF record. This is set from setStoreLocationIdNdefRecord() method.

Parameters: none

Return values: NdefRecord storeLocationIdNdefRecord

getTerminalIdNdefRecord

    public void getTerminalIdNdefRecord()

Optional: Returns the terminal ID NDEF record. This is set from setTerminalIdNdefRecord() method.

Parameters: none

Return values: NdefRecord terminalIdNdefRecord

getMerchantNameNdefRecord

    public void getMerchantNameNdefRecord()

Optional: Returns the merchant name NDEF record. This is set from setMerchantNameNdefRecord() method.

Parameters: none

Return values: NdefRecord merchantNameNdefRecord

getMerchantCategoryNdefRecord

    public void getMerchantCategoryIdNdefRecord()

Optional: - Returns the merchant category NDEF record. This is set from setMerchantCategoryIdNdefRecord().

Parameters: none

Return values: NdefRecord merchantCategoryRecord


SamsungVasConfig

SamsungVasConfig is the configuration class for Samsung Wallet passes. It is inherited from the VASConfig class and contains all parameters required for a transaction.

Constructor:

    public SamsungVasConfig()

Creates and initializes a new SamsungVasConfig object.

setPrivateKey

    public void setPrivateKey(String key)

Sets the EC private key in PEM format required to sign the transaction data, allowing Samsung Wallet to verify the request's authenticity.

Parameters:

  • key: A String containing the PEM-formatted EC private key.

Throws:

  • InvalidParameterException: Thrown if the key string is null, empty, or improperly formatted.

setRandomNonce

    public void setRandomNonce(byte[] nonce)

Sets a random nonce (number used once) for the transaction to prevent replay attacks. In cryptographic operations, a nonce is used to prevent replay attacks by ensuring that each transaction is unique. While optional, using a nonce is a highly recommended security practice.

Parameters:

  • nonce: A byte[] array of 16 bytes containing the random nonce.

Throws:

  • InvalidParameterException: Thrown if the nonce is not 16 bytes in length.

setServiceTypes

    public void setServiceTypes(ServiceType type)

Specifies the type of service being requested from Samsung Wallet.

Parameters:

  • type: A ServiceType enum value.

ServiceType Enum

Enumeration of the different kinds of services supported by Samsung Wallet through the VAS protocol.

  • LOYALTY: For loyalty program services.
  • TICKET: For event ticket validation services.

Pass Class

The Pass class provides access to all data read from the NFC pass via getter methods.

Methods:

    public String getStatusWord()

Returns the StatusWord of the detected NFC pass.

    public Result getResultCode()

Returns the ResultCode of the detected NFC pass.

    public byte[] getMerchantId()

Returns the MerchantID (Pass Type Identifier converted to SHA256) of the pass.

    public String getPassTypeId()

Returns the PassTypeID of the pass.

    public Type getPassType()

Returns the PassType of the pass.

    public byte[] getPayloadMessage()

Returns the decrypted payload message.

    public byte[] getPayloadNDEFMessage()

Returns the raw, full NDEF payload message.

    public Date getPayloadDate()

Returns the payload date of the pass.

    public boolean isDecrypted()

Returns true if the payload message has been successfully decrypted, false otherwise.


Security

Zebra VAS SDK implemented advanced security features to help protect the private key operation and communication between the authorized Zebra NFC VAS service and the line-of-business application. Since the Zebra NFC VAS service requires installation via the supplied .APK file, the enhanced security mechanism is designed to ensure the application connects with a genuine Zebra NFC VAS service.

The Zebra VAS SDK incorporates security measures to ensure the authenticity of the Zebra NFC VAS service. This includes but is not limited to the following:

  • Since the Zebra VAS Service is not part of the device system software (OS), the Zebra NFC VAS service is signed by a Zebra key (unprivileged) to ensure it is obtained from a trusted source.
  • The Zebra VAS SDK validates the Zebra NFC VAS service by checking the signature of the service.
  • The Zebra NFC VAS service source code and binary are secured with an obfuscation tool.

Considering the SDK is an Android .AAR file that is imported into the application project, it is possible to decompile the .AAR file and regenerate it into a modified version that connects to a malicious service. This could compromise the security and encryption of the private key, as well as other essential data, introducing potential security vulnerabilities.

Zebra recommends that application developers implement the following integrity check to verify the authenticity of the Zebra VAS SDK. Ensure the authenticity of the Zebra VAS SDK by verifying that the checksum of the .AAR file matches the checksum of the SDK provided by Zebra. The SDK and its checksum details are available on the Zebra support portal. Use any free "sha512sum" tool for this verification. Compare the resulting checksum with the following checksum for Zebra VAS SDK (.AAR) version 3.0:

    VAS SDK (.aar) version: 4.1
    sha512 checksum:
    9fccabf959112e7e55d62e368d52d182d6e780d562411e75964041ff280e02a8593deef317b05430866bd89911120e67389970e5090448656a1cddbead19874a

Best Practices

Google Smart Tap

Private Key Management

Zebra recommends to follow Google’s specified guidelines for private key management. Effective key management is crucial to ensure the security of the Smart Tap protocol and business application. This guide includes the following sections:

  • Key Generation
  • Key Injection
  • Key Versioning
  • Key Rotation
  • Key Storage

Note: The term “Terminal vendor” used within this section refers to any entity that purchases and sells Zebra devices.

ALL INFORMATION IN THIS SECTION IS SOURCED BY GOOGLE, WITH MINOR MODIFICATIONS FROM ZEBRA


Key Generation

Google recommends the long-term key pair to be generated outside of the Zebra terminal and then injected into the terminal. This is because the same key is shared by all the terminals for a given merchant. The key pair can be generated by the merchant or by the terminal vendor on behalf of the merchant. The private key should always be kept secret using standard best practices. If the terminal vendor generates the key pair and injects the key on behalf of the merchant, only the public key should be shared with the merchant.


Key Injection

The long-term private key can be injected into the Zebra terminal in multiple ways:

  • The terminal vendor can inject the private key using remote key injection.
  • The terminal vendor can encrypt the private key using a key encryption key, provide the encrypted private key to the merchant, and the merchant can configure the terminal with the encrypted private key as a parameter. The terminal can use a decryption key that's already in the terminal to decrypt the encrypted private key.
  • The private key is within an encrypted file that can be securely loaded onto the terminal.
  • The private key is injected within a secure facility and the terminals are hot-swapped.


Key Versioning

To aid in key rotation, a key version must be defined with the long-term public key. Multiple long-term public keys can be in production at the same time. The key version is sent as a binary hex value during Smart Tap authentication.


Key Rotation

Key rotation involves the following steps:

  1. Upload a new public key to Google Wallet
  2. Allow 24 hours for the new public key to be synced to the users' mobile devices
  3. Update all terminals to use the new private key
  4. Revoke the old public key from the Google Wallet platform

Key rotation requires that some terminals use old keys while newer terminals are using new keys. Both long-term public keys (old and new) are uploaded to Google Wallet. Both keys are then synced to the users' mobile devices. A user can tap at a terminal using an old key or a new key while the rotation is in-progress. Once the final terminal has been updated, the old long-term public key can be removed from Google Wallet.

Note: To maintain speedy turnarounds with key rotations, especially when an unexpected compromise occurs, Google strongly recommends merchants have a system in place that allows them to update the private key used on all terminals in less than 24 hours.

Note: Zebra's MX platform exposes functionality to install certificates, see Certificate Manager. To deploy a public certificate along with a private key, Zebra recommends using a certificate container (.PFX) file.

Recommended Rotations

Google recommends having three valid public keys on the Google Wallet platform at any time and suggests performing a daily rotation. The following steps outline an example rotation scenario based on these recommendations. First, create the following initial configuration:

  • Three public keys are uploaded to Google Wallet: A, B, and C.
  • Terminals are configured to use A's private key.
  • Private keys B and C are stored securely outside the terminals.

Next, perform a rotation:

  1. Generate a new key pair, D.
  2. Upload D's public key to Google Wallet.
  3. Store D's private key securely outside the terminals.
  4. Configure terminals to use B's private key.
  5. Remove A's public key from Google Wallet.

Assume that less than 24 hours after this rotation is complete, private key B (currently in use) has become compromised. The following steps outline an example rotation scenario for compromised keys:

  1. Configure terminals to use C's private key.
  2. Revoke B's public key from Google Wallet.
  3. Generate a new key pair, E.
  4. Upload E's public key to Google Wallet.
  5. Store E's private key securely outside the terminals.

The terminals must first be configured to use a new private key before an existing public key is revoked from Google Wallet. Otherwise, the mobile devices would no longer accept the previous key even though there are still terminals that are attempting to use it for authentication.

In this scenario, less than 24 hours had elapsed since key B was "rotated in" (being used on terminals). Within this time period, the newly-created key, D, would not have sufficient opportunity to propagate to all of the users' mobile devices. By having 3 public keys in Google Wallet, a potential outage was avoided when rotating to key C.

Minimum Rotations

Google recommends to have at least one valid public key stored in Google Wallet at any time, and suggests to perform key rotations on a monthly basis. The following steps outline an example rotation scenario based on these recommendations.

First, create the following initial configuration:

  • One public key is uploaded to Google Wallet: X.
  • Terminals are configured to use X’s private key.

Next, perform a rotation:

  1. Generate a new key pair, Y.
  2. Upload Y's public key to Google Wallet.
  3. Wait 24 hours for Y's public key to sync to users' mobile devices.
  4. Configure terminals to use Y’s private key.
  5. Remove X's public key from Google Wallet.

Assume that less than 24 hours after this rotation is complete, private key Y (currently in use) has become compromised. The following steps outline an example rotation scenario for compromised keys:

  1. Generate a new key pair, Z.
  2. Upload Z's public key to Google Wallet.
  3. Configure terminals to use Z's private key.
  4. Revoke Y’s public key from Google Wallet.

When the public key Z is uploaded to Google Wallet, it may take up to 24 hours to sync to users' mobile devices. A merchant in this case must decide how much time, if any, to wait before revoking public key Y. A longer period of time allows more devices to sync to the new public key, reducing the size of an outage when the terminals start using the new key or when users' devices start rejecting the old key. However, it also increases the time in which the compromised private key is valid and accepted as authentication for attacks.

If key Y is revoked before the terminals are configured to use key Z, there would be an outage because there would be no public key available for authentication.


Key Storage

Public Keys

Public keys shouldn't be considered a security mechanism. For the purpose of security modeling, the public key should always be considered as compromised and publicly available. In actuality, the public key shouldn't be made publicly available. Limiting access to the public key increases the difficulty of a potential attack but doesn't eliminate the possibility. Limiting access also limits the potential for a rogue device to emulate the Google Wallet application.

Private Keys

The private key is used to authenticate the merchant terminal. If compromised, a malicious terminal can use the long-term private key to impersonate a legitimate merchant’s terminal until the key is rotated.

While NFC traffic can always be sniffed, it can only ever be decrypted by the initiating terminal. Compromised private keys can never be used to decrypt sniffed traffic from another terminal, even if the same private key was used for authentication.

Relay attacks are possible, but unlikely given the required proximity and collision avoidance mechanisms of the lower-level NFC protocol.

On The Terminal

The private key should be stored and used only in a secure enclave of the terminal. Zebra recommends using Secure Element or Qualcomm TE where available on Zebra products. Refer to Zebra product documentation for more information.

Minimum Requirements

The private key can be stored in the main file system but should be encrypted at rest using a key encryption key (KEK). The KEK used to decrypt the private key should be stored in the secure enclave of the terminal. During a transaction, the encrypted private key is first decrypted in the secure enclave. The decrypted private key should only be used in the main CPU/RAM.

For more information, contact Google or refer to their relevant documentation.


Apple VAS

For optimal performance of the Apple VAS solution, set the Card Detection Mode (polling mode) to Standard in the Enterprise NFC Settings. To access these settings, go to device Settings > Connected Devices. For more information, refer to the product documentation posted on the Zebra support portal.

Card Detection Mode in Enterprise NFC Settings


Samsung Wallet

Securely handling user data and transactions is critical. The Samsung Wallet integration relies on the following key security elements:

  • Private Key Management: The private key is a secret credential used to sign data sent to Samsung Wallet, proving the transaction request originates from an authorized merchant. This key must be stored securely and protected from unauthorized access. Follow industry best practices for key management, such as storing the key securely within the Android Keystore System.
  • Random Nonce Usage: A nonce is a random value generated for a single transaction. Including a cryptographically secure nonce in every request prevents replay attacks, ensuring that an attacker cannot capture and maliciously resubmit a previously valid transaction.
  • Timestamp Verification: Transactions are highly time-sensitive. To further mitigate security risks, the underlying VAS protocol verifies that the transaction timestamp falls within a strict, acceptable window (e.g., < 500ms) before considering the pass valid.

See Also