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.libraries.entitlement.eapaka; 18 19 import static com.android.libraries.entitlement.ServiceEntitlementException.ERROR_ICC_AUTHENTICATION_NOT_AVAILABLE; 20 21 import static java.nio.charset.StandardCharsets.UTF_8; 22 23 import android.util.Base64; 24 import android.util.Log; 25 26 import androidx.annotation.Nullable; 27 28 import com.android.libraries.entitlement.ServiceEntitlementException; 29 30 /** 31 * Parses EAP-AKA challenge from server. Refer to RFC 4187 Section 8.1 Message 32 * Format/RFC 3748 Session 4 EAP Packet Format. 33 */ 34 public class EapAkaChallenge { 35 private static final String TAG = "ServiceEntitlement"; 36 37 private static final int EAP_AKA_HEADER_LENGTH = 8; 38 private static final byte CODE_REQUEST = 0x01; 39 static final byte TYPE_EAP_AKA = 0x17; 40 static final byte SUBTYPE_AKA_CHALLENGE = 0x01; 41 private static final byte ATTRIBUTE_RAND = 0x01; 42 private static final byte ATTRIBUTE_AUTN = 0x02; 43 private static final int ATTRIBUTE_LENGTH = 20; 44 private static final int RAND_LENGTH = 16; 45 private static final int AUTN_LENGTH = 16; 46 47 // The identifier of Response must same as Request 48 private byte mIdentifier = -1; 49 // The value of AT_AUTN, network authentication token 50 private byte[] mAutn; 51 // The value of AT_RAND, random challenge 52 private byte[] mRand; 53 54 // Base64 encoded 3G security context for SIM Authentication request 55 private String mSimAuthenticationRequest; 56 EapAkaChallenge()57 private EapAkaChallenge() {} 58 59 /** Parses a EAP-AKA challenge request message encoded in base64. */ parseEapAkaChallenge(String challenge)60 public static EapAkaChallenge parseEapAkaChallenge(String challenge) 61 throws ServiceEntitlementException { 62 byte[] data; 63 try { 64 data = Base64.decode(challenge.getBytes(UTF_8), Base64.DEFAULT); 65 } catch (IllegalArgumentException illegalArgumentException) { 66 throw new ServiceEntitlementException( 67 ERROR_ICC_AUTHENTICATION_NOT_AVAILABLE, 68 "EAP-AKA challenge is not a valid base64!"); 69 } 70 EapAkaChallenge result = new EapAkaChallenge(); 71 if (result.parseEapAkaHeader(data) && result.parseRandAndAutn(data)) { 72 result.mSimAuthenticationRequest = result.getSimAuthChallengeData(); 73 return result; 74 } else { 75 throw new ServiceEntitlementException( 76 ERROR_ICC_AUTHENTICATION_NOT_AVAILABLE, 77 "EAP-AKA challenge message is not valid"); 78 } 79 } 80 81 /** 82 * Returns the base64 encoded 3G security context for SIM Authentication request, 83 * or {@code null} if the EAP-AKA challenge is not valid. 84 */ 85 @Nullable getSimAuthenticationRequest()86 public String getSimAuthenticationRequest() { 87 return mSimAuthenticationRequest; 88 } 89 90 /** Returns the EAP package identifier in the EAP-AKA challenge. */ getIdentifier()91 public byte getIdentifier() { 92 return mIdentifier; 93 } 94 95 /** 96 * Parses EAP-AKA header, 8 bytes including 2 reserved bytes. 97 * 98 * @return {@code true} if success to parse the header of request data. 99 */ parseEapAkaHeader(byte[] data)100 private boolean parseEapAkaHeader(byte[] data) { 101 if (data.length < EAP_AKA_HEADER_LENGTH) { 102 return false; 103 } 104 // Code for EAP-request should be CODE_REQUEST 105 byte code = data[0]; 106 // EAP package identifier 107 mIdentifier = data[1]; 108 // Total length of full EAP-AKA message, include code, identifier, ... 109 int length = ((data[2] & 0xff) << 8) | (data[3] & 0xff); 110 // Type for EAP-AKA should be TYPE_EAP_AKA 111 byte type = data[4]; 112 // SubType for AKA-Challenge should be SUBTYPE_AKA_CHALLENGE 113 byte subType = data[5]; 114 115 // Validate header 116 if (code != CODE_REQUEST 117 || length != data.length 118 || type != TYPE_EAP_AKA 119 || subType != SUBTYPE_AKA_CHALLENGE) { 120 Log.d( 121 TAG, 122 "Invalid EAP-AKA Header, code=" 123 + code 124 + ", length=" 125 + length 126 + ", real length=" 127 + data.length 128 + ", type=" 129 + type 130 + ", subType=" 131 + subType); 132 return false; 133 } 134 135 return true; 136 } 137 138 /** 139 * Parses AT_RAND and AT_AUTN. Refer to RFC 4187 section 10.6 AT_RAND/section 10.7 AT_AUTN. 140 * 141 * @return {@code true} if success to parse the RAND and AUTN data. 142 */ parseRandAndAutn(byte[] data)143 private boolean parseRandAndAutn(byte[] data) { 144 int index = EAP_AKA_HEADER_LENGTH; 145 while (index < data.length) { 146 int remainsLength = data.length - index; 147 if (remainsLength <= 2) { 148 Log.d(TAG, "Error! remainsLength = " + remainsLength); 149 return false; 150 } 151 152 byte attributeType = data[index]; 153 154 // the length of this attribute in multiples of 4 bytes, include attribute type and 155 // length 156 int length = (data[index + 1] & 0xff) * 4; 157 if (length > remainsLength) { 158 Log.d(TAG, 159 "Length Error! length is " + length + " but only remains " + remainsLength); 160 return false; 161 } 162 163 // see RFC 4187 section 11 for attribute type 164 if (attributeType == ATTRIBUTE_RAND) { 165 if (length != ATTRIBUTE_LENGTH) { 166 Log.d(TAG, "AT_RAND length is " + length); 167 return false; 168 } 169 mRand = new byte[RAND_LENGTH]; 170 System.arraycopy(data, index + 4, mRand, 0, RAND_LENGTH); 171 } else if (attributeType == ATTRIBUTE_AUTN) { 172 if (length != ATTRIBUTE_LENGTH) { 173 Log.d(TAG, "AT_AUTN length is " + length); 174 return false; 175 } 176 mAutn = new byte[AUTN_LENGTH]; 177 System.arraycopy(data, index + 4, mAutn, 0, AUTN_LENGTH); 178 } 179 180 index += length; 181 } 182 183 // check has AT_RAND and AT_AUTH 184 if (mRand == null || mAutn == null) { 185 Log.d(TAG, "Invalid Type Datas!"); 186 return false; 187 } 188 189 return true; 190 } 191 192 /** 193 * Returns Base64 encoded 3G security context for SIM Authentication request. 194 */ 195 @Nullable getSimAuthChallengeData()196 private String getSimAuthChallengeData() { 197 byte[] challengeData = new byte[RAND_LENGTH + AUTN_LENGTH + 2]; 198 challengeData[0] = (byte) RAND_LENGTH; 199 System.arraycopy(mRand, 0, challengeData, 1, RAND_LENGTH); 200 challengeData[RAND_LENGTH + 1] = (byte) AUTN_LENGTH; 201 System.arraycopy(mAutn, 0, challengeData, RAND_LENGTH + 2, AUTN_LENGTH); 202 203 return Base64.encodeToString(challengeData, Base64.NO_WRAP).trim(); 204 } 205 } 206