Statistics
| Branch: | Tag: | Revision:

trustedintents / trustedintents / src / info / guardianproject / trustedintents / TrustedIntents.java @ 6dd86be3

History | View | Annotate | Download (8.49 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

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

    
21
public class TrustedIntents {
22

    
23
    private static TrustedIntents instance;
24

    
25
    private static PackageManager pm;
26

    
27
    private final LinkedHashSet<ApkSignaturePin> pinList;
28

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

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

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

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

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

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

    
95
    /**
96
     * Check if an {@link Intent} is from a trusted sender.
97
     *
98
     * @param intent the {@code Intent} to check
99
     * @return boolean whether {@code intent} is from a trusted sender
100
     * @see #addTrustedSigner(Class)
101
     */
102
    public boolean isIntentFromTrustedSender(Intent intent) {
103
        if (!isIntentSane(intent)) {
104
            return false;
105
        }
106
        String packageName = intent.getPackage();
107
        if (TextUtils.isEmpty(packageName)) {
108
            packageName = intent.getComponent().getPackageName();
109
        }
110
        if (TextUtils.isEmpty(packageName)) {
111
            return false;
112
        }
113
        return isPackageNameTrusted(packageName);
114
    }
115

    
116
    /**
117
     * Returns an {@link Intent} if the sending app is signed by one of
118
     * the trusted signing keys as set in {@link #addTrustedSigner(Class)}.
119
     *
120
     * @returns {@code null} if there is no {@code Intent} or if the
121
     * sender is not trusted.
122
     * @see #addTrustedSigner(Class)
123
     */
124
    public Intent getIntentFromTrustedSender(Activity activity) {
125
        Intent intent = activity.getIntent();
126
        if (isIntentFromTrustedSender(intent)) {
127
            return intent;
128
        }
129
        return null;
130
    }
131

    
132
    private boolean isIntentSane(Intent intent) {
133
        if (intent == null)
134
            return false;
135
        if (TextUtils.isEmpty(intent.getPackage())) {
136
            ComponentName componentName = intent.getComponent();
137
            if (componentName == null || TextUtils.isEmpty(componentName.getPackageName())) {
138
                return false;
139
            }
140
        }
141
        return true;
142
    }
143

    
144
    /**
145
     * Add an APK signature that is always trusted for any packageName.
146
     *
147
     * @param cls {@link Class} of the {@link ApkSignaturePin} to trust
148
     * @return boolean
149
     * @throws {@link IllegalArgumentException} the class cannot be instantiated
150
     */
151
    public boolean addTrustedSigner(Class<? extends ApkSignaturePin> cls) {
152
        try {
153
            Constructor<? extends ApkSignaturePin> constructor = cls.getConstructor();
154
            return pinList.add((ApkSignaturePin) constructor.newInstance((Object[]) null));
155
        } catch (Exception e) {
156
            e.printStackTrace();
157
            throw new IllegalArgumentException(e);
158
        }
159
    }
160

    
161
    /**
162
     * Remove an APK signature from the trusted set.
163
     *
164
     * @param cls {@link Class} of the {@link ApkSignaturePin} to remove
165
     */
166
    public boolean removeTrustedSigner(Class<? extends ApkSignaturePin> cls) {
167
        for (ApkSignaturePin pin : pinList) {
168
            if (pin.getClass().equals(cls)) {
169
                return pinList.remove(pin);
170
            }
171
        }
172
        return false;
173
    }
174

    
175
    /**
176
     * Remove all {@link ApkSignaturePin}s from the trusted set.
177
     */
178
    public boolean removeAllTrustedSigners() {
179
        pinList.clear();
180
        return pinList.isEmpty();
181
    }
182

    
183
    /**
184
     * Check if a {@link ApkSignaturePin} is trusted.
185
     *
186
     * @param cls {@link Class} of the {@link ApkSignaturePin} to check
187
     */
188
    public boolean isTrustedSigner(Class<? extends ApkSignaturePin> cls) {
189
        for (ApkSignaturePin pin : pinList) {
190
            if (pin.getClass().equals(cls)) {
191
                return true;
192
            }
193
        }
194
        return false;
195
    }
196

    
197
    public void checkTrustedSigner(String packageName)
198
            throws NameNotFoundException, CertificateException {
199
        PackageInfo packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
200
        checkTrustedSigner(packageInfo.signatures);
201
    }
202

    
203
    public void checkTrustedSigner(PackageInfo packageInfo)
204
            throws NameNotFoundException, CertificateException {
205
        checkTrustedSigner(packageInfo.signatures);
206
    }
207

    
208
    public void checkTrustedSigner(Signature[] signatures)
209
            throws NameNotFoundException, CertificateException {
210
        if (signatures == null || signatures.length == 0)
211
            throw new CertificateException("signatures cannot be null or empty!");
212
        for (int i = 0; i < signatures.length; i++)
213
            if (signatures[i] == null || signatures[i].toByteArray().length == 0)
214
                throw new CertificateException("Certificates cannot be null or empty!");
215

    
216
        // check whether the APK signer is trusted for all apps
217
        for (ApkSignaturePin pin : pinList)
218
            if (areSignaturesEqual(signatures, pin.getSignatures()))
219
                return; // found a matching trusted APK signer
220

    
221
        throw new CertificateException("APK signatures did not match!");
222
    }
223

    
224
    public boolean areSignaturesEqual(Signature[] sigs0, Signature[] sigs1) {
225
        // TODO where is Android's implementation of this that I can just call?
226
        if (sigs0 == null || sigs1 == null)
227
            return false;
228
        if (sigs0.length == 0 || sigs1.length == 0)
229
            return false;
230
        if (sigs0.length != sigs1.length)
231
            return false;
232
        for (int i = 0; i < sigs0.length; i++)
233
            if (!sigs0[i].equals(sigs1[i]))
234
                return false;
235
        return true;
236
    }
237

    
238
    public void startActivity(Context context, Intent intent) throws CertificateException {
239
        if (!isIntentSane(intent))
240
            throw new ActivityNotFoundException("The intent was null or empty!");
241
        String packageName = intent.getPackage();
242
        if (TextUtils.isEmpty(packageName)) {
243
            packageName = intent.getComponent().getPackageName();
244
            intent.setPackage(packageName);
245
        }
246
        try {
247
            checkTrustedSigner(packageName);
248
        } catch (NameNotFoundException e) {
249
            e.printStackTrace();
250
            throw new ActivityNotFoundException(e.getLocalizedMessage());
251
        }
252
        context.startActivity(intent);
253
    }
254
}