Technical documentation MDC SDK
Introduction
Basic abbreviations and definitions
Field | Description |
---|---|
FCM |
Firebase Cloud Messaging |
DC | Data Core |
MDC |
Mobile DC module |
MPA |
Mobile Payment Application |
UCP | Verestro Cloud Payments |
IBAN | Back Account Number |
JWT | Json Web Token |
JWE | Json Web Encryption |
OTP | One-time password |
What is Mobile DC SDK?
The Mobile DC SDK is a core module dedicated to the management of users, devices, and cards in the Verestro system.
Usage of other Verestro modules (f.e. UCP - Verestro Claud Payment) always depends on this SDK and Mobile DC must be always used.
Versioning and backward compatibility
SDK version contains three numbers. For example: 1.0.0.
- First version digit tracks compatibility-breaking changes in SDK public APIs. It is mandatory to update the application code and to use SDK when this is incremented.
- Second version digit tracks new, not compatibility-breaking changes in the public API of SDK. It is optional to update the application code when this digit is incremented.
-
The third version digit tracks internal changes in SDK. No updates in the application code are necessary to update to the version, which has this number incremented.
Changes not breaking compatibility:
- Adding a new optional interface to SDK setup.
- Adding a new method to any domain.
-
Adding new ENUM value to input or output.
- Adding a new field in the input or output model.
Technical overview
This section describes basic information about Mobile DC SDK, SDK setup, and available methods.
Basic configuration
Artifactory
SDK is available on the Verestro maven repository and can be configured in a project using the Gradle build system.
The username and password are provided by Verestro.
repositories{ maven { credentials { username "<enter_username_here>" password "<enter_password_here>" } url "https://artifactory.upaid.pl/artifactory/libs-release-local/" } |
Versions
Mobile DC SDK is available in two versions: debug and release.
The difference between versions is debugged version allows to use of an application with a debugger connected.
Debug version is ended with appendix "-debug" in version name. Samples below:
For release version, used on production environment in application uploaded to Google Play:
dependencies{ implementation 'pl.upaid.module:mobiledc:{version}' } |
Debug version - could be used for application development:
dependencies{ implementation 'pl.upaid.module:mobiledc:{version}-debug' } |
Min SDK Version
The minSdkVersion must be at least 23 (Android 6.0). In case of using SDK on lower Android API version declare in the application manifest.
<uses-sdk tools:overrideLibrary= "pl.upaid.module.ucp, pl.upaid.module.mobiledc, pl.upaid.module.mcbp, pl.upaid.module.security, com.mastercard.mpsdk, pl.upaid.nativesecurity" /> |
SDK cannot be initialized on a lower Android API version, and none of the SDK methods should be used on it.
Android APIs 31+
Android Worker uses PendingIntent which causes some problems with the newest Android APIs. To resolve this problem application Gradle dependency configuration must override this dependency.
dependencies{ implementation 'androidx.work:work-runtime-ktx:2.7.1' } |
Native libs extraction in AndroidManifest and Gradle.
Native libs should be packaged in application in uncompressed form due to security reasons.
This solution is also recommended by Google due to the smaller app install and download size.
Note: Starting from Android Gradle Plugin 4.2 an attribute is replaced with parameter android.packagingOptions.jniLibs.useLegacyOptions
.
If you need to package native libraries in the compressed form please contact Verestro.
Recommended solution when using Verestro SDK below:
//AndroidManifest.xml <application android:extractNativeLibs="false" ... > </application> |
//build.gradle android { packagingOptions { jniLibs { useLegacyPackaging false } } } |
Source code obfuscation and optimization
As SDK is written in Kotlin the language we recommend adding following code to Gradle configuration:
android { ... kotlinOptions { freeCompilerArgs = [ '-Xno-param-assertions', '-Xno-call-assertions', '-Xno-receiver-assertions' ] } packagingOptions { exclude '/kotlin_metadata/**' } ... } |
And use the newest tools for code shrinking, optimization, and obfuscation from Google by enabling R8 instead Proguard in the gradle.properties file:
android.enableR8=true |
Android permissions
QUERY_ALL_PACKAGES
Module declares the usage of permission QUERY_ALL_PACKAGES, which is required since Android API 30 to list packages available on the device.
Permission is used for security tasks.
On 20.07.2022 Google Play introduced restrictions on high-risk permissions and the application sender could be obliged to send more information about this permission usage.
The application which uses this permission is usually a bank or financial institution - in such case permission may be granted to usage.
Read more https://support.google.com/googleplay/android-developer/answer/10158779?hl=en
In case of lack of agreement from Google Play permission could be removed on the application side AndroidManifest.xml file.
When removed application should declare package visibility to search known root packages.
In case of removing this permission please contact Verestro representative.
Sample code to remove QUERY_ALL_PACKAGES permission in the AndroidManigest.xml file and package visibility declaration:
<manifest package="com.sampleapp"> <!-- remove permission from manifest --> <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" tools:ignore="QueryAllPackagesPermission" tools:node="remove" /> <!-- declare application visibility --> <queries> <package android:name="com.noshufou.android.su" /> <package android:name="com.noshufou.android.su.elite" /> <package android:name="eu.chainfire.supersu" /> <package android:name="com.koushikdutta.superuser" /> <package android:name="com.thirdparty.superuser" /> <package android:name="com.yellowes.su" /> <package android:name="stericson.busybox" /> <package android:name="com.topjohnwu.magisk" /> <package android:name="com.koushikdutta.rommanager" /> <package android:name="com.koushikdutta.rommanager.license" /> <package android:name="com.dimonvideo.luckypatcher" /> <package android:name="com.chelpus.lackypatch" /> <package android:name="com.ramdroid.appquarantine" /> <package android:name="com.ramdroid.appquarantinepro" /> <package android:name="com.android.vending.billing.InAppBillingService.COIN" /> <package android:name="com.chelpus.luckypatcher" /> <package android:name="com.devadvance.rootcloak" /> <package android:name="com.devadvance.rootcloakplus" /> <package android:name="de.robv.android.xposed.installer" /> <package android:name="com.saurik.substrate" /> <package android:name="com.zachspong.temprootremovejb" /> <package android:name="com.amphoras.hidemyroot" /> <package android:name="com.amphoras.hidemyrootadfree" /> <package android:name="com.formyhm.hiderootPremium" /> <package android:name="com.formyhm.hideroot" /> <package android:name="org.projectvoodoo.otarootkeeper" /> </queries> |
MDC SDK Size
The size of SDK is dependent on the apk distribution system.
The table below shows the size of the module for apk and bundle file.
Format | Size | Notes |
---|---|---|
APK | ~15,1 MB | |
Bundle | ~2,5 MB - ~3,6 MB |
Ranged size depends on the ABI of the device. SDK contains native libraries used by different ABI. By using a bundle only the necessary version of the native library will be downloaded to a device. |
Additional information
- size from the table above is referred to release version;
-
size depends on configured proguard (module provides consumer progurd rules);
MDC SDK usage
This chapter describes the structure and basic usage of Mobile DC SDK.
Domains
Every described facade is divided into domains with different responsibilities.
Available domains:
- Cards.
- User.
- Device.
- CloudMessaging.
- TransactionsHistory.
Every domain contains domain-related methods.
Facade
The facade is an entry point to communication with Mobile DC SDK.
Contains an SDK initialization method and domains which allows to manage Verestro account.
Multiple facade types
Mobile DC SDK provides three public APIs with the same functionalities, the APIs are:
- MobileDcApiJava for projects which use Java programming language.
- MobileDcApiKotlin for projects which use Kotlin programming language.
The difference between the APIs is a way of providing data to SDK methods and getting the results from them.
Input and output as information data are always the same.
This documentation presents I/O types in a Kotlin way as it’s easier to mark nullable fields (as a question mark).
Method structure
Every method described in documentation has same structure.
Execution type:
- Asynchronous - Operation could take more time and method is called on thread different than main. The result of execution is provided in callback (described below).
- Synchronous - Result of operation is provided as method return type.
Method type:
- Online - Operation requires internet connection.
- Offline - Operation can be called without internet access.
Input
Input parameters with name, description and validation rules. For some methods input is result of different uPaid method.
Output
If present is delivered in success callback with data described below.
Callbacks
- Success callback - Depends on used programming language and facade type Multiple facade types. s onSuccess or success callback inform about operation success.
- Success callback with data - Callback with additional result object.
- Failure callback - Informs about failure. Contains information about exception (Error handling.). Exception can contains additional details.
Sample usage:
//java sample cardsService.getAll( new SuccessDataCallback<List<Card>>() { @Override public void onSuccess(List<Card> cards) { //show cards on UI } }, new FailureCallback() { @Override public void onFailure(@NotNull Throwable throwable) { //show error } }); SuccessDataCallback and FailureCallback could be replaced by lambda like in kotlin sample below //kotlin sample cardsService.getAll({ cards -> //show cards on UI }, { //show error }) |
Error handling
SDK returns errors by exceptions, which could be caught by the application and shown on UI with a detailed message.
Note: Exception message is only information for a developer and shouldn't be shown on the application UI to the user. Use reason code to add a suitable message on the application layer.
The table below describes exceptions types.
Exception type | Exception class | Description |
---|---|---|
SDK Validation | ValidationException |
Every parameter provided in method is validated locally by SDK. For example, card number is validated with Luhn algorithm Exception object contains field reason with detailed error code |
Backend | BackendException |
Provides additional validation or error on backend side Exception object contains field reasonCode and message Application should get reason code and show suitable message or made action Message should be never shown to user. Could be logged for error reporting and for developers List of available raeson in table below |
Internal Sdk | CoreSdkException |
Provides additional information about error in SDK Exception object contains field reason and message Application should get reason code and show suitable message or made action Message should be never shown to user. Could be logged for error reporting and for developers List of available raeson in table below |
Process related | - |
As every process is different some methods could throw specified exception. Types of exception is described in method description |
Network | NetworkConnectionException | Provide information about network connection error. Check table with possible reasons below |
Internal | UnknownException | For unknown errors in SDK process |
BackendException error codes
Reason code |
Description |
INTERNAL_ERROR |
Error occurred on server |
CRYPTOGRAPHY_ERROR |
Error occurred during cryptography operation |
VALIDATION_ERROR |
Client sent invalid data |
CLIENT_NOT_FOUND |
Requested client id not found |
CARD_NO_OUTSIDE_RANGE_BINS |
Card number is outside of preconfigured range |
CARD_KIND_LIMIT_REACHED |
Card kind limit reached |
CARD_DELETED |
Card was deleted and operation cannot be proceed |
FAIL_3DS_VERIFICATION |
3ds verification failed |
FAIL_AUTHORIZATION |
Fail authorization - error from card verification |
TRANSACTION_DENIED |
Transaction denied - error from card verification |
FAIL_AUTHENTICATION |
Invalid username or password |
CARD_EXPIRED |
Card expired |
LIMITS_EXCEEDED |
Limit of transactions has been exceeded |
REJECTED_BY_BANK |
Current transaction has been rejected by bank - error from card verification |
INSUFFICIENT_FOUNDS |
There is no enough founds on card to complete this transaction |
INVALID_JWS_TOKEN |
Specified JWS token is invalid |
INVALID_FCM_TOKEN |
Given FCM token is invalid |
OPERATION_NOT_SUPPORTED |
Method is not allowed |
CANT_FIND_USER |
Cannot find user by requested identifier |
CANT_FIND_CARD |
Cannot find card by requested identifier |
CANT_FIND_DEVICE |
Cannot find device by requested identifier |
CANT_FIND_WPIN |
Cannot find wallet pin |
CANT_FIND_ADDRESS |
Cannot find requested address |
CANT_FIND_INVITATION |
Cannot find invitation for the requested resource |
RESOURCE_NOT_FOUND |
Cannot find requested resource |
USER_IS_LOCKED |
Requested user is locked |
CARD_IS_LOCKED |
Requested card is locked |
DEVICE_PERMANENTLY_LOCKED |
Requested device is locked |
DEVICE_TEMPORARILY_LOCKED |
Requested device is temporary locked |
BAD_LOGIN_LIMIT_REACHED |
Number of incorrect login attempts is reached |
BAD_WPIN_LIMIT_REACHED |
Requested user is temporary locked |
SMS_FAILCOUNT_REACHED |
Sms fail count limit reached |
TOO_MANY_REQUESTS |
Too Many Requests |
CARD_IS_ALREADY_VERIFIED |
Requested card has been verified |
USER_IS_NOT_VERIFIED |
User is not verified. E.g. User had registered already however did not finish the verification step and is trying to pair/login |
PAYMENT_ALREADY_SUCCESS |
Requested payment has been already finished |
INVALID_CARD |
Card is invalid for this payment |
USER_HAS_NOT_SET_A_PASSWORD |
Requested user has not set password |
DEVICE_LIMIT_REACHED |
Number of device is reached |
BAD_WPIN |
The wallet pin does not match the requested user |
SMS_LIMIT_EXCEEDED |
Number of sent messages has reached the limit. Please try again later |
FAIL_COUNT_LIMIT_REACHED |
Attempt limit has exceeded |
INITIALIZE_PROCESS_REQUIRED |
Fail count limit has been reached and new process has not been initialized |
INVALID_AUTHORIZATION_AMOUNT |
Authorization amount is incorrect |
CLIENT_UNAUTHORIZED |
Client of the api is unauthorized(invalid/expired X-Client-Auth token) |
USER_UNAUTHORIZED |
User is unauthorized |
VERIFICATION_FAILED |
Requested card could not be verified |
CARD_NOT_ENROLLED |
Card does not participate in 3ds v2, please use 3ds v1 |
INVALID_AUTHENTICATION_ID |
Authentication process id does not match card id |
ATTESTATION_FAILED |
The attestation request failed |
ATTESTATION_PLAY_INTEGRITY_REQUIRED |
The Play Store app is not installed, or it is not the official version, or it is an old version |
BackendException - additional error codes related to TransactionsHistoryService
Reason code |
Description |
TRANSACTION_ATTACHMENT_STATUS_APPROVED |
Cannot update transaction or store new attachment. Attachment status in transaction is APPROVED |
MAX_ATTACHMENTS_COUNT |
Maximum number of attachments for given transaction |
MAX_ATTACHMENT_SIZE_EXCEEDED |
Maximum attachment size exceeded |
EMPTY_ATTACHMENT |
Empty attachment file |
EMPTY_ATTACHMENT_NAME |
Empty attachment file name |
NOT_SUPPORTED_ATTACHMENT_FILE_TYPE |
Attachment file type is not supported |
CANT_FIND_TRANSACTION_ATTACHMENT |
Cannot find transaction attachment |
CoreSdkException error codes INTERNAL_ERROR
Reason code | Description |
---|---|
DEVICE_NOT_PAIRED | Device is not paired or trying to call the method without device pairing |
SAFETYNET_ERROR |
An error during device authentication in Google SafetyNet service Deprecated, replaced by ATTESTATION_API_ERROR |
PUSH_INVALID_SOURCE |
Relates to push processing process. Push message should be consumed in another module |
PUSH_INVALID_PUSH_CONTENT |
Cannot process push message. The message is invalid or some process failed |
BIOMETRIC_AUTHENTICATION_NOT_ENABLED | Biometric public key is invalid |
BIOMETRIC_AUTHENTICATION_ABORTED | Cannot authenticate biometric signature |
KEYSTORE_IS_NOT_SECURE | Keystore is not secure. The device is not secured with PIN, pattern or password |
CANNOT_GET_PUBLIC_KEY | Internal error during SDK processing |
SECURITY_EVENT_OCCURRED |
Security event occurred during application usage. Data is cleared and the method can't be used. Application is always informed about a security issue with onSecurityIssueAppeared callback initialized in setup() method |
PIN_AUTHENTICATION_FAILURE |
Provided PIN is invalid |
PIN_AUTHENTICATION_FAILURE_LIMIT_EXCEEDED |
PIN fail count is reached, every next try PIN authentication will finish with this status |
PIN_AUTHENTICATION_NOT_POSSIBLE |
PIN authentication is not possible until the user is at least once logged online with PIN |
PIN_AUTHENTICATION_INTERNAL_ERROR |
Something went wrong during PIN authentication |
PAIRING_IS_ALREADY_IN_PROGRESS | Another pairing is now processed |
PAYMENT_INSTRUMENT_DEFAULT_NOT_FOUND | No default payment instrument for contactless payment found |
PAYMENT_INSTRUMENT_NOT_FOUND | Payment instrument with this id cannot be found in SDK |
APPLICATION_PROCESS_NOT_KILLED | Application process is not killed after SDK reset |
TOKEN_NOT_ACTIVE | Required token status is ACTIVE |
PAYMENT_INSTRUMENT_NOT_FOUND | Token with that id was not found in SDK |
API_SERVICE_NOT_INITIALIZED | API service was not initialized |
DIGITIZATION_NOT_STARTED | Digitization of that payment instrument was not started |
ATTESTATION_API_ERROR |
An error during device authentication in Device Attestation service (Play Integrity) |
NetworkConnectionException error codes
Reason code | Description |
---|---|
UNKNOWN_HOST |
Host is unreachable, check network connection and try again later |
SOCKET_TIMEOUT | Connection timeout, check network connection and try again later |
UNKNOWN_HTTP_ERROR | An unknown HTTP error |
Firebase Cloud Messaging configuration
Dependency
Add Firebase Cloud Messaging dependency entry to build.gradle.
... implementation('com.google.firebase:firebase-messaging:22.0.0') ... |
Initializing
All parameter required to initialize FCM can get from firebase console.
val firebaseOptions = FirebaseOptions .Builder() .setApplicationId(applicationId) .setGcmSenderId(senderId) .setProjectId(projectId) .setApiKey(apiKey) .build() FirebaseApp.initializeApp(this, firebaseOptions) |
To initialize two FCM projects should add name parameter for second one.
//init default project FirebaseApp.initializeApp(this, firebaseOptions) //init additional project FirebaseApp.initializeApp(this, firebaseOptionsOther, otherProjectName) |
Getting FCM registration token
//getting token for default project FirebaseMessaging.getInstance().token .addOnCompleteListener { //handle token } //getting token for additional project val messaging = FirebaseApp.getInstance("other").get(FirebaseMessaging::class.java) messaging.token.addOnCompleteListener { //handle token } |
Security processes overview
This document contains short information about security mechanisms used by SDK. Only high-level functions are described, without technical specifications.
Security processes were designed to protect its assets, and ensure that the application using our product is installed on a safe environment.
SDK security mechanism starts just after initialisation of library. Because of process optimalization part of checks are made only once, other are called periodically.
Process check
Implementation of checking application process is one of many methods that SDK uses to protect itself from hooking tools.
It is based on validating app_process file, checking if it was not manipulated, or corrupted. Many popular hooking tools used nowadays uses that method to upload its hooks, modules, or cloaks over adding root access. Validating that file helps in analysis potential threats from hazardous software, and even checking root acces.
Root check
Checking root access is one of SDK most important security processes. SDK is analysing application enviromnent in following ways:
- Searching for harmful binaries:
SDK analyses device in search for commonly known binaries indicating that device is rooted. Additionally in this step there are checked some binaries connected to hooking tools.
- Checking system partitions permissions:
SDK validates if some paths(commonly connected with root access), along with some common system directories are writtable on specific partition. - Validating installed packages :
SDK analyses installed application on device in search for apps known to be connected with root access, apps requiring root, and root cloak apps.
Android 11 introduced some changes reducing visibility of other packages installed on device. To keep checking harmful application there is added additional android permission: QUERY_ALL_PACKAGES - Checking rom settings:
Analyses device rom properties, to search for flags set on rooted devices. - Checking su existance:
Checks is su directory accessible in some common paths.
Emulator check
SDK validates application installment device by analyzing its properties and searching for evidences indicating that device is emulator.
Other packages check
Searches for well known applications connected with harmful hooking tools.
Especially aimed into finding evidences of magisk.
Debug check
Simple check is debugger connected, based on OS properties. Used only in release version.
SafetyNet Api - deprecated from version 2.3.1714.6
API tool used for protecting application from tampering, harmful applications, and validating device OS. It availability depends on project configuration.
Usage of this process is configured odon server side. If end application already performs this process it could be disabled during project configuration.
Play Integrity API
Due to Safetynet Attestation API planned deprecation it's replaced by Play Integrity API in Mobile DC version 2.13.7.14.6.
API tool used for protecting application from tampering, harmful applications, and validating device OS. It availability depends on project configuration.
Usage of this process is configured odon server side. If end application already performs this process it could be disabled during project configuration.
When used, Play Integrity API must be enabled Google Play Developer Console - https://developer.android.com/google/play/integrity/setup#enable-responses.
Error codes handling in application
Table describes error codes which could be handled on application side with message to user about possible problem.
Other error codes cannot be handled on application side.
Code | Meaning |
---|---|
80a1 | Device is rooted |
80b1 | SDK cannot be used on emulator |
811a | User has installed some harmful application on device |
8a9b | User has attached debugger during SDK usage |
Application Isolated Process
Overview
Starting from Mobile DC SDK version 2.4.3 SDK provides additional security checks which could be enabled in setup. method using "withOptionalDeveloperOptions" and enableAdditionalSecurityChecks parameter.
For some security processes SDK need to start services marked as isolatedProcess to ensure more security by splitting application from a security layer.
It may cause problems with initialize Application as this service will run under a special process that is isolated from the rest of the system and has no permissions of its own.
To avoid problems, check if application start from isolated process and do not invoke any additional action in your Application::onCreate.
Sample implementation
//Mobile DC setup configuration for enableAdditionalSecurityChecks val developerOptions = DeveloperOptions() developerOptions.enableAdditionalSecurityChecks = true MobileDcApiConfigurationBuilder() // other require configuration .withOptionalDeveloperOptions(developerOptions) //Sample Application declaration for handing isolated process class SampleApplication : Application { override fun onCreate() { super.onCreate() if (isIsolatedProcess()) { return } //sample methods called in application startKoin() initializeMobileDcModule() initUcpModule() initFirebase() //other methods.. } private fun isIsolatedProcess(): Boolean { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { android.os.Process.isIsolated() } else { try { val activityManager = getSystemService(ACTIVITY_SERVICE) as ActivityManager activityManager.runningAppProcesses false } catch (securityException: SecurityException) { securityException.message?.contains("isolated", true) ?: false } catch (exception: Exception) { false } } } } |
This functionality is introduced to protect against magisk tool.
Magisk gives root access using daemon, which starts with system booting, with unrestricted SELinux context. When specific application needs access to /su it connects to daemon unix socket, process of granting access access is managed with Magisk Manager application.
Magisk Hide function hides /sbin paths for specific process, what obscures magisk presence in /proc/pid/mounts. This problems is solved with isolated processes introduced in android 4.1. They are started with new, fresh SELinux context without any permissions, what keeps magisk from removing it paths with /sbin. From this process view there is possibility to check mounted paths and find evidences of magisk presence.
Data signing and encryption
Configuration
Signing Data Configuration
Signature will be verified using public key certificate which was signed by preconfigured Root CA. DN from the certificate will be also validated. Root CA and DN will be set during onboarding. Client has two options:
-
provide Root CA and DN of the public key certificate for the private key which already has and use this private key for signing data.
DN cannot change during certificate renewals and all intermediate certificates should be placed in JWT
- generate csr where
CN=Signature-${uniqueClientName}
. Csr will be signed by uPaid. Client needs to use private key for signing from key pair used to generate csr.
Encryption Data Configuration
If the client decide to encrypt data, public key certificate from RSA 2048 should be set during onboarding. Public key certificate can be self-signed.
Signed Data Verification
Some methods requires signed data verification on client side. Public key certificate from RSA 2048 will be provided during onboarding and should be used for signature validation.
Data Encryption
Some data like Token Verification Code can be encrypted using JWE per RFC 7516. |
Configured(see Encrypting Data Configuration in Confoguration chapter) public key certificate will be used to encrypt CEK(Content Encryption Key).
Algorithm used for encryption:
JWE header | Name | Description |
---|---|---|
alg |
RSA-OAEP-256 |
Cryptographic algorithm used to encrypt CEK |
enc |
A256GCM |
Identifies the content encryption algorithm used to perform authenticated encryption |
Payload of the JWE will be json formatted object.
Client needs to perform decryption with private key using standard JWE library.
Sample code which shows how to decrypt JWE for given encryptedData and privateKey:
class RsaJweDecrypter { @Throws(Exception::class) fun decrypt(encryptedData: String, privateKey: PrivateKey): ByteArray { val parse = JWEObject.parse(encryptedData) parse.decrypt(RSADecrypter(privateKey)) return parse.payload.toBytes() } } |
Data Signing
Some fields are signed using JWT per RFC 7519. Data signing should be done on server side. |
JWTs are composed of three sections: a header, a payload (containing a claim set), and a signature.
The header and payload are JSON objects, which are serialized to UTF-8 bytes, then encoded using base64url encoding.
The JWT’s header, payload, and signature are concatenated with periods (.). As a result, a JWT typically takes the following form:
{Base64url encoded header}.{Base64url encoded payload}.{Base64url encoded signature} |
Object fields should be placed as claims in the token. Moreover there are some additional required claims that are needed to protect against replay attack:
Name | Description |
---|---|
iat |
The time at which the JWT was issued. UNIX Epoch timestamp, in seconds |
jti |
JWT ID - unique identifier for the JWT |
JWT tokens have TTL which is validated against "issued at" time from claims (iat). TTL is 10 minutes.
JWT tokens should be signed using private key from the RSA 2048 key pair (see Signing Data Configuration in Configuration chapter).
JWT tokens must have additional header:
Name | Description |
---|---|
x5c |
Parameter contains the X.509 certificates chain (RFC7515#section-4.1.6) |
This chain will be validated against preconfigured Root CA. DN from the public key certificate(from key pair which private key was used for signing) will be also validated (See Signing Data Configuration in Configuration chapter).
Sample code which shows how to build JWT for given claims, certificates and private key:
class JwtGenerator { fun generate( jwtClaimsSet: JWTClaimsSet, certificates: List<X509Certificate>, privateKey: PrivateKey ): String { val claims = buildFinalClaims(jwtClaimsSet) val certChains = certificates .map { it.encoded } .map { Base64.encode(it) } val jwsHeader = JWSHeader.Builder(JWSAlgorithm.RS256) .x509CertChain(certChains) .build() val signer = RSASSASigner(privateKey) val jwsObject = JWSObject( jwsHeader, Payload(claims.toJSONObject()) ) jwsObject.sign(signer) return jwsObject.serialize() } private fun buildFinalClaims(jwtClaimsSet: JWTClaimsSet): JWTClaimsSet { val epochSeconds = Instant.now().epochSecond return JWTClaimsSet .Builder(jwtClaimsSet) .claim("iat", epochSeconds) .jwtID(UUID.randomUUID().toString()) .build() } } |
Signed data verification
Some fields are signed on Wallet Server using JWT per RFC 7519. |
Wallet Server signs some data in response and signature has to be verified before this data are used. Data are signed using JWT per RFC 7519. JWTs are composed of three sections: a header, a payload (containing a claim set) and a signature.
The header and payload are JSON objects, which are serialized to UTF-8 bytes, then encoded using base64url encoding.
The JWT's header, payload, and signature are concatenated with periods (.).
As a result, a JWT typically takes the following form:
{Base64url encoded header}.{Base64url encoded payload}.{Base64url encoded signature} |
Specific object fields are placed as claims in the JWT token.
Moreover there are some additional claims that are added to the JWT token to protect against replay attack:
Name | Description |
---|---|
iat |
The time at which the JWT was issued. UNIX Epoch timestamp, in seconds |
jti |
JWT ID - unique identifier for the JWT |
JWT tokens are signed using private key from the RSA 2048 key pair. Public key certificate will be shared as part of configuration of given project and public key should be used for signature validation.
JWT tokens have additional header:
Name | Description |
---|---|
kid |
fingerprint of public key which's corresponding private key was used to sign data calculated as sha1Hex(publicKeyBytes) |
Sample code which shows how to verify signed JWT:
class JWTVerifier { fun verify( signedData: String, rsaPublicKey: RSAPublicKey, iatToleranceSeconds: Long ): JWTClaimsSet { val signedJWT = SignedJWT.parse(signedData) val signatureResult = signedJWT.verify(RSASSAVerifier(rsaPublicKey)) if (!signatureResult) { throw ValidationException("Invalid signature") } val jwtClaimsSet = signedJWT.getJWTClaimsSet() //optionally check iat with tolerance val isIatInAllowedTimeRange = jwtClaimsSet .getIssueTime() .toInstant() .plusSeconds(iatToleranceSeconds) .isAfter(Instant.now()) if (!isIatInAllowedTimeRange) { throw ValidationException("Signed JWT expired") } return jwtClaimsSet } } |
APK Certificate
About APK certificate
Verestro provides mechanism which ensures that authorized entities using Verestro SDKs.
To achieve that apk certificate needs to configured during onboarding.
Apk Certificate is binded to given client id, if customer uses many applications then many apk certificates digest should be provided and for each client id will be given by Verestro.
Calculate APK certificate digest
You can sign your application using self managed key, or let Google Play sign using Google Play managed keys:
- APK or Bundle signing key (aka "upload key").
- Google Play signing key (optional, may be enabled and configured in Google Play Console).
If you update key, you must follow steps below again and provide key to representative uPaid person.
Please check your installation package and follow the outlined steps.
Using own APK Signing Key
When signing apk in android studio only, please follow the steps below:
1. Convert java keystore (eg. cert.jks) used to sign APK to p12 format using following command:
keytool -importkeystore -srckeystore cert.jks -destkeystore cert.p12 -srcstoretype jks -deststoretype pkcs12 |
2. Convert obtained p12 file (eg. cert.p12) to pem format using following command:
openssl pkcs12 -nokeys -in cert.p12 -out cert.pem |
Using Google Play Signing Key
When signing application in Google Play console(whenever you uploaded own key, or used one that was automatically generated by Google), please follow steps below:
- Add .apk or .aab file in google play console.
- Select "Let Google create and manage my app signing key (recommended)" and press Continue.
- In the application panel in the menu look for release management option.
- Select app signing section.
- Download the certificate from App signing certificate section.
- Convert downloaded certificate from der to pem format using following command:
openssl x509 -inform der -in deployment_cert.der -out certificate.pem |
Recommended MPA data protection mechanisms
- Code obfuscation:
Code obfuscation is basic, and one of the most efficient application protection mechanism. Obfuscating code saves your app from static code analysis, exploring your code in search of vulnerabilities, that can be used by hooking software. There are many tools used for obfuscation nowadays, the most popular are proguard and its successor - R8. If you are using Kotlin in your project, R8 is a better option for you. It is also important to keep app architecture supporting obfuscation, using obfuscation tools is as simple as your architecture is adjusted to it. - Disable logs & turning off debugging:
It is very important to remember turning off logging data and disable debugging in your production application. Data printed in logs in development process should never be available in release application. It is recommended to manage printing it from one place, to not 'forget' removing every single of them. - Input data validation:
Input data should be validated on every level of app structure, it reduces risk of processing some unwanted, or even harmful data. It is recommended to clean sensitive data that is no longer useful, we advise to not use string to keep it. String is immutable, data stored in it can later be accessed by dumping application memory. We recommend using char[], and overwriting it when it is no longer useful. - Prevent taking screenshot:
We recommend adding a mechanism that prevents taking screenshots, and recording your application. It protects you from harmful applications getting users inputted data. - Prevent copy/paste :
Input taking sensitive data such as user password should not have the ability to copying and pasting in it. Storing it in clipboard expose it for copying, or modifying by harmful applications, even if it was initially encrypted it is plaintext when the user copies it. - Prevent screen overlay:
It is recommended to protect MPA from applications that have the possibility to overlay it. Not securing it may result with recording user data. - Checking the installer:
MPA should implement a mechanism that will check installer of application. It should check if the application is downloaded from Google Play, preventing it from possibility of tampering installation binary. - Checksum mechanism:
Implementing checking checksum mechanism is a very efficient way to check if your application was not tampered. It should compare checksum of your application, prepared after generating app with data send by your app after installing it on device. It bases on the fact that every generated application have different checksum, comparing it can ensure you that your server is communicating with the app released by you. - Removal redundant permissions:
It is important to keep in your app only used permissions. You should not give your application access to components, that it not requires. - Network security configuration:
MPA should use network security configuration application customize their network security settings(for example protection application from usage of cleartext traffic) in declarative configuration file without modifying app code.
Models
LimitedResourceInfo
Parameter | Type | Description |
---|---|---|
xRateLimitLimit | Int? | Limit of requests until resource will be locked |
xRateLimitRemaining | Int? | Remaining amount of requests |
retryAfter | Int? | Seconds until lock expires |
xRateLimitReset | Long? | Lock expiration timestamp - epoch in seconds |
External libraries
The SDK uses several external Apache 2.0 libraries:
- com.nimbusds:nimbus-jose-jwt
- commons-codec:commons-codec
- com.fasterxml.jackson.core:jackson-core
- com.fasterxml.jackson.core:jackson-annotations
- com.fasterxml.jackson.core:jackson-databind
- com.fasterxml.jackson.module:jackson-module-kotlin
- io.insert-koin:koin-android
- io.reactivex.rxjava2:rxjava
- io.reactivex.rxjava2:rxandroid
- com.squareup.retrofit2:adapter-rxjava2
- com.squareup.retrofit2:retrofit
- com.squareup.retrofit2:converter
- com.squareup.okhttp3:logging
- com.squareup.okhttp3:okhttp
MDC SDK Setup
Setup
Asynchronous. Offline. Mobile DC initialization method. Contains all necessary data to configure SDK. Should be called at very beginning of application lifecycle. For example in Android Application::onCreate method. Note: Please be aware to use this method before usage of any MobileDC methods or another Verestro product. |
Input
Mobile DC different configuration options depending on SKD usage.
Configuration is provided by builder in facade method named setup.
All builder methods without optional keyword in method name are required.
Available configurations methods:
- setup(MobileDcCoreConfiguration)
- setup(MobileDcApiConfiguration)
SDK Event Listeners - errors catching The application should prevent throwing exceptions in MDC SDK Event Listeners described below as it could cause breaking process in SDK. In case of error in application event listener implementation SDK runs this code in try-catch block. |
MobileDcCoreConfiguration
Used when usage of uPaid product doesn't require Verestro backend system (f.e. UCP with External Wallet Server).
Method | Parameter | Description |
Validation conditions |
---|---|---|---|
withApplicationContext | Context | Application context | Not empty |
withWalletSecurityEventListener |
WalletSecurityEventListener |
Global listener for security events |
Not empty |
withOptionalLogger | EventsLogger | Handler for secure logging of sdk events | |
withOptionalDeveloperOptions |
DeveloperOptions |
Additional options for developers |
MobileDcApiConfiguration
Used when applicaton use Mobile DC API for user data management.
Method | Parameter | Description |
Validation conditions |
---|---|---|---|
withApplicationContext | Context | Application context | Not null |
withMobileDcConnection |
MobileDcConnection |
Mobile DC API connection configuration delivered by uPaid |
Not empty |
withWalletSecurityEventListener |
WalletSecurityEventListener |
Global listener for security events |
Not empty |
withOptionalWalletPin Configuration |
MobileDcWalletPin Configuration |
Wallet Pin configuration in SDK | |
withOptionalLogger | EventsLogger | Handler for secure logging of sdk events | |
withOptionalDeveloperOptions | DeveloperOptions | Additional options for developers | |
withOptionalCloudMessaging Configuration |
MobileDcCloudMessaging |
Interface for providing cloud messaging configuration |
MobileDcConnectionConfiguration
Parameter | Type | Description |
Validation conditions |
---|---|---|---|
url | String |
Url for mobile DC environment in which SDK connect. |
Not empty |
pinningCertificates | List<String> |
List of public key certificate in PEM associated with host server.
|
Not empty |
clientId | String |
Each customer has a unique client id it's different for development and production environment. ClientId should be provided by uPaid representative. |
Not empty |
MobileDcWalletPinConfiguration
Parameter | Type | Description |
Validation conditions |
---|---|---|---|
pinFailCount | Int |
Number of possible fails in offline pin verification |
Greater then 0 |
WalletSecurityEventListener
Method |
Parameters |
Description |
---|---|---|
onSecurityIssueAppeared |
EventSecurityIssueAppeared |
Information about security issue. Due to security requirements this message isn’t readable for user When using setup(MobileDcApiConfiguration) SDK reports event instantly to server (when internet is available). Could be also catched on application side and reported by client. Some error codes could be properly handled in application Security processes overview |
EventsLogger
Method |
Parameters |
Description |
---|---|---|
onEvent | onEvent(eventId:Int) |
Callback to trace list of events that occured in SDK (those are secure aliases - event as Integer). You can collect 'em in ordered list and store to be accesible in case of errors/exceptions. |
DeveloperOptions
Parameter | Type |
Description |
---|---|---|
allowDebug |
Boolean |
Determine AndroidManifest android:debuggable state in <application> scope. When configured as true SDK verifies combination of SDK production(without -debug) version and android:debuggable=true. |
enableAdditionalSecurityChecks | Boolean |
Enables additional security checks in application required during EMV Security Evaluation. Enabling requires additional action on application side, refer to Application Isolated Process to get more details. |
enableCodeObfuscationCheck | Boolean |
Enable additional check if application obfuscates SDK facade classes. Default value is false. |
MobileDcCloudMessagingConfiguration
Parameter |
Type |
Description |
---|---|---|
RegistrationToken |
RegistrationProvider |
Provides information |
Method |
Parameters |
Description |
---|---|---|
getRegistrationToken | - |
Gets actual FCM registration token from application. |
CloudMessagingRegistrationToken
Parameter | Type |
Description |
---|---|---|
type |
CloudMessagingType |
Mandatory. One of: [FCM]. FCM - Firebase Cloud Messaging |
token | String | Registaration token for push notifications |
Sample
Initialization for MobileDcCoreConfiguration (without Wallet Server).
var eventsTrace = arrayListOf<Int>()//make use of this in case of error/failure/unexpected behaviour to retrace sdk internal processes val mobileDcCoreConfiguration = MobileDcCoreConfiguration.create( MobileDcCoreConfigurationBuilder() .withApplicationContext(this) .withWalletSecurityEventListener(object : WalletSecurityEventListener { override fun onSecurityIssueAppeared(event: EventSecurityIssueAppeared) { //eg. show security screen or toast with details from event object //all data collected by SDK will be removed //Clearing application data in context of SDK is recommended } }) .withOptionalLogger(object : EventsLogger { override fun onEvent(eventId: Int) { eventsTrace.add(eventId) } }) .withOptionalDeveloperOptions(DeveloperOptions().apply { allowDebug = false enableAdditionalSecurityChecks = true enableCodeObfuscationCheck = true }) ) MobileDcApiKotlin.setup(mobileDcCoreConfiguration) |
Initialization for MobileDcApiConfiguration.MobileDcApiConfiguration
Additional isolated process check, read more: Application Isolated Process
Address domainaddAddress
Input AddAddress
Output
Sample
getAll
Input
Output
Address
Country
Sample
updateAddress
Input
UpdateAddress
Output
Sample
deleteAddress
Input
Output
Sample
User domainaddUserAsynchronous. Online. Creates new user wallet account with given parameters. Input NewUser model is required, but most of it's fields are optional. Fields requirement depends on product integration process.
NewUser object contains following fields:
Output Success / failure callback. Sample
addUser (deprecated)
Input Most of fields are optional. Fields requirement depends on product integration process.
Output
addUserWithIban
Input
AccountInfo
Output
Sample
updateUser
Input
UpdateUser
Output
Sample
getUser
Input
Output
User contains following fields:
Country contains following fields.
Sample
finalizeUserRegistration
Input
Output
Sample
authenticateByPin
Input
Output
Sample
isBiometricAuthenticationEnabled
Input No input parameters Output
Sample
enableBiometricAuthentication
Input
Output
Sample //KeyguardManager example keyguardManager = context.getSystemService(KEYGUARD_SERVICE) fun isDeviceSecure(): Boolean { return keyguardManager.isKeyguardSecure } //if isDeviceSecure return true device is safe and enableBiometricAuthentication method can be performed fun enableBiometricAuthentication(userName: CharArray, password: CharArray, biometricAuthType : BiometricAuthType) { mobileDcApi.userService .enableBiometricAuthentication(userName, password, biometricAuthType, { // Biometric authentication is enabled. Read startBiometricAuthentication description to authenticate user. }, { throwable -> // Something went wrong // Check exceptions described in Error handling chapter } ) } disableBiometricAuthentication
Input
Output
Sample
enableBiometricAuthenticationWithWalletPin
Input
Output
Sample
startBiometricAuthentication
Input
Output
BiometricAuthenticationData contains following field:
Sample startBiometricAuthentication sample:
BiometricAuthenticationData should be used as parameter in authenticateWithBiometric but application must cast cryptoObject parameter to CryptoObject. Cast to CryptoObject sample :
Complete flow of authentication with biometric methods: Create your own custom view, which require to authenticate with biometric.
authenticateWithBiometric
Input
Output
Sample
loginByTrustedIdentity
Input
TrustedIdentity
Output
Sample Sample code for trustedIdentity generation (see also Data signing and encryption chapter):
Usage of trustedIdentity in application:
loginByPin
Input
Output
Sample
loginBySignature
Input
Output
Sample
changeWalletPin
Input
Output
Sample
resetWalletPin
Input
Output
Sample
changePassword
Input
Output
Sample
initializeResetUserPassword (deprecated)
Input
Output
Sample
initializeResetUserPassword
Input
InitializeResetUserPassword model:
Output
Sample
finalizeResetUserPassword
Input
Output
Sample
initializeEmailUpdate
Input
Output
Sample
finalizeEmailUpdate
Input
Output
Sample
resendOtp
Input
Output
Sample
logout
Input
Output
Sample
deleteUser
Input
Output
Sample
Cards domainaddCard
Input
AddCard
CardHolder
Output
Sample
addCard (deprecated)
Input
Output
Sample
getAll
Input
Output
Card
CardHolder
Sample
deleteCard
Input
Output
Sample
initialize3ds
Input
Output
Initialize3dsResult
Sample
finalize3ds
Input
Output
Finalize3dsResult
Sample
initialize3DSv2
Input
Initialize3DSv2
BrowserDetails
Output
Initialize3DSv2Result
Sample
finalize3DSv2
Input
Finalize3DSv2
Output
Sample
updateCard
Input
UpdateCard
Output
Sample
verifyCard
Input
VerifyCard
Output
Sample
Device domainpairByPassword (deprecated in 2.14.6)
Input
Output
Sample
pairByPassword
Input
PairByPassword
CloudMessagingRegistrationToken
Output
Sample
pairByTrustedIdentity (deprecared in 2.14.6)
Input
TrustedIdentity
Output
Sample Sample code for trustedIdentity generation (see also Data signing and encryption chapter):
Usage of trustedIdentity in application:
pairByTrustedIdentity
Input
PairByTrustedIdentity
TrustedIdentity
CloudMessagingRegistrationToken
Output
Sample Sample code for trustedIdentity generation (see also Data signing and encryption chapter):
Usage of trustedIdentity in application:
pairByExternalCredentials (deprecated in 2.14.6)
Input
Output
Sample
pairByExternalCredentials
Input
PairByExternalCredentials
CloudMessagingRegistrationToken
Output
Sample
isDevicePaired
Input
Output
Sample
unPairDevice
Input
Output
Sample
Cloud messaging domainprocess
Input
Output
Sample
|