1 /** 2 * Copyright (c) 2017, 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 android.net.wifi.hotspot2.pps; 18 19 import android.net.wifi.ParcelUtil; 20 import android.os.Parcel; 21 import android.os.Parcelable; 22 import android.text.TextUtils; 23 import android.util.Base64; 24 import android.util.Log; 25 26 import java.nio.charset.StandardCharsets; 27 import java.security.cert.X509Certificate; 28 import java.util.Arrays; 29 import java.util.Objects; 30 31 /** 32 * Class representing configuration parameters for subscription or policy update in 33 * PerProviderSubscription (PPS) Management Object (MO) tree. This is used by both 34 * PerProviderSubscription/Policy/PolicyUpdate and PerProviderSubscription/SubscriptionUpdate 35 * subtree. 36 * 37 * For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0 38 * Release 2 Technical Specification. 39 * 40 * @hide 41 */ 42 public final class UpdateParameter implements Parcelable { 43 private static final String TAG = "UpdateParameter"; 44 45 /** 46 * Value indicating policy update is not applicable. Thus, never check with policy server 47 * for updates. 48 */ 49 public static final long UPDATE_CHECK_INTERVAL_NEVER = 0xFFFFFFFFL; 50 51 /** 52 * Valid string for UpdateMethod. 53 */ 54 public static final String UPDATE_METHOD_OMADM = "OMA-DM-ClientInitiated"; 55 public static final String UPDATE_METHOD_SSP = "SSP-ClientInitiated"; 56 57 /** 58 * Valid string for Restriction. 59 */ 60 public static final String UPDATE_RESTRICTION_HOMESP = "HomeSP"; 61 public static final String UPDATE_RESTRICTION_ROAMING_PARTNER = "RoamingPartner"; 62 public static final String UPDATE_RESTRICTION_UNRESTRICTED = "Unrestricted"; 63 64 /** 65 * Maximum bytes for URI string. 66 */ 67 private static final int MAX_URI_BYTES = 1023; 68 69 /** 70 * Maximum bytes for URI string. 71 */ 72 private static final int MAX_URL_BYTES = 1023; 73 74 /** 75 * Maximum bytes for username. 76 */ 77 private static final int MAX_USERNAME_BYTES = 63; 78 79 /** 80 * Maximum bytes for password. 81 */ 82 private static final int MAX_PASSWORD_BYTES = 255; 83 84 /** 85 * Number of bytes for certificate SHA-256 fingerprint byte array. 86 */ 87 private static final int CERTIFICATE_SHA256_BYTES = 32; 88 89 /** 90 * This specifies how often the mobile device shall check with policy server for updates. 91 * 92 * Using Long.MIN_VALUE to indicate unset value. 93 */ 94 private long mUpdateIntervalInMinutes = Long.MIN_VALUE; setUpdateIntervalInMinutes(long updateIntervalInMinutes)95 public void setUpdateIntervalInMinutes(long updateIntervalInMinutes) { 96 mUpdateIntervalInMinutes = updateIntervalInMinutes; 97 } getUpdateIntervalInMinutes()98 public long getUpdateIntervalInMinutes() { 99 return mUpdateIntervalInMinutes; 100 } 101 102 /** 103 * The method used to update the policy. Permitted values are "OMA-DM-ClientInitiated" 104 * and "SPP-ClientInitiated". 105 */ 106 private String mUpdateMethod = null; setUpdateMethod(String updateMethod)107 public void setUpdateMethod(String updateMethod) { 108 mUpdateMethod = updateMethod; 109 } getUpdateMethod()110 public String getUpdateMethod() { 111 return mUpdateMethod; 112 } 113 114 /** 115 * This specifies the hotspots at which the subscription update is permitted. Permitted 116 * values are "HomeSP", "RoamingPartner", or "Unrestricted"; 117 */ 118 private String mRestriction = null; setRestriction(String restriction)119 public void setRestriction(String restriction) { 120 mRestriction = restriction; 121 } getRestriction()122 public String getRestriction() { 123 return mRestriction; 124 } 125 126 /** 127 * The URI of the update server. 128 */ 129 private String mServerUri = null; setServerUri(String serverUri)130 public void setServerUri(String serverUri) { 131 mServerUri = serverUri; 132 } getServerUri()133 public String getServerUri() { 134 return mServerUri; 135 } 136 137 /** 138 * Username used to authenticate with the policy server. 139 */ 140 private String mUsername = null; setUsername(String username)141 public void setUsername(String username) { 142 mUsername = username; 143 } getUsername()144 public String getUsername() { 145 return mUsername; 146 } 147 148 /** 149 * Base64 encoded password used to authenticate with the policy server. 150 */ 151 private String mBase64EncodedPassword = null; setBase64EncodedPassword(String password)152 public void setBase64EncodedPassword(String password) { 153 mBase64EncodedPassword = password; 154 } getBase64EncodedPassword()155 public String getBase64EncodedPassword() { 156 return mBase64EncodedPassword; 157 } 158 159 /** 160 * HTTPS URL for retrieving certificate for trust root. The trust root is used to validate 161 * policy server's identity. 162 */ 163 private String mTrustRootCertUrl = null; setTrustRootCertUrl(String trustRootCertUrl)164 public void setTrustRootCertUrl(String trustRootCertUrl) { 165 mTrustRootCertUrl = trustRootCertUrl; 166 } getTrustRootCertUrl()167 public String getTrustRootCertUrl() { 168 return mTrustRootCertUrl; 169 } 170 171 /** 172 * SHA-256 fingerprint of the certificate located at {@code mTrustRootCertUrl} 173 */ 174 private byte[] mTrustRootCertSha256Fingerprint = null; setTrustRootCertSha256Fingerprint(byte[] fingerprint)175 public void setTrustRootCertSha256Fingerprint(byte[] fingerprint) { 176 mTrustRootCertSha256Fingerprint = fingerprint; 177 } getTrustRootCertSha256Fingerprint()178 public byte[] getTrustRootCertSha256Fingerprint() { 179 return mTrustRootCertSha256Fingerprint; 180 } 181 182 /** 183 * CA (Certificate Authority) X509 certificates. 184 */ 185 private X509Certificate mCaCertificate; 186 187 /** 188 * Set the CA (Certification Authority) certificate associated with Policy/Subscription update. 189 * 190 * @param caCertificate The CA certificate to set 191 * @hide 192 */ setCaCertificate(X509Certificate caCertificate)193 public void setCaCertificate(X509Certificate caCertificate) { 194 mCaCertificate = caCertificate; 195 } 196 197 /** 198 * Get the CA (Certification Authority) certificate associated with Policy/Subscription update. 199 * 200 * @return CA certificate associated and {@code null} if certificate is not set. 201 * @hide 202 */ getCaCertificate()203 public X509Certificate getCaCertificate() { 204 return mCaCertificate; 205 } 206 207 /** 208 * Constructor for creating Policy with default values. 209 */ UpdateParameter()210 public UpdateParameter() {} 211 212 /** 213 * Copy constructor. 214 * 215 * @param source The source to copy from 216 */ UpdateParameter(UpdateParameter source)217 public UpdateParameter(UpdateParameter source) { 218 if (source == null) { 219 return; 220 } 221 mUpdateIntervalInMinutes = source.mUpdateIntervalInMinutes; 222 mUpdateMethod = source.mUpdateMethod; 223 mRestriction = source.mRestriction; 224 mServerUri = source.mServerUri; 225 mUsername = source.mUsername; 226 mBase64EncodedPassword = source.mBase64EncodedPassword; 227 mTrustRootCertUrl = source.mTrustRootCertUrl; 228 if (source.mTrustRootCertSha256Fingerprint != null) { 229 mTrustRootCertSha256Fingerprint = Arrays.copyOf(source.mTrustRootCertSha256Fingerprint, 230 source.mTrustRootCertSha256Fingerprint.length); 231 } 232 mCaCertificate = source.mCaCertificate; 233 } 234 235 @Override describeContents()236 public int describeContents() { 237 return 0; 238 } 239 240 @Override writeToParcel(Parcel dest, int flags)241 public void writeToParcel(Parcel dest, int flags) { 242 dest.writeLong(mUpdateIntervalInMinutes); 243 dest.writeString(mUpdateMethod); 244 dest.writeString(mRestriction); 245 dest.writeString(mServerUri); 246 dest.writeString(mUsername); 247 dest.writeString(mBase64EncodedPassword); 248 dest.writeString(mTrustRootCertUrl); 249 dest.writeByteArray(mTrustRootCertSha256Fingerprint); 250 ParcelUtil.writeCertificate(dest, mCaCertificate); 251 } 252 253 @Override equals(Object thatObject)254 public boolean equals(Object thatObject) { 255 if (this == thatObject) { 256 return true; 257 } 258 if (!(thatObject instanceof UpdateParameter)) { 259 return false; 260 } 261 UpdateParameter that = (UpdateParameter) thatObject; 262 263 return mUpdateIntervalInMinutes == that.mUpdateIntervalInMinutes 264 && TextUtils.equals(mUpdateMethod, that.mUpdateMethod) 265 && TextUtils.equals(mRestriction, that.mRestriction) 266 && TextUtils.equals(mServerUri, that.mServerUri) 267 && TextUtils.equals(mUsername, that.mUsername) 268 && TextUtils.equals(mBase64EncodedPassword, that.mBase64EncodedPassword) 269 && TextUtils.equals(mTrustRootCertUrl, that.mTrustRootCertUrl) 270 && Arrays.equals(mTrustRootCertSha256Fingerprint, 271 that.mTrustRootCertSha256Fingerprint) 272 && Credential.isX509CertificateEquals(mCaCertificate, that.mCaCertificate); 273 } 274 275 @Override hashCode()276 public int hashCode() { 277 return Objects.hash(mUpdateIntervalInMinutes, mUpdateMethod, mRestriction, mServerUri, 278 mUsername, mBase64EncodedPassword, mTrustRootCertUrl, 279 Arrays.hashCode(mTrustRootCertSha256Fingerprint), mCaCertificate); 280 } 281 282 @Override toString()283 public String toString() { 284 StringBuilder builder = new StringBuilder(); 285 builder.append("UpdateInterval: ").append(mUpdateIntervalInMinutes).append("\n"); 286 builder.append("UpdateMethod: ").append(mUpdateMethod).append("\n"); 287 builder.append("Restriction: ").append(mRestriction).append("\n"); 288 builder.append("ServerURI: ").append(mServerUri).append("\n"); 289 builder.append("Username: ").append(mUsername).append("\n"); 290 builder.append("TrustRootCertURL: ").append(mTrustRootCertUrl).append("\n"); 291 return builder.toString(); 292 } 293 294 /** 295 * Validate UpdateParameter data. 296 * 297 * @return true on success 298 * @hide 299 */ validate()300 public boolean validate() { 301 if (mUpdateIntervalInMinutes == Long.MIN_VALUE) { 302 Log.d(TAG, "Update interval not specified"); 303 return false; 304 } 305 // Update not applicable. 306 if (mUpdateIntervalInMinutes == UPDATE_CHECK_INTERVAL_NEVER) { 307 return true; 308 } 309 310 if (!TextUtils.equals(mUpdateMethod, UPDATE_METHOD_OMADM) 311 && !TextUtils.equals(mUpdateMethod, UPDATE_METHOD_SSP)) { 312 Log.d(TAG, "Unknown update method: " + mUpdateMethod); 313 return false; 314 } 315 316 if (!TextUtils.equals(mRestriction, UPDATE_RESTRICTION_HOMESP) 317 && !TextUtils.equals(mRestriction, UPDATE_RESTRICTION_ROAMING_PARTNER) 318 && !TextUtils.equals(mRestriction, UPDATE_RESTRICTION_UNRESTRICTED)) { 319 Log.d(TAG, "Unknown restriction: " + mRestriction); 320 return false; 321 } 322 323 if (TextUtils.isEmpty(mServerUri)) { 324 Log.d(TAG, "Missing update server URI"); 325 return false; 326 } 327 if (mServerUri.getBytes(StandardCharsets.UTF_8).length > MAX_URI_BYTES) { 328 Log.d(TAG, "URI bytes exceeded the max: " 329 + mServerUri.getBytes(StandardCharsets.UTF_8).length); 330 return false; 331 } 332 333 if (TextUtils.isEmpty(mUsername)) { 334 Log.d(TAG, "Missing username"); 335 return false; 336 } 337 if (mUsername.getBytes(StandardCharsets.UTF_8).length > MAX_USERNAME_BYTES) { 338 Log.d(TAG, "Username bytes exceeded the max: " 339 + mUsername.getBytes(StandardCharsets.UTF_8).length); 340 return false; 341 } 342 343 if (TextUtils.isEmpty(mBase64EncodedPassword)) { 344 Log.d(TAG, "Missing username"); 345 return false; 346 } 347 if (mBase64EncodedPassword.getBytes(StandardCharsets.UTF_8).length > MAX_PASSWORD_BYTES) { 348 Log.d(TAG, "Password bytes exceeded the max: " 349 + mBase64EncodedPassword.getBytes(StandardCharsets.UTF_8).length); 350 return false; 351 } 352 try { 353 Base64.decode(mBase64EncodedPassword, Base64.DEFAULT); 354 } catch (IllegalArgumentException e) { 355 Log.d(TAG, "Invalid encoding for password: " + mBase64EncodedPassword); 356 return false; 357 } 358 359 if (TextUtils.isEmpty(mTrustRootCertUrl)) { 360 Log.d(TAG, "Missing trust root certificate URL"); 361 return false; 362 } 363 if (mTrustRootCertUrl.getBytes(StandardCharsets.UTF_8).length > MAX_URL_BYTES) { 364 Log.d(TAG, "Trust root cert URL bytes exceeded the max: " 365 + mTrustRootCertUrl.getBytes(StandardCharsets.UTF_8).length); 366 return false; 367 } 368 369 if (mTrustRootCertSha256Fingerprint == null) { 370 Log.d(TAG, "Missing trust root certificate SHA-256 fingerprint"); 371 return false; 372 } 373 if (mTrustRootCertSha256Fingerprint.length != CERTIFICATE_SHA256_BYTES) { 374 Log.d(TAG, "Incorrect size of trust root certificate SHA-256 fingerprint: " 375 + mTrustRootCertSha256Fingerprint.length); 376 return false; 377 } 378 return true; 379 } 380 381 public static final @android.annotation.NonNull Creator<UpdateParameter> CREATOR = 382 new Creator<UpdateParameter>() { 383 @Override 384 public UpdateParameter createFromParcel(Parcel in) { 385 UpdateParameter updateParam = new UpdateParameter(); 386 updateParam.setUpdateIntervalInMinutes(in.readLong()); 387 updateParam.setUpdateMethod(in.readString()); 388 updateParam.setRestriction(in.readString()); 389 updateParam.setServerUri(in.readString()); 390 updateParam.setUsername(in.readString()); 391 updateParam.setBase64EncodedPassword(in.readString()); 392 updateParam.setTrustRootCertUrl(in.readString()); 393 updateParam.setTrustRootCertSha256Fingerprint(in.createByteArray()); 394 updateParam.setCaCertificate(ParcelUtil.readCertificate(in)); 395 return updateParam; 396 } 397 398 @Override 399 public UpdateParameter[] newArray(int size) { 400 return new UpdateParameter[size]; 401 } 402 }; 403 } 404