FileDocCategorySizeDatePackage
SecureInstaller.javaAPI DocJ2ME MIDP 2.09758Thu Nov 07 12:02:24 GMT 2002com.sun.midp.midletsuite

SecureInstaller.java

/*
 * @(#)SecureInstaller.java	1.9 02/09/17 @(#)
 *
 * Copyright (c) 2000-2002 Sun Microsystems, Inc.  All rights reserved.
 * PROPRIETARY/CONFIDENTIAL
 * Use is subject to license terms.
 */

package com.sun.midp.midletsuite;

import java.io.IOException;
import java.io.InputStream;
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.io.DataOutputStream;
import java.io.DataInputStream;

import java.lang.String;
import java.lang.IllegalArgumentException;

import java.util.Vector;

import javax.microedition.io.Connector;
import javax.microedition.io.Connection;
import javax.microedition.io.HttpConnection;

import javax.microedition.pki.CertificateException;

import com.sun.midp.ssl.*;

import com.sun.midp.io.*;

import com.sun.midp.io.j2me.storage.*;

import com.sun.midp.publickeystore.*;

import com.sun.midp.security.*;

/**
 * This class is able to verify JAD's signed according to the wireless
 * extension specification.
 */
public class SecureInstaller extends Installer {
    /**
     * MIDlet property for the application signature
     */
    private static final String SIG_PROP = "MIDlet-Jar-RSA-SHA1";

    /**
     * MIDlet property for the content provider certificates
     */
    private static final String CERT_PROP =
        "MIDlet-Certificate-";

    /** Set to the issuer of last certificate chain checked. */
    private String lastCa;

    /** Authenticated content provider certificate. */
    private X509Certificate cpCert;

    /**
     * Looks up the domain of a MIDlet suite.
     *
     * @param storageName storage name of a MIDlet suite
     * @param ca CA of an installed suite
     *
     * @return security domain of the MIDlet suite
     */
    protected String getSecurityDomainName(String storageName, String ca) {
        Vector keys;
        String domain;

        /*
         * look up the domain owner, then get the domain from the
         * trusted key store and set the security domain
         */
        try {
            keys = WebPublicKeyStore.getTrustedKeyStore().
                         findKeys(ca);

            domain = ((PublicKeyInfo)keys.elementAt(0)).getDomain();
        } catch (Exception e) {
            domain = "untrusted";
        }

        return domain;
    }

    /**
     * Verifies a Jar. On success set the name of the domain owner in the
     * install state. Post any error back to the server.
     *
     * @param jarStorage System store for applications
     * @param jarFilename name of the jar to read.
     *
     * @exception IOException if any error prevents the reading
     *   of the JAR
     * @exception InvalidJadException if the JAR is not valid or the
     *   provider certificate is missing
     */
    protected void verifyJar(RandomAccessStream jarStorage,
            String jarFilename) throws IOException, InvalidJadException {
        InputStream jarStream;
        String jarSig;

        jarSig = state.getAppProperty(SIG_PROP);
        if (jarSig == null) {
            // no signature to verify
            return;
        }

        findProviderCert(); // This will fill in the cpCert and lastCa fields
        jarStorage.connect(jarFilename, Connector.READ);

        try {
            jarStream = jarStorage.openInputStream();
            try {
                verifyStream(jarStream, jarSig);
                state.ca = lastCa;
            } finally {
                jarStream.close();
            }
        } finally {
            jarStorage.disconnect();
        }
    }

    /**
     * Find the first provider certificate that is signed by a known CA.
     * Set the lastCA field to name of the CA. Set the cpCert field to the
     * provider certificate.
     *
     * @exception InvalidJadException if the JAR is not valid or the
     *   provider certificate is missing or a general certificate error
     */
    private void findProviderCert() throws InvalidJadException {
        int chain;
        int result;

        /*
         * To save memory for applications that do not use PKI,
         * the public keys of the certificate authorities may not
         * have been loaded yet.
         */
        WebPublicKeyStore.loadCertificateAuthorities();

        for (chain = 1; ; chain++) {
            result = checkCertChain(chain); // sets the lastCa and cpCert
            if (result == 1) {
                // we found the good chain
                return;
            }

            if (result == -1) {
                // chain not found, done
                break;
            }
        }

        if (chain == 1) {
            throw new
                InvalidJadException(InvalidJadException.MISSING_PROVIDER_CERT);
        }

        // None of the certificates were issued by a known CA
        throw new
            InvalidJadException(InvalidJadException.UNKNOWN_CA, lastCa);
    }

