1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 /** 19 * @author Boris Kuznetsov 20 * @version $Revision$ 21 */ 22 package org.apache.harmony.security.utils; 23 24 import java.io.IOException; 25 import java.io.InputStream; 26 import java.math.BigInteger; 27 import java.security.GeneralSecurityException; 28 import java.security.MessageDigest; 29 import java.security.NoSuchAlgorithmException; 30 import java.security.Principal; 31 import java.security.Signature; 32 import java.security.cert.Certificate; 33 import java.security.cert.X509Certificate; 34 import java.util.Arrays; 35 import java.util.Collection; 36 import java.util.LinkedList; 37 import java.util.List; 38 import javax.security.auth.x500.X500Principal; 39 import org.apache.harmony.security.asn1.BerInputStream; 40 import org.apache.harmony.security.pkcs7.ContentInfo; 41 import org.apache.harmony.security.pkcs7.SignedData; 42 import org.apache.harmony.security.pkcs7.SignerInfo; 43 import org.apache.harmony.security.provider.cert.X509CertImpl; 44 import org.apache.harmony.security.x501.AttributeTypeAndValue; 45 import org.apache.harmony.xnet.provider.jsse.OpenSSLProvider; 46 47 public class JarUtils { 48 49 // as defined in PKCS #9: Selected Attribute Types: 50 // http://www.ietf.org/rfc/rfc2985.txt 51 private static final int[] MESSAGE_DIGEST_OID = 52 new int[] {1, 2, 840, 113549, 1, 9, 4}; 53 54 /** 55 * This method handle all the work with PKCS7, ASN1 encoding, signature verifying, 56 * and certification path building. 57 * See also PKCS #7: Cryptographic Message Syntax Standard: 58 * http://www.ietf.org/rfc/rfc2315.txt 59 * @param signature - the input stream of signature file to be verified 60 * @param signatureBlock - the input stream of corresponding signature block file 61 * @return array of certificates used to verify the signature file 62 * @throws IOException - if some errors occurs during reading from the stream 63 * @throws GeneralSecurityException - if signature verification process fails 64 */ 65 public static Certificate[] verifySignature(InputStream signature, InputStream 66 signatureBlock) throws IOException, GeneralSecurityException { 67 68 BerInputStream bis = new BerInputStream(signatureBlock); 69 ContentInfo info = (ContentInfo)ContentInfo.ASN1.decode(bis); 70 SignedData signedData = info.getSignedData(); 71 if (signedData == null) { 72 throw new IOException("No SignedData found"); 73 } 74 Collection<org.apache.harmony.security.x509.Certificate> encCerts 75 = signedData.getCertificates(); 76 if (encCerts.isEmpty()) { 77 return null; 78 } 79 X509Certificate[] certs = new X509Certificate[encCerts.size()]; 80 int i = 0; 81 for (org.apache.harmony.security.x509.Certificate encCert : encCerts) { 82 certs[i++] = new X509CertImpl(encCert); 83 } 84 85 List<SignerInfo> sigInfos = signedData.getSignerInfos(); 86 SignerInfo sigInfo; 87 if (!sigInfos.isEmpty()) { 88 sigInfo = sigInfos.get(0); 89 } else { 90 return null; 91 } 92 93 // Issuer 94 X500Principal issuer = sigInfo.getIssuer(); 95 96 // Certificate serial number 97 BigInteger snum = sigInfo.getSerialNumber(); 98 99 // Locate the certificate 100 int issuerSertIndex = 0; 101 for (i = 0; i < certs.length; i++) { 102 if (issuer.equals(certs[i].getIssuerDN()) && 103 snum.equals(certs[i].getSerialNumber())) { 104 issuerSertIndex = i; 105 break; 106 } 107 } 108 if (i == certs.length) { // No issuer certificate found 109 return null; 110 } 111 112 if (certs[issuerSertIndex].hasUnsupportedCriticalExtension()) { 113 throw new SecurityException("Can not recognize a critical extension"); 114 } 115 116 // Get Signature instance 117 Signature sig = null; 118 String da = sigInfo.getDigestAlgorithm(); 119 String dea = sigInfo.getDigestEncryptionAlgorithm(); 120 String alg = null; 121 if (da != null && dea != null) { 122 alg = da + "with" + dea; 123 try { 124 sig = Signature.getInstance(alg, OpenSSLProvider.PROVIDER_NAME); 125 } catch (NoSuchAlgorithmException e) {} 126 } 127 if (sig == null) { 128 alg = da; 129 if (alg == null) { 130 return null; 131 } 132 try { 133 sig = Signature.getInstance(alg, OpenSSLProvider.PROVIDER_NAME); 134 } catch (NoSuchAlgorithmException e) { 135 return null; 136 } 137 } 138 sig.initVerify(certs[issuerSertIndex]); 139 140 // If the authenticatedAttributes field of SignerInfo contains more than zero attributes, 141 // compute the message digest on the ASN.1 DER encoding of the Attributes value. 142 // Otherwise, compute the message digest on the data. 143 List<AttributeTypeAndValue> atr = sigInfo.getAuthenticatedAttributes(); 144 145 byte[] sfBytes = new byte[signature.available()]; 146 signature.read(sfBytes); 147 148 if (atr == null) { 149 sig.update(sfBytes); 150 } else { 151 sig.update(sigInfo.getEncodedAuthenticatedAttributes()); 152 153 // If the authenticatedAttributes field contains the message-digest attribute, 154 // verify that it equals the computed digest of the signature file 155 byte[] existingDigest = null; 156 for (AttributeTypeAndValue a : atr) { 157 if (Arrays.equals(a.getType().getOid(), MESSAGE_DIGEST_OID)) { 158 //TODO value existingDigest = a.AttributeValue; 159 } 160 } 161 if (existingDigest != null) { 162 MessageDigest md = MessageDigest.getInstance(sigInfo.getDigestAlgorithm()); 163 byte[] computedDigest = md.digest(sfBytes); 164 if (!Arrays.equals(existingDigest, computedDigest)) { 165 throw new SecurityException("Incorrect MD"); 166 } 167 } 168 } 169 170 if (!sig.verify(sigInfo.getEncryptedDigest())) { 171 throw new SecurityException("Incorrect signature"); 172 } 173 174 return createChain(certs[issuerSertIndex], certs); 175 } 176 177 private static X509Certificate[] createChain(X509Certificate signer, X509Certificate[] candidates) { 178 LinkedList chain = new LinkedList(); 179 chain.add(0, signer); 180 181 // Signer is self-signed 182 if (signer.getSubjectDN().equals(signer.getIssuerDN())){ 183 return (X509Certificate[])chain.toArray(new X509Certificate[1]); 184 } 185 186 Principal issuer = signer.getIssuerDN(); 187 X509Certificate issuerCert; 188 int count = 1; 189 while (true) { 190 issuerCert = findCert(issuer, candidates); 191 if( issuerCert == null) { 192 break; 193 } 194 chain.add(issuerCert); 195 count++; 196 if (issuerCert.getSubjectDN().equals(issuerCert.getIssuerDN())) { 197 break; 198 } 199 issuer = issuerCert.getIssuerDN(); 200 } 201 return (X509Certificate[])chain.toArray(new X509Certificate[count]); 202 } 203 204 private static X509Certificate findCert(Principal issuer, X509Certificate[] candidates) { 205 for (int i = 0; i < candidates.length; i++) { 206 if (issuer.equals(candidates[i].getSubjectDN())) { 207 return candidates[i]; 208 } 209 } 210 return null; 211 } 212 213 } 214