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*/ 22package org.apache.harmony.security.utils; 23 24import java.io.IOException; 25import java.io.InputStream; 26import java.math.BigInteger; 27import java.security.GeneralSecurityException; 28import java.security.MessageDigest; 29import java.security.NoSuchAlgorithmException; 30import java.security.Principal; 31import java.security.Signature; 32import java.security.cert.Certificate; 33import java.security.cert.X509Certificate; 34import java.util.Arrays; 35import java.util.Collection; 36import java.util.LinkedList; 37import java.util.List; 38import javax.security.auth.x500.X500Principal; 39 40import org.apache.harmony.security.asn1.ASN1OctetString; 41import org.apache.harmony.security.asn1.BerInputStream; 42import org.apache.harmony.security.pkcs7.ContentInfo; 43import org.apache.harmony.security.pkcs7.SignedData; 44import org.apache.harmony.security.pkcs7.SignerInfo; 45import org.apache.harmony.security.provider.cert.X509CertImpl; 46import org.apache.harmony.security.x501.AttributeTypeAndValue; 47 48public class JarUtils { 49 50 // as defined in PKCS #9: Selected Attribute Types: 51 // http://www.ietf.org/rfc/rfc2985.txt 52 private static final int[] MESSAGE_DIGEST_OID = 53 new int[] {1, 2, 840, 113549, 1, 9, 4}; 54 55 /** 56 * This method handle all the work with PKCS7, ASN1 encoding, signature verifying, 57 * and certification path building. 58 * See also PKCS #7: Cryptographic Message Syntax Standard: 59 * http://www.ietf.org/rfc/rfc2315.txt 60 * @param signature - the input stream of signature file to be verified 61 * @param signatureBlock - the input stream of corresponding signature block file 62 * @return array of certificates used to verify the signature file 63 * @throws IOException - if some errors occurs during reading from the stream 64 * @throws GeneralSecurityException - if signature verification process fails 65 */ 66 public static Certificate[] verifySignature(InputStream signature, InputStream 67 signatureBlock) throws IOException, GeneralSecurityException { 68 69 BerInputStream bis = new BerInputStream(signatureBlock); 70 ContentInfo info = (ContentInfo)ContentInfo.ASN1.decode(bis); 71 SignedData signedData = info.getSignedData(); 72 if (signedData == null) { 73 throw new IOException("No SignedData found"); 74 } 75 Collection<org.apache.harmony.security.x509.Certificate> encCerts 76 = signedData.getCertificates(); 77 if (encCerts.isEmpty()) { 78 return null; 79 } 80 X509Certificate[] certs = new X509Certificate[encCerts.size()]; 81 int i = 0; 82 for (org.apache.harmony.security.x509.Certificate encCert : encCerts) { 83 certs[i++] = new X509CertImpl(encCert); 84 } 85 86 List<SignerInfo> sigInfos = signedData.getSignerInfos(); 87 SignerInfo sigInfo; 88 if (!sigInfos.isEmpty()) { 89 sigInfo = sigInfos.get(0); 90 } else { 91 return null; 92 } 93 94 // Issuer 95 X500Principal issuer = sigInfo.getIssuer(); 96 97 // Certificate serial number 98 BigInteger snum = sigInfo.getSerialNumber(); 99 100 // Locate the certificate 101 int issuerSertIndex = 0; 102 for (i = 0; i < certs.length; i++) { 103 if (issuer.equals(certs[i].getIssuerDN()) && 104 snum.equals(certs[i].getSerialNumber())) { 105 issuerSertIndex = i; 106 break; 107 } 108 } 109 if (i == certs.length) { // No issuer certificate found 110 return null; 111 } 112 113 if (certs[issuerSertIndex].hasUnsupportedCriticalExtension()) { 114 throw new SecurityException("Can not recognize a critical extension"); 115 } 116 117 // Get Signature instance 118 final String daOid = sigInfo.getDigestAlgorithm(); 119 final String daName = sigInfo.getDigestAlgorithmName(); 120 final String deaOid = sigInfo.getDigestEncryptionAlgorithm(); 121 122 String alg = null; 123 Signature sig = null; 124 125 if (daOid != null && deaOid != null) { 126 alg = daOid + "with" + deaOid; 127 try { 128 sig = Signature.getInstance(alg); 129 } catch (NoSuchAlgorithmException e) { 130 } 131 132 // Try to convert to names instead of OID. 133 if (sig == null) { 134 final String deaName = sigInfo.getDigestEncryptionAlgorithmName(); 135 alg = daName + "with" + deaName; 136 try { 137 sig = Signature.getInstance(alg); 138 } catch (NoSuchAlgorithmException e) { 139 } 140 } 141 } 142 143 /* 144 * TODO figure out the case in which we'd only use digestAlgorithm and 145 * add a test for it. 146 */ 147 if (sig == null && daOid != null) { 148 alg = daOid; 149 try { 150 sig = Signature.getInstance(alg); 151 } catch (NoSuchAlgorithmException e) { 152 } 153 154 if (sig == null && daName != null) { 155 alg = daName; 156 try { 157 sig = Signature.getInstance(alg); 158 } catch (NoSuchAlgorithmException e) { 159 } 160 } 161 } 162 163 // We couldn't find a valid Signature type. 164 if (sig == null) { 165 return null; 166 } 167 168 sig.initVerify(certs[issuerSertIndex]); 169 170 // If the authenticatedAttributes field of SignerInfo contains more than zero attributes, 171 // compute the message digest on the ASN.1 DER encoding of the Attributes value. 172 // Otherwise, compute the message digest on the data. 173 List<AttributeTypeAndValue> atr = sigInfo.getAuthenticatedAttributes(); 174 175 byte[] sfBytes = new byte[signature.available()]; 176 signature.read(sfBytes); 177 178 if (atr == null) { 179 sig.update(sfBytes); 180 } else { 181 sig.update(sigInfo.getEncodedAuthenticatedAttributes()); 182 183 // If the authenticatedAttributes field contains the message-digest attribute, 184 // verify that it equals the computed digest of the signature file 185 byte[] existingDigest = null; 186 for (AttributeTypeAndValue a : atr) { 187 if (Arrays.equals(a.getType().getOid(), MESSAGE_DIGEST_OID)) { 188 if (existingDigest != null) { 189 throw new SecurityException("Too many MessageDigest attributes"); 190 } 191 Collection<?> entries = a.getValue().getValues(ASN1OctetString.getInstance()); 192 if (entries.size() != 1) { 193 throw new SecurityException("Too many values for MessageDigest attribute"); 194 } 195 existingDigest = (byte[]) entries.iterator().next(); 196 } 197 } 198 199 // RFC 3852 section 9.2: it authAttrs is present, it must have a 200 // message digest entry. 201 if (existingDigest == null) { 202 throw new SecurityException("Missing MessageDigest in Authenticated Attributes"); 203 } 204 205 MessageDigest md = null; 206 if (daOid != null) { 207 md = MessageDigest.getInstance(daOid); 208 } 209 if (md == null && daName != null) { 210 md = MessageDigest.getInstance(daName); 211 } 212 if (md == null) { 213 return null; 214 } 215 216 byte[] computedDigest = md.digest(sfBytes); 217 if (!Arrays.equals(existingDigest, computedDigest)) { 218 throw new SecurityException("Incorrect MD"); 219 } 220 } 221 222 if (!sig.verify(sigInfo.getEncryptedDigest())) { 223 throw new SecurityException("Incorrect signature"); 224 } 225 226 return createChain(certs[issuerSertIndex], certs); 227 } 228 229 private static X509Certificate[] createChain(X509Certificate signer, X509Certificate[] candidates) { 230 LinkedList chain = new LinkedList(); 231 chain.add(0, signer); 232 233 // Signer is self-signed 234 if (signer.getSubjectDN().equals(signer.getIssuerDN())){ 235 return (X509Certificate[])chain.toArray(new X509Certificate[1]); 236 } 237 238 Principal issuer = signer.getIssuerDN(); 239 X509Certificate issuerCert; 240 int count = 1; 241 while (true) { 242 issuerCert = findCert(issuer, candidates); 243 if( issuerCert == null) { 244 break; 245 } 246 chain.add(issuerCert); 247 count++; 248 if (issuerCert.getSubjectDN().equals(issuerCert.getIssuerDN())) { 249 break; 250 } 251 issuer = issuerCert.getIssuerDN(); 252 } 253 return (X509Certificate[])chain.toArray(new X509Certificate[count]); 254 } 255 256 private static X509Certificate findCert(Principal issuer, X509Certificate[] candidates) { 257 for (int i = 0; i < candidates.length; i++) { 258 if (issuer.equals(candidates[i].getSubjectDN())) { 259 return candidates[i]; 260 } 261 } 262 return null; 263 } 264 265} 266