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 |
} |