背景
最近在浏览 Android 11 源代码的时候,发现在ART虚拟机头文件 art_method.h 中,存在大量的类似REQUIRES_SHARED(Locks::mutator_lock_);
的代码。
Android是一种基于Linux的自由及开放源代码的操作系统,主要使用于移动设备,如智能手机和平板电脑,由Google公司和开放手机联盟领导及开发。
最近在浏览 Android 11 源代码的时候,发现在ART虚拟机头文件 art_method.h 中,存在大量的类似REQUIRES_SHARED(Locks::mutator_lock_);
的代码。
我们认识到在移动端开发中安全性设置非常重要,尤其是目前非常流程H5混合式开发APP,在Android开发中,我们可以通过证书锁定的方式来增加客户端与服务端的安全保障《证书锁定SSL Pinning简介及用途》,本文主要介绍 SSL数字证书在Android开发中的证书锁定(SSL/TLS Pinning)
通常由CA权威机构签发的证书,其根证书都内置在最新的Android操作系统中,因此默认情况下可不进行SSL证书锁定,开发APP时也就变得非常简单,以infinisign.com为例,摘自App security best practices
1 2 3 4 |
URL url = new URL("https://infinisign.com"); HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection(); urlConnection.connect(); InputStream in = urlConnection.getInputStream(); |
1 2 3 4 5 6 |
val url = URL("https://infinisign.com") val urlConnection = url.openConnection() as HttpsURLConnection urlConnection.connect() urlConnection.inputStream.use { ... } |
本方案是官方提供,但需要依赖Android N(Android 7.0 API 24)及以后版本,可在APP开发阶段在APP中内置安全性设置,以达到防止中间人攻击(MITM)的目的,此方法只限制在Android 7.0 API 24以后版本,因此该版本之前的安全性设置仍然需要使用证书锁定方法,本文以infinisign.com为例。
创建文件res/xml/network_security_config.xml
,需要注意的是,使用证书锁定,需要配置一个备份密钥,假如证书到期或更换了CA品牌后,不至于重新发行APP,这个备份密码可以是中级证书或根证书。
通俗的说,如果系统检测到签发证书过期了,则自动使用其中级或才根级证书作为验证,因为通常中级机构、根机构的证书到期时间非常长。
但实际情况是,infinisign.com所售的CA签发证书有效期都是一年,而现在发行APP或更新APP通常在一年都会有更新重新上架操作。
1 2 3 4 5 6 7 8 9 |
<?xml version="1.0" encoding="utf-8"?> <network-security-config> <domain-config> <domain includeSubdomains="true">infinisign.com</domain> <pin digest="SHA-256">wLgBEAGmLltnXbK6pzpvPMeOCTKZ0QwrWGem6DkNf6o</pin> <!-- 备份密钥,比如infinisign.com的中级机构是geotrust --> <pin digest="SHA-256">wLgBEAGmLltnXbK6pzpvPMeOCTKZ0QwrWGem6DkNf6o</pin> </domain-config> </network-security-config> |
在Androidmanifest.xml
引入配置文件android:networkSecurityConfig="@xml/network_security_config"
1 2 3 4 5 6 7 |
<?xml version="1.0" encoding="utf-8"?> <manifest ... > <application android:networkSecurityConfig="@xml/network_security_config"> <!-- application 其它子元素 --> </application> </manifest> |
okHttp是一个用于Android处理网络请求的开源项目,是安卓端最流行的轻量级的网络框架,其主要用来替代HttpUrlConnection处理方式
1 2 3 4 5 |
client = new OkHttpClient.Builder() .certificatePinner(new CertificatePinner.Builder() .add("infinisign.com", "sha256/S8Ff3JCaO4V...") .build()) .build(); |
不过需要注意的是,okHttp锁定证书方式不适用于公钥锁定方式,必须以证书锁定方式内置SSL数字证书。
TrustManager是一个比较老的证书锁定方法,主要用于早期的Android版本或者用于一些CA根机构在Android系统中缺失根证书的情形下,当然也适用于自签名证书的锁定,通常我们不建议这样做,因为TrustManager的缺点是中间人仍然可以使用成熟的绕过方案来实现截持。
在SSL普及的今天,let's encrypt的开源免费解决方案,和一些入门的便宜DV域名型SSL证书(见PositiveSSL ¥39/年)足以媲美自签名方案。
详细请参考javax.net.ssl.TrustManager接口实现,简单步骤如下
在APP源码中内置证书
1 |
/res/raw |
使用KeyStore加载证书
1 2 3 4 |
val resourceStream = resources.openRawResource(R.raw.infinisign_cert) val keyStoreType = KeyStore.getDefaultType() val keyStore = KeyStore.getInstance(keyStoreType) keyStore.load(resourceStream, null) |
TrustManagerFactory实例化证书
1 2 3 |
val trustManagerAlgorithm = TrustManagerFactory.getDefaultAlgorithm() val trustManagerFactory = TrustManagerFactory.getInstance(trustManagerAlgorithm) trustManagerFactory.init(keyStore) |
创建SSLContext实例,与TrustManager进行绑定。
1 2 3 4 5 |
val sslContext = SSLContext.getInstance("TLS") sslContext.init(null, trustManagerFactory.trustManagers, null) val url = URL("http://infinisign.com/") val urlConnection = url.openConnection() as HttpsURLConnection urlConnection.sslSocketFactory = sslContext.socketFactory |
在多数移动操作系统中有大大小小几十个CA机构内置的根证书,但也不排除已经不被信任的CA机构存在的旧根证书,还有一些例如国内的一些基于Android老版本的操作系统仍然面临着安全风险,所以使用SSL数字证书锁定(SSL/TLS Pinning)的目标是缩小可信CA的范围,让APP客户端传送数据更安全,更切实地保障用户数据。
一. tPacketcapture工具
1.什么是Packetcapture?
tPacketCapture数据包捕获,而无需使用任何root权限,原理是构建了一个本地VPN服务,引导网络请求流经VPN服务,从而实现抓包。 tPacketCapture使用了Android OS VpnService提供。 捕获的数据保存在外部存储为PCAP文件格式。
2.操作步骤
1)安装tPacketCapture.apk
2)打开该APP,点击"Capture"按钮
3)触发需要抓包的操作
4)用以下命令,adb pull /mnt/shell/emulated/0/Android/data/jp.co.taosoftware.android.packetcapture/files/,把文件拷贝出来了
5).pcap会保存在adb的相同路径下
6)将.pcap取出后,用wireshark打开就能看到了相关信息
缓存的主要作用是暂时在内存中保存业务系统的数据处理结果,并且等待下次访问使用。在日长开发有很多场合,有一些数据量不是很大,不会经常改动,并且访问非常频繁。但是由于受限于硬盘IO的性能或者远程网络等原因获取可能非常的费时。会导致我们的程序非常缓慢,这在某些业务上是不能忍的!而缓存正是解决这类问题的神器!
由于app要实现登录缓存功能,但惊讶的发现不经过设置okHttp是不会自动管理header的.
官网的文档也是醉了,找了半天没看懂怎么搞.
其实实现自动管理cookie很简单很简单,在OkHttp的builder中加上个.cookiejar()就能实现自动缓存,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
OkHttpClient.Builder b = new OkHttpClient.Builder(); b.cookieJar(new CookieJar() { //这里一定一定一定是HashMap<String, List<Cookie>>,是String,不是url. private final HashMap<String, List<Cookie>> cookieStore = new HashMap<>(); @Override public void saveFromResponse(HttpUrl url, List<Cookie> cookies) { cookieStore.put(url.host(), cookies); } @Override public List<Cookie> loadForRequest(HttpUrl url) { List<Cookie> cookies = cookieStore.get(url.host()); return cookies != null ? cookies : new ArrayList<Cookie>(); } }); |
!!!!!注意注意注意!!!!!!
HashMap的key是String!!!!直接传进去url,是没有效果的!!!!!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 |
import android.annotation.TargetApi; import android.content.Context; import android.net.ConnectivityManager; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkInfo; import android.net.RouteInfo; import android.os.Build; import android.util.Log; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.LineNumberReader; import java.lang.reflect.Method; import java.net.InetAddress; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; /** * DNS servers detector * <p> * IMPORTANT: don't cache the result. * <p> * Or if you want to cache the result make sure you invalidate the cache * on any network change. * <p> * It is always better to use a new instance of the detector when you need * current DNS servers otherwise you may get into troubles because of invalid/changed * DNS servers. * <p> * This class combines various methods and solutions from: * Dnsjava http://www.xbill.org/dnsjava/ * Minidns https://github.com/MiniDNS/minidns * <p> * Unfortunately both libraries are not aware of Orero changes so new method was added to fix this. * <p> * Created by Madalin Grigore-Enescu on 2/24/18. */ public class DnsServersDetector { private static final String TAG = "DnsServersDetector"; /** * Holds some default DNS servers used in case all DNS servers detection methods fail. * Can be set to null if you want caller to fail in this situation. */ private static final String[] FACTORY_DNS_SERVERS = { "8.8.8.8", "8.8.4.4" }; /** * Properties delimiter used in exec method of DNS servers detection */ private static final String METHOD_EXEC_PROP_DELIM = "]: ["; /** * Holds context this was created under */ private Context context; //region - public ////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////// /** * Constructor */ public DnsServersDetector(Context context) { this.context = context; } /** * Returns android DNS servers used for current connected network * * @return Dns servers array */ public String[] getServers() { // METHOD 1: old deprecated system properties String[] result = getServersMethodSystemProperties(); if (result != null && result.length > 0) { return result; } // METHOD 2 - use connectivity manager result = getServersMethodConnectivityManager(); if (result != null && result.length > 0) { return result; } // LAST METHOD: detect android DNS servers by executing getprop string command in a separate process // This method fortunately works in Oreo too but many people may want to avoid exec // so it's used only as a failsafe scenario result = getServersMethodExec(); if (result != null && result.length > 0) { return result; } // Fall back on factory DNS servers return FACTORY_DNS_SERVERS; } //endregion //region - private ///////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////// /** * Detect android DNS servers by using connectivity manager * <p> * This method is working in android LOLLIPOP or later * * @return Dns servers array */ private String[] getServersMethodConnectivityManager() { // This code only works on LOLLIPOP and higher if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { try { final ArrayList<String> priorityServersArrayList = new ArrayList<>(); final ArrayList<String> serversArrayList = new ArrayList<>(); final ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(android.content.Context.CONNECTIVITY_SERVICE); if (connectivityManager != null) { // Iterate all networks // Notice that android LOLLIPOP or higher allow iterating multiple connected networks of SAME type for (Network network : connectivityManager.getAllNetworks()) { final NetworkInfo networkInfo = connectivityManager.getNetworkInfo(network); if (networkInfo.isConnected()) { final LinkProperties linkProperties = connectivityManager.getLinkProperties(network); final List<InetAddress> dnsServersList = linkProperties.getDnsServers(); // Prioritize the DNS servers for link which have a default route if (linkPropertiesHasDefaultRoute(linkProperties)) { for (InetAddress element : dnsServersList) { final String dnsHost = element.getHostAddress(); priorityServersArrayList.add(dnsHost); } } else { for (InetAddress element : dnsServersList) { final String dnsHost = element.getHostAddress(); serversArrayList.add(dnsHost); } } } } } // Append secondary arrays only if priority is empty if (priorityServersArrayList.isEmpty()) { priorityServersArrayList.addAll(serversArrayList); } // Stop here if we have at least one DNS server if (priorityServersArrayList.size() > 0) { return priorityServersArrayList.toArray(new String[0]); } } catch (Exception ex) { Log.d(TAG, "Exception detecting DNS servers using ConnectivityManager method", ex); } } // Failure return null; } /** * Detect android DNS servers by using old deprecated system properties * <p> * This method is NOT working anymore in Android 8.0 * Official Android documentation state this in the article Android 8.0 Behavior Changes. * The system properties net.dns1, net.dns2, net.dns3, and net.dns4 are no longer available, * a change that improves privacy on the platform. * <p> * https://developer.android.com/about/versions/oreo/android-8.0-changes.html#o-pri * * @return Dns servers array */ private String[] getServersMethodSystemProperties() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { // This originally looked for all lines containing .dns; but // http://code.google.com/p/android/issues/detail?id=2207#c73 // indicates that net.dns* should always be the active nameservers, so // we use those. final String re1 = "^\\d+(\\.\\d+){3}$"; final String re2 = "^[0-9a-f]+(:[0-9a-f]*)+:[0-9a-f]+$"; ArrayList<String> serversArrayList = new ArrayList<>(); try { Class<?> SystemProperties = Class.forName("android.os.SystemProperties"); Method method = SystemProperties.getMethod("get", new Class[]{String.class}); final String[] netdns = new String[]{"net.dns1", "net.dns2", "net.dns3", "net.dns4"}; for (String netdn : netdns) { Object[] args = new Object[]{netdn}; String v = (String) method.invoke(null, args); if (v != null && (v.matches(re1) || v.matches(re2)) && !serversArrayList.contains(v)) { serversArrayList.add(v); } } // Stop here if we have at least one DNS server if (serversArrayList.size() > 0) { return serversArrayList.toArray(new String[0]); } } catch (Exception e) { Log.d(TAG, "Exception detecting DNS servers using SystemProperties method", e); } } // Failed return null; } /** * Detect android DNS servers by executing getprop string command in a separate process * <p> * Notice there is an android bug when Runtime.exec() hangs without providing a Process object. * This problem is fixed in Jelly Bean (Android 4.1) but not in ICS (4.0.4) and probably it will never be fixed in ICS. * https://stackoverflow.com/questions/8688382/runtime-exec-bug-hangs-without-providing-a-process-object/11362081 * * @return Dns servers array */ private String[] getServersMethodExec() { // We are on the safe side and avoid any bug if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { try { Process process = Runtime.getRuntime().exec("getprop"); InputStream inputStream = process.getInputStream(); LineNumberReader lineNumberReader = new LineNumberReader(new InputStreamReader(inputStream)); Set<String> serversSet = methodExecParseProps(lineNumberReader); if (serversSet != null && serversSet.size() > 0) { return serversSet.toArray(new String[0]); } } catch (Exception e) { Log.d(TAG, "Exception in getServersMethodExec", e); } } // Failed return null; } /** * Parse properties produced by executing getprop command * * @param lineNumberReader lineNumberReader * @return Set of parsed properties * @throws Exception Exception */ private Set<String> methodExecParseProps(BufferedReader lineNumberReader) throws Exception { String line; Set<String> serversSet = new HashSet<String>(10); while ((line = lineNumberReader.readLine()) != null) { int split = line.indexOf(METHOD_EXEC_PROP_DELIM); if (split == -1) { continue; } String property = line.substring(1, split); int valueStart = split + METHOD_EXEC_PROP_DELIM.length(); int valueEnd = line.length() - 1; if (valueEnd < valueStart) { // This can happen if a newline sneaks in as the first character of the property value. For example // "[propName]: [\n…]". Log.d(TAG, "Malformed property detected: \"" + line + '"'); continue; } String value = line.substring(valueStart, valueEnd); if (value.isEmpty()) { continue; } if (property.endsWith(".dns") || property.endsWith(".dns1") || property.endsWith(".dns2") || property.endsWith(".dns3") || property.endsWith(".dns4")) { // normalize the address InetAddress ip = InetAddress.getByName(value); if (ip == null) continue; value = ip.getHostAddress(); if (value == null) continue; if (value.length() == 0) continue; serversSet.add(value); } } return serversSet; } /** * Returns true if the specified link properties have any default route * * @param linkProperties linkProperties * @return true if the specified link properties have default route or false otherwise */ @TargetApi(Build.VERSION_CODES.LOLLIPOP) private boolean linkPropertiesHasDefaultRoute(LinkProperties linkProperties) { for (RouteInfo route : linkProperties.getRoutes()) { if (route.isDefaultRoute()) { return true; } } return false; } //endregion } |
DNS(Domain Name System,域名系统),dns用于将域名解析解析为ip地址。
例如:给你www.baidu.com的主机名,你给
我查出对应的ip地址:163.177.151.109。一些主机名还会有别名,如www.baidu.com就
有别名www.a.shifen.com,甚至不止一个别名,或一个别名有2个ip地址。在linux机子
上,运行nslookup(name service lookup)就是进行域名解析。如下面:
1 2 3 4 5 6 7 8 9 10 |
~$ nslookup www.baidu.com Server: 127.0.0.1 Address: 127.0.0.1#53 Non-authoritative answer: www.baidu.com canonical name = www.a.shifen.com. Name: www.a.shifen.com Address: 163.177.151.109 Name: www.a.shifen.com Address: 163.177.151.110 |
DNS工作方式分为递归查询和迭代查询,具体可参考下图
https://pub.flutter-io.cn/packages/permission_handler
https://www.jianshu.com/p/fa68876fbdfd
例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
Future requestPermission() async { // 申请权限 Map<PermissionGroup, PermissionStatus> permissions = await PermissionHandler().requestPermissions([PermissionGroup.storage]); // 申请结果 PermissionStatus permission = await PermissionHandler() .checkPermissionStatus(PermissionGroup.storage); if (permission == PermissionStatus.granted) { print("权限申请通过"); } else { print("权限申请通过"); } } } |
最近在编译Flutter
项目的时候,报错如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
FAILURE: Build failed with an exception. * What went wrong: Execution failed for task ':app:processDebugResources'. > Android resource linking failed Output: /Users/xxxx/Source/developer_quest/build/app/intermediates/incremental/mergeDebugResources/merged.dir/values/values.xml:86: error: resource android:attr/fontVariationSettings not found. /Users/xxxx/Source/developer_quest/build/app/intermediates/incremental/mergeDebugResources/merged.dir/values/values.xml:87: error: resource android:attr/ttcIndex not found. error: failed linking references. Command: /Users/xxxx/.gradle/caches/transforms-1/files-1.1/aapt2-3.2.1-4818971-osx.jar/ace5a2b7765511437e6add51febae423/aapt2-3.2.1-4818971-osx/aapt2 link -I\ /Users/xxxx/Library/Android/sdk/platforms/android-27/android.jar\ --manifest\ /Users/xxxx/Source/developer_quest/build/app/intermediates/merged_manifests/debug/processDebugManifest/merged/AndroidManifest.xml\ -o\ /Users/xxxx/Source/developer_quest/build/app/intermediates/processed_res/debug/processDebugResources/out/resources-debug.ap_\ -R\ @/Users/xxxx/Source/developer_quest/build/app/intermediates/incremental/processDebugResources/resources-list-for-resources-debug.ap_.txt\ --auto-add-overlay\ --java\ /Users/xxxx/Source/developer_quest/build/app/generated/not_namespaced_r_class_sources/debug/processDebugResources/r\ --custom-package\ dev.flutter.devrpg\ -0\ apk\ --output-text-symbols\ /Users/xxxx/Source/developer_quest/build/app/intermediates/symbols/debug/R.txt\ --no-version-vectors Daemon: AAPT2 aapt2-3.2.1-4818971-osx Daemon #0 Output: /Users/xxxx/.gradle/caches/transforms-1/files-1.1/core-1.1.0.aar/becc1f5b1538f3fe61f1f959fb2bd672/res/values/values.xml:174:5-210:25: AAPT: error: resource android:attr/fontVariationSettings not found. /Users/xxxx/.gradle/caches/transforms-1/files-1.1/core-1.1.0.aar/becc1f5b1538f3fe61f1f959fb2bd672/res/values/values.xml:174:5-210:25: AAPT: error: resource android:attr/ttcIndex not found. error: failed linking references. Command: /Users/xxxx/.gradle/caches/transforms-1/files-1.1/aapt2-3.2.1-4818971-osx.jar/ace5a2b7765511437e6add51febae423/aapt2-3.2.1-4818971-osx/aapt2 link -I\ /Users/xxxx/Library/Android/sdk/platforms/android-27/android.jar\ --manifest\ /Users/xxxx/Source/developer_quest/build/app/intermediates/merged_manifests/debug/processDebugManifest/merged/AndroidManifest.xml\ -o\ /Users/xxxx/Source/developer_quest/build/app/intermediates/processed_res/debug/processDebugResources/out/resources-debug.ap_\ -R\ @/Users/xxxx/Source/developer_quest/build/app/intermediates/incremental/processDebugResources/resources-list-for-resources-debug.ap_.txt\ --auto-add-overlay\ --java\ /Users/xxxx/Source/developer_quest/build/app/generated/not_namespaced_r_class_sources/debug/processDebugResources/r\ --custom-package\ dev.flutter.devrpg\ -0\ apk\ --output-text-symbols\ /Users/xxxx/Source/developer_quest/build/app/intermediates/symbols/debug/R.txt\ --no-version-vectors Daemon: AAPT2 aapt2-3.2.1-4818971-osx Daemon #0 * Try: Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights. * Get more help at https://help.gradle.org BUILD FAILED in 13s Exception: Gradle task assembleDebug failed with exit code 1 |
try to change the compileSdkVersion above 27:
compileSdkVersion 28