1 /* 2 * Copyright (C) 2023 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 17 package com.android.internal.telephony.security; 18 19 import static android.telephony.SecurityAlgorithmUpdate.SECURITY_ALGORITHM_UNKNOWN; 20 21 import static com.android.internal.telephony.security.CellularNetworkSecuritySafetySource.NULL_CIPHER_STATE_ENCRYPTED; 22 import static com.android.internal.telephony.security.CellularNetworkSecuritySafetySource.NULL_CIPHER_STATE_NOTIFY_ENCRYPTED; 23 import static com.android.internal.telephony.security.CellularNetworkSecuritySafetySource.NULL_CIPHER_STATE_NOTIFY_NON_ENCRYPTED; 24 25 import android.annotation.IntDef; 26 import android.content.Context; 27 import android.telephony.SecurityAlgorithmUpdate; 28 import android.telephony.SecurityAlgorithmUpdate.ConnectionEvent; 29 import android.telephony.SecurityAlgorithmUpdate.SecurityAlgorithm; 30 31 import com.android.internal.annotations.GuardedBy; 32 import com.android.internal.annotations.VisibleForTesting; 33 import com.android.telephony.Rlog; 34 35 import java.lang.annotation.Retention; 36 import java.lang.annotation.RetentionPolicy; 37 import java.util.HashMap; 38 import java.util.concurrent.Executors; 39 import java.util.concurrent.RejectedExecutionException; 40 import java.util.concurrent.ScheduledExecutorService; 41 42 /** 43 * Encapsulates logic to emit notifications to the user that a null cipher is in use. A null cipher 44 * is one that does not attempt to implement any encryption. 45 * 46 * <p>This class will either emit notifications through SafetyCenterManager if SafetyCenter exists 47 * on a device, or it will emit system notifications otherwise. 48 * 49 * TODO(b/319662115): handle radio availability, no service, SIM removal, etc. 50 * 51 * @hide 52 */ 53 public class NullCipherNotifier { 54 private static final String TAG = "NullCipherNotifier"; 55 private static NullCipherNotifier sInstance; 56 57 private final CellularNetworkSecuritySafetySource mSafetySource; 58 private final HashMap<Integer, SubscriptionState> mSubscriptionState = new HashMap<>(); 59 private final HashMap<Integer, Integer> mActiveSubscriptions = new HashMap<>(); 60 61 private final Object mEnabledLock = new Object(); 62 @GuardedBy("mEnabledLock") 63 private boolean mEnabled = false; 64 65 // This is a single threaded executor. This is important because we want to ensure certain 66 // events are strictly serialized. 67 private ScheduledExecutorService mSerializedWorkQueue; 68 69 /** 70 * Gets a singleton NullCipherNotifier. 71 */ getInstance( CellularNetworkSecuritySafetySource safetySource)72 public static synchronized NullCipherNotifier getInstance( 73 CellularNetworkSecuritySafetySource safetySource) { 74 if (sInstance == null) { 75 sInstance = new NullCipherNotifier( 76 Executors.newSingleThreadScheduledExecutor(), 77 safetySource); 78 } 79 return sInstance; 80 } 81 82 @VisibleForTesting NullCipherNotifier( ScheduledExecutorService notificationQueue, CellularNetworkSecuritySafetySource safetySource)83 public NullCipherNotifier( 84 ScheduledExecutorService notificationQueue, 85 CellularNetworkSecuritySafetySource safetySource) { 86 mSerializedWorkQueue = notificationQueue; 87 mSafetySource = safetySource; 88 } 89 90 /** 91 * Adds a security algorithm update. If appropriate, this will trigger a user notification. 92 */ onSecurityAlgorithmUpdate( Context context, int phoneId, int subId, SecurityAlgorithmUpdate update)93 public void onSecurityAlgorithmUpdate( 94 Context context, int phoneId, int subId, SecurityAlgorithmUpdate update) { 95 Rlog.d(TAG, "Security algorithm update: subId = " + subId + " " + update); 96 97 if (shouldIgnoreUpdate(update)) { 98 return; 99 } 100 101 if (!isEnabled()) { 102 Rlog.i(TAG, "Ignoring onSecurityAlgorithmUpdate. Notifier is disabled."); 103 return; 104 } 105 106 try { 107 mSerializedWorkQueue.execute(() -> { 108 try { 109 maybeUpdateSubscriptionMapping(context, phoneId, subId); 110 SubscriptionState subState = mSubscriptionState.get(subId); 111 if (subState == null) { 112 subState = new SubscriptionState(); 113 mSubscriptionState.put(subId, subState); 114 } 115 116 @CellularNetworkSecuritySafetySource.NullCipherState int nullCipherState = 117 subState.update(update); 118 mSafetySource.setNullCipherState(context, subId, nullCipherState); 119 } catch (Throwable t) { 120 Rlog.e(TAG, "Failed to execute onSecurityAlgorithmUpdate " + t.getMessage()); 121 } 122 }); 123 } catch (RejectedExecutionException e) { 124 Rlog.e(TAG, "Failed to schedule onSecurityAlgorithmUpdate: " + e.getMessage()); 125 } 126 } 127 128 /** 129 * Set or update the current phoneId to subId mapping. When a new subId is mapped to a phoneId, 130 * we update the safety source to clear state of the old subId. 131 */ setSubscriptionMapping(Context context, int phoneId, int subId)132 public void setSubscriptionMapping(Context context, int phoneId, int subId) { 133 134 if (!isEnabled()) { 135 Rlog.i(TAG, "Ignoring setSubscriptionMapping. Notifier is disabled."); 136 } 137 138 try { 139 mSerializedWorkQueue.execute(() -> { 140 try { 141 maybeUpdateSubscriptionMapping(context, phoneId, subId); 142 } catch (Throwable t) { 143 Rlog.e(TAG, "Failed to update subId mapping. phoneId: " + phoneId + " subId: " 144 + subId + ". " + t.getMessage()); 145 } 146 }); 147 148 } catch (RejectedExecutionException e) { 149 Rlog.e(TAG, "Failed to schedule setSubscriptionMapping: " + e.getMessage()); 150 } 151 } 152 maybeUpdateSubscriptionMapping(Context context, int phoneId, int subId)153 private void maybeUpdateSubscriptionMapping(Context context, int phoneId, int subId) { 154 final Integer oldSubId = mActiveSubscriptions.put(phoneId, subId); 155 if (oldSubId == null || oldSubId == subId) { 156 return; 157 } 158 159 // Our subId was updated for this phone, we should clear this subId's state. 160 mSubscriptionState.remove(oldSubId); 161 mSafetySource.clearNullCipherState(context, oldSubId); 162 } 163 164 165 /** 166 * Enables null cipher notification; {@code onSecurityAlgorithmUpdate} will start handling 167 * security algorithm updates and send notifications to the user when required. 168 */ enable(Context context)169 public void enable(Context context) { 170 synchronized (mEnabledLock) { 171 Rlog.d(TAG, "enabled"); 172 mEnabled = true; 173 scheduleOnEnabled(context, true); 174 } 175 } 176 177 /** 178 * Clear all internal state and prevent further notifications until re-enabled. This can be 179 * used in response to a user disabling the feature for null cipher notifications. If 180 * {@code onSecurityAlgorithmUpdate} is called while in a disabled state, security algorithm 181 * updates will be dropped. 182 */ disable(Context context)183 public void disable(Context context) { 184 synchronized (mEnabledLock) { 185 Rlog.d(TAG, "disabled"); 186 mEnabled = false; 187 scheduleOnEnabled(context, false); 188 } 189 } 190 191 /** Checks whether the null cipher notification is enabled. */ isEnabled()192 public boolean isEnabled() { 193 synchronized (mEnabledLock) { 194 return mEnabled; 195 } 196 } 197 scheduleOnEnabled(Context context, boolean enabled)198 private void scheduleOnEnabled(Context context, boolean enabled) { 199 try { 200 mSerializedWorkQueue.execute(() -> { 201 Rlog.i(TAG, "On enable notifier. Enable value: " + enabled); 202 mSafetySource.setNullCipherIssueEnabled(context, enabled); 203 }); 204 } catch (RejectedExecutionException e) { 205 Rlog.e(TAG, "Failed to schedule onEnableNotifier: " + e.getMessage()); 206 } 207 208 } 209 210 /** Returns whether the update should be dropped and the monitoring state left unchanged. */ shouldIgnoreUpdate(SecurityAlgorithmUpdate update)211 private static boolean shouldIgnoreUpdate(SecurityAlgorithmUpdate update) { 212 // Ignore emergencies. 213 if (update.isUnprotectedEmergency()) { 214 return true; 215 } 216 217 switch (update.getConnectionEvent()) { 218 // Ignore non-link layer protocols. Monitoring is only looking for data exposed 219 // over-the-air so only the link layer protocols are tracked. Higher-level protocols can 220 // protect data further into the network but that is out of scope. 221 case SecurityAlgorithmUpdate.CONNECTION_EVENT_VOLTE_SIP: 222 case SecurityAlgorithmUpdate.CONNECTION_EVENT_VOLTE_RTP: 223 case SecurityAlgorithmUpdate.CONNECTION_EVENT_VONR_SIP: 224 case SecurityAlgorithmUpdate.CONNECTION_EVENT_VONR_RTP: 225 // Ignore emergencies. 226 case SecurityAlgorithmUpdate.CONNECTION_EVENT_VOLTE_SIP_SOS: 227 case SecurityAlgorithmUpdate.CONNECTION_EVENT_VOLTE_RTP_SOS: 228 case SecurityAlgorithmUpdate.CONNECTION_EVENT_VONR_SIP_SOS: 229 case SecurityAlgorithmUpdate.CONNECTION_EVENT_VONR_RTP_SOS: 230 return true; 231 default: 232 return false; 233 } 234 } 235 236 /** 237 * Determines whether an algorithm does not attempt to implement any encryption and is therefore 238 * considered a null cipher. 239 * 240 * <p>Only the algorithms known to be null ciphers are classified as such. Explicitly unknown 241 * algorithms, or algorithms that are unknown by means of values added to newer HALs, are 242 * assumed not to be null ciphers. 243 */ isNullCipher(@ecurityAlgorithm int algorithm)244 private static boolean isNullCipher(@SecurityAlgorithm int algorithm) { 245 switch (algorithm) { 246 case SecurityAlgorithmUpdate.SECURITY_ALGORITHM_A50: 247 case SecurityAlgorithmUpdate.SECURITY_ALGORITHM_GEA0: 248 case SecurityAlgorithmUpdate.SECURITY_ALGORITHM_UEA0: 249 case SecurityAlgorithmUpdate.SECURITY_ALGORITHM_EEA0: 250 case SecurityAlgorithmUpdate.SECURITY_ALGORITHM_NEA0: 251 case SecurityAlgorithmUpdate.SECURITY_ALGORITHM_IMS_NULL: 252 case SecurityAlgorithmUpdate.SECURITY_ALGORITHM_SIP_NULL: 253 case SecurityAlgorithmUpdate.SECURITY_ALGORITHM_SRTP_NULL: 254 case SecurityAlgorithmUpdate.SECURITY_ALGORITHM_OTHER: 255 return true; 256 default: 257 return false; 258 } 259 } 260 261 /** The state of network connections for a subscription. */ 262 private static final class SubscriptionState { 263 private @NetworkClass int mActiveNetworkClass = NETWORK_CLASS_UNKNOWN; 264 private final HashMap<Integer, ConnectionState> mState = new HashMap<>(); 265 266 private @CellularNetworkSecuritySafetySource.NullCipherState int update(SecurityAlgorithmUpdate update)267 update(SecurityAlgorithmUpdate update) { 268 boolean fromNullCipherState = hasNullCipher(); 269 270 @NetworkClass int networkClass = getNetworkClass(update.getConnectionEvent()); 271 if (networkClass != mActiveNetworkClass || networkClass == NETWORK_CLASS_UNKNOWN) { 272 mState.clear(); 273 mActiveNetworkClass = networkClass; 274 } 275 276 ConnectionState fromState = 277 mState.getOrDefault(update.getConnectionEvent(), ConnectionState.UNKNOWN); 278 ConnectionState toState = new ConnectionState( 279 update.getEncryption() == SECURITY_ALGORITHM_UNKNOWN 280 ? fromState.getEncryption() 281 : update.getEncryption(), 282 update.getIntegrity() == SECURITY_ALGORITHM_UNKNOWN 283 ? fromState.getIntegrity() 284 : update.getIntegrity()); 285 mState.put(update.getConnectionEvent(), toState); 286 287 if (hasNullCipher()) { 288 return NULL_CIPHER_STATE_NOTIFY_NON_ENCRYPTED; 289 } 290 if (!fromNullCipherState || mActiveNetworkClass == NETWORK_CLASS_UNKNOWN) { 291 return NULL_CIPHER_STATE_ENCRYPTED; 292 } 293 return NULL_CIPHER_STATE_NOTIFY_ENCRYPTED; 294 } 295 hasNullCipher()296 private boolean hasNullCipher() { 297 return mState.values().stream().anyMatch(ConnectionState::hasNullCipher); 298 } 299 300 private static final int NETWORK_CLASS_UNKNOWN = 0; 301 private static final int NETWORK_CLASS_2G = 2; 302 private static final int NETWORK_CLASS_3G = 3; 303 private static final int NETWORK_CLASS_4G = 4; 304 private static final int NETWORK_CLASS_5G = 5; 305 306 @Retention(RetentionPolicy.SOURCE) 307 @IntDef(prefix = {"NETWORK_CLASS_"}, value = {NETWORK_CLASS_UNKNOWN, 308 NETWORK_CLASS_2G, NETWORK_CLASS_3G, NETWORK_CLASS_4G, 309 NETWORK_CLASS_5G}) 310 private @interface NetworkClass {} 311 getNetworkClass( @onnectionEvent int connectionEvent)312 private static @NetworkClass int getNetworkClass( 313 @ConnectionEvent int connectionEvent) { 314 switch (connectionEvent) { 315 case SecurityAlgorithmUpdate.CONNECTION_EVENT_CS_SIGNALLING_GSM: 316 case SecurityAlgorithmUpdate.CONNECTION_EVENT_PS_SIGNALLING_GPRS: 317 return NETWORK_CLASS_2G; 318 case SecurityAlgorithmUpdate.CONNECTION_EVENT_CS_SIGNALLING_3G: 319 case SecurityAlgorithmUpdate.CONNECTION_EVENT_PS_SIGNALLING_3G: 320 return NETWORK_CLASS_3G; 321 case SecurityAlgorithmUpdate.CONNECTION_EVENT_NAS_SIGNALLING_LTE: 322 case SecurityAlgorithmUpdate.CONNECTION_EVENT_AS_SIGNALLING_LTE: 323 return NETWORK_CLASS_4G; 324 case SecurityAlgorithmUpdate.CONNECTION_EVENT_NAS_SIGNALLING_5G: 325 case SecurityAlgorithmUpdate.CONNECTION_EVENT_AS_SIGNALLING_5G: 326 return NETWORK_CLASS_5G; 327 default: 328 return NETWORK_CLASS_UNKNOWN; 329 } 330 } 331 } 332 333 /** The state of security algorithms for a network connection. */ 334 private static final class ConnectionState { 335 private static final ConnectionState UNKNOWN = 336 new ConnectionState(SECURITY_ALGORITHM_UNKNOWN, SECURITY_ALGORITHM_UNKNOWN); 337 338 private final @SecurityAlgorithm int mEncryption; 339 private final @SecurityAlgorithm int mIntegrity; 340 ConnectionState( @ecurityAlgorithm int encryption, @SecurityAlgorithm int integrity)341 private ConnectionState( 342 @SecurityAlgorithm int encryption, @SecurityAlgorithm int integrity) { 343 mEncryption = encryption; 344 mIntegrity = integrity; 345 } 346 getEncryption()347 private @SecurityAlgorithm int getEncryption() { 348 return mEncryption; 349 } 350 getIntegrity()351 private @SecurityAlgorithm int getIntegrity() { 352 return mIntegrity; 353 } 354 hasNullCipher()355 private boolean hasNullCipher() { 356 return isNullCipher(mEncryption) || isNullCipher(mIntegrity); 357 } 358 }; 359 } 360