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