001: /*
002: *
003: *
004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: */
026:
027: package com.sun.midp.installer;
028:
029: import java.io.InputStream;
030: import java.io.IOException;
031: import java.util.Vector;
032:
033: import javax.microedition.io.Connector;
034: import javax.microedition.pki.CertificateException;
035: import com.sun.midp.pki.*;
036: import com.sun.midp.publickeystore.*;
037: import com.sun.midp.crypto.*;
038: import com.sun.midp.security.*;
039:
040: import com.sun.midp.io.j2me.storage.RandomAccessStream;
041: import com.sun.midp.io.Base64;
042:
043: /**
044: * Verifier that is able to verify midlet suite's signature.
045: * It is used when the crypto code is present in the build.
046: */
047: public class VerifierImpl implements Verifier {
048: /**
049: * Current installation state.
050: */
051: InstallState state;
052:
053: /**
054: * Authorization Path: A list of authority names from the verification,
055: * begining with the most trusted.
056: */
057: private String[] authPath;
058:
059: /** Authenticated content provider certificate. */
060: private X509Certificate cpCert;
061:
062: /**
063: * Constructor.
064: *
065: * @param state current state of the installation
066: */
067: public VerifierImpl(InstallState installState) {
068: state = installState;
069: }
070:
071: /**
072: * Checks to see if the JAD has a signature, but does not verify the
073: * signature.
074: *
075: * @return true if the JAD has a signature
076: */
077: public boolean isJadSigned() {
078: return state.getAppProperty(SIG_PROP) != null;
079: }
080:
081: /**
082: * Looks up the domain of a MIDlet suite.
083: *
084: * @param ca CA of an installed suite
085: *
086: * @return security domain of the MIDlet suite
087: */
088: public String getSecurityDomainName(String ca) {
089: Vector keys;
090: String domain;
091:
092: /*
093: * look up the domain owner, then get the domain from the
094: * trusted key store and set the security domain
095: */
096: try {
097: keys = WebPublicKeyStore.getTrustedKeyStore().findKeys(ca);
098:
099: domain = ((PublicKeyInfo) keys.elementAt(0)).getDomain();
100: } catch (Exception e) {
101: domain = Permissions.UNIDENTIFIED_DOMAIN_BINDING;
102: }
103:
104: return domain;
105: }
106:
107: /**
108: * Verifies a Jar. On success set the name of the domain owner in the
109: * install state. Post any error back to the server.
110: *
111: * @param jarStorage System store for applications
112: * @param jarFilename name of the jar to read.
113: *
114: * @exception IOException if any error prevents the reading
115: * of the JAR
116: * @exception InvalidJadException if the JAR is not valid or the
117: * provider certificate is missing
118: */
119: public String[] verifyJar(RandomAccessStream jarStorage,
120: String jarFilename) throws IOException, InvalidJadException {
121: InputStream jarStream;
122: String jarSig;
123:
124: jarSig = state.getAppProperty(SIG_PROP);
125: if (jarSig == null) {
126: // no signature to verify
127: return null;
128: }
129:
130: authPath = null;
131:
132: // This will fill in the cpCert and authPath fields
133: findProviderCert();
134:
135: jarStorage.connect(jarFilename, Connector.READ);
136:
137: try {
138: jarStream = jarStorage.openInputStream();
139:
140: try {
141: verifyStream(jarStream, jarSig);
142: // state.installInfo.authPath = authPath;
143: } finally {
144: jarStream.close();
145: }
146: } finally {
147: jarStorage.disconnect();
148: }
149:
150: return authPath;
151: }
152:
153: /**
154: * Find the first provider certificate that is signed by a known CA.
155: * Set the lastCA field to name of the CA. Set the cpCert field to the
156: * provider certificate.
157: *
158: * IMPL_NOTE: in the case of erroneous certificate chains the first
159: * chain error will be thrown.
160: *
161: * @exception InvalidJadException if the JAR is not valid or the
162: * provider certificate is missing or a general certificate error
163: */
164: private void findProviderCert() throws InvalidJadException {
165: int chain;
166: int result;
167: InvalidJadException pendingException = null;
168:
169: for (chain = 1;; chain++) {
170: // sets the authPath and cpCert
171: try {
172: result = checkCertChain(chain);
173: } catch (InvalidJadException ije) {
174: // According to the spec, if some chain is invalid and
175: // the next chain exists, it should also be verified;
176: // the first valid chain should be used for the jar
177: // verification.
178: if (pendingException == null) {
179: pendingException = ije;
180: }
181: continue;
182: }
183:
184: if (result == 1) {
185: // we found the good chain
186: return;
187: }
188:
189: if (result == -1) {
190: // chain not found, done
191: break;
192: }
193: }
194:
195: if (pendingException != null) {
196: throw pendingException;
197: }
198:
199: if (chain == 1) {
200: throw new InvalidJadException(
201: InvalidJadException.MISSING_PROVIDER_CERT);
202: }
203:
204: // None of the certificates were issued by a known CA
205: throw new InvalidJadException(InvalidJadException.UNKNOWN_CA,
206: authPath[0]);
207: }
208:
209: /**
210: * Check to see if a provider certificate chain is issued by a known
211: * CA. Set the authPath field to names of the auth chain in any case.
212: * Authenticate the chain and set the cpCert field to the provider's
213: * certificate if the CA is known.
214: *
215: * @param chainNum the number of the chain
216: *
217: * @return 1 if the CA of the chain is known, 0 if not, -1 if the
218: * chain is not found
219: *
220: * @exception InvalidJadException if something other wrong with a
221: * other than an unknown CA
222: */
223: private int checkCertChain(int chainNum) throws InvalidJadException {
224: int certNum;
225: Vector derCerts = new Vector();
226: String base64Cert;
227: byte[] derCert;
228: WebPublicKeyStore keyStore;
229: Vector keys;
230: PublicKeyInfo keyInfo;
231:
232: for (certNum = 1;; certNum++) {
233: base64Cert = state.getAppProperty(CERT_PROP + chainNum
234: + "-" + certNum);
235: if (base64Cert == null) {
236: break;
237: }
238:
239: try {
240: derCert = Base64.decode(base64Cert);
241: derCerts
242: .addElement(X509Certificate
243: .generateCertificate(derCert, 0,
244: derCert.length));
245: } catch (Exception e) {
246: throw new InvalidJadException(
247: InvalidJadException.CORRUPT_PROVIDER_CERT);
248: }
249: }
250:
251: if (certNum == 1) {
252: // Chain not found
253: return -1;
254: }
255:
256: try {
257: keyStore = WebPublicKeyStore.getTrustedKeyStore();
258: authPath = X509Certificate.verifyChain(derCerts,
259: X509Certificate.DIGITAL_SIG_KEY_USAGE,
260: X509Certificate.CODE_SIGN_EXT_KEY_USAGE, keyStore);
261: } catch (CertificateException ce) {
262: switch (ce.getReason()) {
263: case CertificateException.UNRECOGNIZED_ISSUER:
264: authPath = new String[1];
265: authPath[0] = ce.getCertificate().getIssuer();
266:
267: // Issuer not found
268: return 0;
269:
270: case CertificateException.EXPIRED:
271: case CertificateException.NOT_YET_VALID:
272: throw new InvalidJadException(
273: InvalidJadException.EXPIRED_PROVIDER_CERT, ce
274: .getCertificate().getSubject());
275:
276: case CertificateException.ROOT_CA_EXPIRED:
277: throw new InvalidJadException(
278: InvalidJadException.EXPIRED_CA_KEY, ce
279: .getCertificate().getIssuer());
280: }
281:
282: throw new InvalidJadException(
283: InvalidJadException.INVALID_PROVIDER_CERT, ce
284: .getCertificate().getSubject());
285: }
286:
287: // The root CA may have been disabled for software authorization.
288: keys = keyStore.findKeys(authPath[0]);
289: keyInfo = (PublicKeyInfo) keys.elementAt(0);
290:
291: if (!keyInfo.isEnabled()) {
292: throw new InvalidJadException(
293: InvalidJadException.CA_DISABLED, authPath[0]);
294: }
295:
296: cpCert = (X509Certificate) derCerts.elementAt(0);
297:
298: // Authenticated
299: return 1;
300: }
301:
302: /**
303: * Common routine that verifies a stream of bytes.
304: * The cpCert field must be set before calling.
305: *
306: * @param stream stream to verify
307: * @param base64Signature The base64 encoding of the PKCS v1.5 SHA with
308: * RSA signature of this stream.
309: *
310: * @exception NullPointerException if the public keystore has not been
311: * established.
312: * @exception InvalidJadException the JAR signature is not valid
313: * @exception IOException if any error prevents the reading
314: * of the JAR
315: */
316: private void verifyStream(InputStream stream, String base64Signature)
317: throws InvalidJadException, IOException {
318: PublicKey cpKey;
319: byte[] sig;
320: Signature sigVerifier;
321: byte[] temp;
322: int bytesRead;
323: byte[] hash;
324:
325: try {
326: cpKey = cpCert.getPublicKey();
327: } catch (CertificateException e) {
328: throw new InvalidJadException(
329: InvalidJadException.INVALID_PROVIDER_CERT);
330: }
331:
332: try {
333: sig = Base64.decode(base64Signature);
334: } catch (IOException e) {
335: throw new InvalidJadException(
336: InvalidJadException.CORRUPT_SIGNATURE);
337: }
338:
339: try {
340: // verify the jad signature
341: sigVerifier = Signature.getInstance("SHA1withRSA");
342: sigVerifier.initVerify(cpKey);
343:
344: temp = new byte[1024];
345: for (;;) {
346: bytesRead = stream.read(temp);
347: if (bytesRead == -1) {
348: break;
349: }
350:
351: sigVerifier.update(temp, 0, bytesRead);
352: }
353:
354: if (!sigVerifier.verify(sig)) {
355: throw new InvalidJadException(
356: InvalidJadException.INVALID_SIGNATURE);
357: }
358: } catch (GeneralSecurityException e) {
359: throw new InvalidJadException(
360: InvalidJadException.INVALID_SIGNATURE);
361: }
362: }
363: }
|