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 * Provides format to handle request/response SIM Authentication with GSM/3G security context. 32 * 33 * <p>Reference ETSI TS 131 102, Section 7.1.2.1 GSM/3G security context. 34 */ 35 class EapAkaSecurityContext { 36 private static final String TAG = "ServiceEntitlement"; 37 38 private static final byte RESPONSE_TAG_SUCCESS = (byte) 0xDB; 39 private static final byte RESPONSE_TAG_SYNC_FAILURE = (byte) 0xDC; 40 41 private boolean mValid; 42 43 // User response, populated on successful authentication 44 @Nullable private byte[] mRes; 45 // Cipher Key, populated on successful authentication 46 @Nullable private byte[] mCk; 47 // Integrity Key, populated on successful authentication 48 @Nullable private byte[] mIk; 49 // AUTS, populated on synchronization failure 50 @Nullable private byte[] mAuts; 51 EapAkaSecurityContext()52 private EapAkaSecurityContext() {} 53 54 /** 55 * Provide {@link EapAkaSecurityContext} from response data. 56 */ from(String response)57 public static EapAkaSecurityContext from(String response) 58 throws ServiceEntitlementException { 59 EapAkaSecurityContext securityContext = new EapAkaSecurityContext(); 60 securityContext.parseResponseData(response); 61 if (!securityContext.isValid()) { 62 throw new ServiceEntitlementException( 63 ERROR_ICC_AUTHENTICATION_NOT_AVAILABLE, 64 "Invalid SIM EAP-AKA authentication response!"); 65 } 66 return securityContext; 67 } 68 69 /** 70 * Parses SIM EAP-AKA Authentication responded data. 71 */ parseResponseData(String response)72 private void parseResponseData(String response) { 73 byte[] data = null; 74 75 try { 76 data = Base64.decode(response.getBytes(UTF_8), Base64.DEFAULT); 77 Log.d(TAG, "Decoded response data length = " + data.length + " bytes"); 78 } catch (IllegalArgumentException e) { 79 Log.e(TAG, "Response is not a valid base-64 content"); 80 return; 81 } 82 if (data.length == 0) { 83 return; 84 } 85 86 // Check tag, the initial byte 87 int index = 0; 88 if (data[index] == RESPONSE_TAG_SUCCESS) { 89 // Parse RES 90 index++; // move to RES length byte 91 mRes = parseTag(index, data); 92 if (mRes == null) { 93 Log.d(TAG, "Invalid data: can't parse RES!"); 94 return; 95 } 96 // Parse CK 97 index += mRes.length + 1; // move to CK length byte 98 mCk = parseTag(index, data); 99 if (mCk == null) { 100 Log.d(TAG, "Invalid data: can't parse CK!"); 101 return; 102 } 103 // Parse IK 104 index += mCk.length + 1; // move to IK length byte 105 mIk = parseTag(index, data); 106 if (mIk == null) { 107 Log.d(TAG, "Invalid data: can't parse IK!"); 108 return; 109 } 110 mValid = true; 111 } else if (data[index] == RESPONSE_TAG_SYNC_FAILURE) { 112 // Parse AUTS 113 index++; // move to AUTS length byte 114 mAuts = parseTag(index, data); 115 if (mAuts == null) { 116 Log.d(TAG, "Invalid data: can't parse AUTS!"); 117 return; 118 } 119 mValid = true; 120 } else { 121 Log.d(TAG, "Not a valid tag, tag=" + data[index]); 122 return; 123 } 124 } 125 126 @Nullable parseTag(int index, byte[] src)127 private byte[] parseTag(int index, byte[] src) { 128 // index at the length byte 129 if (index >= src.length) { 130 Log.d(TAG, "No length byte!"); 131 return null; 132 } 133 int length = src[index] & 0xff; 134 if (index + length >= src.length) { 135 Log.d(TAG, "Invalid data length!"); 136 return null; 137 } 138 index++; // move to first byte of tag value 139 byte[] dest = new byte[length]; 140 System.arraycopy(src, index, dest, 0, length); 141 142 return dest; 143 } 144 isValid()145 private boolean isValid() { 146 return mValid; 147 } 148 149 /** 150 * Returns RES, or {@code null} for a synchronization failure. 151 */ 152 @Nullable getRes()153 public byte[] getRes() { 154 return mRes; 155 } 156 157 /** 158 * Returns CK, or {@code null} for a synchronization failure. 159 */ 160 @Nullable getCk()161 public byte[] getCk() { 162 return mCk; 163 } 164 165 /** 166 * Returns IK, or {@code null} for a synchronization failure. 167 */ 168 @Nullable getIk()169 public byte[] getIk() { 170 return mIk; 171 } 172 173 /** 174 * Returns AUTS, or {@code null} for a successful authentication. 175 */ 176 @Nullable getAuts()177 public byte[] getAuts() { 178 return mAuts; 179 } 180 } 181