content: /*
   * Copyright (C) 2010 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.google.android.vending.licensing;
  
  import com.google.android.vending.licensing.util.Base64;
  import com.google.android.vending.licensing.util.Base64DecoderException;
  
  import android.text.TextUtils;
  import android.util.Log;
  
  import java.security.InvalidKeyException;
  import java.security.NoSuchAlgorithmException;
  import java.security.PublicKey;
  import java.security.Signature;
  import java.security.SignatureException;
  
  /**
   * Contains data related to a licensing request and methods to verify
   * and process the response.
   */
  class LicenseValidator {
      private static final String TAG = "LicenseValidator";
  
      // Server response codes.
      private static final int LICENSED = 0x0;
      private static final int NOT_LICENSED = 0x1;
      private static final int LICENSED_OLD_KEY = 0x2;
      private static final int ERROR_NOT_MARKET_MANAGED = 0x3;
      private static final int ERROR_SERVER_FAILURE = 0x4;
      private static final int ERROR_OVER_QUOTA = 0x5;
  
      private static final int ERROR_CONTACTING_SERVER = 0x101;
      private static final int ERROR_INVALID_PACKAGE_NAME = 0x102;
      private static final int ERROR_NON_MATCHING_UID = 0x103;
  
      private final Policy mPolicy;
      private final LicenseCheckerCallback mCallback;
      private final int mNonce;
      private final String mPackageName;
      private final String mVersionCode;
      private final DeviceLimiter mDeviceLimiter;
  
      LicenseValidator(Policy policy, DeviceLimiter deviceLimiter, LicenseCheckerCallback callback,
               int nonce, String packageName, String versionCode) {
          mPolicy = policy;
          mDeviceLimiter = deviceLimiter;
          mCallback = callback;
          mNonce = nonce;
          mPackageName = packageName;
          mVersionCode = versionCode;
      }
  
      public LicenseCheckerCallback getCallback() {
          return mCallback;
      }
  
      public int getNonce() {
          return mNonce;
      }
  
      public String getPackageName() {
          return mPackageName;
      }
  
      private static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
  
      /**
       * Verifies the response from server and calls appropriate callback method.
       *
       * @param publicKey public key associated with the developer account
       * @param responseCode server response code
       * @param signedData signed data from server
       * @param signature server signature
       */
      public void verify(PublicKey publicKey, int responseCode, String signedData, String signature) {
          String userId = null;
          // Skip signature check for unsuccessful requests
          ResponseData data = null;
          if (responseCode == LICENSED || responseCode == NOT_LICENSED ||
                  responseCode == LICENSED_OLD_KEY) {
              // Verify signature.
              try {
                  Signature sig = Signature.getInstance(SIGNATURE_ALGORITHM);
                  sig.initVerify(publicKey);
                  sig.update(signedData.getBytes());
  
                  if (!sig.verify(Base64.decode(signature))) {
                      Log.e(TAG, "Signature verification failed.");
                      handleInvalidResponse();
                      return;
                  }
              } catch (NoSuchAlgorithmException e) {
                  // This can't happen on an Android compatible device.
                  throw new RuntimeException(e);
              } catch (InvalidKeyException e) {
                  handleApplicationError(LicenseCheckerCallback.ERROR_INVALID_PUBLIC_KEY);
                  return;
              } catch (SignatureException e) {
                  throw new RuntimeException(e);
              } catch (Base64DecoderException e) {
                  Log.e(TAG, "Could not Base64-decode signature.");
                  handleInvalidResponse();
                  return;
              }
  
              // Parse and validate response.
              try {
                  data = ResponseData.parse(signedData);
              } catch (IllegalArgumentException e) {
                  Log.e(TAG, "Could not parse response.");
                  handleInvalidResponse();
                  return;
              }
  
              if (data.responseCode != responseCode) {
                  Log.e(TAG, "Response codes don't match.");
                  handleInvalidResponse();
                  return;
              }
  
              if (data.nonce != mNonce) {
                  Log.e(TAG, "Nonce doesn't match.");
                  handleInvalidResponse();
                  return;
              }
  
              if (!data.packageName.equals(mPackageName)) {
                  Log.e(TAG, "Package name doesn't match.");
                  handleInvalidResponse();
                  return;
              }
  
              if (!data.versionCode.equals(mVersionCode)) {
                  Log.e(TAG, "Version codes don't match.");
                  handleInvalidResponse();
                  return;
              }
  
              // Application-specific user identifier.
              userId = data.userId;
              if (TextUtils.isEmpty(userId)) {
                  Log.e(TAG, "User identifier is empty.");
                  handleInvalidResponse();
                  return;
              }
          }
  
          switch (responseCode) {
              case LICENSED:
              case LICENSED_OLD_KEY:
                  int limiterResponse = mDeviceLimiter.isDeviceAllowed(userId);
                  handleResponse(limiterResponse, data);
                  break;
              case NOT_LICENSED:
                  handleResponse(Policy.NOT_LICENSED, data);
                  break;
              case ERROR_CONTACTING_SERVER:
                  Log.w(TAG, "Error contacting licensing server.");
                  handleResponse(Policy.RETRY, data);
                  break;
              case ERROR_SERVER_FAILURE:
                  Log.w(TAG, "An error has occurred on the licensing server.");
                  handleResponse(Policy.RETRY, data);
                  break;
              case ERROR_OVER_QUOTA:
                  Log.w(TAG, "Licensing server is refusing to talk to this device, over quota.");
                  handleResponse(Policy.RETRY, data);
                  break;
              case ERROR_INVALID_PACKAGE_NAME:
                  handleApplicationError(LicenseCheckerCallback.ERROR_INVALID_PACKAGE_NAME);
                  break;
              case ERROR_NON_MATCHING_UID:
                  handleApplicationError(LicenseCheckerCallback.ERROR_NON_MATCHING_UID);
                  break;
              case ERROR_NOT_MARKET_MANAGED:
                  handleApplicationError(LicenseCheckerCallback.ERROR_NOT_MARKET_MANAGED);
                  break;
              default:
                  Log.e(TAG, "Unknown response code for license check.");
                  handleInvalidResponse();
          }
      }
  
      /**
       * Confers with policy and calls appropriate callback method.
       *
       * @param response
       * @param rawData
       */
      private void handleResponse(int response, ResponseData rawData) {
          // Update policy data and increment retry counter (if needed)
          mPolicy.processServerResponse(response, rawData);
  
          // Given everything we know, including cached data, ask the policy if we should grant
          // access.
          if (mPolicy.allowAccess()) {
              mCallback.allow(response);
          } else {
              mCallback.dontAllow(response);
          }
      }
  
      private void handleApplicationError(int code) {
          mCallback.applicationError(code);
      }
  
      private void handleInvalidResponse() {
          mCallback.dontAllow(Policy.NOT_LICENSED);
      }
  }
  