--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/javacommons/security/javasrc/com/nokia/mj/impl/security/midp/authentication/AuthenticationModule.java Tue Apr 27 16:30:29 2010 +0300
@@ -0,0 +1,1191 @@
+/*
+* Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies).
+* All rights reserved.
+* This component and the accompanying materials are made available
+* under the terms of "Eclipse Public License v1.0"
+* which accompanies this distribution, and is available
+* at the URL "http://www.eclipse.org/legal/epl-v10.html".
+*
+* Initial Contributors:
+* Nokia Corporation - initial contribution.
+*
+* Contributors:
+*
+* Description:
+*
+*/
+
+package com.nokia.mj.impl.security.midp.authentication;
+
+import com.nokia.mj.impl.utils.Uid;
+import com.nokia.mj.impl.utils.Tokenizer;
+import com.nokia.mj.impl.security.common.InstallerSecurityException;
+import com.nokia.mj.impl.security.common.RuntimeSecurityException;
+import com.nokia.mj.impl.utils.InstallerDetailedErrorMessage;
+import com.nokia.mj.impl.utils.InstallerErrorMessage;
+import com.nokia.mj.impl.utils.OtaStatusCode;
+import com.nokia.mj.impl.security.utils.SecurityErrorMessage;
+import com.nokia.mj.impl.security.utils.SecurityDetailedErrorMessage;
+import com.nokia.mj.impl.security.utils.TelUtils;
+import com.nokia.mj.impl.security.midp.common.AuthenticationCredentials;
+import com.nokia.mj.impl.security.midp.common.AuthenticationInfo;
+import com.nokia.mj.impl.security.midp.common.AuthenticationAttribute;
+import com.nokia.mj.impl.security.midp.common.SecurityAttributes;
+import com.nokia.mj.impl.security.common.Certificate;
+import com.nokia.mj.impl.security.common.SecurityCommsMessages;
+import com.nokia.mj.impl.security.midp.common.ProtectionDomain;
+import com.nokia.mj.impl.security.midp.common.SigningCertificate;
+import com.nokia.mj.impl.security.midp.common.SigningInfo;
+import com.nokia.mj.impl.security.midp.common.GeneralSecuritySettings;
+import com.nokia.mj.impl.security.midp.storage.*;
+import com.nokia.mj.impl.storage.StorageSession;
+import com.nokia.mj.impl.rt.support.Jvm;
+import com.nokia.mj.impl.rt.support.ApplicationInfo;
+import com.nokia.mj.impl.security.utils.Logger;
+import com.nokia.mj.impl.comms.CommsEndpoint;
+import com.nokia.mj.impl.comms.CommsMessage;
+import com.nokia.mj.impl.comms.exception.CommsException;
+import com.nokia.mj.impl.fileutils.DriveUtilities;
+import com.nokia.mj.impl.fileutils.DriveInfo;
+import java.util.Hashtable;
+import java.util.Vector;
+
+/**
+ * MIDP authentication is build around X.509 Public Key Infrastructure so that
+ * MIDlet suites are signed using public key certificates and therefore
+ * authenticated by verifying the authenticity of the MIDlet suite's signing
+ * certificates with the help of trusted (root) certificates.
+ * As a result of authentication a MIDlet suite is bound to a protection domain
+ * which will be used as a criteria for granting the MIDlet access to protected
+ * functionality.
+ * This class is used for authenticating MIDlet suites.
+ */
+public final class AuthenticationModule
+{
+ static
+ {
+ Jvm.loadSystemLibrary("javasecurity");
+ }
+
+ /*
+ * Hashtable containing the all of the authentication credentials
+ * of different aplications being installed
+ */
+ private Hashtable iAuthCredentials;
+
+ /*
+ * Hashtable containing the selected authentication credentials of
+ * different aplications being installed
+ */
+ private Hashtable iSelectedAuthCredentials;
+
+ /*
+ * Hashtable containing the ocsp checkers corresponding to
+ * installation of various MIDlet suites
+ */
+ private Hashtable iOcspCheckers;
+
+ /*
+ * Hashtable containing the ocsp event listeners corresponding to
+ * ocsp checks of various MIDlet suites
+ */
+ private Hashtable iOcspEventListeners;
+
+ // self
+ private static AuthenticationModule self;
+
+ // data structure which holds the flags about different suites
+ // being legacy suites or not; the flag info is available at
+ // authentication JAD operation time, but not available at
+ // authentication JAR operation time -> this hashtable carries
+ // the flag between authenticating JAD and JAR operations for
+ // same suite
+ private static Hashtable iLegacySuiteFlags = new Hashtable();
+
+ /**
+ * The ocsp settings
+ */
+ private static OcspSettings iOcspSettings;
+ private static OcspUserPreferences iOcspUserPreferences;
+
+ /*
+ * The security warnings mode
+ */
+ private static int iSecurityWarningsMode = GeneralSecuritySettings.UNINITIALIZED_SECURITY_MODE;
+
+ /**
+ * Creates an instance of the AuthenticationModule
+ *
+ * @return An instance of AuthenticationModule
+ */
+ public static AuthenticationModule getInstance()
+ {
+ if (self == null)
+ {
+ self = new AuthenticationModule();
+ }
+ return self;
+ }
+
+ /**
+ * Authenticates a certain MIDlet suite. This method is called
+ * when/if the JAD is available
+ *
+ * @param msUID the UID if the MIDlet suite being authenticated
+ * @param oldMSUID the UID if the MIDlet suite being updated
+ * (if applicable)
+ * @param authInfo the authentication info based on which the MIDlet suite
+ * is authenticated
+ * @return a set of credentials assigned to the MIDlet suite
+ */
+ public AuthenticationCredentials[] authenticateJad(
+ Uid msUID,
+ Uid oldMSUID,
+ AuthenticationInfo[] authInfo)
+ {
+ Vector allAuthCredentials = null;
+ if (authInfo == null || authInfo.length == 0)
+ {
+ // this is untrusted MIDlet -> save the protection domain only
+ allAuthCredentials = verifyUpdate(
+ new Credentials[] {new Credentials("UnidentifiedThirdParty",
+ ApplicationInfo.UNIDENTIFIED_THIRD_PARTY_DOMAIN,
+ null, null, -1, null)
+ }, oldMSUID);
+ }
+ else
+ {
+ try
+ {
+ // save all the credentials as candidates -> after having the JAR,
+ // based on its hash we will select the protection_domain,
+ // root_hash and the validated_chain_indexes
+ allAuthCredentials = verifyUpdate(
+ authInfo[0].getLegacyInfo(),
+ _validateChainsAndSignatures(authInfo), oldMSUID,
+ true /* order the result */);
+ // start an OCSP client if the ocsp checks are enabled
+ if (performOcsp())
+ {
+ // start the OCSP checks for all the succesfully validated chains
+ if (allAuthCredentials.size() > 0)
+ {
+ OcspData[] ocspData = new OcspData[allAuthCredentials.size()];
+ for (int i=0; i<allAuthCredentials.size(); i++)
+ {
+ Credentials authCredentials =
+ (Credentials)allAuthCredentials.elementAt(i);
+ // need to add the root the to chain when doing the ocsp checks
+ String[] certChain = authInfo[(authCredentials
+ .validatedChainIndex - 1)].getCertChain();
+ String root = getRoot(authCredentials.rootHashValue);
+ if (root != null)
+ {
+ String[] completeCertChain =
+ new String[certChain.length + 1];
+ int j;
+ for (j=0; j<certChain.length; j++)
+ {
+ completeCertChain[j] = certChain[j];
+ }
+ completeCertChain[j] = root;
+ certChain = completeCertChain;
+ }
+ ocspData[i] = new OcspData(certChain);
+ }
+ // build the ocspChecker with the newly built cert chains
+ OcspChecker ocspChecker = new OcspChecker(ocspData,
+ iOcspSettings,
+ iOcspUserPreferences,
+ (OcspEventListener)iOcspEventListeners.get(msUID));
+ iOcspCheckers.put(msUID, ocspChecker);
+ // start the OCSP check
+ ocspChecker.start();
+ }
+ }
+ }
+ catch (AuthenticationException e)
+ {
+ Logger.log(e);
+ switch (e.getErrorCode())
+ {
+ case AuthenticationException.CONNECTION_TO_CAPTAIN_FAILED:
+ case AuthenticationException.SENDING_MSG_TO_CAPTAIN_FAILED:
+ throw new InstallerSecurityException(
+ InstallerErrorMessage.INST_UNEXPECTED_ERR,
+ null, /* no params for short msg */
+ InstallerDetailedErrorMessage.INTERNAL_ERROR,
+ new String[] {"Internal communication problem."},
+ OtaStatusCode.INTERNAL_ERROR);
+ case AuthenticationException.CERT_VERIFICATION_FAILED:
+ case AuthenticationException.CERT_NOT_YET_VALID:
+ case AuthenticationException.CERT_EXPIRED:
+ case AuthenticationException.ROOT_CERT_IN_CHAIN:
+ case AuthenticationException.MISSING_ROOT:
+ case AuthenticationException.UNKNOWN_EXT_KEY_USAGE:
+ throw new InstallerSecurityException(
+ InstallerErrorMessage.INST_AUTHENTICATION_ERR,
+ null, /* no params for short msg */
+ InstallerDetailedErrorMessage.CERT_UNSUPPORTED,
+ null, /* no params for detailed msg */
+ OtaStatusCode.APPLICATION_AUTHENTICATION_FAILURE);
+ case AuthenticationException.SIG_VERIFICATION_FAILED:
+ throw new InstallerSecurityException(
+ InstallerErrorMessage.INST_AUTHORIZATION_ERR,
+ null, /* no params for short msg */
+ InstallerDetailedErrorMessage.ATTR_HANDLING_FAILED,
+ new String[] {AuthenticationAttribute
+ .SECOND_LEGACY_ATTRIBUTE_NAME
+ },
+ OtaStatusCode.APPLICATION_AUTHORIZATION_FAILURE);
+ case AuthenticationException.MISSING_DOMAIN_MAPPING:
+ throw new InstallerSecurityException(
+ InstallerErrorMessage.INST_AUTHORIZATION_ERR,
+ null, /* no params for short msg */
+ InstallerDetailedErrorMessage.CERT_UNSUPPORTED,
+ null, /* no params for detailed msg */
+ OtaStatusCode.APPLICATION_AUTHORIZATION_FAILURE);
+ }
+ }
+ }
+ iAuthCredentials.put(msUID, allAuthCredentials);
+ Credentials[] credentials = new Credentials[allAuthCredentials.size()];
+ allAuthCredentials.copyInto(credentials);
+ Logger.logAuthenticationCredentials(allAuthCredentials);
+ return credentials;
+ }
+
+ /**
+ * Authenticates a certain MIDlet suite. This method is called
+ * when the JAR is available
+ *
+ * @param storageSession the JavaStorage session to be used when
+ * storing security data
+ * @param msUID the UID if the MIDlet suite being authenticated
+ * @param oldMSUID the UID if the MIDlet suite being updated
+ * (if applicable)
+ * @param appJARPath the path to the JAR being authenticated
+ */
+ public void authenticateJar(
+ StorageSession storageSession,
+ Uid msUID,
+ Uid oldMSUID,
+ String appJARPath,
+ boolean drmContent)
+ {
+ AuthenticationStorageData data = null;
+ try
+ {
+ Vector allAuthCredentials = (Vector)iAuthCredentials.get(msUID);
+ String jarHash = _computeHash(appJARPath);
+ if (jarHash == null || jarHash.length() == 0)
+ {
+ // could not compute hash for the given application
+ Logger.logWarning("Could not compute hash for " + appJARPath);
+ throw new InstallerSecurityException(
+ InstallerErrorMessage.INST_UNEXPECTED_ERR,
+ null, /* no params for short msg */
+ InstallerDetailedErrorMessage.INTERNAL_ERROR,
+ new String[] {"Could not compute hash for " + appJARPath},
+ OtaStatusCode.INTERNAL_ERROR);
+ }
+ if (allAuthCredentials == null
+ || (allAuthCredentials.size() == 1
+ && ((Credentials)allAuthCredentials.elementAt(0))
+ .getProtectionDomainCategory().equals(
+ ApplicationInfo.UNIDENTIFIED_THIRD_PARTY_DOMAIN)))
+ {
+ data = new AuthenticationStorageData(
+ "UnidentifiedThirdParty",
+ ApplicationInfo.UNIDENTIFIED_THIRD_PARTY_DOMAIN,
+ jarHash,
+ null /*rootHashValue*/,
+ null /*validatedChainIndexes*/,
+ null /* jarPath*/,
+ iSecurityWarningsMode);
+ verifyUpdate(
+ new Credentials[] {new Credentials(
+ data.getProtectionDomain(),
+ data.getProtectionDomainCategory(),
+ jarHash,
+ null /* root hash */,
+ -1 /* validated chain index*/,
+ null /* signing cert */)
+ },
+ oldMSUID);
+ }
+ else
+ {
+ // get the authentication credential candidates and select the right
+ // domain/root_hash and validated_chain_index based on the hash of
+ // the JAR
+ String protectionDomainName = null;
+ String protectionDomainCategory = null;
+ String jarHashValue = null;
+ String rootHashValue = null;
+ Vector validatedChainIndexes = new Vector();
+ Credentials selectedCredentials = selectCredentials(jarHash, allAuthCredentials, validatedChainIndexes);
+ if (selectedCredentials == null
+ || selectedCredentials.getProtectionDomainName() == null)
+ {
+ if (!drmContent)
+ {
+ Logger.logWarning("Signature verification failure: the computed and the decrypted JAR hashes do not match");
+ throw new InstallerSecurityException(
+ InstallerErrorMessage.INST_AUTHORIZATION_ERR,
+ null, /* no params for short msg */
+ InstallerDetailedErrorMessage.ATTR_HANDLING_FAILED,
+ new String[] {AuthenticationAttribute
+ .SECOND_LEGACY_ATTRIBUTE_NAME
+ },
+ OtaStatusCode.APPLICATION_AUTHORIZATION_FAILURE);
+ }
+ else
+ {
+ // give it one more try
+ String drmJarHash = _drmDecryptAndComputeHash(appJARPath);
+ validatedChainIndexes.removeAllElements();
+ selectedCredentials = selectCredentials(drmJarHash, allAuthCredentials, validatedChainIndexes);
+ if (selectedCredentials == null
+ || selectedCredentials.getProtectionDomainName() == null)
+ {
+ Logger.logWarning("Signature verification failure: the computed and the decrypted JAR hashes do not match");
+ throw new InstallerSecurityException(
+ InstallerErrorMessage.INST_AUTHORIZATION_ERR,
+ null, /* no params for short msg */
+ InstallerDetailedErrorMessage.ATTR_HANDLING_FAILED,
+ new String[] {AuthenticationAttribute
+ .SECOND_LEGACY_ATTRIBUTE_NAME
+ },
+ OtaStatusCode.APPLICATION_AUTHORIZATION_FAILURE);
+ }
+ // re-set the jar hash to the hash of what acts as JAR
+ // (not the JAR from inside the DRM content), so no
+ // need to do the DRM decrypting when doing the
+ // tamper detection at runtime
+ selectedCredentials.jarHashValue = jarHash;
+ }
+ }
+ data = new AuthenticationStorageData(
+ selectedCredentials.getProtectionDomainName(),
+ selectedCredentials.getProtectionDomainCategory(),
+ selectedCredentials.jarHashValue,
+ selectedCredentials.rootHashValue,
+ validatedChainIndexes,
+ iSecurityWarningsMode);
+ }
+
+ writeAuthenticationStorageData(storageSession, msUID, data, (oldMSUID != null && oldMSUID.equals(msUID)));
+ }
+ finally
+ {
+ // remove all the auth credentials with the selection
+ if (data != null)
+ {
+ iSelectedAuthCredentials.put(msUID, data);
+ }
+ }
+ }
+
+ /**
+ */
+ public void authenticateJar(
+ StorageSession storageSession,
+ Uid uid,
+ Uid oldUid,
+ ProtectionDomain protectionDomain,
+ String appJARPath)
+ {
+ if (protectionDomain == null
+ || (!protectionDomain.equals(ProtectionDomain.getManufacturerDomain())
+ && !protectionDomain.equals(ProtectionDomain.getOperatorDomain())
+ && !protectionDomain.equals(ProtectionDomain.getIdentifiedThirdPartyDomain())
+ && !protectionDomain.equals(ProtectionDomain.getUnidentifiedThirdPartyDomain())))
+ {
+ Logger.logWarning("Unknown protection domain " + protectionDomain);
+ throw new InstallerSecurityException(
+ InstallerErrorMessage.INST_UNEXPECTED_ERR,
+ null, /* no params for short msg */
+ InstallerDetailedErrorMessage.INTERNAL_ERROR,
+ new String[] {"Unknown protection domain " + protectionDomain},
+ OtaStatusCode.INTERNAL_ERROR);
+ }
+ String jarHash = _computeHash(appJARPath);
+ if (jarHash == null || jarHash.length() == 0)
+ {
+ // could not compute hash for the given application
+ Logger.logWarning("Could not compute hash for " + appJARPath);
+ throw new InstallerSecurityException(
+ InstallerErrorMessage.INST_UNEXPECTED_ERR,
+ null, /* no params for short msg */
+ InstallerDetailedErrorMessage.INTERNAL_ERROR,
+ new String[] {"Could not compute hash for " + appJARPath},
+ OtaStatusCode.INTERNAL_ERROR);
+ }
+ AuthenticationStorageData data = new AuthenticationStorageData(
+ protectionDomain.getName(),
+ protectionDomain.getCategory(),
+ jarHash,
+ null /*rootHashValue*/,
+ null /*validatedChainIndexes*/,
+ null /* jarPath*/,
+ iSecurityWarningsMode);
+ verifyUpdate(
+ new Credentials[] {new Credentials(
+ data.getProtectionDomain(),
+ data.getProtectionDomainCategory(),
+ jarHash,
+ null /* root hash */,
+ -1 /* validated chain index*/,
+ null /* signing cert */)
+ },
+ oldUid);
+
+ writeAuthenticationStorageData(storageSession, uid, data, (oldUid != null && oldUid.equals(uid)));
+ }
+
+ /**
+ * Registers a listener for ocsp events corresponding to the
+ * installation of a certain MIDlet suite
+ *
+ * @param aMsUid the uid of the MIDlet suite on behalf
+ * of which the listener is registered
+ * @param aListener the ocsp events listener
+ */
+ public void registerOcspEventListener(Uid aMsUid,
+ OcspEventListener aListener)
+ {
+ if (aMsUid != null && aListener != null)
+ {
+ Logger.log("OcspEventListener registered on behalf of the suite " + aMsUid.toString());
+ iOcspEventListeners.put(aMsUid, aListener);
+ }
+ }
+
+ /**
+ * Unregisters the listener for ocsp events corresponding to the
+ * installation of a certain MIDlet suite
+ *
+ * @param aMsUid the uid of the MIDlet suite on behalf
+ * of which the listener is unregistered
+ */
+ public void unregisterOcspEventListener(Uid aMsUid)
+ {
+ if (aMsUid != null)
+ {
+ Logger.log("OcspEventListener unregistered on behalf of the suite " + aMsUid.toString());
+ iOcspEventListeners.remove(aMsUid);
+ }
+ }
+
+ /**
+ * Cancels any Ocsp checks (if any)
+ *
+ * @param msUid the UID if the MIDlet suite oh whose behalf the ocsp
+ * checks are canceled
+ */
+ public void cancelOcspCheck(Uid msUid)
+ {
+ OcspChecker ocspChecker = (OcspChecker)iOcspCheckers.get(msUid);
+ if (ocspChecker != null)
+ {
+ ocspChecker.cancel();
+ }
+ }
+
+ /**
+ * Returns the signing info of certain application suite
+ *
+ * @param aAppSuiteName the name of the application suite for which the
+ signing info is queried
+ * @param aAppSuiteVersion the version of the application suite for which
+ * the signing info is queried
+ * @param aAppSuiteVendor the vendor of the application suite for which
+ the signing info is queried
+ * @return The signing info if the queried application
+ * suite has been signed or NULL otherwise
+ */
+ public SigningInfo getSigningInfo(String aAppSuiteName,
+ String aAppSuiteVersion,
+ String aAppSuiteVendor)
+ {
+ SecurityStorage storage = new SecurityStorage();
+ try
+ {
+ AuthenticationStorageData authData = storage.
+ readAuthenticationStorageData(
+ aAppSuiteName, aAppSuiteVersion, aAppSuiteVendor,
+ SecurityStorage.AUTHENTICATION_DOMAIN_NAME_QUERY
+ | SecurityStorage.AUTHENTICATION_DOMAIN_CATEGORY_QUERY
+ | SecurityStorage.AUTHENTICATION_ROOT_HASH_QUERY);
+ if (authData != null)
+ {
+ Certificate signingCert = null;
+ Certificate rootCert = null;
+ AppAccessAuthorizationStorageData appAccesAuthData = storage
+ .readAppAccessAuthorizationStorageData(aAppSuiteName,
+ aAppSuiteVersion, aAppSuiteVendor,
+ SecurityStorage.APP_ACCESS_AUTH_SIGNERS_LIST_QUERY);
+ if (appAccesAuthData != null && appAccesAuthData.getSignersList() != null
+ && appAccesAuthData.getSignersList().length > 0)
+ {
+ signingCert = _parseCertificate(
+ appAccesAuthData.getSignersList()[0]);
+ rootCert = _getRootCertificate(
+ authData.getRootHashValue());
+ }
+ return new SigningInfo(signingCert, rootCert,
+ new ProtectionDomain(authData.getProtectionDomain(),
+ authData.getProtectionDomainCategory()));
+ }
+ return null;
+ }
+ finally
+ {
+ storage.close();
+ }
+ }
+
+
+ /**
+ * Removes all the security data related to a certain MIDlet suite
+ *
+ * @param sessionID the JavaStorage session to be used when
+ * removing the security data
+ * @param msUID the UID if the MIDlet suite whose security data is
+ * being removed
+ */
+ public void removeSecurityData(StorageSession storageSession, Uid msUID)
+ {
+ Logger.log("Remove authentication data");
+ SecurityStorage storage = new SecurityStorage(storageSession);
+ storage.removeAuthenticationStorageData(msUID);
+ // clean the caches as well
+ iAuthCredentials.remove(msUID);
+ iSelectedAuthCredentials.remove(msUID);
+ iLegacySuiteFlags.remove(msUID);
+ OcspChecker ocspChecker = (OcspChecker)iOcspCheckers.remove(msUID);
+ if (ocspChecker != null)
+ {
+ ocspChecker.destroy();
+ }
+ iOcspEventListeners.remove(msUID);
+ }
+
+ /**
+ * Returns the details of the certificates used for authenticating a
+ * MIDlet suite. This method is used at installation time.
+ *
+ * @param sessionID the JavaStorage session to be used when
+ * retrieving the certificates details
+ * @param msUID the UID if the MIDlet suite whose certificate details
+ * are queried
+ * @return the details of the certificate used for authenticating
+ * the MIDlet suite or null if the details are not
+ * available
+ */
+ public SigningCertificate[] getCertificatesDetails(StorageSession storageSession, Uid msUID)
+ {
+ Vector allAuthCredentials = (Vector)iAuthCredentials.get(msUID);
+ SigningCertificate[] certDetails = null;
+ if (allAuthCredentials != null && allAuthCredentials.size() > 0)
+ {
+ Vector vCertDetails = new Vector();
+ for (int i=0; i<allAuthCredentials.size(); i++)
+ {
+ Credentials credentials = ((Credentials)allAuthCredentials
+ .elementAt(i));
+ Certificate cert = credentials.signingCert;
+ if (cert != null)
+ {
+ vCertDetails.addElement(new SigningCertificate(
+ cert, credentials.rootHashValue,
+ credentials.getProtectionDomainName(),
+ credentials.getProtectionDomainCategory()));
+ }
+ }
+ if (vCertDetails.size() > 0)
+ {
+ certDetails = new SigningCertificate[vCertDetails.size()];
+ vCertDetails.copyInto(certDetails);
+ }
+ }
+ else
+ {
+ // if cert details are not found in cache, retrieve the signing
+ // certificate from storage and extract the details from it
+ SecurityStorage storage = new SecurityStorage(storageSession);
+ SigningCertificate signingCertificate = retrieveSigningCertificate(
+ storage, msUID);
+ if (signingCertificate != null)
+ {
+ certDetails = new SigningCertificate[1];
+ certDetails[0] = signingCertificate;
+ }
+ }
+ return certDetails;
+ }
+
+ /**
+ * Returns the protection domain info of a certain MIDlet suite. This
+ * method is used at uninstallation time.
+ *
+ * @param sessionID the JavaStorage session to be used when
+ * retrieving the domain category
+ * @param msUID the UID if the MIDlet suite whose protection
+ * domain info is queried
+ * @param one of the constants defined in ApplicationInfo
+ */
+ public String getProtectionDomainCategory(StorageSession storageSession, Uid msUID)
+ {
+ SecurityStorage storage = new SecurityStorage(storageSession);
+ return storage.readProtectionDomainCategory(msUID);
+ }
+
+ /**
+ * Notification about the media where a certain MIDlet suite is installed.
+ *
+ * @param aStorageSession the JavaStorage session to be used when/if
+ * making storage operations related to this
+ * notification
+ * @param aMsUid the UID of the MIDlet suite whose media info is
+ * notified
+ * @param aMediaId the identifier of the media where the MIDlet
+ * suite is installed
+ */
+ public void setMediaId(StorageSession aStorageSession, Uid aMsUid, int aMediaId)
+ {
+ // store the jar hash only if the suite was installed on a non-protected media
+ if (isDriveProtected(aMediaId))
+ {
+ SecurityStorage storage = new SecurityStorage(aStorageSession);
+ try
+ {
+ AuthenticationStorageData authStorageData =
+ (AuthenticationStorageData)iSelectedAuthCredentials.get(
+ aMsUid);
+ if (authStorageData != null)
+ {
+ Logger.log("Suite installed on protected media -> the runtime tamper detection is disabled");
+ authStorageData.setJarHashValue(null);
+ storage.writeAuthenticationStorageData(aMsUid,
+ authStorageData,
+ true /* this is an update */);
+ }
+ }
+ finally
+ {
+ iSelectedAuthCredentials.remove(aMsUid);
+ }
+
+ }
+ }
+
+ /**
+ * Setter for the OCSP settings
+ */
+ public void setOCSPFlags(OcspSettings ocspSettings)
+ {
+ Logger.log("Ocsp settings = " + ocspSettings.toString());
+ iOcspSettings = ocspSettings;
+ }
+
+ /**
+ * Performs a cleanup (e.g. on cached data)
+ *
+ */
+ public void cleanup()
+ {
+ Logger.log("Cleanup authentication module cache");
+ iAuthCredentials.clear();
+ iSelectedAuthCredentials.clear();
+ iLegacySuiteFlags.clear();
+ iOcspCheckers.clear();
+ iOcspEventListeners.clear();
+ }
+
+ /**
+ * Verifies the authenticity of MIDlet suites
+ *
+ * @param msUid The Uid of the MIDlet suite whose authenticity
+ * is verified
+ * @param authStorageData The stored authentication data assigned to the
+ * MIDlet suite whose authenticity is verified
+ *
+ */
+ public void verifyMIDletSuiteAuthenticity(Uid msUid, AuthenticationStorageData authStorageData)
+ {
+ Logger.log("Verifying the authenticity of the suite " + msUid.toString());
+ // for the operator MIDlets, check if there are any network restrictions
+ if (ApplicationInfo.OPERATOR_DOMAIN.equals(
+ authStorageData.getProtectionDomainCategory()))
+ {
+ Logger.log(" Checking network restrictions for operator signed suites");
+ // get the restrictions from storage
+ SecurityStorage storage = new SecurityStorage();
+ String networkRestrictions = storage.readNetworkRestrictions(msUid);
+ storage.close();
+ if (networkRestrictions != null
+ && networkRestrictions.length() > 0)
+ {
+ // get the real network codes
+ TelUtils.NetworkCodes networkCodes = TelUtils.getNetworkCodes();
+ boolean found = false;
+ if (networkCodes != null)
+ {
+ Logger.log(" Network restrictions: " + networkRestrictions);
+ Logger.log(" Network codes: mcc(" + networkCodes.mcc + ") mnc(" + networkCodes.mnc + ")");
+ // go through the list of restrictions and try to find a match
+ // the list of restrictions is a space-separated list of MCC-MNC*/
+ String[] tuples = Tokenizer.split(networkRestrictions, " ");
+ if (tuples != null)
+ {
+ for (int i=0; i<tuples.length; i++)
+ {
+ int mccEndPos = tuples[i].indexOf('-');
+ String mcc = tuples[i].substring(0, mccEndPos);
+ String mnc = tuples[i].substring(mccEndPos + 1);
+ if (mcc.equals(networkCodes.mcc)
+ && mnc.equals(networkCodes.mnc))
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+ }
+ if (!found)
+ {
+ Logger.logWarning(" -> the network restrictions are violated");
+ throw new RuntimeSecurityException(
+ SecurityErrorMessage.NETWORK_RESTRICTION_VIOLATION,
+ null, /* no params for short msg */
+ SecurityDetailedErrorMessage.NETWORK_RESTRICTION_VIOLATION,
+ null /* no params for detailed msg */);
+ }
+ Logger.log(" -> the network restrictions are obeyed");
+ }
+ }
+
+ // check the root validity (if applicable)
+ if (authStorageData.getRootHashValue() != null
+ && authStorageData.getRootHashValue().length() > 0)
+ {
+ Logger.log(" Checking validity of the root certificate used in authentication");
+ switch (retrieveRootState(authStorageData.getRootHashValue()))
+ {
+ case SecurityCommsMessages.JAVA_CERT_STORE_STATE_ENABLED:
+ // ok -> just go on
+ Logger.log(" Root ok");
+ break;
+ case SecurityCommsMessages.JAVA_CERT_STORE_STATE_DISABLED:
+ Logger.logWarning(" Root disabled");
+ throw new RuntimeSecurityException(
+ SecurityErrorMessage.CERT_NOT_AVAILABLE,
+ null, /* no params for short msg */
+ SecurityDetailedErrorMessage.CERT_DISABLED,
+ null /* no params for detailed msg */);
+ case SecurityCommsMessages.JAVA_CERT_STORE_STATE_DELETED:
+ Logger.logWarning(" Root deleted");
+ throw new RuntimeSecurityException(
+ SecurityErrorMessage.CERT_NOT_AVAILABLE,
+ null, /* no params for short msg */
+ SecurityDetailedErrorMessage.CERT_DELETED,
+ null /* no params for detailed msg */);
+ case SecurityCommsMessages.JAVA_CERT_STORE_STATE_NOT_PRESENT:
+ Logger.logWarning(" Root not available");
+ throw new RuntimeSecurityException(
+ SecurityErrorMessage.CERT_NOT_AVAILABLE,
+ null, /* no params for short msg */
+ SecurityDetailedErrorMessage.SIM_CHANGED,
+ null /* no params for detailed msg */);
+ case SecurityCommsMessages.JAVA_CERT_STORE_STATE_UNKNOWN:
+ Logger.logWarning(" Root unknown");
+ throw new RuntimeSecurityException(
+ SecurityErrorMessage.CERT_NOT_AVAILABLE,
+ null, /* no params for short msg */
+ SecurityDetailedErrorMessage.UNIDENTIFIED_APPLICATION,
+ null /* no params for detailed msg */);
+ }
+ }
+
+ if (authStorageData.getJarPath() == null
+ || authStorageData.getJarPath().length() == 0)
+ {
+ Logger.logWarning(" JarPath not available");
+ throw new RuntimeSecurityException(
+ SecurityErrorMessage.UNEXPECTED_ERR,
+ null, /* no params for short msg */
+ SecurityDetailedErrorMessage.UNIDENTIFIED_APPLICATION,
+ null /* no params for detailed msg */);
+ }
+
+ // do the tamper detection
+ if (authStorageData.getJarHashValue() != null
+ && authStorageData.getJarHashValue().length() > 0)
+ {
+ Logger.log(" Doing tamper detection");
+ String computedJarHash = _computeHash(authStorageData.getJarPath());
+ // do the tampering check: compute the hash and compare it with the stored hash
+ if (computedJarHash == null || !computedJarHash.equals(
+ authStorageData.getJarHashValue()))
+ {
+ Logger.logWarning(" Application has been tampered");
+ throw new RuntimeSecurityException(
+ SecurityErrorMessage.JAR_TAMPERED,
+ null, /* no params for short msg */
+ SecurityDetailedErrorMessage.JAR_TAMPERED,
+ null /* no params for detailed msg */);
+ }
+ }
+ }
+
+ private AuthenticationModule()
+ {
+ iAuthCredentials = new Hashtable();
+ iSelectedAuthCredentials = new Hashtable();
+ iOcspCheckers = new Hashtable();
+ iOcspEventListeners = new Hashtable();
+ // default ocsp settings
+ iOcspSettings = new OcspSettings(
+ OcspSettings.OCSP_MODE_UNDEFINED,
+ OcspSettings.OCSP_WARNING_UNDEFINED,
+ false /* silent*/,
+ "0" /* iap */,
+ "0" /* snap */);
+ iOcspUserPreferences = new OcspUserPreferences();
+ iSecurityWarningsMode = GeneralSecuritySettings
+ .getDefaultSecurityWarningsMode();
+ Logger.log("Ocsp user preferences = " + iOcspUserPreferences.toString());
+ }
+
+ private SigningCertificate retrieveSigningCertificate(SecurityStorage storage, Uid msUID)
+ {
+ SigningCertificate signingCertificate = null;
+ AppAccessAuthorizationStorageData authData = storage
+ .readAppAccessAuthorizationStorageData(msUID,
+ SecurityStorage.APP_ACCESS_AUTH_SIGNERS_LIST_QUERY);
+ if (authData != null && authData.getSignersList() != null
+ && authData.getSignersList().length > 0)
+ {
+ Certificate cert = _parseCertificate(authData
+ .getSignersList()[0]);
+ if (cert != null)
+ {
+ AuthenticationStorageData signingData = storage.
+ readAuthenticationStorageData(msUID);
+ signingCertificate = new SigningCertificate(cert,
+ signingData.getRootHashValue(),
+ signingData.getProtectionDomain(),
+ signingData.getProtectionDomainCategory());
+ }
+ }
+ return signingCertificate;
+ }
+
+ private Vector verifyUpdate(Credentials[] credentials, Uid oldMSUID)
+ {
+ return verifyUpdate(isLegacySuite(oldMSUID), credentials, oldMSUID, false /* don't order the returned credentials */);
+ }
+
+ // Security rules for update:
+ // 1. the old and the new MIDlet suites must be bound to the same
+ // protection domain
+ // 2. Only for MIDP3 MIDlets, the the old and the new MIDlet
+ // suites MUST share at least one common signer (for legacy MIDlets
+ // it is ok not to share a common signer, case in which the update
+ // is decided by the user)
+ //
+ // Common signer is defined as matching the Organization field
+ // within the Subject field of the signing certificate of MIDlet
+ // Suite update and the signing certificate of the original MIDlet
+ // Suite, where the signing certificates are validated against
+ // the same Protection Domain Root Certificate)
+ //
+ // This method discards the credentials which do not obey the rules
+ // mentioned above and returns either all credentials which have same
+ // signer OR all credentials which do NOT have same signer,
+ // but not any combination
+ private Vector verifyUpdate(boolean legacyMIDletSuite, Credentials[] credentials, Uid oldMSUID, boolean orderResult)
+ {
+ Vector allCredentials = new Vector(credentials.length);
+ for (int i=0; i<credentials.length; i++)
+ {
+ allCredentials.addElement(credentials[i]);
+ }
+ if (oldMSUID != null)
+ {
+ SecurityStorage storage = new SecurityStorage();
+ Vector sameSignerCredentials = new Vector();
+ Vector differentSignerCredentials = new Vector();
+ try
+ {
+ ProtectionDomain oldProtectionDomain = new ProtectionDomain(
+ storage.readProtectionDomain(oldMSUID),
+ storage.readProtectionDomainCategory(oldMSUID));
+ for (int i=0; i<credentials.length; i++)
+ {
+ if (oldProtectionDomain.equals(
+ credentials[i].getProtectionDomain()))
+ {
+ SigningCertificate oldSigningCertificate =
+ retrieveSigningCertificate(storage, oldMSUID);
+ SigningCertificate newSigningCertificate =
+ new SigningCertificate(credentials[i].signingCert,
+ credentials[i].rootHashValue,
+ credentials[i].getProtectionDomainName(),
+ credentials[i].getProtectionDomainCategory());
+ if (newSigningCertificate.isSameSigner(
+ oldSigningCertificate))
+ {
+ sameSignerCredentials.addElement(credentials[i]);
+ }
+ else if (legacyMIDletSuite)
+ {
+ differentSignerCredentials.addElement(credentials[i]);
+ }
+ }
+ else if (legacyMIDletSuite
+ && !ApplicationInfo.UNIDENTIFIED_THIRD_PARTY_DOMAIN.equals(
+ credentials[i].getProtectionDomain().getCategory()))
+ {
+ differentSignerCredentials.addElement(credentials[i]);
+ }
+ }
+ if (sameSignerCredentials.size() != 0)
+ {
+ allCredentials = sameSignerCredentials;
+ }
+ else if (differentSignerCredentials.size() != 0)
+ {
+ allCredentials = differentSignerCredentials;
+ }
+ else
+ {
+ throw new InstallerSecurityException(
+ InstallerErrorMessage.INST_AUTHORIZATION_ERR,
+ null, /* no params for short msg */
+ InstallerDetailedErrorMessage.DIFFERENT_SIGNERS,
+ null, /* no params for the detailed msg*/
+ OtaStatusCode.APPLICATION_AUTHORIZATION_FAILURE);
+ }
+ }
+ finally
+ {
+ storage.close();
+ }
+ }
+ if (orderResult)
+ {
+ // put the credentials with domain info first
+ int i=0;
+ int size = allCredentials.size();
+ while (i<size)
+ {
+ Credentials current = (Credentials)allCredentials.elementAt(i);
+ if (current.getProtectionDomainName() == null
+ || current.getProtectionDomainCategory() == null)
+ {
+ // swap the current element with the last one
+ Object last = allCredentials.lastElement();
+ allCredentials.setElementAt(last,i);
+ allCredentials.setElementAt(current,(size-1));
+ size--;
+ }
+ else
+ {
+ i++;
+ }
+ }
+ }
+ return allCredentials;
+ }
+
+ private boolean isLegacySuite(Uid msUID)
+ {
+ if (msUID == null)
+ {
+ return true;
+ }
+ Boolean tmp = (Boolean)iLegacySuiteFlags.remove(msUID);
+ if (tmp != null)
+ {
+ return tmp.booleanValue();
+ }
+ else
+ {
+ SecurityStorage storage = new SecurityStorage();
+ String suiteVersion = storage.readSuiteVersion(msUID);
+ storage.close();
+ if (suiteVersion != null)
+ {
+ boolean legacySuite = !suiteVersion.equalsIgnoreCase(
+ SecurityAttributes.MIDP3_VERSION_ATTRIBUTE_VALUE);
+ iLegacySuiteFlags.put(msUID, new Boolean(legacySuite));
+ return legacySuite;
+ }
+ return true;
+ }
+ }
+
+ private String getRoot(String rootHash)
+ {
+ CommsEndpoint comms = null;
+ try
+ {
+ comms = new CommsEndpoint();
+ comms.connect(CommsEndpoint.JAVA_CAPTAIN);
+ CommsMessage sMessage = new CommsMessage();
+ sMessage.setMessageId(SecurityCommsMessages.JAVA_CERT_STORE_MSG_ID_REQUEST);
+ sMessage.setModuleId(SecurityCommsMessages.PLUGIN_ID_JAVA_CERT_STORE_EXTENSION);
+ sMessage.write(SecurityCommsMessages.JAVA_CERT_STORE_OPERATION_QUERY_CERTS);
+ // add filter
+ sMessage.write(SecurityCommsMessages.JAVA_CERT_STORE_FILTER_ID_STATE);
+ sMessage.write(SecurityCommsMessages.JAVA_CERT_STORE_STATE_ENABLED);
+ sMessage.write(SecurityCommsMessages.JAVA_CERT_STORE_FILTER_ID_HASH);
+ sMessage.write(rootHash);
+ // add the query ID
+ sMessage.write(SecurityCommsMessages.JAVA_CERT_STORE_QUERY_ID_CERT_CONTENT_PEM);
+ CommsMessage rMessage = comms.sendReceive(sMessage, 10);
+ // read the reply
+ String tmp = rMessage.readString();
+ if (tmp != null && tmp.length() > 0)
+ {
+ return rMessage.readString();
+ }
+ }
+ catch (CommsException e)
+ {
+ // fall through
+ }
+ finally
+ {
+ if (comms != null)
+ {
+ comms.destroy();
+ }
+ }
+ return null;
+ }
+
+ private int retrieveRootState(String rootHash)
+ {
+
+ CommsEndpoint comms = null;
+ try
+ {
+ comms = new CommsEndpoint();
+ comms.connect(CommsEndpoint.JAVA_CAPTAIN);
+ CommsMessage sMessage = new CommsMessage();
+ sMessage.setMessageId(SecurityCommsMessages.JAVA_CERT_STORE_MSG_ID_REQUEST);
+ sMessage.setModuleId(SecurityCommsMessages.PLUGIN_ID_JAVA_CERT_STORE_EXTENSION);
+ sMessage.write(SecurityCommsMessages.JAVA_CERT_STORE_OPERATION_QUERY_CERTS);
+ // add filter
+ sMessage.write(SecurityCommsMessages.JAVA_CERT_STORE_FILTER_ID_HASH);
+ sMessage.write(rootHash);
+ // add the query ID
+ sMessage.write(SecurityCommsMessages.JAVA_CERT_STORE_QUERY_ID_STATE);
+ CommsMessage rMessage = comms.sendReceive(sMessage, 10);
+ // read the reply
+ return rMessage.readInt();
+ }
+ catch (CommsException e)
+ {
+ // fall through
+ }
+ finally
+ {
+ if (comms != null)
+ {
+ comms.destroy();
+ }
+ }
+ return SecurityCommsMessages.JAVA_CERT_STORE_STATE_UNKNOWN;
+ }
+
+ private boolean performOcsp()
+ {
+ if (iOcspSettings.ocspMode
+ != OcspSettings.OCSP_MODE_UNDEFINED)
+ {
+ return (iOcspSettings.ocspMode == OcspSettings.OCSP_MODE_ENABLED);
+ }
+ // check the user preferences
+ return (iOcspUserPreferences.getOcspMode()
+ == OcspUserPreferences.OCSP_MODE_ON
+ || iOcspUserPreferences.getOcspMode()
+ == OcspUserPreferences.OCSP_MODE_MUST);
+ }
+
+ private boolean isDriveProtected(int aMediaId)
+ {
+ DriveInfo[] allDrives = DriveUtilities.getAllDrives();
+ if (allDrives != null)
+ {
+ for (int i=0; i<allDrives.length; i++)
+ {
+ if (aMediaId == allDrives[i].iId)
+ {
+ if (allDrives[i].iIsRemovable)
+ {
+ return false;
+ }
+ if (allDrives[i].iIsExternallyMountable)
+ {
+ return false;
+ }
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private Credentials selectCredentials(String selectedJarHash, Vector allAuthCredentials, Vector validatedChainIndexes)
+ {
+ Credentials selectedCredentials = null;
+ if (selectedJarHash != null)
+ {
+ for (int i=0; i<allAuthCredentials.size(); i++)
+ {
+ Credentials authCredentials =
+ (Credentials)allAuthCredentials.elementAt(i);
+ if (selectedJarHash.equalsIgnoreCase(authCredentials.jarHashValue))
+ {
+ // the first one wins
+ if (selectedCredentials == null)
+ {
+ selectedCredentials = new Credentials(
+ authCredentials.getProtectionDomainName(),
+ authCredentials.getProtectionDomainCategory(),
+ authCredentials.jarHashValue,
+ authCredentials.rootHashValue,
+ -1,
+ null);
+ }
+ // collect the validated chain indexes
+ validatedChainIndexes.addElement(
+ new Integer(authCredentials.validatedChainIndex));
+ }
+ }
+ }
+ return selectedCredentials;
+ }
+
+ private void writeAuthenticationStorageData(StorageSession storageSession, Uid uid, AuthenticationStorageData data, boolean isUpdate)
+ {
+ if (storageSession == null)
+ {
+ return;
+ }
+ SecurityStorage storage = new SecurityStorage(storageSession);
+ storage.writeAuthenticationStorageData(uid, data, isUpdate);
+ Logger.logAuthenticationData(data);
+ }
+
+ private native Credentials[] _validateChainsAndSignatures(AuthenticationInfo[] authInfo);
+ private native String _computeHash(String appJARPath);
+ private native String _drmDecryptAndComputeHash(String appJARPath);
+ private native Certificate _parseCertificate(String rawCert);
+ private native Certificate _getRootCertificate(String certHash);
+}