blob: 56f8c9c9bf1d56a15b85dbfe0b5c3691c3665e8b [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.net.eap;
import static com.android.internal.net.eap.EapAuthenticator.LOG;
import static com.android.internal.net.eap.statemachine.EapSimAkaMethodStateMachine.KEY_LEN;
import static com.android.internal.net.eap.statemachine.EapSimAkaMethodStateMachine.MASTER_KEY_LENGTH;
import android.annotation.Nullable;
import android.os.SystemClock;
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/** EapSimAkaIdentityTracker will manage information to support Re-authentication & Pseudonym. */
public class EapSimAkaIdentityTracker {
private static final String TAG = EapSimAkaIdentityTracker.class.getSimpleName();
/** Lifetime of Reauth Info */
private static final long REAUTH_INFO_LIFETIME_MILLIS = TimeUnit.HOURS.toMillis(12); // 12Hour
/** Quota of Reauth Info */
@VisibleForTesting
static final int MAX_NUMBER_OF_REAUTH_INFO = 20;
/**
* The LinkedHashMap to preserve credentials for re-authentication, with concatenation of reauth
* ID and permanent ID as the key and ReauthInfo as the value.
*
* <p>A map entry will be deleted either if the map size reaches the max quota and the
* entry is the oldest, or if the ReauthInfo of this entry is found expired when a new
* ReauthInfo is registered
*/
private static Map<String, ReauthInfo> sReauthInfoMap =
Collections.synchronizedMap(
new LinkedHashMap<String, ReauthInfo>() {
@Override
protected boolean removeEldestEntry(Map.Entry<String, ReauthInfo> eldest) {
LOG.d(
TAG,
"Reached MAX_NUMBER_OF_REAUTH_INFO("
+ MAX_NUMBER_OF_REAUTH_INFO
+ ") remove EldestEntry");
return size() > MAX_NUMBER_OF_REAUTH_INFO;
}
});
/** ReauthInfo stores information used for re-authentication */
public static class ReauthInfo {
private final int mReauthCount;
private final byte[] mMk;
private final byte[] mKeyEncr;
private final byte[] mKeyAut;
/**
* If system elapsed time is larger than mExpiryTimestampElapsedRealtime, ReauthInfo will be
* deleted.
*/
private final long mExpiryTimestampElapsedRealtime;
/**
* Constructor of ReauthInfo
*
* @param mk : Master key derived at Full auth
* @param kEncr : Encryption key derived at Full auth
* @param kAut : Authentication key derived at Full auth
*/
private ReauthInfo(int reauthCount, byte[] mk, byte[] kEncr, byte[] kAut) {
mReauthCount = reauthCount;
mMk = mk.clone();
mKeyEncr = kEncr.clone();
mKeyAut = kAut.clone();
mExpiryTimestampElapsedRealtime =
SystemClock.elapsedRealtime() + REAUTH_INFO_LIFETIME_MILLIS;
}
@VisibleForTesting
ReauthInfo(
int reauthCount,
byte[] mk,
byte[] kEncr,
byte[] kAut,
long elapsedExpiryTimeMillis) {
mReauthCount = reauthCount;
mMk = mk;
mKeyEncr = kEncr;
mKeyAut = kAut;
mExpiryTimestampElapsedRealtime =
SystemClock.elapsedRealtime() + elapsedExpiryTimeMillis;
}
/**
* Retrieves reauth count for re-authentication
*
* @return : reauth count
*/
public int getReauthCount() {
return mReauthCount;
}
/**
* Retrieves Master key for re-authentication
*
* @return : Master Key
*/
public byte[] getMk() {
return mMk;
}
/**
* Retrieves encryption key for re-authentication
*
* @return : encryption key
*/
public byte[] getKeyEncr() {
return mKeyEncr;
}
/**
* Retrieves authentication key for re-authentication
*
* @return : authentication key
*/
public byte[] getKeyAut() {
return mKeyAut;
}
/**
* Check expiration of this ReauthInfo
*
* @return : true(not expired)/false(expired)
*/
public boolean isValid() {
return SystemClock.elapsedRealtime() < mExpiryTimestampElapsedRealtime;
}
}
private static class EapSimAkaIdentityTrackerHolder {
static final EapSimAkaIdentityTracker INSTANCE = new EapSimAkaIdentityTracker();
}
/**
* Retrieves static EapSimAkaIdentityTracker instance
*
* @return static EapSimAkaIdentityTracker
*/
public static EapSimAkaIdentityTracker getInstance() {
return EapSimAkaIdentityTrackerHolder.INSTANCE;
}
/**
* Create ReauthInfo & add it to ReauthInfo Map
*
* @param reauthId : re-authentication ID
* @param permanentId : permanent ID
* @param count : re-authentication Count
* @param mk : master key derived from full-auth
* @param kEncr : encryption key derived from full-auth
* @param kAut : authentication derived from full-auth
*/
public void registerReauthCredentials(
String reauthId, String permanentId, int count, byte[] mk, byte[] kEncr, byte[] kAut) {
if (mk.length != MASTER_KEY_LENGTH || kEncr.length != KEY_LEN || kAut.length != KEY_LEN) {
throw new IllegalArgumentException("Invalid Full auth key len");
}
ReauthInfo reauthInfo = new ReauthInfo(count, mk, kEncr, kAut);
String key = reauthId + permanentId;
LOG.d(TAG, "registerReauthCredentials: key" + key + " reauth count:" + count);
LOG.d(TAG, " MK=" + LOG.pii(mk));
LOG.d(TAG, " K_encr=" + LOG.pii(kEncr));
LOG.d(TAG, " K_aut=" + LOG.pii(kAut));
sReauthInfoMap.put(key, reauthInfo);
garbageCollect();
}
/**
* Add ReauthInfo to ReauthInfo Map. Only test purpose.
*
* @param reauthInfo : reauth Info
*/
@VisibleForTesting
void addReauthInfo(String key, ReauthInfo reauthInfo) {
sReauthInfoMap.put(key, reauthInfo);
}
/**
* Retrieves ReauthInfo with reauthId & permanentId as a key
*
* @param reauthId : re-authId set by application
* @param permanentId : permanentId set by application
* @return ReauthInfo : mapped ReauthInfo, return null when there is no matched info.
*/
@Nullable
public ReauthInfo getReauthInfo(String reauthId, String permanentId) {
String key = reauthId + permanentId;
ReauthInfo reauthInfo = sReauthInfoMap.get(key);
if (reauthInfo == null) {
LOG.d(TAG, "getReauthInfo no reauthInfo for key:" + key);
}
return reauthInfo;
}
/**
* Delete ReauthInfo with reauthId & permanentId as a key
*
* @param reauthId : re-authId set by application
* @param permanentId : permanentId set by application
*/
public void deleteReauthInfo(String reauthId, String permanentId) {
String key = reauthId + permanentId;
LOG.d(TAG, "deleteReauthInfo for key:" + key);
sReauthInfoMap.remove(key);
}
/** Clean up the reauthInfoMap */
@VisibleForTesting
void garbageCollect() {
long elapsedTimeMillis = SystemClock.elapsedRealtime();
ArrayList<String> expiredKeys = new ArrayList<>();
Iterator<Map.Entry<String, ReauthInfo>> iter = sReauthInfoMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, ReauthInfo> entry = iter.next();
if (!entry.getValue().isValid()) {
iter.remove();
}
}
}
/**
* Retrieves the number of ReauthInfos stored in ReauthInfoMap. Only test purpose.
*
* @return int : Number of ReauthInfos
*/
@VisibleForTesting
int getNumberOfReauthInfo() {
return sReauthInfoMap.size();
}
/** Clear this ReauthInfoMap. Only test purpose. */
@VisibleForTesting
void clearReauthInfoMap() {
sReauthInfoMap.clear();
}
}