This is an automated email from the ASF dual-hosted git repository.

chibenwa pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-jdkim.git

commit c0235cdcdb2657394766d3474b0b81c8d889d392
Author: Benoit TELLIER <[email protected]>
AuthorDate: Fri Oct 30 08:33:04 2026 +0100

    [FIX] SignatureRecord: require exactly one '@' in i=
    
    validate() only checked that the identity ended with "@d" or ".d",
    so an i= without any '@' passed and getIdentityLocalPart() then threw
    StringIndexOutOfBoundsException, while one with several '@' was
    accepted as is.
    
    The identity is now split on its single '@', the domain part must be d=
    or one of its subdomains, and getIdentityLocalPart() uses the same
    split.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../james/jdkim/tagvalue/SignatureRecordImpl.java  | 34 +++++++++++++++++-----
 .../jdkim/tagvalue/SignatureRecordTemplate.java    |  9 ++----
 .../james/jdkim/SignatureRecordImplTest.java       | 20 +++++++++++++
 .../james/jdkim/SignatureRecordTemplateTest.java   |  6 ++++
 4 files changed, 55 insertions(+), 14 deletions(-)

diff --git 
a/main/src/main/java/org/apache/james/jdkim/tagvalue/SignatureRecordImpl.java 
b/main/src/main/java/org/apache/james/jdkim/tagvalue/SignatureRecordImpl.java
index cb7ab22..2836359 100644
--- 
a/main/src/main/java/org/apache/james/jdkim/tagvalue/SignatureRecordImpl.java
+++ 
b/main/src/main/java/org/apache/james/jdkim/tagvalue/SignatureRecordImpl.java
@@ -76,11 +76,7 @@ public class SignatureRecordImpl extends TagValue implements 
SignatureRecord {
             throw new IllegalStateException("Identity (i=) declaration cannot 
be parsed. Probably due to missing quoted printable encoding", e);
         }
 
-        if (!identity.toString().toLowerCase().endsWith(
-                ("@" + getValue("d")).toLowerCase())
-                && !getIdentity().toString().toLowerCase().endsWith(
-                ("." + getValue("d")).toLowerCase()))
-            throw new IllegalStateException("Identity (i=) domain mismatch: 
expected [optional]@[optional.]domain-from-d-attribute");
+        checkIdentity(identity.toString(), getValue("d").toString());
 
         // RFC 6376 section 3.5: sig-t-tag = %x74 [FWS] "=" [FWS] 1*12DIGIT
         if (getValue("t") != null && 
!timestampPattern.matcher(getValue("t")).matches())
@@ -112,6 +108,31 @@ public class SignatureRecordImpl extends TagValue 
implements SignatureRecord {
         // this class).
     }
 
+    /**
+     * RFC 6376 section 3.5: sig-i-tag = [ Local-part ] "@" domain-name, the
+     * domain being d= or one of its subdomains.
+     */
+    static void checkIdentity(String identity, String d) {
+        String domain = identity.substring(identityAt(identity) + 1);
+        // regionMatches compares case-insensitively without the default locale
+        boolean sameDomain = domain.length() == d.length() && 
domain.regionMatches(true, 0, d, 0, d.length());
+        boolean subDomain = domain.length() > d.length()
+                && domain.charAt(domain.length() - d.length() - 1) == '.'
+                && domain.regionMatches(true, domain.length() - d.length(), d, 
0, d.length());
+        if (!sameDomain && !subDomain)
+            throw new IllegalStateException("Identity (i=) domain mismatch: 
expected [optional]@[optional.]domain-from-d-attribute");
+    }
+
+    /**
+     * @return the position of the single '@' of the identity
+     */
+    static int identityAt(String identity) {
+        int pAt = identity.indexOf('@');
+        if (pAt == -1 || identity.indexOf('@', pAt + 1) != -1)
+            throw new IllegalStateException("Identity (i=) must contain 
exactly one '@': " + identity);
+        return pAt;
+    }
+
     private String getTimeMeasure(long lifetime) {
         String measure = "s";
         lifetime = -lifetime;
@@ -159,8 +180,7 @@ public class SignatureRecordImpl extends TagValue 
implements SignatureRecord {
      */
     public CharSequence getIdentityLocalPart() {
         String identity = getIdentity().toString();
-        int pAt = identity.indexOf('@');
-        return identity.subSequence(0, pAt);
+        return identity.substring(0, identityAt(identity));
     }
 
     /**
diff --git 
a/main/src/main/java/org/apache/james/jdkim/tagvalue/SignatureRecordTemplate.java
 
b/main/src/main/java/org/apache/james/jdkim/tagvalue/SignatureRecordTemplate.java
index 0701e30..32f294a 100644
--- 
a/main/src/main/java/org/apache/james/jdkim/tagvalue/SignatureRecordTemplate.java
+++ 
b/main/src/main/java/org/apache/james/jdkim/tagvalue/SignatureRecordTemplate.java
@@ -84,11 +84,7 @@ public class SignatureRecordTemplate extends TagValue 
implements SignatureRecord
             throw new IllegalStateException("Identity (i=) declaration cannot 
be parsed. Probably due to missing quoted printable encoding", e);
         }
 
-        if (!identity.toString().toLowerCase().endsWith(
-                ("@" + getValue("d")).toLowerCase())
-                && !getIdentity().toString().toLowerCase().endsWith(
-                ("." + getValue("d")).toLowerCase()))
-            throw new IllegalStateException("Identity (i=) domain mismatch: 
expected [optional]@[optional.]domain-from-d-attribute");
+        SignatureRecordImpl.checkIdentity(identity.toString(), 
getValue("d").toString());
 
         // An empty t= asks the signer to fill in the signing time.
         if (getValue("t") != null && getValue("t").length() > 0
@@ -168,8 +164,7 @@ public class SignatureRecordTemplate extends TagValue 
implements SignatureRecord
      */
     public CharSequence getIdentityLocalPart() {
         String identity = getIdentity().toString();
-        int pAt = identity.indexOf('@');
-        return identity.subSequence(0, pAt);
+        return identity.substring(0, SignatureRecordImpl.identityAt(identity));
     }
 
     /**
diff --git 
a/main/src/test/java/org/apache/james/jdkim/SignatureRecordImplTest.java 
b/main/src/test/java/org/apache/james/jdkim/SignatureRecordImplTest.java
index 533160d..ff209a4 100644
--- a/main/src/test/java/org/apache/james/jdkim/SignatureRecordImplTest.java
+++ b/main/src/test/java/org/apache/james/jdkim/SignatureRecordImplTest.java
@@ -218,4 +218,24 @@ public class SignatureRecordImplTest {
         assertThat(new SignatureRecordImpl(base + 
"l=0000000000000000042").getBodyHashLimit()).isEqualTo(42);
         assertThat(new SignatureRecordImpl(base + 
"l=0").getBodyHashLimit()).isEqualTo(0);
     }
+
+    @Test
+    public void identityWithoutExactlyOneAtShouldBeRejected() {
+        String base = "v=1; a=rsa-sha256; d=example.com; s=s; h=from; bh=AA==; 
b=AA==; ";
+
+        assertThatThrownBy(() -> new SignatureRecordImpl(base + 
"i=.example.com"))
+                .isInstanceOf(IllegalStateException.class);
+        assertThatThrownBy(() -> new SignatureRecordImpl(base + 
"[email protected]"))
+                .isInstanceOf(IllegalStateException.class);
+        assertThatThrownBy(() -> new SignatureRecordImpl(base + 
"[email protected]"))
+                .isInstanceOf(IllegalStateException.class);
+    }
+
+    @Test
+    public void identityInDomainOrSubdomainShouldBeAccepted() {
+        String base = "v=1; a=rsa-sha256; d=example.com; s=s; h=from; bh=AA==; 
b=AA==; ";
+
+        assertThat(new SignatureRecordImpl(base + 
"[email protected]").getIdentityLocalPart()).isEqualTo("joe");
+        assertThat(new SignatureRecordImpl(base + 
"[email protected]").getIdentityLocalPart()).isEqualTo("");
+    }
 }
diff --git 
a/main/src/test/java/org/apache/james/jdkim/SignatureRecordTemplateTest.java 
b/main/src/test/java/org/apache/james/jdkim/SignatureRecordTemplateTest.java
index 00f76f5..009eba2 100644
--- a/main/src/test/java/org/apache/james/jdkim/SignatureRecordTemplateTest.java
+++ b/main/src/test/java/org/apache/james/jdkim/SignatureRecordTemplateTest.java
@@ -182,4 +182,10 @@ public class SignatureRecordTemplateTest {
         assertThatThrownBy(() -> new SignatureRecordTemplate("v=1; 
a=rsa-sha256; d=example.com; s=s; h=from; l=-1"))
                 .isInstanceOf(IllegalStateException.class);
     }
+
+    @Test
+    public void identityWithoutAtShouldBeRejected() {
+        assertThatThrownBy(() -> new SignatureRecordTemplate("v=1; 
a=rsa-sha256; d=example.com; s=s; h=from; i=.example.com"))
+                .isInstanceOf(IllegalStateException.class);
+    }
 }


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

Reply via email to