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 package com.android.server.wifi.entitlement; 17 18 import static java.nio.charset.StandardCharsets.UTF_8; 19 20 import android.os.Build; 21 import android.telephony.TelephonyManager; 22 import android.text.TextUtils; 23 import android.util.Base64; 24 25 import androidx.annotation.Nullable; 26 27 import com.android.internal.annotations.VisibleForTesting; 28 29 import org.json.JSONArray; 30 import org.json.JSONException; 31 import org.json.JSONObject; 32 33 /** Creates the request to do authentication and query the entitlement status. */ 34 public class RequestFactory { 35 private static final int IMEI_LENGTH = 14; 36 37 @VisibleForTesting 38 static final String METHOD_3GPP_AUTHENTICATION = "3gppAuthentication"; 39 @VisibleForTesting 40 static final String METHOD_GET_IMSI_PSEUDONYM = "getImsiPseudonym"; 41 @VisibleForTesting 42 static final String JSON_KEY_MESSAGE_ID = "message-id"; 43 @VisibleForTesting 44 static final String JSON_KEY_METHOD = "method"; 45 @VisibleForTesting 46 static final String JSON_KEY_DEVICE_ID = "device-id"; 47 @VisibleForTesting 48 static final String JSON_KEY_DEVICE_TYPE = "device-type"; 49 @VisibleForTesting 50 static final String JSON_KEY_OS_TYPE = "os-type"; 51 @VisibleForTesting 52 static final String JSON_KEY_DEVICE_NAME = "device-name"; 53 @VisibleForTesting 54 static final String JSON_KEY_IMSI_EAP = "imsi-eap"; 55 @VisibleForTesting 56 static final String JSON_KEY_AKA_TOKEN = "aka-token"; 57 @VisibleForTesting 58 static final String JSON_KEY_AKA_CHALLENGE_RSP = "aka-challenge-rsp"; 59 60 @VisibleForTesting 61 static final int DEVICE_TYPE_SIM = 0; 62 @VisibleForTesting 63 static final int OS_TYPE_ANDROID = 0; 64 65 public static final int MESSAGE_ID_3GPP_AUTHENTICATION = 1; 66 public static final int MESSAGE_ID_GET_IMSI_PSEUDONYM = 2; 67 68 private final TelephonyManager mTelephonyManager; 69 RequestFactory(TelephonyManager telephonyManager)70 public RequestFactory(TelephonyManager telephonyManager) { 71 mTelephonyManager = telephonyManager; 72 } 73 74 /** 75 * Creates {@link JSONArray} object with method {@code METHOD_3GPP_AUTHENTICATION} to get 76 * challenge response data. 77 */ createAuthRequest()78 public JSONArray createAuthRequest() throws TransientException { 79 JSONArray requests = new JSONArray(); 80 try { 81 requests.put(makeAuthenticationRequest(null /*akaToken*/, null /*challengeResponse*/)); 82 } catch (JSONException e) { 83 throw new TransientException("createAuthRequest failed!" + e); 84 } 85 return requests; 86 } 87 88 /** 89 * Creates {link JSONArray} object with method {@code METHOD_SERVICE_ENTITLEMENT_STATUS} to 90 * query entitlement status. 91 */ createGetImsiPseudonymRequest(String akaToken, String challengeResponse)92 public JSONArray createGetImsiPseudonymRequest(String akaToken, String challengeResponse) 93 throws TransientException { 94 JSONArray requests = new JSONArray(); 95 try { 96 requests.put(makeAuthenticationRequest(akaToken, challengeResponse)); 97 requests.put(makeGetImsiPseudonymRequest()); 98 } catch (JSONException e) { 99 throw new TransientException("createGetImsiPseudonymRequest failed!" + e); 100 } 101 return requests; 102 } 103 makeBaseRequest(int messageId, String method)104 private JSONObject makeBaseRequest(int messageId, String method) throws JSONException { 105 JSONObject request = new JSONObject(); 106 request.put(JSON_KEY_MESSAGE_ID, messageId); 107 request.put(JSON_KEY_METHOD, method); 108 return request; 109 } 110 111 @Nullable getImeiSv()112 private String getImeiSv() { 113 String imeiValue = mTelephonyManager.getImei(); 114 String svnValue = mTelephonyManager.getDeviceSoftwareVersion(); 115 if (TextUtils.isEmpty(imeiValue) || TextUtils.isEmpty(svnValue)) { 116 return null; 117 } 118 if (imeiValue.length() > IMEI_LENGTH) { 119 imeiValue = imeiValue.substring(0, IMEI_LENGTH); 120 } 121 String value = imeiValue + svnValue; // 14 digits + 2 digits 122 return Base64.encodeToString(value.getBytes(UTF_8), Base64.NO_WRAP).trim(); 123 } 124 makeAuthenticationRequest(String akaToken, String challengeResponse)125 private JSONObject makeAuthenticationRequest(String akaToken, String challengeResponse) 126 throws JSONException, TransientException { 127 JSONObject request = 128 makeBaseRequest(MESSAGE_ID_3GPP_AUTHENTICATION, METHOD_3GPP_AUTHENTICATION); 129 String imeiSv = getImeiSv(); 130 if (imeiSv == null) { 131 // device-id(base64 encodede IMEISV) is mandatory. 132 throw new TransientException("IMEISV is null."); 133 } 134 request.put(JSON_KEY_DEVICE_ID, imeiSv); 135 request.put(JSON_KEY_DEVICE_TYPE, DEVICE_TYPE_SIM); 136 request.put(JSON_KEY_OS_TYPE, OS_TYPE_ANDROID); 137 request.put(JSON_KEY_DEVICE_NAME, Build.MODEL); 138 request.put(JSON_KEY_IMSI_EAP, getImsiEap()); 139 if (!TextUtils.isEmpty(akaToken)) { 140 request.put(JSON_KEY_AKA_TOKEN, akaToken); 141 } 142 if (!TextUtils.isEmpty(challengeResponse)) { 143 request.put(JSON_KEY_AKA_CHALLENGE_RSP, challengeResponse); 144 } 145 return request; 146 } 147 148 @Nullable getImsiEap()149 private String getImsiEap() { 150 String imsi = mTelephonyManager.getSubscriberId(); 151 String mccmnc = mTelephonyManager.getSimOperator(); // MCCMNC is 5 or 6 decimal digits 152 if ((imsi == null) || (mccmnc == null) || (mccmnc.length() < 5)) { 153 return null; 154 } 155 String mcc = mccmnc.substring(0, 3); 156 String mnc = mccmnc.substring(3); 157 return String.format("0%s@nai.epc.mnc%s.mcc%s.3gppnetwork.org", imsi, mnc, mcc); 158 } 159 makeGetImsiPseudonymRequest()160 private JSONObject makeGetImsiPseudonymRequest() throws JSONException { 161 return makeBaseRequest(MESSAGE_ID_GET_IMSI_PSEUDONYM, METHOD_GET_IMSI_PSEUDONYM); 162 } 163 } 164 165