• Aucun résultat trouvé

Updating a Package

Dans le document Android Security Internals (Page 99-109)

The process of updating a package follows most of the same steps as install-ing a package, so we’ll highlight only the differences here.

Signature Verification

The first step is to check whether the new package has been signed by the same set of signers as the existing one. This rule is referred to as same origin policy, or Trust On First Use (TOFU). This signature check guarantees that the update is produced by the same entity as the original application (assuming that the signing key has not been compromised) and establishes a trust rela-tionship between the update and the existing application. As we shall see in

“Updating Non-System Apps” on page 75, the update inherits the data of the original application.

N O T E When signing certificates are compared for equality, the certificates are not validated in the PKI sense of the word (time validity, trusted issuer, revocation, and so on are not checked).

The certificate equality check is performed by the PackageManagerService .compareSignatrues() method as shown in Listing 3-14.

static int compareSignatures(Signature[] s1, Signature[] s2) { if (s1 == null) {

return s2 == null

? PackageManager.SIGNATURE_NEITHER_SIGNED : PackageManager.SIGNATURE_FIRST_NOT_SIGNED;

}

9. See CommonsWare, CWAC-Security, https://github.com/commonsguy/cwac-security, for further discussion and a sample project that shows how to perform the check.

if (s2 == null) {

return PackageManager.SIGNATURE_SECOND_NOT_SIGNED;

}

HashSet<Signature> set1 = new HashSet<Signature>();

for (Signature sig : s1) { set1.add(sig);

}

HashSet<Signature> set2 = new HashSet<Signature>();

for (Signature sig : s2) { set2.add(sig);

}

// Make sure s2 contains all signatures in s1.

if (set1.equals(set2)) {u

return PackageManager.SIGNATURE_MATCH;

}

return PackageManager.SIGNATURE_NO_MATCH;

}

Listing 3-14: Package signature comparison method

Here, the Signature class serves as an “opaque, immutable representa-tion of a signature associated with an applicarepresenta-tion package.” 10 In practice, it is a wrapper for the DER-encoded signing certificate associated with an APK file. Listing 3-15 shows an excerpt, focusing on its equals() and hashCode()

methods.

public class Signature implements Parcelable { private final byte[] mSignature;

private int mHashCode;

private boolean mHaveHashCode;

public Signature(byte[] signature) { mSignature = signature.clone();

}

public PublicKey getPublicKey() throws CertificateException { final CertificateFactory certFactory =

CertificateFactory.getInstance("X.509");

final ByteArrayInputStream bais = new ByteArrayInputStream(mSignature);

final Certificate cert = certFactory.generateCertificate(bais);

return cert.getPublicKey();

} @Override

public boolean equals(Object obj) { try {

10. Google, Android API Reference, “Signature,” https://developer.android.com/reference/android/

} catch (ClassCastException e) { }

return false;

} @Override

public int hashCode() { if (mHaveHashCode) { return mHashCode;

}

mHashCode = Arrays.hashCode(mSignature);v mHaveHashCode = true;

return mHashCode;

} --snip--}

Listing 3-15: Package signature representation

As you can see at u, two signature classes are considered equal if the DER-encoding of the underlying X.509 certificates match exactly, and the

Signature class hash code is calculated solely based on the encoded certificate v. If the signing certificates do not match, the compareSignatures() methods returns the INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES error code.

This binary certificate comparison naturally knows nothing about CAs or expiration dates. One consequence of this is that after an app (identified by a unique package name) is installed, updates need to use the same sign-ing certificates (with the exception of system app updates, as discussed in

“Updating System Apps” on page 75).

While multiple signatures on Android apps are rare, they do occur. If the original application was signed by more than one signer, any updates need to be signed by the same signers, each using its original signing cer-tificate (enforced by u in Listing 3-14). This means that if a developer’s signing certificate(s) expires or he loses access to his signing key, he cannot update the app and must release a new one instead. This would result in not only losing any existing user base or ratings, but more importantly losing access to the legacy app’s data and settings.

The solution to this problem is straightforward, if not ideal: back up your signing key and don’t let your certificate expire. The currently rec-ommended validity period is at least 25 years, and the Google Play Store requires validity until at least October 2033. While technically this only amounts to putting off the problem, proper certificate migration support might eventually be added to the platform.

When the package manager establishes that the update has been signed with the same certificate, it proceeds with updating the package. The process is different for system and user-installed apps, as described next.

Updating Non-System Apps

