Author: bago
Date: Fri Sep 17 23:26:19 2010
New Revision: 998368

URL: http://svn.apache.org/viewvc?rev=998368&view=rev
Log:
Added 2 stage verification to the DKIMVerifier (JDKIM-23)
Now BodyHasher is a simpler interface (just getOutputStream exposed) and 
DKIMVerifier/DKIMSigner cast the BH to the concrete implementation they know 
they provided to the client in order to get "memento" data.
DKIMVerifier now exposes a method to initialize the verification process from 
an "Header" object, returning a BodyHasher when the client can write the 
message body and then call the verifier again passing the "computed" bodyhasher.

Added:
    
james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/impl/CompoundBodyHasher.java
   (with props)
Modified:
    james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/DKIMSigner.java
    
james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java
    
james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/api/BodyHasher.java

Modified: 
james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/DKIMSigner.java
URL: 
http://svn.apache.org/viewvc/james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/DKIMSigner.java?rev=998368&r1=998367&r2=998368&view=diff
==============================================================================
--- james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/DKIMSigner.java 
(original)
+++ james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/DKIMSigner.java 
Fri Sep 17 23:26:19 2010
@@ -90,8 +90,12 @@ public class DKIMSigner extends DKIMComm
         }
     }
 
-    public String sign(Headers message, BodyHasher bhj)
-            throws PermFailException {
+    public String sign(Headers message, BodyHasher bh) throws 
PermFailException {
+        if (!(bh instanceof BodyHasherImpl)) {
+            throw new PermFailException(
+                    "Supplied BodyHasher has not been generated with this 
signer");
+        }
+        BodyHasherImpl bhj = (BodyHasherImpl) bh;
         byte[] computedHash = bhj.getDigest();
 
         bhj.getSignatureRecord().setBodyHash(computedHash);

Modified: 
james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java
URL: 
http://svn.apache.org/viewvc/james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java?rev=998368&r1=998367&r2=998368&view=diff
==============================================================================
--- 
james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java 
(original)
+++ 
james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java 
Fri Sep 17 23:26:19 2010
@@ -21,7 +21,6 @@ package org.apache.james.jdkim;
 
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.OutputStream;
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
 import java.security.PublicKey;
@@ -40,11 +39,11 @@ import org.apache.james.jdkim.api.Header
 import org.apache.james.jdkim.api.PublicKeyRecord;
 import org.apache.james.jdkim.api.PublicKeyRecordRetriever;
 import org.apache.james.jdkim.api.SignatureRecord;
-import org.apache.james.jdkim.canon.CompoundOutputStream;
 import org.apache.james.jdkim.exceptions.FailException;
 import org.apache.james.jdkim.exceptions.PermFailException;
 import org.apache.james.jdkim.exceptions.TempFailException;
 import org.apache.james.jdkim.impl.BodyHasherImpl;
+import org.apache.james.jdkim.impl.CompoundBodyHasher;
 import org.apache.james.jdkim.impl.DNSPublicKeyRecordRetriever;
 import org.apache.james.jdkim.impl.Message;
 import org.apache.james.jdkim.impl.MultiplexingPublicKeyRecordRetriever;
@@ -73,7 +72,7 @@ public class DKIMVerifier extends DKIMCo
         return new SignatureRecordImpl(record);
     }
 
-    public BodyHasher newBodyHasher(SignatureRecord signRecord)
+    protected BodyHasherImpl newBodyHasher(SignatureRecord signRecord)
             throws PermFailException {
         return new BodyHasherImpl(signRecord);
     }
@@ -226,22 +225,8 @@ public class DKIMVerifier extends DKIMCo
             is.close();
         }
     }
