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