Non-system apps are updated by essentially reinstalling the app while retain-ing its data directory. The first step is to kill any process of the package beretain-ing updated. Next, the package is removed from internal structures and the package database, which removes all components that the app has registered as well. Next, the PackageManagerService triggers a package scan by calling the scanPackageLI() method. The scan proceeds as it would with new installs, except that it updates the package’s code, resource path, version, and time-stamp. The package manifest is scanned and any defined components are registered with the system. Next, permissions for all packages are re-granted to ensure that they match any definitions in the updated package. Finally, the updated packaged database is written to disk and a PACKAGE_REPLACED system broadcast is sent.

Updating System Apps

As with user-installed apps, preinstalled apps (usually found in /system/app/) can be updated without a full-blown system update, usually via the Google Play Store or a similar app distribution service. Though because the system partition is mounted read-only, updates are installed in /data/app/, while the original app is left intact. In addition to a <package> entry, the updated app will also have an <updated-package> entry that might look like the example in Listing 3-16.

<package name="com.google.android.keep"

codePath="/data/app/com.google.android.keep-1.apk"u

nativeLibraryPath="/data/app-lib/com.google.android.keep-1"

flags="4767461"v ft="142ee64d980"

it="14206f3e320"

ut="142ee64dfcb"

version="2101"

userId="10053"w

installer="com.android.vending">

<sigs count="1">

<cert index="2" />

</sigs>

<signing-keyset identifier="3" />

<signing-keyset identifier="34" />

</package>

--snip--<updated-package name="com.google.android.keep"

codePath="/system/app/Keep.apk"

nativeLibraryPath="/data/app-lib/Keep"

ft="ddc8dee8"

it="14206f3e320"

ut="ddc8dee8"

version="2051"

userId="10053">x

<perms>

<item name="android.permission.READ_EXTERNAL_STORAGE" />

<item name="android.permission.USE_CREDENTIALS" />

<item name="android.permission.WRITE_EXTERNAL_STORAGE" />

</perms>

</updated-package>

Listing 3-16: Package database entries for an updated system package

The update’s codePath attribute is set to the path of the new APK in /data/app/ u. It inherits the original app’s permissions and UID (w and x) and is marked as an update to a system app by adding the

FLAG_UPDATED_SYSTEM_APP (0x80) to its flags attribute v.

System apps can be updated directly in the system partition as well, usu-ally as the result of an OTA system update, and in such case the updated system APK is allowed to be signed with a different certificate. The rationale behind this is that if the installer has enough privileges to write to the system partition, it can be trusted to change the signing certificate as well. The UID, and any files and permissions, are retained. The exception is that if the package is part of a shared user (discussed in Chapter 2), the sig-nature cannot be updated, because doing so would affect other apps. In the reverse case, when a new system app is signed by a different certificate than that of the currently installed non-system app (with the same package name), the non-system app will be deleted first.

Installing Encrypted APKs

Support for installing encrypted APKs was added in Android 4.1 along with support for forward locking using ASEC containers. Both features were announced as app encryption, but we’ll discuss them separately, begin-ning with support for encrypted APK files. But first let’s see how to install encrypted APKs.

Encrypted APKs can be installed using the Google Play Store client, or with the pm command from the Android shell, but the system PackageInstaller

does not support encrypted APKs. Because we can’t control the Google Play Store installation flow, in order to install an encrypted APK we need to either use the pm command or write our own installer app. We’ll take the easy route and use the pm command.

Creating and Installing an Encrypted APK

The adb install command both copies the APK file to a temporary file on the device and starts the install process. The command provides a conve-nient wrapper to the adb push and pm install commands. adb install gained three new parameters in Android 4.1 in order to support encrypted APKs (see Listing 3-17).

adb install [-l] [-r] [-s] [--algo <algorithm name> --key <hex-encoded key>

--iv <hex-encoded iv>] <file>

Listing 3-17: adb install command options

The --algo, --key, and --iv parameters let you specify the encryption algorithm, key, and initialization vector (IV), respectively. But in order to use those new parameters, we need to create an encrypted APK first.

An APK file can be encrypted using the enc OpenSSL commands as shown in Listing 3-18. Here we use AES in CBC mode with a 128-bit key, and specify an IV that is the same as the key in order to make things simpler.

$ openssl enc -aes-128-cbc -K 000102030405060708090A0B0C0D0E0F

-iv 000102030405060708090A0B0C0D0E0F -in my-app.apk -out my-app-enc.apk

Listing 3-18: Encrypting an APK file using OpenSSL

Next, we install our encrypted APK by passing the encryption algo-rithm key (in javax.crypto.Cipher transformation string format, which is discussed in Chapter 5) and IV bytes to the adb install command as shown in Listing 3-19.

$ adb install --algo 'AES/CBC/PKCS5Padding' \ --key 000102030405060708090A0B0C0D0E0F \