    /**
     * Check to see if a provider certificate chain is issued by a known
     * CA. Set the lastCA field to name of the CA in any case.
     * Authenticate the chain and set the cpCert field to the provider's
     * certificate if the CA is known.
     *
     * @param chainNum the number of the chain
     *
     * @return 1 if the CA of the chain is known, 0 if not, -1 if the
     *    chain is not found
     *
     * @exception InvalidJadException if something other wrong with a
     *   other than an unknown CA
     */
    private int checkCertChain(int chainNum) throws InvalidJadException {
        int certNum;
        Vector derCerts = new Vector();
        String base64Cert;
        byte[] derCert;

        for (certNum = 1; ; certNum++) {
            base64Cert = state.getAppProperty(CERT_PROP + 
                                              chainNum + "-" + certNum);
            if (base64Cert == null) {
                break;
            }

            try {
                derCert = Base64.decode(base64Cert);
                derCerts.addElement(X509Certificate.generateCertificate(
                    derCert, 0, derCert.length));
            } catch (Exception e) {
                throw new InvalidJadException(
                    InvalidJadException.CORRUPT_PROVIDER_CERT);
            }
        }

        if (certNum == 1) {
            // Chain not found
            return -1;
        }

        try {
            lastCa = X509Certificate.verifyChain(derCerts,
                         X509Certificate.DIGITAL_SIG_KEY_USAGE,
                         X509Certificate.CODE_SIGN_EXT_KEY_USAGE,
                         WebPublicKeyStore.getTrustedKeyStore()).getIssuer();
            cpCert = (X509Certificate)derCerts.elementAt(0);

            // Authenticated
            return 1;
        } catch (CertificateException ce) {
            switch (ce.getReason()) {
            case CertificateException.UNRECOGNIZED_ISSUER:
                lastCa = ce.getCertificate().getIssuer();

                // Issuer not found
                return 0;

            case CertificateException.EXPIRED:
            case CertificateException.NOT_YET_VALID:
                throw new InvalidJadException(
                    InvalidJadException.EXPIRED_PROVIDER_CERT,
                    ce.getCertificate().getSubject());

            case CertificateException.ROOT_CA_EXPIRED:
                throw new InvalidJadException(
                    InvalidJadException.EXPIRED_CA_KEY,
                    ce.getCertificate().getIssuer());
            }

            throw new InvalidJadException(
                InvalidJadException.INVALID_PROVIDER_CERT,
                ce.getCertificate().getSubject());
        }
    }

    /**
     * Common routine that verifies a stream of bytes.
     * The cpCert field must be set before calling.
     *
     * @param stream stream to verify
     * @param base64Signature The base64 encoding of the PKCS v1.5 SHA with
     *        RSA signature of this stream.
     *
     * @exception NullPointerException if the public keystore has not been
     *            established.
     * @exception InvalidJadException the JAR signature is not valid
     * @exception IOException if any error prevents the reading
     *   of the JAR
     */
    private void verifyStream(InputStream stream, String base64Signature)
            throws InvalidJadException, IOException {
        PublicKey cpKey;
        byte[] sig;
        Signature sigVerifier;
        byte[] temp;
        int bytesRead;
        byte[] hash;

        try {
            cpKey = cpCert.getPublicKey();
        } catch (CertificateException e) {
            throw new
                InvalidJadException(InvalidJadException.INVALID_PROVIDER_CERT);
        }

        try {
            sig = Base64.decode(base64Signature);
        } catch (IOException e) {
            throw new
                InvalidJadException(InvalidJadException.CORRUPT_SIGNATURE);
        }

        try {
            // verify the jad signature
            sigVerifier = Signature.getInstance(Signature.ALG_RSA_SHA_PKCS1,
                                                false);
            sigVerifier.init(cpKey, Signature.MODE_VERIFY);

            temp = new byte[1024];
            for (; ; ) {
                bytesRead = stream.read(temp);
                if (bytesRead == -1) {
                    break;
                }

                sigVerifier.update(temp, 0, bytesRead);
            }
            
            if (!sigVerifier.verify(null, 0, 0,
                    sig, (short)0, (short)sig.length)) {
                throw new
                    InvalidJadException(InvalidJadException.INVALID_SIGNATURE);
            }
        } catch (CryptoException e) {
            throw new
                InvalidJadException(InvalidJadException.INVALID_SIGNATURE);
        }
    }
}