Friday, May 25, 2018

Download IPA file from Apple app store

Sometimes it is important to download an ipa file from iOS app store to examine the app resource and configuration, or re-sign the ipa file for testing purpose.

The following steps can be used to do so

1. install Apple Configurator 2 on your mac.
2. From Top menu bar, select Account and add your AppID account
3. Connect your ios device
4. In the current window, click "add" button and select the app you want to download
5. When the dialog asks you about "Would you like to replace it with the one you are adding", from Finder, goto the below folder and search for the ipa file just downloaded. Copy the ipa file to a different folder for you to use, then click the stop button

~/Library/Group\ Containers/K36BKF7T3D.group.com.apple.configurator/Library/Caches/Assets/TemporaryItems/MobileApps


Thursday, May 24, 2018

Share encrypted data between ios and android clients

It is a common use case to use password to encrypt sensitive data in mobile applications. The basic logic is first deriving an encryption key based on password, and then use the password to encrypt the sensitive data.

The question is when using the same algorithm and password on both ios and android client,
1. Would both clients generate the same encryption key?
2. If so, could data encrypted from one client be decrypted by another client?

Question 1: Encryption key generation

For this testing, key generation is implemented using PBKDF2.

For iOS client (iOS 11.3 on iphone 7) , CCKeyDerivationPBKDF method can be used for this purpose. For testing purpose, the password is hardcoded as "password", and the salt is hardcoded as "salt", and the hash algorithm is KCCPRFHmacAlgSHA512,  the round is set to 100. Note the derived keysize should match the selected algorithm, there is no point to use KCCPRFHmacAlgSHA512 to generate an 32 bytes key (256 bit), as the total result hash is limited to 32 byte instead of 64 byte. Actually in iOS, if the keysize is smaller than the algorithm generated keysize, the generated key will be truncated to the specified keysize.

        var password = "password"
        var salt = "salt"
        var derivedKey : NSMutableData = NSMutableData(length: kCCKeySizeAES256)!
        var passwordData = NSString(string: password).utf8String
        var passwordDataSize = password.utf8.count
        var saltData = NSString(string: salt).utf8String
        var saltDataSize = salt.utf8.count
        var saltDataPointer = UnsafeRawPointer(saltData!).bindMemory(to:UInt8.self, capacity:saltDataSize)

        CCKeyDerivationPBKDF(CCPBKDFAlgorithm(kCCPBKDF2), passwordData,
                             passwordDataSize, saltDataPointer, saltDataSize,
                             CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256),
                             uint(100),
                             UnsafeMutablePointer<UInt8>         (derivedKey.mutableBytes.assumingMemoryBound(to:UInt8.self)),
                             derivedKey.length)

For Android client, to match the iOS side configuration, SecretKeyFactory is used with the algorithm of PBKDF2withHmacSHA512. Note this algorithm is only supported on Android API 26 (Android 8.0) and above. The testing is done on Samsung S9 device.

SecretKeyFactory provider = SecretKeyFactory.getInstance("PBKDF2withHmacSHA256");
String password = "password";
String salt = "salt";
char[] passwordData = password.toCharArray();
byte[] saltData = salt.getBytes("UTF8");
KeySpec keySpec = new PBEKeySpec(passwordData, saltData, 100, 256);
SecretKey key = provider.generateSecret(keySpec);
byte[] keydata = key.getEncoded();

The test shows both ios and android client generate the same encryption key as showing below in hex format, so this confirms the ios and android clients can derive the same encryption key using the same password and algorithm.

"07e6997180cf7f12904f04100d405d34888fdf62af6d506a0ecc23b196fe99d8"


Question 2: Data encryption

Now that we know that the same key can be generated by ios and android client based on the same password, the second steps is checking whether data encrypted by one client can be decrypted by another client using the same encryption key.

The IV is set to a 16 byte array of 0.  The data is a simple string of "this is a testing string".

On ios the encrypt method is using CCCrypte

        var outLength : size_t = 0;
        let cipherData : NSMutableData? = NSMutableData(length: dataToEncrypt.count + kCCBlockSizeAES128);
        let ivb : [UInt8] = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
        let iv = NSData(bytes: ivb, length: 16)
        let result = CCCrypt(            UInt32(kCCEncrypt), // operation
            UInt32(kCCAlgorithmAES128), // algorithm
            UInt32(kCCOptionPKCS7Padding), // options
            (encryptionKey as NSData).bytes, // key
            encryptionKey.count, // keylength
            iv.bytes, // iv
            (dataToEncrypt as NSData).bytes, // dataIn
            dataToEncrypt.count, // dataInLength,
            UnsafeMutablePointer<UInt8>(cipherData!.mutableBytes.assumingMemoryBound(to:UInt8.self)), // dataOut
            cipherData!.length, // dataOutAvailable
            &outLength); // dataOutMoved


On Android client, Cipher is used to encrypt data as below

