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 private byte[] mRes; 45 // Cipher Key, populated on successful authentication 46 private byte[] mCk; 47 // Integrity Key, populated on successful authentication 48 private byte[] mIk; 49 // AUTS, populated on synchronization failure 50 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 parseTag(int index, byte[] src)126 private byte[] parseTag(int index, byte[] src) { 127 // index at the length byte 128 if (index >= src.length) { 129 Log.d(TAG, "No length byte!"); 130 return null; 131 } 132 int length = src[index] & 0xff; 133 if (index + length >= src.length) { 134 Log.d(TAG, "Invalid data length!"); 135 return null; 136 } 137 index++; // move to first byte of tag value 138 byte[] dest = new byte[length]; 139 System.arraycopy(src, index, dest, 0, length); 140 141 return dest; 142 } 143 isValid()144 private boolean isValid() { 145 return mValid; 146 } 147 148 /** 149 * Returns RES, or {@code null} for a synchronization failure. 150 */ 151 @Nullable getRes()152 public byte[] getRes() { 153 return mRes; 154 } 155 156 /** 157 * Returns CK, or {@code null} for a synchronization failure. 158 */ 159 @Nullable getCk()160 public byte[] getCk() { 161 return mCk; 162 } 163 164 /** 165 * Returns IK, or {@code null} for a synchronization failure. 166 */ 167 @Nullable getIk()168 public byte[] getIk() { 169 return mIk; 170 } 171 172 /** 173 * Returns AUTS, or {@code null} for a successful authentication. 174 */ 175 @Nullable getAuts()176 public byte[] getAuts() { 177 return mAuts; 178 } 179 } 180