1 /* 2 * Copyright (C) 2021 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.net.eap; 18 19 import static com.android.internal.net.eap.EapAuthenticator.LOG; 20 import static com.android.internal.net.eap.statemachine.EapSimAkaMethodStateMachine.KEY_LEN; 21 import static com.android.internal.net.eap.statemachine.EapSimAkaMethodStateMachine.MASTER_KEY_LENGTH; 22 23 import android.annotation.Nullable; 24 import android.os.SystemClock; 25 26 import com.android.internal.annotations.VisibleForTesting; 27 28 import java.util.ArrayList; 29 import java.util.Collections; 30 import java.util.Iterator; 31 import java.util.LinkedHashMap; 32 import java.util.Map; 33 import java.util.concurrent.TimeUnit; 34 35 /** EapSimAkaIdentityTracker will manage information to support Re-authentication & Pseudonym. */ 36 public class EapSimAkaIdentityTracker { 37 private static final String TAG = EapSimAkaIdentityTracker.class.getSimpleName(); 38 /** Lifetime of Reauth Info */ 39 private static final long REAUTH_INFO_LIFETIME_MILLIS = TimeUnit.HOURS.toMillis(12); // 12Hour 40 /** Quota of Reauth Info */ 41 @VisibleForTesting 42 static final int MAX_NUMBER_OF_REAUTH_INFO = 20; 43 44 /** 45 * The LinkedHashMap to preserve credentials for re-authentication, with concatenation of reauth 46 * ID and permanent ID as the key and ReauthInfo as the value. 47 * 48 * <p>A map entry will be deleted either if the map size reaches the max quota and the 49 * entry is the oldest, or if the ReauthInfo of this entry is found expired when a new 50 * ReauthInfo is registered 51 */ 52 private static Map<String, ReauthInfo> sReauthInfoMap = 53 Collections.synchronizedMap( 54 new LinkedHashMap<String, ReauthInfo>() { 55 @Override 56 protected boolean removeEldestEntry(Map.Entry<String, ReauthInfo> eldest) { 57 LOG.d( 58 TAG, 59 "Reached MAX_NUMBER_OF_REAUTH_INFO(" 60 + MAX_NUMBER_OF_REAUTH_INFO 61 + ") remove EldestEntry"); 62 return size() > MAX_NUMBER_OF_REAUTH_INFO; 63 } 64 }); 65 66 /** ReauthInfo stores information used for re-authentication */ 67 public static class ReauthInfo { 68 private final int mReauthCount; 69 private final byte[] mMk; 70 private final byte[] mKeyEncr; 71 private final byte[] mKeyAut; 72 73 /** 74 * If system elapsed time is larger than mExpiryTimestampElapsedRealtime, ReauthInfo will be 75 * deleted. 76 */ 77 private final long mExpiryTimestampElapsedRealtime; 78 79 /** 80 * Constructor of ReauthInfo 81 * 82 * @param mk : Master key derived at Full auth 83 * @param kEncr : Encryption key derived at Full auth 84 * @param kAut : Authentication key derived at Full auth 85 */ ReauthInfo(int reauthCount, byte[] mk, byte[] kEncr, byte[] kAut)86 private ReauthInfo(int reauthCount, byte[] mk, byte[] kEncr, byte[] kAut) { 87 mReauthCount = reauthCount; 88 mMk = mk.clone(); 89 mKeyEncr = kEncr.clone(); 90 mKeyAut = kAut.clone(); 91 mExpiryTimestampElapsedRealtime = 92 SystemClock.elapsedRealtime() + REAUTH_INFO_LIFETIME_MILLIS; 93 } 94 95 @VisibleForTesting ReauthInfo( int reauthCount, byte[] mk, byte[] kEncr, byte[] kAut, long elapsedExpiryTimeMillis)96 ReauthInfo( 97 int reauthCount, 98 byte[] mk, 99 byte[] kEncr, 100 byte[] kAut, 101 long elapsedExpiryTimeMillis) { 102 mReauthCount = reauthCount; 103 mMk = mk; 104 mKeyEncr = kEncr; 105 mKeyAut = kAut; 106 mExpiryTimestampElapsedRealtime = 107 SystemClock.elapsedRealtime() + elapsedExpiryTimeMillis; 108 } 109 110 /** 111 * Retrieves reauth count for re-authentication 112 * 113 * @return : reauth count 114 */ getReauthCount()115 public int getReauthCount() { 116 return mReauthCount; 117 } 118 119 /** 120 * Retrieves Master key for re-authentication 121 * 122 * @return : Master Key 123 */ getMk()124 public byte[] getMk() { 125 return mMk; 126 } 127 128 /** 129 * Retrieves encryption key for re-authentication 130 * 131 * @return : encryption key 132 */ getKeyEncr()133 public byte[] getKeyEncr() { 134 return mKeyEncr; 135 } 136 137 /** 138 * Retrieves authentication key for re-authentication 139 * 140 * @return : authentication key 141 */ getKeyAut()142 public byte[] getKeyAut() { 143 return mKeyAut; 144 } 145 146 /** 147 * Check expiration of this ReauthInfo 148 * 149 * @return : true(not expired)/false(expired) 150 */ isValid()151 public boolean isValid() { 152 return SystemClock.elapsedRealtime() < mExpiryTimestampElapsedRealtime; 153 } 154 } 155 156 private static class EapSimAkaIdentityTrackerHolder { 157 static final EapSimAkaIdentityTracker INSTANCE = new EapSimAkaIdentityTracker(); 158 } 159 160 /** 161 * Retrieves static EapSimAkaIdentityTracker instance 162 * 163 * @return static EapSimAkaIdentityTracker 164 */ getInstance()165 public static EapSimAkaIdentityTracker getInstance() { 166 return EapSimAkaIdentityTrackerHolder.INSTANCE; 167 } 168 169 /** 170 * Create ReauthInfo & add it to ReauthInfo Map 171 * 172 * @param reauthId : re-authentication ID 173 * @param permanentId : permanent ID 174 * @param count : re-authentication Count 175 * @param mk : master key derived from full-auth 176 * @param kEncr : encryption key derived from full-auth 177 * @param kAut : authentication derived from full-auth 178 */ registerReauthCredentials( String reauthId, String permanentId, int count, byte[] mk, byte[] kEncr, byte[] kAut)179 public void registerReauthCredentials( 180 String reauthId, String permanentId, int count, byte[] mk, byte[] kEncr, byte[] kAut) { 181 if (mk.length != MASTER_KEY_LENGTH || kEncr.length != KEY_LEN || kAut.length != KEY_LEN) { 182 throw new IllegalArgumentException("Invalid Full auth key len"); 183 } 184 ReauthInfo reauthInfo = new ReauthInfo(count, mk, kEncr, kAut); 185 String key = reauthId + permanentId; 186 LOG.d(TAG, "registerReauthCredentials: key" + key + " reauth count:" + count); 187 LOG.d(TAG, " MK=" + LOG.pii(mk)); 188 LOG.d(TAG, " K_encr=" + LOG.pii(kEncr)); 189 LOG.d(TAG, " K_aut=" + LOG.pii(kAut)); 190 sReauthInfoMap.put(key, reauthInfo); 191 garbageCollect(); 192 } 193 194 /** 195 * Add ReauthInfo to ReauthInfo Map. Only test purpose. 196 * 197 * @param reauthInfo : reauth Info 198 */ 199 @VisibleForTesting addReauthInfo(String key, ReauthInfo reauthInfo)200 void addReauthInfo(String key, ReauthInfo reauthInfo) { 201 sReauthInfoMap.put(key, reauthInfo); 202 } 203 204 /** 205 * Retrieves ReauthInfo with reauthId & permanentId as a key 206 * 207 * @param reauthId : re-authId set by application 208 * @param permanentId : permanentId set by application 209 * @return ReauthInfo : mapped ReauthInfo, return null when there is no matched info. 210 */ 211 @Nullable getReauthInfo(String reauthId, String permanentId)212 public ReauthInfo getReauthInfo(String reauthId, String permanentId) { 213 String key = reauthId + permanentId; 214 ReauthInfo reauthInfo = sReauthInfoMap.get(key); 215 if (reauthInfo == null) { 216 LOG.d(TAG, "getReauthInfo no reauthInfo for key:" + key); 217 } 218 return reauthInfo; 219 } 220 221 /** 222 * Delete ReauthInfo with reauthId & permanentId as a key 223 * 224 * @param reauthId : re-authId set by application 225 * @param permanentId : permanentId set by application 226 */ deleteReauthInfo(String reauthId, String permanentId)227 public void deleteReauthInfo(String reauthId, String permanentId) { 228 String key = reauthId + permanentId; 229 LOG.d(TAG, "deleteReauthInfo for key:" + key); 230 sReauthInfoMap.remove(key); 231 } 232 233 /** Clean up the reauthInfoMap */ 234 @VisibleForTesting garbageCollect()235 void garbageCollect() { 236 long elapsedTimeMillis = SystemClock.elapsedRealtime(); 237 ArrayList<String> expiredKeys = new ArrayList<>(); 238 239 Iterator<Map.Entry<String, ReauthInfo>> iter = sReauthInfoMap.entrySet().iterator(); 240 while (iter.hasNext()) { 241 Map.Entry<String, ReauthInfo> entry = iter.next(); 242 if (!entry.getValue().isValid()) { 243 iter.remove(); 244 } 245 } 246 } 247 248 /** 249 * Retrieves the number of ReauthInfos stored in ReauthInfoMap. Only test purpose. 250 * 251 * @return int : Number of ReauthInfos 252 */ 253 @VisibleForTesting getNumberOfReauthInfo()254 int getNumberOfReauthInfo() { 255 return sReauthInfoMap.size(); 256 } 257 258 /** Clear this ReauthInfoMap. Only test purpose. */ 259 @VisibleForTesting clearReauthInfoMap()260 void clearReauthInfoMap() { 261 sReauthInfoMap.clear(); 262 } 263 } 264