byte[] dataByteToEncrypt = dataToEncrypt.getBytes("UTF8");
Cipher dataCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
dataCipher.init(Cipher.ENCRYPT_MODE, key, iv );
byte[] encrypedData = dataCipher.doFinal(dataByteToEncrypt);

On both client platform, the encryption generates the same output byte array of
"92a78f657da19a444e28c83f604a63401dc9a81300dcf4b2707fe66a9d62f158"ta 

Conclusion:
The testing result indicates when the same algorithm, salt, iv and password are used, the data encrypted in one platform can be decrypted in another platform, so there is no need to use external third party library to handle data encryption/decryption when the encrypted data need to be transferred between android and ios clients.


Friday, May 11, 2018

Understanding Android Studio buildscript repository

The android project gradle build script always starts with the below block

repositories {
    mavenLocal()
    google()
    jcenter()
}

mavenLocal(), by default, mavenLocal will load the dependent library from ~/.m2/respository folder. So assume the gradle file has the below item
   implementation group: 'com.sap.cloud.mobile.fiori', name: 'fiori', version: 1.0.0-SNAPSHOT
then the gradle will try to load the library from the below folder
    ~/.m2/repository/com/sap/cloud/mobile/fiori/fiori/1.0.0-SNAPSHOT
   
Actually, you can set any local folder as a repository by using the below code
    maven {
            url "file://local/repo/"
    }


google() is a repository managed by google for google and android libraries. The actual url is
     https://maven.google.com
which redirects to https://dl.google.com/dl/android/maven2/. You can find all group in the repository from
https://dl.google.com/dl/android/maven2/index.html

   
For example, if the gradle file include the below line
    implementation "com.google.firebase:firebase-core:15.0.0"
you can find the item from above url, which list all versions available for the firebase-core library

The library will be loaded from
https://dl.google.com/dl/android/maven2/com/google/firebase/firebase-core/15.0.2/firebase-core-15.0.2.pom
Based on the pom file, it indicate the library type, so you can download other files of the library


jcenter() is a general repository, if a dependent library cannot be find in local .m2 and google() repository, then it is in jcenter repository.
The url of the repository is
https://jcenter.bintray.com

For example, if gradle file includes the below code
    implementation 'com.squareup.okhttp3:okhttp:3.10.0'
then the gradle will download the library from
https://jcenter.bintray.com/com/squareup/okhttp3/okhttp/
which lists all supported versions. This is much easy to use than the google() repository. You can also get detail about a particular library from
https://jcenter.bintray.com/com/squareup/okhttp3/okhttp/3.10.0/

So next time, a particular version of a dependent library cannot be find, then you can check the repository to find out the reason.

Saturday, May 5, 2018

ios uiwebview xmlhttprequest fails to load local data file from local html page

When local html file is loaded into iOS UIWebView, as long as content security policy does not have limitation, then the javascript code in local html page can use xmlhttprequest to get data from remote server, for example, http://www.odata.com.

However, on the same html page, using xmlhttprequest to load file from local file system using the relative path will fail. The xmlhttprequest will return status 0 in onload callback method (readystate 4). This is strange, as usually the application should always trust the local file system than external web server.

Note even if the status is set to 0, the xmlhttprequest.responseText field already contains the file content. So one workaround is checking the responseText field, and if it is not "", then handle the response as if the status code is 200. A sample code is shown below

  
            var x = new XMLHttpRequest();
           
            x.addEventListener('load', function(e) {
                if (x.status === 200 || x.responseText != "" ) {
                   //handle success
                } else {
                    //handle error
                }
            });
            x.addEventListener('error', function(e) {
                //handle error
            });
            x.open('GET', url);
            try {
                x.send();
            } catch (b) {
               //handle error
            }
        }


Thursday, May 3, 2018

Use fiddler trace to delay the server response and modify response

Recently, there is a test case that requires to delay the server response so as to verify whether the client handles the timeout exception properly.

This can be done with Fidder script, as the only request we want to delay is after 401 credential is provided by user in a post request, so the below Fiddler rule is applied.

To do this,
1. open fiddler
2. select Rule/Customize rules ... menu
3. update the OnBeforeResponse method with the below code to delay the response for 900 seconds
    static function OnBeforeResponse(oSession: Session) {
        if (m_Hide304s && oSession.responseCode == 304) {
            oSession["ui-hide"] = "true";
        }
        if (oSession.HTTPMethodIs("POST")) { 
            if ( oSession.oRequest.headers.Exists("Authorization")){
                Thread.Sleep(90000);
            }
        }
    }

In addition, Fiddler also provides an easy way to customize response from server, like javascript file. To do so
1. send the request to server to get the response in fiddler
2. select the request&response item from fiddler and drag and drop to AutoResponder tab on right side of the screen
3. right click the item added in AutoResponder tab and select "Edit Response ..." context menu
4. in the opened dialog, edit the header and response text and then save the change
5. click Enable rule checkbox in AutoResponder tab
6. send the request again, and verify the customized response is returned to device.