Index: java/net/URLClassLoader.java =================================================================== RCS file: /cvsroot/classpath/classpath/java/net/URLClassLoader.java,v retrieving revision 1.21 diff -u -B -b -r1.21 URLClassLoader.java --- java/net/URLClassLoader.java 23 Apr 2004 21:13:20 -0000 1.21 +++ java/net/URLClassLoader.java 13 Sep 2004 03:01:21 -0000 @@ -359,7 +359,11 @@ Certificate[] getCertificates() { - return entry.getCertificates(); + // We have to get the entry from the jar file again, because the + // certificates will not be available until the entire entry has + // been read. + return ((JarEntry) ((JarURLLoader) loader).jarfile.getEntry(name)) + .getCertificates(); } URL getURL() @@ -810,8 +814,9 @@ // And finally construct the class! SecurityManager sm = System.getSecurityManager(); + Class result = null; if (sm != null && securityContext != null) - return (Class) AccessController.doPrivileged(new PrivilegedAction() + result = (Class) AccessController.doPrivileged(new PrivilegedAction() { public Object run() { @@ -820,7 +825,10 @@ } }, securityContext); else - return defineClass(className, classData, 0, classData.length, source); + result = defineClass(className, classData, 0, classData.length, source); + + super.setSigners(result, resource.getCertificates()); + return result; } catch (IOException ioe) { Index: java/util/jar/JarFile.java =================================================================== RCS file: /cvsroot/classpath/classpath/java/util/jar/JarFile.java,v retrieving revision 1.10 diff -u -B -b -r1.10 JarFile.java --- java/util/jar/JarFile.java 22 Apr 2004 15:16:13 -0000 1.10 +++ java/util/jar/JarFile.java 13 Sep 2004 03:01:22 -0000 @@ -37,11 +37,38 @@ package java.util.jar; +import gnu.java.io.Base64InputStream; +import gnu.java.security.OID; +import gnu.java.security.pkcs.PKCS7SignedData; +import gnu.java.security.pkcs.SignerInfo; + +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; +import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; + +import java.security.InvalidKeyException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.Signature; +import java.security.SignatureException; +import java.security.cert.CRLException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; import java.util.Enumeration; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + import java.util.zip.ZipEntry; import java.util.zip.ZipException; import java.util.zip.ZipFile; @@ -52,11 +79,11 @@ * Note that this class is not a subclass of java.io.File but a subclass of * java.util.zip.ZipFile and you can only read JarFiles with it (although * there are constructors that take a File object). - *

- * XXX - verification of Manifest signatures is not yet implemented. * * @since 1.2 * @author Mark Wielaard (address@hidden) + * @author Casey Marshall (address@hidden) wrote the certificate and entry + * verification code. */ public class JarFile extends ZipFile { @@ -65,6 +92,29 @@ /** The name of the manifest entry: META-INF/MANIFEST.MF */ public static final String MANIFEST_NAME = "META-INF/MANIFEST.MF"; + /** The META-INF directory entry. */ + private static final String META_INF = "META-INF/"; + + /** The suffix for PKCS7 DSA signature entries. */ + private static final String PKCS7_DSA_SUFFIX = ".DSA"; + + /** The suffix for PKCS7 RSA signature entries. */ + private static final String PKCS7_RSA_SUFFIX = ".RSA"; + + /** The suffix for digest attributes. */ + private static final String DIGEST_KEY_SUFFIX = "-Digest"; + + /** The suffix for signature files. */ + private static final String SF_SUFFIX = ".SF"; + + // Signature OIDs. + private static final OID MD2_OID = new OID("1.2.840.113549.2.2"); + private static final OID MD4_OID = new OID("1.2.840.113549.2.4"); + private static final OID MD5_OID = new OID("1.2.840.113549.2.5"); + private static final OID SHA1_OID = new OID("1.3.14.3.2.26"); + private static final OID DSA_ENCRYPTION_OID = new OID("1.2.840.10040.4.1"); + private static final OID RSA_ENCRYPTION_OID = new OID("1.2.840.113549.1.1.1"); + /** * The manifest of this file, if any, otherwise null. * Read when first needed. @@ -77,6 +127,24 @@ /** Whether the has already been loaded. */ private boolean manifestRead = false; + /** Whether the signature files have been loaded. */ + private boolean signaturesRead = false; + + /** A map between entry names and booleans, signaling whether or + not that entry has been verified. */ + private HashMap verified = new HashMap(); + + /** A mapping from entry name to certificates, if any. */ + private HashMap entryCerts; + + private static boolean DEBUG = false; + private static void debug(Object msg) + { + System.err.print(JarFile.class.getName()); + System.err.print(" >>> "); + System.err.println(msg); + } + // Constructors /** @@ -241,7 +309,6 @@ /** * Wraps a given Zip Entries Enumeration. For every zip entry a * JarEntry is created and the corresponding Attributes are looked up. - * XXX - Should also look up the certificates. */ private class JarEnumeration implements Enumeration { @@ -276,7 +343,33 @@ { jar.attr = manifest.getAttributes(jar.getName()); } - // XXX jar.certs + + if (!signaturesRead) + try + { + readSignatures(); + } + catch (IOException ioe) + { + if (DEBUG) + { + debug(ioe); + ioe.printStackTrace(); + } + signaturesRead = true; // fudge it. + } + + // Include the certificates only if we have asserted that the + // signatures are valid. This means the certificates will not be + // available if the entry hasn't been read yet. + if (entryCerts != null && verified.containsKey(zip.getName()) + && ((Boolean) verified.get(zip.getName())).booleanValue()) + { + Set certs = (Set) entryCerts.get(jar.getName()); + if (certs != null) + jar.certs = (Certificate[]) + certs.toArray(new Certificate[certs.size()]); + } return jar; } } @@ -305,7 +398,34 @@ if (manifest != null) { jarEntry.attr = manifest.getAttributes(name); - // XXX jarEntry.certs + } + + if (!signaturesRead) + try + { + readSignatures(); + } + catch (IOException ioe) + { + if (DEBUG) + { + debug(ioe); + ioe.printStackTrace(); + } + signaturesRead = true; + } + // See the comments in the JarEnumeration for why we do this + // check. + if (DEBUG) + debug("entryCerts=" + entryCerts + " verified " + name + + " ? " + verified.get(name)); + if (entryCerts != null && verified.containsKey(name) + && ((Boolean) verified.get(name)).booleanValue()) + { + Set certs = (Set) entryCerts.get(name); + if (certs != null) + jarEntry.certs = (Certificate[]) + certs.toArray(new Certificate[certs.size()]); } return jarEntry; } @@ -313,15 +433,32 @@ } /** - * XXX should verify the inputstream - * @param entry XXX + * Returns an input stream for the given entry. If configured to + * verify entries, the input stream returned will verify them while + * the stream is read, but only on the first time. + * + * @param entry The entry to get the input stream for. * @exception ZipException XXX * @exception IOException XXX */ public synchronized InputStream getInputStream(ZipEntry entry) throws ZipException, IOException { - return super.getInputStream(entry); // XXX verify + // If we haven't verified the hash, do it now. + if (!verified.containsKey(entry.getName()) && verify) + { + if (DEBUG) + debug("reading and verifying " + entry); + return new EntryInputStream(entry); + } + else + { + if (DEBUG) + debug("reading already verified entry " + entry); + if (!((Boolean) verified.get(entry.getName())).booleanValue()) + throw new ZipException("digest for " + entry + " is invalid"); + return super.getInputStream(entry); + } } /** @@ -349,4 +486,535 @@ return manifest; } + + private void readSignatures() throws IOException + { + Map pkcs7Dsa = new HashMap(); + Map pkcs7Rsa = new HashMap(); + Map sigFiles = new HashMap(); + + // Phase 1: Read all signature files. These contain the user + // certificates as well as the signatures themselves. + for (Enumeration e = super.entries(); e.hasMoreElements(); ) + { + ZipEntry ze = (ZipEntry) e.nextElement(); + String name = ze.getName(); + if (name.startsWith(META_INF)) + { + String alias = name.substring(META_INF.length()); + if (alias.lastIndexOf('.') >= 0) + alias = alias.substring(0, alias.lastIndexOf('.')); + + if (name.endsWith(PKCS7_DSA_SUFFIX) || name.endsWith(PKCS7_RSA_SUFFIX)) + { + if (DEBUG) + debug("reading PKCS7 info from " + name + ", alias=" + alias); + PKCS7SignedData sig = null; + try + { + sig = new PKCS7SignedData(super.getInputStream(ze)); + } + catch (CertificateException ce) + { + IOException ioe = new IOException("certificate parsing error"); + ioe.initCause(ce); + throw ioe; + } + catch (CRLException crle) + { + IOException ioe = new IOException("CRL parsing error"); + ioe.initCause(crle); + throw ioe; + } + if (name.endsWith(PKCS7_DSA_SUFFIX)) + pkcs7Dsa.put(alias, sig); + else if (name.endsWith(PKCS7_RSA_SUFFIX)) + pkcs7Rsa.put(alias, sig); + } + else if (name.endsWith(SF_SUFFIX)) + { + if (DEBUG) + debug("reading signature file for " + alias + ": " + name); + Manifest sf = new Manifest(super.getInputStream(ze)); + sigFiles.put(alias, sf); + if (DEBUG) + debug("result: " + sf); + } + } + } + + // Phase 2: verify the signatures on any signature files. + Set validCerts = new HashSet(); + Map entryCerts = new HashMap(); + for (Iterator it = sigFiles.entrySet().iterator(); it.hasNext(); ) + { + int valid = 0; + Map.Entry e = (Map.Entry) it.next(); + String alias = (String) e.getKey(); + + PKCS7SignedData sig = (PKCS7SignedData) pkcs7Dsa.get(alias); + if (sig != null) + { + Certificate[] certs = sig.getCertificates(); + Set signerInfos = sig.getSignerInfos(); + for (Iterator it2 = signerInfos.iterator(); it2.hasNext(); ) + verify(certs, (SignerInfo) it2.next(), alias, validCerts); + } + + sig = (PKCS7SignedData) pkcs7Rsa.get(alias); + if (sig != null) + { + Certificate[] certs = sig.getCertificates(); + Set signerInfos = sig.getSignerInfos(); + for (Iterator it2 = signerInfos.iterator(); it2.hasNext(); ) + verify(certs, (SignerInfo) it2.next(), alias, validCerts); + } + + // It isn't a signature for anything. Punt it. + if (validCerts.isEmpty()) + { + it.remove(); + continue; + } + + entryCerts.put(e.getValue(), new HashSet(validCerts)); + validCerts.clear(); + } + + // Phase 3: verify the signature file signatures against the manifest, + // mapping the entry name to the target certificates. + this.entryCerts = new HashMap(); + for (Iterator it = entryCerts.entrySet().iterator(); it.hasNext(); ) + { + Map.Entry e = (Map.Entry) it.next(); + Manifest sigfile = (Manifest) e.getKey(); + Map entries = sigfile.getEntries(); + Set certificates = (Set) e.getValue(); + + for (Iterator it2 = entries.entrySet().iterator(); it2.hasNext(); ) + { + Map.Entry e2 = (Map.Entry) it2.next(); + String entryname = String.valueOf(e2.getKey()); + Attributes attr = (Attributes) e2.getValue(); + if (verifyHashes(entryname, attr)) + { + if (DEBUG) + debug("entry " + entryname + " has certificates " + certificates); + Set s = (Set) this.entryCerts.get(entryname); + if (s != null) + s.addAll(certificates); + else + this.entryCerts.put(entryname, new HashSet(certificates)); + } + } + } + + signaturesRead = true; + } + + /** + * Tell if the given signer info is over the given alias's signature file, + * given one of the certificates specified. + */ + private void verify(Certificate[] certs, SignerInfo signerInfo, + String alias, Set validCerts) + { + Signature sig = null; + try + { + OID alg = signerInfo.getDigestEncryptionAlgorithmId(); + if (alg.equals(DSA_ENCRYPTION_OID)) + { + if (!signerInfo.getDigestAlgorithmId().equals(SHA1_OID)) + return; + sig = Signature.getInstance("SHA1withDSA"); + } + else if (alg.equals(RSA_ENCRYPTION_OID)) + { + OID hash = signerInfo.getDigestAlgorithmId(); + if (hash.equals(MD2_OID)) + sig = Signature.getInstance("md2WithRsaEncryption"); + else if (hash.equals(MD4_OID)) + sig = Signature.getInstance("md4WithRsaEncryption"); + else if (hash.equals(MD5_OID)) + sig = Signature.getInstance("md5WithRsaEncryption"); + else if (hash.equals(SHA1_OID)) + sig = Signature.getInstance("sha1WithRsaEncryption"); + else + return; + } + } + catch (NoSuchAlgorithmException nsae) + { + if (DEBUG) + { + debug(nsae); + nsae.printStackTrace(); + } + return; + } + ZipEntry sigFileEntry = super.getEntry(META_INF + alias + SF_SUFFIX); + if (sigFileEntry == null) + return; + for (int i = 0; i < certs.length; i++) + { + if (!(certs[i] instanceof X509Certificate)) + continue; + X509Certificate cert = (X509Certificate) certs[i]; + if (!cert.getIssuerX500Principal().equals(signerInfo.getIssuer()) || + !cert.getSerialNumber().equals(signerInfo.getSerialNumber())) + continue; + try + { + sig.initVerify(cert.getPublicKey()); + InputStream in = super.getInputStream(sigFileEntry); + if (in == null) + continue; + byte[] buf = new byte[1024]; + int len = 0; + while ((len = in.read(buf)) != -1) + sig.update(buf, 0, len); + if (sig.verify(signerInfo.getEncryptedDigest())) + { + if (DEBUG) + debug("signature for " + cert.getSubjectDN() + " is good"); + validCerts.add(cert); + } + } + catch (IOException ioe) + { + continue; + } + catch (InvalidKeyException ike) + { + continue; + } + catch (SignatureException se) + { + continue; + } + } + } + + /** + * Verifies that the digest(s) in a signature file were, in fact, made + * over the manifest entry for ENTRY. + * + * @param entry The entry name. + * @param attr The attributes from the signature file to verify. + */ + private boolean verifyHashes(String entry, Attributes attr) + { + int verified = 0; + + // The bytes for ENTRY's manifest entry, which are signed in the + // signature file. + byte[] entryBytes = null; + try + { + entryBytes = readManifestEntry(super.getEntry(entry)); + } + catch (IOException ioe) + { + if (DEBUG) + { + debug(ioe); + ioe.printStackTrace(); + } + return false; + } + + for (Iterator it = attr.entrySet().iterator(); it.hasNext(); ) + { + Map.Entry e = (Map.Entry) it.next(); + String key = String.valueOf(e.getKey()); + if (!key.endsWith(DIGEST_KEY_SUFFIX)) + continue; + String alg = key.substring(0, key.length() - DIGEST_KEY_SUFFIX.length()); + try + { + byte[] hash = Base64InputStream.decode((String) e.getValue()); + MessageDigest md = MessageDigest.getInstance(alg); + md.update(entryBytes); + byte[] hash2 = md.digest(); + if (DEBUG) + debug("verifying SF entry " + entry + " alg: " + md.getAlgorithm() + + " expect=" + new java.math.BigInteger(hash).toString(16) + + " comp=" + new java.math.BigInteger(hash2).toString(16)); + if (!Arrays.equals(hash, hash2)) + return false; + verified++; + } + catch (IOException ioe) + { + if (DEBUG) + { + debug(ioe); + ioe.printStackTrace(); + } + return false; + } + catch (NoSuchAlgorithmException nsae) + { + if (DEBUG) + { + debug(nsae); + nsae.printStackTrace(); + } + return false; + } + } + + // We have to find at least one valid digest. + return verified > 0; + } + + /** + * Read the raw bytes that comprise a manifest entry. We can't use the + * Manifest object itself, because that loses information (such as line + * endings, and order of entries). + */ + private byte[] readManifestEntry(ZipEntry entry) throws IOException + { + InputStream in = super.getInputStream(super.getEntry(MANIFEST_NAME)); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] target = ("Name: " + entry.getName()).getBytes(); + int t = 0, c, prev = -1, state = 0, l = -1; + + while ((c = in.read()) != -1) + { +// if (DEBUG) +// debug("read " +// + (c == '\n' ? "\\n" : (c == '\r' ? "\\r" : String.valueOf((char) c))) +// + " state=" + state + " prev=" +// + (prev == '\n' ? "\\n" : (prev == '\r' ? "\\r" : String.valueOf((char) prev))) +// + " t=" + t + (t < target.length ? (" target[t]=" + (char) target[t]) : "") +// + " l=" + l); + switch (state) + { + + // Step 1: read until we find the "target" bytes: the start + // of the entry we need to read. + case 0: + if (((byte) c) != target[t]) + t = 0; + else + { + t++; + if (t == target.length) + { + out.write(target); + state = 1; + } + } + break; + + // Step 2: assert that there is a newline character after + // the "target" bytes. + case 1: + if (c != '\n' && c != '\r') + { + out.reset(); + t = 0; + state = 0; + } + else + { + out.write(c); + state = 2; + } + break; + + // Step 3: read this whole entry, until we reach an empty + // line. + case 2: + if (c == '\n') + { + out.write(c); + // NL always terminates a line. + if (l == 0 || (l == 1 && prev == '\r')) + return out.toByteArray(); + l = 0; + } + else + { + // Here we see a blank line terminated by a CR, + // followed by the next entry. Technically, `c' should + // always be 'N' at this point. + if (l == 1 && prev == '\r') + return out.toByteArray(); + out.write(c); + l++; + } + prev = c; + break; + + default: + throw new RuntimeException("this statement should be unreachable"); + } + } + + // The last entry, with a single CR terminating the line. + if (state == 2 && prev == '\r' && l == 0) + return out.toByteArray(); + + // We should not reach this point, we didn't find the entry (or, possibly, + // it is the last entry and is malformed). + throw new IOException("could not find " + entry + " in manifest"); + } + + /** + * A utility class that verifies jar entries as they are read. + */ + private class EntryInputStream extends FilterInputStream + { + private final long length; + private long pos; + private final ZipEntry entry; + private final byte[][] hashes; + private final MessageDigest[] md; + private boolean checked; + + EntryInputStream(final ZipEntry entry) throws IOException + { + super(JarFile.super.getInputStream(entry)); + this.entry = entry; + + length = entry.getSize(); + pos = 0; + checked = false; + + Attributes attr = manifest.getAttributes(entry.getName()); + if (DEBUG) + debug("verifying entry " + entry + " attr=" + attr); + if (attr == null) + { + hashes = new byte[0][]; + md = new MessageDigest[0]; + } + else + { + List hashes = new LinkedList(); + List md = new LinkedList(); + for (Iterator it = attr.entrySet().iterator(); it.hasNext(); ) + { + Map.Entry e = (Map.Entry) it.next(); + String key = String.valueOf(e.getKey()); + if (key == null) + continue; + if (!key.endsWith(DIGEST_KEY_SUFFIX)) + continue; + hashes.add(Base64InputStream.decode((String) e.getValue())); + try + { + md.add(MessageDigest.getInstance + (key.substring(0, key.length() - DIGEST_KEY_SUFFIX.length()))); + } + catch (NoSuchAlgorithmException nsae) + { + IOException ioe = new IOException("no such message digest: " + key); + ioe.initCause(nsae); + throw ioe; + } + } + if (DEBUG) + debug("digests=" + md); + this.hashes = (byte[][]) hashes.toArray(new byte[hashes.size()][]); + this.md = (MessageDigest[]) md.toArray(new MessageDigest[md.size()]); + } + } + + public boolean markSupported() + { + return false; + } + + public void mark(int readLimit) + { + } + + public void reset() + { + } + + public int read() throws IOException + { + int b = super.read(); + if (b == -1) + { + eof(); + return -1; + } + for (int i = 0; i < md.length; i++) + md[i].update((byte) b); + pos++; + if (length > 0 && pos >= length) + eof(); + return b; + } + + public int read(byte[] buf, int off, int len) throws IOException + { + int count = super.read(buf, off, (int) Math.min(len, (length != 0 + ? length - pos + : Integer.MAX_VALUE))); + if (count == -1 || (length > 0 && pos >= length)) + { + eof(); + return -1; + } + for (int i = 0; i < md.length; i++) + md[i].update(buf, off, count); + pos += count; + if (length != 0 && pos >= length) + eof(); + return count; + } + + public int read(byte[] buf) throws IOException + { + return read(buf, 0, buf.length); + } + + public long skip(long bytes) throws IOException + { + byte[] b = new byte[1024]; + long amount = 0; + while (amount < bytes) + { + int l = read(b, 0, (int) Math.min(b.length, bytes - amount)); + if (l == -1) + break; + amount += l; + } + return amount; + } + + private void eof() throws IOException + { + if (checked) + return; + checked = true; + for (int i = 0; i < md.length; i++) + { + byte[] hash = md[i].digest(); + if (DEBUG) + debug("verifying " + md[i].getAlgorithm() + " expect=" + + new java.math.BigInteger(hashes[i]).toString(16) + + " comp=" + new java.math.BigInteger(hash).toString(16)); + if (!Arrays.equals(hash, hashes[i])) + { + if (DEBUG) + debug(entry + " could NOT be verified"); + verified.put(entry.getName(), Boolean.FALSE); + return; + // XXX ??? what do we do here? + // throw new ZipException("message digest mismatch"); + } + } + if (DEBUG) + debug(entry + " has been VERIFIED"); + verified.put(entry.getName(), Boolean.TRUE); + } + } } Index: java/util/zip/InflaterInputStream.java =================================================================== RCS file: /cvsroot/classpath/classpath/java/util/zip/InflaterInputStream.java,v retrieving revision 1.13 diff -u -B -b -r1.13 InflaterInputStream.java --- java/util/zip/InflaterInputStream.java 15 Jun 2004 10:37:14 -0000 1.13 +++ java/util/zip/InflaterInputStream.java 13 Sep 2004 03:01:22 -0000 @@ -246,4 +246,18 @@ return skipped; } + + public boolean markSupported() + { + return false; + } + + public void mark(int readLimit) + { + } + + public void reset() throws IOException + { + throw new IOException("reset not supported"); + } } Index: gnu/java/io/Base64InputStream.java =================================================================== RCS file: /cvsroot/classpath/classpath/gnu/java/io/Base64InputStream.java,v retrieving revision 1.2 diff -u -B -b -r1.2 Base64InputStream.java --- gnu/java/io/Base64InputStream.java 23 Apr 2004 21:13:20 -0000 1.2 +++ gnu/java/io/Base64InputStream.java 13 Sep 2004 03:01:22 -0000 @@ -1,5 +1,5 @@ /* Base64InputStream.java -- base-64 input stream. - Copyright (C) 2003 Free Software Foundation, Inc. + Copyright (C) 2003, 2004 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -38,6 +38,8 @@ package gnu.java.io; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; @@ -90,6 +92,30 @@ eof = false; } + // Class method. + // ------------------------------------------------------------------------ + + /** + * Decode a single Base-64 string to a byte array. + * + * @param base64 The Base-64 encoded data. + * @return The decoded bytes. + * @throws IOException If the given data do not compose a valid Base-64 + * sequence. + */ + public static byte[] decode(String base64) throws IOException + { + Base64InputStream in = + new Base64InputStream(new ByteArrayInputStream(base64.getBytes())); + ByteArrayOutputStream out = + new ByteArrayOutputStream((int) (base64.length() / 0.666)); + byte[] buf = new byte[1024]; + int len; + while ((len = in.read(buf)) != -1) + out.write(buf, 0, len); + return out.toByteArray(); + } + // Instance methods. // ------------------------------------------------------------------------ Index: gnu/java/security/der/DERReader.java =================================================================== RCS file: /cvsroot/classpath/classpath/gnu/java/security/der/DERReader.java,v retrieving revision 1.3 diff -u -B -b -r1.3 DERReader.java --- gnu/java/security/der/DERReader.java 14 Aug 2004 17:51:42 -0000 1.3 +++ gnu/java/security/der/DERReader.java 13 Sep 2004 03:01:22 -0000 @@ -62,15 +62,15 @@ * * @author Casey Marshall (address@hidden) */ -public final class DERReader implements DER +public class DERReader implements DER { // Fields. // ------------------------------------------------------------------------ - private InputStream in; + protected InputStream in; - private final ByteArrayOutputStream encBuf; + protected final ByteArrayOutputStream encBuf; // Constructor. // ------------------------------------------------------------------------ @@ -185,6 +185,26 @@ return value; } + protected int readLength() throws IOException + { + int i = in.read(); + if (i == -1) + throw new EOFException(); + encBuf.write(i); + if ((i & ~0x7F) == 0) + { + return i; + } + else if (i < 0xFF) + { + byte[] octets = new byte[i & 0x7F]; + in.read(octets); + encBuf.write(octets); + return new BigInteger(1, octets).intValue(); + } + throw new DEREncodingException(); + } + // Own methods. // ------------------------------------------------------------------------ @@ -236,26 +256,6 @@ } } - private int readLength() throws IOException - { - int i = in.read(); - if (i == -1) - throw new EOFException(); - encBuf.write(i); - if ((i & ~0x7F) == 0) - { - return i; - } - else if (i < 0xFF) - { - byte[] octets = new byte[i & 0x7F]; - in.read(octets); - encBuf.write(octets); - return new BigInteger(1, octets).intValue(); - } - throw new DEREncodingException(); - } - private static String makeString(int tag, byte[] value) throws IOException { Index: gnu/java/security/ber/BER.java =================================================================== RCS file: gnu/java/security/ber/BER.java diff -N gnu/java/security/ber/BER.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ gnu/java/security/ber/BER.java 13 Sep 2004 03:01:22 -0000 @@ -0,0 +1,46 @@ +/* BER.java -- basic encoding rules (BER) constants. + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package gnu.java.security.ber; + +import gnu.java.security.der.DER; + +public interface BER extends DER +{ + BERValue END_OF_SEQUENCE = new BERValue(0, null); +} Index: gnu/java/security/ber/BEREncodingException.java =================================================================== RCS file: gnu/java/security/ber/BEREncodingException.java diff -N gnu/java/security/ber/BEREncodingException.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ gnu/java/security/ber/BEREncodingException.java 13 Sep 2004 03:01:22 -0000 @@ -0,0 +1,54 @@ +/* BEREncodingException.java --- BER Encoding Exception + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package gnu.java.security.ber; + +import gnu.java.security.der.DEREncodingException; + +public class BEREncodingException extends DEREncodingException +{ + public BEREncodingException() + { + super (); + } + + public BEREncodingException (String msg) + { + super (msg); + } +} Index: gnu/java/security/ber/BERReader.java =================================================================== RCS file: gnu/java/security/ber/BERReader.java diff -N gnu/java/security/ber/BERReader.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ gnu/java/security/ber/BERReader.java 13 Sep 2004 03:01:22 -0000 @@ -0,0 +1,103 @@ +/* BERReader.java -- basic encoding rules (BER) reader. + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package gnu.java.security.ber; + +import gnu.java.security.der.DERReader; +import gnu.java.security.der.DERValue; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; + +public class BERReader extends DERReader implements BER +{ + + /** + * Create a new DER reader from a byte array. + * + * @param in The encoded bytes. + */ + public BERReader(byte[] in) + { + super(in); + } + + public BERReader (byte[] in, int off, int len) + { + super(in, off, len); + } + + /** + * Create a new DER readed from an input stream. + * + * @param in The encoded bytes. + */ + public BERReader(InputStream in) + { + super(in); + } + + public DERValue read() throws IOException + { + in.mark(2); + int tag = in.read(); + if (tag == -1) + throw new EOFException(); + int length = in.read(); + if (length == 0) + { + if (tag == 0) + return END_OF_SEQUENCE; + return new BERValue(tag, CONSTRUCTED_VALUE, new byte[] { (byte) tag, 0 }); + } + else + { + in.reset(); + return super.read(); + } + } + + public int peek() throws IOException + { + in.mark(1); + int ret = in.read(); + in.reset(); + return ret; + } +} Index: gnu/java/security/ber/BERValue.java =================================================================== RCS file: gnu/java/security/ber/BERValue.java diff -N gnu/java/security/ber/BERValue.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ gnu/java/security/ber/BERValue.java 13 Sep 2004 03:01:22 -0000 @@ -0,0 +1,82 @@ +/* BERReader.java -- basic encoding rules (BER) value. + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package gnu.java.security.ber; + +import gnu.java.security.der.DERValue; + +public class BERValue extends DERValue +{ + + private boolean indefinite; + + public BERValue(int tag, Object value, byte[] encoded) + { + super(tag, 0, value, encoded); + indefinite = true; + } + + public BERValue(int tag, int length, Object value, byte[] encoded) + { + super(tag, length, value, encoded); + } + + public BERValue(int tag, Object value) + { + super(tag, 0, value, null); + } + + public static boolean isIndefinite(DERValue value) + { + if (value instanceof BERValue) + return ((BERValue) value).getIndefinite(); + return false; + } + + public boolean getIndefinite() + { + return indefinite; + } + + public int getLength() + { + if (indefinite) + return 0; + return super.getLength(); + } +} Index: gnu/java/security/pkcs/PKCS7SignedData.java =================================================================== RCS file: gnu/java/security/pkcs/PKCS7SignedData.java diff -N gnu/java/security/pkcs/PKCS7SignedData.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ gnu/java/security/pkcs/PKCS7SignedData.java 13 Sep 2004 03:01:22 -0000 @@ -0,0 +1,363 @@ +/* PKCS7SignedData.java -- reader for PKCS#7 signedData objects. + Copyright (C) 2004 Free Software Foundation, Inc. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package gnu.java.security.pkcs; + +import gnu.java.security.OID; +import gnu.java.security.ber.BER; +import gnu.java.security.ber.BEREncodingException; +import gnu.java.security.ber.BERReader; +import gnu.java.security.ber.BERValue; +import gnu.java.security.der.DERValue; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import java.math.BigInteger; + +import java.security.cert.CRL; +import java.security.cert.CRLException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +/** + * The SignedData object in PKCS #7. This is a read-only implementation of + * this format, and is used to provide signed Jar file support. + * + * @author Casey Marshall (address@hidden) + */ +public class PKCS7SignedData +{ + + public static final OID PKCS7_DATA = new OID("1.2.840.113549.1.7.1"); + public static final OID PKCS7_SIGNED_DATA = new OID("1.2.840.113549.1.7.2"); + + private BigInteger version; + private Set digestAlgorithms; + private OID contentType; + private byte[] content; + private Certificate[] certificates; + private CRL[] crls; + private Set signerInfos; + + private static final boolean DEBUG = false; + private static void debug(String msg) + { + System.err.print("PKCS7SignedData >> "); + System.err.println(msg); + } + + public PKCS7SignedData(InputStream in) + throws CRLException, CertificateException, IOException + { + this(new BERReader(in)); + } + + /** + * Parse an encoded PKCS#7 SignedData object. The ASN.1 format of this + * object is: + * + *

+   * SignedData ::= SEQUENCE {
+   *   version Version,
+   *   digestAlgorithms DigestAlgorithmIdentifiers,
+   *   contentInfo ContentInfo,
+   *   certificates
+   *     [0] IMPLICIT ExtendedCertificatesAndCertificates OPTIONAL,
+   *   crls
+   *     [1] IMPLICIT CertificateRevocationLists OPTIONAL,
+   *   signerInfos SignerInfos }
+   *
+   * Version ::= INTEGER
+   *
+   * DigestAlgorithmIdentifiers ::= SET OF DigestAlgorithmIdentifier
+   *
+   * DigestAlgorithmIdentifier ::= AlgorithmIdentifier
+   *
+   * ContentInfo ::= SEQUENCE {
+   *   contentType ContentType,
+   *   content [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL }
+   *
+   * ContentType ::= OBJECT IDENTIFIER
+   *
+   * ExtendedCertificatesAndCertificates ::=
+   *   SET OF ExtendedCertificatesAndCertificate
+   *
+   * ExtendedCertificatesAndCertificate ::= CHOICE {
+   *   certificate Certificate, -- from X.509
+   *   extendedCertificate [0] IMPLICIT ExtendedCertificate }
+   *
+   * CertificateRevocationLists ::= SET OF CertificateRevocationList
+   *   -- from X.509
+   *
+   * SignerInfos ::= SET OF SignerInfo
+   *
+   * SignerInfo ::= SEQUENCE {
+   *   version Version,
+   *   issuerAndSerialNumber IssuerAndSerialNumber,
+   *   digestAlgorithm DigestAlgorithmIdentifier,
+   *   authenticatedAttributes
+   *     [0] IMPLICIT Attributes OPTIONAL,
+   *   digestEncryptionAlgorithm DigestEncryptionAlgorithmIdentifier,
+   *   encryptedDigest EncryptedDigest,
+   *   unauthenticatedAttributes
+   *     [1] IMPLICIT Attributes OPTIONAL }
+   *
+   * EncryptedDigest ::= OCTET STRING
+   * 
+ * + *

(Readers who are confused as to why it takes 40 levels of indirection + * to specify "data with a signature", rest assured that the present author + * is as confused as you are).

+ */ + public PKCS7SignedData(BERReader ber) + throws CRLException, CertificateException, IOException + { + CertificateFactory x509 = CertificateFactory.getInstance("X509"); + DERValue val = ber.read(); + if (!val.isConstructed()) + throw new BEREncodingException("malformed ContentInfo"); + + val = ber.read(); + if (val.getTag() != BER.OBJECT_IDENTIFIER) + throw new BEREncodingException("malformed ContentType"); + + if (!PKCS7_SIGNED_DATA.equals(val.getValue())) + throw new BEREncodingException("content is not SignedData"); + + val = ber.read(); + if (val.getTag() != 0) + throw new BEREncodingException("malformed Content"); + + val = ber.read(); + if (!val.isConstructed()) + throw new BEREncodingException("malformed SignedData"); + + if (DEBUG) + debug("SignedData: " + val); + + val = ber.read(); + if (val.getTag() != BER.INTEGER) + throw new BEREncodingException("expecting Version"); + version = (BigInteger) val.getValue(); + + if (DEBUG) + debug(" Version: " + version); + + digestAlgorithms = new HashSet(); + val = ber.read(); + if (!val.isConstructed()) + throw new BEREncodingException("malformed DigestAlgorithmIdentifiers"); + if (DEBUG) + debug(" DigestAlgorithmIdentifiers: " + val); + int count = 0; + DERValue val2 = ber.read(); + while (val2 != BER.END_OF_SEQUENCE && + (val.getLength() > 0 && val.getLength() > count)) + { + if (!val2.isConstructed()) + throw new BEREncodingException("malformed AlgorithmIdentifier"); + if (DEBUG) + debug(" AlgorithmIdentifier: " + val2); + count += val2.getEncodedLength(); + val2 = ber.read(); + if (val2.getTag() != BER.OBJECT_IDENTIFIER) + throw new BEREncodingException("malformed AlgorithmIdentifier"); + if (DEBUG) + debug(" ID: " + val2.getValue()); + List algId = new ArrayList(2); + algId.add(val2.getValue()); + val2 = ber.read(); + if (val2 != BER.END_OF_SEQUENCE) + { + count += val2.getEncodedLength(); + if (val2.getTag() == BER.NULL) + algId.add(null); + else + algId.add(val2.getEncoded()); + if (DEBUG) + debug(" params: " + new BigInteger(1, val2.getEncoded()).toString(16)); + if (val2.isConstructed()) + ber.skip(val2.getLength()); + if (BERValue.isIndefinite(val)) + val2 = ber.read(); + } + else + algId.add(null); + digestAlgorithms.add(algId); + } + + val = ber.read(); + if (!val.isConstructed()) + throw new BEREncodingException("malformed ContentInfo"); + if (DEBUG) + debug(" ContentInfo: " + val); + val2 = ber.read(); + if (val2.getTag() != BER.OBJECT_IDENTIFIER) + throw new BEREncodingException("malformed ContentType"); + contentType = (OID) val2.getValue(); + if (DEBUG) + debug(" ContentType: " + contentType); + if (BERValue.isIndefinite(val) + || (val.getLength() > 0 && val.getLength() > val2.getEncodedLength())) + { + val2 = ber.read(); + if (val2 != BER.END_OF_SEQUENCE) + { + content = val2.getEncoded(); + if (BERValue.isIndefinite(val)) + val2 = ber.read(); + if (DEBUG) + debug(" Content: " + new BigInteger(1, content).toString(16)); + } + } + + val = ber.read(); + if (val.getTag() == 0) + { + if (!val.isConstructed()) + throw new BEREncodingException("malformed ExtendedCertificatesAndCertificates"); + if (DEBUG) + debug(" ExtendedCertificatesAndCertificates: " + val); + count = 0; + val2 = ber.read(); + List certs = new LinkedList(); + while (val2 != BER.END_OF_SEQUENCE && + (val.getLength() > 0 && val.getLength() > count)) + { + Certificate cert = + x509.generateCertificate(new ByteArrayInputStream(val2.getEncoded())); + if (DEBUG) + debug(" Certificate: " + cert); + certs.add(cert); + count += val2.getEncodedLength(); + ber.skip(val2.getLength()); + if (BERValue.isIndefinite(val) || val.getLength() > count) + val2 = ber.read(); + } + certificates = (Certificate[]) certs.toArray(new Certificate[certs.size()]); + val = ber.read(); + } + + if (val.getTag() == 1) + { + if (!val.isConstructed()) + throw new BEREncodingException("malformed CertificateRevocationLists"); + if (DEBUG) + debug(" CertificateRevocationLists: " + val); + count = 0; + val2 = ber.read(); + List crls = new LinkedList(); + while (val2 != BER.END_OF_SEQUENCE && + (val.getLength() > 0 && val.getLength() > count)) + { + CRL crl = x509.generateCRL(new ByteArrayInputStream(val2.getEncoded())); + if (DEBUG) + debug (" CRL: " + crl); + crls.add(crl); + count += val2.getEncodedLength(); + ber.skip(val2.getLength()); + if (BERValue.isIndefinite(val) || val.getLength() > count) + val2 = ber.read(); + } + this.crls = (CRL[]) crls.toArray(new CRL[crls.size()]); + val = ber.read(); + } + + signerInfos = new HashSet(); + if (!val.isConstructed()) + throw new BEREncodingException("malformed SignerInfos"); + + if (DEBUG) + debug(" SignerInfos: " + val); + + // FIXME read this more carefully. + // Since we are just reading a file (probably) we just read until we + // reach the end. + while (true) + { + int i = ber.peek(); + if (i == 0 || i == -1) + break; + signerInfos.add(new SignerInfo(ber)); + } + } + + public BigInteger getVersion() + { + return version; + } + + public Certificate[] getCertificates() + { + return (certificates != null ? (Certificate[]) certificates.clone() + : null); + } + + public OID getContentType() + { + return contentType; + } + + public byte[] getContent() + { + return (content != null ? (byte[]) content.clone() : null); + } + + public Set getDigestAlgorithms() + { + // FIXME copy contents too, they are mutable!!! + return Collections.unmodifiableSet(digestAlgorithms); + } + + public Set getSignerInfos() + { + Set copy = new HashSet(); + for (Iterator it = signerInfos.iterator(); it.hasNext(); ) + copy.add(it.next()); + return Collections.unmodifiableSet(copy); + } +} Index: gnu/java/security/pkcs/SignerInfo.java =================================================================== RCS file: gnu/java/security/pkcs/SignerInfo.java diff -N gnu/java/security/pkcs/SignerInfo.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ gnu/java/security/pkcs/SignerInfo.java 13 Sep 2004 03:01:22 -0000 @@ -0,0 +1,280 @@ +/* SignerInfo.java -- a SignerInfo object, from PKCS #7. + Copyright (C) 2004 Free Software Foundation, Inc. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package gnu.java.security.pkcs; + +import gnu.java.security.OID; +import gnu.java.security.ber.BER; +import gnu.java.security.ber.BEREncodingException; +import gnu.java.security.ber.BERReader; +import gnu.java.security.ber.BERValue; +import gnu.java.security.der.BitString; +import gnu.java.security.der.DERValue; + +import java.io.IOException; +import java.io.InputStream; + +import java.math.BigInteger; + +import javax.security.auth.x500.X500Principal; + +public class SignerInfo +{ + private final BigInteger version; + private final BigInteger serialNumber; + private final X500Principal issuer; + private final OID digestAlgorithmId; + private final byte[] digestAlgorithmParams; + private final byte[] authenticatedAttributes; + private final OID digestEncryptionAlgorithmId; + private final byte[] digestEncryptionAlgorithmParams; + private final byte[] encryptedDigest; + private final byte[] unauthenticatedAttributes; + + private static final boolean DEBUG = false; + private static void debug(String msg) + { + System.err.print("SignerInfo >> "); + System.err.println(msg); + } + + /** + * Parse a SignerInfo object. + */ + public SignerInfo(BERReader ber) throws IOException + { + DERValue val = ber.read(); + if (DEBUG) + debug("SignerInfo: " + val); + if (!val.isConstructed()) + throw new BEREncodingException("malformed SignerInfo"); + + val = ber.read(); + if (val.getTag() != BER.INTEGER) + throw new BEREncodingException("malformed Version"); + version = (BigInteger) val.getValue(); + + if (DEBUG) + debug(" Version: " + version); + + val = ber.read(); + if (!val.isConstructed()) + throw new BEREncodingException("malformed IssuerAndSerialNumber"); + + if (DEBUG) + debug(" IssuerAndSerialNumber: " + val); + + val = ber.read(); + if (!val.isConstructed()) + throw new BEREncodingException("malformed Issuer"); + issuer = new X500Principal(val.getEncoded()); + ber.skip(val.getLength()); + if (DEBUG) + debug(" Issuer: " + issuer); + + val = ber.read(); + if (val.getTag() != BER.INTEGER) + throw new BEREncodingException("malformed SerialNumber"); + serialNumber = (BigInteger) val.getValue(); + if (DEBUG) + debug(" SerialNumber: " + serialNumber); + + val = ber.read(); + if (!val.isConstructed()) + throw new BEREncodingException("malformed DigestAlgorithmIdentifier"); + if (DEBUG) + debug(" DigestAlgorithmIdentifier: " + val); + + int count = 0; + DERValue val2 = ber.read(); + if (val2.getTag() != BER.OBJECT_IDENTIFIER) + throw new BEREncodingException("malformed AlgorithmIdentifier"); + digestAlgorithmId = (OID) val2.getValue(); + if (DEBUG) + debug(" OID: " + digestAlgorithmId); + + if (BERValue.isIndefinite(val)) + { + val2 = ber.read(); + if (val2 != BER.END_OF_SEQUENCE) + { + digestAlgorithmParams = val2.getEncoded(); + val2 = ber.read(); + if (val2 != BER.END_OF_SEQUENCE) + throw new BEREncodingException("expecting BER end-of-sequence"); + } + else + digestAlgorithmParams = null; + } + else if (val2.getEncodedLength() < val.getLength()) + { + val2 = ber.read(); + digestAlgorithmParams = val2.getEncoded(); + if (val2.isConstructed()) + ber.skip(val2.getLength()); + } + else + digestAlgorithmParams = null; + if(DEBUG) + debug(" params: " + (digestAlgorithmParams == null ? null + : new BigInteger(digestAlgorithmParams).toString(16))); + + val = ber.read(); + if (val.getTag() == 0) + { + authenticatedAttributes = val.getEncoded(); + val = ber.read(); + if (val.isConstructed()) + ber.skip(val.getLength()); + if (DEBUG) + debug(" AuthenticatedAttributes: " + val); + val = ber.read(); + } + else + authenticatedAttributes = null; + + if (!val.isConstructed()) + throw new BEREncodingException("malformed DigestEncryptionAlgorithmIdentifier"); + if (DEBUG) + debug(" DigestEncryptionAlgorithmIdentifier: " + val); + count = 0; + val2 = ber.read(); + if (val2.getTag() != BER.OBJECT_IDENTIFIER) + throw new BEREncodingException("malformed AlgorithmIdentifier"); + digestEncryptionAlgorithmId = (OID) val2.getValue(); + if (DEBUG) + debug(" OID: " + digestEncryptionAlgorithmId); + + if (BERValue.isIndefinite(val)) + { + val2 = ber.read(); + if (val2 != BER.END_OF_SEQUENCE) + { + digestEncryptionAlgorithmParams = val2.getEncoded(); + val2 = ber.read(); + if (val2 != BER.END_OF_SEQUENCE) + throw new BEREncodingException("expecting BER end-of-sequence"); + } + else + digestEncryptionAlgorithmParams = null; + } + else if (val2.getEncodedLength() < val.getLength()) + { + val2 = ber.read(); + digestEncryptionAlgorithmParams = val2.getEncoded(); + if (val2.isConstructed()) + ber.skip(val2.getLength()); + } + else + digestEncryptionAlgorithmParams = null; + if(DEBUG) + debug(" params: " + (digestEncryptionAlgorithmParams == null ? null + : new BigInteger(digestEncryptionAlgorithmParams).toString(16))); + + val = ber.read(); + if (val.getTag() != BER.OCTET_STRING) + throw new BEREncodingException("malformed EncryptedDigest"); + encryptedDigest = (byte[]) val.getValue(); + if (DEBUG) + debug(" EncryptedDigest: " + new BigInteger(1, encryptedDigest).toString(16)); + + if (ber.peek() == 1) + unauthenticatedAttributes = ber.read().getEncoded(); + else + unauthenticatedAttributes = null; + + if (ber.peek() == 0) + ber.read(); + } + + public BigInteger getVersion() + { + return version; + } + + public BigInteger getSerialNumber() + { + return serialNumber; + } + + public X500Principal getIssuer() + { + return issuer; + } + + public OID getDigestAlgorithmId() + { + return digestAlgorithmId; + } + + public byte[] getDigestAlgorithmParams() + { + return (digestAlgorithmParams != null + ? (byte[]) digestAlgorithmParams.clone() + : null); + } + + public byte[] getAuthenticatedAttributes() + { + return (authenticatedAttributes != null + ? (byte[]) authenticatedAttributes.clone() + : null); + } + + public OID getDigestEncryptionAlgorithmId() + { + return digestEncryptionAlgorithmId; + } + + public byte[] getDigestEncryptionAlgorithmParams() + { + return (digestEncryptionAlgorithmParams != null + ? (byte[]) digestEncryptionAlgorithmParams.clone() + : null); + } + + public byte[] getEncryptedDigest() + { + return (encryptedDigest != null ? (byte[]) encryptedDigest.clone() : null); + } + + public byte[] getUnauthenticatedAttributes() + { + return (unauthenticatedAttributes != null + ? (byte[]) unauthenticatedAttributes.clone() + : null); + } +}