--iv 000102030405060708090A0B0C0D0E0F my-app-enc.apk pkg: /data/local/tmp/my-app-enc.apk

Success

Listing 3-19: Installing an encrypted APK using adb install

As the Success output indicates, the APK installs without errors. The actual APK file is copied into /data/app/, and comparing its hash with our encrypted APK reveals that it is in fact a different file. The hash value is exactly the same as that of the original (unencrypted) APK, so we conclude that the APK is decrypted at install time using the provided encryption parameters (algorithm, key, and IV).

Implementation and Encryption Parameters

Let’s see how this is implemented. After it has transferred the APK to the device, adb install calls the pm Android command-line utility with the install parameter and the path to the copied APK file. The compo-nent responsible for installing apps on Android is PackageManagerService

and the pm command is just a convenient frontend for some of its func-tionality. When started with the install parameter, pm calls the method

installPackageWithVerificationAndEncryption(), converting its options to the relevant parameters as necessary. Listing 3-20 shows the method’s full signature.

public void installPackageWithVerificationAndEncryption(Uri packageURI, IPackageInstallObserver observer, int flags,

String installerPackageName,

VerificationParams verificationParams, ContainerEncryptionParams encryptionParams) {

--snip--}

Listing 3-20: PackageManagerService.installPackageWithVerificationAndEncryption() method signature

We discussed most of the method’s parameters in “APK Install Process” earlier, but we have yet to encounter the VerificationParams and

ContainerEncryptionParams classes. As the name implies, the VerificationParams

class encapsulates a parameter used during package verification, which we will discuss in “Package Verification” on page 83. The ContainerEncryptionParams

class holds encryption parameters, including the values passed via the --algo,

--key, and --iv options of adb install. Listing 3-21 shows its data members.

public class ContainerEncryptionParams implements Parcelable { private final String mEncryptionAlgorithm;

private final IvParameterSpec mEncryptionSpec;

private final SecretKey mEncryptionKey;

private final String mMacAlgorithm;

private final AlgorithmParameterSpec mMacSpec;

private final SecretKey mMacKey;

private final byte[] mMacTag;

private final long mAuthenticatedDataStart;

private final long mEncryptedDataStart;

private final long mDataEnd;

--snip--}

Listing 3-21: ContainerEncryptionParams data members

The adb install parameters above correspond to the first three fields of the class. While not available through the adb install wrapper, the

pm install command also takes the --macalgo, --mackey, and --tag param-eters, which correspond to the mMacAlgorithm, mMacKey, and mMacTag fields of the ContainerEncryptionParams class. In order to use those parameters, we need to calculate the MAC value of the encrypted APK first, which we accomplish with the OpenSSL dgst command as shown in Listing 3-22.

$ openssl dgst -hmac 'hmac_key_1' -sha1 -hex my-app-enc.apk

HMAC-SHA1(my-app-enc.apk)= 962ecdb4e99551f6c2cf72f641362d657164f55a Listing 3-22: Calculating the MAC of an encrypted APK

N O T E The dgst command doesn’t allow you to specify the HMAC key using hexadecimal or Base64, so we’re limited to ASCII characters. This may not be a good idea for produc-tion use, so consider using a real key and calculating the MAC in some other way (for example, using a JCE program).

Installing an Encrypted APK with Integrity Check

We can now install an encrypted APK and verify its integrity by opening the Android shell using adb shell and executing the command shown in Listing 3-23.

$ pm install -r --algo 'AES/CBC/PKCS5Padding' \ --key 000102030405060708090A0B0C0D0E0F \ --iv 000102030405060708090A0B0C0D0E0F \

--macalgo HmacSHA1 --mackey 686d61635f6b65795f31 \

--tag 962ecdb4e99551f6c2cf72f641362d657164f55a /sdcard/my-app-enc.apk pkg: /sdcard/kr-enc.apk

Success

Listing 3-23: Installing an encrypted APK with integrity verification using pm install

The app’s integrity is checked by comparing the specified MAC tag with the value calculated based on the actual file contents, the contents are decrypted, and the decrypted APK is copied to /data/app/. (To test that MAC verification is indeed performed, change the tag value slightly. Doing so should result in an install error with error code INSTALL_FAILED_INVALID_APK.)

As we saw in Listings 3-19 and 3-23, the APK files that are ultimately copied to /data/app/ are not encrypted and thus the installation process is the same as for unencrypted APKs, except for file decryption and the optional integrity verification. Decryption and integrity verification are performed transparently by the MediaContainerService while copying the APK to the application directory. If a ContainerEncryptionParams instance is passed to its copyResource() method, it uses the provided encryption parameters to instantiate the JCA classes Cipher and Mac (see Chapter 5) that can perform decryption and integrity checking.

