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