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]