N O T E The MAC tag and encrypted APK can be bundled in a single file, in which case the

MediaContainerService uses the mAuthenticatedDataStart, mEncryptedDataStart, and

mDataEnd members to extract the MAC and APK data from the file.

Forward Locking

Forward locking appeared around the time ringtones, wallpapers, and other digital “goods” started selling on feature phones. Because installed APK files are world readable on Android, it’s relatively easy to extract apps from even a production device. In an attempt to lock down paid apps (and prevent a user from forwarding them to another user) without losing any of the OS’s flexibility, early Android versions introduced forward locking (also called copy protection).

The idea behind forward locking was to split app packages into two parts: a world-readable part that contains resources and the manifest (in /data/app/), and a package that is readable only by the system user and which contains executable code (in /data/app-private/). The code package was protected by filesystem permissions, which made it inaccessible to users on most consumer devices, but it could be extracted from devices with root access, and this early forward locking mechanism was quickly deprecated and replaced with an online application licensing service called Google Play Licensing.

The problem with Google Play Licensing was that it shifted app pro-tection implementation from the OS to app developers, and it had mixed results. The forward locking implementation was redesigned in Android 4.1, and now offers the ability to store APKs in an encrypted container that requires a device-specific key to be mounted at runtime. Let’s look at it in a bit more detail.

Android 4.1 Forward Locking Implementation

While the use of encrypted app containers as a forward locking mechanism was introduced in Android version 4.1, encrypted containers were originally introduced in Android 2.2. At that time (mid-2010), most Android devices came with limited internal storage and relatively large (a few gigabytes) external storage, usually in the form of a microSD card. To make file shar-ing easier, external storage was formatted usshar-ing the FAT filesystem, which lacks file permissions. As a result, files on the SD card could be read and written by any application.

To prevent users from simply copying paid apps from the SD card, Android 2.2 created an encrypted filesystem image file and stored the APK in it when a user opted to move an app to external storage. The sys-tem would then create a mount point for the encrypted image, and mount it using Linux’s device-mapper. Android loaded each app’s files from its mount point at runtime.

Android 4.1 built on this idea by making the container use the ext4 filesystem, which allows for file permissions. A typical forward-locked app’s mount point now looks like Listing 3-24 (timestamps omitted).

# ls -l /mnt/asec/com.example.app-1 drwxr-xr-x system system lib drwx--- root root lost+found -rw-r--- system u0_a96 1319057 pkg.apk -rw-r--r-- system system 526091 res.zip Listing 3-24: Contents of a forward-locked app’s mount point

Here, the res.zip holds app resources and the manifest file and is world readable, while the pkg.apk file that holds the full APK is only readable by the system and the app’s dedicated user (u0_a96). The actual app contain-ers are stored in /data/app-asec/ in files with the .asec extension.

Encrypted App Containers

Encrypted app containers are referred to as Android Secure External Caches, or ASEC containers. ASEC container management (creating, deleting, mount-ing, and unmounting) is implemented in the system volume daemon (vold), and the MountService provides an interface to its functionality to framework services. We can also use the vdc command-line utility to interact with vold in order to manage forward-locked apps from Android’s shell (see Listing 3-25).

# vdc asec listu vdc asec list

111 0 com.example.app-1 111 0 org.foo.app-1

200 0 asec operation succeeded

# vdc asec path com.example.app-1v vdc asec path com.example.app-1 211 0 /mnt/asec/com.example.app-1

# vdc asec unmount org.example.app-1w 200 0 asec operation succeeded

# vdc asec mount com.example.app-1 000102030405060708090a0b0c0d0e0f 1000x com.example.app-1 000102030405060708090a0b0c0d0e0f 1000 200 0 asec operation succeeded

Listing 3-25: Issuing ASEC management commands with vdc

Here, the asec list command u lists the namespace IDs of mounted ASEC containers. Namespace IDs are based on the package name and have the same format as APK filenames for non-forward-locked applications. All other commands take a namespace ID as a parameter.

The asec path command v shows the mount point of the specified ASEC container, while the asec unmount command unmounts it w. In addition to a namespace ID, asec mount x requires that you specify the encryption key and the mount point’s owner UID (1000 is system).

The ASEC container encryption algorithm and the key length are unchanged from the original Android 2.2 apps-to-SD implementation: Twofish with a 128-bit key stored in /data/misc/systemkeys/, as shown in Listing 3-26.

# ls -l /data/misc/systemkeys

-rw--- system system 16 AppsOnSD.sks

-rw--- system system 16 AppsOnSD.sks

Dans le document Android Security Internals (Page 99-109)