1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.telephony; 17 18 import android.net.Uri; 19 import android.os.Build; 20 import android.telecom.PhoneAccount; 21 import android.telephony.PhoneNumberUtils; 22 import android.text.TextUtils; 23 import android.util.Base64; 24 import android.util.Log; 25 26 import com.android.internal.telephony.util.TelephonyUtils; 27 28 import java.security.MessageDigest; 29 import java.security.NoSuchAlgorithmException; 30 31 /** 32 * A copy of {@link android.telephony.Rlog} to be used within the telephony mainline module. 33 * 34 * @hide 35 */ 36 public final class Rlog { 37 38 private static final boolean USER_BUILD = TelephonyUtils.IS_USER; 39 Rlog()40 private Rlog() { 41 } 42 log(int priority, String tag, String msg)43 private static int log(int priority, String tag, String msg) { 44 return Log.logToRadioBuffer(priority, tag, msg); 45 } 46 v(String tag, String msg)47 public static int v(String tag, String msg) { 48 return log(Log.VERBOSE, tag, msg); 49 } 50 v(String tag, String msg, Throwable tr)51 public static int v(String tag, String msg, Throwable tr) { 52 return log(Log.VERBOSE, tag, 53 msg + '\n' + Log.getStackTraceString(tr)); 54 } 55 d(String tag, String msg)56 public static int d(String tag, String msg) { 57 return log(Log.DEBUG, tag, msg); 58 } 59 d(String tag, String msg, Throwable tr)60 public static int d(String tag, String msg, Throwable tr) { 61 return log(Log.DEBUG, tag, 62 msg + '\n' + Log.getStackTraceString(tr)); 63 } 64 i(String tag, String msg)65 public static int i(String tag, String msg) { 66 return log(Log.INFO, tag, msg); 67 } 68 i(String tag, String msg, Throwable tr)69 public static int i(String tag, String msg, Throwable tr) { 70 return log(Log.INFO, tag, 71 msg + '\n' + Log.getStackTraceString(tr)); 72 } 73 w(String tag, String msg)74 public static int w(String tag, String msg) { 75 return log(Log.WARN, tag, msg); 76 } 77 w(String tag, String msg, Throwable tr)78 public static int w(String tag, String msg, Throwable tr) { 79 return log(Log.WARN, tag, 80 msg + '\n' + Log.getStackTraceString(tr)); 81 } 82 w(String tag, Throwable tr)83 public static int w(String tag, Throwable tr) { 84 return log(Log.WARN, tag, Log.getStackTraceString(tr)); 85 } 86 e(String tag, String msg)87 public static int e(String tag, String msg) { 88 return log(Log.ERROR, tag, msg); 89 } 90 e(String tag, String msg, Throwable tr)91 public static int e(String tag, String msg, Throwable tr) { 92 return log(Log.ERROR, tag, 93 msg + '\n' + Log.getStackTraceString(tr)); 94 } 95 println(int priority, String tag, String msg)96 public static int println(int priority, String tag, String msg) { 97 return log(priority, tag, msg); 98 } 99 isLoggable(String tag, int level)100 public static boolean isLoggable(String tag, int level) { 101 return Log.isLoggable(tag, level); 102 } 103 104 /** 105 * Redact personally identifiable information for production users. 106 * @param tag used to identify the source of a log message 107 * @param pii the personally identifiable information we want to apply secure hash on. 108 * @return If tag is loggable in verbose mode or pii is null, return the original input. 109 * otherwise return a secure Hash of input pii 110 */ pii(String tag, Object pii)111 public static String pii(String tag, Object pii) { 112 String val = String.valueOf(pii); 113 if (pii == null || TextUtils.isEmpty(val) || isLoggable(tag, Log.VERBOSE)) { 114 return val; 115 } 116 return "[" + secureHash(val.getBytes()) + "]"; 117 } 118 119 /** 120 * Redact personally identifiable information for production users. 121 * @param enablePiiLogging set when caller explicitly want to enable sensitive logging. 122 * @param pii the personally identifiable information we want to apply secure hash on. 123 * @return If enablePiiLogging is set to true or pii is null, return the original input. 124 * otherwise return a secure Hash of input pii 125 */ pii(boolean enablePiiLogging, Object pii)126 public static String pii(boolean enablePiiLogging, Object pii) { 127 String val = String.valueOf(pii); 128 if (pii == null || TextUtils.isEmpty(val) || enablePiiLogging) { 129 return val; 130 } 131 return "[" + secureHash(val.getBytes()) + "]"; 132 } 133 134 /** 135 * Generates an obfuscated string for a calling handle in {@link Uri} format, or a raw phone 136 * phone number in {@link String} format. 137 * @param pii The information to obfuscate. 138 * @return The obfuscated string. 139 */ piiHandle(Object pii)140 public static String piiHandle(Object pii) { 141 StringBuilder sb = new StringBuilder(); 142 if (pii instanceof Uri) { 143 Uri uri = (Uri) pii; 144 String scheme = uri.getScheme(); 145 146 if (!TextUtils.isEmpty(scheme)) { 147 sb.append(scheme).append(":"); 148 } 149 150 String textToObfuscate = uri.getSchemeSpecificPart(); 151 if (PhoneAccount.SCHEME_TEL.equals(scheme)) { 152 obfuscatePhoneNumber(sb, textToObfuscate); 153 } else if (PhoneAccount.SCHEME_SIP.equals(scheme)) { 154 for (int i = 0; i < textToObfuscate.length(); i++) { 155 char c = textToObfuscate.charAt(i); 156 if (c != '@' && c != '.') { 157 c = '*'; 158 } 159 sb.append(c); 160 } 161 } else { 162 sb.append("***"); 163 } 164 } else if (pii instanceof String) { 165 String number = (String) pii; 166 obfuscatePhoneNumber(sb, number); 167 } 168 169 return sb.toString(); 170 } 171 172 /** 173 * Obfuscates a phone number, allowing NUM_DIALABLE_DIGITS_TO_LOG digits to be exposed for the 174 * phone number. 175 * @param sb String buffer to write obfuscated number to. 176 * @param phoneNumber The number to obfuscate. 177 */ obfuscatePhoneNumber(StringBuilder sb, String phoneNumber)178 private static void obfuscatePhoneNumber(StringBuilder sb, String phoneNumber) { 179 int numDigitsToLog = USER_BUILD ? 0 : 2; 180 int numDigitsToObfuscate = getDialableCount(phoneNumber) - numDigitsToLog; 181 for (int i = 0; i < phoneNumber.length(); i++) { 182 char c = phoneNumber.charAt(i); 183 boolean isDialable = PhoneNumberUtils.isDialable(c); 184 if (isDialable) { 185 numDigitsToObfuscate--; 186 } 187 sb.append(isDialable && numDigitsToObfuscate >= 0 ? "*" : c); 188 } 189 } 190 191 /** 192 * Determines the number of dialable characters in a string. 193 * @param toCount The string to count dialable characters in. 194 * @return The count of dialable characters. 195 */ getDialableCount(String toCount)196 private static int getDialableCount(String toCount) { 197 int numDialable = 0; 198 for (char c : toCount.toCharArray()) { 199 if (PhoneNumberUtils.isDialable(c)) { 200 numDialable++; 201 } 202 } 203 return numDialable; 204 } 205 206 /** 207 * Returns a secure hash (using the SHA1 algorithm) of the provided input. 208 * 209 * @return "****" if the build type is user, otherwise the hash 210 * @param input the bytes for which the secure hash should be computed. 211 */ secureHash(byte[] input)212 private static String secureHash(byte[] input) { 213 // Refrain from logging user personal information in user build. 214 if (USER_BUILD) { 215 return "****"; 216 } 217 218 MessageDigest messageDigest; 219 220 try { 221 messageDigest = MessageDigest.getInstance("SHA-1"); 222 } catch (NoSuchAlgorithmException e) { 223 return "####"; 224 } 225 226 byte[] result = messageDigest.digest(input); 227 return Base64.encodeToString( 228 result, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP); 229 } 230 } 231