Statistics
| Branch: | Tag: | Revision:

trustedintents / trustedintents / src / info / guardianproject / trustedintents / TrustedIntents.java @ 0fc4c33f

History | View | Annotate | Download (9.57 KB)

1

    
2
package info.guardianproject.trustedintents;
3

    
4
import android.app.Activity;
5
import android.content.ActivityNotFoundException;
6
import android.content.ComponentName;
7
import android.content.Context;
8
import android.content.Intent;
9
import android.content.pm.ActivityInfo;
10
import android.content.pm.PackageInfo;
11
import android.content.pm.PackageManager;
12
import android.content.pm.PackageManager.NameNotFoundException;
13
import android.content.pm.ResolveInfo;
14
import android.content.pm.Signature;
15
import android.text.TextUtils;
16
import android.util.Log;
17

    
18
import java.lang.reflect.Constructor;
19
import java.security.cert.CertificateException;
20
import java.util.LinkedHashSet;
21

    
22
public class TrustedIntents {
23

    
24
    private static TrustedIntents instance;
25

    
26
    private static PackageManager pm;
27

    
28
    private final LinkedHashSet<ApkSignaturePin> pinList;
29

    
30
    private TrustedIntents(Context context) {
31
        pm = context.getPackageManager();
32
        this.pinList = new LinkedHashSet<ApkSignaturePin>();
33
    }
34

    
35
    public static TrustedIntents get(Context context) {
36
        if (instance == null)
37
            instance = new TrustedIntents(context);
38
        return instance;
39
    }
40

    
41
    /**
42
     * Check whether a resolved {@link Activity} is trusted.
43
     *
44
     * @param resolveInfo the one to check
45
     * @return whether the {@code Intent}'s receiver is trusted
46
     */
47
    public boolean isReceiverTrusted(ResolveInfo resolveInfo) {
48
        return isPackageNameTrusted(resolveInfo.activityInfo.packageName);
49
    }
50

    
51
    /**
52
     * Check whether a resolved {@link Activity} is trusted.
53
     *
54
     * @param activityInfo the one to check
55
     * @return whether the {@code Intent}'s receiver is trusted
56
     */
57
    public boolean isReceiverTrusted(ActivityInfo activityInfo) {
58
        return isPackageNameTrusted(activityInfo.packageName);
59
    }
60

    
61
    /**
62
     * Check an {@link Intent} is trusted based on the {@code packageName} set
63
     * by {@link Intent#setPackage(String)}
64
     *
65
     * @param intent the one to check
66
     * @return whether the {@code Intent}'s receiver is trusted
67
     */
68
    public boolean isReceiverTrusted(Intent intent) {
69
        if (!isIntentSane(intent))
70
            return false;
71
        String packageName = intent.getPackage();
72
        if (TextUtils.isEmpty(packageName)) {
73
            packageName = intent.getComponent().getPackageName();
74
        }
75
        return isPackageNameTrusted(packageName);
76
    }
77

    
78
    /**
79
     * Check whether a {@code packageName} is trusted.
80
     *
81
     * @param packageName the one to check
82
     * @return whether the {@code packageName} is trusted
83
     */
84
    public boolean isPackageNameTrusted(String packageName) {
85
        try {
86
            checkTrustedSigner(packageName);
87
        } catch (NameNotFoundException e) {
88
            e.printStackTrace();
89
            return false;
90
        } catch (CertificateException e) {
91
            return false;
92
        }
93
        return true;
94
    }
95

    
96
    /**
97
     * Returns an {@link Intent} if the sending app is signed by one of
98
     * the trusted signing keys as set in {@link #addTrustedSigner(Class)}.
99
     *
100
     * @returns {@code null} if there is no {@code Intent} or if the
101
     * sender is not trusted.
102
     * @see #addTrustedSigner(Class)
103
     */
104
    public Intent getIntentFromTrustedSender(Activity activity) {
105
        Intent intent = activity.getIntent();
106
        String packageName = getCallingPackageName(activity);
107
        if (TextUtils.isEmpty(packageName)) {
108
            return null;
109
        }
110
        if (isPackageNameTrusted(packageName)) {
111
            return intent;
112
        }
113
        return null;
114
    }
115

    
116
    /**
117
     * Get the package name of the {@link Activity} that sent the
118
     * {@link Intent} that started this {@code Activity}.
119
     * <p/>
120
     * <strong>WARNING</strong>: If the {@code Activity} has
121
     * {@code android:launchMode="singleInstance"} or {@code "singleTask"}, then
122
     * this method will not disconnect because it is not possible to get the
123
     * calling {@code Activity}, as set by
124
     * {@link Activity#startActivityForResult(Intent, int)}
125
     *
126
     * @param activity the {@code Activity} to check for the {@code Intent}
127
     * @return the package of the sending app or {@code null} if it was not a
128
     * {@code ACTION_CONNECT Intent} or the {@code Intent} was not sent
129
     * with {@link Activity#startActivityForResult(Intent, int)}
130
     */
131
    public static String getCallingPackageName(Activity activity) {
132
        // getCallingPackage() was unstable until android-18, use this
133
        ComponentName componentName = activity.getCallingActivity();
134
        if (componentName == null)
135
            return null;
136
        String packageName = componentName.getPackageName();
137
        if (TextUtils.isEmpty(packageName)) {
138
            Log.e(activity.getClass().getSimpleName(),
139
                    "Received Intent without sender! The Intent must be sent using startActivityForResult() and received without launchMode singleTask or singleInstance!");
140
        }
141
        return packageName;
142
    }
143

    
144
    /**
145
     * This is used to check whether an {@link Intent} that will be sent is
146
     * complete. It should <strong>not</strong> be used with {@code Intent}s
147
     * that have been received already.
148
     */
149
    private boolean isIntentSane(Intent intent) {
150
        if (intent == null)
151
            return false;
152
        if (TextUtils.isEmpty(intent.getPackage())) {
153
            ComponentName componentName = intent.getComponent();
154
            if (componentName == null || TextUtils.isEmpty(componentName.getPackageName())) {
155
                return false;
156
            }
157
        }
158
        return true;
159
    }
160

    
161
    /**
162
     * Add an APK signature that is always trusted for any packageName.
163
     *
164
     * @param cls {@link Class} of the {@link ApkSignaturePin} to trust
165
     * @return boolean
166
     * @throws {@link IllegalArgumentException} the class cannot be instantiated
167
     */
168
    public boolean addTrustedSigner(Class<? extends ApkSignaturePin> cls) {
169
        try {
170
            Constructor<? extends ApkSignaturePin> constructor = cls.getConstructor();
171
            return pinList.add((ApkSignaturePin) constructor.newInstance((Object[]) null));
172
        } catch (Exception e) {
173
            e.printStackTrace();
174
            throw new IllegalArgumentException(e);
175
        }
176
    }
177

    
178
    /**
179
     * Remove an APK signature from the trusted set.
180
     *
181
     * @param cls {@link Class} of the {@link ApkSignaturePin} to remove
182
     */
183
    public boolean removeTrustedSigner(Class<? extends ApkSignaturePin> cls) {
184
        for (ApkSignaturePin pin : pinList) {
185
            if (pin.getClass().equals(cls)) {
186
                return pinList.remove(pin);
187
            }
188
        }
189
        return false;
190
    }
191

    
192
    /**
193
     * Remove all {@link ApkSignaturePin}s from the trusted set.
194
     */
195
    public boolean removeAllTrustedSigners() {
196
        pinList.clear();
197
        return pinList.isEmpty();
198
    }
199

    
200
    /**
201
     * Check if a {@link ApkSignaturePin} is trusted.
202
     *
203
     * @param cls {@link Class} of the {@link ApkSignaturePin} to check
204
     */
205
    public boolean isTrustedSigner(Class<? extends ApkSignaturePin> cls) {
206
        for (ApkSignaturePin pin : pinList) {
207
            if (pin.getClass().equals(cls)) {
208
                return true;
209
            }
210
        }
211
        return false;
212
    }
213

    
214
    public void checkTrustedSigner(String packageName)
215
            throws NameNotFoundException, CertificateException {
216
        PackageInfo packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
217
        checkTrustedSigner(packageInfo.signatures);
218
    }
219

    
220
    public void checkTrustedSigner(PackageInfo packageInfo)
221
            throws NameNotFoundException, CertificateException {
222
        checkTrustedSigner(packageInfo.signatures);
223
    }
224

    
225
    public void checkTrustedSigner(Signature[] signatures)
226
            throws NameNotFoundException, CertificateException {
227
        if (signatures == null || signatures.length == 0)
228
            throw new CertificateException("signatures cannot be null or empty!");
229
        for (int i = 0; i < signatures.length; i++)
230
            if (signatures[i] == null || signatures[i].toByteArray().length == 0)
231
                throw new CertificateException("Certificates cannot be null or empty!");
232

    
233
        // check whether the APK signer is trusted for all apps
234
        for (ApkSignaturePin pin : pinList)
235
            if (areSignaturesEqual(signatures, pin.getSignatures()))
236
                return; // found a matching trusted APK signer
237

    
238
        throw new CertificateException("APK signatures did not match!");
239
    }
240

    
241
    public boolean areSignaturesEqual(Signature[] sigs0, Signature[] sigs1) {
242
        // TODO where is Android's implementation of this that I can just call?
243
        if (sigs0 == null || sigs1 == null)
244
            return false;
245
        if (sigs0.length == 0 || sigs1.length == 0)
246
            return false;
247
        if (sigs0.length != sigs1.length)
248
            return false;
249
        for (int i = 0; i < sigs0.length; i++)
250
            if (!sigs0[i].equals(sigs1[i]))
251
                return false;
252
        return true;
253
    }
254

    
255
    public void startActivity(Context context, Intent intent) throws CertificateException {
256
        if (!isIntentSane(intent))
257
            throw new ActivityNotFoundException("The intent was null or empty!");
258
        String packageName = intent.getPackage();
259
        if (TextUtils.isEmpty(packageName)) {
260
            packageName = intent.getComponent().getPackageName();
261
            intent.setPackage(packageName);
262
        }
263
        try {
264
            checkTrustedSigner(packageName);
265
        } catch (NameNotFoundException e) {
266
            e.printStackTrace();
267
            throw new ActivityNotFoundException(e.getLocalizedMessage());
268
        }
269
        context.startActivity(intent);
270
    }
271
}