-
-    /**
-     * Verifies all of the DKIM-Signature records declared in the Headers
-     * object.
-     * 
-     * @param messageHeaders
-     *                parsed headers
-     * @param bodyInputStream
-     *                input stream for the body.
-     * @return a list of verified signature records
-     * @throws IOException
-     * @throws FailException
-     *                 if no signature can be verified
-     */
-    public List<SignatureRecord> verify(Headers messageHeaders,
-            InputStream bodyInputStream) throws IOException, FailException {
+    
+    public BodyHasher newBodyHasher(Headers messageHeaders) throws 
FailException {
         List<String> fields = messageHeaders.getFields("DKIM-Signature");
         if (fields == null || fields.isEmpty()) {
             return null;
@@ -250,7 +235,7 @@ public class DKIMVerifier extends DKIMCo
         // For each DKIM-signature we prepare an hashjob.
         // We calculate all hashes concurrently so to read
         // the inputstream only once.
-        Map<String, BodyHasher> bodyHashJobs = new HashMap<String, 
BodyHasher>();
+        Map<String, BodyHasherImpl> bodyHashJobs = new HashMap<String, 
BodyHasherImpl>();
         Hashtable<String, FailException> signatureExceptions = new 
Hashtable<String, FailException>();
         for (Iterator<String> i = fields.iterator(); i.hasNext();) {
             String signatureField = i.next();
@@ -308,7 +293,7 @@ public class DKIMVerifier extends DKIMCo
                     // we track all canonicalizations+limit+bodyHash we
                     // see so to be able to check all of them in a single
                     // stream run.
-                    BodyHasher bhj = newBodyHasher(signatureRecord);
+                    BodyHasherImpl bhj = newBodyHasher(signatureRecord);
 
                     bodyHashJobs.put(signatureField, bhj);
 
@@ -320,51 +305,105 @@ public class DKIMVerifier extends DKIMCo
                 signatureExceptions.put(signatureField, e);
             } catch (PermFailException e) {
                 signatureExceptions.put(signatureField, e);
-            } catch (InvalidKeyException e) {
-                signatureExceptions.put(signatureField, new PermFailException(e
-                        .getMessage(), e));
-            } catch (NoSuchAlgorithmException e) {
-                signatureExceptions.put(signatureField, new PermFailException(e
-                        .getMessage(), e));
-            } catch (SignatureException e) {
-                signatureExceptions.put(signatureField, new PermFailException(e
-                        .getMessage(), e));
             } catch (RuntimeException e) {
                 signatureExceptions.put(signatureField, new PermFailException(
                         "Unexpected exception processing signature", e));
             }
         }
 
-        OutputStream o;
         if (bodyHashJobs.isEmpty()) {
             if (signatureExceptions.size() > 0) {
                 throw prepareException(signatureExceptions);
             } else {
                 throw new PermFailException("Unexpected condition with 
"+fields);
             }
-        } else if (bodyHashJobs.size() == 1) {
-            o = ((BodyHasher) bodyHashJobs.values().iterator().next())
-                    .getOutputStream();
-        } else {
-            List<OutputStream> outputStreams = new LinkedList<OutputStream>();
-            for (BodyHasher bhj : bodyHashJobs.values()) {
-                outputStreams.add(bhj.getOutputStream());
-            }
-            o = new CompoundOutputStream(outputStreams);
         }
 
+        return new CompoundBodyHasher(bodyHashJobs, signatureExceptions);
+    }
+
+    /**
+     * Verifies all of the DKIM-Signature records declared in the Headers
+     * object.
+     * 
+     * @param messageHeaders
+     *                parsed headers
+     * @param bodyInputStream
+     *                input stream for the body.
+     * @return a list of verified signature records
+     * @throws IOException
+     * @throws FailException
+     *                 if no signature can be verified
+     */
+    public List<SignatureRecord> verify(Headers messageHeaders,
+            InputStream bodyInputStream) throws IOException, FailException {
+        
+        BodyHasher bh = newBodyHasher(messageHeaders);
+        
+        if (bh == null) return null;
+        
+        CompoundBodyHasher cbh = validateBodyHasher(bh);
+
         // simultaneous computation of all the hashes.
-        DKIMCommon.streamCopy(bodyInputStream, o);
+        DKIMCommon.streamCopy(bodyInputStream, cbh.getOutputStream());
 
+        return verify(cbh);
+    }
+    
+    /**
+     * Completes the simultaneous verification of multiple 
+     * signatures given the previously prepared compound body hasher where
+     * the user already written the body to the outputstream and closed it. 
+     * 
+     * @param compoundBodyHasher the BodyHasher previously obtained by this 
class.
+     * @return a list of valid (verified) signatures or null on null input.
+     * @throws FailException if no valid signature is found
+     */
+    public List<SignatureRecord> verify(BodyHasher bh) throws FailException {
+        if (bh == null) return null;
+        CompoundBodyHasher cbh = validateBodyHasher(bh);
+        
+        return verify(cbh);
+    }
+
+    /**
+     * Used by public "verify" methods to make sure the input
+     * bodyHasher is a CompoundBodyHasher as expected.
+     *  
+     * @param bh the BodyHasher previously obtained by this class.
+     * @return a casted CompoundBodyHasher
+     * @throws PermFailException if it wasn't a CompoundBodyHasher
+     */
+    private CompoundBodyHasher validateBodyHasher(BodyHasher bh)
+            throws PermFailException {
+        if (!(bh instanceof CompoundBodyHasher)) {
+            throw new PermFailException("Unexpected BodyHasher type: this is 
not generated by DKIMVerifier!");
+        }
+        
+        CompoundBodyHasher cbh = (CompoundBodyHasher) bh;
+        return cbh;
+    }
+
+    /**
+     * Internal method to complete the simultaneous verification of multiple 
+     * signatures given the previously prepared compound body hasher where
+     * the user already written the body to the outputstream and closed it. 
+     * 
+     * @param compoundBodyHasher the BodyHasher previously obtained by this 
class.
+     * @return a list of valid (verified) signatures
+     * @throws FailException if no valid signature is found
+     */
+    private List<SignatureRecord> verify(CompoundBodyHasher compoundBodyHasher)
+            throws FailException {
         List<SignatureRecord> verifiedSignatures = new 
LinkedList<SignatureRecord>();
-        for (Iterator<BodyHasher> i = bodyHashJobs.values().iterator(); 
i.hasNext();) {
-            BodyHasher bhj = i.next();
+        for (Iterator<BodyHasherImpl> i = 
compoundBodyHasher.getBodyHashJobs().values().iterator(); i.hasNext();) {
+            BodyHasherImpl bhj = i.next();
 
             byte[] computedHash = bhj.getDigest();
             byte[] expectedBodyHash = bhj.getSignatureRecord().getBodyHash();
 
             if (!Arrays.equals(expectedBodyHash, computedHash)) {
-                signatureExceptions
+                compoundBodyHasher.getSignatureExceptions()
                         .put(
                                 
"DKIM-Signature:"+bhj.getSignatureRecord().toString(),
                                 new PermFailException(
@@ -375,7 +414,7 @@ public class DKIMVerifier extends DKIMCo
         }
 
         if (verifiedSignatures.isEmpty()) {
-            throw prepareException(signatureExceptions);
+            throw 
prepareException(compoundBodyHasher.getSignatureExceptions());
         } else {
             // There is no access to the signatureExceptions when
             // there is at least one valid signature (JDKIM-14)
@@ -396,9 +435,16 @@ public class DKIMVerifier extends DKIMCo
             */
             return verifiedSignatures;
         }
-
     }
 
+    /**
+     * Given a map of exceptions prepares a human readable exception.
+     * This simply return the exception if it is only one, otherwise returns
+     * a cumulative exception
+     * 
+     * @param signatureExceptions input exceptions
+     * @return the resulting "compact" exception 
+     */
     private FailException prepareException(Map<String, FailException> 
signatureExceptions) {
         if (signatureExceptions.size() == 1) {
             return signatureExceptions.values().iterator()
@@ -412,26 +458,42 @@ public class DKIMVerifier extends DKIMCo
         }
     }
 
+    /**
+     * Performs signature verification (excluding the body hash).
+     * 
+     * @param h the headers
+     * @param sign the signature record
+     * @param decoded the expected signature hash
+     * @param key the DKIM public key record
+     * @param headers the list of signed headers
+     * @throws PermFailException
+     */
     private void signatureVerify(Headers h, SignatureRecord sign,
             byte[] decoded, PublicKeyRecord key, List<CharSequence> headers)
-            throws NoSuchAlgorithmException, InvalidKeyException,
-            SignatureException, PermFailException {
-
-        Signature signature = Signature.getInstance(sign.getHashMethod()
-                .toString().toUpperCase()
-                + "with" + sign.getHashKeyType().toString().toUpperCase());
-        PublicKey publicKey;
+            throws PermFailException {
         try {
-            publicKey = key.getPublicKey();
-        } catch (IllegalStateException e) {
-            throw new PermFailException("Invalid Public Key: "+e.getMessage(), 
e);
+            Signature signature = Signature.getInstance(sign.getHashMethod()
+                    .toString().toUpperCase()
+                    + "with" + sign.getHashKeyType().toString().toUpperCase());
+            PublicKey publicKey;
+            try {
+                publicKey = key.getPublicKey();
+            } catch (IllegalStateException e) {
+                throw new PermFailException("Invalid Public Key: 
"+e.getMessage(), e);
+            }
+            signature.initVerify(publicKey);
+
+            signatureCheck(h, sign, headers, signature);
+
+            if (!signature.verify(decoded))
+                throw new PermFailException("Header signature does not 
verify");
+        } catch (InvalidKeyException e) {
+            throw new PermFailException(e.getMessage(), e);
+        } catch (NoSuchAlgorithmException e) {
+            throw new PermFailException(e.getMessage(), e);
+        } catch (SignatureException e) {
+            throw new PermFailException(e.getMessage(), e);
         }
-        signature.initVerify(publicKey);
-
-        signatureCheck(h, sign, headers, signature);
-
-        if (!signature.verify(decoded))
-            throw new PermFailException("Header signature does not verify");
     }
 
 }

Modified: 
james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/api/BodyHasher.java
URL: 
http://svn.apache.org/viewvc/james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/api/BodyHasher.java?rev=998368&r1=998367&r2=998368&view=diff
==============================================================================
--- 
james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/api/BodyHasher.java 
(original)
+++ 
james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/api/BodyHasher.java 
Fri Sep 17 23:26:19 2010
@@ -25,8 +25,4 @@ public interface BodyHasher {
 
     public abstract OutputStream getOutputStream();
 
-    public abstract SignatureRecord getSignatureRecord();
-
-    public abstract byte[] getDigest();
-
 }
\ No newline at end of file

Added: 
james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/impl/CompoundBodyHasher.java
URL: 
http://svn.apache.org/viewvc/james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/impl/CompoundBodyHasher.java?rev=998368&view=auto
==============================================================================
--- 
james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/impl/CompoundBodyHasher.java
 (added)
+++ 
james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/impl/CompoundBodyHasher.java
 Fri Sep 17 23:26:19 2010
@@ -0,0 +1,75 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jdkim.impl;
+
+import java.io.OutputStream;
+import java.util.Hashtable;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.james.jdkim.api.BodyHasher;
+import org.apache.james.jdkim.canon.CompoundOutputStream;
+import org.apache.james.jdkim.exceptions.FailException;
+
+/**
+ * CompoundBodyHasher is used for verification purpose.
+ * 
+ * It contains a compund output stream that will calculate
+ * the body hash for multiple signatures.
+ * 
+ * This object is a container for "bodyHashJobs" and 
+ * "signatureExceptions" for 2-stage verification process.
+ */
+public class CompoundBodyHasher implements BodyHasher {
+
+    private final OutputStream o;
+    private final Map<String, BodyHasherImpl> bodyHashJobs;
+    private final Map<String, FailException> signatureExceptions;
+    
+    public CompoundBodyHasher(Map<String, BodyHasherImpl> bodyHashJobs,
+            Hashtable<String, FailException> signatureExceptions) {
+        this.bodyHashJobs = bodyHashJobs;
+        this.signatureExceptions = signatureExceptions;
+        if (bodyHashJobs.size() == 1) {
+            o = ((BodyHasherImpl) bodyHashJobs.values().iterator().next())
+                    .getOutputStream();
+        } else {
+            List<OutputStream> outputStreams = new LinkedList<OutputStream>();
+            for (BodyHasherImpl bhj : bodyHashJobs.values()) {
+                outputStreams.add(bhj.getOutputStream());
+            }
+            o = new CompoundOutputStream(outputStreams);
+        }
+    }
+
+    public OutputStream getOutputStream() {
+        return o;
+    }
+    
+    public Map<String, BodyHasherImpl> getBodyHashJobs() {
+        return bodyHashJobs;
+    }
+
+    public Map<String, FailException> getSignatureExceptions() {
+        return signatureExceptions;
+    }
+
+}

Propchange: 
james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/impl/CompoundBodyHasher.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain



---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to