001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: /**
019: * @author Boris Kuznetsov
020: * @version $Revision$
021: */package org.apache.harmony.security.utils;
022:
023: import java.io.IOException;
024: import java.io.InputStream;
025: import java.math.BigInteger;
026: import java.security.GeneralSecurityException;
027: import java.security.MessageDigest;
028: import java.security.NoSuchAlgorithmException;
029: import java.security.Principal;
030: import java.security.Signature;
031: import java.security.cert.Certificate;
032: import java.security.cert.X509Certificate;
033: import java.util.Arrays;
034: import java.util.Collection;
035: import java.util.Iterator;
036: import java.util.LinkedList;
037: import java.util.List;
038:
039: import javax.security.auth.x500.X500Principal;
040:
041: import org.apache.harmony.security.asn1.BerInputStream;
042: import org.apache.harmony.security.internal.nls.Messages;
043: import org.apache.harmony.security.pkcs7.ContentInfo;
044: import org.apache.harmony.security.pkcs7.SignedData;
045: import org.apache.harmony.security.pkcs7.SignerInfo;
046: import org.apache.harmony.security.provider.cert.X509CertImpl;
047: import org.apache.harmony.security.x501.AttributeTypeAndValue;
048:
049: public class JarUtils {
050:
051: // as defined in PKCS #9: Selected Attribute Types:
052: // http://www.ietf.org/rfc/rfc2985.txt
053: private static final int[] MESSAGE_DIGEST_OID = new int[] { 1, 2,
054: 840, 113549, 1, 9, 4 };
055:
056: /**
057: * This method handle all the work with PKCS7, ASN1 encoding, signature verifying,
058: * and certification path building.
059: * See also PKCS #7: Cryptographic Message Syntax Standard:
060: * http://www.ietf.org/rfc/rfc2315.txt
061: * @param signature - the input stream of signature file to be verified
062: * @param signatureBlock - the input stream of corresponding signature block file
063: * @return array of certificates used to verify the signature file
064: * @throws IOException - if some errors occurs during reading from the stream
065: * @throws GeneralSecurityException - if signature verification process fails
066: */
067: public static Certificate[] verifySignature(InputStream signature,
068: InputStream signatureBlock) throws IOException,
069: GeneralSecurityException {
070:
071: BerInputStream bis = new BerInputStream(signatureBlock);
072: ContentInfo info = (ContentInfo) ContentInfo.ASN1.decode(bis);
073: SignedData signedData = info.getSignedData();
074: if (signedData == null) {
075: throw new IOException(Messages.getString("security.173")); //$NON-NLS-1$
076: }
077: Collection encCerts = signedData.getCertificates();
078: if (encCerts.isEmpty()) {
079: return null;
080: }
081: X509Certificate[] certs = new X509Certificate[encCerts.size()];
082: int i = 0;
083: for (Iterator it = encCerts.iterator(); it.hasNext();) {
084: certs[i++] = new X509CertImpl(
085: (org.apache.harmony.security.x509.Certificate) it
086: .next());
087: }
088:
089: List sigInfos = signedData.getSignerInfos();
090: SignerInfo sigInfo;
091: if (!sigInfos.isEmpty()) {
092: sigInfo = (SignerInfo) sigInfos.get(0);
093: } else {
094: return null;
095: }
096:
097: // Issuer
098: X500Principal issuer = sigInfo.getIssuer();
099:
100: // Certificate serial number
101: BigInteger snum = sigInfo.getSerialNumber();
102:
103: // Locate the certificate
104: int issuerSertIndex = 0;
105: for (i = 0; i < certs.length; i++) {
106: if (issuer.equals(certs[i].getIssuerDN())
107: && snum.equals(certs[i].getSerialNumber())) {
108: issuerSertIndex = i;
109: break;
110: }
111: }
112: if (i == certs.length) { // No issuer certificate found
113: return null;
114: }
115:
116: if (certs[issuerSertIndex].hasUnsupportedCriticalExtension()) {
117: throw new SecurityException(Messages
118: .getString("security.174")); //$NON-NLS-1$
119: }
120:
121: // Get Signature instance
122: Signature sig = null;
123: String da = sigInfo.getdigestAlgorithm();
124: String dea = sigInfo.getDigestEncryptionAlgorithm();
125: String alg = null;
126: if (da != null && dea != null) {
127: alg = da + "with" + dea; //$NON-NLS-1$
128: try {
129: sig = Signature.getInstance(alg);
130: } catch (NoSuchAlgorithmException e) {
131: }
132: }
133: if (sig == null) {
134: alg = da;
135: if (alg == null) {
136: return null;
137: }
138: try {
139: sig = Signature.getInstance(alg);
140: } catch (NoSuchAlgorithmException e) {
141: return null;
142: }
143: }
144: sig.initVerify(certs[issuerSertIndex]);
145:
146: // If the authenticatedAttributes field of SignerInfo contains more than zero attributes,
147: // compute the message digest on the ASN.1 DER encoding of the Attributes value.
148: // Otherwise, compute the message digest on the data.
149: List atr = sigInfo.getAuthenticatedAttributes();
150:
151: byte[] sfBytes = new byte[signature.available()];
152: signature.read(sfBytes);
153:
154: if (atr == null) {
155: sig.update(sfBytes);
156: } else {
157: sig.update(sigInfo.getEncodedAuthenticatedAttributes());
158:
159: // If the authenticatedAttributes field contains the message-digest attribute,
160: // verify that it equals the computed digest of the signature file
161: byte[] existingDigest = null;
162: for (Iterator it = atr.iterator(); it.hasNext();) {
163: AttributeTypeAndValue a = (AttributeTypeAndValue) it
164: .next();
165: if (Arrays.equals(a.getType().getOid(),
166: MESSAGE_DIGEST_OID)) {
167: //TODO value existingDigest = a.AttributeValue;
168: }
169: }
170: if (existingDigest != null) {
171: MessageDigest md = MessageDigest.getInstance(sigInfo
172: .getDigestAlgorithm());
173: byte[] computedDigest = md.digest(sfBytes);
174: if (!Arrays.equals(existingDigest, computedDigest)) {
175: throw new SecurityException(Messages
176: .getString("security.175")); //$NON-NLS-1$
177: }
178: }
179: }
180:
181: if (!sig.verify(sigInfo.getEncryptedDigest())) {
182: throw new SecurityException(Messages
183: .getString("security.176")); //$NON-NLS-1$
184: }
185:
186: return createChain(certs[issuerSertIndex], certs);
187: }
188:
189: private static X509Certificate[] createChain(
190: X509Certificate signer, X509Certificate[] candidates) {
191: LinkedList chain = new LinkedList();
192: chain.add(0, signer);
193:
194: // Signer is self-signed
195: if (signer.getSubjectDN().equals(signer.getIssuerDN())) {
196: return (X509Certificate[]) chain
197: .toArray(new X509Certificate[1]);
198: }
199:
200: Principal issuer = signer.getIssuerDN();
201: X509Certificate issuerCert;
202: int count = 1;
203: while (true) {
204: issuerCert = findCert(issuer, candidates);
205: if (issuerCert == null) {
206: break;
207: }
208: chain.add(issuerCert);
209: count++;
210: if (issuerCert.getSubjectDN().equals(
211: issuerCert.getIssuerDN())) {
212: break;
213: }
214: issuer = issuerCert.getIssuerDN();
215: }
216: return (X509Certificate[]) chain
217: .toArray(new X509Certificate[count]);
218: }
219:
220: private static X509Certificate findCert(Principal issuer,
221: X509Certificate[] candidates) {
222: for (int i = 0; i < candidates.length; i++) {
223: if (issuer.equals(candidates[i].getSubjectDN())) {
224: return candidates[i];
225: }
226: }
227: return null;
228: }
229:
230: }
|