diff --git a/java/ql/src/experimental/Security/CWE/CWE-312/CleartextStorageSharedPrefs.java b/java/ql/src/experimental/Security/CWE/CWE-312/CleartextStorageSharedPrefs.java new file mode 100644 index 000000000000..f4fbeaa230b4 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-312/CleartextStorageSharedPrefs.java @@ -0,0 +1,39 @@ +public void testSetSharedPrefs(Context context, String name, String password) +{ + { + // BAD - save sensitive information in cleartext + SharedPreferences sharedPrefs = context.getSharedPreferences("user_prefs", Context.MODE_PRIVATE); + Editor editor = sharedPrefs.edit(); + editor.putString("name", name); + editor.putString("password", password); + editor.commit(); + } + + { + // GOOD - save sensitive information in encrypted format + SharedPreferences sharedPrefs = context.getSharedPreferences("user_prefs", Context.MODE_PRIVATE); + Editor editor = sharedPrefs.edit(); + editor.putString("name", encrypt(name)); + editor.putString("password", encrypt(password)); + editor.commit(); + } + + { + // GOOD - save sensitive information using the built-in `EncryptedSharedPreferences` class in androidx. + MasterKey masterKey = new MasterKey.Builder(context, MasterKey.DEFAULT_MASTER_KEY_ALIAS) + .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) + .build(); + + SharedPreferences sharedPreferences = EncryptedSharedPreferences.create( + context, + "secret_shared_prefs", + masterKey, + EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, + EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM); + + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putString("name", name); + editor.putString("password", password); + editor.commit(); + } +} diff --git a/java/ql/src/experimental/Security/CWE/CWE-312/CleartextStorageSharedPrefs.qhelp b/java/ql/src/experimental/Security/CWE/CWE-312/CleartextStorageSharedPrefs.qhelp new file mode 100644 index 000000000000..90f87f30b91d --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-312/CleartextStorageSharedPrefs.qhelp @@ -0,0 +1,40 @@ + + + +

+ SharedPreferences is an Android API that stores application preferences using simple sets of data values. Almost every Android application uses this API. It allows to easily save, alter, and retrieve the values stored in the user's profile. However, sensitive information should not be saved in cleartext. Otherwise it can be accessed by any process or user on rooted devices, or can be disclosed through chained vulnerabilities e.g. unexpected access to its private storage through exposed components. +

+
+ + +

+ Use the EncryptedSharedPreferences API or other encryption algorithms for storing sensitive information. +

+
+ + +

+ In the first example, sensitive user information is stored in cleartext. +

+ +

+ In the second and third examples, the code encrypts sensitive information before saving it to the device. +

+ +
+ + +
  • + CWE: + CWE-312: Cleartext Storage of Sensitive Information +
  • +
  • + Android Developers: + Work with data more securely +
  • +
  • + ProAndroidDev: + Encrypted Preferences in Android +
  • +
    +
    diff --git a/java/ql/src/experimental/Security/CWE/CWE-312/CleartextStorageSharedPrefs.ql b/java/ql/src/experimental/Security/CWE/CWE-312/CleartextStorageSharedPrefs.ql new file mode 100644 index 000000000000..fcfe3f826516 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-312/CleartextStorageSharedPrefs.ql @@ -0,0 +1,163 @@ +/** + * @name Cleartext storage of sensitive information using `SharedPreferences` on Android + * @description Cleartext Storage of Sensitive Information using SharedPreferences on Android allows access for users with root privileges or unexpected exposure from chained vulnerabilities. + * @kind problem + * @id java/android/cleartext-storage-shared-prefs + * @tags security + * external/cwe/cwe-312 + */ + +import java +import semmle.code.java.dataflow.DataFlow4 +import semmle.code.java.dataflow.DataFlow5 +import semmle.code.java.dataflow.TaintTracking +import semmle.code.java.frameworks.android.Intent +import semmle.code.java.frameworks.android.SharedPreferences +import semmle.code.java.security.SensitiveActions + +/** Holds if the method call is a setter method of `SharedPreferences`. */ +private predicate sharedPreferencesInput(DataFlow::Node sharedPrefs, Expr input) { + exists(MethodAccess m | + m.getMethod() instanceof PutSharedPreferenceMethod and + input = m.getArgument(1) and + not exists(EncryptedValueFlowConfig conf | conf.hasFlow(_, DataFlow::exprNode(input))) and + sharedPrefs.asExpr() = m.getQualifier() + ) +} + +/** Holds if the method call is the store method of `SharedPreferences`. */ +private predicate sharedPreferencesStore(DataFlow::Node sharedPrefs, Expr store) { + exists(MethodAccess m | + m.getMethod() instanceof StoreSharedPreferenceMethod and + store = m and + sharedPrefs.asExpr() = m.getQualifier() + ) +} + +/** Flow from `SharedPreferences` to either a setter or a store method. */ +class SharedPreferencesFlowConfig extends DataFlow::Configuration { + SharedPreferencesFlowConfig() { + this = "CleartextStorageSharedPrefs::SharedPreferencesFlowConfig" + } + + override predicate isSource(DataFlow::Node src) { + src.asExpr() instanceof SharedPreferencesEditorMethodAccess + } + + override predicate isSink(DataFlow::Node sink) { + sharedPreferencesInput(sink, _) or + sharedPreferencesStore(sink, _) + } +} + +/** + * Method call of encrypting sensitive information. + * As there are various implementations of encryption (reversible and non-reversible) from both JDK and third parties, this class simply checks method name to take a best guess to reduce false positives. + */ +class EncryptedSensitiveMethodAccess extends MethodAccess { + EncryptedSensitiveMethodAccess() { + this.getMethod().getName().toLowerCase().matches(["%encrypt%", "%hash%"]) + } +} + +/** Flow configuration of encrypting sensitive information. */ +class EncryptedValueFlowConfig extends DataFlow5::Configuration { + EncryptedValueFlowConfig() { this = "CleartextStorageSharedPrefs::EncryptedValueFlowConfig" } + + override predicate isSource(DataFlow5::Node src) { + exists(EncryptedSensitiveMethodAccess ema | src.asExpr() = ema) + } + + override predicate isSink(DataFlow5::Node sink) { + exists(MethodAccess ma | + ma.getMethod() instanceof PutSharedPreferenceMethod and + sink.asExpr() = ma.getArgument(1) + ) + } +} + +/** Flow from the create method of `androidx.security.crypto.EncryptedSharedPreferences` to its instance. */ +private class EncryptedSharedPrefFlowConfig extends DataFlow4::Configuration { + EncryptedSharedPrefFlowConfig() { + this = "CleartextStorageSharedPrefs::EncryptedSharedPrefFlowConfig" + } + + override predicate isSource(DataFlow4::Node src) { + src.asExpr().(MethodAccess).getMethod() instanceof CreateEncryptedSharedPreferencesMethod + } + + override predicate isSink(DataFlow4::Node sink) { + sink.asExpr().getType() instanceof SharedPreferences + } +} + +/** The call to get a `SharedPreferences.Editor` object, which can set shared preferences or be stored to device. */ +class SharedPreferencesEditorMethodAccess extends MethodAccess { + SharedPreferencesEditorMethodAccess() { + this.getMethod() instanceof GetSharedPreferencesEditorMethod and + not exists( + EncryptedSharedPrefFlowConfig config // not exists `SharedPreferences sharedPreferences = EncryptedSharedPreferences.create(...)` + | + config.hasFlow(_, DataFlow::exprNode(this.getQualifier())) + ) + } + + /** Gets an input, for example `password` in `editor.putString("password", password);`. */ + Expr getAnInput() { + exists(SharedPreferencesFlowConfig conf, DataFlow::Node n | + sharedPreferencesInput(n, result) and + conf.hasFlow(DataFlow::exprNode(this), n) + ) + } + + /** Gets a store, for example `editor.commit();`. */ + Expr getAStore() { + exists(SharedPreferencesFlowConfig conf, DataFlow::Node n | + sharedPreferencesStore(n, result) and + conf.hasFlow(DataFlow::exprNode(this), n) + ) + } +} + +/** + * Flow from sensitive expressions to shared preferences. + * Note it can be merged into `SensitiveSourceFlowConfig` of `Security/CWE/CWE-312/SensitiveStorage.qll` when this query is promoted from the experimental directory. + */ +private class SensitiveSharedPrefsFlowConfig extends TaintTracking::Configuration { + SensitiveSharedPrefsFlowConfig() { + this = "CleartextStorageSharedPrefs::SensitiveSharedPrefsFlowConfig" + } + + override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof SensitiveExpr } + + override predicate isSink(DataFlow::Node sink) { + exists(MethodAccess m | + m.getMethod() instanceof PutSharedPreferenceMethod and + sink.asExpr() = m.getArgument(1) + ) + } +} + +/** Class for shared preferences that may contain 'sensitive' information */ +class SensitiveSharedPrefsSource extends Expr { + SensitiveSharedPrefsSource() { + // SensitiveExpr is abstract, this lets us inherit from it without + // being a technical subclass + this instanceof SensitiveExpr + } + + /** Holds if this source flows to the `sink`. */ + predicate flowsTo(Expr sink) { + exists(SensitiveSharedPrefsFlowConfig conf | + conf.hasFlow(DataFlow::exprNode(this), DataFlow::exprNode(sink)) + ) + } +} + +from SensitiveSharedPrefsSource data, SharedPreferencesEditorMethodAccess s, Expr input, Expr store +where + input = s.getAnInput() and + store = s.getAStore() and + data.flowsTo(input) +select store, "'SharedPreferences' class $@ containing $@ is stored here. Data was added $@.", s, + s.toString(), data, "sensitive data", input, "here" diff --git a/java/ql/src/semmle/code/java/frameworks/android/SharedPreferences.qll b/java/ql/src/semmle/code/java/frameworks/android/SharedPreferences.qll new file mode 100644 index 000000000000..a3298fd70d87 --- /dev/null +++ b/java/ql/src/semmle/code/java/frameworks/android/SharedPreferences.qll @@ -0,0 +1,57 @@ +/** Provides classes related to `android.content.SharedPreferences`. */ + +import java + +/** The interface `android.content.SharedPreferences`. */ +class SharedPreferences extends Interface { + SharedPreferences() { this.hasQualifiedName("android.content", "SharedPreferences") } +} + +/** The class `androidx.security.crypto.EncryptedSharedPreferences`, which implements `SharedPreferences` with encryption support. */ +class EncryptedSharedPreferences extends Class { + EncryptedSharedPreferences() { + this.hasQualifiedName("androidx.security.crypto", "EncryptedSharedPreferences") + } +} + +/** The `create` method of `androidx.security.crypto.EncryptedSharedPreferences`. */ +class CreateEncryptedSharedPreferencesMethod extends Method { + CreateEncryptedSharedPreferencesMethod() { + this.getDeclaringType() instanceof EncryptedSharedPreferences and + this.hasName("create") + } +} + +/** The method `android.content.SharedPreferences::edit`, which returns an `android.content.SharedPreferences.Editor`. */ +class GetSharedPreferencesEditorMethod extends Method { + GetSharedPreferencesEditorMethod() { + this.getDeclaringType() instanceof SharedPreferences and + this.hasName("edit") and + this.getReturnType() instanceof SharedPreferencesEditor + } +} + +/** The interface `android.content.SharedPreferences.Editor`. */ +class SharedPreferencesEditor extends Interface { + SharedPreferencesEditor() { this.hasQualifiedName("android.content", "SharedPreferences$Editor") } +} + +/** + * A method that updates a key-value pair in a + * `android.content.SharedPreferences` through a `SharedPreferences.Editor`. The + * value is not written until a `StorePreferenceMethod` is called. + */ +class PutSharedPreferenceMethod extends Method { + PutSharedPreferenceMethod() { + this.getDeclaringType() instanceof SharedPreferencesEditor and + this.getName().matches("put%") + } +} + +/** A method on `SharedPreferences.Editor` that writes the pending changes to the underlying `android.content.SharedPreferences`. */ +class StoreSharedPreferenceMethod extends Method { + StoreSharedPreferenceMethod() { + this.getDeclaringType() instanceof SharedPreferencesEditor and + this.hasName(["commit", "apply"]) + } +} diff --git a/java/ql/test/experimental/query-tests/security/CWE-312/CleartextStorageSharedPrefs.expected b/java/ql/test/experimental/query-tests/security/CWE-312/CleartextStorageSharedPrefs.expected new file mode 100644 index 000000000000..ab1db3fe099d --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-312/CleartextStorageSharedPrefs.expected @@ -0,0 +1 @@ +| CleartextStorageSharedPrefs.java:19:3:19:17 | commit(...) | 'SharedPreferences' class $@ containing $@ is stored here. Data was added $@. | CleartextStorageSharedPrefs.java:16:19:16:36 | edit(...) | edit(...) | CleartextStorageSharedPrefs.java:18:32:18:39 | password | sensitive data | CleartextStorageSharedPrefs.java:18:32:18:39 | password | here | diff --git a/java/ql/test/experimental/query-tests/security/CWE-312/CleartextStorageSharedPrefs.java b/java/ql/test/experimental/query-tests/security/CWE-312/CleartextStorageSharedPrefs.java new file mode 100644 index 000000000000..51a92314c67f --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-312/CleartextStorageSharedPrefs.java @@ -0,0 +1,109 @@ +import android.app.Activity; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import androidx.security.crypto.MasterKey; +import androidx.security.crypto.EncryptedSharedPreferences; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.security.MessageDigest; + +/* Android activity that tests saving sensitive information in `SharedPreferences` */ +public class CleartextStorageSharedPrefs extends Activity { + // BAD - save sensitive information in cleartext + public void testSetSharedPrefs1(Context context, String name, String password) { + SharedPreferences sharedPrefs = context.getSharedPreferences("user_prefs", Context.MODE_PRIVATE); + Editor editor = sharedPrefs.edit(); + editor.putString("name", name); + editor.putString("password", password); + editor.commit(); + } + + // GOOD - save sensitive information in encrypted format + public void testSetSharedPrefs2(Context context, String name, String password) { + SharedPreferences sharedPrefs = context.getSharedPreferences("user_prefs", Context.MODE_PRIVATE); + Editor editor = sharedPrefs.edit(); + editor.putString("name", encrypt(name)); + editor.putString("password", encrypt(password)); + editor.commit(); + } + + private static String encrypt(String cleartext) { + // Use an encryption or hashing algorithm in real world. The demo below just returns its hash. + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] hash = digest.digest(cleartext.getBytes(StandardCharsets.UTF_8)); + String encoded = Base64.getEncoder().encodeToString(hash); + return encoded; + } + + // GOOD - save sensitive information in encrypted format using separate variables + public void testSetSharedPrefs3(Context context, String name, String password) { + String encUsername = encrypt(name); + String encPassword = encrypt(password); + SharedPreferences sharedPrefs = context.getSharedPreferences("user_prefs", Context.MODE_PRIVATE); + Editor editor = sharedPrefs.edit(); + editor.putString("name", encUsername); + editor.putString("password", encPassword); + editor.commit(); + } + + + // GOOD - save sensitive information using the built-in `EncryptedSharedPreferences` class in androidx + public void testSetSharedPrefs4(Context context, String name, String password) { + MasterKey masterKey = new MasterKey.Builder(context, MasterKey.DEFAULT_MASTER_KEY_ALIAS) + .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) + .build(); + + SharedPreferences sharedPreferences = EncryptedSharedPreferences.create( + context, + "secret_shared_prefs", + masterKey, + EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, + EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM); + + // Use the shared preferences and editor as you normally would + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putString("name", name); + editor.putString("password", password); + editor.commit(); + } + + // GOOD - save sensitive information using the built-in `EncryptedSharedPreferences` class in androidx + public void testSetSharedPrefs5(Context context, String name, String password) { + MasterKey masterKey = new MasterKey.Builder(context, MasterKey.DEFAULT_MASTER_KEY_ALIAS) + .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) + .build(); + + SharedPreferences.Editor editor = EncryptedSharedPreferences.create( + context, + "secret_shared_prefs", + masterKey, + EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, + EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM) + .edit(); + + // Use the shared preferences and editor as you normally would + editor.putString("name", name); + editor.putString("password", password); + editor.commit(); + } + + // GOOD - save sensitive information using the built-in `EncryptedSharedPreferences` class in androidx + public void testSetSharedPrefs6(Context context, String name, String password) { + MasterKey masterKey = new MasterKey.Builder(context, MasterKey.DEFAULT_MASTER_KEY_ALIAS) + .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) + .build(); + + SharedPreferences.Editor editor = EncryptedSharedPreferences.create( + context, + "secret_shared_prefs", + masterKey, + EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, + EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM) + .edit() + .putString("name", name) // Use the shared preferences and editor as you normally would + .putString("password", password); + + editor.commit(); + } +} diff --git a/java/ql/test/experimental/query-tests/security/CWE-312/CleartextStorageSharedPrefs.qlref b/java/ql/test/experimental/query-tests/security/CWE-312/CleartextStorageSharedPrefs.qlref new file mode 100644 index 000000000000..9319c78be055 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-312/CleartextStorageSharedPrefs.qlref @@ -0,0 +1 @@ +experimental/Security/CWE/CWE-312/CleartextStorageSharedPrefs.ql diff --git a/java/ql/test/experimental/query-tests/security/CWE-312/options b/java/ql/test/experimental/query-tests/security/CWE-312/options new file mode 100644 index 000000000000..43e25f608b67 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-312/options @@ -0,0 +1 @@ +// semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/google-android-9.0.0 diff --git a/java/ql/test/stubs/google-android-9.0.0/android/content/Context.java b/java/ql/test/stubs/google-android-9.0.0/android/content/Context.java index e1ce2140a063..6507cbd371d3 100644 --- a/java/ql/test/stubs/google-android-9.0.0/android/content/Context.java +++ b/java/ql/test/stubs/google-android-9.0.0/android/content/Context.java @@ -26,6 +26,84 @@ * broadcasting and receiving intents, etc. */ public abstract class Context { + /** + * File creation mode: the default mode, where the created file can only + * be accessed by the calling application (or all applications sharing the + * same user ID). + * @see #MODE_WORLD_READABLE + * @see #MODE_WORLD_WRITEABLE + */ + public static final int MODE_PRIVATE = 0x0000; + + /** + * @deprecated Creating world-readable files is very dangerous, and likely + * to cause security holes in applications. It is strongly discouraged; + * instead, applications should use more formal mechanism for interactions + * such as {@link ContentProvider}, {@link BroadcastReceiver}, and + * {@link android.app.Service}. There are no guarantees that this + * access mode will remain on a file, such as when it goes through a + * backup and restore. + * File creation mode: allow all other applications to have read access + * to the created file. + * @see #MODE_PRIVATE + * @see #MODE_WORLD_WRITEABLE + */ + @Deprecated + public static final int MODE_WORLD_READABLE = 0x0001; + + /** + * @deprecated Creating world-writable files is very dangerous, and likely + * to cause security holes in applications. It is strongly discouraged; + * instead, applications should use more formal mechanism for interactions + * such as {@link ContentProvider}, {@link BroadcastReceiver}, and + * {@link android.app.Service}. There are no guarantees that this + * access mode will remain on a file, such as when it goes through a + * backup and restore. + * File creation mode: allow all other applications to have write access + * to the created file. + * @see #MODE_PRIVATE + * @see #MODE_WORLD_READABLE + */ + @Deprecated + public static final int MODE_WORLD_WRITEABLE = 0x0002; + + /** + * File creation mode: for use with {@link #openFileOutput}, if the file + * already exists then write data to the end of the existing file + * instead of erasing it. + * @see #openFileOutput + */ + public static final int MODE_APPEND = 0x8000; + + /** + * SharedPreference loading flag: when set, the file on disk will + * be checked for modification even if the shared preferences + * instance is already loaded in this process. This behavior is + * sometimes desired in cases where the application has multiple + * processes, all writing to the same SharedPreferences file. + * Generally there are better forms of communication between + * processes, though. + * + *

    This was the legacy (but undocumented) behavior in and + * before Gingerbread (Android 2.3) and this flag is implied when + * targetting such releases. For applications targetting SDK + * versions greater than Android 2.3, this flag must be + * explicitly set if desired. + * + * @see #getSharedPreferences + */ + public static final int MODE_MULTI_PROCESS = 0x0004; + + /** + * Database open flag: when set, the database is opened with write-ahead + * logging enabled by default. + * + * @see #openOrCreateDatabase(String, int, CursorFactory) + * @see #openOrCreateDatabase(String, int, CursorFactory, DatabaseErrorHandler) + * @see SQLiteDatabase#enableWriteAheadLogging + */ + public static final int MODE_ENABLE_WRITE_AHEAD_LOGGING = 0x0008; + /** * Return the context of the single, global Application object of the current * process. This generally should only be used if you need a Context whose @@ -76,19 +154,42 @@ public abstract class Context { public abstract File getFileStreamPath(String name); /** - * Returns the absolute path on the filesystem where a file created with - * {@link #getSharedPreferences(String, int)} is stored. - *

    - * The returned path may change over time if the calling app is moved to an - * adopted storage device, so only relative paths should be persisted. + * {@hide} + * Return the full path to the shared prefs file for the given prefs group name. * - * @param name The name of the shared preferences for which you would like to - * get a path. - * @return An absolute path to the given file. - * @see #getSharedPreferences(String, int) - * @removed + *

    Note: this is not generally useful for applications, since they should + * not be directly accessing the file system. + */ + public abstract File getSharedPrefsFile(String name); + + /** + * Retrieve and hold the contents of the preferences file 'name', returning + * a SharedPreferences through which you can retrieve and modify its + * values. Only one instance of the SharedPreferences object is returned + * to any callers for the same name, meaning they will see each other's + * edits as soon as they are made. + * + * @param name Desired preferences file. If a preferences file by this name + * does not exist, it will be created when you retrieve an + * editor (SharedPreferences.edit()) and then commit changes (Editor.commit()). + * @param mode Operating mode. Use 0 or {@link #MODE_PRIVATE} for the + * default operation, {@link #MODE_WORLD_READABLE} + * and {@link #MODE_WORLD_WRITEABLE} to control permissions. The bit + * {@link #MODE_MULTI_PROCESS} can also be used if multiple processes + * are mutating the same SharedPreferences file. {@link #MODE_MULTI_PROCESS} + * is always on in apps targetting Gingerbread (Android 2.3) and below, and + * off by default in later versions. + * + * @return Returns the single SharedPreferences instance that can be used + * to retrieve and modify the preference values. + * + * @see #MODE_PRIVATE + * @see #MODE_WORLD_READABLE + * @see #MODE_WORLD_WRITEABLE + * @see #MODE_MULTI_PROCESS */ - public abstract File getSharedPreferencesPath(String name); + public abstract SharedPreferences getSharedPreferences(String name, + int mode); /** * Returns the absolute path to the directory on the filesystem where all diff --git a/java/ql/test/stubs/google-android-9.0.0/android/content/SharedPreferences.java b/java/ql/test/stubs/google-android-9.0.0/android/content/SharedPreferences.java new file mode 100644 index 000000000000..faac2f1d41e7 --- /dev/null +++ b/java/ql/test/stubs/google-android-9.0.0/android/content/SharedPreferences.java @@ -0,0 +1,362 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.content; + +import java.util.Map; +import java.util.Set; +/** + * Interface for accessing and modifying preference data returned by {@link + * Context#getSharedPreferences}. For any particular set of preferences, + * there is a single instance of this class that all clients share. + * Modifications to the preferences must go through an {@link Editor} object + * to ensure the preference values remain in a consistent state and control + * when they are committed to storage. Objects that are returned from the + * various get methods must be treated as immutable by the application. + * + *

    Note: currently this class does not support use across multiple + * processes. This will be added later. + * + *

    + *

    Developer Guides

    + *

    For more information about using SharedPreferences, read the + * Data Storage + * developer guide.

    + * + * @see Context#getSharedPreferences + */ +public interface SharedPreferences { + /** + * Interface definition for a callback to be invoked when a shared + * preference is changed. + */ + public interface OnSharedPreferenceChangeListener { + /** + * Called when a shared preference is changed, added, or removed. This + * may be called even if a preference is set to its existing value. + * + *

    This callback will be run on your main thread. + * + * @param sharedPreferences The {@link SharedPreferences} that received + * the change. + * @param key The key of the preference that was changed, added, or + * removed. + */ + void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key); + } + + /** + * Interface used for modifying values in a {@link SharedPreferences} + * object. All changes you make in an editor are batched, and not copied + * back to the original {@link SharedPreferences} until you call {@link #commit} + * or {@link #apply} + */ + public interface Editor { + /** + * Set a String value in the preferences editor, to be written back once + * {@link #commit} or {@link #apply} are called. + * + * @param key The name of the preference to modify. + * @param value The new value for the preference. Supplying {@code null} + * as the value is equivalent to calling {@link #remove(String)} with + * this key. + * + * @return Returns a reference to the same Editor object, so you can + * chain put calls together. + */ + Editor putString(String key, String value); + + /** + * Set a set of String values in the preferences editor, to be written + * back once {@link #commit} is called. + * + * @param key The name of the preference to modify. + * @param values The set of new values for the preference. Passing {@code null} + * for this argument is equivalent to calling {@link #remove(String)} with + * this key. + * @return Returns a reference to the same Editor object, so you can + * chain put calls together. + */ + Editor putStringSet(String key, Set values); + + /** + * Set an int value in the preferences editor, to be written back once + * {@link #commit} or {@link #apply} are called. + * + * @param key The name of the preference to modify. + * @param value The new value for the preference. + * + * @return Returns a reference to the same Editor object, so you can + * chain put calls together. + */ + Editor putInt(String key, int value); + + /** + * Set a long value in the preferences editor, to be written back once + * {@link #commit} or {@link #apply} are called. + * + * @param key The name of the preference to modify. + * @param value The new value for the preference. + * + * @return Returns a reference to the same Editor object, so you can + * chain put calls together. + */ + Editor putLong(String key, long value); + + /** + * Set a float value in the preferences editor, to be written back once + * {@link #commit} or {@link #apply} are called. + * + * @param key The name of the preference to modify. + * @param value The new value for the preference. + * + * @return Returns a reference to the same Editor object, so you can + * chain put calls together. + */ + Editor putFloat(String key, float value); + + /** + * Set a boolean value in the preferences editor, to be written back + * once {@link #commit} or {@link #apply} are called. + * + * @param key The name of the preference to modify. + * @param value The new value for the preference. + * + * @return Returns a reference to the same Editor object, so you can + * chain put calls together. + */ + Editor putBoolean(String key, boolean value); + /** + * Mark in the editor that a preference value should be removed, which + * will be done in the actual preferences once {@link #commit} is + * called. + * + *

    Note that when committing back to the preferences, all removals + * are done first, regardless of whether you called remove before + * or after put methods on this editor. + * + * @param key The name of the preference to remove. + * + * @return Returns a reference to the same Editor object, so you can + * chain put calls together. + */ + Editor remove(String key); + /** + * Mark in the editor to remove all values from the + * preferences. Once commit is called, the only remaining preferences + * will be any that you have defined in this editor. + * + *

    Note that when committing back to the preferences, the clear + * is done first, regardless of whether you called clear before + * or after put methods on this editor. + * + * @return Returns a reference to the same Editor object, so you can + * chain put calls together. + */ + Editor clear(); + /** + * Commit your preferences changes back from this Editor to the + * {@link SharedPreferences} object it is editing. This atomically + * performs the requested modifications, replacing whatever is currently + * in the SharedPreferences. + * + *

    Note that when two editors are modifying preferences at the same + * time, the last one to call commit wins. + * + *

    If you don't care about the return value and you're + * using this from your application's main thread, consider + * using {@link #apply} instead. + * + * @return Returns true if the new values were successfully written + * to persistent storage. + */ + boolean commit(); + /** + * Commit your preferences changes back from this Editor to the + * {@link SharedPreferences} object it is editing. This atomically + * performs the requested modifications, replacing whatever is currently + * in the SharedPreferences. + * + *

    Note that when two editors are modifying preferences at the same + * time, the last one to call apply wins. + * + *

    Unlike {@link #commit}, which writes its preferences out + * to persistent storage synchronously, {@link #apply} + * commits its changes to the in-memory + * {@link SharedPreferences} immediately but starts an + * asynchronous commit to disk and you won't be notified of + * any failures. If another editor on this + * {@link SharedPreferences} does a regular {@link #commit} + * while a {@link #apply} is still outstanding, the + * {@link #commit} will block until all async commits are + * completed as well as the commit itself. + * + *

    As {@link SharedPreferences} instances are singletons within + * a process, it's safe to replace any instance of {@link #commit} with + * {@link #apply} if you were already ignoring the return value. + * + *

    You don't need to worry about Android component + * lifecycles and their interaction with apply() + * writing to disk. The framework makes sure in-flight disk + * writes from apply() complete before switching + * states. + * + *

    The SharedPreferences.Editor interface + * isn't expected to be implemented directly. However, if you + * previously did implement it and are now getting errors + * about missing apply(), you can simply call + * {@link #commit} from apply(). + */ + void apply(); + } + /** + * Retrieve all values from the preferences. + * + *

    Note that you must not modify the collection returned + * by this method, or alter any of its contents. The consistency of your + * stored data is not guaranteed if you do. + * + * @return Returns a map containing a list of pairs key/value representing + * the preferences. + * + * @throws NullPointerException + */ + Map getAll(); + /** + * Retrieve a String value from the preferences. + * + * @param key The name of the preference to retrieve. + * @param defValue Value to return if this preference does not exist. + * + * @return Returns the preference value if it exists, or defValue. Throws + * ClassCastException if there is a preference with this name that is not + * a String. + * + * @throws ClassCastException + */ + String getString(String key, String defValue); + + /** + * Retrieve a set of String values from the preferences. + * + *

    Note that you must not modify the set instance returned + * by this call. The consistency of the stored data is not guaranteed + * if you do, nor is your ability to modify the instance at all. + * + * @param key The name of the preference to retrieve. + * @param defValues Values to return if this preference does not exist. + * + * @return Returns the preference values if they exist, or defValues. + * Throws ClassCastException if there is a preference with this name + * that is not a Set. + * + * @throws ClassCastException + */ + Set getStringSet(String key, Set defValues); + + /** + * Retrieve an int value from the preferences. + * + * @param key The name of the preference to retrieve. + * @param defValue Value to return if this preference does not exist. + * + * @return Returns the preference value if it exists, or defValue. Throws + * ClassCastException if there is a preference with this name that is not + * an int. + * + * @throws ClassCastException + */ + int getInt(String key, int defValue); + + /** + * Retrieve a long value from the preferences. + * + * @param key The name of the preference to retrieve. + * @param defValue Value to return if this preference does not exist. + * + * @return Returns the preference value if it exists, or defValue. Throws + * ClassCastException if there is a preference with this name that is not + * a long. + * + * @throws ClassCastException + */ + long getLong(String key, long defValue); + + /** + * Retrieve a float value from the preferences. + * + * @param key The name of the preference to retrieve. + * @param defValue Value to return if this preference does not exist. + * + * @return Returns the preference value if it exists, or defValue. Throws + * ClassCastException if there is a preference with this name that is not + * a float. + * + * @throws ClassCastException + */ + float getFloat(String key, float defValue); + + /** + * Retrieve a boolean value from the preferences. + * + * @param key The name of the preference to retrieve. + * @param defValue Value to return if this preference does not exist. + * + * @return Returns the preference value if it exists, or defValue. Throws + * ClassCastException if there is a preference with this name that is not + * a boolean. + * + * @throws ClassCastException + */ + boolean getBoolean(String key, boolean defValue); + /** + * Checks whether the preferences contains a preference. + * + * @param key The name of the preference to check. + * @return Returns true if the preference exists in the preferences, + * otherwise false. + */ + boolean contains(String key); + + /** + * Create a new Editor for these preferences, through which you can make + * modifications to the data in the preferences and atomically commit those + * changes back to the SharedPreferences object. + * + *

    Note that you must call {@link Editor#commit} to have any + * changes you perform in the Editor actually show up in the + * SharedPreferences. + * + * @return Returns a new instance of the {@link Editor} interface, allowing + * you to modify the values in this SharedPreferences object. + */ + Editor edit(); + + /** + * Registers a callback to be invoked when a change happens to a preference. + * + * @param listener The callback that will run. + * @see #unregisterOnSharedPreferenceChangeListener + */ + void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener); + + /** + * Unregisters a previous callback. + * + * @param listener The callback that should be unregistered. + * @see #registerOnSharedPreferenceChangeListener + */ + void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener); +} diff --git a/java/ql/test/stubs/google-android-9.0.0/androidx/security/crypto/EncryptedSharedPreferences.java b/java/ql/test/stubs/google-android-9.0.0/androidx/security/crypto/EncryptedSharedPreferences.java new file mode 100644 index 000000000000..db918d448f21 --- /dev/null +++ b/java/ql/test/stubs/google-android-9.0.0/androidx/security/crypto/EncryptedSharedPreferences.java @@ -0,0 +1,171 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.security.crypto; + +import android.content.Context; +import android.content.SharedPreferences; +import java.io.IOException; +import java.security.GeneralSecurityException; + +import java.util.Map; +import java.util.Set; + +/** + * An implementation of {@link SharedPreferences} that encrypts keys and values. + * + *

    + *  String masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC);
    + *
    + *  SharedPreferences sharedPreferences = EncryptedSharedPreferences.create(
    + *      "secret_shared_prefs",
    + *      masterKeyAlias,
    + *      context,
    + *      EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
    + *      EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
    + *  );
    + *
    + *  // use the shared preferences and editor as you normally would
    + *  SharedPreferences.Editor editor = sharedPreferences.edit();
    + * 
    + */ +public final class EncryptedSharedPreferences implements SharedPreferences { + /** + * Opens an instance of encrypted SharedPreferences + * + * @param fileName The name of the file to open; can not contain path + * separators. + * @param masterKey The master key to use. + * @param prefKeyEncryptionScheme The scheme to use for encrypting keys. + * @param prefValueEncryptionScheme The scheme to use for encrypting values. + * @return The SharedPreferences instance that encrypts all data. + * @throws GeneralSecurityException when a bad master key or keyset has been attempted + * @throws IOException when fileName can not be used + */ + public static SharedPreferences create(Context context, + String fileName, + MasterKey masterKey, + PrefKeyEncryptionScheme prefKeyEncryptionScheme, + PrefValueEncryptionScheme prefValueEncryptionScheme) + throws GeneralSecurityException, IOException { + return null; + } + + /** + * Opens an instance of encrypted SharedPreferences + * + * @param fileName The name of the file to open; can not contain path + * separators. + * @param masterKeyAlias The alias of the master key to use. + * @param context The context to use to open the preferences file. + * @param prefKeyEncryptionScheme The scheme to use for encrypting keys. + * @param prefValueEncryptionScheme The scheme to use for encrypting values. + * @return The SharedPreferences instance that encrypts all data. + * @throws GeneralSecurityException when a bad master key or keyset has been attempted + * @throws IOException when fileName can not be used + * @deprecated Use {@link #create(Context, String, MasterKey, + * PrefKeyEncryptionScheme, PrefValueEncryptionScheme)} instead. + */ + @Deprecated + public static SharedPreferences create(String fileName, + String masterKeyAlias, + Context context, + PrefKeyEncryptionScheme prefKeyEncryptionScheme, + PrefValueEncryptionScheme prefValueEncryptionScheme) + throws GeneralSecurityException, IOException { + } + + /** + * The encryption scheme to encrypt keys. + */ + public enum PrefKeyEncryptionScheme { + /** + * Pref keys are encrypted deterministically with AES256-SIV-CMAC (RFC 5297). + * + * For more information please see the Tink documentation: + * + * AesSivKeyManager.aes256SivTemplate() + */ + AES256_SIV; + } + /** + * The encryption scheme to encrypt values. + */ + public enum PrefValueEncryptionScheme { + /** + * Pref values are encrypted with AES256-GCM. The associated data is the encrypted pref key. + * + * For more information please see the Tink documentation: + * + * AesGcmKeyManager.aes256GcmTemplate() + */ + AES256_GCM; + } + + // SharedPreferences methods + @Override + public Map getAll() { + return null; + } + + @Override + public String getString(String key, String defValue) { + return null; + } + + @Override + public Set getStringSet(String key, Set defValues) { + return null; + } + + @Override + public int getInt(String key, int defValue) { + return -1; + } + + @Override + public long getLong(String key, long defValue) { + return -1; + } + + @Override + public float getFloat(String key, float defValue) { + return -1; + } + + @Override + public boolean getBoolean(String key, boolean defValue) { + return false; + } + + @Override + public boolean contains(String key) { + return false; + } + + @Override + public SharedPreferences.Editor edit() { + return null; + } + + @Override + public void registerOnSharedPreferenceChangeListener( + OnSharedPreferenceChangeListener listener) { + } + @Override + public void unregisterOnSharedPreferenceChangeListener( + OnSharedPreferenceChangeListener listener) { + } +} \ No newline at end of file diff --git a/java/ql/test/stubs/google-android-9.0.0/androidx/security/crypto/MasterKey.java b/java/ql/test/stubs/google-android-9.0.0/androidx/security/crypto/MasterKey.java new file mode 100644 index 000000000000..c8f696db300c --- /dev/null +++ b/java/ql/test/stubs/google-android-9.0.0/androidx/security/crypto/MasterKey.java @@ -0,0 +1,191 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.security.crypto; + +import android.content.Context; + +import java.io.IOException; +import java.security.GeneralSecurityException; + +/** + * Wrapper for a master key used in the library. + * + * On Android M (API 23) and above, this is class references a key that's stored in the + * Android Keystore. On Android L (API 21, 22), there isn't a master key. + */ +public final class MasterKey { + static final String KEYSTORE_PATH_URI = "android-keystore://"; + /** + * The default master key alias. + */ + public static final String DEFAULT_MASTER_KEY_ALIAS = "_androidx_security_master_key_"; + + /** + * The default and recommended size for the master key. + */ + public static final int DEFAULT_AES_GCM_MASTER_KEY_SIZE = 256; + + /** + * Algorithm/Cipher choices used for the master key. + */ + public enum KeyScheme { + AES256_GCM + } + + /** + * The default validity period for authentication in seconds. + */ + public static int getDefaultAuthenticationValidityDurationSeconds() { + return -1; + } + + /* package */ MasterKey(String keyAlias, Object keyGenParameterSpec) { + } + + /** + * Checks if this key is backed by the Android Keystore. + * + * @return {@code true} if the key is in Android Keystore, {@code false} otherwise. This + * method always returns false when called on Android Lollipop (API 21 and 22). + */ + public boolean isKeyStoreBacked() { + return false; + } + + /** + * Gets whether user authentication is required to use this key. + * + * This method always returns {@code false} on Android L (API 21 + 22). + */ + public boolean isUserAuthenticationRequired() { + return false; + } + + /** + * Gets the duration in seconds that the key is unlocked for following user authentication. + * + * The value returned for this method is only meaningful on Android M+ (API 23) when + * {@link #isUserAuthenticationRequired()} returns {@code true}. + * + * @return The duration the key is unlocked for in seconds. + */ + public int getUserAuthenticationValidityDurationSeconds() { + return -1; + } + + /** + * Gets whether the key is backed by strong box. + */ + public boolean isStrongBoxBacked() { + return false; + } + + /* package */ String getKeyAlias() { + return null; + } + + /** + * Builder for generating a {@link MasterKey}. + */ + public static final class Builder { + /** + * Creates a builder for a {@link MasterKey} using the default alias of + * {@link #DEFAULT_MASTER_KEY_ALIAS}. + * + * @param context The context to use with this master key. + */ + public Builder(Context context) { + } + + /** + * Creates a builder for a {@link MasterKey}. + * + * @param context The context to use with this master key. + */ + public Builder(Context context, String keyAlias) { + } + + /** + * Sets a {@link KeyScheme} to be used for the master key. + * This uses a default {@link KeyGenParameterSpec} associated with the provided + * {@code KeyScheme}. + * NOTE: Either this method OR {@link #setKeyGenParameterSpec} should be used to set + * the parameters to use for building the master key. Calling either function after + * the other will throw an {@link IllegalArgumentException}. + * + * @param keyScheme The KeyScheme to use. + * @return This builder. + */ + public Builder setKeyScheme(KeyScheme keyScheme) { + return null; + } + + /** + * When used with {@link #setKeyScheme(KeyScheme)}, sets that the built master key should + * require the user to authenticate before it's unlocked, probably using the + * androidx.biometric library. + * + * This method sets the validity duration of the key to + * {@link #getDefaultAuthenticationValidityDurationSeconds()}. + * + * @param authenticationRequired Whether user authentication should be required to use + * the key. + * @return This builder. + */ + public Builder setUserAuthenticationRequired(boolean authenticationRequired) { + return null; + } + + /** + * When used with {@link #setKeyScheme(KeyScheme)}, sets that the built master key should + * require the user to authenticate before it's unlocked, probably using the + * androidx.biometric library, and that the key should remain unlocked for the provided + * duration. + * + * @param authenticationRequired Whether user authentication should be + * required to use the key. + * @param userAuthenticationValidityDurationSeconds Duration in seconds that the key + * should remain unlocked following user + * authentication. + * @return This builder. + */ + public Builder setUserAuthenticationRequired(boolean authenticationRequired, + int userAuthenticationValidityDurationSeconds) { + return null; + } + + /** + * Sets whether or not to request this key is strong box backed. This setting is only + * applicable on {@link Build.VERSION_CODES#P} and above, and only on devices that + * support Strongbox. + * + * @param requestStrongBoxBacked Whether to request to use strongbox + * @return This builder. + */ + public Builder setRequestStrongBoxBacked(boolean requestStrongBoxBacked) { + return null; + } + + /** + * Builds a {@link MasterKey} from this builder. + * + * @return The master key. + */ + public MasterKey build() throws GeneralSecurityException, IOException { + return null; + } + } +} \ No newline at end of file