From e8a21b09494d542e568ca2542f77e8e10fc5d07f Mon Sep 17 00:00:00 2001 From: Rene Cordier Date: Wed, 4 Sep 2019 15:29:34 +0700 Subject: [PATCH 001/114] JDKIM-42 Upgrade apache parent pom version 18 -> 21 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ace72d1..93c7e6e 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ org.apache apache - 18 + 21 From ab1880067d49a4f51306b59dfaac35e6d054e079 Mon Sep 17 00:00:00 2001 From: Rene Cordier Date: Wed, 4 Sep 2019 15:35:38 +0700 Subject: [PATCH 002/114] JDKIM-42 Upgrade maven-assembly-plugin 3.0.0 -> 3.1.1 - Use goals `single` instead of `attached` by the recommendation at http://maven.apache.org/plugins-archives/maven-assembly-plugin-2.5.5/attached-mojo.html --- assemble/pom.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/assemble/pom.xml b/assemble/pom.xml index a6d4742..380aa5b 100644 --- a/assemble/pom.xml +++ b/assemble/pom.xml @@ -62,6 +62,7 @@ org.apache.maven.plugins maven-assembly-plugin + 3.1.1 src/assemble/ gnu @@ -72,7 +73,7 @@ make-assembly package - attached + single From bc8ace8240c77894e6f77e44e92caf5009cd8c72 Mon Sep 17 00:00:00 2001 From: Rene Cordier Date: Wed, 4 Sep 2019 15:37:45 +0700 Subject: [PATCH 003/114] JDKIM-42 Upgrade junit 4.10 -> 4.12 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 93c7e6e..86458e3 100644 --- a/pom.xml +++ b/pom.xml @@ -62,7 +62,7 @@ - 4.10 + 4.12 1.1 1.8.3 1.7 From fa039479786a2cc77d27fa7b50f52fcf38cbd9db Mon Sep 17 00:00:00 2001 From: Rene Cordier Date: Wed, 4 Sep 2019 15:41:11 +0700 Subject: [PATCH 004/114] JDKIM-42 Upgrade commons-codec 1.7 -> 1.13 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 86458e3..a054c24 100644 --- a/pom.xml +++ b/pom.xml @@ -65,7 +65,7 @@ 4.12 1.1 1.8.3 - 1.7 + 1.13 1.1.1 3.1.0-SNAPSHOT 2.1.1 From 62408fd9e7a3e5114688d1b79defa0e71ed0bd4c Mon Sep 17 00:00:00 2001 From: Rene Cordier Date: Wed, 4 Sep 2019 15:42:48 +0700 Subject: [PATCH 005/114] JDKIM-42 Upgrade commons-logging 1.1.1 -> 1.2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a054c24..952d22a 100644 --- a/pom.xml +++ b/pom.xml @@ -66,7 +66,7 @@ 1.1 1.8.3 1.13 - 1.1.1 + 1.2 3.1.0-SNAPSHOT 2.1.1 0.3.11 From eabf8275fc59eb5f638de4f5cc411deb7ea51d04 Mon Sep 17 00:00:00 2001 From: Rene Cordier Date: Wed, 4 Sep 2019 15:48:45 +0700 Subject: [PATCH 006/114] JDKIM-42 Upgrade dnsjava 2.1.1 -> 2.1.9 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 952d22a..3e4b6e0 100644 --- a/pom.xml +++ b/pom.xml @@ -68,7 +68,7 @@ 1.13 1.2 3.1.0-SNAPSHOT - 2.1.1 + 2.1.9 0.3.11 0.8.1 1.2.16 From e3f49cbf28b57c0fc86a3d352a4347e2af4f3028 Mon Sep 17 00:00:00 2001 From: Rene Cordier Date: Wed, 4 Sep 2019 15:55:13 +0700 Subject: [PATCH 007/114] JDKIM-42 Upgrade log4j 1.2.16 -> log4j-core 2.12.1 --- mailets/pom.xml | 10 +++++----- main/pom.xml | 4 ++-- pom.xml | 14 +++++++------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/mailets/pom.xml b/mailets/pom.xml index afa6cc7..aa79325 100644 --- a/mailets/pom.xml +++ b/mailets/pom.xml @@ -89,11 +89,6 @@ junit test - - log4j - log4j - test - org.apache.geronimo.specs geronimo-activation_1.1_spec @@ -102,6 +97,11 @@ org.apache.geronimo.javamail geronimo-javamail_1.4_mail + + org.apache.logging.log4j + log4j-core + test + ca.juliusdavies not-yet-commons-ssl diff --git a/main/pom.xml b/main/pom.xml index 850e0c1..99e1a91 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -78,8 +78,8 @@ apache-mime4j-dom - log4j - log4j + org.apache.logging.log4j + log4j-core runtime diff --git a/pom.xml b/pom.xml index 3e4b6e0..563152b 100644 --- a/pom.xml +++ b/pom.xml @@ -71,7 +71,7 @@ 2.1.9 0.3.11 0.8.1 - 1.2.16 + 2.12.1 1.6 1.8 @@ -186,12 +186,6 @@ - - log4j - log4j - runtime - ${log4j.version} - dnsjava dnsjava @@ -214,6 +208,12 @@ apache-jdkim-mailets ${project.version} + + org.apache.logging.log4j + log4j-core + runtime + ${log4j.version} + ca.juliusdavies not-yet-commons-ssl From 2ee6bc6194410a7ab25de84b7e3f8e25682cc223 Mon Sep 17 00:00:00 2001 From: Rene Cordier Date: Wed, 4 Sep 2019 16:04:21 +0700 Subject: [PATCH 008/114] JDKIM-42 Upgrade maven-site-plugin 3.3 -> 3.7.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 563152b..c94bc43 100644 --- a/pom.xml +++ b/pom.xml @@ -290,7 +290,7 @@ org.apache.maven.plugins maven-site-plugin - 3.3 + 3.7.1 attach-descriptor From b4f162c8f4d4926d4e1f140708d90555846d2630 Mon Sep 17 00:00:00 2001 From: Rene Cordier Date: Wed, 4 Sep 2019 16:04:48 +0700 Subject: [PATCH 009/114] JDKIM-42 Upgrade wagon-ssh 2.0 -> 3.3.3 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c94bc43..8fa1465 100644 --- a/pom.xml +++ b/pom.xml @@ -303,7 +303,7 @@ org.apache.maven.wagon wagon-ssh - 2.0 + 3.3.3 From 2d6177a95be04677cb411609ab8d91a8a12744fc Mon Sep 17 00:00:00 2001 From: Rene Cordier Date: Wed, 4 Sep 2019 16:07:00 +0700 Subject: [PATCH 010/114] JDKIM-42 Upgrade maven-bundle-plugin 2.3.7 -> 4.2.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8fa1465..892a8c0 100644 --- a/pom.xml +++ b/pom.xml @@ -238,7 +238,7 @@ org.apache.felix maven-bundle-plugin true - 2.3.7 + 4.2.1 org.apache.maven.plugins From c8bc3db87c31026b580d7f987471cd58e7cc6ec1 Mon Sep 17 00:00:00 2001 From: Rene Cordier Date: Wed, 4 Sep 2019 16:08:15 +0700 Subject: [PATCH 011/114] JDKIM-42 Upgrade maven-compiler-plugin 3.0 -> 3.8.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 892a8c0..8d5c70b 100644 --- a/pom.xml +++ b/pom.xml @@ -243,7 +243,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.0 + 3.8.1 true ${target.jdk} From 7b263de4ed254b1eb82fdbeede7d6a4477050038 Mon Sep 17 00:00:00 2001 From: Rene Cordier Date: Wed, 4 Sep 2019 16:10:37 +0700 Subject: [PATCH 012/114] JDKIM-42 Upgrade apache-rat-plugin 0.8 -> 0.13 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8d5c70b..ef28dd0 100644 --- a/pom.xml +++ b/pom.xml @@ -253,7 +253,7 @@ org.apache.rat apache-rat-plugin - 0.8 + 0.13 BUILDING.* From 3d05194a63425c9a44255493cbe0c61f615546d1 Mon Sep 17 00:00:00 2001 From: Rene Cordier Date: Wed, 4 Sep 2019 16:11:50 +0700 Subject: [PATCH 013/114] JDKIM-42 Upgrade versions-maven-plugin 2.0 -> 2.7 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ef28dd0..fe90099 100644 --- a/pom.xml +++ b/pom.xml @@ -315,7 +315,7 @@ org.codehaus.mojo versions-maven-plugin - 2.0 + 2.7 From b558816eddc2a8ffd171a995ff2a4e091c03542a Mon Sep 17 00:00:00 2001 From: Rene Cordier Date: Wed, 4 Sep 2019 16:41:26 +0700 Subject: [PATCH 014/114] JDKIM-42 Upgrade apache-mime4j 0.8.1 -> 0.8.3 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index fe90099..d1f9be8 100644 --- a/pom.xml +++ b/pom.xml @@ -70,7 +70,7 @@ 3.1.0-SNAPSHOT 2.1.9 0.3.11 - 0.8.1 + 0.8.3 2.12.1 1.6 1.8 From 8a92e65ef66cf8c6de9cf4e336f382721280f3ba Mon Sep 17 00:00:00 2001 From: Rene Cordier Date: Wed, 4 Sep 2019 17:03:57 +0700 Subject: [PATCH 015/114] JDKIM-42 Reorder dependencies in pom files Following alphabetical order with groupId > artifactId --- assemble/pom.xml | 20 +++--- mailets/pom.xml | 70 ++++++++++---------- main/pom.xml | 36 +++++------ pom.xml | 162 +++++++++++++++++++++++------------------------ 4 files changed, 144 insertions(+), 144 deletions(-) diff --git a/assemble/pom.xml b/assemble/pom.xml index 380aa5b..2468208 100644 --- a/assemble/pom.xml +++ b/assemble/pom.xml @@ -36,25 +36,25 @@ - org.apache.james.jdkim - apache-jdkim-library - - - org.apache.james.jdkim - apache-jdkim-mailets + ca.juliusdavies + not-yet-commons-ssl dnsjava dnsjava - - ca.juliusdavies - not-yet-commons-ssl - org.apache.james apache-mime4j-core + + org.apache.james.jdkim + apache-jdkim-library + + + org.apache.james.jdkim + apache-jdkim-mailets + diff --git a/mailets/pom.xml b/mailets/pom.xml index aa79325..846c386 100644 --- a/mailets/pom.xml +++ b/mailets/pom.xml @@ -36,10 +36,45 @@ http://james.apache.org/jdkim/mailets/ + + ca.juliusdavies + not-yet-commons-ssl + + + commons-codec + commons-codec + + + commons-logging + commons-logging + + + junit + junit + test + + + org.apache.geronimo.javamail + geronimo-javamail_1.4_mail + + + org.apache.geronimo.specs + geronimo-activation_1.1_spec + org.apache.james apache-mailet-api + + org.apache.james + apache-mailet-base + + + org.apache.james + apache-mailet-base + test-jar + test + org.apache.james.jdkim apache-jdkim-library @@ -70,45 +105,10 @@ - - org.apache.james - apache-mailet-base - - - org.apache.james - apache-mailet-base - test-jar - test - - - commons-logging - commons-logging - - - junit - junit - test - - - org.apache.geronimo.specs - geronimo-activation_1.1_spec - - - org.apache.geronimo.javamail - geronimo-javamail_1.4_mail - org.apache.logging.log4j log4j-core test - - ca.juliusdavies - not-yet-commons-ssl - - - commons-codec - commons-codec - diff --git a/main/pom.xml b/main/pom.xml index 99e1a91..6d6c375 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -40,34 +40,38 @@ - junit - junit + commons-codec + commons-codec - org.apache.geronimo.specs - geronimo-activation_1.1_spec + dnsjava + dnsjava + + + junit + junit org.apache.geronimo.javamail geronimo-javamail_1.4_mail - commons-codec - commons-codec + org.apache.geronimo.specs + geronimo-activation_1.1_spec org.apache.james - apache-mailet-base + apache-mailet-api org.apache.james apache-mailet-base - test - tests org.apache.james - apache-mailet-api + apache-mailet-base + test + tests org.apache.james @@ -82,10 +86,6 @@ log4j-core runtime - - dnsjava - dnsjava - @@ -100,10 +100,6 @@ - - org.apache.rat - apache-rat-plugin - org.apache.maven.plugins @@ -168,6 +164,10 @@ + + org.apache.rat + apache-rat-plugin + diff --git a/pom.xml b/pom.xml index d1f9be8..f1e0082 100644 --- a/pom.xml +++ b/pom.xml @@ -39,9 +39,9 @@ 2009 - main - mailets assemble + mailets + main @@ -62,37 +62,26 @@ - 4.12 - 1.1 - 1.8.3 + 3.1.0-SNAPSHOT + 0.8.3 1.13 1.2 - 3.1.0-SNAPSHOT 2.1.9 - 0.3.11 - 0.8.3 + 1.1 + 1.8.3 + 1.8 + 4.12 2.12.1 + 0.3.11 1.6 - 1.8 - junit - junit - ${junit.version} - test - - - org.apache.geronimo.specs - geronimo-activation_1.1_spec - ${geronimo-activation.version} - - - org.apache.geronimo.javamail - geronimo-javamail_1.4_mail - ${geronimo-javamail.version} + ca.juliusdavies + not-yet-commons-ssl + ${not-yet-commons-ssl.version} commons-codec @@ -108,50 +97,69 @@ avalon-framework avalon-framework - - logkit - logkit - log4j log4j + + logkit + logkit + javax.servlet servlet-api + + dnsjava + dnsjava + ${dnsjava.version} + + + junit + junit + ${junit.version} + test + + + org.apache.geronimo.javamail + geronimo-javamail_1.4_mail + ${geronimo-javamail.version} + + + org.apache.geronimo.specs + geronimo-activation_1.1_spec + ${geronimo-activation.version} + org.apache.james - apache-mailet-base + apache-mailet-api ${apache-mailet.version} - - javax.mail - mail - javax.activation activation + + javax.mail + mail + org.apache.james apache-mailet-base ${apache-mailet.version} - test - tests - - javax.mail - mail - javax.activation activation + + javax.mail + mail + @@ -161,20 +169,22 @@ test-jar test - - javax.mail - mail - javax.activation activation + + javax.mail + mail + org.apache.james - apache-mailet-api + apache-mailet-base ${apache-mailet.version} + test + tests javax.mail @@ -187,9 +197,14 @@ - dnsjava - dnsjava - ${dnsjava.version} + org.apache.james + apache-mime4j-core + ${apache-mime4j.version} + + + org.apache.james + apache-mime4j-dom + ${apache-mime4j.version} org.apache.james.jdkim @@ -214,21 +229,6 @@ runtime ${log4j.version} - - ca.juliusdavies - not-yet-commons-ssl - ${not-yet-commons-ssl.version} - - - org.apache.james - apache-mime4j-core - ${apache-mime4j.version} - - - org.apache.james - apache-mime4j-dom - ${apache-mime4j.version} - @@ -250,6 +250,26 @@ ${target.jdk} + + org.apache.maven.plugins + maven-site-plugin + 3.7.1 + + + attach-descriptor + + attach-descriptor + + + + + + org.apache.maven.wagon + wagon-ssh + 3.3.3 + + + org.apache.rat apache-rat-plugin @@ -287,26 +307,6 @@ - - org.apache.maven.plugins - maven-site-plugin - 3.7.1 - - - attach-descriptor - - attach-descriptor - - - - - - org.apache.maven.wagon - wagon-ssh - 3.3.3 - - - From c896a1680722f3369625f292e337ecded525ba16 Mon Sep 17 00:00:00 2001 From: Rene Cordier Date: Wed, 4 Sep 2019 18:06:54 +0700 Subject: [PATCH 016/114] JDKIM-42 Remove apache-jdkim-mailets The JDKIM mailet has been moved into James server project --- assemble/pom.xml | 4 - mailets/pom.xml | 114 ------ .../james/jdkim/mailets/CRLFOutputStream.java | 161 -------- .../james/jdkim/mailets/ConvertTo7Bit.java | 85 ----- .../apache/james/jdkim/mailets/DKIMSign.java | 176 --------- .../james/jdkim/mailets/DKIMVerify.java | 108 ------ .../mailets/HeaderSkippingOutputStream.java | 71 ---- .../jdkim/mailets/MimeMessageHeaders.java | 70 ---- mailets/src/reporting-site/site.xml | 29 -- .../james/jdkim/mailets/DKIMSignTest.java | 353 ------------------ .../james/jdkim/mailets/DKIMVerifyTest.java | 126 ------- main/pom.xml | 14 - pom.xml | 71 ---- 13 files changed, 1382 deletions(-) delete mode 100644 mailets/pom.xml delete mode 100644 mailets/src/main/java/org/apache/james/jdkim/mailets/CRLFOutputStream.java delete mode 100644 mailets/src/main/java/org/apache/james/jdkim/mailets/ConvertTo7Bit.java delete mode 100644 mailets/src/main/java/org/apache/james/jdkim/mailets/DKIMSign.java delete mode 100644 mailets/src/main/java/org/apache/james/jdkim/mailets/DKIMVerify.java delete mode 100644 mailets/src/main/java/org/apache/james/jdkim/mailets/HeaderSkippingOutputStream.java delete mode 100644 mailets/src/main/java/org/apache/james/jdkim/mailets/MimeMessageHeaders.java delete mode 100644 mailets/src/reporting-site/site.xml delete mode 100644 mailets/src/test/java/org/apache/james/jdkim/mailets/DKIMSignTest.java delete mode 100644 mailets/src/test/java/org/apache/james/jdkim/mailets/DKIMVerifyTest.java diff --git a/assemble/pom.xml b/assemble/pom.xml index 2468208..8dec225 100644 --- a/assemble/pom.xml +++ b/assemble/pom.xml @@ -51,10 +51,6 @@ org.apache.james.jdkim apache-jdkim-library - - org.apache.james.jdkim - apache-jdkim-mailets - diff --git a/mailets/pom.xml b/mailets/pom.xml deleted file mode 100644 index 846c386..0000000 --- a/mailets/pom.xml +++ /dev/null @@ -1,114 +0,0 @@ - - - - 4.0.0 - - - apache-jdkim-project - org.apache.james.jdkim - 0.3-SNAPSHOT - ../pom.xml - - - apache-jdkim-mailets - bundle - - Apache James :: jDKIM :: Mailets - Mailets integrating DKIM functions - http://james.apache.org/jdkim/mailets/ - - - - ca.juliusdavies - not-yet-commons-ssl - - - commons-codec - commons-codec - - - commons-logging - commons-logging - - - junit - junit - test - - - org.apache.geronimo.javamail - geronimo-javamail_1.4_mail - - - org.apache.geronimo.specs - geronimo-activation_1.1_spec - - - org.apache.james - apache-mailet-api - - - org.apache.james - apache-mailet-base - - - org.apache.james - apache-mailet-base - test-jar - test - - - org.apache.james.jdkim - apache-jdkim-library - - - org.apache.james - apache-mime4j-core - - - org.apache.james - apache-mime4j-dom - - - - - org.apache.james.jdkim - apache-jdkim-library - test-jar - test - - - org.apache.james - apache-mime4j-core - - - org.apache.james - apache-mime4j-dom - - - - - org.apache.logging.log4j - log4j-core - test - - - diff --git a/mailets/src/main/java/org/apache/james/jdkim/mailets/CRLFOutputStream.java b/mailets/src/main/java/org/apache/james/jdkim/mailets/CRLFOutputStream.java deleted file mode 100644 index b686b72..0000000 --- a/mailets/src/main/java/org/apache/james/jdkim/mailets/CRLFOutputStream.java +++ /dev/null @@ -1,161 +0,0 @@ -/**************************************************************** - * 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.mailets; - -import java.io.FilterOutputStream; -import java.io.IOException; -import java.io.OutputStream; - -/** - * A Filter for use with SMTP or other protocols in which lines must end with - * CRLF. Converts every "isolated" occourency of \r or \n with \r\n - * - * RFC 2821 #2.3.7 mandates that line termination is CRLF, and that CR and LF - * must not be transmitted except in that pairing. If we get a naked LF, convert - * to CRLF. - * - */ -public class CRLFOutputStream extends FilterOutputStream { - - /** - * Counter for number of last (0A or 0D). - */ - protected int statusLast; - - protected final static int LAST_WAS_OTHER = 0; - - protected final static int LAST_WAS_CR = 1; - - protected final static int LAST_WAS_LF = 2; - - protected boolean startOfLine = true; - - /** - * Constructor that wraps an OutputStream. - * - * @param out - * the OutputStream to be wrapped - */ - public CRLFOutputStream(OutputStream out) { - super(out); - statusLast = LAST_WAS_LF; // we already assume a CRLF at beginning - // (otherwise TOP would not work correctly - // !) - } - - /** - * Writes a byte to the stream Fixes any naked CR or LF to the RFC 2821 - * mandated CFLF pairing. - * - * @param b - * the byte to write - * - * @throws IOException - * if an error occurs writing the byte - */ - public void write(int b) throws IOException { - switch (b) { - case '\r': - out.write('\r'); - out.write('\n'); - startOfLine = true; - statusLast = LAST_WAS_CR; - break; - case '\n': - if (statusLast != LAST_WAS_CR) { - out.write('\r'); - out.write('\n'); - startOfLine = true; - } - statusLast = LAST_WAS_LF; - break; - default: - // we're no longer at the start of a line - out.write(b); - startOfLine = false; - statusLast = LAST_WAS_OTHER; - break; - } - } - - /** - * Provides an extension point for ExtraDotOutputStream to be able to add - * dots at the beginning of new lines. - * - * @see java.io.FilterOutputStream#write(byte[], int, int) - */ - protected void writeChunk(byte buffer[], int offset, int length) - throws IOException { - out.write(buffer, offset, length); - } - - /** - * @see java.io.FilterOutputStream#write(byte[], int, int) - */ - public synchronized void write(byte buffer[], int offset, int length) - throws IOException { - /* optimized */ - int lineStart = offset; - for (int i = offset; i < length + offset; i++) { - switch (buffer[i]) { - case '\r': - // CR case. Write down the last line - // and position the new lineStart at the next char - writeChunk(buffer, lineStart, i - lineStart); - out.write('\r'); - out.write('\n'); - startOfLine = true; - lineStart = i + 1; - statusLast = LAST_WAS_CR; - break; - case '\n': - if (statusLast != LAST_WAS_CR) { - writeChunk(buffer, lineStart, i - lineStart); - out.write('\r'); - out.write('\n'); - startOfLine = true; - } - lineStart = i + 1; - statusLast = LAST_WAS_LF; - break; - default: - statusLast = LAST_WAS_OTHER; - } - } - if (length + offset > lineStart) { - writeChunk(buffer, lineStart, length + offset - lineStart); - startOfLine = false; - } - } - - /** - * Ensure that the stream is CRLF terminated. - * - * @throws IOException - * if an error occurs writing the byte - */ - public void checkCRLFTerminator() throws IOException { - if (statusLast == LAST_WAS_OTHER) { - out.write('\r'); - out.write('\n'); - statusLast = LAST_WAS_CR; - } - } -} diff --git a/mailets/src/main/java/org/apache/james/jdkim/mailets/ConvertTo7Bit.java b/mailets/src/main/java/org/apache/james/jdkim/mailets/ConvertTo7Bit.java deleted file mode 100644 index 72e4e77..0000000 --- a/mailets/src/main/java/org/apache/james/jdkim/mailets/ConvertTo7Bit.java +++ /dev/null @@ -1,85 +0,0 @@ -/**************************************************************** - * 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.mailets; - -import java.io.IOException; - -import javax.mail.MessagingException; -import javax.mail.internet.MimeMessage; -import javax.mail.internet.MimeMultipart; -import javax.mail.internet.MimePart; - -import org.apache.mailet.Mail; -import org.apache.mailet.base.GenericMailet; - -/** - * Make sure the message stream is 7bit. Every 8bit part is encoded to - * quoted-printable or base64 and the message is saved. - */ -public class ConvertTo7Bit extends GenericMailet { - - public void service(Mail mail) throws MessagingException { - MimeMessage message = mail.getMessage(); - try { - convertTo7Bit(message); - message.saveChanges(); - } catch (IOException e) { - throw new MessagingException( - "IOException converting message to 7bit: " + e.getMessage(), - e); - } - } - - /** - * Converts a message to 7 bit. - * - * @param part - */ - private void convertTo7Bit(MimePart part) throws MessagingException, - IOException { - if (part.isMimeType("multipart/*")) { - MimeMultipart parts = (MimeMultipart) part.getContent(); - int count = parts.getCount(); - for (int i = 0; i < count; i++) { - convertTo7Bit((MimePart) parts.getBodyPart(i)); - } - } else if ("8bit".equals(part.getEncoding())) { - // The content may already be in encoded the form (likely with mail - // created from a stream). In that case, just changing the encoding - // to quoted-printable will mangle the result when this is - // transmitted. - // We must first convert the content into its native format, set it - // back, and only THEN set the transfer encoding to force the - // content to be encoded appropriately. - - // if the part doesn't contain text it will be base64 encoded. - String contentTransferEncoding = part.isMimeType("text/*") ? "quoted-printable" - : "base64"; - part.setContent(part.getContent(), part.getContentType()); - part - .setHeader("Content-Transfer-Encoding", - contentTransferEncoding); - part.addHeader("X-MIME-Autoconverted", "from 8bit to " - + contentTransferEncoding + " by " - + getMailetContext().getServerInfo()); - } - } - -} diff --git a/mailets/src/main/java/org/apache/james/jdkim/mailets/DKIMSign.java b/mailets/src/main/java/org/apache/james/jdkim/mailets/DKIMSign.java deleted file mode 100644 index c71d754..0000000 --- a/mailets/src/main/java/org/apache/james/jdkim/mailets/DKIMSign.java +++ /dev/null @@ -1,176 +0,0 @@ -/**************************************************************** - * 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.mailets; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.security.GeneralSecurityException; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.spec.InvalidKeySpecException; -import java.util.Enumeration; -import java.util.LinkedList; -import java.util.List; - -import javax.mail.Header; -import javax.mail.MessagingException; -import javax.mail.internet.MimeMessage; - -import org.apache.commons.ssl.PKCS8Key; -import org.apache.james.jdkim.DKIMSigner; -import org.apache.james.jdkim.api.BodyHasher; -import org.apache.james.jdkim.api.Headers; -import org.apache.james.jdkim.api.SignatureRecord; -import org.apache.james.jdkim.exceptions.PermFailException; -import org.apache.mailet.Mail; -import org.apache.mailet.base.GenericMailet; - -/** - * This mailet sign a message using the DKIM protocol - * If the privateKey is encoded using a password then you can pass - * the password as privateKeyPassword parameter. - * - * Sample configuration: - * - *

- * <mailet match="All" class="DKIMSign">
- *   <signatureTemplate>v=1; s=selector; d=example.com; h=from:to:received:received; a=rsa-sha256; bh=; b=;</signatureTemplate>
- *   <privateKey>
- *   -----BEGIN RSA PRIVATE KEY-----
- *   MIICXAIBAAKBgQDYDaYKXzwVYwqWbLhmuJ66aTAN8wmDR+rfHE8HfnkSOax0oIoT
- *   M5zquZrTLo30870YMfYzxwfB6j/Nz3QdwrUD/t0YMYJiUKyWJnCKfZXHJBJ+yfRH
- *   r7oW+UW3cVo9CG2bBfIxsInwYe175g9UjyntJpWueqdEIo1c2bhv9Mp66QIDAQAB
- *   AoGBAI8XcwnZi0Sq5N89wF+gFNhnREFo3rsJDaCY8iqHdA5DDlnr3abb/yhipw0I
- *   /1HlgC6fIG2oexXOXFWl+USgqRt1kTt9jXhVFExg8mNko2UelAwFtsl8CRjVcYQO
- *   cedeH/WM/mXjg2wUqqZenBmlKlD6vNb70jFJeVaDJ/7n7j8BAkEA9NkH2D4Zgj/I
- *   OAVYccZYH74+VgO0e7VkUjQk9wtJ2j6cGqJ6Pfj0roVIMUWzoBb8YfErR8l6JnVQ
- *   bfy83gJeiQJBAOHk3ow7JjAn8XuOyZx24KcTaYWKUkAQfRWYDFFOYQF4KV9xLSEt
- *   ycY0kjsdxGKDudWcsATllFzXDCQF6DTNIWECQEA52ePwTjKrVnLTfCLEG4OgHKvl
- *   Zud4amthwDyJWoMEH2ChNB2je1N4JLrABOE+hk+OuoKnKAKEjWd8f3Jg/rkCQHj8
- *   mQmogHqYWikgP/FSZl518jV48Tao3iXbqvU9Mo2T6yzYNCCqIoDLFWseNVnCTZ0Q
- *   b+IfiEf1UeZVV5o4J+ECQDatNnS3V9qYUKjj/krNRD/U0+7eh8S2ylLqD3RlSn9K
- *   tYGRMgAtUXtiOEizBH6bd/orzI9V9sw8yBz+ZqIH25Q=
- *   -----END RSA PRIVATE KEY-----
- *   </privateKey>
- * </mailet>
- * 
- * - * By default the mailet assume that Javamail will convert LF to CRLF when sending - * so will compute the hash using converted newlines. If you don't want this - * behaviout then set forceCRLF attribute to false. - */ -public class DKIMSign extends GenericMailet { - - private String signatureTemplate; - private PrivateKey privateKey; - private boolean forceCRLF; - - /** - * @return the signatureTemplate - */ - protected String getSignatureTemplate() { - return signatureTemplate; - } - - /** - * @return the privateKey - */ - protected PrivateKey getPrivateKey() { - return privateKey; - } - - public void init() throws MessagingException { - signatureTemplate = getInitParameter("signatureTemplate"); - String privateKeyString = getInitParameter("privateKey"); - String privateKeyPassword = getInitParameter("privateKeyPassword", null); - forceCRLF = getInitParameter("forceCRLF", true); - try { - PKCS8Key pkcs8 = new PKCS8Key(new ByteArrayInputStream( - privateKeyString.getBytes()), - privateKeyPassword != null ? privateKeyPassword - .toCharArray() : null); - privateKey = pkcs8.getPrivateKey(); - // privateKey = DKIMSigner.getPrivateKey(privateKeyString); - } catch (NoSuchAlgorithmException e) { - throw new MessagingException("Unknown private key algorythm: " - + e.getMessage(), e); - } catch (InvalidKeySpecException e) { - throw new MessagingException( - "PrivateKey should be in base64 encoded PKCS8 (der) format: " - + e.getMessage(), e); - } catch (GeneralSecurityException e) { - throw new MessagingException("General security exception: " - + e.getMessage(), e); - } - } - - public void service(Mail mail) throws MessagingException { - DKIMSigner signer = new DKIMSigner(getSignatureTemplate(), getPrivateKey()); - SignatureRecord signRecord = signer - .newSignatureRecordTemplate(getSignatureTemplate()); - try { - BodyHasher bhj = signer.newBodyHasher(signRecord); - MimeMessage message = mail.getMessage(); - Headers headers = new MimeMessageHeaders(message); - try { - OutputStream os = new HeaderSkippingOutputStream(bhj.getOutputStream()); - if (forceCRLF) os = new CRLFOutputStream(os); - message.writeTo(os); - bhj.getOutputStream().close(); - } catch (IOException e) { - throw new MessagingException("Exception calculating bodyhash: " - + e.getMessage(), e); - } - String signatureHeader = signer.sign(headers, bhj); - // Unfortunately JavaMail does not give us a method to add headers - // on top. - // message.addHeaderLine(signatureHeader); - prependHeader(message, signatureHeader); - } catch (PermFailException e) { - throw new MessagingException("PermFail while signing: " - + e.getMessage(), e); - } - - } - - private void prependHeader(MimeMessage message, String signatureHeader) - throws MessagingException { - List prevHeader = new LinkedList(); - // read all the headers - for (Enumeration e = message.getAllHeaderLines(); e.hasMoreElements();) { - String headerLine = e.nextElement(); - prevHeader.add(headerLine); - } - // remove all the headers - for (Enumeration
e = message.getAllHeaders(); e.hasMoreElements();) { - Header header = e.nextElement(); - message.removeHeader(header.getName()); - } - // add our header - message.addHeaderLine(signatureHeader); - // add the remaining headers using "addHeaderLine" that won't alter the - // insertion order. - for (String header : prevHeader) { - message.addHeaderLine(header); - } - } - -} diff --git a/mailets/src/main/java/org/apache/james/jdkim/mailets/DKIMVerify.java b/mailets/src/main/java/org/apache/james/jdkim/mailets/DKIMVerify.java deleted file mode 100644 index 68c0cf9..0000000 --- a/mailets/src/main/java/org/apache/james/jdkim/mailets/DKIMVerify.java +++ /dev/null @@ -1,108 +0,0 @@ -/**************************************************************** - * 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.mailets; - -import java.io.IOException; -import java.io.OutputStream; -import java.util.List; - -import javax.mail.MessagingException; -import javax.mail.internet.MimeMessage; - -import org.apache.james.jdkim.DKIMVerifier; -import org.apache.james.jdkim.api.BodyHasher; -import org.apache.james.jdkim.api.Headers; -import org.apache.james.jdkim.api.SignatureRecord; -import org.apache.james.jdkim.exceptions.FailException; -import org.apache.mailet.Mail; -import org.apache.mailet.base.GenericMailet; - -/** - * This mailet verify a message using the DKIM protocol - * - * Sample configuration: - *

- * <mailet match="All" class="DKIMVerify">
- * </mailet>
- * 
- * - * By default the mailet assume that Javamail will use LF instead of CRLF - * so it will verify the hash using converted newlines. If you don't want this - * behaviout then set forceCRLF attribute to false. - */ -public class DKIMVerify extends GenericMailet { - - public static final String DKIM_AUTH_RESULT_ATTRIBUTE = "jDKIM.AUTHRESULT"; - - protected DKIMVerifier verifier = null; - private boolean forceCRLF; - - @Override - public void init() throws MessagingException { - verifier = new DKIMVerifier(); - forceCRLF = getInitParameter("forceCRLF", true); - } - - public void service(Mail mail) throws MessagingException { - try { - MimeMessage message = mail.getMessage(); - List res = verify(verifier, message, forceCRLF); - if (res == null || res.isEmpty()) { - // neutral - mail.setAttribute(DKIM_AUTH_RESULT_ATTRIBUTE, "neutral (no signatures)"); - } else { - // pass - StringBuilder msg = new StringBuilder(); - msg.append("pass"); - for (SignatureRecord rec : res) { - msg.append(" ("); - msg.append("identity "); - msg.append(rec.getIdentity().toString()); - msg.append(")"); - } - mail.setAttribute(DKIM_AUTH_RESULT_ATTRIBUTE, msg.toString()); - } - } catch (FailException e) { - // fail - mail.setAttribute(DKIM_AUTH_RESULT_ATTRIBUTE, "fail ("+(e.getRelatedRecordIdentity() != null ? "identity "+ e.getRelatedRecordIdentity() + ": " : "")+e.getMessage()+")"); - } - - } - - protected static List verify(DKIMVerifier verifier, MimeMessage message, boolean forceCRLF) - throws MessagingException, FailException { - Headers headers = new MimeMessageHeaders(message); - BodyHasher bh = verifier.newBodyHasher(headers); - try { - if (bh != null) { - OutputStream os = new HeaderSkippingOutputStream(bh - .getOutputStream()); - if (forceCRLF) os = new CRLFOutputStream(os); - message.writeTo(os); - bh.getOutputStream().close(); - } - - } catch (IOException e) { - throw new MessagingException("Exception calculating bodyhash: " - + e.getMessage(), e); - } - return verifier.verify(bh); - } -} diff --git a/mailets/src/main/java/org/apache/james/jdkim/mailets/HeaderSkippingOutputStream.java b/mailets/src/main/java/org/apache/james/jdkim/mailets/HeaderSkippingOutputStream.java deleted file mode 100644 index 16e7f25..0000000 --- a/mailets/src/main/java/org/apache/james/jdkim/mailets/HeaderSkippingOutputStream.java +++ /dev/null @@ -1,71 +0,0 @@ -/**************************************************************** - * 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.mailets; - -import java.io.FilterOutputStream; -import java.io.IOException; -import java.io.OutputStream; - -/** - * Ignore writes until the given sequence is found. - */ -public class HeaderSkippingOutputStream extends FilterOutputStream { - - boolean inHeaders = true; - final byte[] skipTo = "\r\n\r\n".getBytes(); - int pos = 0; - - public HeaderSkippingOutputStream(OutputStream out) { - super(out); - } - - public void write(byte[] b, int off, int len) throws IOException { - if (inHeaders) { - for (int i = off; i < off + len; i++) { - if (b[i] == skipTo[pos]) { - pos++; - if (pos == skipTo.length) { - inHeaders = false; - if (len - i - 1 > 0) - out.write(b, i + 1, len - i - 1); - break; - } - } else - pos = 0; - } - } else { - out.write(b, off, len); - } - } - - public void write(int b) throws IOException { - if (inHeaders) { - if (skipTo[pos] == b) { - pos++; - if (pos == skipTo.length) - inHeaders = false; - } else - pos = 0; - } else { - out.write(b); - } - } - -} diff --git a/mailets/src/main/java/org/apache/james/jdkim/mailets/MimeMessageHeaders.java b/mailets/src/main/java/org/apache/james/jdkim/mailets/MimeMessageHeaders.java deleted file mode 100644 index 5b70968..0000000 --- a/mailets/src/main/java/org/apache/james/jdkim/mailets/MimeMessageHeaders.java +++ /dev/null @@ -1,70 +0,0 @@ -/**************************************************************** - * 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.mailets; - -import java.util.Enumeration; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - -import javax.mail.MessagingException; -import javax.mail.internet.MimeMessage; - -import org.apache.james.jdkim.api.Headers; - -/** - * An adapter to let DKIMSigner read headers from MimeMessage - */ -final class MimeMessageHeaders implements Headers { - - private final Map> headers; - private final List fields; - - public MimeMessageHeaders(MimeMessage message) - throws MessagingException { - headers = new HashMap>(); - fields = new LinkedList(); - for (Enumeration e = message.getAllHeaderLines(); e - .hasMoreElements();) { - String head = e.nextElement(); - int p = head.indexOf(':'); - if (p <= 0) - throw new MessagingException("Bad header line: " + head); - String headerName = head.substring(0, p).trim(); - String headerNameLC = headerName.toLowerCase(); - fields.add(headerName); - List strings = headers.get(headerNameLC); - if (strings == null) { - strings = new LinkedList(); - headers.put(headerNameLC, strings); - } - strings.add(head); - } - } - - public List getFields() { - return fields; - } - - public List getFields(String name) { - return headers.get(name.toLowerCase()); - } -} \ No newline at end of file diff --git a/mailets/src/reporting-site/site.xml b/mailets/src/reporting-site/site.xml deleted file mode 100644 index d919164..0000000 --- a/mailets/src/reporting-site/site.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - diff --git a/mailets/src/test/java/org/apache/james/jdkim/mailets/DKIMSignTest.java b/mailets/src/test/java/org/apache/james/jdkim/mailets/DKIMSignTest.java deleted file mode 100644 index 2e46dd0..0000000 --- a/mailets/src/test/java/org/apache/james/jdkim/mailets/DKIMSignTest.java +++ /dev/null @@ -1,353 +0,0 @@ -/**************************************************************** - * 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.mailets; - -import org.apache.james.jdkim.DKIMVerifier; -import org.apache.james.jdkim.MockPublicKeyRecordRetriever; -import org.apache.james.jdkim.api.SignatureRecord; -import org.apache.james.jdkim.exceptions.FailException; -import org.apache.james.jdkim.exceptions.PermFailException; -import org.apache.mailet.Mail; -import org.apache.mailet.Mailet; -import org.apache.mailet.base.test.FakeMail; -import org.apache.mailet.base.test.FakeMailContext; -import org.apache.mailet.base.test.FakeMailetConfig; -import org.junit.Assert; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import org.junit.Test; - -import javax.mail.Address; -import javax.mail.MessagingException; -import javax.mail.Session; -import javax.mail.internet.InternetAddress; -import javax.mail.internet.MimeMessage; -import javax.mail.internet.MimeMessage.RecipientType; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.List; -import java.util.Properties; - -public class DKIMSignTest { - - private static final String TESTING_PEM = "-----BEGIN RSA PRIVATE KEY-----\r\n" + - "MIICXAIBAAKBgQDYDaYKXzwVYwqWbLhmuJ66aTAN8wmDR+rfHE8HfnkSOax0oIoT\r\n" + - "M5zquZrTLo30870YMfYzxwfB6j/Nz3QdwrUD/t0YMYJiUKyWJnCKfZXHJBJ+yfRH\r\n" + - "r7oW+UW3cVo9CG2bBfIxsInwYe175g9UjyntJpWueqdEIo1c2bhv9Mp66QIDAQAB\r\n" + - "AoGBAI8XcwnZi0Sq5N89wF+gFNhnREFo3rsJDaCY8iqHdA5DDlnr3abb/yhipw0I\r\n" + - "/1HlgC6fIG2oexXOXFWl+USgqRt1kTt9jXhVFExg8mNko2UelAwFtsl8CRjVcYQO\r\n" + - "cedeH/WM/mXjg2wUqqZenBmlKlD6vNb70jFJeVaDJ/7n7j8BAkEA9NkH2D4Zgj/I\r\n" + - "OAVYccZYH74+VgO0e7VkUjQk9wtJ2j6cGqJ6Pfj0roVIMUWzoBb8YfErR8l6JnVQ\r\n" + - "bfy83gJeiQJBAOHk3ow7JjAn8XuOyZx24KcTaYWKUkAQfRWYDFFOYQF4KV9xLSEt\r\n" + - "ycY0kjsdxGKDudWcsATllFzXDCQF6DTNIWECQEA52ePwTjKrVnLTfCLEG4OgHKvl\r\n" + - "Zud4amthwDyJWoMEH2ChNB2je1N4JLrABOE+hk+OuoKnKAKEjWd8f3Jg/rkCQHj8\r\n" + - "mQmogHqYWikgP/FSZl518jV48Tao3iXbqvU9Mo2T6yzYNCCqIoDLFWseNVnCTZ0Q\r\n" + - "b+IfiEf1UeZVV5o4J+ECQDatNnS3V9qYUKjj/krNRD/U0+7eh8S2ylLqD3RlSn9K\r\n" + - "tYGRMgAtUXtiOEizBH6bd/orzI9V9sw8yBz+ZqIH25Q=\r\n" + - "-----END RSA PRIVATE KEY-----\r\n"; - private static final FakeMailContext FAKE_MAIL_CONTEXT = FakeMailContext.defaultContext(); - - @Test - public void testDKIMSign() throws MessagingException, IOException, - FailException { - String message = "Received: by 10.XX.XX.12 with SMTP id dfgskldjfhgkljsdfhgkljdhfg;\r\n\tTue, 06 Oct 2009 07:37:34 -0700 (PDT)\r\nReturn-Path: \r\nReceived: from example.co.uk (example.co.uk [XX.XXX.125.19])\r\n\tby mx.example.com with ESMTP id dgdfgsdfgsd.97.2009.10.06.07.37.32;\r\n\tTue, 06 Oct 2009 07:37:32 -0700 (PDT)\r\nFrom: apache@bago.org\r\nTo: apache@bago.org\r\n\r\nbody\r\nprova\r\n"; - - Mailet mailet = new DKIMSign(); - - FakeMailetConfig mci = FakeMailetConfig.builder() - .mailetName("Test") - .mailetContext(FAKE_MAIL_CONTEXT) - .setProperty( - "signatureTemplate", - "v=1; s=selector; d=example.com; h=from:to:received:received; a=rsa-sha256; bh=; b=;") - .setProperty("privateKey", TESTING_PEM) - .build(); - - mailet.init(mci); - - Mail mail = FakeMail.builder() - .mimeMessage(new MimeMessage(Session - .getDefaultInstance(new Properties()), - new ByteArrayInputStream(message.getBytes()))) - .build(); - - mailet.service(mail); - - Mailet m7bit = new ConvertTo7Bit(); - m7bit.init(mci); - m7bit.service(mail); - - ByteArrayOutputStream rawMessage = new ByteArrayOutputStream(); - mail.getMessage().writeTo(rawMessage); - - MockPublicKeyRecordRetriever mockPublicKeyRecordRetriever = new MockPublicKeyRecordRetriever( - "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYDaYKXzwVYwqWbLhmuJ66aTAN8wmDR+rfHE8HfnkSOax0oIoTM5zquZrTLo30870YMfYzxwfB6j/Nz3QdwrUD/t0YMYJiUKyWJnCKfZXHJBJ+yfRHr7oW+UW3cVo9CG2bBfIxsInwYe175g9UjyntJpWueqdEIo1c2bhv9Mp66QIDAQAB;", - "selector", "example.com"); - verify(rawMessage, mockPublicKeyRecordRetriever); - } - - private List verify(ByteArrayOutputStream rawMessage, - MockPublicKeyRecordRetriever mockPublicKeyRecordRetriever) - throws MessagingException, FailException { - List signs = DKIMVerify.verify(new DKIMVerifier(mockPublicKeyRecordRetriever), new MimeMessage(Session.getDefaultInstance(new Properties()), new ByteArrayInputStream(rawMessage.toByteArray())), true); - assertNotNull(signs); - assertEquals(1, signs.size()); - return signs; - } - - @Test - public void testDKIMSignFuture() throws MessagingException, IOException, - FailException { - String message = "Received: by 10.XX.XX.12 with SMTP id dfgskldjfhgkljsdfhgkljdhfg;\r\n\tTue, 06 Oct 2009 07:37:34 -0700 (PDT)\r\nReturn-Path: \r\nReceived: from example.co.uk (example.co.uk [XX.XXX.125.19])\r\n\tby mx.example.com with ESMTP id dgdfgsdfgsd.97.2009.10.06.07.37.32;\r\n\tTue, 06 Oct 2009 07:37:32 -0700 (PDT)\r\nFrom: apache@bago.org\r\nTo: apache@bago.org\r\n\r\nbody\r\nprova\r\n"; - - Mailet mailet = new DKIMSign(); - - FakeMailetConfig mci = FakeMailetConfig.builder() - .mailetName("Test") - .mailetContext(FAKE_MAIL_CONTEXT) - .setProperty( - "signatureTemplate", - "v=1; t=" + ((System.currentTimeMillis() / 1000) + 1000) + "; s=selector; d=example.com; h=from:to:received:received; a=rsa-sha256; bh=; b=;") - .setProperty("privateKey", TESTING_PEM) - .build(); - - mailet.init(mci); - - Mail mail = FakeMail.builder() - .mimeMessage(new MimeMessage(Session - .getDefaultInstance(new Properties()), - new ByteArrayInputStream(message.getBytes()))) - .build(); - - mailet.service(mail); - - Mailet m7bit = new ConvertTo7Bit(); - m7bit.init(mci); - m7bit.service(mail); - - ByteArrayOutputStream rawMessage = new ByteArrayOutputStream(); - mail.getMessage().writeTo(rawMessage); - - MockPublicKeyRecordRetriever mockPublicKeyRecordRetriever = new MockPublicKeyRecordRetriever( - "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYDaYKXzwVYwqWbLhmuJ66aTAN8wmDR+rfHE8HfnkSOax0oIoTM5zquZrTLo30870YMfYzxwfB6j/Nz3QdwrUD/t0YMYJiUKyWJnCKfZXHJBJ+yfRHr7oW+UW3cVo9CG2bBfIxsInwYe175g9UjyntJpWueqdEIo1c2bhv9Mp66QIDAQAB;", - "selector", "example.com"); - try { - verify(rawMessage, mockPublicKeyRecordRetriever); - Assert.fail("Expecting signature to be ignored"); - } catch (PermFailException e) { - // signature ignored, so fail for missing signatures. - } - } - - - @Test - public void testDKIMSignTime() throws MessagingException, IOException, - FailException { - String message = "Received: by 10.XX.XX.12 with SMTP id dfgskldjfhgkljsdfhgkljdhfg;\r\n\tTue, 06 Oct 2009 07:37:34 -0700 (PDT)\r\nReturn-Path: \r\nReceived: from example.co.uk (example.co.uk [XX.XXX.125.19])\r\n\tby mx.example.com with ESMTP id dgdfgsdfgsd.97.2009.10.06.07.37.32;\r\n\tTue, 06 Oct 2009 07:37:32 -0700 (PDT)\r\nFrom: apache@bago.org\r\nTo: apache@bago.org\r\n\r\nbody\r\nprova\r\n"; - - Mailet mailet = new DKIMSign(); - - FakeMailetConfig mci = FakeMailetConfig.builder() - .mailetName("Test") - .mailetContext(FAKE_MAIL_CONTEXT) - .setProperty( - "signatureTemplate", - "v=1; t=; s=selector; d=example.com; h=from:to:received:received; a=rsa-sha256; bh=; b=;") - .setProperty("privateKey", TESTING_PEM) - .build(); - - mailet.init(mci); - - Mail mail = FakeMail.builder() - .mimeMessage(new MimeMessage(Session - .getDefaultInstance(new Properties()), - new ByteArrayInputStream(message.getBytes()))) - .build(); - - mailet.service(mail); - - Mailet m7bit = new ConvertTo7Bit(); - m7bit.init(mci); - m7bit.service(mail); - - ByteArrayOutputStream rawMessage = new ByteArrayOutputStream(); - mail.getMessage().writeTo(rawMessage); - - MockPublicKeyRecordRetriever mockPublicKeyRecordRetriever = new MockPublicKeyRecordRetriever( - "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYDaYKXzwVYwqWbLhmuJ66aTAN8wmDR+rfHE8HfnkSOax0oIoTM5zquZrTLo30870YMfYzxwfB6j/Nz3QdwrUD/t0YMYJiUKyWJnCKfZXHJBJ+yfRHr7oW+UW3cVo9CG2bBfIxsInwYe175g9UjyntJpWueqdEIo1c2bhv9Mp66QIDAQAB;", - "selector", "example.com"); - verify(rawMessage, mockPublicKeyRecordRetriever); - - List rs = verify(rawMessage, mockPublicKeyRecordRetriever); - - // check we have a valued signatureTimestamp - Assert.assertNotNull(rs.get(0).getSignatureTimestamp()); - long ref = System.currentTimeMillis() / 1000; - // Chech that the signature timestamp is in the past 60 seconds. - Assert.assertTrue(rs.get(0).getSignatureTimestamp() <= ref); - Assert.assertTrue(rs.get(0).getSignatureTimestamp() >= ref - 60); - } - - @Test - public void testDKIMSignMessageAsText() throws MessagingException, - IOException, FailException { - MimeMessage mm = new MimeMessage(Session - .getDefaultInstance(new Properties())); - mm.addFrom(new Address[]{new InternetAddress("io@bago.org")}); - mm.addRecipient(RecipientType.TO, new InternetAddress("io@bago.org")); - mm.setText("An 8bit encoded body with \u20ACuro symbol.", "ISO-8859-15"); - - Mailet mailet = new DKIMSign(); - - FakeMailetConfig mci = FakeMailetConfig.builder() - .mailetName("Test") - .mailetContext(FAKE_MAIL_CONTEXT) - .setProperty( - "signatureTemplate", - "v=1; s=selector; d=example.com; h=from:to:received:received; a=rsa-sha256; bh=; b=;") - .setProperty("privateKey", TESTING_PEM) - .build(); - - mailet.init(mci); - - Mail mail = FakeMail.builder() - .mimeMessage(mm) - .build(); - - Mailet m7bit = new ConvertTo7Bit(); - m7bit.init(mci); - - mailet.service(mail); - - m7bit.service(mail); - - ByteArrayOutputStream rawMessage = new ByteArrayOutputStream(); - mail.getMessage().writeTo(rawMessage); - - MockPublicKeyRecordRetriever mockPublicKeyRecordRetriever = new MockPublicKeyRecordRetriever( - "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYDaYKXzwVYwqWbLhmuJ66aTAN8wmDR+rfHE8HfnkSOax0oIoTM5zquZrTLo30870YMfYzxwfB6j/Nz3QdwrUD/t0YMYJiUKyWJnCKfZXHJBJ+yfRHr7oW+UW3cVo9CG2bBfIxsInwYe175g9UjyntJpWueqdEIo1c2bhv9Mp66QIDAQAB;", - "selector", "example.com"); - - verify(rawMessage, mockPublicKeyRecordRetriever); - } - - @Test - public void testDKIMSignMessageAsObjectConvertedTo7Bit() - throws MessagingException, IOException, FailException { - MimeMessage mm = new MimeMessage(Session - .getDefaultInstance(new Properties())); - mm.addFrom(new Address[]{new InternetAddress("io@bago.org")}); - mm.addRecipient(RecipientType.TO, new InternetAddress("io@bago.org")); - mm.setContent("An 8bit encoded body with \u20ACuro symbol.", - "text/plain; charset=iso-8859-15"); - mm.setHeader("Content-Transfer-Encoding", "8bit"); - mm.saveChanges(); - - FakeMailContext FakeMailContext = FAKE_MAIL_CONTEXT; - FakeMailContext.getServerInfo(); - - FakeMailetConfig mci = FakeMailetConfig.builder() - .mailetName("Test") - .mailetContext(FAKE_MAIL_CONTEXT) - .setProperty( - "signatureTemplate", - "v=1; s=selector; d=example.com; h=from:to:received:received; a=rsa-sha256; bh=; b=;") - .setProperty("privateKey", TESTING_PEM) - .build(); - - Mail mail = FakeMail.builder() - .mimeMessage(mm) - .build(); - - Mailet mailet = new DKIMSign(); - mailet.init(mci); - - Mailet m7bit = new ConvertTo7Bit(); - m7bit.init(mci); - m7bit.service(mail); - - mailet.service(mail); - - m7bit.service(mail); - - ByteArrayOutputStream rawMessage = new ByteArrayOutputStream(); - mail.getMessage().writeTo(rawMessage); - - MockPublicKeyRecordRetriever mockPublicKeyRecordRetriever = new MockPublicKeyRecordRetriever( - "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYDaYKXzwVYwqWbLhmuJ66aTAN8wmDR+rfHE8HfnkSOax0oIoTM5zquZrTLo30870YMfYzxwfB6j/Nz3QdwrUD/t0YMYJiUKyWJnCKfZXHJBJ+yfRHr7oW+UW3cVo9CG2bBfIxsInwYe175g9UjyntJpWueqdEIo1c2bhv9Mp66QIDAQAB;", - "selector", "example.com"); - verify(rawMessage, mockPublicKeyRecordRetriever); - } - - @Test - public void testDKIMSignMessageAsObjectNotConverted() - throws MessagingException, IOException, FailException { - MimeMessage mm = new MimeMessage(Session - .getDefaultInstance(new Properties())); - mm.addFrom(new Address[]{new InternetAddress("io@bago.org")}); - mm.addRecipient(RecipientType.TO, new InternetAddress("io@bago.org")); - mm.setContent("An 8bit encoded body with \u20ACuro symbol.", - "text/plain; charset=iso-8859-15"); - mm.setHeader("Content-Transfer-Encoding", "8bit"); - mm.saveChanges(); - - FakeMailContext FakeMailContext = FAKE_MAIL_CONTEXT; - FakeMailContext.getServerInfo(); - - FakeMailetConfig mci = FakeMailetConfig.builder() - .mailetName("Test") - .mailetContext(FAKE_MAIL_CONTEXT) - .setProperty( - "signatureTemplate", - "v=1; s=selector; d=example.com; h=from:to:received:received; a=rsa-sha256; bh=; b=;") - .setProperty("privateKey", TESTING_PEM) - .build(); - - Mail mail = FakeMail.builder() - .mimeMessage(mm) - .build(); - - Mailet mailet = new DKIMSign(); - mailet.init(mci); - - Mailet m7bit = new ConvertTo7Bit(); - m7bit.init(mci); - // m7bit.service(mail); - - mailet.service(mail); - - m7bit.service(mail); - - ByteArrayOutputStream rawMessage = new ByteArrayOutputStream(); - mail.getMessage().writeTo(rawMessage); - - MockPublicKeyRecordRetriever mockPublicKeyRecordRetriever = new MockPublicKeyRecordRetriever( - "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYDaYKXzwVYwqWbLhmuJ66aTAN8wmDR+rfHE8HfnkSOax0oIoTM5zquZrTLo30870YMfYzxwfB6j/Nz3QdwrUD/t0YMYJiUKyWJnCKfZXHJBJ+yfRHr7oW+UW3cVo9CG2bBfIxsInwYe175g9UjyntJpWueqdEIo1c2bhv9Mp66QIDAQAB;", - "selector", "example.com"); - try { - verify(rawMessage, mockPublicKeyRecordRetriever); - Assert.fail("Expected PermFail"); - } catch (PermFailException e) { - - } - } - -} diff --git a/mailets/src/test/java/org/apache/james/jdkim/mailets/DKIMVerifyTest.java b/mailets/src/test/java/org/apache/james/jdkim/mailets/DKIMVerifyTest.java deleted file mode 100644 index cf4129a..0000000 --- a/mailets/src/test/java/org/apache/james/jdkim/mailets/DKIMVerifyTest.java +++ /dev/null @@ -1,126 +0,0 @@ -/**************************************************************** - * 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.mailets; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.util.Properties; - -import javax.mail.MessagingException; -import javax.mail.Session; -import javax.mail.internet.MimeMessage; - -import org.apache.james.jdkim.DKIMVerifier; -import org.apache.james.jdkim.MockPublicKeyRecordRetriever; -import org.apache.james.jdkim.exceptions.FailException; -import org.apache.mailet.Mail; -import org.apache.mailet.Mailet; -import org.apache.mailet.base.test.FakeMail; -import org.apache.mailet.base.test.FakeMailContext; -import org.apache.mailet.base.test.FakeMailetConfig; -import org.junit.Assert; -import org.junit.Test; - -public class DKIMVerifyTest { - - @Test - public void testDKIMVerifyPass() throws MessagingException, IOException, - FailException { - String message = "DKIM-Signature: v=1; d=example.com; t=1284762805; b=ZFfwSIzTQM7k9syRnl9VfQh0/dr99euvBe1gn/DiTrnEZjxyjzQBD2MMvowVdbHpPMtSjtCtehU9zZ3urXmj5iHKujpEkP92FEKinzElkQ2eT2zoxdg1zByPHsKPX+KjrBespAJcO2k052aOK5kIBFxpQumP4aiW7ZklBKSWMBk=; s=selector; a=rsa-sha256; bh=rHOD7fd9xnNxK7OSl5ellpQVF14NNFbOIizqtUMhnio=; h=from:to:received:received;\r\n" - + "Received: by 10.XX.XX.12 with SMTP id dfgskldjfhgkljsdfhgkljdhfg;\r\n\tTue, 06 Oct 2009 07:37:34 -0700 (PDT)\r\nReturn-Path: \r\nReceived: from example.co.uk (example.co.uk [XX.XXX.125.19])\r\n\tby mx.example.com with ESMTP id dgdfgsdfgsd.97.2009.10.06.07.37.32;\r\n\tTue, 06 Oct 2009 07:37:32 -0700 (PDT)\r\nFrom: apache@bago.org\r\nTo: apache@bago.org\r\n\r\nbody\r\nprova\r\n"; - - Mail mail = process(message); - - String attr = (String) mail.getAttribute(DKIMVerify.DKIM_AUTH_RESULT_ATTRIBUTE); - Assert.assertNotNull(attr); - Assert.assertTrue(attr.startsWith("pass")); - } - - @Test - public void testDKIMVerifyFail() throws MessagingException, IOException, - FailException { - // altered message body - String message = "DKIM-Signature: v=1; d=example.com; t=1284762805; b=ZFfwSIzTQM7k9syRnl9VfQh0/dr99euvBe1gn/DiTrnEZjxyjzQBD2MMvowVdbHpPMtSjtCtehU9zZ3urXmj5iHKujpEkP92FEKinzElkQ2eT2zoxdg1zByPHsKPX+KjrBespAJcO2k052aOK5kIBFxpQumP4aiW7ZklBKSWMBk=; s=selector; a=rsa-sha256; bh=rHOD7fd9xnNxK7OSl5ellpQVF14NNFbOIizqtUMhnio=; h=from:to:received:received;\r\n" - + "Received: by 10.XX.XX.12 with SMTP id dfgskldjfhgkljsdfhgkljdhfg;\r\n\tTue, 06 Oct 2009 07:37:34 -0700 (PDT)\r\nReturn-Path: \r\nReceived: from example.co.uk (example.co.uk [XX.XXX.125.19])\r\n\tby mx.example.com with ESMTP id dgdfgsdfgsd.97.2009.10.06.07.37.32;\r\n\tTue, 06 Oct 2009 07:37:32 -0700 (PDT)\r\nFrom: apache@bago.org\r\nTo: apache@bago.org\r\n\r\nbody\r\nprova altered\r\n"; - - Mail mail = process(message); - - String attr = (String) mail.getAttribute(DKIMVerify.DKIM_AUTH_RESULT_ATTRIBUTE); - Assert.assertNotNull(attr); - Assert.assertTrue(attr.startsWith("fail")); - } - - @Test - public void testDKIMVerifyFailInvalid() throws MessagingException, IOException, - FailException { - // invalid version v=2 - String message = "DKIM-Signature: v=2; d=example.com; t=1284762805; b=ZFfwSIzTQM7k9syRnl9VfQh0/dr99euvBe1gn/DiTrnEZjxyjzQBD2MMvowVdbHpPMtSjtCtehU9zZ3urXmj5iHKujpEkP92FEKinzElkQ2eT2zoxdg1zByPHsKPX+KjrBespAJcO2k052aOK5kIBFxpQumP4aiW7ZklBKSWMBk=; s=selector; a=rsa-sha256; bh=rHOD7fd9xnNxK7OSl5ellpQVF14NNFbOIizqtUMhnio=; h=from:to:received:received;\r\n" - + "Received: by 10.XX.XX.12 with SMTP id dfgskldjfhgkljsdfhgkljdhfg;\r\n\tTue, 06 Oct 2009 07:37:34 -0700 (PDT)\r\nReturn-Path: \r\nReceived: from example.co.uk (example.co.uk [XX.XXX.125.19])\r\n\tby mx.example.com with ESMTP id dgdfgsdfgsd.97.2009.10.06.07.37.32;\r\n\tTue, 06 Oct 2009 07:37:32 -0700 (PDT)\r\nFrom: apache@bago.org\r\nTo: apache@bago.org\r\n\r\nbody\r\nprova\r\n"; - - Mail mail = process(message); - - String attr = (String) mail.getAttribute(DKIMVerify.DKIM_AUTH_RESULT_ATTRIBUTE); - Assert.assertNotNull(attr); - Assert.assertTrue(attr.startsWith("fail")); - } - - @Test - public void testDKIMVerifyNeutral() throws MessagingException, IOException, - FailException { - // no signatures! - String message = "" - + "Received: by 10.XX.XX.12 with SMTP id dfgskldjfhgkljsdfhgkljdhfg;\r\n\tTue, 06 Oct 2009 07:37:34 -0700 (PDT)\r\nReturn-Path: \r\nReceived: from example.co.uk (example.co.uk [XX.XXX.125.19])\r\n\tby mx.example.com with ESMTP id dgdfgsdfgsd.97.2009.10.06.07.37.32;\r\n\tTue, 06 Oct 2009 07:37:32 -0700 (PDT)\r\nFrom: apache@bago.org\r\nTo: apache@bago.org\r\n\r\nbody\r\nprova altered\r\n"; - - Mail mail = process(message); - - String attr = (String) mail.getAttribute(DKIMVerify.DKIM_AUTH_RESULT_ATTRIBUTE); - Assert.assertNotNull(attr); - Assert.assertTrue(attr.startsWith("neutral")); - } - - private Mail process(String message) throws MessagingException { - Mailet mailet = new DKIMVerify() { - - @Override - public void init() throws MessagingException { - verifier = new DKIMVerifier(new MockPublicKeyRecordRetriever( - "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYDaYKXzwVYwqWbLhmuJ66aTAN8wmDR+rfHE8HfnkSOax0oIoTM5zquZrTLo30870YMfYzxwfB6j/Nz3QdwrUD/t0YMYJiUKyWJnCKfZXHJBJ+yfRHr7oW+UW3cVo9CG2bBfIxsInwYe175g9UjyntJpWueqdEIo1c2bhv9Mp66QIDAQAB;", - "selector", "example.com")); - } - - }; - - FakeMailetConfig mci = FakeMailetConfig.builder() - .mailetName("Test") - .mailetContext(FakeMailContext.defaultContext()) - .build(); - - mailet.init(mci); - - Mail mail = FakeMail.builder() - .mimeMessage(new MimeMessage(Session - .getDefaultInstance(new Properties()), - new ByteArrayInputStream(message.getBytes()))) - .build(); - - mailet.service(mail); - return mail; - } -} diff --git a/main/pom.xml b/main/pom.xml index 6d6c375..20c2d0b 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -59,20 +59,6 @@ org.apache.geronimo.specs geronimo-activation_1.1_spec - - org.apache.james - apache-mailet-api - - - org.apache.james - apache-mailet-base - - - org.apache.james - apache-mailet-base - test - tests - org.apache.james apache-mime4j-core diff --git a/pom.xml b/pom.xml index f1e0082..478988a 100644 --- a/pom.xml +++ b/pom.xml @@ -40,7 +40,6 @@ assemble - mailets main @@ -62,7 +61,6 @@ - 3.1.0-SNAPSHOT 0.8.3 1.13 1.2 @@ -132,70 +130,6 @@ geronimo-activation_1.1_spec ${geronimo-activation.version} - - org.apache.james - apache-mailet-api - ${apache-mailet.version} - - - javax.activation - activation - - - javax.mail - mail - - - - - org.apache.james - apache-mailet-base - ${apache-mailet.version} - - - javax.activation - activation - - - javax.mail - mail - - - - - org.apache.james - apache-mailet-base - ${apache-mailet.version} - test-jar - test - - - javax.activation - activation - - - javax.mail - mail - - - - - org.apache.james - apache-mailet-base - ${apache-mailet.version} - test - tests - - - javax.mail - mail - - - javax.activation - activation - - - org.apache.james apache-mime4j-core @@ -218,11 +152,6 @@ test-jar test - - org.apache.james.jdkim - apache-jdkim-mailets - ${project.version} - org.apache.logging.log4j log4j-core From 82c43f586117053f08ed6bf315c1e43664ca1d09 Mon Sep 17 00:00:00 2001 From: Rene Cordier Date: Thu, 5 Sep 2019 10:02:02 +0700 Subject: [PATCH 017/114] JDKIM-42 Remove geronimo libs After removing the mailet (been moved to James), those libs are not necessary anymore. --- main/pom.xml | 8 -------- pom.xml | 12 ------------ 2 files changed, 20 deletions(-) diff --git a/main/pom.xml b/main/pom.xml index 20c2d0b..291716d 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -51,14 +51,6 @@ junit junit - - org.apache.geronimo.javamail - geronimo-javamail_1.4_mail - - - org.apache.geronimo.specs - geronimo-activation_1.1_spec - org.apache.james apache-mime4j-core diff --git a/pom.xml b/pom.xml index 478988a..975fe6c 100644 --- a/pom.xml +++ b/pom.xml @@ -65,8 +65,6 @@ 1.13 1.2 2.1.9 - 1.1 - 1.8.3 1.8 4.12 2.12.1 @@ -120,16 +118,6 @@ ${junit.version} test - - org.apache.geronimo.javamail - geronimo-javamail_1.4_mail - ${geronimo-javamail.version} - - - org.apache.geronimo.specs - geronimo-activation_1.1_spec - ${geronimo-activation.version} - org.apache.james apache-mime4j-core From 6ce11b2275ae43980218c90b5192580f8c451403 Mon Sep 17 00:00:00 2001 From: Rene Cordier Date: Thu, 5 Sep 2019 10:03:52 +0700 Subject: [PATCH 018/114] JDKIM-42 Update README to state that the mailet has been moved to James project --- README.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.txt b/README.txt index 921fd38..28b05c9 100644 --- a/README.txt +++ b/README.txt @@ -1,7 +1,9 @@ JAMES jDKIM library ------------------- -Contains library and mailets dealing with crytography. +Contains library dealing with crytography. + +The mailet has been moved to James project: https://github.com/apache/james-project/tree/master/server/mailet/dkim Cryptography Notice ------------------- @@ -28,7 +30,7 @@ Cryptography Notice The following provides more details on the included cryptographic software: - - jDKIM includes code designed to work with Java SE Security + - jDKIM includes code designed to work with Java SE Security Export classifications and source links can be found at http://www.apache.org/licenses/exports/. From a3269a8de8fccaef8aea1ae482d9f47385d177a5 Mon Sep 17 00:00:00 2001 From: Benoit Tellier Date: Mon, 23 Sep 2019 11:13:42 +0700 Subject: [PATCH 019/114] JDKIM-44 Use JDK 8 as a target JDK --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 975fe6c..7167b52 100644 --- a/pom.xml +++ b/pom.xml @@ -69,7 +69,7 @@ 4.12 2.12.1 0.3.11 - 1.6 + 1.8 From 1005b1c4f291d35269c203c67065dd320f1a3f29 Mon Sep 17 00:00:00 2001 From: Eugen Stan Date: Fri, 31 Jul 2020 02:02:49 +0300 Subject: [PATCH 020/114] [JAMES-3226] #comment Added antora docs stub --- docs/antora.yml | 4 ++++ docs/modules/ROOT/pages/index.adoc | 4 ++++ 2 files changed, 8 insertions(+) create mode 100644 docs/antora.yml create mode 100644 docs/modules/ROOT/pages/index.adoc diff --git a/docs/antora.yml b/docs/antora.yml new file mode 100644 index 0000000..4b57624 --- /dev/null +++ b/docs/antora.yml @@ -0,0 +1,4 @@ +name: jdkim +title: Apache James JDKIM +version: '0.3-SNAPSHOT' +prerelease: true diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc new file mode 100644 index 0000000..b73da30 --- /dev/null +++ b/docs/modules/ROOT/pages/index.adoc @@ -0,0 +1,4 @@ += Apache James JDKIM documentation + + +TODO: This is a placeholder. Migrate older documentation. From ac5600770906eb00dcc95b7cb666be07527dc959 Mon Sep 17 00:00:00 2001 From: Benoit Tellier Date: Fri, 28 May 2021 13:32:45 +0700 Subject: [PATCH 021/114] JDKIM-46 Set up Jenkins build system --- Jenkinsfile | 166 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 Jenkinsfile diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..44e19db --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,166 @@ +#!groovy + +/* + * + * 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. + * + */ + +def AGENT_LABEL = env.AGENT_LABEL ?: 'ubuntu' +def JDK_NAME = env.JDK_NAME ?: 'jdk_11_latest' + +pipeline { + + agent { + node { + label AGENT_LABEL + } + } + + environment { + // ... setup any environment variables ... + BUILD_ID = UUID.randomUUID().toString() + MVN_LOCAL_REPO_OPT = '-Dmaven.repo.local=.repository' + MVN_TEST_FAIL_IGNORE = '-Dmaven.test.failure.ignore=true' + MVN_SHOW_TIMESTAMPS="-Dorg.slf4j.simpleLogger.showDateTime=true -Dorg.slf4j.simpleLogger.dateTimeFormat=HH:mm:ss,SSS" + CI = true + LC_CTYPE = 'en_US.UTF-8' + } + + tools { + // ... tell Jenkins what java version, maven version or other tools are required ... + maven 'maven_3_latest' + jdk JDK_NAME + } + + options { + // Configure an overall timeout for the build of 10 minutes. + timeout(time: 10, unit: 'MINUTES') + // When we have test-fails e.g. we don't need to run the remaining steps + skipStagesAfterUnstable() + buildDiscarder( + logRotator(artifactNumToKeepStr: '10', numToKeepStr: '30') + ) + disableConcurrentBuilds() + } + + triggers { + issueCommentTrigger('.*test this please.*') + } + + stages { + stage('Initialization') { + steps { + echo 'Building branch ' + env.BRANCH_NAME + echo 'Using PATH ' + env.PATH + } + } + + stage('Cleanup') { + steps { + echo 'Cleaning up the workspace' + deleteDir() + } + } + + stage('Checkout') { + steps { + echo 'Checking out branch ' + env.BRANCH_NAME + checkout scm + } + } + + stage('Build') { + steps { + echo 'Building' + sh 'mvn -U -B -e clean install -DskipTests ${MVN_SHOW_TIMESTAMPS}' + } + } + + stage('Stable Tests') { + steps { + echo 'Running tests' + sh 'mvn -U -B -e clean install ${MVN_SHOW_TIMESTAMPS}' + } + post { + always { + junit(testResults: '**/surefire-reports/*.xml', allowEmptyResults: false) + junit(testResults: '**/failsafe-reports/*.xml', allowEmptyResults: true) + } + failure { + archiveArtifacts artifacts: '**/target/test-run.log' , fingerprint: true + archiveArtifacts artifacts: '**/surefire-reports/*' , fingerprint: true + } + } + } + + stage('Deploy') { + when { branch 'master' } + steps { + echo 'Deploying' + sh 'mvn -B -e deploy -Pdeploy -DskipTests' + } + } + } +// Do any post build stuff ... such as sending emails depending on the overall build result. + post { + // If this build failed, send an email to the list. + failure { + script { + if (env.BRANCH_NAME == "master") { + emailext( + subject: "[BUILD-FAILURE]: Job '${env.JOB_NAME} [${env.BRANCH_NAME}] [${env.BUILD_NUMBER}]'", + body: """ +BUILD-FAILURE: Job '${env.JOB_NAME} [${env.BRANCH_NAME}] [${env.BUILD_NUMBER}]': +Check console output at "${env.JOB_NAME} [${env.BRANCH_NAME}] [${env.BUILD_NUMBER}]" +""", + to: "server-dev@james.apache.org", + recipientProviders: [[$class: 'DevelopersRecipientProvider']] + ) + }else{ + emailext( + subject: "[BUILD-FAILURE]: Job '${env.JOB_NAME} [${env.BRANCH_NAME}] [${env.BUILD_NUMBER}]'", + body: """ +BUILD-FAILURE: Job '${env.JOB_NAME} [${env.BRANCH_NAME}] [${env.BUILD_NUMBER}]': +Check console output at "${env.JOB_NAME} [${env.BRANCH_NAME}] [${env.BUILD_NUMBER}]" +""", + recipientProviders: [[$class: 'DevelopersRecipientProvider']] + ) + } + } + } + + // Send an email, if the last build was not successful and this one is. + success { + // Cleanup the build directory if the build was successful + // (in this cae we probably don't have to do any post-build analysis) + deleteDir() + script { + if (env.BRANCH_NAME == "master" && (currentBuild.previousBuild != null) && (currentBuild.previousBuild.result != 'SUCCESS')) { + emailext( + subject: "[BUILD-STABLE]: Job '${env.JOB_NAME} [${env.BRANCH_NAME}] [${env.BUILD_NUMBER}]'", + body: """ +BUILD-STABLE: Job '${env.JOB_NAME} [${env.BRANCH_NAME}] [${env.BUILD_NUMBER}]': +Is back to normal. +""", + to: "server-dev@james.apache.org", + recipientProviders: [[$class: 'DevelopersRecipientProvider']] + ) + } + } + } + } +} From a5a91b25f1a02e6cc34a159fdc3dbf56a0c1ef9a Mon Sep 17 00:00:00 2001 From: Benoit Tellier Date: Fri, 30 Jul 2021 08:46:19 +0700 Subject: [PATCH 022/114] [Documentation] Links should enforce HTTPS in menus --- src/site/site.xml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/site/site.xml b/src/site/site.xml index 6a5e41b..d07b870 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -8,7 +8,7 @@ "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 + https://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 @@ -28,7 +28,7 @@ JAMES jDKIM images/james-logo.png - http://james.apache.org/ + https://james.apache.org/ james-logo.png @@ -41,25 +41,25 @@ + href="https://james.apache.org/index.html" /> + href="https://james.apache.org/documentation.html" /> + href="https://james.apache.org/mime4j/index.html" /> + href="https://james.apache.org/jsieve/index.html" /> + href="https://james.apache.org/jspf/index.html" /> + href="https://james.apache.org/jdkim/index.html" /> + href="https://james.apache.org/hupa/index.html" /> @@ -69,11 +69,11 @@ - - - + + + - + From cb715a8f474376c486924ae1d4792d4cac40c01e Mon Sep 17 00:00:00 2001 From: Benoit Tellier Date: Fri, 30 Jul 2021 08:46:37 +0700 Subject: [PATCH 023/114] [Documentation] Retire HUPA --- src/site/site.xml | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/site/site.xml b/src/site/site.xml index d07b870..0caafe5 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -57,9 +57,6 @@ - From 1b04fb8317b6b6166f28d3304d2181ff14360477 Mon Sep 17 00:00:00 2001 From: Benoit Tellier Date: Wed, 15 Sep 2021 08:58:27 +0700 Subject: [PATCH 024/114] Upgrade DNS Java 2.1.9 -> 3.4.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7167b52..dd35719 100644 --- a/pom.xml +++ b/pom.xml @@ -64,7 +64,7 @@ 0.8.3 1.13 1.2 - 2.1.9 + 3.4.1 1.8 4.12 2.12.1 From 087df46c45b612d99e6bdf0ff58fd6fb6089992c Mon Sep 17 00:00:00 2001 From: Benoit Tellier Date: Wed, 15 Sep 2021 08:58:53 +0700 Subject: [PATCH 025/114] Upgrade commons-codec 1.13 -> 1.15 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index dd35719..1eadf20 100644 --- a/pom.xml +++ b/pom.xml @@ -62,7 +62,7 @@ 0.8.3 - 1.13 + 1.15 1.2 3.4.1 1.8 From 3fa5e755c65d0b8fd79c3315fe39c9f173ce8db7 Mon Sep 17 00:00:00 2001 From: Benoit Tellier Date: Wed, 15 Sep 2021 08:59:42 +0700 Subject: [PATCH 026/114] Upgrade junit 4.12 -> 4.13.2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1eadf20..5680f18 100644 --- a/pom.xml +++ b/pom.xml @@ -66,7 +66,7 @@ 1.2 3.4.1 1.8 - 4.12 + 4.13.2 2.12.1 0.3.11 1.8 From 95862c5971eeb3812d2828c5b548b5c1cc6dc016 Mon Sep 17 00:00:00 2001 From: Benoit Tellier Date: Wed, 15 Sep 2021 09:00:10 +0700 Subject: [PATCH 027/114] Upgrade log4j 2.12.1 -> 2.14.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5680f18..f99d574 100644 --- a/pom.xml +++ b/pom.xml @@ -67,7 +67,7 @@ 3.4.1 1.8 4.13.2 - 2.12.1 + 2.14.1 0.3.11 1.8 From 80a69091daa0f72899e7b8fca170bc3646c45938 Mon Sep 17 00:00:00 2001 From: Benoit Tellier Date: Wed, 15 Sep 2021 09:02:13 +0700 Subject: [PATCH 028/114] Upgrade wagon-ssh 3.3.3 -> 3.4.3 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f99d574..b4b3591 100644 --- a/pom.xml +++ b/pom.xml @@ -183,7 +183,7 @@ org.apache.maven.wagon wagon-ssh - 3.3.3 + 3.4.3 From 4f4d6ef057397fc2511ca8ae6a7ed85e665cc430 Mon Sep 17 00:00:00 2001 From: Quan Tran Date: Mon, 20 Dec 2021 09:22:11 +0700 Subject: [PATCH 029/114] Remove unused log4j --- main/pom.xml | 5 ----- pom.xml | 7 ------- 2 files changed, 12 deletions(-) diff --git a/main/pom.xml b/main/pom.xml index 291716d..7c1285f 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -59,11 +59,6 @@ org.apache.james apache-mime4j-dom - - org.apache.logging.log4j - log4j-core - runtime - diff --git a/pom.xml b/pom.xml index b4b3591..5f80083 100644 --- a/pom.xml +++ b/pom.xml @@ -67,7 +67,6 @@ 3.4.1 1.8 4.13.2 - 2.14.1 0.3.11 1.8 @@ -140,12 +139,6 @@ test-jar test - - org.apache.logging.log4j - log4j-core - runtime - ${log4j.version} - From 9204dc55cf33b65923784901d567bcb69381cd67 Mon Sep 17 00:00:00 2001 From: Benoit Tellier Date: Sun, 2 Oct 2022 15:41:35 +0700 Subject: [PATCH 030/114] Relocate SCM --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 5f80083..e25fe92 100644 --- a/pom.xml +++ b/pom.xml @@ -44,9 +44,9 @@ - scm:svn:http://svn.apache.org/repos/asf/james/jdkim/trunk - scm:svn:https://svn.apache.org/repos/asf/james/jdkim/trunk - http://svn.apache.org/viewcvs.cgi/james/jdkim/trunk/?root=Apache-SVN + scm:git:https://git-wip-us.apache.org/repos/asf/james-jdkim.git + scm:git:ssh://git@github.com/apache/james-jdkim.git + http://git-wip-us.apache.org/repos/asf/james-jdkim.git JIRA From 8282f63f9d3267315860728e80c4eb48497d3c9f Mon Sep 17 00:00:00 2001 From: Benoit Tellier Date: Sun, 2 Oct 2022 15:57:10 +0700 Subject: [PATCH 031/114] [maven-release-plugin] prepare release apache-jdkim-project-0.3 --- assemble/pom.xml | 5 ++--- main/pom.xml | 10 +++------- pom.xml | 10 +++++----- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/assemble/pom.xml b/assemble/pom.xml index 8dec225..a1cdc4c 100644 --- a/assemble/pom.xml +++ b/assemble/pom.xml @@ -17,14 +17,13 @@ specific language governing permissions and limitations under the License. --> - + 4.0.0 apache-jdkim-project org.apache.james.jdkim - 0.3-SNAPSHOT + 0.3 ../pom.xml diff --git a/main/pom.xml b/main/pom.xml index 7c1285f..b3d7856 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -17,16 +17,13 @@ specific language governing permissions and limitations under the License. --> - + 4.0.0 apache-jdkim-project org.apache.james.jdkim - 0.3-SNAPSHOT + 0.3 ../pom.xml @@ -130,8 +127,7 @@ - + diff --git a/pom.xml b/pom.xml index e25fe92..a0d8fca 100644 --- a/pom.xml +++ b/pom.xml @@ -17,20 +17,19 @@ specific language governing permissions and limitations under the License. --> - + 4.0.0 org.apache apache 21 - + org.apache.james.jdkim apache-jdkim-project - 0.3-SNAPSHOT + 0.3 pom Apache James :: jDKIM :: DomainKey Project @@ -47,7 +46,8 @@ scm:git:https://git-wip-us.apache.org/repos/asf/james-jdkim.git scm:git:ssh://git@github.com/apache/james-jdkim.git http://git-wip-us.apache.org/repos/asf/james-jdkim.git - + apache-jdkim-project-0.3 + JIRA https://issues.apache.org/jira/browse/JDKIM From 2a9eaf4346e500b71955dc5d84b05b2d31119b06 Mon Sep 17 00:00:00 2001 From: Benoit Tellier Date: Sun, 2 Oct 2022 15:57:28 +0700 Subject: [PATCH 032/114] [maven-release-plugin] prepare for next development iteration --- assemble/pom.xml | 2 +- main/pom.xml | 2 +- pom.xml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/assemble/pom.xml b/assemble/pom.xml index a1cdc4c..8359366 100644 --- a/assemble/pom.xml +++ b/assemble/pom.xml @@ -23,7 +23,7 @@ apache-jdkim-project org.apache.james.jdkim - 0.3 + 0.4-SNAPSHOT ../pom.xml diff --git a/main/pom.xml b/main/pom.xml index b3d7856..c7d8faf 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -23,7 +23,7 @@ apache-jdkim-project org.apache.james.jdkim - 0.3 + 0.4-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index a0d8fca..d61d71c 100644 --- a/pom.xml +++ b/pom.xml @@ -29,7 +29,7 @@ org.apache.james.jdkim apache-jdkim-project - 0.3 + 0.4-SNAPSHOT pom Apache James :: jDKIM :: DomainKey Project @@ -46,7 +46,7 @@ scm:git:https://git-wip-us.apache.org/repos/asf/james-jdkim.git scm:git:ssh://git@github.com/apache/james-jdkim.git http://git-wip-us.apache.org/repos/asf/james-jdkim.git - apache-jdkim-project-0.3 + HEAD JIRA From c3558b00e2f91f8251f12f8fa775b070d740abc7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Feb 2024 07:30:20 +0100 Subject: [PATCH 033/114] Bump org.apache.james:apache-mime4j-core from 0.8.3 to 0.8.10 (#16) Bumps org.apache.james:apache-mime4j-core from 0.8.3 to 0.8.10. --- updated-dependencies: - dependency-name: org.apache.james:apache-mime4j-core dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d61d71c..561d413 100644 --- a/pom.xml +++ b/pom.xml @@ -61,7 +61,7 @@ - 0.8.3 + 0.8.10 1.15 1.2 3.4.1 From 49863010191055f08a13590388acfd791eb28c8b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Jul 2024 13:52:21 +0200 Subject: [PATCH 034/114] Bump dnsjava:dnsjava from 3.4.1 to 3.6.0 (#17) Bumps [dnsjava:dnsjava](https://github.com/dnsjava/dnsjava) from 3.4.1 to 3.6.0. - [Release notes](https://github.com/dnsjava/dnsjava/releases) - [Changelog](https://github.com/dnsjava/dnsjava/blob/master/Changelog) - [Commits](https://github.com/dnsjava/dnsjava/compare/v3.4.1...v3.6.0) --- updated-dependencies: - dependency-name: dnsjava:dnsjava dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 561d413..ec93d83 100644 --- a/pom.xml +++ b/pom.xml @@ -64,7 +64,7 @@ 0.8.10 1.15 1.2 - 3.4.1 + 3.6.0 1.8 4.13.2 0.3.11 From 32f443883576ffd25d676f4f5a0b73cefb3938fe Mon Sep 17 00:00:00 2001 From: Benoit TELLIER Date: Fri, 26 Jul 2024 13:55:50 +0200 Subject: [PATCH 035/114] Upgrade to latest mime4j version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ec93d83..e04cb57 100644 --- a/pom.xml +++ b/pom.xml @@ -61,7 +61,7 @@ - 0.8.10 + 0.8.11 1.15 1.2 3.6.0 From 9526639b73f3af99a6ab45a0fda081a28e5bb7f9 Mon Sep 17 00:00:00 2001 From: Benoit TELLIER Date: Fri, 26 Jul 2024 13:58:06 +0200 Subject: [PATCH 036/114] Update commons-logging --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e04cb57..d5e6970 100644 --- a/pom.xml +++ b/pom.xml @@ -63,7 +63,7 @@ 0.8.11 1.15 - 1.2 + 1.3.3 3.6.0 1.8 4.13.2 From 7bab6250f8ac482dd6e1647706a0a2314e2c10fb Mon Sep 17 00:00:00 2001 From: Benoit TELLIER Date: Fri, 26 Jul 2024 13:58:28 +0200 Subject: [PATCH 037/114] Update commons-codec --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d5e6970..3d5a1ce 100644 --- a/pom.xml +++ b/pom.xml @@ -62,7 +62,7 @@ 0.8.11 - 1.15 + 1.17.1 1.3.3 3.6.0 1.8 From 4ac911db4997d597ef4f0518b709c2ad919c2360 Mon Sep 17 00:00:00 2001 From: Benoit TELLIER Date: Fri, 26 Jul 2024 15:21:37 +0200 Subject: [PATCH 038/114] Remove bundle packaging - release fails --- main/pom.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/main/pom.xml b/main/pom.xml index c7d8faf..4701516 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -28,7 +28,6 @@ apache-jdkim-library - bundle Apache James :: jDKIM A Java implementation for the DKIM specification. From ef0f4bc2c869c5685e57467a326602392f267e1d Mon Sep 17 00:00:00 2001 From: Benoit TELLIER Date: Fri, 26 Jul 2024 15:23:01 +0200 Subject: [PATCH 039/114] [maven-release-plugin] prepare release apache-jdkim-project-0.4 --- assemble/pom.xml | 2 +- main/pom.xml | 2 +- pom.xml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/assemble/pom.xml b/assemble/pom.xml index 8359366..2a52776 100644 --- a/assemble/pom.xml +++ b/assemble/pom.xml @@ -23,7 +23,7 @@ apache-jdkim-project org.apache.james.jdkim - 0.4-SNAPSHOT + 0.4 ../pom.xml diff --git a/main/pom.xml b/main/pom.xml index 4701516..3f9af00 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -23,7 +23,7 @@ apache-jdkim-project org.apache.james.jdkim - 0.4-SNAPSHOT + 0.4 ../pom.xml diff --git a/pom.xml b/pom.xml index 3d5a1ce..ebfb11c 100644 --- a/pom.xml +++ b/pom.xml @@ -29,7 +29,7 @@ org.apache.james.jdkim apache-jdkim-project - 0.4-SNAPSHOT + 0.4 pom Apache James :: jDKIM :: DomainKey Project @@ -46,7 +46,7 @@ scm:git:https://git-wip-us.apache.org/repos/asf/james-jdkim.git scm:git:ssh://git@github.com/apache/james-jdkim.git http://git-wip-us.apache.org/repos/asf/james-jdkim.git - HEAD + apache-jdkim-project-0.4 JIRA From 0a92278d24a30664a212ae481024587d338d6742 Mon Sep 17 00:00:00 2001 From: Benoit TELLIER Date: Fri, 26 Jul 2024 15:23:16 +0200 Subject: [PATCH 040/114] [maven-release-plugin] prepare for next development iteration --- assemble/pom.xml | 2 +- main/pom.xml | 2 +- pom.xml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/assemble/pom.xml b/assemble/pom.xml index 2a52776..affd898 100644 --- a/assemble/pom.xml +++ b/assemble/pom.xml @@ -23,7 +23,7 @@ apache-jdkim-project org.apache.james.jdkim - 0.4 + 0.5-SNAPSHOT ../pom.xml diff --git a/main/pom.xml b/main/pom.xml index 3f9af00..8dce3ef 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -23,7 +23,7 @@ apache-jdkim-project org.apache.james.jdkim - 0.4 + 0.5-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index ebfb11c..797f968 100644 --- a/pom.xml +++ b/pom.xml @@ -29,7 +29,7 @@ org.apache.james.jdkim apache-jdkim-project - 0.4 + 0.5-SNAPSHOT pom Apache James :: jDKIM :: DomainKey Project @@ -46,7 +46,7 @@ scm:git:https://git-wip-us.apache.org/repos/asf/james-jdkim.git scm:git:ssh://git@github.com/apache/james-jdkim.git http://git-wip-us.apache.org/repos/asf/james-jdkim.git - apache-jdkim-project-0.4 + HEAD JIRA From 33c3f230daa7f12f35f40db7344dfcf8210d4d0e Mon Sep 17 00:00:00 2001 From: Rene Cordier Date: Tue, 4 Mar 2025 16:57:16 +0700 Subject: [PATCH 041/114] Revert "[maven-release-plugin] prepare for next development iteration" This reverts commit 4b0b365b85847a83c3e09f5538fb9c6279a6c891. --- assemble/pom.xml | 2 +- main/pom.xml | 2 +- pom.xml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/assemble/pom.xml b/assemble/pom.xml index affd898..2a52776 100644 --- a/assemble/pom.xml +++ b/assemble/pom.xml @@ -23,7 +23,7 @@ apache-jdkim-project org.apache.james.jdkim - 0.5-SNAPSHOT + 0.4 ../pom.xml diff --git a/main/pom.xml b/main/pom.xml index 8dce3ef..3f9af00 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -23,7 +23,7 @@ apache-jdkim-project org.apache.james.jdkim - 0.5-SNAPSHOT + 0.4 ../pom.xml diff --git a/pom.xml b/pom.xml index 797f968..ebfb11c 100644 --- a/pom.xml +++ b/pom.xml @@ -29,7 +29,7 @@ org.apache.james.jdkim apache-jdkim-project - 0.5-SNAPSHOT + 0.4 pom Apache James :: jDKIM :: DomainKey Project @@ -46,7 +46,7 @@ scm:git:https://git-wip-us.apache.org/repos/asf/james-jdkim.git scm:git:ssh://git@github.com/apache/james-jdkim.git http://git-wip-us.apache.org/repos/asf/james-jdkim.git - HEAD + apache-jdkim-project-0.4 JIRA From dab5d0f32a36aa86471e57d6f17c738ff7dd77de Mon Sep 17 00:00:00 2001 From: Rene Cordier Date: Tue, 4 Mar 2025 16:57:29 +0700 Subject: [PATCH 042/114] Revert "[maven-release-plugin] prepare release apache-jdkim-project-0.4" This reverts commit 8a6dab323e58634d63a6620c0212577ea5aa8fdc. --- assemble/pom.xml | 2 +- main/pom.xml | 2 +- pom.xml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/assemble/pom.xml b/assemble/pom.xml index 2a52776..8359366 100644 --- a/assemble/pom.xml +++ b/assemble/pom.xml @@ -23,7 +23,7 @@ apache-jdkim-project org.apache.james.jdkim - 0.4 + 0.4-SNAPSHOT ../pom.xml diff --git a/main/pom.xml b/main/pom.xml index 3f9af00..4701516 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -23,7 +23,7 @@ apache-jdkim-project org.apache.james.jdkim - 0.4 + 0.4-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index ebfb11c..3d5a1ce 100644 --- a/pom.xml +++ b/pom.xml @@ -29,7 +29,7 @@ org.apache.james.jdkim apache-jdkim-project - 0.4 + 0.4-SNAPSHOT pom Apache James :: jDKIM :: DomainKey Project @@ -46,7 +46,7 @@ scm:git:https://git-wip-us.apache.org/repos/asf/james-jdkim.git scm:git:ssh://git@github.com/apache/james-jdkim.git http://git-wip-us.apache.org/repos/asf/james-jdkim.git - apache-jdkim-project-0.4 + HEAD JIRA From c372f280e5bc8d971642f3c5d3b835e6bb7127df Mon Sep 17 00:00:00 2001 From: Rene Cordier Date: Tue, 4 Mar 2025 17:01:57 +0700 Subject: [PATCH 043/114] [maven-release-plugin] prepare release apache-jdkim-project-0.4 --- assemble/pom.xml | 2 +- main/pom.xml | 2 +- pom.xml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/assemble/pom.xml b/assemble/pom.xml index 8359366..2a52776 100644 --- a/assemble/pom.xml +++ b/assemble/pom.xml @@ -23,7 +23,7 @@ apache-jdkim-project org.apache.james.jdkim - 0.4-SNAPSHOT + 0.4 ../pom.xml diff --git a/main/pom.xml b/main/pom.xml index 4701516..3f9af00 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -23,7 +23,7 @@ apache-jdkim-project org.apache.james.jdkim - 0.4-SNAPSHOT + 0.4 ../pom.xml diff --git a/pom.xml b/pom.xml index 3d5a1ce..ebfb11c 100644 --- a/pom.xml +++ b/pom.xml @@ -29,7 +29,7 @@ org.apache.james.jdkim apache-jdkim-project - 0.4-SNAPSHOT + 0.4 pom Apache James :: jDKIM :: DomainKey Project @@ -46,7 +46,7 @@ scm:git:https://git-wip-us.apache.org/repos/asf/james-jdkim.git scm:git:ssh://git@github.com/apache/james-jdkim.git http://git-wip-us.apache.org/repos/asf/james-jdkim.git - HEAD + apache-jdkim-project-0.4 JIRA From 0a8a798c2687e61750efb4b71518b0bf484beb26 Mon Sep 17 00:00:00 2001 From: Rene Cordier Date: Tue, 4 Mar 2025 17:02:15 +0700 Subject: [PATCH 044/114] [maven-release-plugin] prepare for next development iteration --- assemble/pom.xml | 2 +- main/pom.xml | 2 +- pom.xml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/assemble/pom.xml b/assemble/pom.xml index 2a52776..affd898 100644 --- a/assemble/pom.xml +++ b/assemble/pom.xml @@ -23,7 +23,7 @@ apache-jdkim-project org.apache.james.jdkim - 0.4 + 0.5-SNAPSHOT ../pom.xml diff --git a/main/pom.xml b/main/pom.xml index 3f9af00..8dce3ef 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -23,7 +23,7 @@ apache-jdkim-project org.apache.james.jdkim - 0.4 + 0.5-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index ebfb11c..797f968 100644 --- a/pom.xml +++ b/pom.xml @@ -29,7 +29,7 @@ org.apache.james.jdkim apache-jdkim-project - 0.4 + 0.5-SNAPSHOT pom Apache James :: jDKIM :: DomainKey Project @@ -46,7 +46,7 @@ scm:git:https://git-wip-us.apache.org/repos/asf/james-jdkim.git scm:git:ssh://git@github.com/apache/james-jdkim.git http://git-wip-us.apache.org/repos/asf/james-jdkim.git - apache-jdkim-project-0.4 + HEAD JIRA From edeceed76156e619c29b4887a975b53835791912 Mon Sep 17 00:00:00 2001 From: Rene Cordier Date: Fri, 14 Mar 2025 16:39:34 +0700 Subject: [PATCH 045/114] [FIX] Shade the all commons-codec lib into jdkim (#19) Some issues were detected when running jdkim lib with other projects. After commons-codec upgrade, seems BinaryCodec is being used now for Base64 encoding, which was excluded from shading, as it was not the case before. Shading the all commons-codec lib allows to avoid similar issues with future upgrades while still not forcing other projects to align their commons-codec version with jdkim. --- main/pom.xml | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/main/pom.xml b/main/pom.xml index 8dce3ef..402674e 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -105,20 +105,6 @@ commons-codec:commons-codec - - - commons-codec:commons-codec - - org/apache/commons/codec/String* - org/apache/commons/codec/language/* - org/apache/commons/codec/net/* - org/apache/commons/codec/digest/* - org/apache/commons/codec/binary/He* - org/apache/commons/codec/binary/Binary* - org/apache/commons/codec/binary/*Stream* - - - org.apache.commons.codec From b6d70e875fc66c7f8c090c027147db1cf29c0714 Mon Sep 17 00:00:00 2001 From: Emerson Pinter Date: Fri, 21 Mar 2025 12:19:22 -0300 Subject: [PATCH 046/114] Add dependency-reduced-pom to gitignore (#22) --- .gitignore | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index f83e8cf..205919f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ .idea -target *.iml +/main/dependency-reduced-pom.xml +/assemble/target +/target +/main/target + From 304984b91e2abbb990c353640eacf9861fec103f Mon Sep 17 00:00:00 2001 From: Emerson Pinter Date: Thu, 20 Mar 2025 13:47:57 -0300 Subject: [PATCH 047/114] Store SignatureRecord in FailException With this change is possible to use other fields like selector to generate a results header. --- .../org/apache/james/jdkim/DKIMVerifier.java | 18 +++++++++--------- .../james/jdkim/exceptions/FailException.java | 17 +++++++++++++---- .../jdkim/exceptions/PermFailException.java | 11 +++++++++-- 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java b/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java index 546c01c..2648a5e 100644 --- a/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java +++ b/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java @@ -119,27 +119,27 @@ public static void apply(PublicKeyRecord pkr, SignatureRecord sign) throws PermF .matches()) { throw new PermFailException("inapplicable key identity local=" + sign.getIdentityLocalPart() + " Pattern: " - + pkr.getGranularityPattern().pattern(), sign.getIdentity().toString()); + + pkr.getGranularityPattern().pattern(), sign); } if (!pkr.isHashMethodSupported(sign.getHashMethod())) { throw new PermFailException("inappropriate hash for a=" - + sign.getHashKeyType() + "/" + sign.getHashMethod(), sign.getIdentity().toString()); + + sign.getHashKeyType() + "/" + sign.getHashMethod(), sign); } if (!pkr.isKeyTypeSupported(sign.getHashKeyType())) { throw new PermFailException("inappropriate key type for a=" - + sign.getHashKeyType() + "/" + sign.getHashMethod(), sign.getIdentity().toString()); + + sign.getHashKeyType() + "/" + sign.getHashMethod(), sign); } if (pkr.isDenySubdomains()) { if (!sign.getIdentity().toString().toLowerCase().endsWith( ("@" + sign.getDToken()).toLowerCase())) { throw new PermFailException( - "AUID in subdomain of SDID is not allowed by the public key record.", sign.getIdentity().toString()); + "AUID in subdomain of SDID is not allowed by the public key record.", sign); } } } catch (IllegalStateException e) { - throw new PermFailException("Invalid public key: " + e.getMessage(), sign.getIdentity().toString()); + throw new PermFailException("Invalid public key: " + e.getMessage(), sign); } } @@ -179,16 +179,16 @@ public PublicKeyRecord publicRecordLookup(SignatureRecord sign) } if (key == null) { if (lastTempFailure != null) { - if (sign != null) lastTempFailure.setRelatedRecordIdentity(sign.getIdentity().toString()); + if (sign != null) lastTempFailure.setRelatedRecord(sign); throw lastTempFailure; } else if (lastPermFailure != null) { - if (sign != null) lastPermFailure.setRelatedRecordIdentity(sign.getIdentity().toString()); + if (sign != null) lastPermFailure.setRelatedRecord(sign); throw lastPermFailure; } // this is unexpected because the publicKeySelector always returns // null or exception else { throw new PermFailException( - "no key for signature [unexpected condition]", sign.getIdentity().toString()); + "no key for signature [unexpected condition]", sign); } } return key; @@ -482,7 +482,7 @@ private void signatureVerify(Headers h, SignatureRecord sign, signatureCheck(h, sign, headers, signature); if (!signature.verify(decoded)) - throw new PermFailException("Header signature does not verify"); + throw new PermFailException("Header signature does not verify", sign); } catch (InvalidKeyException e) { throw new PermFailException(e.getMessage(), e); } catch (NoSuchAlgorithmException e) { diff --git a/main/src/main/java/org/apache/james/jdkim/exceptions/FailException.java b/main/src/main/java/org/apache/james/jdkim/exceptions/FailException.java index 17cc7d0..cf33fe0 100644 --- a/main/src/main/java/org/apache/james/jdkim/exceptions/FailException.java +++ b/main/src/main/java/org/apache/james/jdkim/exceptions/FailException.java @@ -19,11 +19,13 @@ package org.apache.james.jdkim.exceptions; +import org.apache.james.jdkim.api.SignatureRecord; + public class FailException extends Exception { private static final long serialVersionUID = 1584103235607992818L; - private String relatedRecordIdentity = null; + private SignatureRecord relatedRecord = null; public FailException(String error) { super(error); @@ -34,10 +36,17 @@ public FailException(String string, Exception e) { } public String getRelatedRecordIdentity() { - return relatedRecordIdentity; + if(relatedRecord != null) { + return relatedRecord.getIdentity().toString(); + } + return null; + } + + public SignatureRecord getRelatedRecord() { + return relatedRecord; } - public void setRelatedRecordIdentity(String relatedRecordIdentity) { - this.relatedRecordIdentity = relatedRecordIdentity; + public void setRelatedRecord(SignatureRecord relatedRecord) { + this.relatedRecord = relatedRecord; } } diff --git a/main/src/main/java/org/apache/james/jdkim/exceptions/PermFailException.java b/main/src/main/java/org/apache/james/jdkim/exceptions/PermFailException.java index 2673e86..2fe72ff 100644 --- a/main/src/main/java/org/apache/james/jdkim/exceptions/PermFailException.java +++ b/main/src/main/java/org/apache/james/jdkim/exceptions/PermFailException.java @@ -20,6 +20,8 @@ package org.apache.james.jdkim.exceptions; +import org.apache.james.jdkim.api.SignatureRecord; + public class PermFailException extends FailException { private static final long serialVersionUID = 1304736020453821093L; @@ -32,9 +34,14 @@ public PermFailException(String string, Exception e) { super(string, e); } - public PermFailException(String string, String signatureIdentity) { + public PermFailException(String string, SignatureRecord signatureRecord, Exception e) { + super(string, e); + setRelatedRecord(signatureRecord); + } + + public PermFailException(String string, SignatureRecord signatureRecord) { super(string); - setRelatedRecordIdentity(signatureIdentity); + setRelatedRecord(signatureRecord); } } From 7a07d58bdc378d8364c5ad5011f9925a41cc1963 Mon Sep 17 00:00:00 2001 From: Emerson Pinter Date: Thu, 20 Mar 2025 17:28:54 -0300 Subject: [PATCH 048/114] Pass SignatureRecord to PermFailException --- .../org/apache/james/jdkim/DKIMSigner.java | 6 ++--- .../org/apache/james/jdkim/DKIMVerifier.java | 26 +++++++++---------- .../james/jdkim/impl/BodyHasherImpl.java | 6 ++--- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/main/src/main/java/org/apache/james/jdkim/DKIMSigner.java b/main/src/main/java/org/apache/james/jdkim/DKIMSigner.java index bee8fdf..1a91c1e 100644 --- a/main/src/main/java/org/apache/james/jdkim/DKIMSigner.java +++ b/main/src/main/java/org/apache/james/jdkim/DKIMSigner.java @@ -121,12 +121,12 @@ public String sign(Headers message, BodyHasher bh) throws PermFailException { return "DKIM-Signature:" + bhj.getSignatureRecord().toString(); } catch (InvalidKeyException e) { - throw new PermFailException("Invalid key: " + e.getMessage(), e); + throw new PermFailException("Invalid key: " + e.getMessage(), bhj.getSignatureRecord(), e); } catch (NoSuchAlgorithmException e) { - throw new PermFailException("Unknown algorythm: " + e.getMessage(), + throw new PermFailException("Unknown algorythm: " + e.getMessage(), bhj.getSignatureRecord(), e); } catch (SignatureException e) { - throw new PermFailException("Signing exception: " + e.getMessage(), + throw new PermFailException("Signing exception: " + e.getMessage(), bhj.getSignatureRecord(), e); } } diff --git a/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java b/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java index 2648a5e..2b1966d 100644 --- a/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java +++ b/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java @@ -246,13 +246,13 @@ public BodyHasher newBodyHasher(Headers messageHeaders) throws FailException { if (pos > 0) { String v = signatureField.substring(pos + 1, signatureField .length()); - SignatureRecord signatureRecord; + SignatureRecord signatureRecord = null; try { signatureRecord = newSignatureRecord(v); // validate signatureRecord.validate(); } catch (IllegalStateException e) { - throw new PermFailException("Invalid signature record: " + e.getMessage(), e); + throw new PermFailException("Invalid signature record: " + e.getMessage(), signatureRecord, e); } // Specification say we MAY refuse to verify the signature. @@ -261,22 +261,22 @@ public BodyHasher newBodyHasher(Headers messageHeaders) throws FailException { long elapsed = (System.currentTimeMillis() / 1000 - signedTime); if (elapsed < -3600 * 24 * 365 * 3) { throw new PermFailException("Signature date is more than " - + -elapsed / (3600 * 24 * 365) + " years in the future."); + + -elapsed / (3600 * 24 * 365) + " years in the future.", signatureRecord); } else if (elapsed < -3600 * 24 * 30 * 3) { throw new PermFailException("Signature date is more than " - + -elapsed / (3600 * 24 * 30) + " months in the future."); + + -elapsed / (3600 * 24 * 30) + " months in the future.", signatureRecord); } else if (elapsed < -3600 * 24 * 3) { throw new PermFailException("Signature date is more than " - + -elapsed / (3600 * 24) + " days in the future."); + + -elapsed / (3600 * 24) + " days in the future.", signatureRecord); } else if (elapsed < -3600 * 3) { throw new PermFailException("Signature date is more than " - + -elapsed / 3600 + " hours in the future."); + + -elapsed / 3600 + " hours in the future.", signatureRecord); } else if (elapsed < -60 * 3) { throw new PermFailException("Signature date is more than " - + -elapsed / 60 + " minutes in the future."); + + -elapsed / 60 + " minutes in the future.", signatureRecord); } else if (elapsed < 0) { throw new PermFailException("Signature date is " - + elapsed + " seconds in the future."); + + elapsed + " seconds in the future.", signatureRecord); } } @@ -403,7 +403,7 @@ private List verify(CompoundBodyHasher compoundBodyHasher) .put( "DKIM-Signature:" + bhj.getSignatureRecord().toString(), new PermFailException( - "Computed bodyhash is different from the expected one")); + "Computed bodyhash is different from the expected one", bhj.getSignatureRecord())); } else { verifiedSignatures.add(bhj.getSignatureRecord()); } @@ -475,7 +475,7 @@ private void signatureVerify(Headers h, SignatureRecord sign, try { publicKey = key.getPublicKey(); } catch (IllegalStateException e) { - throw new PermFailException("Invalid Public Key: " + e.getMessage(), e); + throw new PermFailException("Invalid Public Key: " + e.getMessage(), sign, e); } signature.initVerify(publicKey); @@ -484,11 +484,11 @@ private void signatureVerify(Headers h, SignatureRecord sign, if (!signature.verify(decoded)) throw new PermFailException("Header signature does not verify", sign); } catch (InvalidKeyException e) { - throw new PermFailException(e.getMessage(), e); + throw new PermFailException(e.getMessage(), sign, e); } catch (NoSuchAlgorithmException e) { - throw new PermFailException(e.getMessage(), e); + throw new PermFailException(e.getMessage(), sign, e); } catch (SignatureException e) { - throw new PermFailException(e.getMessage(), e); + throw new PermFailException(e.getMessage(), sign, e); } } diff --git a/main/src/main/java/org/apache/james/jdkim/impl/BodyHasherImpl.java b/main/src/main/java/org/apache/james/jdkim/impl/BodyHasherImpl.java index ef8577d..e180991 100644 --- a/main/src/main/java/org/apache/james/jdkim/impl/BodyHasherImpl.java +++ b/main/src/main/java/org/apache/james/jdkim/impl/BodyHasherImpl.java @@ -45,13 +45,13 @@ public BodyHasherImpl(SignatureRecord sign) throws PermFailException { md = MessageDigest.getInstance(sign.getHashAlgo().toString()); } catch (NoSuchAlgorithmException e) { throw new PermFailException("Unsupported algorythm: " - + sign.getHashAlgo(), e); + + sign.getHashAlgo(), sign, e); } try { sign.validate(); } catch (IllegalStateException e) { - throw new PermFailException("Invalid signature template", e); + throw new PermFailException("Invalid signature template", sign, e); } int limit = sign.getBodyHashLimit(); @@ -65,7 +65,7 @@ public BodyHasherImpl(SignatureRecord sign) throws PermFailException { .getBodyCanonicalisationMethod())) { throw new PermFailException( "Unsupported body canonicalization method: " - + sign.getBodyCanonicalisationMethod()); + + sign.getBodyCanonicalisationMethod(), sign); } DigestOutputStream dout = new DigestOutputStream(md); From 92438bed7d0dfd9fe90b61570c787cdfb720e249 Mon Sep 17 00:00:00 2001 From: Emerson Pinter Date: Thu, 20 Mar 2025 23:02:40 -0300 Subject: [PATCH 049/114] Remove unused commons-logging --- pom.xml | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/pom.xml b/pom.xml index 797f968..c9385a9 100644 --- a/pom.xml +++ b/pom.xml @@ -63,7 +63,6 @@ 0.8.11 1.17.1 - 1.3.3 3.6.0 1.8 4.13.2 @@ -83,29 +82,6 @@ commons-codec ${commons-codec.version} - - commons-logging - commons-logging - ${commons-logging.version} - - - avalon-framework - avalon-framework - - - log4j - log4j - - - logkit - logkit - - - javax.servlet - servlet-api - - - dnsjava dnsjava From 5d6bac948aa2b23d105e07b36ff07ff996c2f689 Mon Sep 17 00:00:00 2001 From: Emerson Pinter Date: Fri, 21 Mar 2025 10:39:05 -0300 Subject: [PATCH 050/114] Remove unused not-yet-commons-ssl --- assemble/pom.xml | 4 ---- pom.xml | 6 ------ 2 files changed, 10 deletions(-) diff --git a/assemble/pom.xml b/assemble/pom.xml index affd898..2ae288e 100644 --- a/assemble/pom.xml +++ b/assemble/pom.xml @@ -34,10 +34,6 @@ A Java implementation for the DKIM specification. - - ca.juliusdavies - not-yet-commons-ssl - dnsjava dnsjava diff --git a/pom.xml b/pom.xml index c9385a9..cc9a9f1 100644 --- a/pom.xml +++ b/pom.xml @@ -66,17 +66,11 @@ 3.6.0 1.8 4.13.2 - 0.3.11 1.8 - - ca.juliusdavies - not-yet-commons-ssl - ${not-yet-commons-ssl.version} - commons-codec commons-codec From 1efd3c3d08c56961f74c575021568368e0c1e2b7 Mon Sep 17 00:00:00 2001 From: Emerson Pinter Date: Fri, 21 Mar 2025 10:43:00 -0300 Subject: [PATCH 051/114] Update mime4j to 0.8.12 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index cc9a9f1..4fa9b89 100644 --- a/pom.xml +++ b/pom.xml @@ -61,7 +61,7 @@ - 0.8.11 + 0.8.12 1.17.1 3.6.0 1.8 From ba0ab5c34cd64b310befb6375779797de138b45e Mon Sep 17 00:00:00 2001 From: Emerson Pinter Date: Fri, 21 Mar 2025 10:45:52 -0300 Subject: [PATCH 052/114] Update commons-codec to 1.18.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4fa9b89..07f6284 100644 --- a/pom.xml +++ b/pom.xml @@ -62,7 +62,7 @@ 0.8.12 - 1.17.1 + 1.18.0 3.6.0 1.8 4.13.2 From 7c85b64c70a7a55efcca68783d5b837caf7396e5 Mon Sep 17 00:00:00 2001 From: Emerson Pinter Date: Fri, 21 Mar 2025 10:49:08 -0300 Subject: [PATCH 053/114] Update dnsjava to 3.6.3 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 07f6284..b9696bf 100644 --- a/pom.xml +++ b/pom.xml @@ -63,7 +63,7 @@ 0.8.12 1.18.0 - 3.6.0 + 3.6.3 1.8 4.13.2 1.8 From f5b87efe25b4883e62a4df3205ffb5919bcd5b04 Mon Sep 17 00:00:00 2001 From: Emerson Pinter Date: Tue, 25 Mar 2025 04:42:07 -0300 Subject: [PATCH 054/114] Add a list of results to verifier (#23) With this, the user can get the results of verification also when it fails partially. This commit also adds a method to return a header text representing the result. --- .../org/apache/james/jdkim/DKIMVerifier.java | 98 +++++---- .../org/apache/james/jdkim/api/Result.java | 187 ++++++++++++++++++ .../james/jdkim/api/SignatureRecord.java | 2 + .../exceptions/CompositeFailException.java | 18 ++ .../jdkim/tagvalue/SignatureRecordImpl.java | 4 + .../org/apache/james/jdkim/FileBasedTest.java | 4 +- .../org/apache/james/jdkim/PerlDKIMTest.java | 22 ++- 7 files changed, 299 insertions(+), 36 deletions(-) create mode 100644 main/src/main/java/org/apache/james/jdkim/api/Result.java create mode 100644 main/src/main/java/org/apache/james/jdkim/exceptions/CompositeFailException.java diff --git a/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java b/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java index 2b1966d..73478c5 100644 --- a/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java +++ b/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java @@ -23,7 +23,9 @@ import org.apache.james.jdkim.api.Headers; import org.apache.james.jdkim.api.PublicKeyRecord; import org.apache.james.jdkim.api.PublicKeyRecordRetriever; +import org.apache.james.jdkim.api.Result; import org.apache.james.jdkim.api.SignatureRecord; +import org.apache.james.jdkim.exceptions.CompositeFailException; import org.apache.james.jdkim.exceptions.FailException; import org.apache.james.jdkim.exceptions.PermFailException; import org.apache.james.jdkim.exceptions.TempFailException; @@ -42,6 +44,7 @@ import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Hashtable; @@ -53,6 +56,7 @@ public class DKIMVerifier extends DKIMCommon { private final PublicKeyRecordRetriever publicKeyRecordRetriever; + private final List result = new ArrayList<>(); public DKIMVerifier() { this.publicKeyRecordRetriever = new MultiplexingPublicKeyRecordRetriever( @@ -209,9 +213,7 @@ public List verify(InputStream is) throws IOException, try { try { message = new Message(is); - } catch (RuntimeException e) { - throw e; - } catch (IOException e) { + } catch (RuntimeException | IOException e) { throw e; } catch (Exception e1) { // This can only be a MimeException but we don't declare to allow usage of @@ -244,8 +246,7 @@ public BodyHasher newBodyHasher(Headers messageHeaders) throws FailException { try { int pos = signatureField.indexOf(':'); if (pos > 0) { - String v = signatureField.substring(pos + 1, signatureField - .length()); + String v = signatureField.substring(pos + 1); SignatureRecord signatureRecord = null; try { signatureRecord = newSignatureRecord(v); @@ -303,9 +304,7 @@ public BodyHasher newBodyHasher(Headers messageHeaders) throws FailException { throw new PermFailException( "unexpected bad signature field"); } - } catch (TempFailException e) { - signatureExceptions.put(signatureField, e); - } catch (PermFailException e) { + } catch (TempFailException | PermFailException e) { signatureExceptions.put(signatureField, e); } catch (RuntimeException e) { signatureExceptions.put(signatureField, new PermFailException( @@ -313,12 +312,8 @@ public BodyHasher newBodyHasher(Headers messageHeaders) throws FailException { } } - if (bodyHashJobs.isEmpty()) { - if (signatureExceptions.size() > 0) { - throw prepareException(signatureExceptions); - } else { - throw new PermFailException("Unexpected condition with " + fields); - } + if (bodyHashJobs.isEmpty() && signatureExceptions.isEmpty()) { + throw new PermFailException("Unexpected condition with " + fields); } return new CompoundBodyHasher(bodyHashJobs, signatureExceptions); @@ -409,28 +404,66 @@ private List verify(CompoundBodyHasher compoundBodyHasher) } } + for(SignatureRecord s: verifiedSignatures) { + result.add(new Result(s)); + } + result.addAll(resultsFromExceptions(compoundBodyHasher.getSignatureExceptions())); + if (verifiedSignatures.isEmpty()) { throw prepareException(compoundBodyHasher.getSignatureExceptions()); } else { - // There is no access to the signatureExceptions when - // there is at least one valid signature (JDKIM-14) - /* - for (Iterator i = signatureExceptions.keySet().iterator(); i - .hasNext();) { - String f = (String) i.next(); - System.out.println("DKIM-Error:" - + ((FailException) signatureExceptions.get(f)) - .getMessage() + " FIELD: " + f); + return verifiedSignatures; + } + } + + /** + * Return the results of all signature checks, success and fail. + * + * @return List of {@link Result} object. + */ + public List getResults() { + return result; + } + + /** + * Returns true when all signature verification are successful. A message without dkim-signature is considered a success. + * + * @return true when success + */ + public boolean isSuccess() { + return result.stream().allMatch(Result::isSuccess); + } + + /** + * Clears results list for the DKIMVerifier instance + */ + public void resetResults() { + result.clear(); + } + + private List resultsFromExceptions(Map exceptions) { + List results = new ArrayList<>(); + for (Map.Entry e : exceptions.entrySet()) { + SignatureRecord rec = e.getValue().getRelatedRecord(); + if (rec == null) { + rec = new SignatureRecordImpl("v=1; d=invalid; h=from; s=invalid; b=invalidsig"); } - */ - /* - for (Iterator i = verifiedSignatures.iterator(); i.hasNext();) { - SignatureRecord sr = (SignatureRecord) i.next(); - System.out.println("DKIM-Pass:" + sr); + + Result.Type resultType = Result.Type.NONE; + if (e.getValue() instanceof TempFailException) { + resultType = Result.Type.TEMPERROR; + } else if (e.getValue() instanceof PermFailException) { + if (e.getValue().getRelatedRecord() == null) { + //FailException without the SignatureRecord + resultType = Result.Type.PERMERROR; + } else { + resultType = Result.Type.FAIL; + } } - */ - return verifiedSignatures; + results.add(new Result(e.getValue().getMessage(), e.getKey() != null ? e.getKey() : "", rec, resultType)); } + + return results; } /** @@ -446,10 +479,7 @@ private FailException prepareException(Map signatureExcep return signatureExceptions.values().iterator() .next(); } else { - // TODO loops signatureExceptions to give a more complete - // response, using nested exception or a compound exception. - // System.out.println(signatureExceptions); - return new PermFailException("found " + signatureExceptions.size() + return new CompositeFailException(signatureExceptions.values(), "found " + signatureExceptions.size() + " invalid signatures"); } } diff --git a/main/src/main/java/org/apache/james/jdkim/api/Result.java b/main/src/main/java/org/apache/james/jdkim/api/Result.java new file mode 100644 index 0000000..065fe81 --- /dev/null +++ b/main/src/main/java/org/apache/james/jdkim/api/Result.java @@ -0,0 +1,187 @@ +/**************************************************************** + * 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.api; + +/** + * Class to hold results of DKIMVerifier + */ +public class Result { + private final String errorMessage; + private final String dkimRawField; + private final SignatureRecord record; + private final Type type; + + /** + * Result type + * + * @see RFC8601 2.7.1 + */ + public enum Type { + NONE, + PASS, + FAIL, + POLICY, + NEUTRAL, + TEMPERROR, + PERMERROR + } + + /** + * Constructor to create a Result instance with error message + * + * @param errorMessage Error message, from exception + * @param dkimRawField The DKIM-Signature field + * @param record SignatureRecord + * @param type Result type + */ + public Result(String errorMessage, String dkimRawField, SignatureRecord record, Type type) { + this.errorMessage = errorMessage; + this.dkimRawField = dkimRawField; + this.record = record; + this.type = type; + } + + /** + * Constructor to create a Result instance of a successful verification + * + * @param record SignatureRecord + */ + public Result(SignatureRecord record) { + this.errorMessage = null; + this.dkimRawField = null; + this.record = record; + this.type = Type.PASS; + } + + /** + * Returns a string representing the result, with a reason field + */ + public String getHeaderTextWithReason() { + return getHeaderText(true); + } + + /** + * Returns a string representing the result + */ + public String getHeaderText() { + return getHeaderText(false); + } + + /** + * Returns the header text for usage with authentication results header, like defined in RFC7601 + * + * @param withReason If true, add reason field with error/success message + * @return String + */ + private String getHeaderText(boolean withReason) { + if (record == null) { + return ""; + } + + String partialSig = ""; + String reasonProp = ""; + if (record.getRawSignature() != null) { + if (record.getRawSignature().length() >= 12) { + partialSig = " header.b=" + record.getRawSignature().subSequence(0, 12); + } else { + partialSig = " header.b=" + record.getRawSignature(); + } + } + + if (withReason) { + String reasonMsg; + switch (type) { + case PASS: + reasonMsg = "valid signature"; + break; + case NONE: + reasonMsg = "not signed"; + break; + default: + reasonMsg = errorMessage != null ? errorMessage : ""; + break; + } + reasonProp = reasonMsg.isEmpty() ? "" : String.format(" reason=\"%s\"", reasonMsg); + } + + return String.format("dkim=%s header.d=%s header.s=%s%s%s", + type.toString().toLowerCase(), record.getDToken(), record.getSelector(), partialSig, reasonProp); + } + + /** + * Get ErrorMessage + * + * @return The error message produced when the exception was thrown + */ + public String getErrorMessage() { + return errorMessage; + } + + /** + * Get dkim field + * + * @return The DKIM-Signature field that was verified + */ + public String getDkimRawField() { + return dkimRawField; + } + + /** + * @return Returns true if success + */ + public boolean isSuccess() { + return type == Type.PASS || type == Type.NONE || type == Type.NEUTRAL; + } + + /** + * @return Returns true if fail + */ + public boolean isFail() { + return !isSuccess(); + } + + /** + * The resulting SignatureRecord + * + * @return SignatureRecord + */ + public SignatureRecord getRecord() { + return record; + } + + /** + * Result Type + * + * @return The result type + */ + public Type getResultType() { + return type; + } + + @Override + public String toString() { + return "Result{" + + "headerText='" + getHeaderText() + '\'' + + ", errorMessage='" + errorMessage + '\'' + + ", dkimRawField='" + dkimRawField + '\'' + + ", type=" + type + + '}'; + } +} diff --git a/main/src/main/java/org/apache/james/jdkim/api/SignatureRecord.java b/main/src/main/java/org/apache/james/jdkim/api/SignatureRecord.java index 3281e23..ca6b9a6 100644 --- a/main/src/main/java/org/apache/james/jdkim/api/SignatureRecord.java +++ b/main/src/main/java/org/apache/james/jdkim/api/SignatureRecord.java @@ -60,6 +60,8 @@ public interface SignatureRecord { public abstract void validate(); public abstract byte[] getSignature(); + + public abstract CharSequence getRawSignature(); public abstract void setSignature(byte[] newSignature); diff --git a/main/src/main/java/org/apache/james/jdkim/exceptions/CompositeFailException.java b/main/src/main/java/org/apache/james/jdkim/exceptions/CompositeFailException.java new file mode 100644 index 0000000..36bbdee --- /dev/null +++ b/main/src/main/java/org/apache/james/jdkim/exceptions/CompositeFailException.java @@ -0,0 +1,18 @@ +package org.apache.james.jdkim.exceptions; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class CompositeFailException extends FailException { + private final List exceptions = new ArrayList<>(); + + public CompositeFailException(Collection exceptions, String message) { + super(message); + this.exceptions.addAll(exceptions); + } + + public List getExceptions() { + return exceptions; + } +} 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 1cee2b9..fc30b10 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 @@ -284,6 +284,10 @@ public byte[] getSignature() { return Base64.decodeBase64(getValue("b").toString().getBytes()); } + public CharSequence getRawSignature() { + return getValue("b"); + } + public int getBodyHashLimit() { String limit = getValue("l").toString(); if (ALL.equals(limit)) diff --git a/main/src/test/java/org/apache/james/jdkim/FileBasedTest.java b/main/src/test/java/org/apache/james/jdkim/FileBasedTest.java index f7cdd36..956f98a 100644 --- a/main/src/test/java/org/apache/james/jdkim/FileBasedTest.java +++ b/main/src/test/java/org/apache/james/jdkim/FileBasedTest.java @@ -209,7 +209,9 @@ protected void runTest() throws Throwable { "k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC1CTqmkuRWkxlHcv1peAz3c0RuXHthVO1xx1Hy4HryZUJwSJo/R3cnEwKorQvlRuDSMgXSLLxI8u6n7h6mzRmHdsS/A+pKc7nx/6WS4N6U57PSNqOclxfwa27m/EIL6KTk9KDhaKsXxquQUBkP1CQEUZHPhQ/t7s4dmU/kvGFgNQIDAQAB"); try { - List res = new DKIMVerifier(pkr).verify(is); + DKIMVerifier verifier = new DKIMVerifier(pkr); + List res = verifier.verify(is); + assertEquals(1, verifier.getResults().size()); if (getName().startsWith("NONE_")) assertNull(res); if (getName().startsWith("FAIL_")) diff --git a/main/src/test/java/org/apache/james/jdkim/PerlDKIMTest.java b/main/src/test/java/org/apache/james/jdkim/PerlDKIMTest.java index 88504e7..e24ee7f 100644 --- a/main/src/test/java/org/apache/james/jdkim/PerlDKIMTest.java +++ b/main/src/test/java/org/apache/james/jdkim/PerlDKIMTest.java @@ -22,6 +22,7 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.apache.james.jdkim.api.Result; import org.apache.james.jdkim.api.SignatureRecord; import org.apache.james.jdkim.exceptions.FailException; @@ -111,7 +112,26 @@ else if (getName().startsWith("bad_")) expectFailure = true; try { - List res = new DKIMVerifier(pkr).verify(is); + DKIMVerifier verifier = new DKIMVerifier(pkr); + List res = verifier.verify(is); + + if (getName().matches("good_dk_7|good_dk_6|dk_headers_2|good_dk_3") + || getName().matches("|good_dk_gmail|dk_headers_1|good_dk_5|good_dk_4") + || getName().matches("good_dk_2|good_dk_yahoo|bad_dk_1|bad_dk_2|good_dk_1|dk_multiple_1")) { + assertEquals(0, verifier.getResults().size()); + } else if (getName().equals("multiple_2")) { + assertEquals(4, verifier.getResults().size()); + assertEquals(1, verifier.getResults().stream().filter(r -> r.getResultType() == Result.Type.PASS).count()); + assertEquals(1, verifier.getResults().stream().filter(r -> r.getResultType() == Result.Type.FAIL).count()); + assertEquals(2, verifier.getResults().stream().filter(r -> r.getResultType() == Result.Type.PERMERROR).count()); + assertEquals(1, verifier.getResults().stream().filter(r -> r.getResultType() == Result.Type.PASS + && r.getHeaderText().equals("dkim=pass header.d=messiah.edu header.s=selector1 header.b=keocS8z7y+ut")).count()); + assertEquals(1, verifier.getResults().stream().filter(r -> r.getResultType() == Result.Type.FAIL + && r.getHeaderText().equals("dkim=fail header.d=messiah.edu header.s=selector1 header.b=shouldfailut")).count()); + } else { + assertEquals(1, verifier.getResults().size()); + } + assertTrue(verifier.getResults().stream().allMatch(f -> f.getRecord().getRawSignature() != null)); if (expectNull) assertNull(res); if (expectFailure) From df871da0dfcf1e4c961e4e61d8a1f965c82fc3ce Mon Sep 17 00:00:00 2001 From: Emerson Pinter Date: Tue, 25 Mar 2025 12:34:46 -0300 Subject: [PATCH 055/114] Code cleanup --- .../org/apache/james/jdkim/DKIMCommon.java | 4 +-- .../org/apache/james/jdkim/DKIMSigner.java | 4 +-- .../org/apache/james/jdkim/DKIMVerifier.java | 26 ++++++++----------- .../jdkim/canon/SimpleBodyCanonicalizer.java | 4 +-- .../james/jdkim/impl/CompoundBodyHasher.java | 6 ++--- .../impl/DNSPublicKeyRecordRetriever.java | 1 - .../org/apache/james/jdkim/impl/Message.java | 6 ++--- .../MultiplexingPublicKeyRecordRetriever.java | 2 +- .../jdkim/tagvalue/PublicKeyRecordImpl.java | 4 +-- .../jdkim/tagvalue/SignatureRecordImpl.java | 4 +-- .../apache/james/jdkim/tagvalue/TagValue.java | 17 +++++------- 11 files changed, 33 insertions(+), 45 deletions(-) diff --git a/main/src/main/java/org/apache/james/jdkim/DKIMCommon.java b/main/src/main/java/org/apache/james/jdkim/DKIMCommon.java index 83d451d..853cdd1 100644 --- a/main/src/main/java/org/apache/james/jdkim/DKIMCommon.java +++ b/main/src/main/java/org/apache/james/jdkim/DKIMCommon.java @@ -75,12 +75,12 @@ protected static void signatureCheck(Headers h, SignatureRecord sign, // NOTE: this could be improved by using iterators. // NOTE: this relies on the list returned by Message being in insertion // order - Map processedHeader = new HashMap(); + Map processedHeader = new HashMap<>(); for (CharSequence header : headers) { // NOTE check this getter is case insensitive List hl = h.getFields(header.toString()); - if (hl != null && hl.size() > 0) { + if (hl != null && !hl.isEmpty()) { String lowerCaseHeader = header.toString().toLowerCase(Locale.US); Integer done = processedHeader.get(lowerCaseHeader); if (done == null) diff --git a/main/src/main/java/org/apache/james/jdkim/DKIMSigner.java b/main/src/main/java/org/apache/james/jdkim/DKIMSigner.java index 1a91c1e..82d1087 100644 --- a/main/src/main/java/org/apache/james/jdkim/DKIMSigner.java +++ b/main/src/main/java/org/apache/james/jdkim/DKIMSigner.java @@ -65,9 +65,7 @@ public String sign(InputStream is) throws IOException, FailException { try { try { message = new Message(is); - } catch (RuntimeException e) { - throw e; - } catch (IOException e) { + } catch (RuntimeException | IOException e) { throw e; } catch (Exception e1) { // This can only be a MimeException but we don't declare to allow usage of diff --git a/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java b/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java index 73478c5..b557624 100644 --- a/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java +++ b/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java @@ -152,8 +152,8 @@ public static void apply(PublicKeyRecord pkr, SignatureRecord sign) throws PermF * * @param sign the signature record * @return an "applicable" PublicKeyRecord - * @throws TempFailException - * @throws PermFailException + * @throws TempFailException For temporary error + * @throws PermFailException For unrecoverable failure */ public PublicKeyRecord publicRecordLookup(SignatureRecord sign) throws TempFailException, PermFailException { @@ -183,10 +183,10 @@ public PublicKeyRecord publicRecordLookup(SignatureRecord sign) } if (key == null) { if (lastTempFailure != null) { - if (sign != null) lastTempFailure.setRelatedRecord(sign); + lastTempFailure.setRelatedRecord(sign); throw lastTempFailure; } else if (lastPermFailure != null) { - if (sign != null) lastPermFailure.setRelatedRecord(sign); + lastPermFailure.setRelatedRecord(sign); throw lastPermFailure; } // this is unexpected because the publicKeySelector always returns // null or exception @@ -204,7 +204,7 @@ public PublicKeyRecord publicRecordLookup(SignatureRecord sign) * * @param is inputStream * @return a list of verified signature records. - * @throws IOException + * @throws IOException If error occurs handling data * @throws FailException if no signature can be verified */ public List verify(InputStream is) throws IOException, @@ -240,8 +240,8 @@ public BodyHasher newBodyHasher(Headers messageHeaders) throws FailException { // For each DKIM-signature we prepare an hashjob. // We calculate all hashes concurrently so to read // the inputstream only once. - Map bodyHashJobs = new HashMap(); - Hashtable signatureExceptions = new Hashtable(); + Map bodyHashJobs = new HashMap<>(); + Hashtable signatureExceptions = new Hashtable<>(); for (String signatureField : fields) { try { int pos = signatureField.indexOf(':'); @@ -326,7 +326,7 @@ public BodyHasher newBodyHasher(Headers messageHeaders) throws FailException { * @param messageHeaders parsed headers * @param bodyInputStream input stream for the body. * @return a list of verified signature records - * @throws IOException + * @throws IOException If error occurs handling data * @throws FailException if no signature can be verified */ public List verify(Headers messageHeaders, @@ -388,7 +388,7 @@ private CompoundBodyHasher validateBodyHasher(BodyHasher bh) */ private List verify(CompoundBodyHasher compoundBodyHasher) throws FailException { - List verifiedSignatures = new LinkedList(); + List verifiedSignatures = new LinkedList<>(); for (BodyHasherImpl bhj : compoundBodyHasher.getBodyHashJobs().values()) { byte[] computedHash = bhj.getDigest(); byte[] expectedBodyHash = bhj.getSignatureRecord().getBodyHash(); @@ -492,7 +492,7 @@ private FailException prepareException(Map signatureExcep * @param decoded the expected signature hash * @param key the DKIM public key record * @param headers the list of signed headers - * @throws PermFailException + * @throws PermFailException If signature or public key validation fails */ private void signatureVerify(Headers h, SignatureRecord sign, byte[] decoded, PublicKeyRecord key, List headers) @@ -513,11 +513,7 @@ private void signatureVerify(Headers h, SignatureRecord sign, if (!signature.verify(decoded)) throw new PermFailException("Header signature does not verify", sign); - } catch (InvalidKeyException e) { - throw new PermFailException(e.getMessage(), sign, e); - } catch (NoSuchAlgorithmException e) { - throw new PermFailException(e.getMessage(), sign, e); - } catch (SignatureException e) { + } catch (InvalidKeyException | NoSuchAlgorithmException | SignatureException e) { throw new PermFailException(e.getMessage(), sign, e); } } diff --git a/main/src/main/java/org/apache/james/jdkim/canon/SimpleBodyCanonicalizer.java b/main/src/main/java/org/apache/james/jdkim/canon/SimpleBodyCanonicalizer.java index 43515ac..90b14bf 100644 --- a/main/src/main/java/org/apache/james/jdkim/canon/SimpleBodyCanonicalizer.java +++ b/main/src/main/java/org/apache/james/jdkim/canon/SimpleBodyCanonicalizer.java @@ -45,7 +45,7 @@ public void write(byte[] b, int off, int len) throws IOException { System.out.println("I:(" + lastWasCR + "|" + countCRLF + ") [" + new String(b, off, len) + "]"); if (lastWasCR) { - if (len > 0 && b[off] == '\n') { + if (b[off] == '\n') { countCRLF++; lastWasCR = false; off++; @@ -77,7 +77,7 @@ public void write(byte[] b, int off, int len) throws IOException { public void write(int b) throws IOException { if (DEEP_DEBUG) - System.out.println("B:(" + lastWasCR + "|" + countCRLF + ") [" + "" + (char) b + "]"); + System.out.println("B:(" + lastWasCR + "|" + countCRLF + ") [" + (char) b + "]"); if (lastWasCR && '\n' == b) { lastWasCR = false; countCRLF++; diff --git a/main/src/main/java/org/apache/james/jdkim/impl/CompoundBodyHasher.java b/main/src/main/java/org/apache/james/jdkim/impl/CompoundBodyHasher.java index 8836fb8..31f9fdd 100644 --- a/main/src/main/java/org/apache/james/jdkim/impl/CompoundBodyHasher.java +++ b/main/src/main/java/org/apache/james/jdkim/impl/CompoundBodyHasher.java @@ -31,11 +31,9 @@ /** * 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 + * This object is a container for "bodyHashJobs" and * "signatureExceptions" for 2-stage verification process. */ public class CompoundBodyHasher implements BodyHasher { @@ -52,7 +50,7 @@ public CompoundBodyHasher(Map bodyHashJobs, o = bodyHashJobs.values().iterator().next() .getOutputStream(); } else { - List outputStreams = new LinkedList(); + List outputStreams = new LinkedList<>(); for (BodyHasherImpl bhj : bodyHashJobs.values()) { outputStreams.add(bhj.getOutputStream()); } diff --git a/main/src/main/java/org/apache/james/jdkim/impl/DNSPublicKeyRecordRetriever.java b/main/src/main/java/org/apache/james/jdkim/impl/DNSPublicKeyRecordRetriever.java index 27e668e..f7eb449 100644 --- a/main/src/main/java/org/apache/james/jdkim/impl/DNSPublicKeyRecordRetriever.java +++ b/main/src/main/java/org/apache/james/jdkim/impl/DNSPublicKeyRecordRetriever.java @@ -79,7 +79,6 @@ public List getRecords(CharSequence methodAndOptions, * @param rr Record array * @return list */ - @SuppressWarnings("unchecked") public static List convertRecordsToList(Record[] rr) { List records; if (rr != null && rr.length > 0) { diff --git a/main/src/main/java/org/apache/james/jdkim/impl/Message.java b/main/src/main/java/org/apache/james/jdkim/impl/Message.java index 43f49e9..8e84063 100644 --- a/main/src/main/java/org/apache/james/jdkim/impl/Message.java +++ b/main/src/main/java/org/apache/james/jdkim/impl/Message.java @@ -41,7 +41,7 @@ */ public class Message implements Headers { - private org.apache.james.mime4j.dom.Message message; + private final org.apache.james.mime4j.dom.Message message; /** * Creates a new Header from the specified stream. @@ -87,7 +87,7 @@ public List getFields() { } private List convertFields(List res) { - List res2 = new LinkedList(); + List res2 = new LinkedList<>(); MessageWriter mw; try { mw = newMessageBuilder().newMessageWriter(); @@ -102,7 +102,7 @@ private List convertFields(List res) { // writeField always ends with CRLF and we don't want it. byte[] fieldbytes = bos.toByteArray(); field = new String(fieldbytes, 0, fieldbytes.length - 2); - } catch (IOException e) { + } catch (IOException ignored) { } res2.add(field); } diff --git a/main/src/main/java/org/apache/james/jdkim/impl/MultiplexingPublicKeyRecordRetriever.java b/main/src/main/java/org/apache/james/jdkim/impl/MultiplexingPublicKeyRecordRetriever.java index 3fde794..dc45acd 100644 --- a/main/src/main/java/org/apache/james/jdkim/impl/MultiplexingPublicKeyRecordRetriever.java +++ b/main/src/main/java/org/apache/james/jdkim/impl/MultiplexingPublicKeyRecordRetriever.java @@ -33,7 +33,7 @@ public class MultiplexingPublicKeyRecordRetriever implements private final Map retrievers; public MultiplexingPublicKeyRecordRetriever() { - retrievers = new HashMap(); + retrievers = new HashMap<>(); } public MultiplexingPublicKeyRecordRetriever(String methodName, diff --git a/main/src/main/java/org/apache/james/jdkim/tagvalue/PublicKeyRecordImpl.java b/main/src/main/java/org/apache/james/jdkim/tagvalue/PublicKeyRecordImpl.java index 5cab1b6..c1225d9 100644 --- a/main/src/main/java/org/apache/james/jdkim/tagvalue/PublicKeyRecordImpl.java +++ b/main/src/main/java/org/apache/james/jdkim/tagvalue/PublicKeyRecordImpl.java @@ -131,7 +131,7 @@ public Pattern getGranularityPattern() { throw new IllegalStateException("Syntax error in granularity: " + g); } - if (g.length() == 0) { + if (g.isEmpty()) { // TODO this works but smells too much as an hack. // in case of "g=" with nothing specified then we return a pattern // that won't match @@ -156,7 +156,7 @@ public Pattern getGranularityPattern() { public List getFlags() { String flags = getValue("t").toString(); String[] flagsStrings = flags.split(":"); - List res = new ArrayList(); + List res = new ArrayList<>(); for (String flagsString : flagsStrings) { res.add(trimFWS(flagsString, 0, flagsString.length() - 1, true)); 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 fc30b10..2fdaf96 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 @@ -325,7 +325,7 @@ public String getHeaderCanonicalisationMethod() { public List getRecordLookupMethods() { String flags = getValue("q").toString(); String[] flagsStrings = flags.split(":"); - List res = new LinkedList(); + List res = new LinkedList<>(); for (String flagsString : flagsStrings) { // TODO add validation method[/option] // if (VALIDATION) @@ -345,7 +345,7 @@ public void setBodyHash(byte[] newBodyHash) { setValue("bh", bodyHash); // If a t=; parameter is present in the signature, make sure to // fill it with the current timestamp - if (getValue("t") != null && getValue("t").toString().trim().length() == 0) { + if (getValue("t") != null && getValue("t").toString().trim().isEmpty()) { setValue("t", "" + (System.currentTimeMillis() / 1000)); } } diff --git a/main/src/main/java/org/apache/james/jdkim/tagvalue/TagValue.java b/main/src/main/java/org/apache/james/jdkim/tagvalue/TagValue.java index 146dabb..6af2d22 100644 --- a/main/src/main/java/org/apache/james/jdkim/tagvalue/TagValue.java +++ b/main/src/main/java/org/apache/james/jdkim/tagvalue/TagValue.java @@ -48,8 +48,8 @@ public class TagValue { // we may use a TreeMap because we may need to know original order. private final Map tagValues; - protected final Set mandatoryTags = new HashSet(); - protected final Map defaults = new HashMap(); + protected final Set mandatoryTags = new HashSet<>(); + protected final Map defaults = new HashMap<>(); private String stringRepresentation = null; protected Set tagSet() { @@ -125,7 +125,7 @@ public TagValue(String data) { protected Map newTagValue() { // extensions may override this to use TreeMaps in order to keep track // of orders - return new HashMap(); + return new HashMap<>(); } protected void init() { @@ -144,7 +144,7 @@ protected void parse(String data) { // TODO check whether this is correct or not // this allow FWS/WSP after the final ";" String rest = data.substring(i); - if (rest.length() > 0 + if (!rest.isEmpty() && trimFWS(rest, 0, rest.length() - 1, true).length() > 0) { throw new IllegalStateException( "Unexpected termination at position " + i + ": " @@ -204,11 +204,8 @@ public boolean equals(Object obj) { return false; TagValue other = (TagValue) obj; if (tagValues == null) { - if (other.tagValues != null) - return false; - } else if (!tagValues.equals(other.tagValues)) - return false; - return true; + return other.tagValues == null; + } else return tagValues.equals(other.tagValues); } public Set getTags() { @@ -242,7 +239,7 @@ public void validate() { } protected List stringToColonSeparatedList(String h, Pattern pattern) { - List headers = new ArrayList(); + List headers = new ArrayList<>(); for (int i = 0; i < h.length(); i++) { int p = h.indexOf(':', i); if (p == -1) From 9ddcce8d63b97a7ee99c45c2f0e2751f4b661f62 Mon Sep 17 00:00:00 2001 From: Emerson Pinter Date: Tue, 25 Mar 2025 12:36:16 -0300 Subject: [PATCH 056/114] Remove unused condition The exceptions are not thrown here anymore, signatureExceptions and bodyHashJobs are never empty at the same time at this point. --- main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java b/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java index b557624..54cfec3 100644 --- a/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java +++ b/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java @@ -312,10 +312,6 @@ public BodyHasher newBodyHasher(Headers messageHeaders) throws FailException { } } - if (bodyHashJobs.isEmpty() && signatureExceptions.isEmpty()) { - throw new PermFailException("Unexpected condition with " + fields); - } - return new CompoundBodyHasher(bodyHashJobs, signatureExceptions); } From 2f62fac9999b17a591e0f748c7f7ae3acd64dfa4 Mon Sep 17 00:00:00 2001 From: Emerson Pinter Date: Tue, 25 Mar 2025 13:30:11 -0300 Subject: [PATCH 057/114] Simplify dns lookup code This commit adds a PermFailException when dnsjava fails to parse the name, also replaces the convert method. Now it returns an empty list when Record[] is null, which is already handled by DKIMVerifier (publicKeySelector). --- .../impl/DNSPublicKeyRecordRetriever.java | 77 +++++-------------- 1 file changed, 20 insertions(+), 57 deletions(-) diff --git a/main/src/main/java/org/apache/james/jdkim/impl/DNSPublicKeyRecordRetriever.java b/main/src/main/java/org/apache/james/jdkim/impl/DNSPublicKeyRecordRetriever.java index f7eb449..65141a3 100644 --- a/main/src/main/java/org/apache/james/jdkim/impl/DNSPublicKeyRecordRetriever.java +++ b/main/src/main/java/org/apache/james/jdkim/impl/DNSPublicKeyRecordRetriever.java @@ -19,8 +19,10 @@ package org.apache.james.jdkim.impl; -import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; import org.apache.james.jdkim.api.PublicKeyRecordRetriever; import org.apache.james.jdkim.exceptions.PermFailException; @@ -46,7 +48,7 @@ public DNSPublicKeyRecordRetriever(Resolver resolver) { } /** - * @see org.apache.james.jdkim.api.PublicKeyRecordRetriever#getRecords(java.lang.CharSequence, java.lang.CharSequence, java.lang.CharSequence) + * {@inheritDoc} */ public List getRecords(CharSequence methodAndOptions, CharSequence selector, CharSequence token) @@ -54,66 +56,27 @@ public List getRecords(CharSequence methodAndOptions, if (!"dns/txt".equals(methodAndOptions)) throw new PermFailException("Only dns/txt is supported: " + methodAndOptions + " options unsupported."); + Lookup query; try { - Lookup query = new Lookup(selector + "._domainkey." + token, - Type.TXT); - query.setResolver(resolver); - - Record[] rr = query.run(); - int queryResult = query.getResult(); + query = new Lookup(selector + "._domainkey." + token, Type.TXT); + } catch (TextParseException e) { + throw new PermFailException("Invalid dns record", e); + } + query.setResolver(resolver); - if (queryResult == Lookup.TRY_AGAIN) { - throw new TempFailException(query.getErrorString()); - } + Record[] rr = query.run(); - return convertRecordsToList(rr); - } catch (TextParseException e) { - // TODO log - return null; + if (query.getResult() == Lookup.TRY_AGAIN) { + throw new TempFailException(query.getErrorString()); } - } - /** - * Convert the given TXT Record array to a String List - * - * @param rr Record array - * @return list - */ - public static List convertRecordsToList(Record[] rr) { - List records; - if (rr != null && rr.length > 0) { - records = new ArrayList(); - for (Record aRr : rr) { - switch (aRr.getType()) { - case Type.TXT: - TXTRecord txt = (TXTRecord) aRr; - if (txt.getStrings().size() == 1) { - // This was required until dnsjava 2.0.6 because dnsjava - // was escaping - // the result like it was doublequoted (JDKIM-7). - // records.add(((String)txt.getStrings().get(0)).replaceAll("\\\\", - // "")); - records.add(((String) txt.getStrings().get(0))); - } else { - StringBuilder sb = new StringBuilder(); - for (String k : (Iterable) txt.getStrings()) { - // This was required until dnsjava 2.0.6 because - // dnsjava was escaping - // the result like it was doublequoted (JDKIM-7). - // k = k.replaceAll("\\\\", ""); - sb.append(k); - } - records.add(sb.toString()); - } - break; - default: - return null; - } - } - } else { - records = null; + if (rr == null || rr.length == 0) { + return Collections.emptyList(); } - return records; - } + return Arrays.stream(rr) + .filter(r -> r.getType() == Type.TXT) + .map(r -> String.join("", ((TXTRecord) r).getStrings())) + .collect(Collectors.toList()); + } } From 2d37cd6f9ee21ee9ab8b354afd3ed4210ab326ca Mon Sep 17 00:00:00 2001 From: Jean Helou Date: Wed, 2 Apr 2025 16:00:26 +0200 Subject: [PATCH 058/114] [devscout] fixes selector for google dkim signature the gamma selector for gmail dkim no longer resolves to anything. --- .../java/org/apache/james/jdkim/DNSPublicKeyRetrieverTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/src/test/java/org/apache/james/jdkim/DNSPublicKeyRetrieverTest.java b/main/src/test/java/org/apache/james/jdkim/DNSPublicKeyRetrieverTest.java index 4c089fb..4b60e9c 100644 --- a/main/src/test/java/org/apache/james/jdkim/DNSPublicKeyRetrieverTest.java +++ b/main/src/test/java/org/apache/james/jdkim/DNSPublicKeyRetrieverTest.java @@ -62,7 +62,7 @@ public void testConstructor() { public void testRetrieve() throws TempFailException, PermFailException { PublicKeyRecordRetriever pkr = new DNSPublicKeyRecordRetriever(); pkr.getRecords("dns/txt", "lima", "yahoogroups.com"); - pkr.getRecords("dns/txt", "gamma", "gmail.com"); + pkr.getRecords("dns/txt", "20230601", "gmail.com"); new TagValue(pkr.getRecords("dns/txt", "lima", "yahoogroups.com").get(0)); } From f5222fceeb91d9341ab235edf340b259b38fd8e1 Mon Sep 17 00:00:00 2001 From: Jean Helou Date: Wed, 2 Apr 2025 16:03:04 +0200 Subject: [PATCH 059/114] [devscout] cleans up redundant keywords --- .../apache/james/jdkim/api/BodyHasher.java | 2 +- .../org/apache/james/jdkim/api/Headers.java | 4 +- .../james/jdkim/api/PublicKeyRecord.java | 22 ++++----- .../jdkim/api/PublicKeyRecordRetriever.java | 2 +- .../james/jdkim/api/SignatureRecord.java | 46 +++++++++---------- 5 files changed, 38 insertions(+), 38 deletions(-) diff --git a/main/src/main/java/org/apache/james/jdkim/api/BodyHasher.java b/main/src/main/java/org/apache/james/jdkim/api/BodyHasher.java index 4b9c06f..8c79b6c 100644 --- a/main/src/main/java/org/apache/james/jdkim/api/BodyHasher.java +++ b/main/src/main/java/org/apache/james/jdkim/api/BodyHasher.java @@ -23,6 +23,6 @@ public interface BodyHasher { - public abstract OutputStream getOutputStream(); + OutputStream getOutputStream(); } \ No newline at end of file diff --git a/main/src/main/java/org/apache/james/jdkim/api/Headers.java b/main/src/main/java/org/apache/james/jdkim/api/Headers.java index 70bfca0..3e07cff 100644 --- a/main/src/main/java/org/apache/james/jdkim/api/Headers.java +++ b/main/src/main/java/org/apache/james/jdkim/api/Headers.java @@ -28,7 +28,7 @@ public interface Headers { * * @return the list of Field objects. */ - public abstract List getFields(); + List getFields(); /** * Gets all Fields having the specified field name in a case @@ -38,6 +38,6 @@ public interface Headers { * the field name (e.g. From, Subject). * @return the list of fields. */ - public abstract List getFields(final String name); + List getFields(final String name); } \ No newline at end of file diff --git a/main/src/main/java/org/apache/james/jdkim/api/PublicKeyRecord.java b/main/src/main/java/org/apache/james/jdkim/api/PublicKeyRecord.java index ffc861e..746c7ba 100644 --- a/main/src/main/java/org/apache/james/jdkim/api/PublicKeyRecord.java +++ b/main/src/main/java/org/apache/james/jdkim/api/PublicKeyRecord.java @@ -25,32 +25,32 @@ public interface PublicKeyRecord { - public final static String ANY = ";any;"; + String ANY = ";any;"; - public abstract void validate(); + void validate(); - public abstract boolean isHashMethodSupported(CharSequence hash); + boolean isHashMethodSupported(CharSequence hash); - public abstract boolean isKeyTypeSupported(CharSequence hash); + boolean isKeyTypeSupported(CharSequence hash); /** * @return null if "any", otherwise a list of supported methods */ - public abstract List getAcceptableHashMethods(); + List getAcceptableHashMethods(); /** * @return null if "any", otherwise a list of supported methods */ - public abstract List getAcceptableKeyTypes(); + List getAcceptableKeyTypes(); - public abstract Pattern getGranularityPattern(); + Pattern getGranularityPattern(); - public abstract PublicKey getPublicKey(); + PublicKey getPublicKey(); - public abstract List getFlags(); + List getFlags(); - public abstract boolean isTesting(); + boolean isTesting(); - public abstract boolean isDenySubdomains(); + boolean isDenySubdomains(); } \ No newline at end of file diff --git a/main/src/main/java/org/apache/james/jdkim/api/PublicKeyRecordRetriever.java b/main/src/main/java/org/apache/james/jdkim/api/PublicKeyRecordRetriever.java index 1310b39..4945f56 100644 --- a/main/src/main/java/org/apache/james/jdkim/api/PublicKeyRecordRetriever.java +++ b/main/src/main/java/org/apache/james/jdkim/api/PublicKeyRecordRetriever.java @@ -39,7 +39,7 @@ public interface PublicKeyRecordRetriever { * @throws PermFailException * in case of unsupported options */ - public List getRecords(CharSequence methodAndOption, + List getRecords(CharSequence methodAndOption, CharSequence selector, CharSequence token) throws TempFailException, PermFailException; diff --git a/main/src/main/java/org/apache/james/jdkim/api/SignatureRecord.java b/main/src/main/java/org/apache/james/jdkim/api/SignatureRecord.java index ca6b9a6..7adcaaf 100644 --- a/main/src/main/java/org/apache/james/jdkim/api/SignatureRecord.java +++ b/main/src/main/java/org/apache/james/jdkim/api/SignatureRecord.java @@ -27,48 +27,48 @@ */ public interface SignatureRecord { - public final static String RELAXED = "relaxed"; - public final static String SIMPLE = "simple"; - public final static String ALL = ";all;"; + String RELAXED = "relaxed"; + String SIMPLE = "simple"; + String ALL = ";all;"; - public abstract List getHeaders(); + List getHeaders(); - public abstract CharSequence getIdentityLocalPart(); + CharSequence getIdentityLocalPart(); - public abstract CharSequence getIdentity(); + CharSequence getIdentity(); - public abstract CharSequence getHashKeyType(); + CharSequence getHashKeyType(); - public abstract CharSequence getHashMethod(); + CharSequence getHashMethod(); - public abstract CharSequence getHashAlgo(); + CharSequence getHashAlgo(); - public abstract CharSequence getSelector(); + CharSequence getSelector(); - public abstract CharSequence getDToken(); + CharSequence getDToken(); - public abstract byte[] getBodyHash(); + byte[] getBodyHash(); - public abstract int getBodyHashLimit(); + int getBodyHashLimit(); - public abstract String getHeaderCanonicalisationMethod(); + String getHeaderCanonicalisationMethod(); - public abstract String getBodyCanonicalisationMethod(); + String getBodyCanonicalisationMethod(); - public abstract List getRecordLookupMethods(); + List getRecordLookupMethods(); - public abstract void validate(); + void validate(); - public abstract byte[] getSignature(); + byte[] getSignature(); - public abstract CharSequence getRawSignature(); + CharSequence getRawSignature(); - public abstract void setSignature(byte[] newSignature); + void setSignature(byte[] newSignature); - public abstract void setBodyHash(byte[] newBodyHash); + void setBodyHash(byte[] newBodyHash); - public abstract String toUnsignedString(); + String toUnsignedString(); - public abstract Long getSignatureTimestamp(); + Long getSignatureTimestamp(); } \ No newline at end of file From cf7b6532ef7169f18341baf9109ce4cacca3f7b6 Mon Sep 17 00:00:00 2001 From: Jean Helou Date: Wed, 9 Apr 2025 10:30:51 +0200 Subject: [PATCH 060/114] [devscout] removes accessors for internals --- .../james/jdkim/impl/BodyHasherImpl.java | 24 ++++--------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/main/src/main/java/org/apache/james/jdkim/impl/BodyHasherImpl.java b/main/src/main/java/org/apache/james/jdkim/impl/BodyHasherImpl.java index e180991..a788e32 100644 --- a/main/src/main/java/org/apache/james/jdkim/impl/BodyHasherImpl.java +++ b/main/src/main/java/org/apache/james/jdkim/impl/BodyHasherImpl.java @@ -75,9 +75,9 @@ public BodyHasherImpl(SignatureRecord sign) throws PermFailException { out = new DebugOutputStream(out); out = prepareCanonicalizerOutputStream(limit, relaxedBody, out); - setSignatureRecord(sign); - setDigestOutputStream(dout); - setOutputStream(out); + this.sign = sign; + this.digesterOS = dout; + this.out = out; } private OutputStream prepareCanonicalizerOutputStream(int limit, @@ -103,24 +103,8 @@ public SignatureRecord getSignatureRecord() { return sign; } - private DigestOutputStream getDigesterOutputStream() { - return digesterOS; - } - public byte[] getDigest() { - return getDigesterOutputStream().getDigest(); - } - - public void setSignatureRecord(SignatureRecord sign) { - this.sign = sign; - } - - public void setDigestOutputStream(DigestOutputStream dout) { - this.digesterOS = dout; - } - - public void setOutputStream(OutputStream out) { - this.out = out; + return digesterOS.getDigest(); } } From 45eff3a0ee006d77b7c71b4d808cc84cf03c9a67 Mon Sep 17 00:00:00 2001 From: Jean Helou Date: Wed, 9 Apr 2025 10:55:39 +0200 Subject: [PATCH 061/114] [devscout] makes MultiplexingPublicKeyRecordRetriever API immutable Breaking api changes: - MultiplexingPublicKeyRecordRetriever can no longer be instantiated without any methods - addRetriever is made private --- main/pom.xml | 5 + .../MultiplexingPublicKeyRecordRetriever.java | 23 +++- ...tiplexingPublicKeyRecordRetrieverTest.java | 121 +++++++++++------- pom.xml | 5 + 4 files changed, 105 insertions(+), 49 deletions(-) diff --git a/main/pom.xml b/main/pom.xml index 402674e..7c7a41f 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -55,6 +55,11 @@ org.apache.james apache-mime4j-dom + + org.assertj + assertj-core + test + diff --git a/main/src/main/java/org/apache/james/jdkim/impl/MultiplexingPublicKeyRecordRetriever.java b/main/src/main/java/org/apache/james/jdkim/impl/MultiplexingPublicKeyRecordRetriever.java index dc45acd..52facf1 100644 --- a/main/src/main/java/org/apache/james/jdkim/impl/MultiplexingPublicKeyRecordRetriever.java +++ b/main/src/main/java/org/apache/james/jdkim/impl/MultiplexingPublicKeyRecordRetriever.java @@ -22,6 +22,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import org.apache.james.jdkim.api.PublicKeyRecordRetriever; import org.apache.james.jdkim.exceptions.PermFailException; @@ -29,10 +30,22 @@ public class MultiplexingPublicKeyRecordRetriever implements PublicKeyRecordRetriever { + public static class Entry{ + String methodName; + PublicKeyRecordRetriever retriever; + private Entry(String methodName, PublicKeyRecordRetriever retriever) { + this.methodName = methodName; + this.retriever = retriever; + } + + public static Entry of(String methodName, PublicKeyRecordRetriever retriever){ + return new Entry(methodName, retriever); + } + } private final Map retrievers; - public MultiplexingPublicKeyRecordRetriever() { + private MultiplexingPublicKeyRecordRetriever() { retrievers = new HashMap<>(); } @@ -41,8 +54,14 @@ public MultiplexingPublicKeyRecordRetriever(String methodName, this(); addRetriever(methodName, pkrr); } + public MultiplexingPublicKeyRecordRetriever(Set retrieverEntries) { + this(); + retrieverEntries.forEach(it -> + addRetriever(it.methodName, it.retriever) + ); + } - public void addRetriever(String methodName, PublicKeyRecordRetriever pkrr) { + private void addRetriever(String methodName, PublicKeyRecordRetriever pkrr) { retrievers.put(methodName, pkrr); } diff --git a/main/src/test/java/org/apache/james/jdkim/MultiplexingPublicKeyRecordRetrieverTest.java b/main/src/test/java/org/apache/james/jdkim/MultiplexingPublicKeyRecordRetrieverTest.java index 4dc08e1..6b4919b 100644 --- a/main/src/test/java/org/apache/james/jdkim/MultiplexingPublicKeyRecordRetrieverTest.java +++ b/main/src/test/java/org/apache/james/jdkim/MultiplexingPublicKeyRecordRetrieverTest.java @@ -19,78 +19,105 @@ package org.apache.james.jdkim; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.ArrayList; +import java.util.List; + import org.apache.james.jdkim.api.PublicKeyRecordRetriever; 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.MultiplexingPublicKeyRecordRetriever; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; +import org.assertj.core.util.Sets; import org.junit.Test; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - public class MultiplexingPublicKeyRecordRetrieverTest { - private final PublicKeyRecordRetriever myMethodRetriever = new PublicKeyRecordRetriever() { - - public List getRecords(CharSequence methodAndOption, - CharSequence selector, CharSequence token) - throws TempFailException, PermFailException { - List l = new ArrayList(); - l.add(selector.toString()); - l.add(token.toString()); - return l; - } - + private final PublicKeyRecordRetriever myMethodRetriever = (methodAndOption, selector, token) -> { + List l = new ArrayList<>(); + l.add(selector.toString()); + l.add(token.toString()); + return l; }; - @Test - public void testMultiplexingPublicKeyRecordRetriever() { - MultiplexingPublicKeyRecordRetriever pkrr = new MultiplexingPublicKeyRecordRetriever(); - try { - pkrr.getRecords("method", "selector", "token"); - fail("method is unknown"); - } catch (FailException e) { - } - } @Test - public void testMultiplexingPublicKeyRecordRetrieverStringPublicKeyRecordRetriever() + public void should_retrieve_records_by_known_method() throws TempFailException, PermFailException { MultiplexingPublicKeyRecordRetriever pkrr = new MultiplexingPublicKeyRecordRetriever( - "mymethod", myMethodRetriever); + "mymethod", + myMethodRetriever + ); + check(pkrr, "mymethod"); } - private void check(MultiplexingPublicKeyRecordRetriever pkrr, String method) + @Test + public void should_retrieve_records_by_known_method_with_options() throws TempFailException, PermFailException { - List l = pkrr.getRecords(method, "selector", "token"); - Iterator i = l.iterator(); - assertEquals("selector", i.next()); - assertEquals("token", i.next()); - try { - l = pkrr.getRecords("anothermethod", "selector", "token"); - fail("anothermethod is not declared"); - } catch (FailException e) { - } + MultiplexingPublicKeyRecordRetriever pkrr = new MultiplexingPublicKeyRecordRetriever( + "mymethod", + myMethodRetriever + ); + + check(pkrr, "mymethod/option"); } + @Test - public void testAddRetriever() throws TempFailException, PermFailException { - MultiplexingPublicKeyRecordRetriever pkrr = new MultiplexingPublicKeyRecordRetriever(); - pkrr.addRetriever("mymethod", myMethodRetriever); - check(pkrr, "mymethod"); + public void should_fail_on_unknown_method() throws TempFailException, PermFailException { + MultiplexingPublicKeyRecordRetriever retriever = new MultiplexingPublicKeyRecordRetriever( + "mymethod", + myMethodRetriever + ); + check(retriever, "mymethod"); + assertThatThrownBy(() -> + retriever.getRecords("unknownMethod", "selector", "token") + ).isInstanceOf(FailException.class); } @Test - public void testAddRetrieverWithOptions() throws TempFailException, - PermFailException { - MultiplexingPublicKeyRecordRetriever pkrr = new MultiplexingPublicKeyRecordRetriever(); - pkrr.addRetriever("mymethod", myMethodRetriever); - check(pkrr, "mymethod/option"); + public void should_retrieve_records_from_several_known_methods() throws TempFailException, PermFailException { + MultiplexingPublicKeyRecordRetriever retriever = new MultiplexingPublicKeyRecordRetriever( + Sets.set( + MultiplexingPublicKeyRecordRetriever.Entry.of("myMethod", myMethodRetriever), + MultiplexingPublicKeyRecordRetriever.Entry.of("myOtherMethod", myMethodRetriever) + ) + ); + check(retriever, "myMethod"); + check(retriever, "myOtherMethod"); + assertThatThrownBy(() -> + retriever.getRecords("unknownMethod", "selector", "token") + ).isInstanceOf(FailException.class); + } + + @Test + public void should_retrieve_records_from_several_known_methods_with_mixed_option() throws TempFailException, PermFailException { + MultiplexingPublicKeyRecordRetriever retriever = new MultiplexingPublicKeyRecordRetriever( + Sets.set( + MultiplexingPublicKeyRecordRetriever.Entry.of("myMethod", myMethodRetriever), + MultiplexingPublicKeyRecordRetriever.Entry.of("myOtherMethod", myMethodRetriever) + ) + ); + check(retriever, "myMethod"); + check(retriever, "myMethod/option"); + check(retriever, "myOtherMethod"); + check(retriever, "myOtherMethod/option"); + assertThatThrownBy(() -> + retriever.getRecords("unknownMethod", "selector", "token") + ).isInstanceOf(FailException.class); + assertThatThrownBy(() -> + retriever.getRecords("unknownMethod/option", "selector", "token") + ).isInstanceOf(FailException.class); + } + + + private void check(MultiplexingPublicKeyRecordRetriever pkrr, String method) + throws TempFailException, PermFailException { + List records = pkrr.getRecords(method, "selector", "token"); + assertThat(records).containsExactly("selector", "token"); } } diff --git a/pom.xml b/pom.xml index b9696bf..1eceb37 100644 --- a/pom.xml +++ b/pom.xml @@ -109,6 +109,11 @@ test-jar test + + org.assertj + assertj-core + 3.26.0 + From 6daf8b5c52ce1a17f55fba56f3b59ba47d474302 Mon Sep 17 00:00:00 2001 From: Jean Helou Date: Wed, 9 Apr 2025 12:11:28 +0200 Subject: [PATCH 062/114] [devscout] removes unnecessary inheritance of DKIMCommon --- main/src/main/java/org/apache/james/jdkim/DKIMSigner.java | 4 +++- main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/main/src/main/java/org/apache/james/jdkim/DKIMSigner.java b/main/src/main/java/org/apache/james/jdkim/DKIMSigner.java index 82d1087..6a962ce 100644 --- a/main/src/main/java/org/apache/james/jdkim/DKIMSigner.java +++ b/main/src/main/java/org/apache/james/jdkim/DKIMSigner.java @@ -19,6 +19,8 @@ package org.apache.james.jdkim; +import static org.apache.james.jdkim.DKIMCommon.signatureCheck; + import org.apache.commons.codec.binary.Base64; import org.apache.james.jdkim.api.BodyHasher; import org.apache.james.jdkim.api.Headers; @@ -41,7 +43,7 @@ import java.security.spec.PKCS8EncodedKeySpec; import java.util.List; -public class DKIMSigner extends DKIMCommon { +public class DKIMSigner { private final PrivateKey privateKey; private final String signatureRecordTemplate; diff --git a/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java b/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java index 54cfec3..189adcb 100644 --- a/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java +++ b/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java @@ -19,6 +19,8 @@ package org.apache.james.jdkim; +import static org.apache.james.jdkim.DKIMCommon.signatureCheck; + import org.apache.james.jdkim.api.BodyHasher; import org.apache.james.jdkim.api.Headers; import org.apache.james.jdkim.api.PublicKeyRecord; @@ -53,7 +55,7 @@ import java.util.List; import java.util.Map; -public class DKIMVerifier extends DKIMCommon { +public class DKIMVerifier { private final PublicKeyRecordRetriever publicKeyRecordRetriever; private final List result = new ArrayList<>(); From 9aca9bd8bf21dd6da8507ee1016a3db6de9089c4 Mon Sep 17 00:00:00 2001 From: Jean Helou Date: Wed, 9 Apr 2025 13:15:15 +0200 Subject: [PATCH 063/114] [devscout] moves dkimQuotedPrintableDecode to SignatureRecord Breaking api change dkimQuotedPrintableDecode was moved from SignatureRecordImpl to SignatureRecord since it is relevant to all SignatureRecords not only to our implementation. While technically a public API I don't expect many users of the lib to call this directly. Updating the static import should be acceptable for the few who do. This led to a reorganization and a modernization of the tests. The tests for the decoding are now on SignatureRecordTest while the tests of SignatureRecordImpl specificities were moved to SignatureRecordImplTest. --- .../james/jdkim/api/SignatureRecord.java | 1 + .../jdkim/parser/DKIMQuotedPrintable.java | 88 +++++++++ .../jdkim/tagvalue/SignatureRecordImpl.java | 67 +------ .../james/jdkim/SignatureRecordImplTest.java | 179 ++++++++---------- .../james/jdkim/SignatureRecordTest.java | 109 ----------- .../jdkim/parser/DKIMQuotedPrintableTest.java | 103 ++++++++++ 6 files changed, 278 insertions(+), 269 deletions(-) create mode 100644 main/src/main/java/org/apache/james/jdkim/parser/DKIMQuotedPrintable.java delete mode 100644 main/src/test/java/org/apache/james/jdkim/SignatureRecordTest.java create mode 100644 main/src/test/java/org/apache/james/jdkim/parser/DKIMQuotedPrintableTest.java diff --git a/main/src/main/java/org/apache/james/jdkim/api/SignatureRecord.java b/main/src/main/java/org/apache/james/jdkim/api/SignatureRecord.java index 7adcaaf..bed81d5 100644 --- a/main/src/main/java/org/apache/james/jdkim/api/SignatureRecord.java +++ b/main/src/main/java/org/apache/james/jdkim/api/SignatureRecord.java @@ -31,6 +31,7 @@ public interface SignatureRecord { String SIMPLE = "simple"; String ALL = ";all;"; + List getHeaders(); CharSequence getIdentityLocalPart(); diff --git a/main/src/main/java/org/apache/james/jdkim/parser/DKIMQuotedPrintable.java b/main/src/main/java/org/apache/james/jdkim/parser/DKIMQuotedPrintable.java new file mode 100644 index 0000000..7a0de9b --- /dev/null +++ b/main/src/main/java/org/apache/james/jdkim/parser/DKIMQuotedPrintable.java @@ -0,0 +1,88 @@ +/****************************************************************************** + * 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.parser; + +import java.util.Arrays; + +public class DKIMQuotedPrintable { + public static String dkimQuotedPrintableDecode(CharSequence input) + throws IllegalArgumentException { + StringBuilder sb = new StringBuilder(input.length()); + // TODO should we fail on WSP that is not part of FWS? + // the specification in 2.6 DKIM-Quoted-Printable is not + // clear + int state = 0; + int start = 0; + int d = 0; + boolean lastWasNL = false; + for (int i = 0; i < input.length(); i++) { + if (lastWasNL && input.charAt(i) != ' ' && input.charAt(i) != '\t') { + throw new IllegalArgumentException( + "Unexpected LF not part of an FWS"); + } + lastWasNL = false; + switch (state) { + case 0: + switch (input.charAt(i)) { + case ' ': + case '\t': + case '\r': + case '\n': + if ('\n' == input.charAt(i)) + lastWasNL = true; + sb.append(input.subSequence(start, i)); + start = i + 1; + // ignoring whitespace by now. + break; + case '=': + sb.append(input.subSequence(start, i)); + state = 1; + break; + } + break; + case 1: + case 2: + if (input.charAt(i) >= '0' && input.charAt(i) <= '9' + || input.charAt(i) >= 'A' && input.charAt(i) <= 'F') { + int v = Arrays.binarySearch("0123456789ABCDEF".getBytes(), + (byte) input.charAt(i)); + if (state == 1) { + state = 2; + d = v; + } else { + d = d * 16 + v; + sb.append((char) d); + state = 0; + start = i + 1; + } + } else { + throw new IllegalArgumentException( + "Invalid input sequence at " + i); + } + } + } + if (state != 0) { + throw new IllegalArgumentException( + "Invalid quoted printable termination"); + } + sb.append(input.subSequence(start, input.length())); + return sb.toString(); + } +} 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 2fdaf96..d0373e7 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 @@ -19,10 +19,11 @@ package org.apache.james.jdkim.tagvalue; +import static org.apache.james.jdkim.parser.DKIMQuotedPrintable.dkimQuotedPrintableDecode; + import org.apache.commons.codec.binary.Base64; import org.apache.james.jdkim.api.SignatureRecord; -import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.regex.Pattern; @@ -158,70 +159,6 @@ public CharSequence getIdentity() { return dkimQuotedPrintableDecode(getValue("i")); } - public static String dkimQuotedPrintableDecode(CharSequence input) - throws IllegalArgumentException { - StringBuilder sb = new StringBuilder(input.length()); - // TODO should we fail on WSP that is not part of FWS? - // the specification in 2.6 DKIM-Quoted-Printable is not - // clear - int state = 0; - int start = 0; - int d = 0; - boolean lastWasNL = false; - for (int i = 0; i < input.length(); i++) { - if (lastWasNL && input.charAt(i) != ' ' && input.charAt(i) != '\t') { - throw new IllegalArgumentException( - "Unexpected LF not part of an FWS"); - } - lastWasNL = false; - switch (state) { - case 0: - switch (input.charAt(i)) { - case ' ': - case '\t': - case '\r': - case '\n': - if ('\n' == input.charAt(i)) - lastWasNL = true; - sb.append(input.subSequence(start, i)); - start = i + 1; - // ignoring whitespace by now. - break; - case '=': - sb.append(input.subSequence(start, i)); - state = 1; - break; - } - break; - case 1: - case 2: - if (input.charAt(i) >= '0' && input.charAt(i) <= '9' - || input.charAt(i) >= 'A' && input.charAt(i) <= 'F') { - int v = Arrays.binarySearch("0123456789ABCDEF".getBytes(), - (byte) input.charAt(i)); - if (state == 1) { - state = 2; - d = v; - } else { - d = d * 16 + v; - sb.append((char) d); - state = 0; - start = i + 1; - } - } else { - throw new IllegalArgumentException( - "Invalid input sequence at " + i); - } - } - } - if (state != 0) { - throw new IllegalArgumentException( - "Invalid quoted printable termination"); - } - sb.append(input.subSequence(start, input.length())); - return sb.toString(); - } - /** * @see org.apache.james.jdkim.api.SignatureRecord#getHashKeyType() */ 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 5d733f6..7bbd85e 100644 --- a/main/src/test/java/org/apache/james/jdkim/SignatureRecordImplTest.java +++ b/main/src/test/java/org/apache/james/jdkim/SignatureRecordImplTest.java @@ -21,87 +21,92 @@ import org.apache.james.jdkim.api.SignatureRecord; import org.apache.james.jdkim.tagvalue.SignatureRecordImpl; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + import org.junit.Test; public class SignatureRecordImplTest { + @Test + public void testBasic() { + new SignatureRecordImpl( + "v=1; a=rsa-sha256; c=relaxed/relaxed;\r\n" + + " d=gmail.com; s=beta;\r\n" + + " h=domainkey-signature:received:received:message-id:date:from:to:subject:mime-version:content-type;\r\n" + + " bh=9sd6eO/xnGLInYGPFN86r9q27iClGpwfkl4PBc5XEuQ=;\r\n" + + " b=tGQtBQg1sO+JKopOylApWLngylEqeMcXwCEUQN+S2PSpi9c1G9Nm5df9pMShus3iFaQb0PPvTfpw++cAC8/N0p3Gi/lVLc+Yh7xWEIPZ3Nxd3xqTQy7grIkBpV0q6559dEhhfFoEyLS0OK/IrqFIUVDRIMnsMjimXV7u+Sgoi7Q=") + .validate(); + + } @Test - public void testQPDecode() { - assertEquals("", SignatureRecordImpl.dkimQuotedPrintableDecode("")); - assertEquals("@", SignatureRecordImpl.dkimQuotedPrintableDecode("=40")); - assertEquals("\r\n", SignatureRecordImpl - .dkimQuotedPrintableDecode("=0D=0A")); - assertEquals("\0CIAO\0", SignatureRecordImpl - .dkimQuotedPrintableDecode("=00CIAO=00")); - assertEquals("thisisatest", SignatureRecordImpl - .dkimQuotedPrintableDecode("this\r\n\tis\r\n a\r\n \t test")); + public void testWrongOrMissingVersion() { + assertThatThrownBy(() -> + new SignatureRecordImpl( + "a=rsa-sha1; c=relaxed/relaxed;\r\n" + + " d=gmail.com; s=beta;\r\n" + + " h=domainkey-signature:received:received:message-id:date:from:to:subject:mime-version:content-type;\r\n" + + " b=Kw/TqnjB4L5ZC7DX1ibiNkuIw630uHZvzuozn/e6yTm3U8ObWEz/rJK5GO8RSrF56JrCA/xo8W2CGmyNmpQYbEpLl5P9/NcJSYUmln/O6GSa4Usyv4FdEU4FVjkyW0ToGFHNkw9Mm0urveA4Lcfk9gClJczXnvGBdiv/bkVBEJk=") + .validate() + ).isInstanceOf(IllegalStateException.class) + .hasMessageContaining("mandatory") + .hasMessageContaining("v"); + assertThatThrownBy(() -> + new SignatureRecordImpl( + "v=2; a=rsa-sha256; c=relaxed/relaxed;\r\n" + + " d=gmail.com; s=beta;\r\n" + + " h=domainkey-signature:received:received:message-id:date:from:to:subject:mime-version:content-type;\r\n" + + " bh=9sd6eO/xnGLInYGPFN86r9q27iClGpwfkl4PBc5XEuQ=;\r\n" + + " b=tGQtBQg1sO+JKopOylApWLngylEqeMcXwCEUQN+S2PSpi9c1G9Nm5df9pMShus3iFaQb0PPvTfpw++cAC8/N0p3Gi/lVLc+Yh7xWEIPZ3Nxd3xqTQy7grIkBpV0q6559dEhhfFoEyLS0OK/IrqFIUVDRIMnsMjimXV7u+Sgoi7Q=") + .validate() + ).isInstanceOf(IllegalStateException.class) + .hasMessageContaining("Invalid") + .hasMessageContaining("version") + .hasMessageContaining("2"); } @Test - public void testQPWhiteSpaces() { - assertEquals("thisisatest", SignatureRecordImpl - .dkimQuotedPrintableDecode("this is a test")); - assertEquals("thisisatest", SignatureRecordImpl - .dkimQuotedPrintableDecode("this\r\n is a test")); + public void testMissingRequired() { + assertThatThrownBy(() -> + new SignatureRecordImpl( + "v=1; a=rsa-sha256; c=relaxed/relaxed;\r\n" + + " d=gmail.com; s=beta;\r\n" + + " h=domainkey-signature:received:received:message-id:date:from:to:subject:mime-version:content-type;\r\n" + + " b=tGQtBQg1sO+JKopOylApWLngylEqeMcXwCEUQN+S2PSpi9c1G9Nm5df9pMShus3iFaQb0PPvTfpw++cAC8/N0p3Gi/lVLc+Yh7xWEIPZ3Nxd3xqTQy7grIkBpV0q6559dEhhfFoEyLS0OK/IrqFIUVDRIMnsMjimXV7u+Sgoi7Q=") + .validate() + ).isInstanceOf(IllegalStateException.class) + .hasMessageContaining("mandatory") + .hasMessageContaining("bh"); } @Test - public void testQPInvalid() { - try { - SignatureRecordImpl.dkimQuotedPrintableDecode("="); - fail("invalid sequence parsed."); - } catch (IllegalArgumentException e) { - } - try { - SignatureRecordImpl.dkimQuotedPrintableDecode("=="); - fail("invalid sequence parsed."); - } catch (IllegalArgumentException e) { - } - try { - SignatureRecordImpl.dkimQuotedPrintableDecode("=2 3"); - fail("invalid sequence parsed."); - } catch (IllegalArgumentException e) { - } - try { - SignatureRecordImpl.dkimQuotedPrintableDecode("=3"); - fail("invalid sequence parsed."); - } catch (IllegalArgumentException e) { - } - try { - SignatureRecordImpl.dkimQuotedPrintableDecode("=3a"); - fail("invalid sequence parsed."); - } catch (IllegalArgumentException e) { - } - try { - SignatureRecordImpl.dkimQuotedPrintableDecode("==20"); - fail("invalid sequence parsed."); - } catch (IllegalArgumentException e) { - } - try { - SignatureRecordImpl.dkimQuotedPrintableDecode("=20="); - fail("invalid sequence parsed."); - } catch (IllegalArgumentException e) { - } - try { - SignatureRecordImpl.dkimQuotedPrintableDecode("=3x"); - fail("invalid sequence parsed."); - } catch (IllegalArgumentException e) { - } - try { - SignatureRecordImpl.dkimQuotedPrintableDecode("this\r\nis a test"); - fail("invalid sequence parsed."); - } catch (IllegalArgumentException e) { - } + public void testDomainMismatch() { + assertThatThrownBy(() -> + new SignatureRecordImpl( + "v=1; a=rsa-sha256; c=relaxed/relaxed;\r\n" + + " d=gmail.com; s=beta; i=@agmail.com;\r\n" + + " h=domainkey-signature:received:received:message-id:date:from:to:subject:mime-version:content-type;\r\n" + + " bh=9sd6eO/xnGLInYGPFN86r9q27iClGpwfkl4PBc5XEuQ=;\r\n" + + " b=tGQtBQg1sO+JKopOylApWLngylEqeMcXwCEUQN+S2PSpi9c1G9Nm5df9pMShus3iFaQb0PPvTfpw++cAC8/N0p3Gi/lVLc+Yh7xWEIPZ3Nxd3xqTQy7grIkBpV0q6559dEhhfFoEyLS0OK/IrqFIUVDRIMnsMjimXV7u+Sgoi7Q=") + .validate() + ).isInstanceOf(IllegalStateException.class) + .hasMessageContaining("domain mismatch"); + } + + @Test + public void testMissingFrom() { + assertThatThrownBy(() -> + new SignatureRecordImpl( + "v=1; a=rsa-sha256; c=relaxed/relaxed;\r\n" + + " d=gmail.com; s=beta; i=@subdomain.gmail.com;\r\n" + + " h=domainkey-signature:received:received:message-id:date:fram:to:subject:mime-version:content-type;\r\n" + + " bh=9sd6eO/xnGLInYGPFN86r9q27iClGpwfkl4PBc5XEuQ=;\r\n" + + " b=tGQtBQg1sO+JKopOylApWLngylEqeMcXwCEUQN+S2PSpi9c1G9Nm5df9pMShus3iFaQb0PPvTfpw++cAC8/N0p3Gi/lVLc+Yh7xWEIPZ3Nxd3xqTQy7grIkBpV0q6559dEhhfFoEyLS0OK/IrqFIUVDRIMnsMjimXV7u+Sgoi7Q=") + .validate() + ).isInstanceOf(IllegalStateException.class) + .hasMessage("From field not signed"); } - // TODO check bytes > 128 - /* - * public void test8bit() { - * assertEquals("PROVA\144CIAO\144",Main.dkimQuotedPrintableDecode("PROVA=90CIAO=90")); } - */ /* * when we moved from Sun's Base64 to CommonsCodec the decoding changed @@ -116,37 +121,21 @@ public void testQPInvalid() { @Test public void testWrongHashSyntaxes() { - SignatureRecord sr = new SignatureRecordImpl("v=1; a=nothyphenedword;"); - try { - sr.getHashAlgo(); - fail("expected failure"); - } catch (Exception e) { - assertTrue(e.getMessage().toLowerCase().contains("hash")); - } - try { - sr.getHashMethod(); - fail("expected failure"); - } catch (Exception e) { - assertTrue(e.getMessage().toLowerCase().contains("hash")); - } - try { - sr.getHashAlgo(); - fail("expected failure"); - } catch (Exception e) { - assertTrue(e.getMessage().toLowerCase().contains("hash")); - } + SignatureRecord sr = new SignatureRecordImpl("v=1; a=nothyphenedword; bh=abcdef; b=1235345987; h=from:to; s=select; d=example.com"); + assertThatThrownBy(sr::getHashAlgo) + .hasMessageContaining("hash"); + + assertThatThrownBy(sr::getHashMethod) + .hasMessageContaining("hash"); } @Test public void testExpired() { - SignatureRecord sr = new SignatureRecordImpl( - "v=1; c=simple; h=from:to; s=select; d=example.com; a=rsa-sha1; x=0; bh=abcdef; b=1235345987;"); - try { - sr.validate(); - fail("expected failure"); - } catch (Exception e) { - assertTrue(e.getMessage().contains("expired")); - } + assertThatThrownBy(() -> + new SignatureRecordImpl( + "v=1; c=simple; h=from:to; s=select; d=example.com; a=rsa-sha1; x=0; bh=abcdef; b=1235345987;" + ).validate() + ).hasMessageContaining("expired"); } } diff --git a/main/src/test/java/org/apache/james/jdkim/SignatureRecordTest.java b/main/src/test/java/org/apache/james/jdkim/SignatureRecordTest.java deleted file mode 100644 index cab324c..0000000 --- a/main/src/test/java/org/apache/james/jdkim/SignatureRecordTest.java +++ /dev/null @@ -1,109 +0,0 @@ -/**************************************************************** - * 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; - -import org.apache.james.jdkim.api.SignatureRecord; -import org.apache.james.jdkim.tagvalue.SignatureRecordImpl; -import org.junit.Assert; -import org.junit.Test; - -public class SignatureRecordTest { - - @Test - public void testBasic() { - SignatureRecord sign = new SignatureRecordImpl( - "v=1; a=rsa-sha256; c=relaxed/relaxed;\r\n" - + " d=gmail.com; s=beta;\r\n" - + " h=domainkey-signature:received:received:message-id:date:from:to:subject:mime-version:content-type;\r\n" - + " bh=9sd6eO/xnGLInYGPFN86r9q27iClGpwfkl4PBc5XEuQ=;\r\n" - + " b=tGQtBQg1sO+JKopOylApWLngylEqeMcXwCEUQN+S2PSpi9c1G9Nm5df9pMShus3iFaQb0PPvTfpw++cAC8/N0p3Gi/lVLc+Yh7xWEIPZ3Nxd3xqTQy7grIkBpV0q6559dEhhfFoEyLS0OK/IrqFIUVDRIMnsMjimXV7u+Sgoi7Q="); - sign.validate(); - } - - @Test - public void testWrongOrMissingVersion() { - try { - SignatureRecord sign = new SignatureRecordImpl( - "a=rsa-sha1; c=relaxed/relaxed;\r\n" - + " d=gmail.com; s=beta;\r\n" - + " h=domainkey-signature:received:received:message-id:date:from:to:subject:mime-version:content-type;\r\n" - + " b=Kw/TqnjB4L5ZC7DX1ibiNkuIw630uHZvzuozn/e6yTm3U8ObWEz/rJK5GO8RSrF56JrCA/xo8W2CGmyNmpQYbEpLl5P9/NcJSYUmln/O6GSa4Usyv4FdEU4FVjkyW0ToGFHNkw9Mm0urveA4Lcfk9gClJczXnvGBdiv/bkVBEJk="); - sign.validate(); - Assert.fail("expected error on missing v="); - } catch (IllegalStateException e) { - } - try { - SignatureRecord sign = new SignatureRecordImpl( - "v=2; a=rsa-sha256; c=relaxed/relaxed;\r\n" - + " d=gmail.com; s=beta;\r\n" - + " h=domainkey-signature:received:received:message-id:date:from:to:subject:mime-version:content-type;\r\n" - + " bh=9sd6eO/xnGLInYGPFN86r9q27iClGpwfkl4PBc5XEuQ=;\r\n" - + " b=tGQtBQg1sO+JKopOylApWLngylEqeMcXwCEUQN+S2PSpi9c1G9Nm5df9pMShus3iFaQb0PPvTfpw++cAC8/N0p3Gi/lVLc+Yh7xWEIPZ3Nxd3xqTQy7grIkBpV0q6559dEhhfFoEyLS0OK/IrqFIUVDRIMnsMjimXV7u+Sgoi7Q="); - sign.validate(); - Assert.fail("expected error on wrong v="); - } catch (IllegalStateException e) { - } - } - - @Test - public void testMissingRequired() { - try { - SignatureRecord sign = new SignatureRecordImpl( - "v=1; a=rsa-sha256; c=relaxed/relaxed;\r\n" - + " d=gmail.com; s=beta;\r\n" - + " h=domainkey-signature:received:received:message-id:date:from:to:subject:mime-version:content-type;\r\n" - + " b=tGQtBQg1sO+JKopOylApWLngylEqeMcXwCEUQN+S2PSpi9c1G9Nm5df9pMShus3iFaQb0PPvTfpw++cAC8/N0p3Gi/lVLc+Yh7xWEIPZ3Nxd3xqTQy7grIkBpV0q6559dEhhfFoEyLS0OK/IrqFIUVDRIMnsMjimXV7u+Sgoi7Q="); - sign.validate(); - Assert.fail("expected error on missing bh="); - } catch (IllegalStateException e) { - } - } - - @Test - public void testDomainMismatch() { - try { - SignatureRecord sign = new SignatureRecordImpl( - "v=1; a=rsa-sha256; c=relaxed/relaxed;\r\n" - + " d=gmail.com; s=beta; i=@agmail.com;\r\n" - + " h=domainkey-signature:received:received:message-id:date:from:to:subject:mime-version:content-type;\r\n" - + " bh=9sd6eO/xnGLInYGPFN86r9q27iClGpwfkl4PBc5XEuQ=;\r\n" - + " b=tGQtBQg1sO+JKopOylApWLngylEqeMcXwCEUQN+S2PSpi9c1G9Nm5df9pMShus3iFaQb0PPvTfpw++cAC8/N0p3Gi/lVLc+Yh7xWEIPZ3Nxd3xqTQy7grIkBpV0q6559dEhhfFoEyLS0OK/IrqFIUVDRIMnsMjimXV7u+Sgoi7Q="); - sign.validate(); - Assert.fail("expected error on domain mismatch"); - } catch (IllegalStateException e) { - } - } - - @Test - public void testMissingFrom() { - try { - SignatureRecord sign = new SignatureRecordImpl( - "v=1; a=rsa-sha256; c=relaxed/relaxed;\r\n" - + " d=gmail.com; s=beta; i=@subdomain.gmail.com;\r\n" - + " h=domainkey-signature:received:received:message-id:date:fram:to:subject:mime-version:content-type;\r\n" - + " bh=9sd6eO/xnGLInYGPFN86r9q27iClGpwfkl4PBc5XEuQ=;\r\n" - + " b=tGQtBQg1sO+JKopOylApWLngylEqeMcXwCEUQN+S2PSpi9c1G9Nm5df9pMShus3iFaQb0PPvTfpw++cAC8/N0p3Gi/lVLc+Yh7xWEIPZ3Nxd3xqTQy7grIkBpV0q6559dEhhfFoEyLS0OK/IrqFIUVDRIMnsMjimXV7u+Sgoi7Q="); - sign.validate(); - Assert.fail("expected error on missing 'from' header"); - } catch (IllegalStateException e) { - } - } - -} diff --git a/main/src/test/java/org/apache/james/jdkim/parser/DKIMQuotedPrintableTest.java b/main/src/test/java/org/apache/james/jdkim/parser/DKIMQuotedPrintableTest.java new file mode 100644 index 0000000..617aff5 --- /dev/null +++ b/main/src/test/java/org/apache/james/jdkim/parser/DKIMQuotedPrintableTest.java @@ -0,0 +1,103 @@ +/****************************************************************************** + * 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.parser; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class DKIMQuotedPrintableTest { + + @Test + public void testQPDecode() { + assertEquals("", DKIMQuotedPrintable.dkimQuotedPrintableDecode("")); + assertEquals("@", DKIMQuotedPrintable.dkimQuotedPrintableDecode("=40")); + assertEquals("\r\n", DKIMQuotedPrintable + .dkimQuotedPrintableDecode("=0D=0A")); + assertEquals("\0CIAO\0", DKIMQuotedPrintable + .dkimQuotedPrintableDecode("=00CIAO=00")); + assertEquals("thisisatest", DKIMQuotedPrintable + .dkimQuotedPrintableDecode("this\r\n\tis\r\n a\r\n \t test")); + } + + @Test + public void testQPWhiteSpaces() { + assertEquals("thisisatest", DKIMQuotedPrintable + .dkimQuotedPrintableDecode("this is a test")); + assertEquals("thisisatest", DKIMQuotedPrintable + .dkimQuotedPrintableDecode("this\r\n is a test")); + } + + @Test + public void testQPInvalid() { + assertThatThrownBy(()-> + DKIMQuotedPrintable.dkimQuotedPrintableDecode("=") + ).isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Invalid quoted printable termination"); + + assertThatThrownBy(()-> + DKIMQuotedPrintable.dkimQuotedPrintableDecode("==") + ).isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Invalid input sequence at"); + + assertThatThrownBy(()-> + DKIMQuotedPrintable.dkimQuotedPrintableDecode("=2 3") + ).isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Invalid input sequence at"); + + assertThatThrownBy(()-> + DKIMQuotedPrintable.dkimQuotedPrintableDecode("=3") + ).isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Invalid quoted printable termination"); + + assertThatThrownBy(()-> + DKIMQuotedPrintable.dkimQuotedPrintableDecode("=3a") + ).isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Invalid input sequence at"); + + assertThatThrownBy(()-> + DKIMQuotedPrintable.dkimQuotedPrintableDecode("==20") + ).isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Invalid input sequence at"); + + assertThatThrownBy(()-> + DKIMQuotedPrintable.dkimQuotedPrintableDecode("=20=") + ).isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Invalid quoted printable termination"); + + assertThatThrownBy(()-> + DKIMQuotedPrintable.dkimQuotedPrintableDecode("=3x") + ).isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Invalid input sequence at"); + + assertThatThrownBy(()-> + DKIMQuotedPrintable.dkimQuotedPrintableDecode("this\r\nis a test") + ).isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Unexpected LF not part of an FWS"); + } + + @Test + public void test8bit() { + assertEquals("smiling face ØÞ with heart eyes", DKIMQuotedPrintable.dkimQuotedPrintableDecode("smiling=20face=20=D8=DE=20with=20heart=20eyes")); + } + + // TODO UTF-8 see https://datatracker.ietf.org/doc/html/rfc6376#section-3.2 +} From e895067b76733b628ff86811b08ab33cf39e0992 Mon Sep 17 00:00:00 2001 From: Jean Helou Date: Wed, 9 Apr 2025 13:22:32 +0200 Subject: [PATCH 064/114] [devscout] introduce SignatureRecordTemplate Using `SignatureRecordImpl` to represent templates is problematic as it forces users to write nonsensical templates that look like DKIM signatures but are not valid ones. It also forced the split of the creation and the validation only to support the construction of a specific error case in DKIMVerifier. The template relaxes the constraints of the record implementation by not requiring the following tags : - a - b - bh The template has a method to build a valid signature record when provided : - a SigningAlgorithm - a HashMethod - a body hash - a signature This will be used to further refine JDKIM apis in a later proposal. --- .../org/apache/james/jdkim/DKIMSigner.java | 4 +- .../org/apache/james/jdkim/DKIMVerifier.java | 6 +- .../apache/james/jdkim/api/HashMethod.java | 67 ++++ .../james/jdkim/api/SigningAlgorithm.java | 46 +++ .../james/jdkim/impl/BodyHasherImpl.java | 6 - .../jdkim/tagvalue/SignatureRecordImpl.java | 1 + .../tagvalue/SignatureRecordTemplate.java | 318 ++++++++++++++++++ .../james/jdkim/SignatureRecordImplTest.java | 10 +- .../jdkim/SignatureRecordTemplateTest.java | 162 +++++++++ 9 files changed, 600 insertions(+), 20 deletions(-) create mode 100644 main/src/main/java/org/apache/james/jdkim/api/HashMethod.java create mode 100644 main/src/main/java/org/apache/james/jdkim/api/SigningAlgorithm.java create mode 100644 main/src/main/java/org/apache/james/jdkim/tagvalue/SignatureRecordTemplate.java create mode 100644 main/src/test/java/org/apache/james/jdkim/SignatureRecordTemplateTest.java diff --git a/main/src/main/java/org/apache/james/jdkim/DKIMSigner.java b/main/src/main/java/org/apache/james/jdkim/DKIMSigner.java index 6a962ce..7f05aa7 100644 --- a/main/src/main/java/org/apache/james/jdkim/DKIMSigner.java +++ b/main/src/main/java/org/apache/james/jdkim/DKIMSigner.java @@ -29,7 +29,7 @@ import org.apache.james.jdkim.exceptions.PermFailException; import org.apache.james.jdkim.impl.BodyHasherImpl; import org.apache.james.jdkim.impl.Message; -import org.apache.james.jdkim.tagvalue.SignatureRecordImpl; +import org.apache.james.jdkim.tagvalue.SignatureRecordTemplate; import java.io.IOException; import java.io.InputStream; @@ -54,7 +54,7 @@ public DKIMSigner(String signatureRecordTemplate, PrivateKey privateKey) { } public SignatureRecord newSignatureRecordTemplate(String record) { - return new SignatureRecordImpl(record); + return new SignatureRecordTemplate(record); } public BodyHasher newBodyHasher(SignatureRecord signRecord) diff --git a/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java b/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java index 189adcb..84fcedd 100644 --- a/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java +++ b/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java @@ -38,6 +38,7 @@ import org.apache.james.jdkim.impl.MultiplexingPublicKeyRecordRetriever; import org.apache.james.jdkim.tagvalue.PublicKeyRecordImpl; import org.apache.james.jdkim.tagvalue.SignatureRecordImpl; +import org.apache.james.jdkim.tagvalue.SignatureRecordTemplate; import java.io.IOException; import java.io.InputStream; @@ -159,7 +160,6 @@ public static void apply(PublicKeyRecord pkr, SignatureRecord sign) throws PermF */ public PublicKeyRecord publicRecordLookup(SignatureRecord sign) throws TempFailException, PermFailException { - // System.out.println(sign); PublicKeyRecord key = null; TempFailException lastTempFailure = null; PermFailException lastPermFailure = null; @@ -252,8 +252,6 @@ public BodyHasher newBodyHasher(Headers messageHeaders) throws FailException { SignatureRecord signatureRecord = null; try { signatureRecord = newSignatureRecord(v); - // validate - signatureRecord.validate(); } catch (IllegalStateException e) { throw new PermFailException("Invalid signature record: " + e.getMessage(), signatureRecord, e); } @@ -444,7 +442,7 @@ private List resultsFromExceptions(Map exceptions for (Map.Entry e : exceptions.entrySet()) { SignatureRecord rec = e.getValue().getRelatedRecord(); if (rec == null) { - rec = new SignatureRecordImpl("v=1; d=invalid; h=from; s=invalid; b=invalidsig"); + rec = new SignatureRecordTemplate("v=1; d=invalid; h=from; s=invalid; b=invalidsig"); } Result.Type resultType = Result.Type.NONE; diff --git a/main/src/main/java/org/apache/james/jdkim/api/HashMethod.java b/main/src/main/java/org/apache/james/jdkim/api/HashMethod.java new file mode 100644 index 0000000..c813f32 --- /dev/null +++ b/main/src/main/java/org/apache/james/jdkim/api/HashMethod.java @@ -0,0 +1,67 @@ +/****************************************************************************** + * 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.api; + +import java.util.Arrays; +import java.util.Locale; +import java.util.Optional; + +public enum HashMethod { + /** + * SHA-1 is specified standard RFC 6376 [1]. + * However, in january 2018, RFC 8301 [1] was proposed which states that + * SHA-1 it MUST NOT be used any more. + * While RFC 8301 is not an internet standard, the reasons it lists for + * dropping SHA-1 (security implications because hash collision) apply + * today + * [1] https://datatracker.ietf.org/doc/html/rfc6376 + * [2] https://datatracker.ietf.org/doc/html/rfc8301 + * + * @deprecated prefer using SHA256 hashing, see javadoc for details. + */ + + @Deprecated() + SHA1{ + @Override + public String asMessageDigestAlgorithm() { + return "SHA-1"; + } + }, + + SHA256{ + @Override + public String asMessageDigestAlgorithm() { + return "SHA-256"; + } + }, + + ; + + public String asTagValue() { + return this.name().toLowerCase(Locale.ROOT); + } + public abstract String asMessageDigestAlgorithm(); + + public static Optional of(String methodName){ + return Arrays.stream(values()) + .filter(m-> m.name().toLowerCase(Locale.ROOT).equals(methodName.toLowerCase(Locale.ROOT))) + .findFirst(); + } +} \ No newline at end of file diff --git a/main/src/main/java/org/apache/james/jdkim/api/SigningAlgorithm.java b/main/src/main/java/org/apache/james/jdkim/api/SigningAlgorithm.java new file mode 100644 index 0000000..7de80f0 --- /dev/null +++ b/main/src/main/java/org/apache/james/jdkim/api/SigningAlgorithm.java @@ -0,0 +1,46 @@ +/****************************************************************************** + * 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.api; + +import java.util.Arrays; +import java.util.Locale; +import java.util.Optional; + +public enum SigningAlgorithm { + RSA { + @Override + public String asJdkSignatureAlgorithm(HashMethod hashMethod) { + return hashMethod.name() + "with" + name(); + } + }, // RFC 6376 https://datatracker.ietf.org/doc/html/rfc6376#section-3.3 + ; + + public String asTagValue() { + return this.name().toLowerCase(Locale.US); + } + + public abstract String asJdkSignatureAlgorithm(HashMethod hashMethod); + + public static Optional of(String signingAlgorithm) { + return Arrays.stream(SigningAlgorithm.values()) + .filter(a -> a.name().toLowerCase(Locale.ROOT).equals(signingAlgorithm.toLowerCase(Locale.ROOT))) + .findFirst(); + } +} diff --git a/main/src/main/java/org/apache/james/jdkim/impl/BodyHasherImpl.java b/main/src/main/java/org/apache/james/jdkim/impl/BodyHasherImpl.java index a788e32..eb69126 100644 --- a/main/src/main/java/org/apache/james/jdkim/impl/BodyHasherImpl.java +++ b/main/src/main/java/org/apache/james/jdkim/impl/BodyHasherImpl.java @@ -47,12 +47,6 @@ public BodyHasherImpl(SignatureRecord sign) throws PermFailException { throw new PermFailException("Unsupported algorythm: " + sign.getHashAlgo(), sign, e); } - - try { - sign.validate(); - } catch (IllegalStateException e) { - throw new PermFailException("Invalid signature template", sign, e); - } int limit = sign.getBodyHashLimit(); 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 d0373e7..dfa3e1c 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 @@ -36,6 +36,7 @@ public class SignatureRecordImpl extends TagValue implements SignatureRecord { public SignatureRecordImpl(String data) { super(data); + validate(); } protected void init() { 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 new file mode 100644 index 0000000..186c3c8 --- /dev/null +++ b/main/src/main/java/org/apache/james/jdkim/tagvalue/SignatureRecordTemplate.java @@ -0,0 +1,318 @@ +/**************************************************************** + * 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.tagvalue; + +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.regex.Pattern; + +import org.apache.commons.codec.binary.Base64; +import org.apache.james.jdkim.api.HashMethod; +import org.apache.james.jdkim.api.SignatureRecord; +import org.apache.james.jdkim.api.SigningAlgorithm; +import org.apache.james.jdkim.parser.DKIMQuotedPrintable; + +public class SignatureRecordTemplate extends TagValue implements SignatureRecord { + + // TODO ftext is defined as a sequence of at least one in %d33-57 or + // %d59-126 + private static final Pattern hdrNamePattern = Pattern.compile("^[^: \r\n\t]+$"); + + public SignatureRecordTemplate(String data) { + super(data); + validate(); + Set tags = getTags(); + defaults.forEach((key, value) -> { + if (!tags.contains(key) && !"l".equals(key)) { + setValue(key, value.toString()); + } + } + ); + } + + protected void init() { + mandatoryTags.add("v"); + mandatoryTags.add("d"); + mandatoryTags.add("h"); + mandatoryTags.add("s"); + + defaults.put("b", ""); + defaults.put("bh", ""); + defaults.put("c", SIMPLE + "/" + SIMPLE); + defaults.put("l", ALL); + defaults.put("q", "dns/txt"); + } + + /** + * @see SignatureRecord#validate() + */ + public void validate() throws IllegalStateException { + super.validate(); + // TODO: what about v=0.5 and no v= at all? + // do specs allow parsing? what should we check? + if (!"1".equals(getValue("v"))) + throw new IllegalStateException( + "Invalid DKIM-Signature version (expected '1'): " + + getValue("v")); + if (getValue("h").length() == 0) + throw new IllegalStateException("Tag h= cannot be empty."); + + CharSequence identity; + try { + identity = getIdentity(); + } catch (IllegalArgumentException e) { + 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"); + + // when "x=" exists and signature expired then return PERMFAIL + // (signature expired) + if (getValue("x") != null) { + long expiration = Long.parseLong(getValue("x").toString()); + long lifetime = (expiration - System.currentTimeMillis() / 1000); + if (lifetime < 0) { + throw new IllegalStateException("Signature is expired since " + + getTimeMeasure(lifetime) + "."); + } + } + + // when "h=" does not contain "from" return PERMFAIL (From field not + // signed). + if (!isInListCaseInsensitive("from", getHeaders())) + throw new IllegalStateException("From field not signed"); + // TODO support ignoring signature for certain d values (externally to + // this class). + } + + private String getTimeMeasure(long lifetime) { + String measure = "s"; + lifetime = -lifetime; + if (lifetime > 600) { + lifetime = lifetime / 60; + measure = "m"; + if (lifetime > 600) { + lifetime = lifetime / 60; + measure = "h"; + if (lifetime > 120) { + lifetime = lifetime / 24; + measure = "d"; + if (lifetime > 90) { + lifetime = lifetime / 30; + measure = " months"; + if (lifetime > 24) { + lifetime = lifetime / 12; + measure = " years"; + } + } + } + } + } + return lifetime + measure; + } + + /** + * @see SignatureRecord#getHeaders() + */ + public List getHeaders() { + return stringToColonSeparatedList(getValue("h").toString(), + hdrNamePattern); + } + + // If i= is unspecified the default is @d + protected CharSequence getDefault(String tag) { + if ("i".equals(tag)) { + return "@" + getValue("d"); + } else + return super.getDefault(tag); + } + + /** + * @see SignatureRecord#getIdentityLocalPart() + */ + public CharSequence getIdentityLocalPart() { + String identity = getIdentity().toString(); + int pAt = identity.indexOf('@'); + return identity.subSequence(0, pAt); + } + + /** + * This may throws IllegalArgumentException on invalid "i" content, + * but should always happen during validation! + * + * @see SignatureRecord#getIdentity() + */ + public CharSequence getIdentity() { + return DKIMQuotedPrintable.dkimQuotedPrintableDecode(getValue("i")); + } + + /** + * @see SignatureRecord#getHashKeyType() + */ + public CharSequence getHashKeyType() { + String a = getValue("a").toString(); + int pHyphen = a.indexOf('-'); + // TODO x-sig-a-tag-h = ALPHA *(ALPHA / DIGIT) + if (pHyphen == -1) + throw new IllegalStateException( + "Invalid hash algorythm (key type): " + a); + return a.subSequence(0, pHyphen); + } + + /** + * @see SignatureRecord#getHashMethod() + */ + public CharSequence getHashMethod() { + String a = getValue("a").toString(); + int pHyphen = a.indexOf('-'); + // TODO x-sig-a-tag-h = ALPHA *(ALPHA / DIGIT) + if (pHyphen == -1) + throw new IllegalStateException("Invalid hash method: " + a); + return a.subSequence(pHyphen + 1, a.length()); + } + + /** + * @see SignatureRecord#getHashAlgo() + */ + public CharSequence getHashAlgo() { + String a = getValue("a").toString(); + int pHyphen = a.indexOf('-'); + if (pHyphen == -1) + throw new IllegalStateException("Invalid hash method: " + a); + if (a.length() > pHyphen + 3 && a.charAt(pHyphen + 1) == 's' + && a.charAt(pHyphen + 2) == 'h' && a.charAt(pHyphen + 3) == 'a') { + return "sha-" + a.subSequence(pHyphen + 4, a.length()); + } else + return a.subSequence(pHyphen + 1, a.length()); + } + + /** + * @see SignatureRecord#getSelector() + */ + public CharSequence getSelector() { + return getValue("s"); + } + + /** + * @see SignatureRecord#getDToken() + */ + public CharSequence getDToken() { + return getValue("d"); + } + + public byte[] getBodyHash() { + return Base64.decodeBase64(getValue("bh").toString().getBytes()); + } + + public byte[] getSignature() { + return Base64.decodeBase64(getValue("b").toString().getBytes()); + } + + public CharSequence getRawSignature() { + return getValue("b"); + } + + public int getBodyHashLimit() { + String limit = getValue("l").toString(); + if (ALL.equals(limit)) + return -1; + else + return Integer.parseInt(limit); + } + + public Long getSignatureTimestamp() { + CharSequence cs = getValue("t"); + if (cs == null) return null; + return Long.parseLong(cs.toString()); + } + + public String getBodyCanonicalisationMethod() { + String c = getValue("c").toString(); + int pSlash = c.indexOf("/"); + if (pSlash != -1) { + return c.substring(pSlash + 1); + } else { + return SIMPLE; + } + } + + public String getHeaderCanonicalisationMethod() { + String c = getValue("c").toString(); + int pSlash = c.indexOf("/"); + if (pSlash != -1) { + return c.substring(0, pSlash); + } else { + return c; + } + } + + public List getRecordLookupMethods() { + String flags = getValue("q").toString(); + String[] flagsStrings = flags.split(":"); + List res = new LinkedList<>(); + for (String flagsString : flagsStrings) { + // TODO add validation method[/option] + // if (VALIDATION) + res.add(trimFWS(flagsString, 0, flagsString.length() - 1, + true)); + } + return res; + } + + public void setSignature(byte[] newSignature) { + String signature = new String(Base64.encodeBase64(newSignature)); + setValue("b", signature); + } + + public void setBodyHash(byte[] newBodyHash) { + String bodyHash = new String(Base64.encodeBase64(newBodyHash)); + setValue("bh", bodyHash); + // If a t=; parameter is present in the signature, make sure to + // fill it with the current timestamp + if (getValue("t") != null && getValue("t").toString().trim().isEmpty()) { + setValue("t", "" + (System.currentTimeMillis() / 1000)); + } + } + + public SignatureRecordImpl toSignatureRecord(SigningAlgorithm algorithm, HashMethod hashMethod, byte[] bodyHash, byte[] signature) { + setValue("a", algorithm.asTagValue() + "-" + hashMethod.asTagValue()); + + String bodyHashTagValue = new String(Base64.encodeBase64(bodyHash)); + setValue("bh", bodyHashTagValue); + + String signatureTagValue = new String(Base64.encodeBase64(signature)); + setValue("b", signatureTagValue); + // If a t=; parameter is present in the signature, make sure to + // fill it with the current timestamp + if (getValue("t") != null && getValue("t").toString().trim().isEmpty()) { + setValue("t", "" + (System.currentTimeMillis() / 1000)); + } + return new SignatureRecordImpl(this.toString()); + } + + public String toUnsignedString() { + return toString().replaceFirst("b=[^;]*", "b="); + } +} 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 7bbd85e..d31ef44 100644 --- a/main/src/test/java/org/apache/james/jdkim/SignatureRecordImplTest.java +++ b/main/src/test/java/org/apache/james/jdkim/SignatureRecordImplTest.java @@ -34,8 +34,7 @@ public void testBasic() { + " d=gmail.com; s=beta;\r\n" + " h=domainkey-signature:received:received:message-id:date:from:to:subject:mime-version:content-type;\r\n" + " bh=9sd6eO/xnGLInYGPFN86r9q27iClGpwfkl4PBc5XEuQ=;\r\n" - + " b=tGQtBQg1sO+JKopOylApWLngylEqeMcXwCEUQN+S2PSpi9c1G9Nm5df9pMShus3iFaQb0PPvTfpw++cAC8/N0p3Gi/lVLc+Yh7xWEIPZ3Nxd3xqTQy7grIkBpV0q6559dEhhfFoEyLS0OK/IrqFIUVDRIMnsMjimXV7u+Sgoi7Q=") - .validate(); + + " b=tGQtBQg1sO+JKopOylApWLngylEqeMcXwCEUQN+S2PSpi9c1G9Nm5df9pMShus3iFaQb0PPvTfpw++cAC8/N0p3Gi/lVLc+Yh7xWEIPZ3Nxd3xqTQy7grIkBpV0q6559dEhhfFoEyLS0OK/IrqFIUVDRIMnsMjimXV7u+Sgoi7Q="); } @@ -47,7 +46,6 @@ public void testWrongOrMissingVersion() { + " d=gmail.com; s=beta;\r\n" + " h=domainkey-signature:received:received:message-id:date:from:to:subject:mime-version:content-type;\r\n" + " b=Kw/TqnjB4L5ZC7DX1ibiNkuIw630uHZvzuozn/e6yTm3U8ObWEz/rJK5GO8RSrF56JrCA/xo8W2CGmyNmpQYbEpLl5P9/NcJSYUmln/O6GSa4Usyv4FdEU4FVjkyW0ToGFHNkw9Mm0urveA4Lcfk9gClJczXnvGBdiv/bkVBEJk=") - .validate() ).isInstanceOf(IllegalStateException.class) .hasMessageContaining("mandatory") .hasMessageContaining("v"); @@ -58,7 +56,6 @@ public void testWrongOrMissingVersion() { + " h=domainkey-signature:received:received:message-id:date:from:to:subject:mime-version:content-type;\r\n" + " bh=9sd6eO/xnGLInYGPFN86r9q27iClGpwfkl4PBc5XEuQ=;\r\n" + " b=tGQtBQg1sO+JKopOylApWLngylEqeMcXwCEUQN+S2PSpi9c1G9Nm5df9pMShus3iFaQb0PPvTfpw++cAC8/N0p3Gi/lVLc+Yh7xWEIPZ3Nxd3xqTQy7grIkBpV0q6559dEhhfFoEyLS0OK/IrqFIUVDRIMnsMjimXV7u+Sgoi7Q=") - .validate() ).isInstanceOf(IllegalStateException.class) .hasMessageContaining("Invalid") .hasMessageContaining("version") @@ -73,7 +70,6 @@ public void testMissingRequired() { + " d=gmail.com; s=beta;\r\n" + " h=domainkey-signature:received:received:message-id:date:from:to:subject:mime-version:content-type;\r\n" + " b=tGQtBQg1sO+JKopOylApWLngylEqeMcXwCEUQN+S2PSpi9c1G9Nm5df9pMShus3iFaQb0PPvTfpw++cAC8/N0p3Gi/lVLc+Yh7xWEIPZ3Nxd3xqTQy7grIkBpV0q6559dEhhfFoEyLS0OK/IrqFIUVDRIMnsMjimXV7u+Sgoi7Q=") - .validate() ).isInstanceOf(IllegalStateException.class) .hasMessageContaining("mandatory") .hasMessageContaining("bh"); @@ -88,7 +84,6 @@ public void testDomainMismatch() { + " h=domainkey-signature:received:received:message-id:date:from:to:subject:mime-version:content-type;\r\n" + " bh=9sd6eO/xnGLInYGPFN86r9q27iClGpwfkl4PBc5XEuQ=;\r\n" + " b=tGQtBQg1sO+JKopOylApWLngylEqeMcXwCEUQN+S2PSpi9c1G9Nm5df9pMShus3iFaQb0PPvTfpw++cAC8/N0p3Gi/lVLc+Yh7xWEIPZ3Nxd3xqTQy7grIkBpV0q6559dEhhfFoEyLS0OK/IrqFIUVDRIMnsMjimXV7u+Sgoi7Q=") - .validate() ).isInstanceOf(IllegalStateException.class) .hasMessageContaining("domain mismatch"); } @@ -102,7 +97,6 @@ public void testMissingFrom() { + " h=domainkey-signature:received:received:message-id:date:fram:to:subject:mime-version:content-type;\r\n" + " bh=9sd6eO/xnGLInYGPFN86r9q27iClGpwfkl4PBc5XEuQ=;\r\n" + " b=tGQtBQg1sO+JKopOylApWLngylEqeMcXwCEUQN+S2PSpi9c1G9Nm5df9pMShus3iFaQb0PPvTfpw++cAC8/N0p3Gi/lVLc+Yh7xWEIPZ3Nxd3xqTQy7grIkBpV0q6559dEhhfFoEyLS0OK/IrqFIUVDRIMnsMjimXV7u+Sgoi7Q=") - .validate() ).isInstanceOf(IllegalStateException.class) .hasMessage("From field not signed"); } @@ -134,7 +128,7 @@ public void testExpired() { assertThatThrownBy(() -> new SignatureRecordImpl( "v=1; c=simple; h=from:to; s=select; d=example.com; a=rsa-sha1; x=0; bh=abcdef; b=1235345987;" - ).validate() + ) ).hasMessageContaining("expired"); } diff --git a/main/src/test/java/org/apache/james/jdkim/SignatureRecordTemplateTest.java b/main/src/test/java/org/apache/james/jdkim/SignatureRecordTemplateTest.java new file mode 100644 index 0000000..18ba729 --- /dev/null +++ b/main/src/test/java/org/apache/james/jdkim/SignatureRecordTemplateTest.java @@ -0,0 +1,162 @@ +/**************************************************************** + * 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; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.nio.charset.StandardCharsets; + +import org.apache.james.jdkim.api.HashMethod; +import org.apache.james.jdkim.api.SignatureRecord; +import org.apache.james.jdkim.api.SigningAlgorithm; +import org.apache.james.jdkim.tagvalue.SignatureRecordImpl; +import org.apache.james.jdkim.tagvalue.SignatureRecordTemplate; +import org.junit.Test; + +public class SignatureRecordTemplateTest { + + // TODO check bytes > 128 + /* + * public void test8bit() { + * assertEquals("PROVA\144CIAO\144",Main.dkimQuotedPrintableDecode("PROVA=90CIAO=90")); } + */ + @Test + public void testBasic() { + new SignatureRecordTemplate( + "v=1; a=rsa-sha256; c=relaxed/relaxed;\r\n" + + " d=gmail.com; s=beta;\r\n" + + " h=domainkey-signature:received:received:message-id:date:from:to:subject:mime-version:content-type;\r\n" + + " bh=9sd6eO/xnGLInYGPFN86r9q27iClGpwfkl4PBc5XEuQ=;\r\n" + + " b=tGQtBQg1sO+JKopOylApWLngylEqeMcXwCEUQN+S2PSpi9c1G9Nm5df9pMShus3iFaQb0PPvTfpw++cAC8/N0p3Gi/lVLc+Yh7xWEIPZ3Nxd3xqTQy7grIkBpV0q6559dEhhfFoEyLS0OK/IrqFIUVDRIMnsMjimXV7u+Sgoi7Q="); + } + + @Test + public void testWrongOrMissingVersion() { + assertThatThrownBy(() -> + new SignatureRecordTemplate( + "a=rsa-sha1; c=relaxed/relaxed;\r\n" + + " d=gmail.com; s=beta;\r\n" + + " h=domainkey-signature:received:received:message-id:date:from:to:subject:mime-version:content-type;\r\n" + + " b=Kw/TqnjB4L5ZC7DX1ibiNkuIw630uHZvzuozn/e6yTm3U8ObWEz/rJK5GO8RSrF56JrCA/xo8W2CGmyNmpQYbEpLl5P9/NcJSYUmln/O6GSa4Usyv4FdEU4FVjkyW0ToGFHNkw9Mm0urveA4Lcfk9gClJczXnvGBdiv/bkVBEJk=") + ).isInstanceOf(IllegalStateException.class) + .hasMessageContaining("mandatory") + .hasMessageContaining("v"); + assertThatThrownBy(() -> + new SignatureRecordTemplate( + "v=2; a=rsa-sha256; c=relaxed/relaxed;\r\n" + + " d=gmail.com; s=beta;\r\n" + + " h=domainkey-signature:received:received:message-id:date:from:to:subject:mime-version:content-type;\r\n" + + " bh=9sd6eO/xnGLInYGPFN86r9q27iClGpwfkl4PBc5XEuQ=;\r\n" + + " b=tGQtBQg1sO+JKopOylApWLngylEqeMcXwCEUQN+S2PSpi9c1G9Nm5df9pMShus3iFaQb0PPvTfpw++cAC8/N0p3Gi/lVLc+Yh7xWEIPZ3Nxd3xqTQy7grIkBpV0q6559dEhhfFoEyLS0OK/IrqFIUVDRIMnsMjimXV7u+Sgoi7Q=") + ).isInstanceOf(IllegalStateException.class) + .hasMessageContaining("Invalid") + .hasMessageContaining("version") + .hasMessageContaining("2"); + } + + @Test + public void testMissingRequired() { + assertThatThrownBy(() -> + new SignatureRecordTemplate( + "v=1; \r\n" + + " s=beta;\r\n" + + " h=domainkey-signature:received:received:message-id:date:from:to:subject:mime-version:content-type;\r\n" + ) + ).isInstanceOf(IllegalStateException.class); + } + + @Test + public void testDomainMismatch() { + assertThatThrownBy(() -> + new SignatureRecordTemplate( + "v=1; a=rsa-sha256; c=relaxed/relaxed;\r\n" + + " d=gmail.com; s=beta; i=@agmail.com;\r\n" + + " h=domainkey-signature:received:received:message-id:date:from:to:subject:mime-version:content-type;\r\n" + + " bh=9sd6eO/xnGLInYGPFN86r9q27iClGpwfkl4PBc5XEuQ=;\r\n" + + " b=tGQtBQg1sO+JKopOylApWLngylEqeMcXwCEUQN+S2PSpi9c1G9Nm5df9pMShus3iFaQb0PPvTfpw++cAC8/N0p3Gi/lVLc+Yh7xWEIPZ3Nxd3xqTQy7grIkBpV0q6559dEhhfFoEyLS0OK/IrqFIUVDRIMnsMjimXV7u+Sgoi7Q=") + + ).isInstanceOf(IllegalStateException.class); + } + + @Test + public void testMissingFrom() { + assertThatThrownBy(() -> + new SignatureRecordTemplate( + "v=1; a=rsa-sha256; c=relaxed/relaxed;\r\n" + + " d=gmail.com; s=beta; i=@subdomain.gmail.com;\r\n" + + " h=domainkey-signature:received:received:message-id:date:fram:to:subject:mime-version:content-type;\r\n" + + " bh=9sd6eO/xnGLInYGPFN86r9q27iClGpwfkl4PBc5XEuQ=;\r\n" + + " b=tGQtBQg1sO+JKopOylApWLngylEqeMcXwCEUQN+S2PSpi9c1G9Nm5df9pMShus3iFaQb0PPvTfpw++cAC8/N0p3Gi/lVLc+Yh7xWEIPZ3Nxd3xqTQy7grIkBpV0q6559dEhhfFoEyLS0OK/IrqFIUVDRIMnsMjimXV7u+Sgoi7Q=") + ).isInstanceOf(IllegalStateException.class) + .hasMessage("From field not signed"); + } + + /* + * when we moved from Sun's Base64 to CommonsCodec the decoding changed + * behaviour. it does no more fail on bad encoded data. public void + * testWrongBase64Encoding() { SignatureRecord sr = new + * SignatureRecordTemplate("v=1; bh=0012=GG; b==GG;"); try { sr.getBodyHash(); + * fail("expected failure"); } catch (Exception e) { + * assertTrue(e.getMessage().toLowerCase().contains("decod")); } try { + * sr.getSignature(); fail("expected failure"); } catch (Exception e) { + * assertTrue(e.getMessage().toLowerCase().contains("decod")); } } + */ + + @Test + public void testWrongHashSyntaxes() { + SignatureRecord sr = new SignatureRecordTemplate("v=1; a=nothyphenedword; bh=abcdef; b=1235345987; h=from:to; s=select; d=example.com"); + + assertThatThrownBy(sr::getHashAlgo) + .hasMessageContaining("hash"); + + assertThatThrownBy(sr::getHashMethod) + .hasMessageContaining("hash"); + } + + @Test + public void testExpired() { + assertThatThrownBy(() -> + new SignatureRecordTemplate( + "v=1; c=simple; h=from:to; s=select; d=example.com; a=rsa-sha1; x=0; bh=abcdef; b=1235345987;" + ) + ).hasMessageContaining("expired"); + } + + @Test + public void should_return_a_full_signature_record_when_provided_the_signature() { + SignatureRecordTemplate template = new SignatureRecordTemplate( + "v=1; c=simple; h=from:to; s=select; d=example.com;" + ); + + byte[] bodyHash = "bodyHash".getBytes(StandardCharsets.UTF_8); + byte[] signature = "signature".getBytes(StandardCharsets.UTF_8); + SignatureRecordImpl signatureRecord = template.toSignatureRecord( + SigningAlgorithm.RSA, + HashMethod.SHA256, + bodyHash, + signature + ); + + + assertThat(signatureRecord.getBodyHash()).isEqualTo(bodyHash); + assertThat(signatureRecord.getSignature()).isEqualTo(signature); + } +} From a562f92cfbc6fed2eea002918e4d2e32950063d2 Mon Sep 17 00:00:00 2001 From: Jean Helou Date: Wed, 9 Apr 2025 22:52:01 +0200 Subject: [PATCH 065/114] [devscout] documents proper JDKIM API usage --- README.adoc | 88 +++++++++ README.txt | 36 ---- RELEASE_NOTES.txt => RELEASE_NOTES.adoc | 28 ++- .../java/org/apache/james/jdkim/DKIMTest.java | 186 ++++++++++++++++++ .../jdkim/MockPublicKeyRecordRetriever.java | 42 +++- .../java/org/apache/james/jdkim/TestKeys.java | 83 ++++++++ .../org/apache/james/jdkim/keys/private.2.key | 28 +++ .../org/apache/james/jdkim/keys/private.key | 28 +++ .../org/apache/james/jdkim/keys/public.2.pem | 9 + .../org/apache/james/jdkim/keys/public.pem | 9 + 10 files changed, 489 insertions(+), 48 deletions(-) create mode 100644 README.adoc delete mode 100644 README.txt rename RELEASE_NOTES.txt => RELEASE_NOTES.adoc (61%) create mode 100644 main/src/test/java/org/apache/james/jdkim/DKIMTest.java create mode 100644 main/src/test/java/org/apache/james/jdkim/TestKeys.java create mode 100644 main/src/test/resources/org/apache/james/jdkim/keys/private.2.key create mode 100644 main/src/test/resources/org/apache/james/jdkim/keys/private.key create mode 100644 main/src/test/resources/org/apache/james/jdkim/keys/public.2.pem create mode 100644 main/src/test/resources/org/apache/james/jdkim/keys/public.pem diff --git a/README.adoc b/README.adoc new file mode 100644 index 0000000..64e0546 --- /dev/null +++ b/README.adoc @@ -0,0 +1,88 @@ += JAMES jDKIM library + + +Library dealing with parsing and crytography to sign and verify DKIM signatures. + +The mailet has been moved to James project: https://github.com/apache/james-project/tree/master/server/mailet/dkim + +== Usage + +A full example is available in +https://github.com/apache/james-jdkim/blob/master/main/src/test/java/org/apache/james/jdkim/DKIMTest.java[DKIMTest] + +=== Signing + +Signing a mime message can be achieved using the following snippet + +[source,java] +---- +import java.io.InputStream; +import java.security.PrivateKey; + +String signatureTemplate = "v=1; a=rsa-sha256; c=simple; d=messiah.edu; h=date:from:subject; q=dns/txt; s=selector2;"; + +PrivateKey privateKey = null; +DKIMSigner dkimSigner = new DKIMSigner(signatureTemplate, privateKey); +// You need to provide the input stream of the mime message, it will be parsed +// by mime4j +InputStream stream = null; +String signature = dkimSigner.sign(inputStream); +// `signature` contains the full header +// DKIM-Signature: a=rsa-sha256; q=dns/txt; b=Axa8s/g...U1SIw==; c=simple; s=selector2; d=messiah.edu; v=1; bh=6pQ...6g=; h=date:from:subject; +---- + +More advanced usage such as including multiple signatures can be found in +https://github.com/apache/james-jdkim/blob/master/main/src/test/java/org/apache/james/jdkim/DKIMTest.java[DKIMTest] + +=== Verifying + +Verifying a mime message DKIM signatures can be achieved using the following +snippet. The verifier always verifies all the signatures. + +[source,java] +---- +import java.io.InputStream; +// You can override the resolver in the constructor, use your own +// implementation of a retriever or use multiple implementations using a +// `MultiplexingPublicKeyRecordRetriever` +PublicKeyRecordRetriever keyRecordRetriever = new DNSPublicKeyRecordRetriever(); +DKIMVerifier verifier = new DKIMVerifier(keyRecordRetriever); +InputStream stream = null; // you need to provide the input stream of the mime message +List verifiedSignatures = verifier.verify(stream); +// `verifiedSignatures` contains only the signatures that have successfully +// passed the validation. +// If you want to query all the results including all the failures, you can +// retrieve them from the verifier +List results = verifier.getResults(); +---- + +== Cryptography Notice + +---- + This distribution includes cryptographic software. The country in + which you currently reside may have restrictions on the import, + possession, use, and/or re-export to another country, of + encryption software. BEFORE using any encryption software, please + check your country's laws, regulations and policies concerning the + import, possession, or use, and re-export of encryption software, to + see if this is permitted. See http://www.wassenaar.org for more + information. + + The U.S. Government Department of Commerce, Bureau of Industry and + Security (BIS), has classified this software as Export Commodity + Control Number (ECCN) 5D002.C.1, which includes information security + software using or performing cryptographic functions with asymmetric + algorithms. The form and manner of this Apache Software Foundation + distribution makes it eligible for export under the License Exception + ENC Technology Software Unrestricted (TSU) exception (see the BIS + Export Administration Regulations, Section 740.13) for both object + code and source code. + + The following provides more details on the included cryptographic + software: + + - jDKIM includes code designed to work with Java SE Security + + Export classifications and source links can be found + at http://www.apache.org/licenses/exports/. +---- \ No newline at end of file diff --git a/README.txt b/README.txt deleted file mode 100644 index 28b05c9..0000000 --- a/README.txt +++ /dev/null @@ -1,36 +0,0 @@ -JAMES jDKIM library -------------------- - -Contains library dealing with crytography. - -The mailet has been moved to James project: https://github.com/apache/james-project/tree/master/server/mailet/dkim - -Cryptography Notice -------------------- - - This distribution includes cryptographic software. The country in - which you currently reside may have restrictions on the import, - possession, use, and/or re-export to another country, of - encryption software. BEFORE using any encryption software, please - check your country's laws, regulations and policies concerning the - import, possession, or use, and re-export of encryption software, to - see if this is permitted. See http://www.wassenaar.org for more - information. - - The U.S. Government Department of Commerce, Bureau of Industry and - Security (BIS), has classified this software as Export Commodity - Control Number (ECCN) 5D002.C.1, which includes information security - software using or performing cryptographic functions with asymmetric - algorithms. The form and manner of this Apache Software Foundation - distribution makes it eligible for export under the License Exception - ENC Technology Software Unrestricted (TSU) exception (see the BIS - Export Administration Regulations, Section 740.13) for both object - code and source code. - - The following provides more details on the included cryptographic - software: - - - jDKIM includes code designed to work with Java SE Security - - Export classifications and source links can be found - at http://www.apache.org/licenses/exports/. diff --git a/RELEASE_NOTES.txt b/RELEASE_NOTES.adoc similarity index 61% rename from RELEASE_NOTES.txt rename to RELEASE_NOTES.adoc index 6a657bf..093706d 100644 --- a/RELEASE_NOTES.txt +++ b/RELEASE_NOTES.adoc @@ -1,5 +1,25 @@ -Release 0.2 -------------------- += jDKIM Release notes + +== Release 0.5 + +Api has been refined and documented a bit but is still not considered mature and will change in future releases. + +=== Notable changes since 0.2 + + * Mailet has been extracted back to james project + * jDKIM API is still considered unstable and is likely to change in future releases + * Default parsing is done using mime4j 0.8.12 parser. + +=== Breaking api changes: + - MultiplexingPublicKeyRecordRetriever can no longer be instantiated without any methods + - MultiplexingPublicKeyRecordRetriever#addRetriever is made private + - `dkimQuotedPrintableDecode` was moved from `SignatureRecordImpl` to `org.apache.james.jdkim.parser.DKIMQuotedPrintable` + since it is not specific to SignatureRecordImpl. + +While technically a public API I don't expect many users of the lib to call this directly. +Updating the static import should be acceptable for the few who do. + +== Release 0.2 jDKIM is a DKIM implementation library written in Java. It provides both verification and signing and also provides Mailets for the Apache JAMES project. @@ -19,9 +39,7 @@ implementation simply prepare the OutputStream and copy the supplied InputStream In order to reduce depencencies and to not reinvent the wheel the resulting library includes a "shaded" version of the commons-codec 1.4 classes needed to do Base64 encoding/decoding. -Notes ------ - +=== Notes * jDKIM API is still considered unstable and is likely to change in future releases * Default parsing is done using mime4j 0.7 parser. * The mailet does rely on javamail parsing instead of mime4j. diff --git a/main/src/test/java/org/apache/james/jdkim/DKIMTest.java b/main/src/test/java/org/apache/james/jdkim/DKIMTest.java new file mode 100644 index 0000000..681406a --- /dev/null +++ b/main/src/test/java/org/apache/james/jdkim/DKIMTest.java @@ -0,0 +1,186 @@ +/****************************************************************************** + * 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; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.SequenceInputStream; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.List; + +import org.apache.commons.codec.binary.Base64; +import org.apache.james.jdkim.MockPublicKeyRecordRetriever.Record; +import org.apache.james.jdkim.api.Headers; +import org.apache.james.jdkim.api.Result; +import org.apache.james.jdkim.api.SignatureRecord; +import org.apache.james.jdkim.impl.Message; +import org.junit.Test; + +public class DKIMTest { + private final MockPublicKeyRecordRetriever keyRecordRetriever = new MockPublicKeyRecordRetriever( + Record.of( + "selector2", + "messiah.edu", + "k=rsa; p=" + Base64.encodeBase64String(TestKeys.publicKey.getEncoded()) + ";" + ), + Record.of( + "selector3", + "messiah.edu", + "k=rsa; p=" + Base64.encodeBase64String(TestKeys.publicKey_2.getEncoded()) + ";" + ) + ); + + /** + * - "a" field will be added by the signer based on signer setup + * - "bh=" and "b=" placeholder are required for now because the same implementation is used for + * signing and verifying. The fields are mandatory for verifying. + */ + private static final String SIGNATURE_TEMPLATE = "v=1; a=rsa-sha256; c=simple; d=messiah.edu; h=date:from:subject; q=dns/txt; s=selector2;"; + private static final String SIGNATURE_TEMPLATE_2 = "v=1; a=rsa-sha1; c=simple; d=messiah.edu; h=date:from:subject; q=dns/txt; s=selector2;"; + private static final String SIGNATURE_TEMPLATE_3 = "v=1; a=rsa-sha256; c=simple; d=messiah.edu; h=date:from:subject; q=dns/txt; s=selector3;"; + + private final DKIMSigner dkimSigner = new DKIMSigner(SIGNATURE_TEMPLATE, TestKeys.privateKey); + private final DKIMVerifier verifier = new DKIMVerifier(keyRecordRetriever); + + @Test + public void should_verify_generated_signature_single_key() throws Exception { + String expectedSignature = "DKIM-Signature: a=rsa-sha256; q=dns/txt; b=Axa8s/gTnnJ8em45KV/AQw33hQ4uYtBKiQp3dLq7oRFt+WmDZ5ZErPq4lBVXfP+IAvP+Au91J8270ivn1J/6E0YqKntn4s1hjcNBPPRVohvmlcQ1mEMd6DuYDtWjDFwG2GWZwtilaPY2afhlTuAbHkn8nHm7MVtAGETO8QQ2zfD1NSGzKbNYP9I+hrDJq5ajka6PZn1d+mDhUH5Px8yScYqo5i8Z8GXaejSIu7RsLDuxtOO2cuClRi8MKGxc7MiGndMufXB8xbS1L80IFlyunOVY5eBaqnnhF2YrDDQfZ6DTqorzX6D5dNjpjOG6AbsqkW83Drx0TTV/5M0raU1SIw==; c=simple; s=selector2; d=messiah.edu; v=1; bh=6pQY5V6Dw8mCYWq017gfbpv+x2X4GvOhIIZtKw6iU6g=; h=date:from:subject;"; + + ByteArrayInputStream inputStream = readFileToByteArrayInputStream("/org/apache/james/jdkim/Mail-DKIM/corpus/multiple_2.txt"); + String actualSignature = dkimSigner.sign(inputStream); + + // sanity check as it is very easy to change the bodyhash behaviour and thus break the signature + assertEquals(expectedSignature, actualSignature); + + InputStream originalInputStream = readFileToByteArrayInputStream("/org/apache/james/jdkim/Mail-DKIM/corpus/multiple_2.txt"); + InputStream signatureInputStream = new ByteArrayInputStream((actualSignature + "\r\n").getBytes(StandardCharsets.UTF_8)); + + SequenceInputStream verifyInputStream = new SequenceInputStream(signatureInputStream, originalInputStream); + List verifiedSignatures = verifier.verify(verifyInputStream); + + assertThat(verifiedSignatures) + .hasSize(1) + .allSatisfy(it -> + assertThat("DKIM-Signature:" + it.toString()).isEqualTo(expectedSignature) + ); + + } + + @Test + public void should_verify_generated_signature_multiple_keys() throws Exception { + // You might want to sign with multiple keys to support several hashmethods on the same selector + // or several signing algorithm on the same selector ( ECDSA is a proposed alternative to rsa ) + // or when upgrading keys for any reason to add several signatures using different selectors. + String expectedSignature = "DKIM-Signature: a=rsa-sha256; q=dns/txt; b=Axa8s/gTnnJ8em45KV/AQw33hQ4uYtBKiQp3dLq7oRFt+WmDZ5ZErPq4lBVXfP+IAvP+Au91J8270ivn1J/6E0YqKntn4s1hjcNBPPRVohvmlcQ1mEMd6DuYDtWjDFwG2GWZwtilaPY2afhlTuAbHkn8nHm7MVtAGETO8QQ2zfD1NSGzKbNYP9I+hrDJq5ajka6PZn1d+mDhUH5Px8yScYqo5i8Z8GXaejSIu7RsLDuxtOO2cuClRi8MKGxc7MiGndMufXB8xbS1L80IFlyunOVY5eBaqnnhF2YrDDQfZ6DTqorzX6D5dNjpjOG6AbsqkW83Drx0TTV/5M0raU1SIw==; c=simple; s=selector2; d=messiah.edu; v=1; bh=6pQY5V6Dw8mCYWq017gfbpv+x2X4GvOhIIZtKw6iU6g=; h=date:from:subject;"; + String expectedSignature2 = "DKIM-Signature: a=rsa-sha1; q=dns/txt; b=YiwcfjqM7myZ/OENExlyGVzy+rg/779R6pF7bPl79aL6e7yGYeN0XdLRcJEqhg+/uNFwcC7zrbWUwPBVFpFN8pKdQT7TgTr+ydoN65QiBa/rXH4m8Ga+oKx8652dXAHm9oMvG166VdMRsEKTJq2bFpM9RR4mW0KtHPte2JWiOtCYO6MPTlWA2JnIgQp+3+03rnOcKdQ+sn/bi9OwanwE4jgIBcPeHkHVr1fVsV53nvDlbk1DiDX+uOXvuk6bjPVBN4srZiSIvFKsmco0tGZx8cgs5OKyjmtIWVOvjxgupXWvEJJ1nMi1UQ1AXh6jDqWrMDCioRCMG9TeGy8fjcjcfw==; c=simple; s=selector2; d=messiah.edu; v=1; bh=q6DWKdHUzNbVPt6YBbD1KOai/b8=; h=date:from:subject;"; + String expectedSignature3 = "DKIM-Signature: a=rsa-sha256; q=dns/txt; b=crkBsqVuTJJmjZyNuJtmGXBsHIT7tJq0ONWLvNfO29sl1kNm9UzTZ4mOYR+akNJqonkaFFaVM9MZ/6QUd5NbGaIytxXnxv+NPNu6ZzUunlcRyOPhEQ/znemg3WjibRs24gWubBZZXApkqQ9kFh/atatoaJhTls/lnbP8ZV3XlWVN12UuESU3qdieRvrhKWX5/Od7LqZS04ZTeToAabOtDmm6hYl2R6wxizdHrOkGiNERfbB8Iaws5f3Qnt0S94wQ5FVTPzgRiO9OW8hYbAijS4Bh8/NXV5xauMXjCETfxX3pQYUuxc4QVhnoMbmuqgEulzuJUzapjotLLFxQRaSsjw==; c=simple; s=selector3; d=messiah.edu; v=1; bh=6pQY5V6Dw8mCYWq017gfbpv+x2X4GvOhIIZtKw6iU6g=; h=date:from:subject;"; + + ByteArrayInputStream inputStream1 = readFileToByteArrayInputStream("/org/apache/james/jdkim/Mail-DKIM/corpus/multiple_2.txt"); + String actualSignature1 = dkimSigner.sign(inputStream1); + ByteArrayInputStream inputStream2 = readFileToByteArrayInputStream("/org/apache/james/jdkim/Mail-DKIM/corpus/multiple_2.txt"); + DKIMSigner signer2 = new DKIMSigner(SIGNATURE_TEMPLATE_2, TestKeys.privateKey); + String actualSignature2 = signer2.sign(inputStream2); + ByteArrayInputStream inputStream3 = readFileToByteArrayInputStream("/org/apache/james/jdkim/Mail-DKIM/corpus/multiple_2.txt"); + DKIMSigner signer3 = new DKIMSigner(SIGNATURE_TEMPLATE_3, TestKeys.privateKey_2); + String actualSignature3 = signer3.sign(inputStream3); + + // sanity check as it is very easy to change the bodyhash behaviour and thus break the signature + assertEquals(expectedSignature, actualSignature1); + assertEquals(expectedSignature2, actualSignature2); + assertEquals(expectedSignature3, actualSignature3); + + // prepend signatures to the message as per https://datatracker.ietf.org/doc/html/rfc6376#section-3.5 + InputStream originalInputStream = readFileToByteArrayInputStream("/org/apache/james/jdkim/Mail-DKIM/corpus/multiple_2.txt"); + String signatures = String.join("\r\n", Arrays.asList(actualSignature1, actualSignature2, actualSignature3)); + InputStream signatureInputStream = new ByteArrayInputStream((signatures+"\r\n").getBytes(StandardCharsets.UTF_8)); + + SequenceInputStream verifyInputStream = new SequenceInputStream(signatureInputStream, originalInputStream); + + List verifiedSignatures = verifier.verify(verifyInputStream); + + List results = verifier.getResults(); + assertThat(results) + .filteredOn(Result::isSuccess) + .hasSize(3) + .allSatisfy(it -> + assertThat(it.getRecord().getSelector()).isIn("selector2", "selector3") + ) + ; + assertThat(verifiedSignatures) + .hasSize(3) + .satisfiesOnlyOnce(it -> + assertThat("DKIM-Signature:" + it.toString()).isEqualTo(expectedSignature) + ).satisfiesOnlyOnce(it -> + assertThat("DKIM-Signature:" + it.toString()).isEqualTo(expectedSignature2) + ).satisfiesOnlyOnce(it -> + assertThat("DKIM-Signature:" + it.toString()).isEqualTo(expectedSignature3) + ); + + } + + + private ByteArrayInputStream readFileToByteArrayInputStream(String fileName) throws URISyntaxException, IOException { + URL resource = this.getClass().getResource(fileName); + FileInputStream file = new FileInputStream(new File(resource.toURI())); + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + + DKIMCommon.streamCopy(file, byteArrayOutputStream); + String string = byteArrayOutputStream.toString(); + return new ByteArrayInputStream(string.getBytes(StandardCharsets.UTF_8)); + } + + private static class HeaderSignatureOverride implements Headers { + private final Message headers; + private final List dkimSignatures; + + public HeaderSignatureOverride(Message headers, List signatures) { + this.headers = headers; + this.dkimSignatures = signatures; + } + + @Override + public List getFields() { + return headers.getFields(); + } + + @Override + public List getFields(String name) { + if ("DKIM-Signature".equals(name)) { + return dkimSignatures; + } else { + return headers.getFields(name); + } + } + + public InputStream getBodyInputStream() { + return headers.getBodyInputStream(); + } + } +} \ No newline at end of file diff --git a/main/src/test/java/org/apache/james/jdkim/MockPublicKeyRecordRetriever.java b/main/src/test/java/org/apache/james/jdkim/MockPublicKeyRecordRetriever.java index bb81220..3919523 100644 --- a/main/src/test/java/org/apache/james/jdkim/MockPublicKeyRecordRetriever.java +++ b/main/src/test/java/org/apache/james/jdkim/MockPublicKeyRecordRetriever.java @@ -23,6 +23,7 @@ import org.apache.james.jdkim.exceptions.PermFailException; import org.apache.james.jdkim.exceptions.TempFailException; +import java.util.Arrays; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -33,16 +34,32 @@ * local map. */ public class MockPublicKeyRecordRetriever implements PublicKeyRecordRetriever { + public static class Record { + String selector; + String domain; + String record; + + public Record(String selector, String domain, String record) { + assert (selector != null) : "selector cannot be null"; + assert (domain != null) : "domain cannot be null"; + assert (record != null) : "record cannot be null"; + + this.selector = selector; + this.domain = domain; + this.record = record; + } + + public static Record of(String selector, String domain, String record) { + return new Record(selector, domain, record); + } + } + private static final String _DOMAINKEY = "._domainkey."; private final Map> records = new HashMap>(); - public void addRecord(String selector, String token, String record) { - String key = selector + _DOMAINKEY + token; - List l = records.get(key); - if (l == null) { - l = new LinkedList(); - records.put(key, l); - } + public void addRecord(String selector, String domain, String record) { + String key = selector + _DOMAINKEY + domain; + List l = records.computeIfAbsent(key, k -> new LinkedList<>()); if (record != null) { l.add(record); } @@ -56,6 +73,17 @@ public MockPublicKeyRecordRetriever(String record, CharSequence selector, addRecord(selector.toString(), token.toString(), record); } + public MockPublicKeyRecordRetriever(Record... records) { + Arrays.stream(records).forEach(this::addRecord); + } + + private void addRecord(Record record) { + String key = record.selector + _DOMAINKEY + record.domain; + List l = records.computeIfAbsent(key, k -> new LinkedList<>()); + l.add(record.record); + + } + public List getRecords(CharSequence methodAndOptions, CharSequence selector, CharSequence token) throws TempFailException, PermFailException { diff --git a/main/src/test/java/org/apache/james/jdkim/TestKeys.java b/main/src/test/java/org/apache/james/jdkim/TestKeys.java new file mode 100644 index 0000000..82b1900 --- /dev/null +++ b/main/src/test/java/org/apache/james/jdkim/TestKeys.java @@ -0,0 +1,83 @@ +/****************************************************************************** + * 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; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; + +import org.apache.commons.codec.binary.Base64; + +public class TestKeys { + public static final PrivateKey privateKey = loadPrivateKey("org/apache/james/jdkim/keys/private.key"); + public static final PublicKey publicKey = loadPublicKey("org/apache/james/jdkim/keys/public.pem"); + public static final KeyPair keyPair = new KeyPair(publicKey, privateKey); + + public static final PrivateKey privateKey_2 = loadPrivateKey("org/apache/james/jdkim/keys/private.2.key"); + public static final PublicKey publicKey_2 = loadPublicKey("org/apache/james/jdkim/keys/public.2.pem"); + public static final KeyPair keyPair_2 = new KeyPair(publicKey, privateKey); + + // poor man´s pem loaders, I'm too lazy to pull in bouncy castle + private static PublicKey loadPublicKey(String uri) { + try { + String keyText = readFileContent(uri) + .replace("-----BEGIN PUBLIC KEY-----", "") + .replace("-----END PUBLIC KEY-----", "") + .replaceAll(System.lineSeparator(), ""); + byte[] encoded = Base64.decodeBase64(keyText); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encoded); + return keyFactory.generatePublic(keySpec); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static String readFileContent(String uri) throws URISyntaxException, IOException { + URL resource = TestKeys.class.getClassLoader().getResource(uri); + File file = new File(resource.toURI()); + return new String(Files.readAllBytes(file.toPath()), Charset.defaultCharset()); + } + + private static PrivateKey loadPrivateKey(String uri) { + try { + String keyText = readFileContent(uri) + .replace("-----BEGIN PRIVATE KEY-----", "") + .replace("-----END PRIVATE KEY-----", "") + .replaceAll(System.lineSeparator(), ""); + byte[] encoded = Base64.decodeBase64(keyText); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded); + return keyFactory.generatePrivate(keySpec); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + +} diff --git a/main/src/test/resources/org/apache/james/jdkim/keys/private.2.key b/main/src/test/resources/org/apache/james/jdkim/keys/private.2.key new file mode 100644 index 0000000..306c5a6 --- /dev/null +++ b/main/src/test/resources/org/apache/james/jdkim/keys/private.2.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDKgswXewXYzfcP +VT9kXWJTLv3cPDMu+luCm7VqjXPUT/WKNBMUwTvcefGvcNuUpS9ueLq4rRwu1Hqy +3mtB+vBd50CifaTyF+9haM4FuIqx1TU5FTfL/qEz9qKpDEz3FkTckPmtefeHzwcS +uRsHSUQ0oU6jUJvsRRzmb3TOKkvStZxhePu9uh1IWXpUMjPQGP2zyaIm4Pa1ROdt +cbR4XCl5JYCxuIxHNJuvTRi5Ez24Un1/F4N3i/JqQTbXdWti6tooNnK61QNL5Ue+ +f5IbS4mog0sQkJLYN1GskgNKcAZDoPIE2vQxnkHdwThB4m+Or6ZNiYqEEeMniDrC +MVwYJbeNAgMBAAECggEABcEjmA4l8EBm1kwJKxu7rcvWk6nJMXsUihNNBiQXHl/g +Wm54pneHve3XjPWfny2Pys+yoc2XWWzH/A3+MbfHEMQIRo06HKq5twsFW2rtBxJ6 ++I5G/mpte5LKb/H3RNlcNoFb2Qyi3PPf3ZQzomqRoB/l1Z52gNyjO1RD/ZDSHfPV +bZd/46tABDHBPi/H9C/+IoJ0XaWNScdIiiL69OFPSBEBb39oh2PY9yXzvdBeb1oT +AQc5v65Z8pwWmBBq3uHcTEnT6/a3S8PqyN+ixTbZ3iWPUpedJgsYJ+KoP95dXCjL +2CEBvdsgqxFLlVvVEax+GiNtLSDfb2z5G2kMiwL4OQKBgQDpSHPJMzDd/eutunHb +dglagykjz43vKp1zUza5UV0gw30oTIoRQPMgWsCSNCYTBy69I6NWKNjwLMIWVhjd +/LB93jXGo/i8vwieIKJQu+3ySpynpN3ZhziAq+JZ5af1mpLuctGY5vtwNcNaaGxz +dOtYSQDO37j7KxqZKmuvt0NhMwKBgQDeOzkMRlMADxx/BbbsTqGVPvPTIpXDzGBZ +PRpeyggx0MNk2D6WiU+QgonRm7sThqNhuFmRCepW/URh3RZHcUV9JpLoeZ7iQvU9 +RpRDeTTYDxw7VARJWwGX5tcya/SQUpNkmtiCbvkFaJjSSSCtnF2aGyAKb162WNK7 +zjS3YsQEPwKBgQDn7i2Xmrp56srXBWfPflIcWAyd2uvNHbw+fTwKEPea7GDRoQ8f +ykguIawVb51vC5QPXZA1yBSu7IDmfiDpCyJZcYRbgoh49yDVPbLCu6T9X+rRzdr3 +OzYUz08Se356c3yjbyotb3AKhpk7mFhCjbqyIpqYEtuxc5BVOVSo9XYa+wKBgQDL +dKzl7/xNMYiFQlYWQmbsnJXd1uwGNjZ/fNGHtq+J6/b3fILQjyWox9olbM35Mmqi +LITTpKBGw9i3QBIFlk5oJciZzb8IpwSe9N8IZw9aP3s5VdIW1pq+1zhtkSh2eSM4 +GAvgXnjd2sUOwKSKTX3keLD83Ll5eBh2mHLq8iFDQQKBgQDn1fdjyZ7eiBjglgQf +HeUa4BpK9d8imM6q/4YzfMu7vuzQ/UBsOdldBHEVxwZEzrVW943Rbf5lGzsoMGuu +FL7cgjw/bHM0eHQBnyT+Pwf5LtzvYMfJqznYaaQy9/sVDl9CeiTHS12Ynsyj79pJ +b5p3QdhX8+YF7Y0Paid5h2RDSw== +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/main/src/test/resources/org/apache/james/jdkim/keys/private.key b/main/src/test/resources/org/apache/james/jdkim/keys/private.key new file mode 100644 index 0000000..b124ecf --- /dev/null +++ b/main/src/test/resources/org/apache/james/jdkim/keys/private.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDBdtAJHIoZ4Od3 +j6I9Kg7MMzmZZ/5y+wwhV6l58LGoWL4Q2Gby4wpmpsEMTY+GaE8PXP1JpicY2Mc/ +ZS2D6OpFsP37xcveUxZv50t7KAMh5ZoQCjM0954RELE/YRUBl7JqPwa9iT3fJu/9 +uijNx8Z6u2D4ITeYmhfdrMgJPzTTIU7xfWMWQ0aTZJaK8dijn78OPzgo8hKYOXkW +CFStE5p9b+2rA8vXe2RUp/9M71ohXTqpI3KRQdnrh+ELtvQnlYIS3EbERVmSHLvy +t43rGzFl1mStsvpQmycbQLzPeDqDp9KCE7Vm4Oh1h39oOBfdqdpMeY2BRdp97iXn +bvqW2X8jAgMBAAECggEACZSmMcVRi32JSPD6glfqWq9wu0mcnroS/DW4pVrHmYQn +iXMGCiI3OiUo60COCoRw4dxjiLmrNv5JQ7jxe5ZVv7THpyfA9FYeBtWn9cXh/dQ+ +gGvY5Fcc41B1zqMypJ0MD1nmrg01XK8zdFe7PJGXoS1VXIRcmfiLlQVA9MBGD+yA +zMRYXdjZB1Y3nQUipYaN1L1Hkkoot3a3JkDfACe9XdCzubWLjoxuPy4JERvsRNlQ +TXWmYz63tHnnQ4k0z2Ff5F08ny/wsoGy3IkcLVXjg6H5LdK89q85EFBSuJSGl/31 +j/c0ZRIfYTttTof9fRNuzlYdrdyLpBf1FkI2DFdh3QKBgQDk+d338HUKycbUh9E+ +Gz4ycyYBIbxm0JwHgiOfdQLOQbaMWocPtdyerQQjsEP9GF+yyoOgIArxxJ1F7KwE +BnoBdPU/X3ui58iph5d5N6U+n5uOM3zZ35+OXPyy4yXagnPOzrqJGPLFbHHq82el +HACqoynvyiYdqvFjxRCJWQYtzQKBgQDYTALfrOhQC3XZa58mpclxhwPkrHEa/RmW +48GWQdPcyx6oJ30RoNAMPgn8af2krLxJTCaZEQOPmH4rKHiXWESx5aHahJIxNQtb +PoWUpWpJQ0rnh/t1PeTwX9FYxQ9u1Rsfv29jazFYXmgVbQVz+VDaBIAqHukJAoD7 +2zJxg3DwrwKBgB/zgfbMFfj4aQdVIGyTLp2plb8IrxqFt5AQp9njYQ3Y+5kgNMKL +dQqrR+2W9hXGJexz9+QLNJvB/NQ6D1LMeI/leydpsn3r7ANECW3/xs4zRWiVZLGe +U8xwy05HBgw0seZE0KC6232TAV5wsqDGhI1LEVoFkkYHiYGLiSAxTPxtAoGAaf61 +2c7t+WvkUKvAzjuoQA4eBk3LD6LAi4UJNQ0FOcoAN/9B9wWLbrRoSF3Ygn5ztqeu +sov35sKf8Z5fmisTXupdKqyUx7Bq5Ef7RiDZuRVU+6lTvNyb+H+8I3hOW83rAN/b +QikhgWCiOP24EmE3Ed/qvrlvEY88ji+YxK/ZBNkCgYEAjGn8HmI1XnamgsiW4VIb +n/oH396DgsKiBNFFoVqdNSVU5reORbUhC1os8KYVOVsFp7h8pofuz+2VIMr1k3Qb +lVmODd7ok/A4d4kE6lSv1/rKEsFoBrNXaIwe4hrZdXkcLHJpy0kRnGy9AAunROW5 +xcDjmjDRSL0uGvilfi4LZfw= +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/main/src/test/resources/org/apache/james/jdkim/keys/public.2.pem b/main/src/test/resources/org/apache/james/jdkim/keys/public.2.pem new file mode 100644 index 0000000..734f49f --- /dev/null +++ b/main/src/test/resources/org/apache/james/jdkim/keys/public.2.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyoLMF3sF2M33D1U/ZF1i +Uy793DwzLvpbgpu1ao1z1E/1ijQTFME73Hnxr3DblKUvbni6uK0cLtR6st5rQfrw +XedAon2k8hfvYWjOBbiKsdU1ORU3y/6hM/aiqQxM9xZE3JD5rXn3h88HErkbB0lE +NKFOo1Cb7EUc5m90zipL0rWcYXj7vbodSFl6VDIz0Bj9s8miJuD2tUTnbXG0eFwp +eSWAsbiMRzSbr00YuRM9uFJ9fxeDd4vyakE213VrYuraKDZyutUDS+VHvn+SG0uJ +qINLEJCS2DdRrJIDSnAGQ6DyBNr0MZ5B3cE4QeJvjq+mTYmKhBHjJ4g6wjFcGCW3 +jQIDAQAB +-----END PUBLIC KEY----- \ No newline at end of file diff --git a/main/src/test/resources/org/apache/james/jdkim/keys/public.pem b/main/src/test/resources/org/apache/james/jdkim/keys/public.pem new file mode 100644 index 0000000..8955e7c --- /dev/null +++ b/main/src/test/resources/org/apache/james/jdkim/keys/public.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwXbQCRyKGeDnd4+iPSoO +zDM5mWf+cvsMIVepefCxqFi+ENhm8uMKZqbBDE2PhmhPD1z9SaYnGNjHP2Utg+jq +RbD9+8XL3lMWb+dLeygDIeWaEAozNPeeERCxP2EVAZeyaj8GvYk93ybv/boozcfG +ertg+CE3mJoX3azICT800yFO8X1jFkNGk2SWivHYo5+/Dj84KPISmDl5FghUrROa +fW/tqwPL13tkVKf/TO9aIV06qSNykUHZ64fhC7b0J5WCEtxGxEVZkhy78reN6xsx +ZdZkrbL6UJsnG0C8z3g6g6fSghO1ZuDodYd/aDgX3anaTHmNgUXafe4l5276ltl/ +IwIDAQAB +-----END PUBLIC KEY----- \ No newline at end of file From 948680ab3c3deb32f3cb544776266990fd297452 Mon Sep 17 00:00:00 2001 From: Emerson Pinter Date: Wed, 26 Mar 2025 14:19:16 -0300 Subject: [PATCH 066/114] Add DMARC compliant result method Also updates javadoc. --- .../org/apache/james/jdkim/DKIMVerifier.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java b/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java index 84fcedd..40e9836 100644 --- a/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java +++ b/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java @@ -413,7 +413,11 @@ private List verify(CompoundBodyHasher compoundBodyHasher) } /** - * Return the results of all signature checks, success and fail. + * Returns the results of all signature checks, success and fail. + * If a message doesn't have a DKIM-Signature header the result list will + * be empty. Use {@link Result#getResultType} or {@link Result#isSuccess} + * to get verification result of each DKIM-Signature check. Call {@link #resetResults} + * if the same instance of {@code DKIMVerifier} is reused for a new {@code verify} call. * * @return List of {@link Result} object. */ @@ -421,6 +425,20 @@ public List getResults() { return result; } + /** + * Returns {@code true} if at least one signature is successfully verified. + * A message pass the DKIM check when at least one signature is valid. + * This method is DMARC compliant, and should be called after {@code DKIMVerifier.verify}. + * Use {@link #getResults} to get result details. + * + * @return {@code true} if a valid signature is found, + * {@code false} if there's no valid signature. + * @see #getResults + */ + public boolean hasAnyValidSignature() { + return result.stream().anyMatch(Result::isSuccess); + } + /** * Returns true when all signature verification are successful. A message without dkim-signature is considered a success. * From 2262f9745c3fb545a22c11236ae452748f9cc268 Mon Sep 17 00:00:00 2001 From: Emerson Pinter Date: Thu, 27 Mar 2025 12:32:32 -0300 Subject: [PATCH 067/114] Only 'pass' result should be considered for valid signatures. --- main/src/main/java/org/apache/james/jdkim/api/Result.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main/src/main/java/org/apache/james/jdkim/api/Result.java b/main/src/main/java/org/apache/james/jdkim/api/Result.java index 065fe81..a383a48 100644 --- a/main/src/main/java/org/apache/james/jdkim/api/Result.java +++ b/main/src/main/java/org/apache/james/jdkim/api/Result.java @@ -112,7 +112,7 @@ private String getHeaderText(boolean withReason) { reasonMsg = "valid signature"; break; case NONE: - reasonMsg = "not signed"; + reasonMsg = "unknown error"; break; default: reasonMsg = errorMessage != null ? errorMessage : ""; @@ -147,7 +147,7 @@ public String getDkimRawField() { * @return Returns true if success */ public boolean isSuccess() { - return type == Type.PASS || type == Type.NONE || type == Type.NEUTRAL; + return type == Type.PASS; } /** From f079e46890c89cac103abc8e9dd34f051b7e245f Mon Sep 17 00:00:00 2001 From: Emerson Pinter Date: Sat, 12 Apr 2025 17:39:48 -0300 Subject: [PATCH 068/114] Remove unneeded method. A message needs only one valid signature to be valid. --- .../main/java/org/apache/james/jdkim/DKIMVerifier.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java b/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java index 40e9836..6f6e4c6 100644 --- a/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java +++ b/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java @@ -439,15 +439,6 @@ public boolean hasAnyValidSignature() { return result.stream().anyMatch(Result::isSuccess); } - /** - * Returns true when all signature verification are successful. A message without dkim-signature is considered a success. - * - * @return true when success - */ - public boolean isSuccess() { - return result.stream().allMatch(Result::isSuccess); - } - /** * Clears results list for the DKIMVerifier instance */ From a20366f2ecaa3f7bd003c405c368ba9bef3407fc Mon Sep 17 00:00:00 2001 From: Rene Cordier Date: Wed, 21 May 2025 15:04:03 +0700 Subject: [PATCH 069/114] [maven-release-plugin] prepare release apache-jdkim-project-0.5 --- assemble/pom.xml | 2 +- main/pom.xml | 2 +- pom.xml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/assemble/pom.xml b/assemble/pom.xml index 2ae288e..03fa64d 100644 --- a/assemble/pom.xml +++ b/assemble/pom.xml @@ -23,7 +23,7 @@ apache-jdkim-project org.apache.james.jdkim - 0.5-SNAPSHOT + 0.5 ../pom.xml diff --git a/main/pom.xml b/main/pom.xml index 7c7a41f..eeaf757 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -23,7 +23,7 @@ apache-jdkim-project org.apache.james.jdkim - 0.5-SNAPSHOT + 0.5 ../pom.xml diff --git a/pom.xml b/pom.xml index 1eceb37..7e9dbda 100644 --- a/pom.xml +++ b/pom.xml @@ -29,7 +29,7 @@ org.apache.james.jdkim apache-jdkim-project - 0.5-SNAPSHOT + 0.5 pom Apache James :: jDKIM :: DomainKey Project @@ -46,7 +46,7 @@ scm:git:https://git-wip-us.apache.org/repos/asf/james-jdkim.git scm:git:ssh://git@github.com/apache/james-jdkim.git http://git-wip-us.apache.org/repos/asf/james-jdkim.git - HEAD + apache-jdkim-project-0.5 JIRA From d55ca3c086ae96bb42a2df66ee5cdd8bb12fdd80 Mon Sep 17 00:00:00 2001 From: Rene Cordier Date: Wed, 21 May 2025 15:04:21 +0700 Subject: [PATCH 070/114] [maven-release-plugin] prepare for next development iteration --- assemble/pom.xml | 2 +- main/pom.xml | 2 +- pom.xml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/assemble/pom.xml b/assemble/pom.xml index 03fa64d..cca09e5 100644 --- a/assemble/pom.xml +++ b/assemble/pom.xml @@ -23,7 +23,7 @@ apache-jdkim-project org.apache.james.jdkim - 0.5 + 0.6-SNAPSHOT ../pom.xml diff --git a/main/pom.xml b/main/pom.xml index eeaf757..7093386 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -23,7 +23,7 @@ apache-jdkim-project org.apache.james.jdkim - 0.5 + 0.6-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index 7e9dbda..16481e0 100644 --- a/pom.xml +++ b/pom.xml @@ -29,7 +29,7 @@ org.apache.james.jdkim apache-jdkim-project - 0.5 + 0.6-SNAPSHOT pom Apache James :: jDKIM :: DomainKey Project @@ -46,7 +46,7 @@ scm:git:https://git-wip-us.apache.org/repos/asf/james-jdkim.git scm:git:ssh://git@github.com/apache/james-jdkim.git http://git-wip-us.apache.org/repos/asf/james-jdkim.git - apache-jdkim-project-0.5 + HEAD JIRA From c4cd7a412e540bd061566a24ed2b63a1ebc9695f Mon Sep 17 00:00:00 2001 From: Emerson Pinter Date: Mon, 25 Aug 2025 23:43:35 -0300 Subject: [PATCH 071/114] JDKIM-49 Add clock drift tolerance to signature validation Avoids signature validation failures when clock drift is lower than the threshold. --- .../org/apache/james/jdkim/DKIMVerifier.java | 60 +++++---- .../james/jdkim/api/VerifierOptions.java | 125 ++++++++++++++++++ .../java/org/apache/james/jdkim/DKIMTest.java | 3 +- .../james/jdkim/DKIMVerifierOptionsTest.java | 105 +++++++++++++++ .../jdkim/DNSPublicKeyRetrieverTest.java | 3 +- .../org/apache/james/jdkim/FileBasedTest.java | 3 +- .../org/apache/james/jdkim/PerlDKIMTest.java | 3 +- 7 files changed, 271 insertions(+), 31 deletions(-) create mode 100644 main/src/main/java/org/apache/james/jdkim/api/VerifierOptions.java create mode 100644 main/src/test/java/org/apache/james/jdkim/DKIMVerifierOptionsTest.java diff --git a/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java b/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java index 6f6e4c6..44537a1 100644 --- a/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java +++ b/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java @@ -27,15 +27,14 @@ import org.apache.james.jdkim.api.PublicKeyRecordRetriever; import org.apache.james.jdkim.api.Result; import org.apache.james.jdkim.api.SignatureRecord; +import org.apache.james.jdkim.api.VerifierOptions; import org.apache.james.jdkim.exceptions.CompositeFailException; 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; import org.apache.james.jdkim.tagvalue.PublicKeyRecordImpl; import org.apache.james.jdkim.tagvalue.SignatureRecordImpl; import org.apache.james.jdkim.tagvalue.SignatureRecordTemplate; @@ -47,6 +46,8 @@ import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; +import java.time.Duration; +import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -57,17 +58,24 @@ import java.util.Map; public class DKIMVerifier { - - private final PublicKeyRecordRetriever publicKeyRecordRetriever; private final List result = new ArrayList<>(); + private final VerifierOptions options; public DKIMVerifier() { - this.publicKeyRecordRetriever = new MultiplexingPublicKeyRecordRetriever( - "dns", new DNSPublicKeyRecordRetriever()); + this(new VerifierOptions.Builder().build()); + } + + /** + * Constructor with configuration, see {@link VerifierOptions.Builder} for available options. + * + * @param verifierOptions An instance of VerifierOptions, use {@link VerifierOptions.Builder} + */ + public DKIMVerifier(VerifierOptions verifierOptions) { + this.options = verifierOptions; } public DKIMVerifier(PublicKeyRecordRetriever publicKeyRecordRetriever) { - this.publicKeyRecordRetriever = publicKeyRecordRetriever; + this(new VerifierOptions.Builder().withPublicKeyRecordRetriever(publicKeyRecordRetriever).build()); } protected PublicKeyRecord newPublicKeyRecord(String record) { @@ -85,7 +93,7 @@ protected BodyHasherImpl newBodyHasher(SignatureRecord signRecord) protected PublicKeyRecordRetriever getPublicKeyRecordRetriever() throws PermFailException { - return publicKeyRecordRetriever; + return options.getPublicKeyRecordRetriever(); } public PublicKeyRecord publicKeySelector(List records) @@ -258,26 +266,24 @@ public BodyHasher newBodyHasher(Headers messageHeaders) throws FailException { // Specification say we MAY refuse to verify the signature. if (signatureRecord.getSignatureTimestamp() != null) { - long signedTime = signatureRecord.getSignatureTimestamp(); - long elapsed = (System.currentTimeMillis() / 1000 - signedTime); - if (elapsed < -3600 * 24 * 365 * 3) { - throw new PermFailException("Signature date is more than " - + -elapsed / (3600 * 24 * 365) + " years in the future.", signatureRecord); - } else if (elapsed < -3600 * 24 * 30 * 3) { - throw new PermFailException("Signature date is more than " - + -elapsed / (3600 * 24 * 30) + " months in the future.", signatureRecord); - } else if (elapsed < -3600 * 24 * 3) { - throw new PermFailException("Signature date is more than " - + -elapsed / (3600 * 24) + " days in the future.", signatureRecord); - } else if (elapsed < -3600 * 3) { - throw new PermFailException("Signature date is more than " - + -elapsed / 3600 + " hours in the future.", signatureRecord); - } else if (elapsed < -60 * 3) { + Instant signedTime = Instant.ofEpochSecond(signatureRecord.getSignatureTimestamp()); + Instant now = Instant.now(); + if (signedTime.isAfter(now.plus(options.getClockDriftTolerance()))) { + // RFC 6376, Section 3.5 page 25, about clock drift: + // Receivers MAY add a 'fudge factor' to allow for such possible drift. + Duration diff = Duration.between(now, signedTime); + String diffText; + if (diff.toMillis() >= 86400000) { + diffText = diff.toDays() + " day(s)"; + } else if (diff.toMillis() >= 3600000) { + diffText = diff.toHours() + " hour(s)"; + } else if (diff.toMillis() >= 60000) { + diffText = diff.toMinutes() + " minute(s)"; + } else { + diffText = (diff.toMillis() / 1000) + " second(s)"; + } throw new PermFailException("Signature date is more than " - + -elapsed / 60 + " minutes in the future.", signatureRecord); - } else if (elapsed < 0) { - throw new PermFailException("Signature date is " - + elapsed + " seconds in the future.", signatureRecord); + + diffText + " in the future.", signatureRecord); } } diff --git a/main/src/main/java/org/apache/james/jdkim/api/VerifierOptions.java b/main/src/main/java/org/apache/james/jdkim/api/VerifierOptions.java new file mode 100644 index 0000000..799d635 --- /dev/null +++ b/main/src/main/java/org/apache/james/jdkim/api/VerifierOptions.java @@ -0,0 +1,125 @@ +/**************************************************************** + * 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.api; + +import org.apache.james.jdkim.impl.DNSPublicKeyRecordRetriever; +import org.apache.james.jdkim.impl.MultiplexingPublicKeyRecordRetriever; +import org.xbill.DNS.Lookup; +import org.xbill.DNS.Resolver; + +import java.time.Duration; + +public class VerifierOptions { + private final Duration clockDriftTolerance; + private final PublicKeyRecordRetriever publicKeyRecordRetriever; + private final Resolver dnsResolver; + + public static class Builder { + private Duration clockDriftTolerance = Duration.ofSeconds(300); + private Resolver dnsResolver = Lookup.getDefaultResolver(); + private PublicKeyRecordRetriever publicKeyRecordRetriever = new MultiplexingPublicKeyRecordRetriever( + "dns", new DNSPublicKeyRecordRetriever(this.dnsResolver)); + + /** + * Sets the clock drift tolerance for signature verification, default is 300 seconds. + * + * @param clockDriftTolerance a {@link Duration} + * @return {@link Builder} + */ + public Builder withClockDriftTolerance(Duration clockDriftTolerance) { + this.clockDriftTolerance = clockDriftTolerance; + return this; + } + + /** + * Sets a custom PublicKeyRecordRetriever, a default is used if not set. + * + * @param publicKeyRecordRetriever a {@link PublicKeyRecordRetriever} + * @return {@link Builder} + */ + public Builder withPublicKeyRecordRetriever(PublicKeyRecordRetriever publicKeyRecordRetriever) { + this.publicKeyRecordRetriever = publicKeyRecordRetriever; + return this; + } + + /** + * A custom dns resolver + * + * @param dnsResolver a {@link Resolver} + * @return {@link Builder} + */ + public Builder withDnsResolver(Resolver dnsResolver) { + this.dnsResolver = dnsResolver; + return this; + } + + public VerifierOptions build() { + return new VerifierOptions(this); + } + } + + private VerifierOptions(Builder builder) { + if (builder.clockDriftTolerance == null) { + throw new IllegalArgumentException("clockDriftTolerance can not be null"); + } + if (builder.clockDriftTolerance.isNegative()) { + throw new IllegalArgumentException("clockDriftTolerance must not be negative"); + } + + if (builder.publicKeyRecordRetriever == null) { + throw new IllegalArgumentException("publicKeyRecordRetriever can not be null"); + } + + if (builder.dnsResolver == null) { + throw new IllegalArgumentException("dnsResolver can not be null"); + } + + this.clockDriftTolerance = builder.clockDriftTolerance; + this.dnsResolver = builder.dnsResolver; + this.publicKeyRecordRetriever = builder.publicKeyRecordRetriever; + } + + /** + * Gets current clock drift tolerance used for signature verification + * + * @return {@link Duration} + */ + public Duration getClockDriftTolerance() { + return clockDriftTolerance; + } + + /** + * Gets current PublicKeyRecordRetriever instance + * + * @return {@link PublicKeyRecordRetriever} + */ + public PublicKeyRecordRetriever getPublicKeyRecordRetriever() { + return publicKeyRecordRetriever; + } + + /** + * Gets current dns resolver + * + * @return {@link Resolver} + */ + public Resolver getDnsResolver() { + return dnsResolver; + } +} diff --git a/main/src/test/java/org/apache/james/jdkim/DKIMTest.java b/main/src/test/java/org/apache/james/jdkim/DKIMTest.java index 681406a..fce5524 100644 --- a/main/src/test/java/org/apache/james/jdkim/DKIMTest.java +++ b/main/src/test/java/org/apache/james/jdkim/DKIMTest.java @@ -40,6 +40,7 @@ import org.apache.james.jdkim.api.Headers; import org.apache.james.jdkim.api.Result; import org.apache.james.jdkim.api.SignatureRecord; +import org.apache.james.jdkim.api.VerifierOptions; import org.apache.james.jdkim.impl.Message; import org.junit.Test; @@ -67,7 +68,7 @@ public class DKIMTest { private static final String SIGNATURE_TEMPLATE_3 = "v=1; a=rsa-sha256; c=simple; d=messiah.edu; h=date:from:subject; q=dns/txt; s=selector3;"; private final DKIMSigner dkimSigner = new DKIMSigner(SIGNATURE_TEMPLATE, TestKeys.privateKey); - private final DKIMVerifier verifier = new DKIMVerifier(keyRecordRetriever); + private final DKIMVerifier verifier = new DKIMVerifier(new VerifierOptions.Builder().withPublicKeyRecordRetriever(keyRecordRetriever).build()); @Test public void should_verify_generated_signature_single_key() throws Exception { diff --git a/main/src/test/java/org/apache/james/jdkim/DKIMVerifierOptionsTest.java b/main/src/test/java/org/apache/james/jdkim/DKIMVerifierOptionsTest.java new file mode 100644 index 0000000..afd2077 --- /dev/null +++ b/main/src/test/java/org/apache/james/jdkim/DKIMVerifierOptionsTest.java @@ -0,0 +1,105 @@ +/**************************************************************** + * 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; + +import org.apache.james.jdkim.api.PublicKeyRecordRetriever; +import org.apache.james.jdkim.api.VerifierOptions; +import org.apache.james.jdkim.impl.DNSPublicKeyRecordRetriever; +import org.apache.james.jdkim.impl.MultiplexingPublicKeyRecordRetriever; +import org.junit.Test; +import org.xbill.DNS.Lookup; +import org.xbill.DNS.Resolver; +import org.xbill.DNS.SimpleResolver; + +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.time.Duration; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class DKIMVerifierOptionsTest { + + @Test + public void shouldNotReturnNullClockDriftTolerance() { + VerifierOptions opt = new VerifierOptions.Builder().build(); + assertNotNull(opt); + assertNotNull(opt.getClockDriftTolerance()); + } + + @Test + public void shouldReturnCorrectClockDriftTolerance() { + Duration duration = Duration.ofSeconds(1234); + VerifierOptions opt = new VerifierOptions.Builder().withClockDriftTolerance(duration).build(); + assertEquals(duration.toMillis(), opt.getClockDriftTolerance().toMillis()); + } + + @Test + public void shouldReturnDefaultClockDriftTolerance() { + VerifierOptions opt = new VerifierOptions.Builder().build(); + assertEquals("Invalid clock drift", 300000L, opt.getClockDriftTolerance().toMillis()); + } + + @Test + public void shouldNotReturnNullResolver() { + VerifierOptions opt = new VerifierOptions.Builder().build(); + assertNotNull(opt); + assertNotNull(opt.getDnsResolver()); + } + + @Test + public void shouldReturnDnsResolver() throws UnknownHostException { + Resolver resolver = new SimpleResolver("9.8.7.6"); + VerifierOptions opt = new VerifierOptions.Builder().withDnsResolver(resolver).build(); + assertEquals("Invalid dnsResolver", resolver, opt.getDnsResolver()); + assertTrue("Must be an instance of SimpleResolver", opt.getDnsResolver() instanceof SimpleResolver); + assertEquals("Invalid hostname", new InetSocketAddress("9.8.7.6", 53), ((SimpleResolver) opt.getDnsResolver()).getAddress()); + } + + @Test + public void shouldReturnDefaultResolver() throws UnknownHostException { + Resolver defaultResolver = Lookup.getDefaultResolver(); + VerifierOptions opt = new VerifierOptions.Builder().build(); + assertEquals("Resolver is not the default", defaultResolver, opt.getDnsResolver()); + } + + @Test + public void shouldNotReturnNullPublicKeyRecordRetriever() { + VerifierOptions opt = new VerifierOptions.Builder().build(); + assertNotNull(opt); + assertNotNull(opt.getPublicKeyRecordRetriever()); + } + + @Test + public void shouldReturnDefaultPublicKeyRecordRetriever() { + VerifierOptions opt = new VerifierOptions.Builder().build(); + assertTrue("Must be an instance of MultiplexingPublicKeyRecordRetriever", opt.getPublicKeyRecordRetriever() instanceof MultiplexingPublicKeyRecordRetriever); + + } + + @Test + public void shouldReturnCorrectPublicKeyRecordRetriever() { + PublicKeyRecordRetriever retr = new DNSPublicKeyRecordRetriever(); + VerifierOptions opt = new VerifierOptions.Builder().withPublicKeyRecordRetriever(retr).build(); + assertEquals("Invalid instance", retr, opt.getPublicKeyRecordRetriever()); + assertTrue("Must be an instance of DNSPublicKeyRecordRetriever", opt.getPublicKeyRecordRetriever() instanceof DNSPublicKeyRecordRetriever); + } +} diff --git a/main/src/test/java/org/apache/james/jdkim/DNSPublicKeyRetrieverTest.java b/main/src/test/java/org/apache/james/jdkim/DNSPublicKeyRetrieverTest.java index 4b60e9c..21d75d8 100644 --- a/main/src/test/java/org/apache/james/jdkim/DNSPublicKeyRetrieverTest.java +++ b/main/src/test/java/org/apache/james/jdkim/DNSPublicKeyRetrieverTest.java @@ -21,6 +21,7 @@ import org.apache.james.jdkim.api.PublicKeyRecord; import org.apache.james.jdkim.api.PublicKeyRecordRetriever; +import org.apache.james.jdkim.api.VerifierOptions; import org.apache.james.jdkim.exceptions.FailException; import org.apache.james.jdkim.exceptions.PermFailException; import org.apache.james.jdkim.exceptions.TempFailException; @@ -112,7 +113,7 @@ public void testSignVerify() throws NoSuchAlgorithmException, String signedMessage = res + "\r\n" + "From: test@example.com\r\nTo: test@example.com\r\n\r\nbody\r\n"; - new DKIMVerifier(mockPublicKeyRecordRetriever) + new DKIMVerifier(new VerifierOptions.Builder().withPublicKeyRecordRetriever(mockPublicKeyRecordRetriever).build()) .verify(new ByteArrayInputStream(signedMessage.getBytes())); } diff --git a/main/src/test/java/org/apache/james/jdkim/FileBasedTest.java b/main/src/test/java/org/apache/james/jdkim/FileBasedTest.java index 956f98a..f12b5f8 100644 --- a/main/src/test/java/org/apache/james/jdkim/FileBasedTest.java +++ b/main/src/test/java/org/apache/james/jdkim/FileBasedTest.java @@ -23,6 +23,7 @@ import junit.framework.TestCase; import junit.framework.TestSuite; import org.apache.james.jdkim.api.SignatureRecord; +import org.apache.james.jdkim.api.VerifierOptions; import org.apache.james.jdkim.exceptions.PermFailException; import java.io.File; @@ -209,7 +210,7 @@ protected void runTest() throws Throwable { "k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC1CTqmkuRWkxlHcv1peAz3c0RuXHthVO1xx1Hy4HryZUJwSJo/R3cnEwKorQvlRuDSMgXSLLxI8u6n7h6mzRmHdsS/A+pKc7nx/6WS4N6U57PSNqOclxfwa27m/EIL6KTk9KDhaKsXxquQUBkP1CQEUZHPhQ/t7s4dmU/kvGFgNQIDAQAB"); try { - DKIMVerifier verifier = new DKIMVerifier(pkr); + DKIMVerifier verifier = new DKIMVerifier(new VerifierOptions.Builder().withPublicKeyRecordRetriever(pkr).build()); List res = verifier.verify(is); assertEquals(1, verifier.getResults().size()); if (getName().startsWith("NONE_")) diff --git a/main/src/test/java/org/apache/james/jdkim/PerlDKIMTest.java b/main/src/test/java/org/apache/james/jdkim/PerlDKIMTest.java index e24ee7f..e4e8d4b 100644 --- a/main/src/test/java/org/apache/james/jdkim/PerlDKIMTest.java +++ b/main/src/test/java/org/apache/james/jdkim/PerlDKIMTest.java @@ -24,6 +24,7 @@ import junit.framework.TestSuite; import org.apache.james.jdkim.api.Result; import org.apache.james.jdkim.api.SignatureRecord; +import org.apache.james.jdkim.api.VerifierOptions; import org.apache.james.jdkim.exceptions.FailException; import java.io.BufferedReader; @@ -112,7 +113,7 @@ else if (getName().startsWith("bad_")) expectFailure = true; try { - DKIMVerifier verifier = new DKIMVerifier(pkr); + DKIMVerifier verifier = new DKIMVerifier(new VerifierOptions.Builder().withPublicKeyRecordRetriever(pkr).build()); List res = verifier.verify(is); if (getName().matches("good_dk_7|good_dk_6|dk_headers_2|good_dk_3") From ccd7b4dd492d1f4a7fc9c20c15901fdfdf49f506 Mon Sep 17 00:00:00 2001 From: Benoit TELLIER Date: Tue, 16 Sep 2025 20:54:55 +0200 Subject: [PATCH 072/114] Adopt JDK 11 as a build target (#29) --- main/pom.xml | 1 + pom.xml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/main/pom.xml b/main/pom.xml index 7093386..1f1d29e 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -98,6 +98,7 @@ org.apache.maven.plugins maven-shade-plugin + 3.6.0 package diff --git a/pom.xml b/pom.xml index 16481e0..3f78004 100644 --- a/pom.xml +++ b/pom.xml @@ -66,7 +66,7 @@ 3.6.3 1.8 4.13.2 - 1.8 + 11 From a93add89788cb1d87ed59b595cc1fb0434b68be4 Mon Sep 17 00:00:00 2001 From: Artem Grinchenko Date: Tue, 7 Oct 2025 09:43:01 -0400 Subject: [PATCH 073/114] First commit --- arc/pom.xml | 87 ++++ .../apache/james/arc/ARCChainValidator.java | 164 +++++++ .../java/org/apache/james/arc/ARCCommon.java | 141 ++++++ .../java/org/apache/james/arc/ARCSigner.java | 176 ++++++++ .../org/apache/james/arc/ARCVerifier.java | 402 ++++++++++++++++++ .../apache/james/arc/ArcSealVerifyData.java | 38 ++ .../org/apache/james/arc/ArcSetBuilder.java | 167 ++++++++ .../james/arc/ArcSignatureRecordImpl.java | 200 +++++++++ .../apache/james/arc/AuthResultsBuilder.java | 240 +++++++++++ .../arc/DNSPublicKeyRecordRetrieverArc.java | 66 +++ .../james/arc/PublicKeyRetrieverArc.java | 10 + .../james/arc/exceptions/ArcException.java | 29 ++ .../java/org/apache/james/arc/ARCTest.java | 137 ++++++ .../org/apache/james/arc/ArcTestKeys.java | 80 ++++ .../arc/MockPublicKeyRecordRetrieverArc.java | 70 +++ .../test/resources/keys/arc_test_pri.1.key | 16 + .../test/resources/keys/arc_test_pub.1.pem | 6 + .../test/resources/keys/dkim_test_pri.1.key | 16 + .../test/resources/keys/dkim_test_pub.1.pem | 6 + .../test/resources/mail/rfc8617_no_arc.eml | 31 ++ pom.xml | 1 + 21 files changed, 2083 insertions(+) create mode 100644 arc/pom.xml create mode 100644 arc/src/main/java/org/apache/james/arc/ARCChainValidator.java create mode 100644 arc/src/main/java/org/apache/james/arc/ARCCommon.java create mode 100644 arc/src/main/java/org/apache/james/arc/ARCSigner.java create mode 100644 arc/src/main/java/org/apache/james/arc/ARCVerifier.java create mode 100644 arc/src/main/java/org/apache/james/arc/ArcSealVerifyData.java create mode 100644 arc/src/main/java/org/apache/james/arc/ArcSetBuilder.java create mode 100644 arc/src/main/java/org/apache/james/arc/ArcSignatureRecordImpl.java create mode 100644 arc/src/main/java/org/apache/james/arc/AuthResultsBuilder.java create mode 100644 arc/src/main/java/org/apache/james/arc/DNSPublicKeyRecordRetrieverArc.java create mode 100644 arc/src/main/java/org/apache/james/arc/PublicKeyRetrieverArc.java create mode 100644 arc/src/main/java/org/apache/james/arc/exceptions/ArcException.java create mode 100644 arc/src/test/java/org/apache/james/arc/ARCTest.java create mode 100644 arc/src/test/java/org/apache/james/arc/ArcTestKeys.java create mode 100644 arc/src/test/java/org/apache/james/arc/MockPublicKeyRecordRetrieverArc.java create mode 100644 arc/src/test/resources/keys/arc_test_pri.1.key create mode 100644 arc/src/test/resources/keys/arc_test_pub.1.pem create mode 100644 arc/src/test/resources/keys/dkim_test_pri.1.key create mode 100644 arc/src/test/resources/keys/dkim_test_pub.1.pem create mode 100644 arc/src/test/resources/mail/rfc8617_no_arc.eml diff --git a/arc/pom.xml b/arc/pom.xml new file mode 100644 index 0000000..f586c09 --- /dev/null +++ b/arc/pom.xml @@ -0,0 +1,87 @@ + + + + 4.0.0 + + + apache-jdkim-project + org.apache.james.jdkim + 0.6-SNAPSHOT + ../pom.xml + + + apache-arc-library + + Apache James :: ARC + A Java implementation for the ARC specification. + http://james.apache.org/jdkim/main/ + 2008 + + + + commons-codec + commons-codec + + + org.apache.james.jdkim + apache-jdkim-library + 0.6-SNAPSHOT + + + org.apache.james.jdkim + apache-jdkim-library + 0.6-SNAPSHOT + test-jar + test + + + org.apache.james.jspf + apache-jspf-resolver + 1.0.5 + + + org.freemarker + freemarker + 2.3.31 + + + org.apache.geronimo.javamail + geronimo-javamail_1.4_mail + 1.6 + + + junit + junit + + + org.apache.james + apache-mime4j-core + + + org.apache.james + apache-mime4j-dom + + + org.assertj + assertj-core + test + + + diff --git a/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java b/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java new file mode 100644 index 0000000..2d251d8 --- /dev/null +++ b/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java @@ -0,0 +1,164 @@ +/**************************************************************** + * 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.arc; + +import org.apache.james.arc.exceptions.ArcException; +import org.apache.james.mime4j.dom.Message; +import org.apache.james.mime4j.dom.Header; +import org.apache.james.mime4j.stream.Field; + +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; +import java.util.Base64; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Validates the ARC (Authenticated Received Chain) chain in an email message. + *

+ * This class provides methods to validate the ARC chain by checking the structure, + * verifying ARC-Message-Signature and ARC-Seal headers, and ensuring the integrity + * of previous ARC hops. It uses DNS records and cryptographic verification to + * ensure the authenticity of the ARC chain. + *

+ */ +public class ARCChainValidator { + public static final String ARC_MESSAGE_SIGNATURE = "ARC-Message-Signature"; + public static final String ARC_SEAL = "ARC-Seal"; + private static final String SHA256_RSA = "SHA256withRSA"; + private final Pattern INST_RGX_PATTERN = Pattern.compile("i=([0-9]+)"); + private final PublicKeyRetrieverArc _keyRecordRetriever; + + public ARCChainValidator(PublicKeyRetrieverArc keyRecordRetriever) { + this._keyRecordRetriever = keyRecordRetriever; + } + + public String validateArcChain(Message message) { + + Header messageHeaders = message.getHeader(); + int curInstance = getCurrentInstance(messageHeaders); // Incremented by 1 + + if (curInstance == 1) { //we are the first ARC Hop and there is no previous ARC hops in the chain to validate + return "none"; + } + else if (curInstance > 51) { // Not allowed to be > 50 + return "fail"; + } + else { // there are previous ARC hops that need to be validated + return validatePreviousArcHops(message, messageHeaders, curInstance); + } + } + + private String validatePreviousArcHops(Message message, Header messageHeaders, int myInstance) { + ARCVerifier arcVerifier = new ARCVerifier(_keyRecordRetriever); + Map> arcHeadersByI = arcVerifier.getArcHeadersByI(messageHeaders.getFields()); + int numArcInstances = myInstance -1; + boolean isArcSetStructureOK = arcVerifier.validateArcSetStructure(arcHeadersByI); + if (!isArcSetStructureOK) { + return "fail"; + } + + Set prevArcSet; + prevArcSet = arcVerifier.extractArcSet(messageHeaders, numArcInstances); + if (prevArcSet != null) { + boolean amsOk = checkArcAms (prevArcSet, message, arcVerifier); + boolean asOk = checkArcSeal (messageHeaders.getFields(), numArcInstances, arcVerifier); + if (amsOk && asOk) { + return "pass"; + } + } + return "fail"; + } + + private boolean checkArcAms(Set prevArcSet, Message message, ARCVerifier arcVerifier){ + boolean retVal = false; + + Field amsHeader = prevArcSet.stream() + .filter(f -> f.getName().equalsIgnoreCase(ARC_MESSAGE_SIGNATURE)) + .findFirst().orElse(null); + if (amsHeader == null) return retVal; + + String txtDnsRecord = arcVerifier.getTxtDnsRecordByField(amsHeader); + if (txtDnsRecord == null) return retVal; + + retVal = arcVerifier.verifyAms(amsHeader, message, txtDnsRecord); + + return retVal; + } + + private boolean checkArcSeal(List headers, int instToVerify, ARCVerifier arcVerifier) { + boolean retVal = false; + Map> arcHeadersByI = arcVerifier.getArcHeadersByI(headers); + ArcSealVerifyData verifyData = arcVerifier.buildArcSealSigningData(arcHeadersByI, instToVerify); + Field arcSealHeader = headers.stream() + .filter(f -> f.getName().equalsIgnoreCase(ARC_SEAL)) + .findFirst().orElse(null); + if (arcSealHeader == null) return retVal; + + String txtDnsRecord = arcVerifier.getTxtDnsRecordByField(arcSealHeader); + if (txtDnsRecord == null) return retVal; + + PublicKey publicKey = arcVerifier.parsePublicKeyFromDns(txtDnsRecord); + if (publicKey == null) { + throw new ArcException(String.format("Unable to parse public key from dns record %s", txtDnsRecord)); + } + + String b64 = verifyData.getB64Signature(); + String data = verifyData.getSignedData(); + + try { + Signature sig = Signature.getInstance(SHA256_RSA); + sig.initVerify(publicKey); + sig.update(data.getBytes(StandardCharsets.UTF_8)); + byte[] signatureBytes = Base64.getDecoder().decode(b64); + retVal = sig.verify(signatureBytes); + } catch (NoSuchAlgorithmException e) { + throw new ArcException("Unsupported signing algorithm", e); + } + catch (InvalidKeyException e) { + throw new ArcException(String.format("Invalid public key used for %s record", txtDnsRecord), e); + } catch (SignatureException e) { + throw new ArcException(String.format("Invalid signature for %s record", txtDnsRecord), e); + } + return retVal; + } + + public int getCurrentInstance(Header messageHeaders) { + int retVal = 1; + for (Field field : messageHeaders.getFields()) { + if (field.getName().startsWith("ARC-")) { + Matcher m = INST_RGX_PATTERN.matcher(field.getBody()); + if (m.find()) { + int iVal = Integer.parseInt(m.group(1)); + if (iVal >= retVal) { + retVal = iVal + 1; + } + } + } + } + return retVal; + } +} diff --git a/arc/src/main/java/org/apache/james/arc/ARCCommon.java b/arc/src/main/java/org/apache/james/arc/ARCCommon.java new file mode 100644 index 0000000..47c186f --- /dev/null +++ b/arc/src/main/java/org/apache/james/arc/ARCCommon.java @@ -0,0 +1,141 @@ +/**************************************************************** + * 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.arc; + +import org.apache.james.arc.exceptions.ArcException; +import org.apache.james.jdkim.api.Headers; +import org.apache.james.jdkim.api.SignatureRecord; +import org.apache.james.jdkim.exceptions.PermFailException; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.Signature; +import java.security.SignatureException; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Base64; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +/** + * Utility class for ARC (Authenticated Received Chain) operations. + *

+ * Provides methods for: + *

    + *
  • Canonicalizing and updating cryptographic signatures for ARC headers
  • + *
  • Signing ARC-Message-Signature and ARC-Seal headers
  • + *
  • Copying streams
  • + *
  • Decoding Base64-encoded PKCS#8 private keys
  • + *
+ *

+ * This class is not intended to be instantiated. + */ +public class ARCCommon { + private ARCCommon(){} + + private static void updateSignature(Signature signature, boolean relaxed, + CharSequence header, String fv) throws SignatureException { + if (relaxed) { + signature.update(header.toString().toLowerCase().getBytes()); + signature.update(":".getBytes()); + String headerValue = fv.substring(fv.indexOf(':') + 1); + headerValue = headerValue.replaceAll("\r\n[\t ]", " "); + headerValue = headerValue.replaceAll("[\t ]+", " "); + headerValue = headerValue.trim(); + signature.update(headerValue.getBytes()); + } else { + signature.update(fv.getBytes()); + } + } + + static void amsSign(Headers h, SignatureRecord sign, + List headers, Signature signature) + throws SignatureException, PermFailException { + + boolean relaxedHeaders = isRelaxedHeaders(sign, true); + + Map processedHeader = new HashMap<>(); + + for (CharSequence header : headers) { + List hl = h.getFields(header.toString()); + if (hl != null && !hl.isEmpty()) { + Integer done = processedHeader.get(header.toString()); + if (done == null) + done = 0; + int doneHeaders = done + 1; + if (doneHeaders <= hl.size()) { + String fv = hl.get(hl.size() - doneHeaders); + updateSignature(signature, relaxedHeaders, header, fv); + signature.update("\r\n".getBytes()); + processedHeader.put(header.toString(), doneHeaders); + } + } + } + + String amsHeader = "ARC-Message-Signature:" + sign.toUnsignedString(); + updateSignature(signature, relaxedHeaders, "arc-message-signature", amsHeader); + } + + static void arcSeal(SignatureRecord sign, + Map headersToSeal, Signature signature) + throws SignatureException, PermFailException { + + boolean relaxedHeaders = isRelaxedHeaders(sign, false); + + for (Map.Entry headerEntry : headersToSeal.entrySet()) { + String headerName = headerEntry.getKey(); + String headerValue = headerName+": " +headerEntry.getValue(); + updateSignature(signature, relaxedHeaders, headerName, headerValue); + signature.update("\r\n".getBytes()); + } + + String signatureStub = "ARC-Seal:" + sign.toUnsignedString(); + updateSignature(signature, relaxedHeaders, "arc-seal", + signatureStub); + } + + private static boolean isRelaxedHeaders(SignatureRecord sign, boolean isAms) throws PermFailException { + boolean relaxedHeaders = !isAms || SignatureRecord.RELAXED.equals(sign + .getHeaderCanonicalisationMethod()); // RFC 8617 : ARC-seal: only "relaxed" header field canonicalization allowed + if (!relaxedHeaders + && !SignatureRecord.SIMPLE.equals(sign + .getHeaderCanonicalisationMethod())) { + + throw new PermFailException( + "Unsupported canonicalization algorithm: " + + sign.getHeaderCanonicalisationMethod()); + } + return relaxedHeaders; + } + + public static void streamCopy(InputStream bodyIs, OutputStream out) + throws IOException { + byte[] buffer = new byte[2048]; + int read; + while ((read = bodyIs.read(buffer)) > 0) { + out.write(buffer, 0, read); + } + bodyIs.close(); + out.close(); + } +} diff --git a/arc/src/main/java/org/apache/james/arc/ARCSigner.java b/arc/src/main/java/org/apache/james/arc/ARCSigner.java new file mode 100644 index 0000000..97b0034 --- /dev/null +++ b/arc/src/main/java/org/apache/james/arc/ARCSigner.java @@ -0,0 +1,176 @@ +/**************************************************************** + * 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.arc; +import org.apache.james.arc.exceptions.ArcException; +import org.apache.james.jdkim.api.BodyHasher; +import org.apache.james.jdkim.api.Headers; +import org.apache.james.jdkim.api.SignatureRecord; +import org.apache.james.jdkim.exceptions.FailException; +import org.apache.james.jdkim.exceptions.PermFailException; +import org.apache.james.jdkim.impl.BodyHasherImpl; +import org.apache.james.jdkim.impl.Message; + +import java.io.IOException; +import java.io.InputStream; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.Signature; +import java.security.SignatureException; +import java.util.List; +import java.util.Map; + +/** + * ARCSigner is responsible for generating and sealing ARC (Authenticated Received Chain) + * signatures for email messages. It uses a provided private key and signature record template + * to create ARC signature records, hash message bodies, and sign headers or message content. + *

+ * Main responsibilities: + *

    + *
  • Generate ARC signature records using a template
  • + *
  • Hash message bodies for signing
  • + *
  • Sign message headers and bodies using the provided private key
  • + *
  • Seal headers with ARC signatures
  • + *
+ *

+ * This class relies on the Java Cryptography Architecture and helper classes from the + * org.apache.james.jdkim and org.apache.james.arc packages. + */ +public class ARCSigner { + private final PrivateKey privateKey; + private final String signatureRecordTemplate; + + public ARCSigner(String signatureRecordTemplate, PrivateKey privateKey) { + this.privateKey = privateKey; + this.signatureRecordTemplate = signatureRecordTemplate; + } + + public SignatureRecord newSignatureRecordTemplate(String sigRecord) { + return new ArcSignatureRecordImpl(sigRecord); + } + + public BodyHasher newBodyHasher(SignatureRecord signRecord) + throws PermFailException { + return new BodyHasherImpl(signRecord); + } + + public String generateAms(InputStream is) throws IOException, FailException { + Message message; + try { + try { + message = new Message(is); + } catch (RuntimeException | IOException e) { + throw e; + } catch (Exception e1) { + throw new ArcException("MIME parsing exception: " + + e1.getMessage(), e1); + } + + try { + SignatureRecord srt = newSignatureRecordTemplate(signatureRecordTemplate); + BodyHasher bhj = newBodyHasher(srt); + + ARCCommon.streamCopy(message.getBodyInputStream(), bhj + .getOutputStream()); + + return generateAms(message, bhj); + } finally { + message.dispose(); + } + } finally { + is.close(); + } + } + + public String sealHeaders(Map headersToSeal) throws FailException { + SignatureRecord srt = newSignatureRecordTemplate(signatureRecordTemplate); + return seal(srt, headersToSeal); + } + + public String generateAms(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; + List headers; + byte[] computedHash = bhj.getDigest(); + bhj.getSignatureRecord().setBodyHash(computedHash); + headers = bhj.getSignatureRecord().getHeaders(); + + try { + byte[] signatureHash = signatureSign(message, bhj + .getSignatureRecord(), privateKey, headers); + + bhj.getSignatureRecord().setSignature(signatureHash); + return "ARC-element:" + ((ArcSignatureRecordImpl)bhj.getSignatureRecord()).getStringInTemplateOrder(); + } catch (InvalidKeyException e) { + throw new ArcException("Invalid key: " + e.getMessage(), e); + } catch (NoSuchAlgorithmException e) { + throw new ArcException("Unknown algorithm: " + e.getMessage(), e); + } catch (SignatureException e) { + throw new ArcException("Signing exception: " + e.getMessage(), e); + } + } + + public String seal(SignatureRecord signatureRecord, Map headersToSeal) throws PermFailException { + + try { + byte[] signatureHash = signatureSeal(signatureRecord, privateKey, headersToSeal); + + signatureRecord.setSignature(signatureHash); + return "ARC-element:" + ((ArcSignatureRecordImpl)signatureRecord).getStringInTemplateOrder(); + } catch (InvalidKeyException e) { + throw new ArcException("Invalid key: " + e.getMessage(), e); + } catch (NoSuchAlgorithmException e) { + throw new ArcException("Unknown algorithm: " + e.getMessage(), e); + } catch (SignatureException e) { + throw new ArcException("Signing exception: " + e.getMessage(), e); + } + } + + private byte[] signatureSeal(SignatureRecord sign, PrivateKey key, Map headersToSeal) + throws NoSuchAlgorithmException, InvalidKeyException, + SignatureException, PermFailException { + Signature signature = Signature.getInstance(sign.getHashMethod() + .toString().toUpperCase() + + "with" + sign.getHashKeyType().toString().toUpperCase()); + signature.initSign(key); + + ARCCommon.arcSeal(sign, headersToSeal, signature); + return signature.sign(); + + } + + private byte[] signatureSign(Headers h, SignatureRecord sign, + PrivateKey key, List headers) + throws NoSuchAlgorithmException, InvalidKeyException, + SignatureException, PermFailException { + + Signature signature = Signature.getInstance(sign.getHashMethod() + .toString().toUpperCase() + + "with" + sign.getHashKeyType().toString().toUpperCase()); + signature.initSign(key); + + ARCCommon.amsSign(h, sign, headers, signature); + return signature.sign(); + } +} + diff --git a/arc/src/main/java/org/apache/james/arc/ARCVerifier.java b/arc/src/main/java/org/apache/james/arc/ARCVerifier.java new file mode 100644 index 0000000..b84f57e --- /dev/null +++ b/arc/src/main/java/org/apache/james/arc/ARCVerifier.java @@ -0,0 +1,402 @@ +/**************************************************************** + * 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.arc; + +import org.apache.james.arc.exceptions.ArcException; +import org.apache.james.jdkim.exceptions.PermFailException; +import org.apache.james.jdkim.exceptions.TempFailException; +import org.apache.james.mime4j.dom.Header; +import org.apache.james.mime4j.dom.Message; +import org.apache.james.mime4j.stream.Field; + +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; +import java.util.ArrayList; +import java.util.Base64; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.TreeMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Utility class for verifying ARC (Authenticated Received Chain) headers in email messages. + *

+ * Provides methods for: + *

    + *
  • Verifying ARC-Message-Signature (AMS) using public keys from DNS
  • + *
  • Parsing and canonicalizing ARC headers and bodies
  • + *
  • Validating ARC set structure and continuity
  • + *
  • Building DNS queries for public key retrieval
  • + *
  • Extracting and organizing ARC headers by instance
  • + *
  • Looking up DNS TXT records for ARC public keys
  • + *
  • Building signing data for ARC-Seal verification
  • + *
+ *

+ * This class is not instantiable and all methods are static. + */ +public class ARCVerifier { + public static final String RSA = "RSA"; + public static final String B_TAG_REGEX = "b=[^;]*"; + public static final Pattern TAG_PATTERN = Pattern.compile("([a-z]+)=([^;]+)"); + public static final Pattern PUBLIC_KEY_PATTERN = Pattern.compile("p=([^;]+)"); + public static final String ARC_AUTHENTICATION_RESULTS = "ARC-Authentication-Results"; + public static final String ARC_MESSAGE_SIGNATURE = "ARC-Message-Signature"; + public static final String ARC_SEAL = "ARC-Seal"; + public static final String SHA256RSA = "SHA256withRSA"; + private static final String DNS_RECORD_TYPE = "_domainkey"; + private PublicKeyRetrieverArc _keyRecordRetriever; + + ARCVerifier(PublicKeyRetrieverArc keyRecordRetriever) { + _keyRecordRetriever = keyRecordRetriever; + } + + public boolean verifyAms(Field amsField, Message message, String publicKeyDnsRecord) { + // Extract AMS params + String amsValue = amsField.getBody(); + Map tags = parseTagList(amsValue); + + String signedHeaders = tags.get("h"); + String bodyHash = tags.get("bh"); + String signatureB64 = tags.get("b"); + String b64 = signatureB64 + .replaceAll("\\s+", "") // remove spaces, tabs, newlines + .replace(";", ""); // defensive: strip trailing semicolon if present + + if (signedHeaders == null || bodyHash == null) { + throw new ArcException("AMS missing required tags"); + } + + String amsForSigning = amsValue.replaceFirst(B_TAG_REGEX, "b="); + // Canonicalize headers listed in h= + StringBuilder signingData = new StringBuilder(); + for (String hName : signedHeaders.split(":")) { + hName = hName.trim(); + for (Field f : message.getHeader().getFields(hName)) { + signingData.append(canonicalizeRegularHeader(f)); + } + } + + // AMS itself must be included last + signingData.append(canonicalizeHeader(amsField.getName(), amsForSigning)); + + // Build RSA public key from DNS record + PublicKey publicKey = parsePublicKeyFromDns(publicKeyDnsRecord); + + // Verify signature + Signature sig = getSignature( publicKey, signingData); + + byte[] signatureBytes = Base64.getDecoder().decode(b64); + + boolean result = false; + if (sig != null) { + try { + result = sig.verify(signatureBytes); + } catch (SignatureException e) { + throw new ArcException("Signature verification failed", e); + } + } + return result; + } + + private Signature getSignature(PublicKey publicKey, StringBuilder signingData) { + Signature sig; + try { + sig = Signature.getInstance(SHA256RSA); + sig.initVerify(publicKey); + String dataToSign = signingData.toString(); + sig.update(dataToSign.getBytes(StandardCharsets.UTF_8)); + } catch (NoSuchAlgorithmException e) { + throw new ArcException("Unsupported signing algorithm when used with public key", e); + } catch (InvalidKeyException e) { + throw new ArcException("Invalid key when used with public key", e); + } catch (SignatureException e) { + throw new ArcException("Invalid signature when used with public key", e); + } + return sig; + } + + public Map parseTagList(String value) { + Map map = new HashMap<>(); + Matcher m = TAG_PATTERN.matcher(value); + while (m.find()) { + map.put(m.group(1).trim(), m.group(2).trim()); + } + return map; + } + + private String canonicalizeRegularHeader(Field field) { + String retVal = canonicalizeHeader(field.getName(), field.getBody()); + return retVal + "\r\n"; + } + + private String canonicalizeHeader(String name, String value) { + // relaxed canonicalization: lowercase field name, unfold spaces, trim + String n = name.toLowerCase(Locale.ROOT); + String v = value.replaceAll("[\\r\\n]+", " ") + .replaceAll("\\s+", " ") + .trim(); + return n + ":" + v; + } + + private PublicKey getPublicKeyFromTxtRecord(String keyText) { + + keyText = keyText.replaceAll("\\s+", ""); // remove ALL spaces/newlines + byte[] keyBytes = Base64.getDecoder().decode(keyText); + + X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes); + PublicKey pubKey; + try { + pubKey = KeyFactory.getInstance(RSA).generatePublic(spec); + } catch (InvalidKeySpecException e) { + throw new ArcException("Invalid key provided when getting public key", e); + } catch (NoSuchAlgorithmException e) { + throw new ArcException("Unsupported algorithm provided when getting public key", e); + } + return pubKey; + } + + public PublicKey parsePublicKeyFromDns(String dnsRecord) { + Matcher m = PUBLIC_KEY_PATTERN.matcher(dnsRecord); + + if (!m.find()) { + throw new IllegalArgumentException("Illegal argument exception -- No p= tag in DNS record"); + } + + String base64Key = m.group(1).replaceAll("\\s+", ""); // remove ALL spaces/newlines + byte[] keyBytes = Base64.getDecoder().decode(base64Key); + + X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes); + PublicKey pubKey; + try { + pubKey = KeyFactory.getInstance(RSA).generatePublic(spec); + } catch (InvalidKeySpecException e) { + throw new ArcException("Invalid key provided when getting public key", e); + } catch (NoSuchAlgorithmException e) { + throw new ArcException("Unsupported algorithm provided when getting public key", e); + } + return pubKey; + } + + public boolean validateArcSetStructure(Map> arcHeadersByI) { + for (int i = 1; i <= arcHeadersByI.size(); i++) { + List arcSet = arcHeadersByI.get(i); + if (arcSet == null) { // continuity of instances is broken + throw new IllegalStateException("ARC Chain validation fails due to i instances not continued after [" + (i - 1) + "] instance."); + } + + boolean eachOfOne = checkArcSetCompose(arcSet); + if (!eachOfOne) { + throw new ArcException("ARC Chain validation fails due to one or more ARC Set headers missing at instance [" + i + "]."); + } + + if (arcSet.size() != 3){ + throw new ArcException("ARC Chain validation fails due to incorrect size of Arc Headers (not 3) at instance [" + i + "]."); + } + + boolean cvOk = checkCv(arcSet, i); + if (!cvOk) { + throw new ArcException("ARC Chain validation fails due to cv check failing at instance [" + i + "]."); + } + } + return true; + } + + private boolean checkCv(List lastArcSet, int instToVerify) { + Optional arcSealHeader = lastArcSet.stream().filter(f -> f.getName().equalsIgnoreCase(ARC_SEAL)).findFirst(); + if (arcSealHeader.isPresent()) { + Map tags = parseTagList(arcSealHeader.get().getBody()); + String lastCv = tags.get("cv"); + return (instToVerify == 1 && lastCv.equalsIgnoreCase("none")) || + (instToVerify > 1 && lastCv.equalsIgnoreCase("pass")); + } + return false; + } + + private boolean checkArcSetCompose(List arcSet) { + Optional aar = arcSet.stream().filter(p-> p.getName() + .equalsIgnoreCase(ARC_AUTHENTICATION_RESULTS)).findFirst(); + + Optional ams = arcSet.stream().filter(p-> p.getName() + .equalsIgnoreCase(ARC_MESSAGE_SIGNATURE)).findFirst(); + + Optional as = arcSet.stream().filter(p-> p.getName() + .equalsIgnoreCase(ARC_SEAL)).findFirst(); + return aar.isPresent() && ams.isPresent() && as.isPresent(); + } + + public String buildDnsQuery(Field signedField, String recordType) { + String retVal = ""; + Map tags = parseTagList(signedField.getBody()); + if (tags.isEmpty()) { // we should always have tags on the valid AMS + return retVal; + } + String amsSelector = tags.get("s"); + String amsDomain = tags.get("d"); + if (amsSelector == null || amsDomain == null) { // we should always have these tags on the valid AMS + return retVal; + } + retVal = amsSelector+"."+ recordType+"."+amsDomain; + return retVal; + } + + public Map> getArcHeadersByI(List headers) { + Map> headersByI = new TreeMap<>(); + for (Field f : headers) { + String name = f.getName().toUpperCase(Locale.ROOT); + if (name.startsWith("ARC-")) { + int i = -1; + String iTag = parseTagGeneric(f.getBody(), "i"); + if (iTag != null) { + i = Integer.parseInt(iTag); + } + if (i == -1) { + throw new IllegalStateException("ARC Header missing i= tag"); + } + else { + headersByI.computeIfAbsent(i, k -> new ArrayList<>()).add(f); + } + } + } + return headersByI; + } + + public String canonicalizeBody(String body) { + body = body.replaceAll("\r\n[\t ]", " "); + body = body.replaceAll("[\t ]+", " "); + body = body.trim(); + return body; + } + + public String parseTagGeneric(String record, String tag) { + String[] parts = record.split(";"); + for (String part : parts) { + String trimmed = part.trim(); + if (trimmed.startsWith(tag + "=")) { + return trimmed.substring((tag + "=").length()); + } + } + return null; + } + + public ArcSealVerifyData buildArcSealSigningData(Map> headersByI, int targetI) { + ArcSealVerifyData result = new ArcSealVerifyData(); + StringBuilder signingData = new StringBuilder(); + + //Iterate over hops in ascending i order, for the last hop, make sure to clear b= tag on the ARC-Seal + for (Map.Entry> entry : headersByI.entrySet()) { + int hopI = entry.getKey(); + if (hopI > targetI) break; + + List hopFields = entry.getValue(); + Optional aar = hopFields.stream().filter(p-> p.getName() + .equalsIgnoreCase(ARC_AUTHENTICATION_RESULTS)).findFirst(); + + Optional ams = hopFields.stream().filter(p-> p.getName() + .equalsIgnoreCase(ARC_MESSAGE_SIGNATURE)).findFirst(); + + Optional as = hopFields.stream().filter(p-> p.getName() + .equalsIgnoreCase(ARC_SEAL)).findFirst(); + + aar.ifPresent(f -> signingData + .append(f.getName().toLowerCase(Locale.ROOT)) + .append(":").append(canonicalizeBody(f.getBody())) + .append("\r\n")); + + ams.ifPresent(f -> signingData + .append(f.getName().toLowerCase(Locale.ROOT)) + .append(":").append(canonicalizeBody(f.getBody())) + .append("\r\n")); + + if (hopI == targetI) { // this is last hop so we need to clear b= tag on the ARC-Seal Header and not tail it with CRLF + as.ifPresent(f -> { + Map tags = parseTagList(f.getBody()); + String signatureB64 = tags.get("b"); + String b64 = signatureB64 + .replaceAll("\\s+", "") // remove spaces, tabs, newlines + .replace(";", ""); + String arcSealBodyClearedB= f.getBody().replaceAll("\\bb=([^;]*)", "b="); + signingData.append(f.getName().toLowerCase(Locale.ROOT)).append(":").append(canonicalizeBody(arcSealBodyClearedB)); + result.setB64Signature(b64); + result.setSignedData(signingData.toString()); + }); + } + else { // this is one of the previous hops, not the last one, so we want to preserve b= tag on the ARC-Seal and tail it with CRLF + as.ifPresent(f -> signingData + .append(f.getName().toLowerCase(Locale.ROOT)) + .append(":").append(canonicalizeBody(f.getBody())) + .append("\r\n")); + } + } + return result; + } + + public Set extractArcSet(Header messageHeaders, int instance) { + Set prevArcSet = null; + for (Field field : messageHeaders.getFields()) { + if (field.getName().startsWith("ARC-") && field.getBody().contains("i="+instance)) { + if (prevArcSet == null) { + prevArcSet = new HashSet<>(); + } + prevArcSet.add(field); + } + } + return prevArcSet; + } + + public String getTxtDnsRecordByField(Field signedHeader) { + String dnsQuery = buildDnsQuery(signedHeader, DNS_RECORD_TYPE); + if (dnsQuery == null || dnsQuery.isEmpty()) return null; // corrupted AMS - unable to pull PubKey from DNS + Map tags = parseTagList(signedHeader.getBody()); + if (tags.isEmpty()) { // we should always have tags on the valid AMS + throw new ArcException("Missing tags for dns record") ; + } + String amsSelector = tags.get("s"); + String amsDomain = tags.get("d"); + + try { + List results = getPublicKeyRecordRetriever().getRecords("dns/txt", amsSelector, amsDomain); + if (!results.isEmpty()) { + return results.get(0); //Todo: handle multiple records? + } + } catch (TempFailException e) { + throw new RuntimeException(e); + } catch (PermFailException e) { + throw new RuntimeException(e); + } + return null; + } + + protected PublicKeyRetrieverArc getPublicKeyRecordRetriever() + { + return _keyRecordRetriever; + } +} diff --git a/arc/src/main/java/org/apache/james/arc/ArcSealVerifyData.java b/arc/src/main/java/org/apache/james/arc/ArcSealVerifyData.java new file mode 100644 index 0000000..2af6fcb --- /dev/null +++ b/arc/src/main/java/org/apache/james/arc/ArcSealVerifyData.java @@ -0,0 +1,38 @@ +/**************************************************************** + * 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.arc; + +public class ArcSealVerifyData { + private String _b64; + private String _dataToVerify; + + public void setSignedData(String _dataToVerify) { + this._dataToVerify = _dataToVerify; + } + + public void setB64Signature(String _b64) { + this._b64 = _b64; + } + + public String getB64Signature() { + return _b64; + } + public String getSignedData() { return + _dataToVerify; } +} \ No newline at end of file diff --git a/arc/src/main/java/org/apache/james/arc/ArcSetBuilder.java b/arc/src/main/java/org/apache/james/arc/ArcSetBuilder.java new file mode 100644 index 0000000..e121105 --- /dev/null +++ b/arc/src/main/java/org/apache/james/arc/ArcSetBuilder.java @@ -0,0 +1,167 @@ +/**************************************************************** + * 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.arc; + +import org.apache.james.arc.exceptions.ArcException; +import org.apache.james.mime4j.dom.Header; +import org.apache.james.mime4j.dom.Message; +import org.apache.james.mime4j.message.DefaultMessageWriter; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.StringWriter; +import java.security.PrivateKey; +import java.time.Instant; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Builder class for generating ARC (Authenticated Received Chain) header sets. + *

+ * This class is responsible for constructing and signing ARC-Authentication-Results, + * ARC-Message-Signature, and ARC-Seal headers for a given email message, using + * provided templates and cryptographic keys. + *

+ *

+ * Usage involves providing the necessary templates, DMARC responses, authentication + * service, and private key. The {@link #buildArcSet(Message, String, String, String, PublicKeyRetrieverArc)} + * method generates the ARC headers and returns them as a map. + *

+ */ +public class ArcSetBuilder { + public static final String ARC_ELEMENT = "ARC-element:"; + public static final String ARC_SEAL = "ARC-Seal"; + public static final String ARC_MESSAGE_SIGNATURE = "ARC-Message-Signature"; + public static final String AUTHENTICATION_RESULTS = "Authentication-Results"; + public static final String ARC_AUTHENTICATION_RESULTS = "ARC-Authentication-Results"; + + private final PrivateKey _arcPrivateKey; + private final String _arcAmsTemplate; + private final String _arcSealTemplate; + private final String _dmarcResponse; + private final String _dmarcNonResponse; + private final String _authService; + private long _debugTimestamp; + + public ArcSetBuilder(PrivateKey arcPrivateKey, String arcAmsTemplate, String arcSealTemplate, + String dmarcResponse, String dmarcNonResponse, + String authService, long debugTimestamp) { + this(arcPrivateKey, arcAmsTemplate, arcSealTemplate, dmarcResponse, dmarcNonResponse, authService); + _debugTimestamp = debugTimestamp; + } + + public ArcSetBuilder(PrivateKey arcPrivateKey, String arcAmsTemplate, String arcSealTemplate, + String dmarcResponse, String dmarcNonResponse, + String authService) { + _arcAmsTemplate = arcAmsTemplate; + _arcSealTemplate = arcSealTemplate; + _arcPrivateKey = arcPrivateKey; + _dmarcResponse = dmarcResponse; + _dmarcNonResponse = dmarcNonResponse; + _authService = authService; + } + + /** + * Builds the ARC (Authenticated Received Chain) header set for the given email message. + *

+ * This method generates and signs the ARC-Authentication-Results, ARC-Message-Signature, + * and ARC-Seal headers using the provided message, HELO, MAIL FROM, and IP address. + * The headers are constructed using configured templates and cryptographic keys. + *

+ * + * @param message the email message to process + * @param helo the HELO/EHLO string from the SMTP transaction + * @param mailFrom the MAIL FROM address from the SMTP transaction + * @param ip the connecting client IP address + * @param keyRecordRetriever + * @return a map containing the generated ARC headers and their values + * @throws ArcException if ARC header generation or signing fails + */ + public Map buildArcSet(Message message, String helo, String mailFrom, String ip, PublicKeyRetrieverArc keyRecordRetriever) { + Map arcHeaders = new HashMap<>(); + + try { + Header headers = message.getHeader(); + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + AuthResultsBuilder authResultsBuilder = new AuthResultsBuilder(_dmarcResponse, _dmarcNonResponse, _authService, keyRecordRetriever); + String cv = arcChainValidator.validateArcChain(message); + int instance = arcChainValidator.getCurrentInstance(headers); + + //Build ARC-Authentication-Results header + String arHeaderValue = authResultsBuilder.getAuthResultsHeader(message, helo, mailFrom,ip); + if (arHeaderValue == null){ + throw new ArcException("Unable to build Authentication-Results header"); + } + + arcHeaders.put(AUTHENTICATION_RESULTS, arHeaderValue); + Map headersToSeal = new LinkedHashMap<>(); + String aarHeaderValue = "i=" + instance + "; " + arHeaderValue.trim(); + + arcHeaders.put(ARC_AUTHENTICATION_RESULTS, aarHeaderValue); + headersToSeal.put(ARC_AUTHENTICATION_RESULTS, aarHeaderValue); + DefaultMessageWriter writer = new DefaultMessageWriter(); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + writer.writeMessage(message,os); + + Map fmContext = new HashMap<>(); + fmContext.put("instance", instance); + long timestamp = Instant.now().getEpochSecond(); + if (_debugTimestamp != 0) { + timestamp = _debugTimestamp; + } + fmContext.put("timestamp", Long.toString(timestamp)); + fmContext.put("cv", cv); + + //Build and add ARC-AMS header + String amsTemplate = mergeTemplate(_arcAmsTemplate, fmContext); + ARCSigner amsSigner = new ARCSigner(amsTemplate, _arcPrivateKey); + String amsHeader = amsSigner.generateAms(new ByteArrayInputStream(os.toByteArray())); + String amsValue = amsHeader.split(ARC_ELEMENT)[1]; + arcHeaders.put(ARC_MESSAGE_SIGNATURE, amsValue); + headersToSeal.put(ARC_MESSAGE_SIGNATURE, amsValue); + + //Build and add ARC-Seal header + String asTemplate = mergeTemplate(_arcSealTemplate, fmContext); + ARCSigner asSigner = new ARCSigner(asTemplate, _arcPrivateKey); + String asHeader = asSigner.sealHeaders(headersToSeal ); + String asValue = asHeader.split(ARC_ELEMENT)[1]; + arcHeaders.put(ARC_SEAL, asValue); + } + catch (Exception ex){ + throw new ArcException("Unable to generate ARC Seal", ex); + } + return arcHeaders; + } + + private String mergeTemplate(String templateString, Map context) throws IOException { + try (StringWriter writer = new StringWriter()) { + freemarker.template.Configuration cfg = new freemarker.template.Configuration(freemarker.template.Configuration.VERSION_2_3_31); + cfg.setTemplateLoader(new freemarker.cache.StringTemplateLoader()); + freemarker.cache.StringTemplateLoader loader = (freemarker.cache.StringTemplateLoader) cfg.getTemplateLoader(); + loader.putTemplate("template", templateString); + freemarker.template.Template template = cfg.getTemplate("template"); + template.process(context, writer); + return writer.toString(); + } catch (freemarker.template.TemplateException e) { + throw new IOException("Error merging template", e); + } + } +} diff --git a/arc/src/main/java/org/apache/james/arc/ArcSignatureRecordImpl.java b/arc/src/main/java/org/apache/james/arc/ArcSignatureRecordImpl.java new file mode 100644 index 0000000..2b790da --- /dev/null +++ b/arc/src/main/java/org/apache/james/arc/ArcSignatureRecordImpl.java @@ -0,0 +1,200 @@ +/**************************************************************** + * 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.arc; + +import org.apache.james.jdkim.tagvalue.SignatureRecordImpl; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; +/** + * Implementation of an ARC (Authenticated Received Chain) signature record. + *

+ * This class extends {@link SignatureRecordImpl} to provide parsing, validation, + * and string formatting for ARC signature header fields as defined in the ARC protocol. + * It maintains the original order of tags and supports validation of expiration, + * header lists, and tag/value syntax. + *

+ */ +public class ArcSignatureRecordImpl extends SignatureRecordImpl { + private static final Pattern hdrNamePattern = Pattern.compile("^[^: \r\n\t]+$"); + private static final Pattern tagPattern = Pattern.compile("^[A-Za-z][A-Za-z0-9_]*$"); + private static final String tagValFormatPattern = "[^; \t\r\n]++"; + private static final Pattern valuePattern = Pattern.compile("^(?:" + tagValFormatPattern + + "(?:(?:(?:\r\n)?[\t ])++" + tagValFormatPattern + ")*+)?$"); + private final Map tagValuesOriginal = new LinkedHashMap<>(); + + public ArcSignatureRecordImpl(String data) { + super(data); + parseOriginal(data); + } + + @Override + public void validate() throws IllegalStateException { + if (getValue("x") != null) { + long expiration = Long.parseLong(getValue("x").toString()); + long lifetime = (expiration - System.currentTimeMillis() / 1000); + if (lifetime < 0) { + throw new IllegalStateException("Signature is expired since " + + getTimeMeasureText(lifetime) + "."); + } + } + } + + @Override + public List getHeaders() { + if (getValue("h") == null) + return new ArrayList<>(); + else + return stringToColonSeparatedList(getValue("h").toString(), + hdrNamePattern); + } + + private String getTimeMeasureText(long lifetime) { + String measure = "s"; + lifetime = -lifetime; + if (lifetime > 600) { + lifetime = lifetime / 60; + measure = "m"; + if (lifetime > 600) { + lifetime = lifetime / 60; + + measure = "h"; + if (lifetime > 120) { + lifetime = lifetime / 24; + measure = "d"; + if (lifetime > 90) { + lifetime = lifetime / 30; + measure = " months"; + if (lifetime > 24) { + lifetime = lifetime / 12; + measure = " years"; + } + } + } + } + } + return lifetime + measure; + } + + @Override + public String toUnsignedString() { + String retValue = toString().replaceFirst("b=[^;]*", "b="); + return getOrigOrderedString(retValue); + } + + private String getOrigOrderedString(String retValue) { + List retValPartsList = Arrays.asList(retValue.trim().split(";")); + StringBuilder sb = new StringBuilder(); + int originalTagIndex = 0; + for (String tag : tagValuesOriginal.keySet()) { + String tagPart = retValPartsList.stream().filter(p -> p.trim().startsWith(tag + "=")).findFirst().orElse(null); + if (tagPart != null) { + boolean isLastTag = originalTagIndex == tagValuesOriginal.size() - 1; + if (tagPart.trim().startsWith("h") && tagPart.contains(":")) { + tagPart = tagPart.replace(":", " : "); + sb.append(tagPart.toLowerCase().trim()); + } else { + sb.append(tagPart.trim()); + } + if (!isLastTag) { + sb.append("; "); + } + } + originalTagIndex++; + } + return sb.toString(); + } + + public String getStringInTemplateOrder(){ + return getOrigOrderedString(toString()); + } + + private void parseOriginal(String data) { + for (int i = 0; i < data.length(); i++) { + int equal = data.indexOf('=', i); + if (equal == -1) { + String rest = data.substring(i); + if (!rest.isEmpty() + && trimFWS(rest, 0, rest.length() - 1, true).length() > 0) { + throw new IllegalStateException( + "Unexpected termination at position " + i + ": " + + data + " | [" + rest + "]"); + } + i = data.length(); + continue; + } + // we could start from "equals" but we start from "i" in + // order to spot invalid values before validation. + int next = data.indexOf(';', i); + if (next == -1) { + next = data.length(); + } + + if (equal > next) { + throw new IllegalStateException("Found ';' before '=' in " + + data); + } + + CharSequence tag = trimFWS(data, i, equal - 1, true).toString(); + if (VALIDATION && !tagPattern.matcher(tag).matches()) { + throw new IllegalStateException("Syntax error in tag: " + tag); + } + String tagString = tag.toString(); + if (tagValuesOriginal.containsKey(tagString)) { + throw new IllegalStateException( + "Syntax error (duplicate tag): " + tag); + } + + CharSequence value = trimFWS(data, equal + 1, next - 1, true); + if (VALIDATION && !valuePattern.matcher(value).matches()) { + throw new IllegalStateException("Syntax error in value: " + + value); + } + + tagValuesOriginal.put(tagString, value); + i = next; + } + } + + @Override + public CharSequence getIdentity() { + // In ARC, i= is just an integer + return getValue("i"); + } + + @Override + public CharSequence getIdentityLocalPart() { + // Not applicable for ARC + return getIdentity(); + } + + @Override + public boolean equals(Object obj) { + return super.equals(obj); + } + + @Override + public int hashCode() { + return super.hashCode(); + } +} diff --git a/arc/src/main/java/org/apache/james/arc/AuthResultsBuilder.java b/arc/src/main/java/org/apache/james/arc/AuthResultsBuilder.java new file mode 100644 index 0000000..a7f34e5 --- /dev/null +++ b/arc/src/main/java/org/apache/james/arc/AuthResultsBuilder.java @@ -0,0 +1,240 @@ +/**************************************************************** + * 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.arc; + +import org.apache.james.arc.exceptions.ArcException; +import org.apache.james.jdkim.DKIMVerifier; +import org.apache.james.jdkim.api.SignatureRecord; +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.tagvalue.SignatureRecordImpl; +import org.apache.james.mime4j.dom.Message; +import org.apache.james.mime4j.message.DefaultMessageWriter; + +import javax.mail.internet.AddressException; +import javax.mail.internet.InternetAddress; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Base64; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +/** + * Builds the Authentication-Results header for email messages by performing SPF, DKIM, and DMARC checks. + *

+ * This class runs SPF and DKIM verifications on the provided message and then evaluates DMARC alignment + * using the results and the message's From domain. It constructs a formatted Authentication-Results header + * string summarizing the authentication status. + *

+ *
    + *
  • SPF: Uses the sender's IP, HELO, and envelope-from address.
  • + *
  • DKIM: Verifies DKIM signatures in the message.
  • + *
  • DMARC: Checks alignment and policy based on DNS records for the From domain.
  • + *
+ *

+ * Throws {@link org.apache.james.arc.exceptions.ArcException} for errors in the authentication process. + *

+ */ +public class AuthResultsBuilder { + public static final String FROM = "From"; + public static final String HEADER_I = "header.i="; + private final PublicKeyRetrieverArc _keyRecordRetriever; + private String _dmarcNoneResponse; + private String _dmarcResponse; + private String _authService; + + public AuthResultsBuilder(String dmarcResponse, String dmarcNoneResponse, String authService, PublicKeyRetrieverArc keyRecordRetriever) { + this._dmarcResponse = dmarcResponse; + this._dmarcNoneResponse = dmarcNoneResponse; + this._authService = authService; + this._keyRecordRetriever = keyRecordRetriever; + } + + public String getAuthResultsHeader(Message message, String helo, String from, String ip) { + + // 1. Run SPF check + String spfResultText = _keyRecordRetriever.getSpfRecord(helo, from, ip); + + + // 2. Run DKIM verification + String dkimResultFull; + try { + dkimResultFull = runDkimCheck(message); + } catch (IOException e) { + throw new ArcException("IO Error while checking DKIM results", e); + } + String dkimResultShort = dkimResultFull.split(" ")[0]; + + // 3. Run DMARC check (using SPF + DKIM results + From domain) + String dkimDomain = extractDkimDomain(dkimResultFull); + String spfDomain = extractSpfDomain(spfResultText); + String dmarcResult = runDmarcCheck(message, spfResultText, spfDomain, dkimResultShort, dkimDomain); + if (dmarcResult == null || dmarcResult.isEmpty()) { + dmarcResult = _dmarcNoneResponse + spfDomain; + } + + return _authService + "; " + + "spf=" + spfResultText.replace(";", "") + "; " + + "dkim=" + dkimResultFull + "; " + + dmarcResult; + + } + + private String runDkimCheck(Message message) throws IOException { + final DKIMVerifier verifier = new DKIMVerifier(_keyRecordRetriever); + InputStream is = messageToInputStream(message); + + // Verify DKIM signatures + List results; + try { + results = verifier.verify(is); + if (!results.isEmpty() && results.stream().allMatch(Objects::nonNull) && results.get(0) != null) { + SignatureRecord signatureRecord = results.get(0); + String iTag = (String) signatureRecord.getIdentity(); + if (iTag == null || iTag.isEmpty()) { + iTag = (String) signatureRecord.getDToken(); + } + iTag = iTag.replace("@", ""); //most implementations drop the leading @ + CharSequence sTag = signatureRecord.getSelector(); + Set tags = ((SignatureRecordImpl) signatureRecord).getTags(); + String bTag = ""; + if (!tags.isEmpty() && tags.contains("b")) { + byte[] signature = signatureRecord.getSignature(); + bTag = Base64.getEncoder().encodeToString(signature); + bTag=bTag.substring(0,8); + } + String outcome = "pass"; + return outcome + " header.i=" + iTag + " header.s=" + sTag+ " header.b=" + bTag; + } + } + catch (PermFailException e) { + throw new ArcException("DKIM PermFail", e); + } catch (TempFailException e) { + throw new ArcException("DKIM TempFail", e); + } catch (FailException e) { + throw new ArcException("DKIM Fail", e); + } catch (Exception e) { + throw new ArcException("DKIM Error", e); + } + return "fail (no valid signature records)"; + } + + private String extractSpfDomain(String spfHeaderText) { + String[] parts = spfHeaderText.split(" "); + for (String part : parts) { + if (part.startsWith("envelope-from=")) { + String[] subParts = part.substring("envelope-from=".length()).split("@"); + if (subParts.length < 2) return null; + String envFrom = subParts[1]; + envFrom = envFrom.replaceAll("[<>]", "").replace(";", ""); + return envFrom.trim(); + } + } + return null; + } + + private String extractDkimDomain(String dkimResultFull) { + String[] parts = dkimResultFull.split(" "); + for (String part : parts) { + if (part.startsWith(HEADER_I)) { + String partValue = part.substring(HEADER_I.length()); + if (partValue.contains("@")) //some implementations drop the leading @ + return partValue.split("@")[1].trim(); + else + return partValue.trim(); + } + } + return null; + } + + private String runDmarcCheck(Message message, String spfHeaderText, String spfDomain, String dkim, String dkimDomain) { + // Combine SPF + DKIM results with From: domain + // 1. Extract RFC5322.From domain from the From header of the message + String shortSpfResut = spfHeaderText.split(" ")[0]; + String fromHeader = message.getHeader().getField(FROM).getBody(); + String fromDomain = getFromDomain(fromHeader); + if (fromDomain == null || fromDomain.isEmpty()) { + return _dmarcNoneResponse + "unknown"; + } + try { + fromDomain = new InternetAddress(fromHeader).getAddress().split("@")[1]; + } catch (AddressException e) { + throw new ArcException("Internet address error", e); + } + + // 2. Fetch DMARC record from DNS + ARCVerifier arcVerifier = new ARCVerifier(_keyRecordRetriever); + + String dmarcRecord = arcVerifier.getPublicKeyRecordRetriever().getDmarcRecord(fromDomain); + if (dmarcRecord == null) { + return _dmarcNoneResponse + fromDomain; + } + + // Parse DMARC policy + String policy = arcVerifier.parseTagGeneric(dmarcRecord, "p"); // p=none|quarantine|reject +// String aspf = parseTag(dmarcRecord, "aspf"); // optional +// String adkim = parseTag(dmarcRecord, "adkim"); // optional + + // 3. Alignment checks + boolean spfAligned = "pass".equals(shortSpfResut) && fromDomain.equalsIgnoreCase(spfDomain); + boolean dkimAligned = "pass".equals(dkim) && fromDomain.equalsIgnoreCase(dkimDomain); + + // 4. DMARC result logic + String result; + if (spfAligned || dkimAligned) { + result = "pass"; + } else { + result = "fail"; + } + + // 5. Build Authentication-Results string + return String.format(_dmarcResponse, result, policy, fromDomain); + } + + private String getFromDomain(String fromHeader) { + String fromDomain = null; + if (fromHeader.contains("<") && fromHeader.contains(">")) { + int start = fromHeader.indexOf('<'); + int end = fromHeader.indexOf('>'); + if (start != -1 && end != -1 && end > start) { + String email = fromHeader.substring(start + 1, end); + fromDomain = email.split("@")[1]; + } + } + else { + try { + fromDomain = new InternetAddress(fromHeader).getAddress().split("@")[1]; + } catch (AddressException e) { + throw new ArcException("Internet address error", e); + } + } + return fromDomain; + } + + private InputStream messageToInputStream(Message message) throws IOException { + DefaultMessageWriter writer = new DefaultMessageWriter(); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + writer.writeEntity(message,os); + return new ByteArrayInputStream(os.toByteArray()); + } +} diff --git a/arc/src/main/java/org/apache/james/arc/DNSPublicKeyRecordRetrieverArc.java b/arc/src/main/java/org/apache/james/arc/DNSPublicKeyRecordRetrieverArc.java new file mode 100644 index 0000000..d08e210 --- /dev/null +++ b/arc/src/main/java/org/apache/james/arc/DNSPublicKeyRecordRetrieverArc.java @@ -0,0 +1,66 @@ +package org.apache.james.arc; + +import org.apache.james.arc.exceptions.ArcException; +import org.apache.james.jdkim.impl.DNSPublicKeyRecordRetriever; +import org.apache.james.jspf.impl.DefaultSPF; +import org.apache.james.jspf.impl.SPF; + +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.InitialDirContext; +import java.util.Hashtable; + +public class DNSPublicKeyRecordRetrieverArc extends DNSPublicKeyRecordRetriever implements PublicKeyRetrieverArc { + public static final String JAVA_NAMING_FACTORY_INITIAL = "java.naming.factory.initial"; + public static final String COM_SUN_JNDI_DNS_DNS_CONTEXT_FACTORY = "com.sun.jndi.dns.DnsContextFactory"; + public static final String TXT = "TXT"; + + public DNSPublicKeyRecordRetrieverArc() { + super(); + } + + @Override + public String getSpfRecord(String helo, String from, String ip) { + Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); + SPF spf = new DefaultSPF(); + return spf.checkSPF(ip, from, helo).getHeaderText(); + } + + @Override + public String getDmarcRecord(String dnsLabel) { + Hashtable env = new Hashtable<>(); + env.put(JAVA_NAMING_FACTORY_INITIAL, COM_SUN_JNDI_DNS_DNS_CONTEXT_FACTORY); + DirContext ctx; + dnsLabel = "_dmarc." + dnsLabel; + try { + ctx = new InitialDirContext(env); + } catch (NamingException e) { + throw new ArcException(String.format("Naming error when creating InitialDirContext using [%s]", dnsLabel), e); + } + + Attributes attrs; + try { + attrs = ctx.getAttributes(dnsLabel, new String[]{TXT}); + } catch (NamingException e) { + throw new ArcException(String.format("Naming error when getting attributes using [%s]", dnsLabel), e); + } + + Attribute txtAttr = attrs.get(TXT); + try { + if (txtAttr != null) { + StringBuilder sb = new StringBuilder(); + NamingEnumeration e = txtAttr.getAll(); + while (e.hasMore()) { + sb.append(e.next().toString().replace("\"", "")); + } + return sb.toString(); + } + } catch (NamingException e) { + throw new ArcException(String.format("Naming error when looping through attributes using [%s]", dnsLabel), e); + } + return null; + } +} diff --git a/arc/src/main/java/org/apache/james/arc/PublicKeyRetrieverArc.java b/arc/src/main/java/org/apache/james/arc/PublicKeyRetrieverArc.java new file mode 100644 index 0000000..905d75a --- /dev/null +++ b/arc/src/main/java/org/apache/james/arc/PublicKeyRetrieverArc.java @@ -0,0 +1,10 @@ +package org.apache.james.arc; + +import org.apache.james.jdkim.api.PublicKeyRecordRetriever; + +public interface PublicKeyRetrieverArc extends PublicKeyRecordRetriever { + + String getDmarcRecord(String query); + + String getSpfRecord(String helo, String from, String ip); +} diff --git a/arc/src/main/java/org/apache/james/arc/exceptions/ArcException.java b/arc/src/main/java/org/apache/james/arc/exceptions/ArcException.java new file mode 100644 index 0000000..e5e4549 --- /dev/null +++ b/arc/src/main/java/org/apache/james/arc/exceptions/ArcException.java @@ -0,0 +1,29 @@ +/**************************************************************** + * 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.arc.exceptions; + +public class ArcException extends RuntimeException { + public ArcException(String message, Throwable cause) { + super(message, cause); + } + + public ArcException(String message) { + super(message); + } +} diff --git a/arc/src/test/java/org/apache/james/arc/ARCTest.java b/arc/src/test/java/org/apache/james/arc/ARCTest.java new file mode 100644 index 0000000..a4bebc6 --- /dev/null +++ b/arc/src/test/java/org/apache/james/arc/ARCTest.java @@ -0,0 +1,137 @@ +/****************************************************************************** + * 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.arc; + +import org.apache.commons.codec.binary.Base64; +import org.apache.james.jdkim.DKIMCommon; +import org.apache.james.jdkim.MockPublicKeyRecordRetriever; +import org.apache.james.mime4j.dom.Message; +import org.apache.james.mime4j.message.DefaultMessageBuilder; +import org.apache.james.mime4j.stream.RawField; +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ARCTest { + public static final String AUTHENTICATION_RESULTS = "Authentication-Results"; + public static final String ARC_AUTHENTICATION_RESULTS = "ARC-Authentication-Results"; + public static final String ARC_MESSAGE_SIGNATURE = "ARC-Message-Signature"; + public static final String ARC_SEAL = "ARC-Seal"; + + private final MockPublicKeyRecordRetrieverArc keyRecordRetriever = new MockPublicKeyRecordRetrieverArc( + MockPublicKeyRecordRetriever.Record.of( + "arc", + "dmarc.example", + "k=rsa; p=" + Base64.encodeBase64String(ArcTestKeys.publicKeyArc.getEncoded()) + ";" + ), + MockPublicKeyRecordRetriever.Record.of( + "origin2015", + "d1.example", + "k=rsa; p=" + Base64.encodeBase64String(ArcTestKeys.publicKeyDkim.getEncoded()) + ";" + ), + MockPublicKeyRecordRetrieverArc.DmarcRecord.dmarcOf("", + "d1.example", + "k=rsa; v=DMARC1; p=reject; pct=100; rua=mailto:noc@d1.example" + ), + MockPublicKeyRecordRetrieverArc.SpfRecord.spfOf("d1.example", + "jqd@d1.example", + "222.222.222.222", + "softfail (spfCheck: transitioning domain of d1.example does not designate 222.222.222.222 as permitted sender) client-ip=222.222.222.222; envelope-from=jqd@d1.example; helo=d1.example") + ); + + /** + * - "a" field will be added by the signer based on signer setup + * - "bh=" and "b=" placeholder are required for now because the same implementation is used for + * signing and verifying. The fields are mandatory for verifying. + */ + //Todo: replace freemarker templates with something less heavy weight + private static final String ARC_AMS_TEMPLATE = "i=${instance}; a=rsa-sha256; c=relaxed/relaxed; d=dmarc.example; s=arc; t=${timestamp}; h=Subject:From:To; bh=; b="; + private static final String ARC_SEAL_TEMPLATE = "i=${instance}; cv=${cv}; a=rsa-sha256; d=dmarc.example; s=arc; t=${timestamp}; b="; + + private static final String AUTH_SERVICE = "smtp.d1.example"; + private static final String HELO = "d1.example"; + + private static final String MAIL_FROM = "jqd@d1.example"; + private static final String IP = "222.222.222.222"; + private static final long TIMESTAMP = 1755918846L; // fixed timestamp for repeatable tests + + private static final String DMARC_RESPONSE_TEMPLATE = "dmarc=%s (p=%s) header.from=%s"; + private static final String DMARC_NON_RESPONSE_TEMPLATE = "dmarc=none (no policy) header.from="; + + ArcSetBuilder arcSetBuilder = new ArcSetBuilder(ArcTestKeys.privateKeyArc, ARC_AMS_TEMPLATE, ARC_SEAL_TEMPLATE, DMARC_RESPONSE_TEMPLATE, DMARC_NON_RESPONSE_TEMPLATE, AUTH_SERVICE, TIMESTAMP); + + @Test + public void generate_and_verify_arc_set() throws Exception { + String expectedCv = "pass"; + String authResultsExp = "smtp.d1.example; spf=softfail (spfCheck: transitioning domain of d1.example does not designate 222.222.222.222 as permitted sender) client-ip=222.222.222.222 envelope-from=jqd@d1.example helo=d1.example; dkim=pass header.i=d1.example header.s=origin2015 header.b=iEn8fLQ/; dmarc=pass (p=reject) header.from=d1.example"; + String arcAuthResultsExp = "i=1; smtp.d1.example; spf=softfail (spfCheck: transitioning domain of d1.example does not designate 222.222.222.222 as permitted sender) client-ip=222.222.222.222 envelope-from=jqd@d1.example helo=d1.example; dkim=pass header.i=d1.example header.s=origin2015 header.b=iEn8fLQ/; dmarc=pass (p=reject) header.from=d1.example"; + String arcSignExp = "i=1; a=rsa-sha256; c=relaxed/relaxed; d=dmarc.example; s=arc; t=1755918846; h=subject : from : to; bh=KWSe46TZKCcDbH4klJPo+tjk5LWJnVRlP5pvjXFZYLQ=; b=FL3H8cG2U7RcyMSdx4j8iAD/7Uhzhl4XmWicLD+Uuxf3VsVghJ/lswvdQrjnyr6R9oyfPzP7rE2BEX0CFKlSvTVWy5/+8Vc3CXqj+tnKYoHnuWxH4sH0jMTpHzgceGLgMXvamilPyYWrCeF3r5yaUPYQ04fhfeAFAs6OTLeKvL0="; + String arcSealExp = "i=1; cv=none; a=rsa-sha256; d=dmarc.example; s=arc; t=1755918846; b=LsqQnv1KZhtbEX6SYLn0gk0t+Pjg3WmLu0aqNVwHa3nMcRq1dt4wJX1ka9lZAY/RARH74hwtfGnW1ba1gXLZ2WhevLwXvQcuw3NK6aC2YcYCjQ9kQWmlpvLe96xXsASl8MPXWyOmTEOdCeH06mkf3jahb4+bBjp1875568hTFhQ="; + + ByteArrayInputStream emailStream = readFileToByteArrayInputStream("/mail/rfc8617_no_arc.eml"); + DefaultMessageBuilder builder = new DefaultMessageBuilder(); + Message message = builder.parseMessage(emailStream); + +// Map arcSet = arcSetBuilder.buildArcSet(message, HELO , MAIL_FROM,IP, new DNSPublicKeyRecordRetrieverArc()); // use this for real/external DNS lookups + Map arcSet = arcSetBuilder.buildArcSet(message, HELO , MAIL_FROM,IP, keyRecordRetriever); // mock DNS for testing + + assertThat(arcSet).hasSize(4); + + String authResults = arcSet.get(AUTHENTICATION_RESULTS); + String arcAuthResults = arcSet.get(ARC_AUTHENTICATION_RESULTS); + String arcMsgSignature = arcSet.get(ARC_MESSAGE_SIGNATURE); + String arcSeal = arcSet.get(ARC_SEAL); + assertThat(authResults).isEqualTo(authResultsExp); + assertThat(arcAuthResults).isEqualTo(arcAuthResultsExp); + assertThat(arcMsgSignature).isEqualTo(arcSignExp); + assertThat(arcSeal).isEqualTo(arcSealExp); + + //add new ARC set to the message and do chain validation on it + for (Map.Entry entry: arcSet.entrySet()){ + message.getHeader().addField(new RawField(entry.getKey(), entry.getValue())); + } + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + String cv = arcChainValidator.validateArcChain(message); + assertThat(cv).isEqualTo(expectedCv); + + } + + private ByteArrayInputStream readFileToByteArrayInputStream(String fileName) throws URISyntaxException, IOException { + URL resource = this.getClass().getResource(fileName); + FileInputStream file = new FileInputStream(new File(resource.toURI())); + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + + DKIMCommon.streamCopy(file, byteArrayOutputStream); + String string = byteArrayOutputStream.toString(); + return new ByteArrayInputStream(string.getBytes(StandardCharsets.UTF_8)); + } + +} \ No newline at end of file diff --git a/arc/src/test/java/org/apache/james/arc/ArcTestKeys.java b/arc/src/test/java/org/apache/james/arc/ArcTestKeys.java new file mode 100644 index 0000000..cec4e7e --- /dev/null +++ b/arc/src/test/java/org/apache/james/arc/ArcTestKeys.java @@ -0,0 +1,80 @@ +/****************************************************************************** + * 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.arc; + +import org.apache.commons.codec.binary.Base64; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; + +public class ArcTestKeys { + public static final PrivateKey privateKeyArc = loadPrivateKey("keys/arc_test_pri.1.key"); + public static final PublicKey publicKeyArc = loadPublicKey("keys/arc_test_pub.1.pem"); + public static final PrivateKey privateKeyDkim = loadPrivateKey("keys/dkim_test_pri.1.key"); + public static final PublicKey publicKeyDkim = loadPublicKey("keys/dkim_test_pub.1.pem"); + public static final KeyPair keyPair = new KeyPair(publicKeyArc, privateKeyArc); + + private static PublicKey loadPublicKey(String uri) { + try { + String keyText = readFileContent(uri) + .replace("-----BEGIN PUBLIC KEY-----", "") + .replace("-----END PUBLIC KEY-----", "") + .replaceAll(System.lineSeparator(), ""); + byte[] encoded = Base64.decodeBase64(keyText); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encoded); + return keyFactory.generatePublic(keySpec); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static String readFileContent(String uri) throws URISyntaxException, IOException { + URL resource = ArcTestKeys.class.getClassLoader().getResource(uri); + File file = new File(resource.toURI()); + return new String(Files.readAllBytes(file.toPath()), Charset.defaultCharset()); + } + + private static PrivateKey loadPrivateKey(String uri) { + try { + String keyText = readFileContent(uri) + .replace("-----BEGIN PRIVATE KEY-----", "") + .replace("-----END PRIVATE KEY-----", "") + .replaceAll(System.lineSeparator(), ""); + byte[] encoded = Base64.decodeBase64(keyText); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded); + return keyFactory.generatePrivate(keySpec); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + +} diff --git a/arc/src/test/java/org/apache/james/arc/MockPublicKeyRecordRetrieverArc.java b/arc/src/test/java/org/apache/james/arc/MockPublicKeyRecordRetrieverArc.java new file mode 100644 index 0000000..2f78bc3 --- /dev/null +++ b/arc/src/test/java/org/apache/james/arc/MockPublicKeyRecordRetrieverArc.java @@ -0,0 +1,70 @@ +package org.apache.james.arc; + +import org.apache.james.arc.exceptions.ArcException; +import org.apache.james.jdkim.MockPublicKeyRecordRetriever; +import org.apache.james.jdkim.exceptions.PermFailException; +import org.apache.james.jdkim.exceptions.TempFailException; + +import java.util.List; + +public class MockPublicKeyRecordRetrieverArc extends MockPublicKeyRecordRetriever implements PublicKeyRetrieverArc { + + public static final String DMARC = "_dmarc."; + public static final String SPF = "_spf."; + public static class SpfRecord extends MockPublicKeyRecordRetriever.Record { + + public SpfRecord(String helo, String from, String ip, String spfRecord) { + super(SPF, ip + helo + from, spfRecord); + } + + public static SpfRecord spfOf(String helo, String from, String ip, String spfRecord) { + return new SpfRecord(helo, from, ip, spfRecord); + } + } + + public static class DmarcRecord extends MockPublicKeyRecordRetriever.Record { + + public DmarcRecord(String selector, String domain, String dmarcRecord) { + super(DMARC, domain, dmarcRecord); + } + + public static DmarcRecord dmarcOf(String selector, String domain, String dmarcRecord) { + return new DmarcRecord(selector, domain, dmarcRecord); + } + } + + public MockPublicKeyRecordRetrieverArc(Record... records) { + super(records); + } + + @Override + public String getSpfRecord(String helo, String from, String ip) { + try { + String token = ip + helo + from; + List recs = super.getRecords("dns/txt", SPF,token); + if (recs.isEmpty()) { + return null; + } + return recs.get(0); //TODO: multiple records? + } catch (TempFailException e) { + throw new ArcException("Temporary failure looking up DMARC record", e); + } catch (PermFailException e) { + throw new ArcException("Permanent failure looking up DMARC record", e); + } + } + + @Override + public String getDmarcRecord(String searchKey){ + try { + List recs = super.getRecords("dns/txt", DMARC,searchKey); + if (recs.isEmpty()) { + return null; + } + return recs.get(0); //TODO: multiple records? + } catch (TempFailException e) { + throw new ArcException("Temporary failure looking up DMARC record", e); + } catch (PermFailException e) { + throw new ArcException("Permanent failure looking up DMARC record", e); + } + } +} diff --git a/arc/src/test/resources/keys/arc_test_pri.1.key b/arc/src/test/resources/keys/arc_test_pri.1.key new file mode 100644 index 0000000..f196261 --- /dev/null +++ b/arc/src/test/resources/keys/arc_test_pri.1.key @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAKmDF45YX+LMhwzV +4ZsskhMLGXRBGxs96cjNdgbsmhzUcpjW0Zaxzi7IimWR6IAPSZXkC+DjZ6UABxk7 +YfD8VZ7QV0SzMXk0RAnOHOLrxuQrav+jflKkvl5cdkstWLQLCFBIbsDsHebDM+sv +35Efo9yXQDEOTt8v56n81BAvzy5fAgMBAAECgYEAh14YwaPxbrzGXImw0KqXPH3w +pdYYP3kB6Umqp3zq1XsSyNtEJIN5lAKyAsqyURHkQb8LfVwcuLd887loTXo1JIFO +355B6JtZPnejIUinfmMgmDr3y1IXQf0RX8132X9C5o6r2SslIYrAHSjWWZlFAV6o +qGkVfieO/+c2vFjj/IECQQDZ67UuPUBybw/Ul/uj3TdmQ6YY+C8pBJGvorvOWYVC +Z6IznOissegIrPf9IyYNWmOG2628UCLpWtxy7ZmByCQ7AkEAxyHo/RS+6gzjCgZi +zScTyo8SV88gshs3t+xiiCBBEAWIgdQ7h6rbWqVvH7Nn8VhKerQmkcpDc3y47Mr5 +xeVwLQJBALCrOdCJ0dS0G2Zj7Js1PbOHhoHZuwoK7T0xtgYdZz6lm8cyHyPae12F +NOsg8rmCnQt4z0nKwfLjObNm0rt3kX8CQBrqMG2UkkFcQIuoVU5ZS8mDEP2hV0/7 +ccqAPskbYu/hb5PstacepstXtO9Z9mCeiGKRWu01o2xGnVAUFzJyUnkCQQCcEZzG +g+5OX+wNEMyfTY/kEUoue+ZtqXbuBOPnDOb+k+3Hrh8+q3tGwKg7w2jUU9DEwLtC +FHkx1Nnb8sURPTqy +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/arc/src/test/resources/keys/arc_test_pub.1.pem b/arc/src/test/resources/keys/arc_test_pub.1.pem new file mode 100644 index 0000000..288f28d --- /dev/null +++ b/arc/src/test/resources/keys/arc_test_pub.1.pem @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCpgxeOWF/izIcM1eGbLJITCxl0 +QRsbPenIzXYG7Joc1HKY1tGWsc4uyIplkeiAD0mV5Avg42elAAcZO2Hw/FWe0FdE +szF5NEQJzhzi68bkK2r/o35SpL5eXHZLLVi0CwhQSG7A7B3mwzPrL9+RH6Pcl0Ax +Dk7fL+ep/NQQL88uXwIDAQAB +-----END PUBLIC KEY----- \ No newline at end of file diff --git a/arc/src/test/resources/keys/dkim_test_pri.1.key b/arc/src/test/resources/keys/dkim_test_pri.1.key new file mode 100644 index 0000000..6bc101e --- /dev/null +++ b/arc/src/test/resources/keys/dkim_test_pri.1.key @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBANLJL27JQ8P6OKae +DYzvu2OnAjgkBxcadreX0HtSNFvuHJYwUWrw93kQ3ZSajAS/TqD2LS8+pZ/0Ak5+ +S5WmBQXN/bBPWjbqos/vEFOu5uTx7qn1fSqMlJEGxFzq08mO4ZRIRwawKQ7AohPt +Ew6vEb205AU+moCUtbteU/rD2wV5AgMBAAECgYAaBeKIP+rQ2CSEVYEAxFwTKnw4 +qCID9S1w7xo7D2QNcXEwDZkPpd43oSBqB0aAE4pGjv33FjnmbH6YaDk2qX93BZY0 +ggXM0IMYkvmgnIh0/DKli2w0Fjx5pQJRl60FUnFlcQrdwp0HOfmDS28RHZZGMnLV +lv0a4bDtBK7/NxmFPQJBAPIewEh5a86hH5hF6S7NZ/cJX8twqbiq0xbY3Hm+3GcC +qbSuqxDgYZgtNySyP3N6GSTdZDYMUwspvpPciXJgbLcCQQDe3pYpa1m7qlPqgwyO +TcXbrbTKB3TSgMunVrYCMgkMMdEn2dPW4dnlpA+ZMPvjTd1tuh9AWLXRUAnm+Uv6 +209PAkEAnC9CEn5hEPXXD79pYIuYWT9u0ClpEnr/mGlkMBTy0HBjUO6r40MbMbNZ +Mw7Y54EH30QBdOwWVckj6vYEpAeXmQJAJVbdiar2qb5ruMqj++OD1r5Pn9mH9Qyn +Ei4w6EVBxs1B4Y9ZMpM8UoEeK+hNC1QsWQnp2noCXEMwpYX2+NxteQJAPofx2296 +wdac8Qhy8l7tk+WLPQWWbFlCmLkaX7Fd6z2KqUXXTt/YoL+LpxoWmgIkAKmdxssC +nIVcOf1X+NIxRg== +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/arc/src/test/resources/keys/dkim_test_pub.1.pem b/arc/src/test/resources/keys/dkim_test_pub.1.pem new file mode 100644 index 0000000..27d7983 --- /dev/null +++ b/arc/src/test/resources/keys/dkim_test_pub.1.pem @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDSyS9uyUPD+jimng2M77tjpwI4 +JAcXGna3l9B7UjRb7hyWMFFq8Pd5EN2UmowEv06g9i0vPqWf9AJOfkuVpgUFzf2w +T1o26qLP7xBTrubk8e6p9X0qjJSRBsRc6tPJjuGUSEcGsCkOwKIT7RMOrxG9tOQF +PpqAlLW7XlP6w9sFeQIDAQAB +-----END PUBLIC KEY----- \ No newline at end of file diff --git a/arc/src/test/resources/mail/rfc8617_no_arc.eml b/arc/src/test/resources/mail/rfc8617_no_arc.eml new file mode 100644 index 0000000..2470990 --- /dev/null +++ b/arc/src/test/resources/mail/rfc8617_no_arc.eml @@ -0,0 +1,31 @@ +Return-Path: +Received: from example.org (example.org [208.69.40.157]) + by gmail.example with ESMTP id d200mr22663000ykb.93.1421363207 + for ; Thu, 14 Jan 2015 15:02:40 -0800 (PST) +Received: from segv.d1.example (segv.d1.example [72.52.75.15]) + by lists.example.org (8.14.5/8.14.5) with ESMTP id t0EKaNU9010123 + for ; Thu, 14 Jan 2015 15:01:30 -0800 (PST) + (envelope-from jqd@d1.example) +Received: from [2001:DB8::1A] (w-x-y-z.dsl.static.isp.example [w.x.y.z]) + (authenticated bits=0) + by segv.d1.example with ESMTP id t0FN4a8O084569; + Thu, 14 Jan 2015 15:00:01 -0800 (PST) + (envelope-from jqd@d1.example) +Received: from mail-ob0-f188.google.example + (mail-ob0-f188.google.example [208.69.40.157]) by + clochette.example.org with ESMTP id d200mr22663000ykb.93.1421363268 + for ; Thu, 14 Jan 2015 15:03:15 -0800 (PST) +Message-ID: <54B84785.1060301@d1.example> +Date: Thu, 14 Jan 2015 15:00:01 -0800 +From: John Q Doe +To: arc@dmarc.example +Subject: [List 2] Example 1 +DKIM-Signature: a=rsa-sha256; + b=iEn8fLQ/ymdoZ4EkI3ELK3dTcc4jqn1VOvbNZWAMzcZcFiSKSZXgJ9kgXlBv8JGqaLFjuQi3+p73Al9P2JJU4IkBF1PSHrTI6rcdPyTWMP5yL6vKrn0tu0VdPhwPmbEr4H0yhYqc0KPPPzbJw668zoharH9Ljq43W8mj6sGSN18=; + c=relaxed/relaxed; s=origin2015; d=d1.example; v=1; + bh=KWSe46TZKCcDbH4klJPo+tjk5LWJnVRlP5pvjXFZYLQ=; h=Subject:From:To; + +Hey gang, +This is a test message. +--J. + diff --git a/pom.xml b/pom.xml index 3f78004..f22cc0b 100644 --- a/pom.xml +++ b/pom.xml @@ -40,6 +40,7 @@ assemble main + arc From b68a0d8b74fbf5460098f2c167de0b5d96805152 Mon Sep 17 00:00:00 2001 From: Artem Grinchenko Date: Wed, 8 Oct 2025 19:23:30 -0400 Subject: [PATCH 074/114] Update arc/src/main/java/org/apache/james/arc/ArcSealVerifyData.java Co-authored-by: Benoit TELLIER --- .../main/java/org/apache/james/arc/ArcSealVerifyData.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/arc/src/main/java/org/apache/james/arc/ArcSealVerifyData.java b/arc/src/main/java/org/apache/james/arc/ArcSealVerifyData.java index 2af6fcb..b8123aa 100644 --- a/arc/src/main/java/org/apache/james/arc/ArcSealVerifyData.java +++ b/arc/src/main/java/org/apache/james/arc/ArcSealVerifyData.java @@ -33,6 +33,8 @@ public void setB64Signature(String _b64) { public String getB64Signature() { return _b64; } - public String getSignedData() { return - _dataToVerify; } + + public String getSignedData() { + return _dataToVerify; + } } \ No newline at end of file From bb93a645b4f605b71e4e6f762d14d9ee369df618 Mon Sep 17 00:00:00 2001 From: Artem Grinchenko Date: Wed, 8 Oct 2025 19:28:22 -0400 Subject: [PATCH 075/114] Update arc/src/main/java/org/apache/james/arc/ARCChainValidator.java Co-authored-by: Benoit TELLIER --- arc/src/main/java/org/apache/james/arc/ARCChainValidator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java b/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java index 2d251d8..ef10b6a 100644 --- a/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java +++ b/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java @@ -78,7 +78,7 @@ private String validatePreviousArcHops(Message message, Header messageHeaders, i int numArcInstances = myInstance -1; boolean isArcSetStructureOK = arcVerifier.validateArcSetStructure(arcHeadersByI); if (!isArcSetStructureOK) { - return "fail"; + return "fail"; } Set prevArcSet; From 90dd4a270e2356d5047aa3723a5a82ca10bbfdd6 Mon Sep 17 00:00:00 2001 From: Artem Grinchenko Date: Wed, 8 Oct 2025 19:28:58 -0400 Subject: [PATCH 076/114] Update arc/src/main/java/org/apache/james/arc/ARCChainValidator.java Co-authored-by: Benoit TELLIER --- arc/src/main/java/org/apache/james/arc/ARCChainValidator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java b/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java index ef10b6a..d6d52ac 100644 --- a/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java +++ b/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java @@ -84,8 +84,8 @@ private String validatePreviousArcHops(Message message, Header messageHeaders, i Set prevArcSet; prevArcSet = arcVerifier.extractArcSet(messageHeaders, numArcInstances); if (prevArcSet != null) { - boolean amsOk = checkArcAms (prevArcSet, message, arcVerifier); - boolean asOk = checkArcSeal (messageHeaders.getFields(), numArcInstances, arcVerifier); + boolean amsOk = checkArcAms(prevArcSet, message, arcVerifier); + boolean asOk = checkArcSeal(messageHeaders.getFields(), numArcInstances, arcVerifier); if (amsOk && asOk) { return "pass"; } From 95d9aba3b07b1ddbfb76f90691ab1035c2ca4ac7 Mon Sep 17 00:00:00 2001 From: Artem Grinchenko Date: Wed, 8 Oct 2025 19:29:34 -0400 Subject: [PATCH 077/114] Update arc/src/main/java/org/apache/james/arc/ARCChainValidator.java Co-authored-by: Benoit TELLIER --- arc/src/main/java/org/apache/james/arc/ARCChainValidator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java b/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java index d6d52ac..918e579 100644 --- a/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java +++ b/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java @@ -75,7 +75,7 @@ else if (curInstance > 51) { // Not allowed to be > 50 private String validatePreviousArcHops(Message message, Header messageHeaders, int myInstance) { ARCVerifier arcVerifier = new ARCVerifier(_keyRecordRetriever); Map> arcHeadersByI = arcVerifier.getArcHeadersByI(messageHeaders.getFields()); - int numArcInstances = myInstance -1; + int numArcInstances = myInstance - 1; boolean isArcSetStructureOK = arcVerifier.validateArcSetStructure(arcHeadersByI); if (!isArcSetStructureOK) { return "fail"; From f766428a80f3b4fc6b246080d4aa30c6ce54c46c Mon Sep 17 00:00:00 2001 From: Artem Grinchenko Date: Wed, 8 Oct 2025 21:49:50 -0400 Subject: [PATCH 078/114] Refactor ARC to minimize dependencies and address some initial PR feedback - Introduced ArcValidationResult enum - Made ArcSealVerifyData immutable pojo - Removed dependency on geronimo.javamail - Removed dependency on Freemarker and simplified ARC templates - Updated versions in pom to pull them from the parent pom - Fixed missing licensing headers - Applied suggested style fixes --- arc/pom.xml | 16 +---- .../apache/james/arc/ARCChainValidator.java | 14 ++--- .../java/org/apache/james/arc/ARCCommon.java | 7 --- .../org/apache/james/arc/ARCVerifier.java | 24 ++++--- .../apache/james/arc/ArcSealVerifyData.java | 23 +++---- .../org/apache/james/arc/ArcSetBuilder.java | 29 ++++----- .../apache/james/arc/ArcValidationResult.java | 7 +++ .../apache/james/arc/AuthResultsBuilder.java | 63 +++++++++++-------- .../arc/DNSPublicKeyRecordRetrieverArc.java | 18 ++++++ .../james/arc/PublicKeyRetrieverArc.java | 18 ++++++ .../java/org/apache/james/arc/ARCTest.java | 7 +-- pom.xml | 1 + 12 files changed, 127 insertions(+), 100 deletions(-) create mode 100644 arc/src/main/java/org/apache/james/arc/ArcValidationResult.java diff --git a/arc/pom.xml b/arc/pom.xml index f586c09..b2d3eca 100644 --- a/arc/pom.xml +++ b/arc/pom.xml @@ -42,29 +42,19 @@ org.apache.james.jdkim apache-jdkim-library - 0.6-SNAPSHOT + ${project.version} org.apache.james.jdkim apache-jdkim-library - 0.6-SNAPSHOT + ${project.version} test-jar test org.apache.james.jspf apache-jspf-resolver - 1.0.5 - - - org.freemarker - freemarker - 2.3.31 - - - org.apache.geronimo.javamail - geronimo-javamail_1.4_mail - 1.6 + ${jspf-resolver.version} junit diff --git a/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java b/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java index 918e579..d50c094 100644 --- a/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java +++ b/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java @@ -56,29 +56,29 @@ public ARCChainValidator(PublicKeyRetrieverArc keyRecordRetriever) { this._keyRecordRetriever = keyRecordRetriever; } - public String validateArcChain(Message message) { + public ArcValidationResult validateArcChain(Message message) { Header messageHeaders = message.getHeader(); int curInstance = getCurrentInstance(messageHeaders); // Incremented by 1 if (curInstance == 1) { //we are the first ARC Hop and there is no previous ARC hops in the chain to validate - return "none"; + return ArcValidationResult.NONE; } else if (curInstance > 51) { // Not allowed to be > 50 - return "fail"; + return ArcValidationResult.FAIL; } else { // there are previous ARC hops that need to be validated return validatePreviousArcHops(message, messageHeaders, curInstance); } } - private String validatePreviousArcHops(Message message, Header messageHeaders, int myInstance) { + private ArcValidationResult validatePreviousArcHops(Message message, Header messageHeaders, int myInstance) { ARCVerifier arcVerifier = new ARCVerifier(_keyRecordRetriever); Map> arcHeadersByI = arcVerifier.getArcHeadersByI(messageHeaders.getFields()); int numArcInstances = myInstance - 1; boolean isArcSetStructureOK = arcVerifier.validateArcSetStructure(arcHeadersByI); if (!isArcSetStructureOK) { - return "fail"; + return ArcValidationResult.FAIL; } Set prevArcSet; @@ -87,10 +87,10 @@ private String validatePreviousArcHops(Message message, Header messageHeaders, i boolean amsOk = checkArcAms(prevArcSet, message, arcVerifier); boolean asOk = checkArcSeal(messageHeaders.getFields(), numArcInstances, arcVerifier); if (amsOk && asOk) { - return "pass"; + return ArcValidationResult.PASS; } } - return "fail"; + return ArcValidationResult.FAIL; } private boolean checkArcAms(Set prevArcSet, Message message, ARCVerifier arcVerifier){ diff --git a/arc/src/main/java/org/apache/james/arc/ARCCommon.java b/arc/src/main/java/org/apache/james/arc/ARCCommon.java index 47c186f..44c1cab 100644 --- a/arc/src/main/java/org/apache/james/arc/ARCCommon.java +++ b/arc/src/main/java/org/apache/james/arc/ARCCommon.java @@ -18,7 +18,6 @@ ****************************************************************/ package org.apache.james.arc; -import org.apache.james.arc.exceptions.ArcException; import org.apache.james.jdkim.api.Headers; import org.apache.james.jdkim.api.SignatureRecord; import org.apache.james.jdkim.exceptions.PermFailException; @@ -26,14 +25,8 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.security.KeyFactory; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; import java.security.Signature; import java.security.SignatureException; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.PKCS8EncodedKeySpec; -import java.util.Base64; import java.util.HashMap; import java.util.List; import java.util.Map; diff --git a/arc/src/main/java/org/apache/james/arc/ARCVerifier.java b/arc/src/main/java/org/apache/james/arc/ARCVerifier.java index b84f57e..49d273f 100644 --- a/arc/src/main/java/org/apache/james/arc/ARCVerifier.java +++ b/arc/src/main/java/org/apache/james/arc/ARCVerifier.java @@ -308,7 +308,7 @@ public String parseTagGeneric(String record, String tag) { } public ArcSealVerifyData buildArcSealSigningData(Map> headersByI, int targetI) { - ArcSealVerifyData result = new ArcSealVerifyData(); + ArcSealVerifyData result = null; StringBuilder signingData = new StringBuilder(); //Iterate over hops in ascending i order, for the last hop, make sure to clear b= tag on the ARC-Seal @@ -336,18 +336,16 @@ public ArcSealVerifyData buildArcSealSigningData(Map> heade .append(":").append(canonicalizeBody(f.getBody())) .append("\r\n")); - if (hopI == targetI) { // this is last hop so we need to clear b= tag on the ARC-Seal Header and not tail it with CRLF - as.ifPresent(f -> { - Map tags = parseTagList(f.getBody()); - String signatureB64 = tags.get("b"); - String b64 = signatureB64 - .replaceAll("\\s+", "") // remove spaces, tabs, newlines - .replace(";", ""); - String arcSealBodyClearedB= f.getBody().replaceAll("\\bb=([^;]*)", "b="); - signingData.append(f.getName().toLowerCase(Locale.ROOT)).append(":").append(canonicalizeBody(arcSealBodyClearedB)); - result.setB64Signature(b64); - result.setSignedData(signingData.toString()); - }); + if (hopI == targetI && as.isPresent()) { // this is last hop so we need to clear b= tag on the ARC-Seal Header and not tail it with CRLF + Field asField = as.get(); + Map tags = parseTagList(asField.getBody()); + String signatureB64 = tags.get("b"); + String b64 = signatureB64.replaceAll("\\s+", "").replace(";", ""); + String arcSealBodyClearedB = asField.getBody().replaceAll("\\bb=([^;]*)", "b="); + signingData.append(asField.getName().toLowerCase(Locale.ROOT)) + .append(":").append(canonicalizeBody(arcSealBodyClearedB)); + result = new ArcSealVerifyData(b64, signingData.toString()); + break; // we have the target hop, can exit loop } else { // this is one of the previous hops, not the last one, so we want to preserve b= tag on the ARC-Seal and tail it with CRLF as.ifPresent(f -> signingData diff --git a/arc/src/main/java/org/apache/james/arc/ArcSealVerifyData.java b/arc/src/main/java/org/apache/james/arc/ArcSealVerifyData.java index b8123aa..12b449c 100644 --- a/arc/src/main/java/org/apache/james/arc/ArcSealVerifyData.java +++ b/arc/src/main/java/org/apache/james/arc/ArcSealVerifyData.java @@ -18,23 +18,20 @@ ****************************************************************/ package org.apache.james.arc; -public class ArcSealVerifyData { - private String _b64; - private String _dataToVerify; +public final class ArcSealVerifyData { + private final String b64; + private final String dataToVerify; - public void setSignedData(String _dataToVerify) { - this._dataToVerify = _dataToVerify; - } - - public void setB64Signature(String _b64) { - this._b64 = _b64; + public ArcSealVerifyData(String b64, String dataToVerify) { + this.b64 = b64; + this.dataToVerify = dataToVerify; } public String getB64Signature() { - return _b64; + return b64; } - + public String getSignedData() { - return _dataToVerify; - } + return dataToVerify; + } } \ No newline at end of file diff --git a/arc/src/main/java/org/apache/james/arc/ArcSetBuilder.java b/arc/src/main/java/org/apache/james/arc/ArcSetBuilder.java index e121105..fccc46e 100644 --- a/arc/src/main/java/org/apache/james/arc/ArcSetBuilder.java +++ b/arc/src/main/java/org/apache/james/arc/ArcSetBuilder.java @@ -25,8 +25,6 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.StringWriter; import java.security.PrivateKey; import java.time.Instant; import java.util.HashMap; @@ -102,7 +100,7 @@ public Map buildArcSet(Message message, String helo, String mail Header headers = message.getHeader(); ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); AuthResultsBuilder authResultsBuilder = new AuthResultsBuilder(_dmarcResponse, _dmarcNonResponse, _authService, keyRecordRetriever); - String cv = arcChainValidator.validateArcChain(message); + String cv = arcChainValidator.validateArcChain(message).name().toLowerCase(); int instance = arcChainValidator.getCurrentInstance(headers); //Build ARC-Authentication-Results header @@ -131,7 +129,7 @@ public Map buildArcSet(Message message, String helo, String mail fmContext.put("cv", cv); //Build and add ARC-AMS header - String amsTemplate = mergeTemplate(_arcAmsTemplate, fmContext); + String amsTemplate = fillArcTemplate(_arcAmsTemplate, instance, timestamp); ARCSigner amsSigner = new ARCSigner(amsTemplate, _arcPrivateKey); String amsHeader = amsSigner.generateAms(new ByteArrayInputStream(os.toByteArray())); String amsValue = amsHeader.split(ARC_ELEMENT)[1]; @@ -139,7 +137,7 @@ public Map buildArcSet(Message message, String helo, String mail headersToSeal.put(ARC_MESSAGE_SIGNATURE, amsValue); //Build and add ARC-Seal header - String asTemplate = mergeTemplate(_arcSealTemplate, fmContext); + String asTemplate = fillArcSealTemplate(_arcSealTemplate, instance, timestamp, cv); ARCSigner asSigner = new ARCSigner(asTemplate, _arcPrivateKey); String asHeader = asSigner.sealHeaders(headersToSeal ); String asValue = asHeader.split(ARC_ELEMENT)[1]; @@ -151,17 +149,14 @@ public Map buildArcSet(Message message, String helo, String mail return arcHeaders; } - private String mergeTemplate(String templateString, Map context) throws IOException { - try (StringWriter writer = new StringWriter()) { - freemarker.template.Configuration cfg = new freemarker.template.Configuration(freemarker.template.Configuration.VERSION_2_3_31); - cfg.setTemplateLoader(new freemarker.cache.StringTemplateLoader()); - freemarker.cache.StringTemplateLoader loader = (freemarker.cache.StringTemplateLoader) cfg.getTemplateLoader(); - loader.putTemplate("template", templateString); - freemarker.template.Template template = cfg.getTemplate("template"); - template.process(context, writer); - return writer.toString(); - } catch (freemarker.template.TemplateException e) { - throw new IOException("Error merging template", e); - } + private String fillArcSealTemplate(String template, int instance, long timestamp, String cv) { + String filledCv = template.replaceAll("cv=\\s*;", "cv=" + cv + ";"); + return fillArcTemplate(filledCv, instance, timestamp); + } + + private String fillArcTemplate(String template, int instance, long timestamp) { + return template + .replaceAll("i=\\s*;", "i=" + instance + ";") + .replaceAll("t=\\s*;", "t=" + timestamp + ";"); } } diff --git a/arc/src/main/java/org/apache/james/arc/ArcValidationResult.java b/arc/src/main/java/org/apache/james/arc/ArcValidationResult.java new file mode 100644 index 0000000..18c669a --- /dev/null +++ b/arc/src/main/java/org/apache/james/arc/ArcValidationResult.java @@ -0,0 +1,7 @@ +package org.apache.james.arc; + +public enum ArcValidationResult { + NONE, + PASS, + FAIL +} diff --git a/arc/src/main/java/org/apache/james/arc/AuthResultsBuilder.java b/arc/src/main/java/org/apache/james/arc/AuthResultsBuilder.java index a7f34e5..85b1553 100644 --- a/arc/src/main/java/org/apache/james/arc/AuthResultsBuilder.java +++ b/arc/src/main/java/org/apache/james/arc/AuthResultsBuilder.java @@ -28,8 +28,6 @@ import org.apache.james.mime4j.dom.Message; import org.apache.james.mime4j.message.DefaultMessageWriter; -import javax.mail.internet.AddressException; -import javax.mail.internet.InternetAddress; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -75,7 +73,6 @@ public String getAuthResultsHeader(Message message, String helo, String from, St // 1. Run SPF check String spfResultText = _keyRecordRetriever.getSpfRecord(helo, from, ip); - // 2. Run DKIM verification String dkimResultFull; try { @@ -170,17 +167,12 @@ private String extractDkimDomain(String dkimResultFull) { private String runDmarcCheck(Message message, String spfHeaderText, String spfDomain, String dkim, String dkimDomain) { // Combine SPF + DKIM results with From: domain // 1. Extract RFC5322.From domain from the From header of the message - String shortSpfResut = spfHeaderText.split(" ")[0]; + String shortSpfResult = spfHeaderText.split(" ")[0]; String fromHeader = message.getHeader().getField(FROM).getBody(); - String fromDomain = getFromDomain(fromHeader); + String fromDomain = extractDomain(fromHeader); if (fromDomain == null || fromDomain.isEmpty()) { return _dmarcNoneResponse + "unknown"; } - try { - fromDomain = new InternetAddress(fromHeader).getAddress().split("@")[1]; - } catch (AddressException e) { - throw new ArcException("Internet address error", e); - } // 2. Fetch DMARC record from DNS ARCVerifier arcVerifier = new ARCVerifier(_keyRecordRetriever); @@ -196,7 +188,7 @@ private String runDmarcCheck(Message message, String spfHeaderText, String spfDo // String adkim = parseTag(dmarcRecord, "adkim"); // optional // 3. Alignment checks - boolean spfAligned = "pass".equals(shortSpfResut) && fromDomain.equalsIgnoreCase(spfDomain); + boolean spfAligned = "pass".equals(shortSpfResult) && fromDomain.equalsIgnoreCase(spfDomain); boolean dkimAligned = "pass".equals(dkim) && fromDomain.equalsIgnoreCase(dkimDomain); // 4. DMARC result logic @@ -211,24 +203,43 @@ private String runDmarcCheck(Message message, String spfHeaderText, String spfDo return String.format(_dmarcResponse, result, policy, fromDomain); } - private String getFromDomain(String fromHeader) { - String fromDomain = null; - if (fromHeader.contains("<") && fromHeader.contains(">")) { - int start = fromHeader.indexOf('<'); - int end = fromHeader.indexOf('>'); - if (start != -1 && end != -1 && end > start) { - String email = fromHeader.substring(start + 1, end); - fromDomain = email.split("@")[1]; - } + // Using own parser to avoid dependency on javax.mail which does not handle all From: formats + // e.g. From: "dpw demo Date: Wed, 8 Oct 2025 15:36:51 -0400" " + private String extractDomain(String fromHeader) throws ArcException { + if (fromHeader == null || fromHeader.isEmpty()) { + throw new ArcException("From header is empty"); } - else { - try { - fromDomain = new InternetAddress(fromHeader).getAddress().split("@")[1]; - } catch (AddressException e) { - throw new ArcException("Internet address error", e); + + // Extract address inside <...> + String address = fromHeader; + int lt = fromHeader.indexOf('<'); + int gt = fromHeader.indexOf('>'); + if (lt != -1 && gt != -1 && gt > lt) { + address = fromHeader.substring(lt + 1, gt).trim(); + } else { + // No brackets — just finding raw address + int at = fromHeader.indexOf('@'); + if (at == -1) { + throw new ArcException("Invalid From header: " + fromHeader); + } + + // Lookin for something@domain + String[] parts = fromHeader.split("\\s+"); + for (String part : parts) { + if (part.contains("@")) { + address = part; + break; + } } } - return fromDomain; + + // And finally extracting the domain + int atIndex = address.lastIndexOf('@'); + if (atIndex == -1 || atIndex == address.length() - 1) { + throw new ArcException("Invalid email address: " + fromHeader); + } + + return address.substring(atIndex + 1); } private InputStream messageToInputStream(Message message) throws IOException { diff --git a/arc/src/main/java/org/apache/james/arc/DNSPublicKeyRecordRetrieverArc.java b/arc/src/main/java/org/apache/james/arc/DNSPublicKeyRecordRetrieverArc.java index d08e210..a23d61a 100644 --- a/arc/src/main/java/org/apache/james/arc/DNSPublicKeyRecordRetrieverArc.java +++ b/arc/src/main/java/org/apache/james/arc/DNSPublicKeyRecordRetrieverArc.java @@ -1,3 +1,21 @@ +/**************************************************************** + * 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.arc; import org.apache.james.arc.exceptions.ArcException; diff --git a/arc/src/main/java/org/apache/james/arc/PublicKeyRetrieverArc.java b/arc/src/main/java/org/apache/james/arc/PublicKeyRetrieverArc.java index 905d75a..7e12860 100644 --- a/arc/src/main/java/org/apache/james/arc/PublicKeyRetrieverArc.java +++ b/arc/src/main/java/org/apache/james/arc/PublicKeyRetrieverArc.java @@ -1,3 +1,21 @@ +/**************************************************************** + * 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.arc; import org.apache.james.jdkim.api.PublicKeyRecordRetriever; diff --git a/arc/src/test/java/org/apache/james/arc/ARCTest.java b/arc/src/test/java/org/apache/james/arc/ARCTest.java index a4bebc6..f50a1e5 100644 --- a/arc/src/test/java/org/apache/james/arc/ARCTest.java +++ b/arc/src/test/java/org/apache/james/arc/ARCTest.java @@ -71,9 +71,8 @@ public class ARCTest { * - "bh=" and "b=" placeholder are required for now because the same implementation is used for * signing and verifying. The fields are mandatory for verifying. */ - //Todo: replace freemarker templates with something less heavy weight - private static final String ARC_AMS_TEMPLATE = "i=${instance}; a=rsa-sha256; c=relaxed/relaxed; d=dmarc.example; s=arc; t=${timestamp}; h=Subject:From:To; bh=; b="; - private static final String ARC_SEAL_TEMPLATE = "i=${instance}; cv=${cv}; a=rsa-sha256; d=dmarc.example; s=arc; t=${timestamp}; b="; + private static final String ARC_AMS_TEMPLATE = "i=; a=rsa-sha256; c=relaxed/relaxed; d=dmarc.example; s=arc; t=; h=Subject:From:To; bh=; b="; + private static final String ARC_SEAL_TEMPLATE = "i=; cv=; a=rsa-sha256; d=dmarc.example; s=arc; t=; b="; private static final String AUTH_SERVICE = "smtp.d1.example"; private static final String HELO = "d1.example"; @@ -119,7 +118,7 @@ public void generate_and_verify_arc_set() throws Exception { } ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); - String cv = arcChainValidator.validateArcChain(message); + String cv = arcChainValidator.validateArcChain(message).name().toLowerCase(); assertThat(cv).isEqualTo(expectedCv); } diff --git a/pom.xml b/pom.xml index f22cc0b..a9aed59 100644 --- a/pom.xml +++ b/pom.xml @@ -68,6 +68,7 @@ 1.8 4.13.2 11 + 1.0.5
From fb3718fa5703e87f4b9bfeb851cb97c37876ac60 Mon Sep 17 00:00:00 2001 From: Emerson Pinter Date: Thu, 2 Oct 2025 20:46:40 -0300 Subject: [PATCH 079/114] Remove commons-codec --- main/pom.xml | 34 ------------------- .../org/apache/james/jdkim/DKIMSigner.java | 5 +-- .../org/apache/james/jdkim/DKIMVerifier.java | 10 ++++-- .../jdkim/tagvalue/PublicKeyRecordImpl.java | 4 +-- .../jdkim/tagvalue/SignatureRecordImpl.java | 10 +++--- .../tagvalue/SignatureRecordTemplate.java | 14 ++++---- .../apache/james/jdkim/tagvalue/TagValue.java | 12 ++++++- .../java/org/apache/james/jdkim/DKIMTest.java | 6 ++-- .../james/jdkim/SignatureRecordImplTest.java | 16 +++++++++ .../java/org/apache/james/jdkim/TestKeys.java | 7 ++-- pom.xml | 6 ---- 11 files changed, 57 insertions(+), 67 deletions(-) diff --git a/main/pom.xml b/main/pom.xml index 1f1d29e..29db1aa 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -35,10 +35,6 @@ 2008 - - commons-codec - commons-codec - dnsjava dnsjava @@ -94,36 +90,6 @@ - - - org.apache.maven.plugins - maven-shade-plugin - 3.6.0 - - - package - - shade - - - - - commons-codec:commons-codec - - - - - org.apache.commons.codec - org.apache.james.jdkim.codec - - - - - - - - - org.apache.rat apache-rat-plugin diff --git a/main/src/main/java/org/apache/james/jdkim/DKIMSigner.java b/main/src/main/java/org/apache/james/jdkim/DKIMSigner.java index 7f05aa7..c280d40 100644 --- a/main/src/main/java/org/apache/james/jdkim/DKIMSigner.java +++ b/main/src/main/java/org/apache/james/jdkim/DKIMSigner.java @@ -21,7 +21,6 @@ import static org.apache.james.jdkim.DKIMCommon.signatureCheck; -import org.apache.commons.codec.binary.Base64; import org.apache.james.jdkim.api.BodyHasher; import org.apache.james.jdkim.api.Headers; import org.apache.james.jdkim.api.SignatureRecord; @@ -41,6 +40,7 @@ import java.security.SignatureException; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Base64; import java.util.List; public class DKIMSigner { @@ -160,7 +160,8 @@ private byte[] signatureSign(Headers h, SignatureRecord sign, */ public static PrivateKey getPrivateKey(String privateKeyPKCS8) throws NoSuchAlgorithmException, InvalidKeySpecException { - byte[] encKey = Base64.decodeBase64(privateKeyPKCS8.getBytes()); + byte[] encKey = Base64.getMimeDecoder().decode(privateKeyPKCS8.getBytes()); + // byte[] encKey = privateKey.getBytes(); PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(encKey); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); diff --git a/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java b/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java index 44537a1..07c1ddc 100644 --- a/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java +++ b/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java @@ -295,9 +295,13 @@ public BodyHasher newBodyHasher(Headers messageHeaders) throws FailException { List signedHeadersList = signatureRecord.getHeaders(); - byte[] decoded = signatureRecord.getSignature(); - signatureVerify(messageHeaders, signatureRecord, decoded, - publicKeyRecord, signedHeadersList); + try { + byte[] decoded = signatureRecord.getSignature(); + signatureVerify(messageHeaders, signatureRecord, decoded, + publicKeyRecord, signedHeadersList); + } catch (IllegalArgumentException e) { + throw new PermFailException("Invalid signature record: " + e.getMessage(), signatureRecord, e); + } // we track all canonicalizations+limit+bodyHash we // see so to be able to check all of them in a single diff --git a/main/src/main/java/org/apache/james/jdkim/tagvalue/PublicKeyRecordImpl.java b/main/src/main/java/org/apache/james/jdkim/tagvalue/PublicKeyRecordImpl.java index c1225d9..d482eab 100644 --- a/main/src/main/java/org/apache/james/jdkim/tagvalue/PublicKeyRecordImpl.java +++ b/main/src/main/java/org/apache/james/jdkim/tagvalue/PublicKeyRecordImpl.java @@ -19,7 +19,6 @@ package org.apache.james.jdkim.tagvalue; -import org.apache.commons.codec.binary.Base64; import org.apache.james.jdkim.api.PublicKeyRecord; import java.security.KeyFactory; @@ -29,6 +28,7 @@ import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; import java.util.ArrayList; +import java.util.Base64; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -178,7 +178,7 @@ public boolean isTesting() { public PublicKey getPublicKey() { try { String p = getValue("p").toString(); - byte[] key = Base64.decodeBase64(p.getBytes()); + byte[] key = Base64.getMimeDecoder().decode(p.getBytes()); KeyFactory keyFactory; keyFactory = KeyFactory.getInstance(getValue("k").toString()); X509EncodedKeySpec pubSpec = new X509EncodedKeySpec(key); 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 dfa3e1c..c2cfc88 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 @@ -21,9 +21,9 @@ import static org.apache.james.jdkim.parser.DKIMQuotedPrintable.dkimQuotedPrintableDecode; -import org.apache.commons.codec.binary.Base64; import org.apache.james.jdkim.api.SignatureRecord; +import java.util.Base64; import java.util.LinkedList; import java.util.List; import java.util.regex.Pattern; @@ -215,11 +215,11 @@ public CharSequence getDToken() { } public byte[] getBodyHash() { - return Base64.decodeBase64(getValue("bh").toString().getBytes()); + return decodeBase64TagValue("bh"); } public byte[] getSignature() { - return Base64.decodeBase64(getValue("b").toString().getBytes()); + return decodeBase64TagValue("b"); } public CharSequence getRawSignature() { @@ -274,12 +274,12 @@ public List getRecordLookupMethods() { } public void setSignature(byte[] newSignature) { - String signature = new String(Base64.encodeBase64(newSignature)); + String signature = new String(Base64.getMimeDecoder().decode(newSignature)); setValue("b", signature); } public void setBodyHash(byte[] newBodyHash) { - String bodyHash = new String(Base64.encodeBase64(newBodyHash)); + String bodyHash = new String(Base64.getMimeDecoder().decode(newBodyHash)); setValue("bh", bodyHash); // If a t=; parameter is present in the signature, make sure to // fill it with the current timestamp 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 186c3c8..d33bab4 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 @@ -19,12 +19,12 @@ package org.apache.james.jdkim.tagvalue; +import java.util.Base64; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.regex.Pattern; -import org.apache.commons.codec.binary.Base64; import org.apache.james.jdkim.api.HashMethod; import org.apache.james.jdkim.api.SignatureRecord; import org.apache.james.jdkim.api.SigningAlgorithm; @@ -223,11 +223,11 @@ public CharSequence getDToken() { } public byte[] getBodyHash() { - return Base64.decodeBase64(getValue("bh").toString().getBytes()); + return decodeBase64TagValue("bh"); } public byte[] getSignature() { - return Base64.decodeBase64(getValue("b").toString().getBytes()); + return decodeBase64TagValue("b"); } public CharSequence getRawSignature() { @@ -282,12 +282,12 @@ public List getRecordLookupMethods() { } public void setSignature(byte[] newSignature) { - String signature = new String(Base64.encodeBase64(newSignature)); + String signature = new String(Base64.getEncoder().encode(newSignature)); setValue("b", signature); } public void setBodyHash(byte[] newBodyHash) { - String bodyHash = new String(Base64.encodeBase64(newBodyHash)); + String bodyHash = new String(Base64.getEncoder().encode(newBodyHash)); setValue("bh", bodyHash); // If a t=; parameter is present in the signature, make sure to // fill it with the current timestamp @@ -299,10 +299,10 @@ public void setBodyHash(byte[] newBodyHash) { public SignatureRecordImpl toSignatureRecord(SigningAlgorithm algorithm, HashMethod hashMethod, byte[] bodyHash, byte[] signature) { setValue("a", algorithm.asTagValue() + "-" + hashMethod.asTagValue()); - String bodyHashTagValue = new String(Base64.encodeBase64(bodyHash)); + String bodyHashTagValue = new String(Base64.getEncoder().encode(bodyHash)); setValue("bh", bodyHashTagValue); - String signatureTagValue = new String(Base64.encodeBase64(signature)); + String signatureTagValue = new String(Base64.getEncoder().encode(signature)); setValue("b", signatureTagValue); // If a t=; parameter is present in the signature, make sure to // fill it with the current timestamp diff --git a/main/src/main/java/org/apache/james/jdkim/tagvalue/TagValue.java b/main/src/main/java/org/apache/james/jdkim/tagvalue/TagValue.java index 6af2d22..68562a5 100644 --- a/main/src/main/java/org/apache/james/jdkim/tagvalue/TagValue.java +++ b/main/src/main/java/org/apache/james/jdkim/tagvalue/TagValue.java @@ -20,6 +20,7 @@ package org.apache.james.jdkim.tagvalue; import java.util.ArrayList; +import java.util.Base64; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -44,6 +45,7 @@ public class TagValue { // Use possessive matching to avoid heavy stack usage private static final Pattern valuePattern = Pattern.compile("^(?:" + tval + "(?:(?:(?:\r\n)?[\t ])++" + tval + ")*+)?$"); + private static final Pattern b64PadFixPattern = Pattern.compile("=(\r?\n\\s*)=$"); // we may use a TreeMap because we may need to know original order. private final Map tagValues; @@ -219,7 +221,15 @@ protected CharSequence getValue(String key) { else return val; } - + + protected byte[] decodeBase64TagValue(String key) { + // When a base64 string ends with a newline between two '=', java.util.Base64 fails. + String value = getValue(key).toString(); + return Base64.getMimeDecoder().decode( + b64PadFixPattern.matcher(value).replaceAll("$1==") + ); + } + protected void setValue(String tag, String value) { stringRepresentation = null; tagValues.put(tag, value); diff --git a/main/src/test/java/org/apache/james/jdkim/DKIMTest.java b/main/src/test/java/org/apache/james/jdkim/DKIMTest.java index fce5524..09f7441 100644 --- a/main/src/test/java/org/apache/james/jdkim/DKIMTest.java +++ b/main/src/test/java/org/apache/james/jdkim/DKIMTest.java @@ -33,9 +33,9 @@ import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.Arrays; +import java.util.Base64; import java.util.List; -import org.apache.commons.codec.binary.Base64; import org.apache.james.jdkim.MockPublicKeyRecordRetriever.Record; import org.apache.james.jdkim.api.Headers; import org.apache.james.jdkim.api.Result; @@ -49,12 +49,12 @@ public class DKIMTest { Record.of( "selector2", "messiah.edu", - "k=rsa; p=" + Base64.encodeBase64String(TestKeys.publicKey.getEncoded()) + ";" + "k=rsa; p=" + Base64.getEncoder().encodeToString(TestKeys.publicKey.getEncoded()) + ";" ), Record.of( "selector3", "messiah.edu", - "k=rsa; p=" + Base64.encodeBase64String(TestKeys.publicKey_2.getEncoded()) + ";" + "k=rsa; p=" + Base64.getEncoder().encodeToString(TestKeys.publicKey_2.getEncoded()) + ";" ) ); 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 d31ef44..d380ec1 100644 --- a/main/src/test/java/org/apache/james/jdkim/SignatureRecordImplTest.java +++ b/main/src/test/java/org/apache/james/jdkim/SignatureRecordImplTest.java @@ -24,6 +24,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; +import org.junit.Assert; import org.junit.Test; public class SignatureRecordImplTest { @@ -132,4 +133,19 @@ public void testExpired() { ).hasMessageContaining("expired"); } + @Test + public void should_parse_signature_with_newline_between_padding() { + String signature = "v=1; a=rsa-sha256; q=dns/txt; c=simple; s=selector2; d=messiah.edu; bh=6pQY\r\n" + + " 5V6Dw8mCYWq017gfbpv+x2X4GvOhIIZtKw6iU6g=; h=date:from:subject; b=Axa\r\n" + + " 8s/gTnnJ8em45KV/AQw33hQ4uYtBKiQp3dLq7oRFt+WmDZ5ZErPq4lBVXfP+IAvP+Au9\r\n" + + " 1J8270ivn1J/6E0YqKntn4s1hjcNBPPRVohvmlcQ1mEMd6DuYDtWjDFwG2GWZwtilaPY\r\n" + + " 2afhlTuAbHkn8nHm7MVtAGETO8QQ2zfD1NSGzKbNYP9I+hrDJq5ajka6PZn1d+mDhUH5\r\n" + + " Px8yScYqo5i8Z8GXaejSIu7RsLDuxtOO2cuClRi8MKGxc7MiGndMufXB8xbS1L80IFly\r\n" + + " unOVY5eBaqnnhF2YrDDQfZ6DTqorzX6D5dNjpjOG6AbsqkW83Drx0TTV/5M0raU1SIw=\r\n" + + " ="; + + SignatureRecordImpl signatureRecord = new SignatureRecordImpl(signature); + Assert.assertTrue(signatureRecord.getSignature().length > 0); + Assert.assertTrue(signatureRecord.getBodyHash().length > 0); + } } diff --git a/main/src/test/java/org/apache/james/jdkim/TestKeys.java b/main/src/test/java/org/apache/james/jdkim/TestKeys.java index 82b1900..72265ed 100644 --- a/main/src/test/java/org/apache/james/jdkim/TestKeys.java +++ b/main/src/test/java/org/apache/james/jdkim/TestKeys.java @@ -31,8 +31,7 @@ import java.security.PublicKey; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; - -import org.apache.commons.codec.binary.Base64; +import java.util.Base64; public class TestKeys { public static final PrivateKey privateKey = loadPrivateKey("org/apache/james/jdkim/keys/private.key"); @@ -50,7 +49,7 @@ private static PublicKey loadPublicKey(String uri) { .replace("-----BEGIN PUBLIC KEY-----", "") .replace("-----END PUBLIC KEY-----", "") .replaceAll(System.lineSeparator(), ""); - byte[] encoded = Base64.decodeBase64(keyText); + byte[] encoded = Base64.getMimeDecoder().decode(keyText); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encoded); return keyFactory.generatePublic(keySpec); @@ -71,7 +70,7 @@ private static PrivateKey loadPrivateKey(String uri) { .replace("-----BEGIN PRIVATE KEY-----", "") .replace("-----END PRIVATE KEY-----", "") .replaceAll(System.lineSeparator(), ""); - byte[] encoded = Base64.decodeBase64(keyText); + byte[] encoded = Base64.getMimeDecoder().decode(keyText); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded); return keyFactory.generatePrivate(keySpec); diff --git a/pom.xml b/pom.xml index 3f78004..696678e 100644 --- a/pom.xml +++ b/pom.xml @@ -62,7 +62,6 @@ 0.8.12 - 1.18.0 3.6.3 1.8 4.13.2 @@ -71,11 +70,6 @@ - - commons-codec - commons-codec - ${commons-codec.version} - dnsjava dnsjava From dcaec3ad7921eb80caf89e49c56c70074cd2534d Mon Sep 17 00:00:00 2001 From: Artem Grinchenko Date: Fri, 17 Oct 2025 16:22:45 -0400 Subject: [PATCH 080/114] Rework DMARK and reduce dependencies - Rewired DMARC into separate module - Added relaxed header alignment for DMARC using PSL list - Removed commons-codec dependency --- arc/pom.xml | 12 +- .../org/apache/james/arc/ARCVerifier.java | 17 - .../apache/james/arc/ArcValidationResult.java | 18 + .../apache/james/arc/AuthResultsBuilder.java | 83 +- .../arc/DNSPublicKeyRecordRetrieverArc.java | 49 +- .../james/arc/PublicKeyRetrieverArc.java | 5 +- .../java/org/apache/james/arc/ARCTest.java | 21 +- .../org/apache/james/arc/ArcTestKeys.java | 12 +- .../arc/MockPublicKeyRecordRetrieverArc.java | 53 +- .../org/apache/james/dmarc/DMARCVerifier.java | 137 + .../DNSPublicKeyRecordRetrieverDmarc.java | 75 + .../dmarc/PublicKeyRecordRetrieverDmarc.java | 26 + .../apache/james/dmarc/PublicSuffixList.java | 70 + .../dmarc/exceptions/DmarcException.java | 30 + .../src/main/resources/public_suffix_list.dat | 15997 ++++++++++++++++ .../org/apache/james/dmarc/DMARCTest.java | 56 + .../apache/james/dmarc/DmarcRequestMock.java | 77 + .../MockPublicKeyRecordRetrieverDmarc.java | 65 + .../james/dmarc/PublicSuffixListTest.java | 60 + dmarc/src/main/test/resources/mail/e1.eml | 31 + dmarc/src/main/test/resources/mail/e2.eml | 14 + dmarc/src/main/test/resources/mail/e3.eml | 14 + pom.xml | 1 + 23 files changed, 16735 insertions(+), 188 deletions(-) create mode 100644 dmarc/src/main/java/org/apache/james/dmarc/DMARCVerifier.java create mode 100644 dmarc/src/main/java/org/apache/james/dmarc/DNSPublicKeyRecordRetrieverDmarc.java create mode 100644 dmarc/src/main/java/org/apache/james/dmarc/PublicKeyRecordRetrieverDmarc.java create mode 100644 dmarc/src/main/java/org/apache/james/dmarc/PublicSuffixList.java create mode 100644 dmarc/src/main/java/org/apache/james/dmarc/exceptions/DmarcException.java create mode 100644 dmarc/src/main/resources/public_suffix_list.dat create mode 100644 dmarc/src/main/test/java/org/apache/james/dmarc/DMARCTest.java create mode 100644 dmarc/src/main/test/java/org/apache/james/dmarc/DmarcRequestMock.java create mode 100644 dmarc/src/main/test/java/org/apache/james/dmarc/MockPublicKeyRecordRetrieverDmarc.java create mode 100644 dmarc/src/main/test/java/org/apache/james/dmarc/PublicSuffixListTest.java create mode 100644 dmarc/src/main/test/resources/mail/e1.eml create mode 100644 dmarc/src/main/test/resources/mail/e2.eml create mode 100644 dmarc/src/main/test/resources/mail/e3.eml diff --git a/arc/pom.xml b/arc/pom.xml index b2d3eca..820ffb6 100644 --- a/arc/pom.xml +++ b/arc/pom.xml @@ -36,8 +36,16 @@ - commons-codec - commons-codec + org.apache.james.jdkim + apache-dmarc-library + ${project.version} + + + org.apache.james.jdkim + apache-dmarc-library + ${project.version} + test-jar + test org.apache.james.jdkim diff --git a/arc/src/main/java/org/apache/james/arc/ARCVerifier.java b/arc/src/main/java/org/apache/james/arc/ARCVerifier.java index 49d273f..b831dfc 100644 --- a/arc/src/main/java/org/apache/james/arc/ARCVerifier.java +++ b/arc/src/main/java/org/apache/james/arc/ARCVerifier.java @@ -167,23 +167,6 @@ private String canonicalizeHeader(String name, String value) { return n + ":" + v; } - private PublicKey getPublicKeyFromTxtRecord(String keyText) { - - keyText = keyText.replaceAll("\\s+", ""); // remove ALL spaces/newlines - byte[] keyBytes = Base64.getDecoder().decode(keyText); - - X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes); - PublicKey pubKey; - try { - pubKey = KeyFactory.getInstance(RSA).generatePublic(spec); - } catch (InvalidKeySpecException e) { - throw new ArcException("Invalid key provided when getting public key", e); - } catch (NoSuchAlgorithmException e) { - throw new ArcException("Unsupported algorithm provided when getting public key", e); - } - return pubKey; - } - public PublicKey parsePublicKeyFromDns(String dnsRecord) { Matcher m = PUBLIC_KEY_PATTERN.matcher(dnsRecord); diff --git a/arc/src/main/java/org/apache/james/arc/ArcValidationResult.java b/arc/src/main/java/org/apache/james/arc/ArcValidationResult.java index 18c669a..a549919 100644 --- a/arc/src/main/java/org/apache/james/arc/ArcValidationResult.java +++ b/arc/src/main/java/org/apache/james/arc/ArcValidationResult.java @@ -1,3 +1,21 @@ +/**************************************************************** + * 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.arc; public enum ArcValidationResult { diff --git a/arc/src/main/java/org/apache/james/arc/AuthResultsBuilder.java b/arc/src/main/java/org/apache/james/arc/AuthResultsBuilder.java index 85b1553..5c82937 100644 --- a/arc/src/main/java/org/apache/james/arc/AuthResultsBuilder.java +++ b/arc/src/main/java/org/apache/james/arc/AuthResultsBuilder.java @@ -19,6 +19,7 @@ package org.apache.james.arc; import org.apache.james.arc.exceptions.ArcException; +import org.apache.james.dmarc.DMARCVerifier; import org.apache.james.jdkim.DKIMVerifier; import org.apache.james.jdkim.api.SignatureRecord; import org.apache.james.jdkim.exceptions.FailException; @@ -54,7 +55,6 @@ *

*/ public class AuthResultsBuilder { - public static final String FROM = "From"; public static final String HEADER_I = "header.i="; private final PublicKeyRetrieverArc _keyRecordRetriever; private String _dmarcNoneResponse; @@ -85,7 +85,8 @@ public String getAuthResultsHeader(Message message, String helo, String from, St // 3. Run DMARC check (using SPF + DKIM results + From domain) String dkimDomain = extractDkimDomain(dkimResultFull); String spfDomain = extractSpfDomain(spfResultText); - String dmarcResult = runDmarcCheck(message, spfResultText, spfDomain, dkimResultShort, dkimDomain); + DMARCVerifier dmarcVerifier = new DMARCVerifier(_dmarcResponse, _dmarcNoneResponse, _keyRecordRetriever.getDmarcRetriever()); + String dmarcResult = dmarcVerifier.runDmarcCheck(message, spfResultText, spfDomain, dkimResultShort, dkimDomain); if (dmarcResult == null || dmarcResult.isEmpty()) { dmarcResult = _dmarcNoneResponse + spfDomain; } @@ -164,84 +165,6 @@ private String extractDkimDomain(String dkimResultFull) { return null; } - private String runDmarcCheck(Message message, String spfHeaderText, String spfDomain, String dkim, String dkimDomain) { - // Combine SPF + DKIM results with From: domain - // 1. Extract RFC5322.From domain from the From header of the message - String shortSpfResult = spfHeaderText.split(" ")[0]; - String fromHeader = message.getHeader().getField(FROM).getBody(); - String fromDomain = extractDomain(fromHeader); - if (fromDomain == null || fromDomain.isEmpty()) { - return _dmarcNoneResponse + "unknown"; - } - - // 2. Fetch DMARC record from DNS - ARCVerifier arcVerifier = new ARCVerifier(_keyRecordRetriever); - - String dmarcRecord = arcVerifier.getPublicKeyRecordRetriever().getDmarcRecord(fromDomain); - if (dmarcRecord == null) { - return _dmarcNoneResponse + fromDomain; - } - - // Parse DMARC policy - String policy = arcVerifier.parseTagGeneric(dmarcRecord, "p"); // p=none|quarantine|reject -// String aspf = parseTag(dmarcRecord, "aspf"); // optional -// String adkim = parseTag(dmarcRecord, "adkim"); // optional - - // 3. Alignment checks - boolean spfAligned = "pass".equals(shortSpfResult) && fromDomain.equalsIgnoreCase(spfDomain); - boolean dkimAligned = "pass".equals(dkim) && fromDomain.equalsIgnoreCase(dkimDomain); - - // 4. DMARC result logic - String result; - if (spfAligned || dkimAligned) { - result = "pass"; - } else { - result = "fail"; - } - - // 5. Build Authentication-Results string - return String.format(_dmarcResponse, result, policy, fromDomain); - } - - // Using own parser to avoid dependency on javax.mail which does not handle all From: formats - // e.g. From: "dpw demo Date: Wed, 8 Oct 2025 15:36:51 -0400" " - private String extractDomain(String fromHeader) throws ArcException { - if (fromHeader == null || fromHeader.isEmpty()) { - throw new ArcException("From header is empty"); - } - - // Extract address inside <...> - String address = fromHeader; - int lt = fromHeader.indexOf('<'); - int gt = fromHeader.indexOf('>'); - if (lt != -1 && gt != -1 && gt > lt) { - address = fromHeader.substring(lt + 1, gt).trim(); - } else { - // No brackets — just finding raw address - int at = fromHeader.indexOf('@'); - if (at == -1) { - throw new ArcException("Invalid From header: " + fromHeader); - } - - // Lookin for something@domain - String[] parts = fromHeader.split("\\s+"); - for (String part : parts) { - if (part.contains("@")) { - address = part; - break; - } - } - } - - // And finally extracting the domain - int atIndex = address.lastIndexOf('@'); - if (atIndex == -1 || atIndex == address.length() - 1) { - throw new ArcException("Invalid email address: " + fromHeader); - } - - return address.substring(atIndex + 1); - } - private InputStream messageToInputStream(Message message) throws IOException { DefaultMessageWriter writer = new DefaultMessageWriter(); ByteArrayOutputStream os = new ByteArrayOutputStream(); diff --git a/arc/src/main/java/org/apache/james/arc/DNSPublicKeyRecordRetrieverArc.java b/arc/src/main/java/org/apache/james/arc/DNSPublicKeyRecordRetrieverArc.java index a23d61a..44813f8 100644 --- a/arc/src/main/java/org/apache/james/arc/DNSPublicKeyRecordRetrieverArc.java +++ b/arc/src/main/java/org/apache/james/arc/DNSPublicKeyRecordRetrieverArc.java @@ -18,23 +18,14 @@ ****************************************************************/ package org.apache.james.arc; -import org.apache.james.arc.exceptions.ArcException; +import org.apache.james.dmarc.DNSPublicKeyRecordRetrieverDmarc; +import org.apache.james.dmarc.PublicKeyRecordRetrieverDmarc; import org.apache.james.jdkim.impl.DNSPublicKeyRecordRetriever; import org.apache.james.jspf.impl.DefaultSPF; import org.apache.james.jspf.impl.SPF; -import javax.naming.NamingEnumeration; -import javax.naming.NamingException; -import javax.naming.directory.Attribute; -import javax.naming.directory.Attributes; -import javax.naming.directory.DirContext; -import javax.naming.directory.InitialDirContext; -import java.util.Hashtable; - public class DNSPublicKeyRecordRetrieverArc extends DNSPublicKeyRecordRetriever implements PublicKeyRetrieverArc { - public static final String JAVA_NAMING_FACTORY_INITIAL = "java.naming.factory.initial"; - public static final String COM_SUN_JNDI_DNS_DNS_CONTEXT_FACTORY = "com.sun.jndi.dns.DnsContextFactory"; - public static final String TXT = "TXT"; + public static final DNSPublicKeyRecordRetrieverDmarc DMARC = new DNSPublicKeyRecordRetrieverDmarc(); public DNSPublicKeyRecordRetrieverArc() { super(); @@ -48,37 +39,7 @@ public String getSpfRecord(String helo, String from, String ip) { } @Override - public String getDmarcRecord(String dnsLabel) { - Hashtable env = new Hashtable<>(); - env.put(JAVA_NAMING_FACTORY_INITIAL, COM_SUN_JNDI_DNS_DNS_CONTEXT_FACTORY); - DirContext ctx; - dnsLabel = "_dmarc." + dnsLabel; - try { - ctx = new InitialDirContext(env); - } catch (NamingException e) { - throw new ArcException(String.format("Naming error when creating InitialDirContext using [%s]", dnsLabel), e); - } - - Attributes attrs; - try { - attrs = ctx.getAttributes(dnsLabel, new String[]{TXT}); - } catch (NamingException e) { - throw new ArcException(String.format("Naming error when getting attributes using [%s]", dnsLabel), e); - } - - Attribute txtAttr = attrs.get(TXT); - try { - if (txtAttr != null) { - StringBuilder sb = new StringBuilder(); - NamingEnumeration e = txtAttr.getAll(); - while (e.hasMore()) { - sb.append(e.next().toString().replace("\"", "")); - } - return sb.toString(); - } - } catch (NamingException e) { - throw new ArcException(String.format("Naming error when looping through attributes using [%s]", dnsLabel), e); - } - return null; + public PublicKeyRecordRetrieverDmarc getDmarcRetriever() { + return DMARC; } } diff --git a/arc/src/main/java/org/apache/james/arc/PublicKeyRetrieverArc.java b/arc/src/main/java/org/apache/james/arc/PublicKeyRetrieverArc.java index 7e12860..50dc70e 100644 --- a/arc/src/main/java/org/apache/james/arc/PublicKeyRetrieverArc.java +++ b/arc/src/main/java/org/apache/james/arc/PublicKeyRetrieverArc.java @@ -18,11 +18,12 @@ ****************************************************************/ package org.apache.james.arc; +import org.apache.james.dmarc.PublicKeyRecordRetrieverDmarc; import org.apache.james.jdkim.api.PublicKeyRecordRetriever; public interface PublicKeyRetrieverArc extends PublicKeyRecordRetriever { - String getDmarcRecord(String query); - String getSpfRecord(String helo, String from, String ip); + + PublicKeyRecordRetrieverDmarc getDmarcRetriever(); } diff --git a/arc/src/test/java/org/apache/james/arc/ARCTest.java b/arc/src/test/java/org/apache/james/arc/ARCTest.java index f50a1e5..a38b8c3 100644 --- a/arc/src/test/java/org/apache/james/arc/ARCTest.java +++ b/arc/src/test/java/org/apache/james/arc/ARCTest.java @@ -16,10 +16,9 @@ * specific language governing permissions and limitations * * under the License. * ******************************************************************************/ - package org.apache.james.arc; -import org.apache.commons.codec.binary.Base64; +import org.apache.james.dmarc.MockPublicKeyRecordRetrieverDmarc; import org.apache.james.jdkim.DKIMCommon; import org.apache.james.jdkim.MockPublicKeyRecordRetriever; import org.apache.james.mime4j.dom.Message; @@ -27,6 +26,7 @@ import org.apache.james.mime4j.stream.RawField; import org.junit.Test; +import java.util.Base64; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; @@ -45,20 +45,23 @@ public class ARCTest { public static final String ARC_MESSAGE_SIGNATURE = "ARC-Message-Signature"; public static final String ARC_SEAL = "ARC-Seal"; - private final MockPublicKeyRecordRetrieverArc keyRecordRetriever = new MockPublicKeyRecordRetrieverArc( + private final MockPublicKeyRecordRetrieverDmarc dmarcRetriever = new MockPublicKeyRecordRetrieverDmarc( + MockPublicKeyRecordRetrieverDmarc.DmarcRecord.dmarcOf( + "d1.example", + "k=rsa; v=DMARC1; p=reject; pct=100; rua=mailto:noc@d1.example" + ) + ); + + private final MockPublicKeyRecordRetrieverArc keyRecordRetriever = new MockPublicKeyRecordRetrieverArc( dmarcRetriever, MockPublicKeyRecordRetriever.Record.of( "arc", "dmarc.example", - "k=rsa; p=" + Base64.encodeBase64String(ArcTestKeys.publicKeyArc.getEncoded()) + ";" + "k=rsa; p=" + Base64.getEncoder().encodeToString(ArcTestKeys.publicKeyArc.getEncoded()) + ";" ), MockPublicKeyRecordRetriever.Record.of( "origin2015", "d1.example", - "k=rsa; p=" + Base64.encodeBase64String(ArcTestKeys.publicKeyDkim.getEncoded()) + ";" - ), - MockPublicKeyRecordRetrieverArc.DmarcRecord.dmarcOf("", - "d1.example", - "k=rsa; v=DMARC1; p=reject; pct=100; rua=mailto:noc@d1.example" + "k=rsa; p=" + Base64.getEncoder().encodeToString(ArcTestKeys.publicKeyDkim.getEncoded()) + ";" ), MockPublicKeyRecordRetrieverArc.SpfRecord.spfOf("d1.example", "jqd@d1.example", diff --git a/arc/src/test/java/org/apache/james/arc/ArcTestKeys.java b/arc/src/test/java/org/apache/james/arc/ArcTestKeys.java index cec4e7e..4178a33 100644 --- a/arc/src/test/java/org/apache/james/arc/ArcTestKeys.java +++ b/arc/src/test/java/org/apache/james/arc/ArcTestKeys.java @@ -16,11 +16,9 @@ * specific language governing permissions and limitations * * under the License. * ******************************************************************************/ - package org.apache.james.arc; -import org.apache.commons.codec.binary.Base64; - +import java.util.Base64; import java.io.File; import java.io.IOException; import java.net.URISyntaxException; @@ -47,9 +45,9 @@ private static PublicKey loadPublicKey(String uri) { .replace("-----BEGIN PUBLIC KEY-----", "") .replace("-----END PUBLIC KEY-----", "") .replaceAll(System.lineSeparator(), ""); - byte[] encoded = Base64.decodeBase64(keyText); + byte[] decoded = Base64.getDecoder().decode(keyText); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); - X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encoded); + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decoded); return keyFactory.generatePublic(keySpec); } catch (Exception e) { throw new RuntimeException(e); @@ -68,9 +66,9 @@ private static PrivateKey loadPrivateKey(String uri) { .replace("-----BEGIN PRIVATE KEY-----", "") .replace("-----END PRIVATE KEY-----", "") .replaceAll(System.lineSeparator(), ""); - byte[] encoded = Base64.decodeBase64(keyText); + byte[] decoded = Base64.getDecoder().decode(keyText); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); - PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decoded); return keyFactory.generatePrivate(keySpec); } catch (Exception e) { throw new RuntimeException(e); diff --git a/arc/src/test/java/org/apache/james/arc/MockPublicKeyRecordRetrieverArc.java b/arc/src/test/java/org/apache/james/arc/MockPublicKeyRecordRetrieverArc.java index 2f78bc3..71d0505 100644 --- a/arc/src/test/java/org/apache/james/arc/MockPublicKeyRecordRetrieverArc.java +++ b/arc/src/test/java/org/apache/james/arc/MockPublicKeyRecordRetrieverArc.java @@ -1,6 +1,25 @@ +/****************************************************************************** + * 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.arc; import org.apache.james.arc.exceptions.ArcException; +import org.apache.james.dmarc.MockPublicKeyRecordRetrieverDmarc; import org.apache.james.jdkim.MockPublicKeyRecordRetriever; import org.apache.james.jdkim.exceptions.PermFailException; import org.apache.james.jdkim.exceptions.TempFailException; @@ -9,8 +28,9 @@ public class MockPublicKeyRecordRetrieverArc extends MockPublicKeyRecordRetriever implements PublicKeyRetrieverArc { - public static final String DMARC = "_dmarc."; public static final String SPF = "_spf."; + private final MockPublicKeyRecordRetrieverDmarc _dmarcRetriever; + public static class SpfRecord extends MockPublicKeyRecordRetriever.Record { public SpfRecord(String helo, String from, String ip, String spfRecord) { @@ -22,19 +42,9 @@ public static SpfRecord spfOf(String helo, String from, String ip, String spfRec } } - public static class DmarcRecord extends MockPublicKeyRecordRetriever.Record { - - public DmarcRecord(String selector, String domain, String dmarcRecord) { - super(DMARC, domain, dmarcRecord); - } - - public static DmarcRecord dmarcOf(String selector, String domain, String dmarcRecord) { - return new DmarcRecord(selector, domain, dmarcRecord); - } - } - - public MockPublicKeyRecordRetrieverArc(Record... records) { + public MockPublicKeyRecordRetrieverArc(MockPublicKeyRecordRetrieverDmarc dmarcRetriever, Record... records) { super(records); + _dmarcRetriever = dmarcRetriever; } @Override @@ -45,7 +55,7 @@ public String getSpfRecord(String helo, String from, String ip) { if (recs.isEmpty()) { return null; } - return recs.get(0); //TODO: multiple records? + return recs.getFirst(); } catch (TempFailException e) { throw new ArcException("Temporary failure looking up DMARC record", e); } catch (PermFailException e) { @@ -53,18 +63,7 @@ public String getSpfRecord(String helo, String from, String ip) { } } - @Override - public String getDmarcRecord(String searchKey){ - try { - List recs = super.getRecords("dns/txt", DMARC,searchKey); - if (recs.isEmpty()) { - return null; - } - return recs.get(0); //TODO: multiple records? - } catch (TempFailException e) { - throw new ArcException("Temporary failure looking up DMARC record", e); - } catch (PermFailException e) { - throw new ArcException("Permanent failure looking up DMARC record", e); - } + public MockPublicKeyRecordRetrieverDmarc getDmarcRetriever() { + return _dmarcRetriever; } } diff --git a/dmarc/src/main/java/org/apache/james/dmarc/DMARCVerifier.java b/dmarc/src/main/java/org/apache/james/dmarc/DMARCVerifier.java new file mode 100644 index 0000000..4d0c9a0 --- /dev/null +++ b/dmarc/src/main/java/org/apache/james/dmarc/DMARCVerifier.java @@ -0,0 +1,137 @@ +/****************************************************************************** + * 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.dmarc; + +import org.apache.james.dmarc.exceptions.DmarcException; +import org.apache.james.mime4j.dom.Message; + +public class DMARCVerifier { + public static final String FROM = "From"; + private final String _dmarcResponse; + private final String _dmarcNonResponse; + private final PublicKeyRecordRetrieverDmarc _recordRetriever; + + public DMARCVerifier(String dmarcResponse, String dmarcNonResponse, PublicKeyRecordRetrieverDmarc recordRetriever) { + _dmarcResponse = dmarcResponse; + _dmarcNonResponse = dmarcNonResponse; + _recordRetriever = recordRetriever; + } + + public String runDmarcCheck(Message message, String spfHeaderText, String + spfDomain, String dkimResult, String dkimDomain){ + // Combine SPF + DKIM results with From: domain + // 1. Extract RFC5322.From domain from the From header of the message + String shortSpfResult = spfHeaderText.split(" ")[0]; + String fromHeader = message.getHeader().getField(FROM).getBody(); + String fromDomain = extractDomain(fromHeader); + if (fromDomain == null || fromDomain.isEmpty()) { + return _dmarcNonResponse + "unknown"; + } + + // 2. Fetch DMARC record from DNS + String dmarcRecord = _recordRetriever.getDmarcRecord(fromDomain); + if (dmarcRecord == null) { + return _dmarcNonResponse + fromDomain; + } + + // Parse DMARC policy + String policy = parseTag(dmarcRecord, "p"); // p=none|quarantine|reject + String aspf = parseTag(dmarcRecord, "aspf"); // "s" or "r" for strict or relaxed domain alignment; default is "r" + String adkim = parseTag(dmarcRecord, "adkim"); // "s" or "r" for strict or relaxed domain alignment; default is "r" + + // 3. Alignment checks + boolean spfAligned = getDomainAlignment(aspf, shortSpfResult, fromDomain, spfDomain); + boolean dkimAligned = getDomainAlignment(adkim, dkimResult, fromDomain, dkimDomain); + + // 4. DMARC result logic + String result; + if (spfAligned || dkimAligned) { + result = "pass"; + } else { + result = "fail"; + } + + // 5. Build Authentication-Results string + return String.format(_dmarcResponse, result, policy, fromDomain); + } + + private boolean getDomainAlignment(String flag, String result, String receivedDomain, String expectedDomain) { + // we expect flag to be either "s" or "r"; default is "r" + if (flag == null || flag.equalsIgnoreCase("r")){ //relaxed + String fromOrgDomain = PublicSuffixList.getOrgDomain(receivedDomain); //we get the organizational domain using PSL + String spfOrgDomain = PublicSuffixList.getOrgDomain(expectedDomain); + + return "pass".equals(result) + && fromOrgDomain.equalsIgnoreCase(spfOrgDomain); + } + else if (flag.equalsIgnoreCase("s")){ // strict + return "pass".equals(result) && receivedDomain.equalsIgnoreCase(expectedDomain); + } + else { + throw new DmarcException(String.format("Unknown alignment flag value: %s", flag)); + } + } + + private String extractDomain(String fromHeader) throws DmarcException { + if (fromHeader == null || fromHeader.isEmpty()) { + throw new DmarcException("From header is empty"); + } + + // Extract address inside <...> + String address = fromHeader; + int lt = fromHeader.indexOf('<'); + int gt = fromHeader.indexOf('>'); + if (lt != -1 && gt != -1 && gt > lt) { + address = fromHeader.substring(lt + 1, gt).trim(); + } else { + // No brackets — just finding raw address + int at = fromHeader.indexOf('@'); + if (at == -1) { + throw new DmarcException("Invalid From header: " + fromHeader); + } + + // Lookin for something@domain + String[] parts = fromHeader.split("\\s+"); + for (String part : parts) { + if (part.contains("@")) { + address = part; + break; + } + } + } + + // And finally extracting the domain + int atIndex = address.lastIndexOf('@'); + if (atIndex == -1 || atIndex == address.length() - 1) { + throw new DmarcException("Invalid email address: " + fromHeader); + } + return address.substring(atIndex + 1); + } + + public String parseTag(String dmarcRecord, String tag) { + String[] parts = dmarcRecord.split(";"); + for (String part : parts) { + String trimmed = part.trim(); + if (trimmed.startsWith(tag + "=")) { + return trimmed.substring((tag + "=").length()); + } + } + return null; + } +} diff --git a/dmarc/src/main/java/org/apache/james/dmarc/DNSPublicKeyRecordRetrieverDmarc.java b/dmarc/src/main/java/org/apache/james/dmarc/DNSPublicKeyRecordRetrieverDmarc.java new file mode 100644 index 0000000..c73e4f2 --- /dev/null +++ b/dmarc/src/main/java/org/apache/james/dmarc/DNSPublicKeyRecordRetrieverDmarc.java @@ -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.dmarc; + +import org.apache.james.dmarc.exceptions.DmarcException; +import org.apache.james.jdkim.impl.DNSPublicKeyRecordRetriever; + +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.InitialDirContext; +import java.util.Hashtable; + +public class DNSPublicKeyRecordRetrieverDmarc extends DNSPublicKeyRecordRetriever implements PublicKeyRecordRetrieverDmarc { + public static final String JAVA_NAMING_FACTORY_INITIAL = "java.naming.factory.initial"; + public static final String COM_SUN_JNDI_DNS_DNS_CONTEXT_FACTORY = "com.sun.jndi.dns.DnsContextFactory"; + public static final String TXT = "TXT"; + + public DNSPublicKeyRecordRetrieverDmarc() { + super(); + } + + @Override + public String getDmarcRecord(String dnsLabel) { + Hashtable env = new Hashtable<>(); + env.put(JAVA_NAMING_FACTORY_INITIAL, COM_SUN_JNDI_DNS_DNS_CONTEXT_FACTORY); + DirContext ctx; + dnsLabel = "_dmarc." + dnsLabel; + try { + ctx = new InitialDirContext(env); + } catch (NamingException e) { + throw new DmarcException(String.format("Naming error when creating InitialDirContext using [%s]", dnsLabel), e); + } + + Attributes attrs; + try { + attrs = ctx.getAttributes(dnsLabel, new String[]{TXT}); + } catch (NamingException e) { + throw new DmarcException(String.format("Naming error when getting attributes using [%s]", dnsLabel), e); + } + + Attribute txtAttr = attrs.get(TXT); + try { + if (txtAttr != null) { + StringBuilder sb = new StringBuilder(); + NamingEnumeration e = txtAttr.getAll(); + while (e.hasMore()) { + sb.append(e.next().toString().replace("\"", "")); + } + return sb.toString(); + } + } catch (NamingException e) { + throw new DmarcException(String.format("Naming error when looping through attributes using [%s]", dnsLabel), e); + } + return null; + } +} diff --git a/dmarc/src/main/java/org/apache/james/dmarc/PublicKeyRecordRetrieverDmarc.java b/dmarc/src/main/java/org/apache/james/dmarc/PublicKeyRecordRetrieverDmarc.java new file mode 100644 index 0000000..3576131 --- /dev/null +++ b/dmarc/src/main/java/org/apache/james/dmarc/PublicKeyRecordRetrieverDmarc.java @@ -0,0 +1,26 @@ +/****************************************************************************** + * 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.dmarc; + +import org.apache.james.jdkim.api.PublicKeyRecordRetriever; + +public interface PublicKeyRecordRetrieverDmarc extends PublicKeyRecordRetriever { + + String getDmarcRecord(String query); +} diff --git a/dmarc/src/main/java/org/apache/james/dmarc/PublicSuffixList.java b/dmarc/src/main/java/org/apache/james/dmarc/PublicSuffixList.java new file mode 100644 index 0000000..23ebd48 --- /dev/null +++ b/dmarc/src/main/java/org/apache/james/dmarc/PublicSuffixList.java @@ -0,0 +1,70 @@ +/****************************************************************************** + * 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.dmarc; + +import org.apache.james.dmarc.exceptions.DmarcException; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +public class PublicSuffixList { + private static final Set SUFFIXES = new HashSet<>(); + + static { + try (InputStream is = PublicSuffixList.class.getResourceAsStream("/public_suffix_list.dat")) { + assert is != null; + try (BufferedReader reader = new BufferedReader(new InputStreamReader(is))) { + String line; + while ((line = reader.readLine()) != null) { + line = line.trim(); + if (line.isEmpty() || line.startsWith("//")) continue; + SUFFIXES.add(line.toLowerCase()); + } + } + } catch (Exception e) { + throw new DmarcException("Failed to load Public Suffix List", e); + } + } + + private PublicSuffixList() {} + + public static boolean isPublicSuffix(String domain) { + return SUFFIXES.contains(domain.toLowerCase()); + } + + public static String getOrgDomain(String receivedDomain) { + String[] parts = receivedDomain.toLowerCase().split("\\."); + for (int i = 0; i < parts.length - 1; i++) { + //we start checking from the most specific part on the left moving to the right until we find a match + String candidate = String.join(".", Arrays.copyOfRange(parts, i, parts.length)); + if (isPublicSuffix(candidate)) { + return candidate; + } + } + return receivedDomain; + } + + static void main() { + System.out.println(getOrgDomain("id.replit.app")); // example.co.uk + } +} \ No newline at end of file diff --git a/dmarc/src/main/java/org/apache/james/dmarc/exceptions/DmarcException.java b/dmarc/src/main/java/org/apache/james/dmarc/exceptions/DmarcException.java new file mode 100644 index 0000000..bf3aea7 --- /dev/null +++ b/dmarc/src/main/java/org/apache/james/dmarc/exceptions/DmarcException.java @@ -0,0 +1,30 @@ +/**************************************************************** + * 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.dmarc.exceptions; + +public class DmarcException extends RuntimeException { + + public DmarcException(String message) { + super(message); + } + + public DmarcException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/dmarc/src/main/resources/public_suffix_list.dat b/dmarc/src/main/resources/public_suffix_list.dat new file mode 100644 index 0000000..d047478 --- /dev/null +++ b/dmarc/src/main/resources/public_suffix_list.dat @@ -0,0 +1,15997 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +// Please pull this list from, and only from https://publicsuffix.org/list/public_suffix_list.dat, +// rather than any other VCS sites. Pulling from any other URL is not guaranteed to be supported. + +// VERSION: 2025-10-13_13-22-32_UTC +// COMMIT: c002d19a8ca969110dd8a3838d972ca293bdc48f + +// Instructions on pulling and using this list can be found at https://publicsuffix.org/list/. + +// ===BEGIN ICANN DOMAINS=== + +// ac : http://nic.ac/rules.htm +ac +com.ac +edu.ac +gov.ac +mil.ac +net.ac +org.ac + +// ad : https://www.iana.org/domains/root/db/ad.html +// Confirmed by Amadeu Abril i Abril (CORE) 2024-11-17 +ad + +// ae : https://www.iana.org/domains/root/db/ae.html +ae +ac.ae +co.ae +gov.ae +mil.ae +net.ae +org.ae +sch.ae + +// aero : https://information.aero/registration/policies/dmp +aero +// 2LDs +airline.aero +airport.aero +// 2LDs (currently not accepting registration, seemingly never have) +// As of 2024-07, these are marked as reserved for potential 3LD +// registrations (clause 11 "allocated subdomains" in the 2006 TLD +// policy), but the relevant industry partners have not opened them up +// for registration. Current status can be determined from the TLD's +// policy document: 2LDs that are open for registration must list +// their policy in the TLD's policy. Any 2LD without such a policy is +// not open for registrations. +accident-investigation.aero +accident-prevention.aero +aerobatic.aero +aeroclub.aero +aerodrome.aero +agents.aero +air-surveillance.aero +air-traffic-control.aero +aircraft.aero +airtraffic.aero +ambulance.aero +association.aero +author.aero +ballooning.aero +broker.aero +caa.aero +cargo.aero +catering.aero +certification.aero +championship.aero +charter.aero +civilaviation.aero +club.aero +conference.aero +consultant.aero +consulting.aero +control.aero +council.aero +crew.aero +design.aero +dgca.aero +educator.aero +emergency.aero +engine.aero +engineer.aero +entertainment.aero +equipment.aero +exchange.aero +express.aero +federation.aero +flight.aero +freight.aero +fuel.aero +gliding.aero +government.aero +groundhandling.aero +group.aero +hanggliding.aero +homebuilt.aero +insurance.aero +journal.aero +journalist.aero +leasing.aero +logistics.aero +magazine.aero +maintenance.aero +marketplace.aero +media.aero +microlight.aero +modelling.aero +navigation.aero +parachuting.aero +paragliding.aero +passenger-association.aero +pilot.aero +press.aero +production.aero +recreation.aero +repbody.aero +res.aero +research.aero +rotorcraft.aero +safety.aero +scientist.aero +services.aero +show.aero +skydiving.aero +software.aero +student.aero +taxi.aero +trader.aero +trading.aero +trainer.aero +union.aero +workinggroup.aero +works.aero + +// af : https://www.nic.af/domain-price +af +com.af +edu.af +gov.af +net.af +org.af + +// ag : http://www.nic.ag/prices.htm +ag +co.ag +com.ag +net.ag +nom.ag +org.ag + +// ai : http://nic.com.ai/ +ai +com.ai +net.ai +off.ai +org.ai + +// al : http://www.ert.gov.al/ert_alb/faq_det.html?Id=31 +al +com.al +edu.al +gov.al +mil.al +net.al +org.al + +// am : https://www.amnic.net/policy/en/Policy_EN.pdf +// Confirmed by ISOC AM 2024-11-18 +am +co.am +com.am +commune.am +net.am +org.am + +// ao : https://www.iana.org/domains/root/db/ao.html +// https://www.dns.ao/ao/ +ao +co.ao +ed.ao +edu.ao +gov.ao +gv.ao +it.ao +og.ao +org.ao +pb.ao + +// aq : https://www.iana.org/domains/root/db/aq.html +aq + +// ar : https://nic.ar/es/nic-argentina/normativa +ar +bet.ar +com.ar +coop.ar +edu.ar +gob.ar +gov.ar +int.ar +mil.ar +musica.ar +mutual.ar +net.ar +org.ar +seg.ar +senasa.ar +tur.ar + +// arpa : https://www.iana.org/domains/root/db/arpa.html +// Confirmed by registry 2008-06-18 +arpa +e164.arpa +home.arpa +in-addr.arpa +ip6.arpa +iris.arpa +uri.arpa +urn.arpa + +// as : https://www.iana.org/domains/root/db/as.html +as +gov.as + +// asia : https://www.iana.org/domains/root/db/asia.html +asia + +// at : https://www.iana.org/domains/root/db/at.html +// Confirmed by registry 2008-06-17 +at +ac.at +sth.ac.at +co.at +gv.at +or.at + +// au : https://www.iana.org/domains/root/db/au.html +// https://www.auda.org.au/ +// Confirmed by registry 2025-07-16 +au +// 2LDs +asn.au +com.au +edu.au +gov.au +id.au +net.au +org.au +// Historic 2LDs (closed to new registration, but sites still exist) +conf.au +oz.au +// CGDNs : https://www.auda.org.au/au-domain-names/the-different-au-domain-names/state-and-territory-domain-names/ +act.au +nsw.au +nt.au +qld.au +sa.au +tas.au +vic.au +wa.au +// 3LDs +act.edu.au +catholic.edu.au +// eq.edu.au - Removed at the request of the Queensland Department of Education +nsw.edu.au +nt.edu.au +qld.edu.au +sa.edu.au +tas.edu.au +vic.edu.au +wa.edu.au +// act.gov.au - Bug 984824 - Removed at request of Greg Tankard +// nsw.gov.au - Bug 547985 - Removed at request of +// nt.gov.au - Bug 940478 - Removed at request of Greg Connors +qld.gov.au +sa.gov.au +tas.gov.au +vic.gov.au +wa.gov.au +// 4LDs +// education.tas.edu.au - Removed at the request of the Department of Education Tasmania +// schools.nsw.edu.au - Removed at the request of the New South Wales Department of Education. + +// aw : https://www.iana.org/domains/root/db/aw.html +aw +com.aw + +// ax : https://www.iana.org/domains/root/db/ax.html +ax + +// az : https://www.iana.org/domains/root/db/az.html +// Confirmed via https://whois.az/?page_id=10 2024-12-11 +az +biz.az +co.az +com.az +edu.az +gov.az +info.az +int.az +mil.az +name.az +net.az +org.az +pp.az +// No longer available for registration, however domains exist as of 2024-12-11 +// see https://whois.az/?page_id=783 +pro.az + +// ba : https://www.iana.org/domains/root/db/ba.html +ba +com.ba +edu.ba +gov.ba +mil.ba +net.ba +org.ba + +// bb : https://www.iana.org/domains/root/db/bb.html +bb +biz.bb +co.bb +com.bb +edu.bb +gov.bb +info.bb +net.bb +org.bb +store.bb +tv.bb + +// bd : https://www.iana.org/domains/root/db/bd.html +*.bd + +// be : https://www.iana.org/domains/root/db/be.html +// Confirmed by registry 2008-06-08 +be +ac.be + +// bf : https://www.iana.org/domains/root/db/bf.html +bf +gov.bf + +// bg : https://www.iana.org/domains/root/db/bg.html +// https://www.register.bg/user/static/rules/en/index.html +bg +0.bg +1.bg +2.bg +3.bg +4.bg +5.bg +6.bg +7.bg +8.bg +9.bg +a.bg +b.bg +c.bg +d.bg +e.bg +f.bg +g.bg +h.bg +i.bg +j.bg +k.bg +l.bg +m.bg +n.bg +o.bg +p.bg +q.bg +r.bg +s.bg +t.bg +u.bg +v.bg +w.bg +x.bg +y.bg +z.bg + +// bh : https://www.iana.org/domains/root/db/bh.html +bh +com.bh +edu.bh +gov.bh +net.bh +org.bh + +// bi : https://www.iana.org/domains/root/db/bi.html +// http://whois.nic.bi/ +bi +co.bi +com.bi +edu.bi +or.bi +org.bi + +// biz : https://www.iana.org/domains/root/db/biz.html +biz + +// bj : https://nic.bj/bj-suffixes.txt +// Submitted by registry +bj +africa.bj +agro.bj +architectes.bj +assur.bj +avocats.bj +co.bj +com.bj +eco.bj +econo.bj +edu.bj +info.bj +loisirs.bj +money.bj +net.bj +org.bj +ote.bj +restaurant.bj +resto.bj +tourism.bj +univ.bj + +// bm : https://www.bermudanic.bm/domain-registration/index.php +bm +com.bm +edu.bm +gov.bm +net.bm +org.bm + +// bn : http://www.bnnic.bn/faqs +bn +com.bn +edu.bn +gov.bn +net.bn +org.bn + +// bo : https://nic.bo +// Confirmed by registry 2024-11-19 +bo +com.bo +edu.bo +gob.bo +int.bo +mil.bo +net.bo +org.bo +tv.bo +web.bo +// Social Domains +academia.bo +agro.bo +arte.bo +blog.bo +bolivia.bo +ciencia.bo +cooperativa.bo +democracia.bo +deporte.bo +ecologia.bo +economia.bo +empresa.bo +indigena.bo +industria.bo +info.bo +medicina.bo +movimiento.bo +musica.bo +natural.bo +nombre.bo +noticias.bo +patria.bo +plurinacional.bo +politica.bo +profesional.bo +pueblo.bo +revista.bo +salud.bo +tecnologia.bo +tksat.bo +transporte.bo +wiki.bo + +// br : http://registro.br/dominio/categoria.html +// Submitted by registry +br +9guacu.br +abc.br +adm.br +adv.br +agr.br +aju.br +am.br +anani.br +aparecida.br +api.br +app.br +arq.br +art.br +ato.br +b.br +barueri.br +belem.br +bet.br +bhz.br +bib.br +bio.br +blog.br +bmd.br +boavista.br +bsb.br +campinagrande.br +campinas.br +caxias.br +cim.br +cng.br +cnt.br +com.br +contagem.br +coop.br +coz.br +cri.br +cuiaba.br +curitiba.br +def.br +des.br +det.br +dev.br +ecn.br +eco.br +edu.br +emp.br +enf.br +eng.br +esp.br +etc.br +eti.br +far.br +feira.br +flog.br +floripa.br +fm.br +fnd.br +fortal.br +fot.br +foz.br +fst.br +g12.br +geo.br +ggf.br +goiania.br +gov.br +// gov.br 26 states + df https://en.wikipedia.org/wiki/States_of_Brazil +ac.gov.br +al.gov.br +am.gov.br +ap.gov.br +ba.gov.br +ce.gov.br +df.gov.br +es.gov.br +go.gov.br +ma.gov.br +mg.gov.br +ms.gov.br +mt.gov.br +pa.gov.br +pb.gov.br +pe.gov.br +pi.gov.br +pr.gov.br +rj.gov.br +rn.gov.br +ro.gov.br +rr.gov.br +rs.gov.br +sc.gov.br +se.gov.br +sp.gov.br +to.gov.br +gru.br +ia.br +imb.br +ind.br +inf.br +jab.br +jampa.br +jdf.br +joinville.br +jor.br +jus.br +leg.br +leilao.br +lel.br +log.br +londrina.br +macapa.br +maceio.br +manaus.br +maringa.br +mat.br +med.br +mil.br +morena.br +mp.br +mus.br +natal.br +net.br +niteroi.br +*.nom.br +not.br +ntr.br +odo.br +ong.br +org.br +osasco.br +palmas.br +poa.br +ppg.br +pro.br +psc.br +psi.br +pvh.br +qsl.br +radio.br +rec.br +recife.br +rep.br +ribeirao.br +rio.br +riobranco.br +riopreto.br +salvador.br +sampa.br +santamaria.br +santoandre.br +saobernardo.br +saogonca.br +seg.br +sjc.br +slg.br +slz.br +social.br +sorocaba.br +srv.br +taxi.br +tc.br +tec.br +teo.br +the.br +tmp.br +trd.br +tur.br +tv.br +udi.br +vet.br +vix.br +vlog.br +wiki.br +xyz.br +zlg.br + +// bs : http://www.nic.bs/rules.html +bs +com.bs +edu.bs +gov.bs +net.bs +org.bs + +// bt : https://www.iana.org/domains/root/db/bt.html +bt +com.bt +edu.bt +gov.bt +net.bt +org.bt + +// bv : No registrations at this time. +// Submitted by registry +bv + +// bw : https://www.iana.org/domains/root/db/bw.html +// https://nic.net.bw/bw-name-structure +bw +ac.bw +co.bw +gov.bw +net.bw +org.bw + +// by : https://www.iana.org/domains/root/db/by.html +// http://tld.by/rules_2006_en.html +// list of other 2nd level tlds ? +by +gov.by +mil.by +// Official information does not indicate that com.by is a reserved +// second-level domain, but it's being used as one (see www.google.com.by and +// www.yahoo.com.by, for example), so we list it here for safety's sake. +com.by +// http://hoster.by/ +of.by + +// bz : https://www.iana.org/domains/root/db/bz.html +// http://www.belizenic.bz/ +bz +co.bz +com.bz +edu.bz +gov.bz +net.bz +org.bz + +// ca : https://www.iana.org/domains/root/db/ca.html +ca +// ca geographical names +ab.ca +bc.ca +mb.ca +nb.ca +nf.ca +nl.ca +ns.ca +nt.ca +nu.ca +on.ca +pe.ca +qc.ca +sk.ca +yk.ca +// gc.ca: https://en.wikipedia.org/wiki/.gc.ca +// see also: http://registry.gc.ca/en/SubdomainFAQ +gc.ca + +// cat : https://www.iana.org/domains/root/db/cat.html +cat + +// cc : https://www.iana.org/domains/root/db/cc.html +cc + +// cd : https://www.iana.org/domains/root/db/cd.html +// https://www.nic.cd +cd +gov.cd + +// cf : https://www.iana.org/domains/root/db/cf.html +cf + +// cg : https://www.iana.org/domains/root/db/cg.html +cg + +// ch : https://www.iana.org/domains/root/db/ch.html +ch + +// ci : https://www.iana.org/domains/root/db/ci.html +ci +ac.ci +aéroport.ci +asso.ci +co.ci +com.ci +ed.ci +edu.ci +go.ci +gouv.ci +int.ci +net.ci +or.ci +org.ci + +// ck : https://www.iana.org/domains/root/db/ck.html +*.ck +!www.ck + +// cl : https://www.nic.cl +// Confirmed by .CL registry +cl +co.cl +gob.cl +gov.cl +mil.cl + +// cm : https://www.iana.org/domains/root/db/cm.html plus bug 981927 +cm +co.cm +com.cm +gov.cm +net.cm + +// cn : https://www.iana.org/domains/root/db/cn.html +// Submitted by registry +cn +ac.cn +com.cn +edu.cn +gov.cn +mil.cn +net.cn +org.cn +公司.cn +網絡.cn +网络.cn +// cn geographic names +ah.cn +bj.cn +cq.cn +fj.cn +gd.cn +gs.cn +gx.cn +gz.cn +ha.cn +hb.cn +he.cn +hi.cn +hk.cn +hl.cn +hn.cn +jl.cn +js.cn +jx.cn +ln.cn +mo.cn +nm.cn +nx.cn +qh.cn +sc.cn +sd.cn +sh.cn +sn.cn +sx.cn +tj.cn +tw.cn +xj.cn +xz.cn +yn.cn +zj.cn + +// co : https://www.iana.org/domains/root/db/co.html +// https://www.cointernet.com.co/como-funciona-un-dominio-restringido +// Confirmed by registry 2024-11-18 +co +com.co +edu.co +gov.co +mil.co +net.co +nom.co +org.co + +// com : https://www.iana.org/domains/root/db/com.html +com + +// coop : https://www.iana.org/domains/root/db/coop.html +coop + +// cr : https://nic.cr/capitulo-1-registro-de-un-nombre-de-dominio/ +cr +ac.cr +co.cr +ed.cr +fi.cr +go.cr +or.cr +sa.cr + +// cu : https://www.iana.org/domains/root/db/cu.html +cu +com.cu +edu.cu +gob.cu +inf.cu +nat.cu +net.cu +org.cu + +// cv : https://www.iana.org/domains/root/db/cv.html +// https://ola.cv/domain-extensions-under-cv/ +// Confirmed by registry 2024-11-26 +cv +com.cv +edu.cv +id.cv +int.cv +net.cv +nome.cv +org.cv +publ.cv + +// cw : https://www.uoc.cw/cw-registry +// Confirmed by registry 2024-11-19 +cw +com.cw +edu.cw +net.cw +org.cw + +// cx : https://www.iana.org/domains/root/db/cx.html +// list of other 2nd level tlds ? +cx +gov.cx + +// cy : http://www.nic.cy/ +// Submitted by Panayiotou Fotia +// https://nic.cy/wp-content/uploads/2024/01/Create-Request-for-domain-name-registration-1.pdf +cy +ac.cy +biz.cy +com.cy +ekloges.cy +gov.cy +ltd.cy +mil.cy +net.cy +org.cy +press.cy +pro.cy +tm.cy + +// cz : https://www.iana.org/domains/root/db/cz.html +// Confirmed by registry 2025-08-06 +cz +gov.cz + +// de : https://www.iana.org/domains/root/db/de.html +// Confirmed by registry (with technical +// reservations) 2008-07-01 +de + +// dj : https://www.iana.org/domains/root/db/dj.html +dj + +// dk : https://www.iana.org/domains/root/db/dk.html +// Confirmed by registry 2008-06-17 +dk + +// dm : https://www.iana.org/domains/root/db/dm.html +// https://nic.dm/policies/pdf/DMRulesandGuidelines2024v1.pdf +// Confirmed by registry 2024-11-19 +dm +co.dm +com.dm +edu.dm +gov.dm +net.dm +org.dm + +// do : https://www.iana.org/domains/root/db/do.html +do +art.do +com.do +edu.do +gob.do +gov.do +mil.do +net.do +org.do +sld.do +web.do + +// dz : http://www.nic.dz/images/pdf_nic/charte.pdf +dz +art.dz +asso.dz +com.dz +edu.dz +gov.dz +net.dz +org.dz +pol.dz +soc.dz +tm.dz + +// ec : https://www.nic.ec/ +// Submitted by registry +ec +abg.ec +adm.ec +agron.ec +arqt.ec +art.ec +bar.ec +chef.ec +com.ec +cont.ec +cpa.ec +cue.ec +dent.ec +dgn.ec +disco.ec +doc.ec +edu.ec +eng.ec +esm.ec +fin.ec +fot.ec +gal.ec +gob.ec +gov.ec +gye.ec +ibr.ec +info.ec +k12.ec +lat.ec +loj.ec +med.ec +mil.ec +mktg.ec +mon.ec +net.ec +ntr.ec +odont.ec +org.ec +pro.ec +prof.ec +psic.ec +psiq.ec +pub.ec +rio.ec +rrpp.ec +sal.ec +tech.ec +tul.ec +tur.ec +uio.ec +vet.ec +xxx.ec + +// edu : https://www.iana.org/domains/root/db/edu.html +edu + +// ee : https://www.internet.ee/domains/general-domains-and-procedure-for-registration-of-sub-domains-under-general-domains +ee +aip.ee +com.ee +edu.ee +fie.ee +gov.ee +lib.ee +med.ee +org.ee +pri.ee +riik.ee + +// eg : https://www.iana.org/domains/root/db/eg.html +// https://domain.eg/en/domain-rules/subdomain-names-types/ +eg +ac.eg +com.eg +edu.eg +eun.eg +gov.eg +info.eg +me.eg +mil.eg +name.eg +net.eg +org.eg +sci.eg +sport.eg +tv.eg + +// er : https://www.iana.org/domains/root/db/er.html +*.er + +// es : https://www.dominios.es/en +es +com.es +edu.es +gob.es +nom.es +org.es + +// et : https://www.iana.org/domains/root/db/et.html +et +biz.et +com.et +edu.et +gov.et +info.et +name.et +net.et +org.et + +// eu : https://www.iana.org/domains/root/db/eu.html +eu + +// fi : https://www.iana.org/domains/root/db/fi.html +fi +// aland.fi : https://www.iana.org/domains/root/db/ax.html +// This domain is being phased out in favor of .ax. As there are still many +// domains under aland.fi, we still keep it on the list until aland.fi is +// completely removed. +aland.fi + +// fj : http://domains.fj/ +// Submitted by registry 2020-02-11 +fj +ac.fj +biz.fj +com.fj +gov.fj +info.fj +mil.fj +name.fj +net.fj +org.fj +pro.fj + +// fk : https://www.iana.org/domains/root/db/fk.html +*.fk + +// fm : https://www.iana.org/domains/root/db/fm.html +fm +com.fm +edu.fm +net.fm +org.fm + +// fo : https://www.iana.org/domains/root/db/fo.html +fo + +// fr : https://www.afnic.fr/ https://www.afnic.fr/wp-media/uploads/2022/12/afnic-naming-policy-2023-01-01.pdf +fr +asso.fr +com.fr +gouv.fr +nom.fr +prd.fr +tm.fr +// Other SLDs now selfmanaged out of AFNIC range. Former "domaines sectoriels", still registration suffixes +avoues.fr +cci.fr +greta.fr +huissier-justice.fr + +// ga : https://www.iana.org/domains/root/db/ga.html +ga + +// gb : This registry is effectively dormant +// Submitted by registry +gb + +// gd : https://www.iana.org/domains/root/db/gd.html +gd +edu.gd +gov.gd + +// ge : https://nic.ge/en/administrator/the-ge-domain-regulations +// Confirmed by registry 2024-11-20 +ge +com.ge +edu.ge +gov.ge +net.ge +org.ge +pvt.ge +school.ge + +// gf : https://www.iana.org/domains/root/db/gf.html +gf + +// gg : https://www.channelisles.net/register-1/register-direct +// Confirmed by registry 2013-11-28 +gg +co.gg +net.gg +org.gg + +// gh : https://www.iana.org/domains/root/db/gh.html +// https://www.nic.gh/ +// Although domains directly at second level are not possible at the moment, +// they have been possible for some time and may come back. +gh +biz.gh +com.gh +edu.gh +gov.gh +mil.gh +net.gh +org.gh + +// gi : http://www.nic.gi/rules.html +gi +com.gi +edu.gi +gov.gi +ltd.gi +mod.gi +org.gi + +// gl : https://www.iana.org/domains/root/db/gl.html +// http://nic.gl +gl +co.gl +com.gl +edu.gl +net.gl +org.gl + +// gm : http://www.nic.gm/htmlpages%5Cgm-policy.htm +gm + +// gn : http://psg.com/dns/gn/gn.txt +// Submitted by registry +gn +ac.gn +com.gn +edu.gn +gov.gn +net.gn +org.gn + +// gov : https://www.iana.org/domains/root/db/gov.html +gov + +// gp : http://www.nic.gp/index.php?lang=en +gp +asso.gp +com.gp +edu.gp +mobi.gp +net.gp +org.gp + +// gq : https://www.iana.org/domains/root/db/gq.html +gq + +// gr : https://www.iana.org/domains/root/db/gr.html +// Submitted by registry +gr +com.gr +edu.gr +gov.gr +net.gr +org.gr + +// gs : https://www.iana.org/domains/root/db/gs.html +gs + +// gt : https://www.gt/sitio/registration_policy.php?lang=en +gt +com.gt +edu.gt +gob.gt +ind.gt +mil.gt +net.gt +org.gt + +// gu : http://gadao.gov.gu/register.html +// University of Guam : https://www.uog.edu +// Submitted by uognoc@triton.uog.edu +gu +com.gu +edu.gu +gov.gu +guam.gu +info.gu +net.gu +org.gu +web.gu + +// gw : https://www.iana.org/domains/root/db/gw.html +// gw : https://nic.gw/regras/ +gw + +// gy : https://www.iana.org/domains/root/db/gy.html +// http://registry.gy/ +gy +co.gy +com.gy +edu.gy +gov.gy +net.gy +org.gy + +// hk : https://www.hkirc.hk +// Submitted by registry +hk +com.hk +edu.hk +gov.hk +idv.hk +net.hk +org.hk +个人.hk +個人.hk +公司.hk +政府.hk +敎育.hk +教育.hk +箇人.hk +組織.hk +組织.hk +網絡.hk +網络.hk +组織.hk +组织.hk +网絡.hk +网络.hk + +// hm : https://www.iana.org/domains/root/db/hm.html +hm + +// hn : https://www.iana.org/domains/root/db/hn.html +hn +com.hn +edu.hn +gob.hn +mil.hn +net.hn +org.hn + +// hr : http://www.dns.hr/documents/pdf/HRTLD-regulations.pdf +hr +com.hr +from.hr +iz.hr +name.hr + +// ht : http://www.nic.ht/info/charte.cfm +ht +adult.ht +art.ht +asso.ht +com.ht +coop.ht +edu.ht +firm.ht +gouv.ht +info.ht +med.ht +net.ht +org.ht +perso.ht +pol.ht +pro.ht +rel.ht +shop.ht + +// hu : https://www.iana.org/domains/root/db/hu.html +// Confirmed by registry 2008-06-12 +hu +2000.hu +agrar.hu +bolt.hu +casino.hu +city.hu +co.hu +erotica.hu +erotika.hu +film.hu +forum.hu +games.hu +hotel.hu +info.hu +ingatlan.hu +jogasz.hu +konyvelo.hu +lakas.hu +media.hu +news.hu +org.hu +priv.hu +reklam.hu +sex.hu +shop.hu +sport.hu +suli.hu +szex.hu +tm.hu +tozsde.hu +utazas.hu +video.hu + +// id : https://www.iana.org/domains/root/db/id.html +id +ac.id +biz.id +co.id +desa.id +go.id +kop.id +mil.id +my.id +net.id +or.id +ponpes.id +sch.id +web.id + +// ie : https://www.iana.org/domains/root/db/ie.html +ie +gov.ie + +// il : http://www.isoc.org.il/domains/ +// see also: https://en.isoc.org.il/il-cctld/registration-rules +// ISOC-IL (operated by .il Registry) +il +ac.il +co.il +gov.il +idf.il +k12.il +muni.il +net.il +org.il +// xn--4dbrk0ce ("Israel", Hebrew) : IL +ישראל +// xn--4dbgdty6c.xn--4dbrk0ce. +אקדמיה.ישראל +// xn--5dbhl8d.xn--4dbrk0ce. +ישוב.ישראל +// xn--8dbq2a.xn--4dbrk0ce. +צהל.ישראל +// xn--hebda8b.xn--4dbrk0ce. +ממשל.ישראל + +// im : https://www.nic.im/ +// Submitted by registry +im +ac.im +co.im +ltd.co.im +plc.co.im +com.im +net.im +org.im +tt.im +tv.im + +// in : https://www.iana.org/domains/root/db/in.html +// see also: https://registry.in/policies +// Please note, that nic.in is not an official eTLD, but used by most +// government institutions. +in +5g.in +6g.in +ac.in +ai.in +am.in +bihar.in +biz.in +business.in +ca.in +cn.in +co.in +com.in +coop.in +cs.in +delhi.in +dr.in +edu.in +er.in +firm.in +gen.in +gov.in +gujarat.in +ind.in +info.in +int.in +internet.in +io.in +me.in +mil.in +net.in +nic.in +org.in +pg.in +post.in +pro.in +res.in +travel.in +tv.in +uk.in +up.in +us.in + +// info : https://www.iana.org/domains/root/db/info.html +info + +// int : https://www.iana.org/domains/root/db/int.html +// Confirmed by registry 2008-06-18 +int +eu.int + +// io : http://www.nic.io/rules.htm +io +co.io +com.io +edu.io +gov.io +mil.io +net.io +nom.io +org.io + +// iq : http://www.cmc.iq/english/iq/iqregister1.htm +iq +com.iq +edu.iq +gov.iq +mil.iq +net.iq +org.iq + +// ir : http://www.nic.ir/Terms_and_Conditions_ir,_Appendix_1_Domain_Rules +// Also see http://www.nic.ir/Internationalized_Domain_Names +// Two .ir entries added at request of , 2010-04-16 +ir +ac.ir +co.ir +gov.ir +id.ir +net.ir +org.ir +sch.ir +// xn--mgba3a4f16a.ir (.ir, Persian YEH) +ایران.ir +// xn--mgba3a4fra.ir (.ir, Arabic YEH) +ايران.ir + +// is : http://www.isnic.is/domain/rules.php +// Confirmed by registry 2024-11-17 +is + +// it : https://www.iana.org/domains/root/db/it.html +// https://www.nic.it/ +it +edu.it +gov.it +// Regions (3.3.1) +// https://www.nic.it/en/manage-your-it/forms-and-docs -> "Assignment and Management of domain names" +abr.it +abruzzo.it +aosta-valley.it +aostavalley.it +bas.it +basilicata.it +cal.it +calabria.it +cam.it +campania.it +emilia-romagna.it +emiliaromagna.it +emr.it +friuli-v-giulia.it +friuli-ve-giulia.it +friuli-vegiulia.it +friuli-venezia-giulia.it +friuli-veneziagiulia.it +friuli-vgiulia.it +friuliv-giulia.it +friulive-giulia.it +friulivegiulia.it +friulivenezia-giulia.it +friuliveneziagiulia.it +friulivgiulia.it +fvg.it +laz.it +lazio.it +lig.it +liguria.it +lom.it +lombardia.it +lombardy.it +lucania.it +mar.it +marche.it +mol.it +molise.it +piedmont.it +piemonte.it +pmn.it +pug.it +puglia.it +sar.it +sardegna.it +sardinia.it +sic.it +sicilia.it +sicily.it +taa.it +tos.it +toscana.it +trentin-sud-tirol.it +trentin-süd-tirol.it +trentin-sudtirol.it +trentin-südtirol.it +trentin-sued-tirol.it +trentin-suedtirol.it +trentino.it +trentino-a-adige.it +trentino-aadige.it +trentino-alto-adige.it +trentino-altoadige.it +trentino-s-tirol.it +trentino-stirol.it +trentino-sud-tirol.it +trentino-süd-tirol.it +trentino-sudtirol.it +trentino-südtirol.it +trentino-sued-tirol.it +trentino-suedtirol.it +trentinoa-adige.it +trentinoaadige.it +trentinoalto-adige.it +trentinoaltoadige.it +trentinos-tirol.it +trentinostirol.it +trentinosud-tirol.it +trentinosüd-tirol.it +trentinosudtirol.it +trentinosüdtirol.it +trentinosued-tirol.it +trentinosuedtirol.it +trentinsud-tirol.it +trentinsüd-tirol.it +trentinsudtirol.it +trentinsüdtirol.it +trentinsued-tirol.it +trentinsuedtirol.it +tuscany.it +umb.it +umbria.it +val-d-aosta.it +val-daosta.it +vald-aosta.it +valdaosta.it +valle-aosta.it +valle-d-aosta.it +valle-daosta.it +valleaosta.it +valled-aosta.it +valledaosta.it +vallee-aoste.it +vallée-aoste.it +vallee-d-aoste.it +vallée-d-aoste.it +valleeaoste.it +valléeaoste.it +valleedaoste.it +valléedaoste.it +vao.it +vda.it +ven.it +veneto.it +// Provinces (3.3.2) +ag.it +agrigento.it +al.it +alessandria.it +alto-adige.it +altoadige.it +an.it +ancona.it +andria-barletta-trani.it +andria-trani-barletta.it +andriabarlettatrani.it +andriatranibarletta.it +ao.it +aosta.it +aoste.it +ap.it +aq.it +aquila.it +ar.it +arezzo.it +ascoli-piceno.it +ascolipiceno.it +asti.it +at.it +av.it +avellino.it +ba.it +balsan.it +balsan-sudtirol.it +balsan-südtirol.it +balsan-suedtirol.it +bari.it +barletta-trani-andria.it +barlettatraniandria.it +belluno.it +benevento.it +bergamo.it +bg.it +bi.it +biella.it +bl.it +bn.it +bo.it +bologna.it +bolzano.it +bolzano-altoadige.it +bozen.it +bozen-sudtirol.it +bozen-südtirol.it +bozen-suedtirol.it +br.it +brescia.it +brindisi.it +bs.it +bt.it +bulsan.it +bulsan-sudtirol.it +bulsan-südtirol.it +bulsan-suedtirol.it +bz.it +ca.it +cagliari.it +caltanissetta.it +campidano-medio.it +campidanomedio.it +campobasso.it +carbonia-iglesias.it +carboniaiglesias.it +carrara-massa.it +carraramassa.it +caserta.it +catania.it +catanzaro.it +cb.it +ce.it +cesena-forli.it +cesena-forlì.it +cesenaforli.it +cesenaforlì.it +ch.it +chieti.it +ci.it +cl.it +cn.it +co.it +como.it +cosenza.it +cr.it +cremona.it +crotone.it +cs.it +ct.it +cuneo.it +cz.it +dell-ogliastra.it +dellogliastra.it +en.it +enna.it +fc.it +fe.it +fermo.it +ferrara.it +fg.it +fi.it +firenze.it +florence.it +fm.it +foggia.it +forli-cesena.it +forlì-cesena.it +forlicesena.it +forlìcesena.it +fr.it +frosinone.it +ge.it +genoa.it +genova.it +go.it +gorizia.it +gr.it +grosseto.it +iglesias-carbonia.it +iglesiascarbonia.it +im.it +imperia.it +is.it +isernia.it +kr.it +la-spezia.it +laquila.it +laspezia.it +latina.it +lc.it +le.it +lecce.it +lecco.it +li.it +livorno.it +lo.it +lodi.it +lt.it +lu.it +lucca.it +macerata.it +mantova.it +massa-carrara.it +massacarrara.it +matera.it +mb.it +mc.it +me.it +medio-campidano.it +mediocampidano.it +messina.it +mi.it +milan.it +milano.it +mn.it +mo.it +modena.it +monza.it +monza-brianza.it +monza-e-della-brianza.it +monzabrianza.it +monzaebrianza.it +monzaedellabrianza.it +ms.it +mt.it +na.it +naples.it +napoli.it +no.it +novara.it +nu.it +nuoro.it +og.it +ogliastra.it +olbia-tempio.it +olbiatempio.it +or.it +oristano.it +ot.it +pa.it +padova.it +padua.it +palermo.it +parma.it +pavia.it +pc.it +pd.it +pe.it +perugia.it +pesaro-urbino.it +pesarourbino.it +pescara.it +pg.it +pi.it +piacenza.it +pisa.it +pistoia.it +pn.it +po.it +pordenone.it +potenza.it +pr.it +prato.it +pt.it +pu.it +pv.it +pz.it +ra.it +ragusa.it +ravenna.it +rc.it +re.it +reggio-calabria.it +reggio-emilia.it +reggiocalabria.it +reggioemilia.it +rg.it +ri.it +rieti.it +rimini.it +rm.it +rn.it +ro.it +roma.it +rome.it +rovigo.it +sa.it +salerno.it +sassari.it +savona.it +si.it +siena.it +siracusa.it +so.it +sondrio.it +sp.it +sr.it +ss.it +südtirol.it +suedtirol.it +sv.it +ta.it +taranto.it +te.it +tempio-olbia.it +tempioolbia.it +teramo.it +terni.it +tn.it +to.it +torino.it +tp.it +tr.it +trani-andria-barletta.it +trani-barletta-andria.it +traniandriabarletta.it +tranibarlettaandria.it +trapani.it +trento.it +treviso.it +trieste.it +ts.it +turin.it +tv.it +ud.it +udine.it +urbino-pesaro.it +urbinopesaro.it +va.it +varese.it +vb.it +vc.it +ve.it +venezia.it +venice.it +verbania.it +vercelli.it +verona.it +vi.it +vibo-valentia.it +vibovalentia.it +vicenza.it +viterbo.it +vr.it +vs.it +vt.it +vv.it + +// je : https://www.iana.org/domains/root/db/je.html +// Confirmed by registry 2013-11-28 +je +co.je +net.je +org.je + +// jm : http://www.com.jm/register.html +*.jm + +// jo : https://www.dns.jo/JoFamily.aspx +// Confirmed by registry 2024-11-17 +jo +agri.jo +ai.jo +com.jo +edu.jo +eng.jo +fm.jo +gov.jo +mil.jo +net.jo +org.jo +per.jo +phd.jo +sch.jo +tv.jo + +// jobs : https://www.iana.org/domains/root/db/jobs.html +jobs + +// jp : https://www.iana.org/domains/root/db/jp.html +// http://jprs.co.jp/en/jpdomain.html +// Confirmed by registry 2024-11-22 +jp +// jp organizational type names +ac.jp +ad.jp +co.jp +ed.jp +go.jp +gr.jp +lg.jp +ne.jp +or.jp +// jp prefecture type names +aichi.jp +akita.jp +aomori.jp +chiba.jp +ehime.jp +fukui.jp +fukuoka.jp +fukushima.jp +gifu.jp +gunma.jp +hiroshima.jp +hokkaido.jp +hyogo.jp +ibaraki.jp +ishikawa.jp +iwate.jp +kagawa.jp +kagoshima.jp +kanagawa.jp +kochi.jp +kumamoto.jp +kyoto.jp +mie.jp +miyagi.jp +miyazaki.jp +nagano.jp +nagasaki.jp +nara.jp +niigata.jp +oita.jp +okayama.jp +okinawa.jp +osaka.jp +saga.jp +saitama.jp +shiga.jp +shimane.jp +shizuoka.jp +tochigi.jp +tokushima.jp +tokyo.jp +tottori.jp +toyama.jp +wakayama.jp +yamagata.jp +yamaguchi.jp +yamanashi.jp +三重.jp +京都.jp +佐賀.jp +兵庫.jp +北海道.jp +千葉.jp +和歌山.jp +埼玉.jp +大分.jp +大阪.jp +奈良.jp +宮城.jp +宮崎.jp +富山.jp +山口.jp +山形.jp +山梨.jp +岐阜.jp +岡山.jp +岩手.jp +島根.jp +広島.jp +徳島.jp +愛媛.jp +愛知.jp +新潟.jp +東京.jp +栃木.jp +沖縄.jp +滋賀.jp +熊本.jp +石川.jp +神奈川.jp +福井.jp +福岡.jp +福島.jp +秋田.jp +群馬.jp +茨城.jp +長崎.jp +長野.jp +青森.jp +静岡.jp +香川.jp +高知.jp +鳥取.jp +鹿児島.jp +// jp geographic type names +// http://jprs.jp/doc/rule/saisoku-1.html +// 2024-11-22: JPRS confirmed that jp geographic type names no longer accept new registrations. +// Once all existing registrations expire (marking full discontinuation), these suffixes +// will be removed from the PSL. +*.kawasaki.jp +!city.kawasaki.jp +*.kitakyushu.jp +!city.kitakyushu.jp +*.kobe.jp +!city.kobe.jp +*.nagoya.jp +!city.nagoya.jp +*.sapporo.jp +!city.sapporo.jp +*.sendai.jp +!city.sendai.jp +*.yokohama.jp +!city.yokohama.jp +// 4th level registration +aisai.aichi.jp +ama.aichi.jp +anjo.aichi.jp +asuke.aichi.jp +chiryu.aichi.jp +chita.aichi.jp +fuso.aichi.jp +gamagori.aichi.jp +handa.aichi.jp +hazu.aichi.jp +hekinan.aichi.jp +higashiura.aichi.jp +ichinomiya.aichi.jp +inazawa.aichi.jp +inuyama.aichi.jp +isshiki.aichi.jp +iwakura.aichi.jp +kanie.aichi.jp +kariya.aichi.jp +kasugai.aichi.jp +kira.aichi.jp +kiyosu.aichi.jp +komaki.aichi.jp +konan.aichi.jp +kota.aichi.jp +mihama.aichi.jp +miyoshi.aichi.jp +nishio.aichi.jp +nisshin.aichi.jp +obu.aichi.jp +oguchi.aichi.jp +oharu.aichi.jp +okazaki.aichi.jp +owariasahi.aichi.jp +seto.aichi.jp +shikatsu.aichi.jp +shinshiro.aichi.jp +shitara.aichi.jp +tahara.aichi.jp +takahama.aichi.jp +tobishima.aichi.jp +toei.aichi.jp +togo.aichi.jp +tokai.aichi.jp +tokoname.aichi.jp +toyoake.aichi.jp +toyohashi.aichi.jp +toyokawa.aichi.jp +toyone.aichi.jp +toyota.aichi.jp +tsushima.aichi.jp +yatomi.aichi.jp +akita.akita.jp +daisen.akita.jp +fujisato.akita.jp +gojome.akita.jp +hachirogata.akita.jp +happou.akita.jp +higashinaruse.akita.jp +honjo.akita.jp +honjyo.akita.jp +ikawa.akita.jp +kamikoani.akita.jp +kamioka.akita.jp +katagami.akita.jp +kazuno.akita.jp +kitaakita.akita.jp +kosaka.akita.jp +kyowa.akita.jp +misato.akita.jp +mitane.akita.jp +moriyoshi.akita.jp +nikaho.akita.jp +noshiro.akita.jp +odate.akita.jp +oga.akita.jp +ogata.akita.jp +semboku.akita.jp +yokote.akita.jp +yurihonjo.akita.jp +aomori.aomori.jp +gonohe.aomori.jp +hachinohe.aomori.jp +hashikami.aomori.jp +hiranai.aomori.jp +hirosaki.aomori.jp +itayanagi.aomori.jp +kuroishi.aomori.jp +misawa.aomori.jp +mutsu.aomori.jp +nakadomari.aomori.jp +noheji.aomori.jp +oirase.aomori.jp +owani.aomori.jp +rokunohe.aomori.jp +sannohe.aomori.jp +shichinohe.aomori.jp +shingo.aomori.jp +takko.aomori.jp +towada.aomori.jp +tsugaru.aomori.jp +tsuruta.aomori.jp +abiko.chiba.jp +asahi.chiba.jp +chonan.chiba.jp +chosei.chiba.jp +choshi.chiba.jp +chuo.chiba.jp +funabashi.chiba.jp +futtsu.chiba.jp +hanamigawa.chiba.jp +ichihara.chiba.jp +ichikawa.chiba.jp +ichinomiya.chiba.jp +inzai.chiba.jp +isumi.chiba.jp +kamagaya.chiba.jp +kamogawa.chiba.jp +kashiwa.chiba.jp +katori.chiba.jp +katsuura.chiba.jp +kimitsu.chiba.jp +kisarazu.chiba.jp +kozaki.chiba.jp +kujukuri.chiba.jp +kyonan.chiba.jp +matsudo.chiba.jp +midori.chiba.jp +mihama.chiba.jp +minamiboso.chiba.jp +mobara.chiba.jp +mutsuzawa.chiba.jp +nagara.chiba.jp +nagareyama.chiba.jp +narashino.chiba.jp +narita.chiba.jp +noda.chiba.jp +oamishirasato.chiba.jp +omigawa.chiba.jp +onjuku.chiba.jp +otaki.chiba.jp +sakae.chiba.jp +sakura.chiba.jp +shimofusa.chiba.jp +shirako.chiba.jp +shiroi.chiba.jp +shisui.chiba.jp +sodegaura.chiba.jp +sosa.chiba.jp +tako.chiba.jp +tateyama.chiba.jp +togane.chiba.jp +tohnosho.chiba.jp +tomisato.chiba.jp +urayasu.chiba.jp +yachimata.chiba.jp +yachiyo.chiba.jp +yokaichiba.chiba.jp +yokoshibahikari.chiba.jp +yotsukaido.chiba.jp +ainan.ehime.jp +honai.ehime.jp +ikata.ehime.jp +imabari.ehime.jp +iyo.ehime.jp +kamijima.ehime.jp +kihoku.ehime.jp +kumakogen.ehime.jp +masaki.ehime.jp +matsuno.ehime.jp +matsuyama.ehime.jp +namikata.ehime.jp +niihama.ehime.jp +ozu.ehime.jp +saijo.ehime.jp +seiyo.ehime.jp +shikokuchuo.ehime.jp +tobe.ehime.jp +toon.ehime.jp +uchiko.ehime.jp +uwajima.ehime.jp +yawatahama.ehime.jp +echizen.fukui.jp +eiheiji.fukui.jp +fukui.fukui.jp +ikeda.fukui.jp +katsuyama.fukui.jp +mihama.fukui.jp +minamiechizen.fukui.jp +obama.fukui.jp +ohi.fukui.jp +ono.fukui.jp +sabae.fukui.jp +sakai.fukui.jp +takahama.fukui.jp +tsuruga.fukui.jp +wakasa.fukui.jp +ashiya.fukuoka.jp +buzen.fukuoka.jp +chikugo.fukuoka.jp +chikuho.fukuoka.jp +chikujo.fukuoka.jp +chikushino.fukuoka.jp +chikuzen.fukuoka.jp +chuo.fukuoka.jp +dazaifu.fukuoka.jp +fukuchi.fukuoka.jp +hakata.fukuoka.jp +higashi.fukuoka.jp +hirokawa.fukuoka.jp +hisayama.fukuoka.jp +iizuka.fukuoka.jp +inatsuki.fukuoka.jp +kaho.fukuoka.jp +kasuga.fukuoka.jp +kasuya.fukuoka.jp +kawara.fukuoka.jp +keisen.fukuoka.jp +koga.fukuoka.jp +kurate.fukuoka.jp +kurogi.fukuoka.jp +kurume.fukuoka.jp +minami.fukuoka.jp +miyako.fukuoka.jp +miyama.fukuoka.jp +miyawaka.fukuoka.jp +mizumaki.fukuoka.jp +munakata.fukuoka.jp +nakagawa.fukuoka.jp +nakama.fukuoka.jp +nishi.fukuoka.jp +nogata.fukuoka.jp +ogori.fukuoka.jp +okagaki.fukuoka.jp +okawa.fukuoka.jp +oki.fukuoka.jp +omuta.fukuoka.jp +onga.fukuoka.jp +onojo.fukuoka.jp +oto.fukuoka.jp +saigawa.fukuoka.jp +sasaguri.fukuoka.jp +shingu.fukuoka.jp +shinyoshitomi.fukuoka.jp +shonai.fukuoka.jp +soeda.fukuoka.jp +sue.fukuoka.jp +tachiarai.fukuoka.jp +tagawa.fukuoka.jp +takata.fukuoka.jp +toho.fukuoka.jp +toyotsu.fukuoka.jp +tsuiki.fukuoka.jp +ukiha.fukuoka.jp +umi.fukuoka.jp +usui.fukuoka.jp +yamada.fukuoka.jp +yame.fukuoka.jp +yanagawa.fukuoka.jp +yukuhashi.fukuoka.jp +aizubange.fukushima.jp +aizumisato.fukushima.jp +aizuwakamatsu.fukushima.jp +asakawa.fukushima.jp +bandai.fukushima.jp +date.fukushima.jp +fukushima.fukushima.jp +furudono.fukushima.jp +futaba.fukushima.jp +hanawa.fukushima.jp +higashi.fukushima.jp +hirata.fukushima.jp +hirono.fukushima.jp +iitate.fukushima.jp +inawashiro.fukushima.jp +ishikawa.fukushima.jp +iwaki.fukushima.jp +izumizaki.fukushima.jp +kagamiishi.fukushima.jp +kaneyama.fukushima.jp +kawamata.fukushima.jp +kitakata.fukushima.jp +kitashiobara.fukushima.jp +koori.fukushima.jp +koriyama.fukushima.jp +kunimi.fukushima.jp +miharu.fukushima.jp +mishima.fukushima.jp +namie.fukushima.jp +nango.fukushima.jp +nishiaizu.fukushima.jp +nishigo.fukushima.jp +okuma.fukushima.jp +omotego.fukushima.jp +ono.fukushima.jp +otama.fukushima.jp +samegawa.fukushima.jp +shimogo.fukushima.jp +shirakawa.fukushima.jp +showa.fukushima.jp +soma.fukushima.jp +sukagawa.fukushima.jp +taishin.fukushima.jp +tamakawa.fukushima.jp +tanagura.fukushima.jp +tenei.fukushima.jp +yabuki.fukushima.jp +yamato.fukushima.jp +yamatsuri.fukushima.jp +yanaizu.fukushima.jp +yugawa.fukushima.jp +anpachi.gifu.jp +ena.gifu.jp +gifu.gifu.jp +ginan.gifu.jp +godo.gifu.jp +gujo.gifu.jp +hashima.gifu.jp +hichiso.gifu.jp +hida.gifu.jp +higashishirakawa.gifu.jp +ibigawa.gifu.jp +ikeda.gifu.jp +kakamigahara.gifu.jp +kani.gifu.jp +kasahara.gifu.jp +kasamatsu.gifu.jp +kawaue.gifu.jp +kitagata.gifu.jp +mino.gifu.jp +minokamo.gifu.jp +mitake.gifu.jp +mizunami.gifu.jp +motosu.gifu.jp +nakatsugawa.gifu.jp +ogaki.gifu.jp +sakahogi.gifu.jp +seki.gifu.jp +sekigahara.gifu.jp +shirakawa.gifu.jp +tajimi.gifu.jp +takayama.gifu.jp +tarui.gifu.jp +toki.gifu.jp +tomika.gifu.jp +wanouchi.gifu.jp +yamagata.gifu.jp +yaotsu.gifu.jp +yoro.gifu.jp +annaka.gunma.jp +chiyoda.gunma.jp +fujioka.gunma.jp +higashiagatsuma.gunma.jp +isesaki.gunma.jp +itakura.gunma.jp +kanna.gunma.jp +kanra.gunma.jp +katashina.gunma.jp +kawaba.gunma.jp +kiryu.gunma.jp +kusatsu.gunma.jp +maebashi.gunma.jp +meiwa.gunma.jp +midori.gunma.jp +minakami.gunma.jp +naganohara.gunma.jp +nakanojo.gunma.jp +nanmoku.gunma.jp +numata.gunma.jp +oizumi.gunma.jp +ora.gunma.jp +ota.gunma.jp +shibukawa.gunma.jp +shimonita.gunma.jp +shinto.gunma.jp +showa.gunma.jp +takasaki.gunma.jp +takayama.gunma.jp +tamamura.gunma.jp +tatebayashi.gunma.jp +tomioka.gunma.jp +tsukiyono.gunma.jp +tsumagoi.gunma.jp +ueno.gunma.jp +yoshioka.gunma.jp +asaminami.hiroshima.jp +daiwa.hiroshima.jp +etajima.hiroshima.jp +fuchu.hiroshima.jp +fukuyama.hiroshima.jp +hatsukaichi.hiroshima.jp +higashihiroshima.hiroshima.jp +hongo.hiroshima.jp +jinsekikogen.hiroshima.jp +kaita.hiroshima.jp +kui.hiroshima.jp +kumano.hiroshima.jp +kure.hiroshima.jp +mihara.hiroshima.jp +miyoshi.hiroshima.jp +naka.hiroshima.jp +onomichi.hiroshima.jp +osakikamijima.hiroshima.jp +otake.hiroshima.jp +saka.hiroshima.jp +sera.hiroshima.jp +seranishi.hiroshima.jp +shinichi.hiroshima.jp +shobara.hiroshima.jp +takehara.hiroshima.jp +abashiri.hokkaido.jp +abira.hokkaido.jp +aibetsu.hokkaido.jp +akabira.hokkaido.jp +akkeshi.hokkaido.jp +asahikawa.hokkaido.jp +ashibetsu.hokkaido.jp +ashoro.hokkaido.jp +assabu.hokkaido.jp +atsuma.hokkaido.jp +bibai.hokkaido.jp +biei.hokkaido.jp +bifuka.hokkaido.jp +bihoro.hokkaido.jp +biratori.hokkaido.jp +chippubetsu.hokkaido.jp +chitose.hokkaido.jp +date.hokkaido.jp +ebetsu.hokkaido.jp +embetsu.hokkaido.jp +eniwa.hokkaido.jp +erimo.hokkaido.jp +esan.hokkaido.jp +esashi.hokkaido.jp +fukagawa.hokkaido.jp +fukushima.hokkaido.jp +furano.hokkaido.jp +furubira.hokkaido.jp +haboro.hokkaido.jp +hakodate.hokkaido.jp +hamatonbetsu.hokkaido.jp +hidaka.hokkaido.jp +higashikagura.hokkaido.jp +higashikawa.hokkaido.jp +hiroo.hokkaido.jp +hokuryu.hokkaido.jp +hokuto.hokkaido.jp +honbetsu.hokkaido.jp +horokanai.hokkaido.jp +horonobe.hokkaido.jp +ikeda.hokkaido.jp +imakane.hokkaido.jp +ishikari.hokkaido.jp +iwamizawa.hokkaido.jp +iwanai.hokkaido.jp +kamifurano.hokkaido.jp +kamikawa.hokkaido.jp +kamishihoro.hokkaido.jp +kamisunagawa.hokkaido.jp +kamoenai.hokkaido.jp +kayabe.hokkaido.jp +kembuchi.hokkaido.jp +kikonai.hokkaido.jp +kimobetsu.hokkaido.jp +kitahiroshima.hokkaido.jp +kitami.hokkaido.jp +kiyosato.hokkaido.jp +koshimizu.hokkaido.jp +kunneppu.hokkaido.jp +kuriyama.hokkaido.jp +kuromatsunai.hokkaido.jp +kushiro.hokkaido.jp +kutchan.hokkaido.jp +kyowa.hokkaido.jp +mashike.hokkaido.jp +matsumae.hokkaido.jp +mikasa.hokkaido.jp +minamifurano.hokkaido.jp +mombetsu.hokkaido.jp +moseushi.hokkaido.jp +mukawa.hokkaido.jp +muroran.hokkaido.jp +naie.hokkaido.jp +nakagawa.hokkaido.jp +nakasatsunai.hokkaido.jp +nakatombetsu.hokkaido.jp +nanae.hokkaido.jp +nanporo.hokkaido.jp +nayoro.hokkaido.jp +nemuro.hokkaido.jp +niikappu.hokkaido.jp +niki.hokkaido.jp +nishiokoppe.hokkaido.jp +noboribetsu.hokkaido.jp +numata.hokkaido.jp +obihiro.hokkaido.jp +obira.hokkaido.jp +oketo.hokkaido.jp +okoppe.hokkaido.jp +otaru.hokkaido.jp +otobe.hokkaido.jp +otofuke.hokkaido.jp +otoineppu.hokkaido.jp +oumu.hokkaido.jp +ozora.hokkaido.jp +pippu.hokkaido.jp +rankoshi.hokkaido.jp +rebun.hokkaido.jp +rikubetsu.hokkaido.jp +rishiri.hokkaido.jp +rishirifuji.hokkaido.jp +saroma.hokkaido.jp +sarufutsu.hokkaido.jp +shakotan.hokkaido.jp +shari.hokkaido.jp +shibecha.hokkaido.jp +shibetsu.hokkaido.jp +shikabe.hokkaido.jp +shikaoi.hokkaido.jp +shimamaki.hokkaido.jp +shimizu.hokkaido.jp +shimokawa.hokkaido.jp +shinshinotsu.hokkaido.jp +shintoku.hokkaido.jp +shiranuka.hokkaido.jp +shiraoi.hokkaido.jp +shiriuchi.hokkaido.jp +sobetsu.hokkaido.jp +sunagawa.hokkaido.jp +taiki.hokkaido.jp +takasu.hokkaido.jp +takikawa.hokkaido.jp +takinoue.hokkaido.jp +teshikaga.hokkaido.jp +tobetsu.hokkaido.jp +tohma.hokkaido.jp +tomakomai.hokkaido.jp +tomari.hokkaido.jp +toya.hokkaido.jp +toyako.hokkaido.jp +toyotomi.hokkaido.jp +toyoura.hokkaido.jp +tsubetsu.hokkaido.jp +tsukigata.hokkaido.jp +urakawa.hokkaido.jp +urausu.hokkaido.jp +uryu.hokkaido.jp +utashinai.hokkaido.jp +wakkanai.hokkaido.jp +wassamu.hokkaido.jp +yakumo.hokkaido.jp +yoichi.hokkaido.jp +aioi.hyogo.jp +akashi.hyogo.jp +ako.hyogo.jp +amagasaki.hyogo.jp +aogaki.hyogo.jp +asago.hyogo.jp +ashiya.hyogo.jp +awaji.hyogo.jp +fukusaki.hyogo.jp +goshiki.hyogo.jp +harima.hyogo.jp +himeji.hyogo.jp +ichikawa.hyogo.jp +inagawa.hyogo.jp +itami.hyogo.jp +kakogawa.hyogo.jp +kamigori.hyogo.jp +kamikawa.hyogo.jp +kasai.hyogo.jp +kasuga.hyogo.jp +kawanishi.hyogo.jp +miki.hyogo.jp +minamiawaji.hyogo.jp +nishinomiya.hyogo.jp +nishiwaki.hyogo.jp +ono.hyogo.jp +sanda.hyogo.jp +sannan.hyogo.jp +sasayama.hyogo.jp +sayo.hyogo.jp +shingu.hyogo.jp +shinonsen.hyogo.jp +shiso.hyogo.jp +sumoto.hyogo.jp +taishi.hyogo.jp +taka.hyogo.jp +takarazuka.hyogo.jp +takasago.hyogo.jp +takino.hyogo.jp +tamba.hyogo.jp +tatsuno.hyogo.jp +toyooka.hyogo.jp +yabu.hyogo.jp +yashiro.hyogo.jp +yoka.hyogo.jp +yokawa.hyogo.jp +ami.ibaraki.jp +asahi.ibaraki.jp +bando.ibaraki.jp +chikusei.ibaraki.jp +daigo.ibaraki.jp +fujishiro.ibaraki.jp +hitachi.ibaraki.jp +hitachinaka.ibaraki.jp +hitachiomiya.ibaraki.jp +hitachiota.ibaraki.jp +ibaraki.ibaraki.jp +ina.ibaraki.jp +inashiki.ibaraki.jp +itako.ibaraki.jp +iwama.ibaraki.jp +joso.ibaraki.jp +kamisu.ibaraki.jp +kasama.ibaraki.jp +kashima.ibaraki.jp +kasumigaura.ibaraki.jp +koga.ibaraki.jp +miho.ibaraki.jp +mito.ibaraki.jp +moriya.ibaraki.jp +naka.ibaraki.jp +namegata.ibaraki.jp +oarai.ibaraki.jp +ogawa.ibaraki.jp +omitama.ibaraki.jp +ryugasaki.ibaraki.jp +sakai.ibaraki.jp +sakuragawa.ibaraki.jp +shimodate.ibaraki.jp +shimotsuma.ibaraki.jp +shirosato.ibaraki.jp +sowa.ibaraki.jp +suifu.ibaraki.jp +takahagi.ibaraki.jp +tamatsukuri.ibaraki.jp +tokai.ibaraki.jp +tomobe.ibaraki.jp +tone.ibaraki.jp +toride.ibaraki.jp +tsuchiura.ibaraki.jp +tsukuba.ibaraki.jp +uchihara.ibaraki.jp +ushiku.ibaraki.jp +yachiyo.ibaraki.jp +yamagata.ibaraki.jp +yawara.ibaraki.jp +yuki.ibaraki.jp +anamizu.ishikawa.jp +hakui.ishikawa.jp +hakusan.ishikawa.jp +kaga.ishikawa.jp +kahoku.ishikawa.jp +kanazawa.ishikawa.jp +kawakita.ishikawa.jp +komatsu.ishikawa.jp +nakanoto.ishikawa.jp +nanao.ishikawa.jp +nomi.ishikawa.jp +nonoichi.ishikawa.jp +noto.ishikawa.jp +shika.ishikawa.jp +suzu.ishikawa.jp +tsubata.ishikawa.jp +tsurugi.ishikawa.jp +uchinada.ishikawa.jp +wajima.ishikawa.jp +fudai.iwate.jp +fujisawa.iwate.jp +hanamaki.iwate.jp +hiraizumi.iwate.jp +hirono.iwate.jp +ichinohe.iwate.jp +ichinoseki.iwate.jp +iwaizumi.iwate.jp +iwate.iwate.jp +joboji.iwate.jp +kamaishi.iwate.jp +kanegasaki.iwate.jp +karumai.iwate.jp +kawai.iwate.jp +kitakami.iwate.jp +kuji.iwate.jp +kunohe.iwate.jp +kuzumaki.iwate.jp +miyako.iwate.jp +mizusawa.iwate.jp +morioka.iwate.jp +ninohe.iwate.jp +noda.iwate.jp +ofunato.iwate.jp +oshu.iwate.jp +otsuchi.iwate.jp +rikuzentakata.iwate.jp +shiwa.iwate.jp +shizukuishi.iwate.jp +sumita.iwate.jp +tanohata.iwate.jp +tono.iwate.jp +yahaba.iwate.jp +yamada.iwate.jp +ayagawa.kagawa.jp +higashikagawa.kagawa.jp +kanonji.kagawa.jp +kotohira.kagawa.jp +manno.kagawa.jp +marugame.kagawa.jp +mitoyo.kagawa.jp +naoshima.kagawa.jp +sanuki.kagawa.jp +tadotsu.kagawa.jp +takamatsu.kagawa.jp +tonosho.kagawa.jp +uchinomi.kagawa.jp +utazu.kagawa.jp +zentsuji.kagawa.jp +akune.kagoshima.jp +amami.kagoshima.jp +hioki.kagoshima.jp +isa.kagoshima.jp +isen.kagoshima.jp +izumi.kagoshima.jp +kagoshima.kagoshima.jp +kanoya.kagoshima.jp +kawanabe.kagoshima.jp +kinko.kagoshima.jp +kouyama.kagoshima.jp +makurazaki.kagoshima.jp +matsumoto.kagoshima.jp +minamitane.kagoshima.jp +nakatane.kagoshima.jp +nishinoomote.kagoshima.jp +satsumasendai.kagoshima.jp +soo.kagoshima.jp +tarumizu.kagoshima.jp +yusui.kagoshima.jp +aikawa.kanagawa.jp +atsugi.kanagawa.jp +ayase.kanagawa.jp +chigasaki.kanagawa.jp +ebina.kanagawa.jp +fujisawa.kanagawa.jp +hadano.kanagawa.jp +hakone.kanagawa.jp +hiratsuka.kanagawa.jp +isehara.kanagawa.jp +kaisei.kanagawa.jp +kamakura.kanagawa.jp +kiyokawa.kanagawa.jp +matsuda.kanagawa.jp +minamiashigara.kanagawa.jp +miura.kanagawa.jp +nakai.kanagawa.jp +ninomiya.kanagawa.jp +odawara.kanagawa.jp +oi.kanagawa.jp +oiso.kanagawa.jp +sagamihara.kanagawa.jp +samukawa.kanagawa.jp +tsukui.kanagawa.jp +yamakita.kanagawa.jp +yamato.kanagawa.jp +yokosuka.kanagawa.jp +yugawara.kanagawa.jp +zama.kanagawa.jp +zushi.kanagawa.jp +aki.kochi.jp +geisei.kochi.jp +hidaka.kochi.jp +higashitsuno.kochi.jp +ino.kochi.jp +kagami.kochi.jp +kami.kochi.jp +kitagawa.kochi.jp +kochi.kochi.jp +mihara.kochi.jp +motoyama.kochi.jp +muroto.kochi.jp +nahari.kochi.jp +nakamura.kochi.jp +nankoku.kochi.jp +nishitosa.kochi.jp +niyodogawa.kochi.jp +ochi.kochi.jp +okawa.kochi.jp +otoyo.kochi.jp +otsuki.kochi.jp +sakawa.kochi.jp +sukumo.kochi.jp +susaki.kochi.jp +tosa.kochi.jp +tosashimizu.kochi.jp +toyo.kochi.jp +tsuno.kochi.jp +umaji.kochi.jp +yasuda.kochi.jp +yusuhara.kochi.jp +amakusa.kumamoto.jp +arao.kumamoto.jp +aso.kumamoto.jp +choyo.kumamoto.jp +gyokuto.kumamoto.jp +kamiamakusa.kumamoto.jp +kikuchi.kumamoto.jp +kumamoto.kumamoto.jp +mashiki.kumamoto.jp +mifune.kumamoto.jp +minamata.kumamoto.jp +minamioguni.kumamoto.jp +nagasu.kumamoto.jp +nishihara.kumamoto.jp +oguni.kumamoto.jp +ozu.kumamoto.jp +sumoto.kumamoto.jp +takamori.kumamoto.jp +uki.kumamoto.jp +uto.kumamoto.jp +yamaga.kumamoto.jp +yamato.kumamoto.jp +yatsushiro.kumamoto.jp +ayabe.kyoto.jp +fukuchiyama.kyoto.jp +higashiyama.kyoto.jp +ide.kyoto.jp +ine.kyoto.jp +joyo.kyoto.jp +kameoka.kyoto.jp +kamo.kyoto.jp +kita.kyoto.jp +kizu.kyoto.jp +kumiyama.kyoto.jp +kyotamba.kyoto.jp +kyotanabe.kyoto.jp +kyotango.kyoto.jp +maizuru.kyoto.jp +minami.kyoto.jp +minamiyamashiro.kyoto.jp +miyazu.kyoto.jp +muko.kyoto.jp +nagaokakyo.kyoto.jp +nakagyo.kyoto.jp +nantan.kyoto.jp +oyamazaki.kyoto.jp +sakyo.kyoto.jp +seika.kyoto.jp +tanabe.kyoto.jp +uji.kyoto.jp +ujitawara.kyoto.jp +wazuka.kyoto.jp +yamashina.kyoto.jp +yawata.kyoto.jp +asahi.mie.jp +inabe.mie.jp +ise.mie.jp +kameyama.mie.jp +kawagoe.mie.jp +kiho.mie.jp +kisosaki.mie.jp +kiwa.mie.jp +komono.mie.jp +kumano.mie.jp +kuwana.mie.jp +matsusaka.mie.jp +meiwa.mie.jp +mihama.mie.jp +minamiise.mie.jp +misugi.mie.jp +miyama.mie.jp +nabari.mie.jp +shima.mie.jp +suzuka.mie.jp +tado.mie.jp +taiki.mie.jp +taki.mie.jp +tamaki.mie.jp +toba.mie.jp +tsu.mie.jp +udono.mie.jp +ureshino.mie.jp +watarai.mie.jp +yokkaichi.mie.jp +furukawa.miyagi.jp +higashimatsushima.miyagi.jp +ishinomaki.miyagi.jp +iwanuma.miyagi.jp +kakuda.miyagi.jp +kami.miyagi.jp +kawasaki.miyagi.jp +marumori.miyagi.jp +matsushima.miyagi.jp +minamisanriku.miyagi.jp +misato.miyagi.jp +murata.miyagi.jp +natori.miyagi.jp +ogawara.miyagi.jp +ohira.miyagi.jp +onagawa.miyagi.jp +osaki.miyagi.jp +rifu.miyagi.jp +semine.miyagi.jp +shibata.miyagi.jp +shichikashuku.miyagi.jp +shikama.miyagi.jp +shiogama.miyagi.jp +shiroishi.miyagi.jp +tagajo.miyagi.jp +taiwa.miyagi.jp +tome.miyagi.jp +tomiya.miyagi.jp +wakuya.miyagi.jp +watari.miyagi.jp +yamamoto.miyagi.jp +zao.miyagi.jp +aya.miyazaki.jp +ebino.miyazaki.jp +gokase.miyazaki.jp +hyuga.miyazaki.jp +kadogawa.miyazaki.jp +kawaminami.miyazaki.jp +kijo.miyazaki.jp +kitagawa.miyazaki.jp +kitakata.miyazaki.jp +kitaura.miyazaki.jp +kobayashi.miyazaki.jp +kunitomi.miyazaki.jp +kushima.miyazaki.jp +mimata.miyazaki.jp +miyakonojo.miyazaki.jp +miyazaki.miyazaki.jp +morotsuka.miyazaki.jp +nichinan.miyazaki.jp +nishimera.miyazaki.jp +nobeoka.miyazaki.jp +saito.miyazaki.jp +shiiba.miyazaki.jp +shintomi.miyazaki.jp +takaharu.miyazaki.jp +takanabe.miyazaki.jp +takazaki.miyazaki.jp +tsuno.miyazaki.jp +achi.nagano.jp +agematsu.nagano.jp +anan.nagano.jp +aoki.nagano.jp +asahi.nagano.jp +azumino.nagano.jp +chikuhoku.nagano.jp +chikuma.nagano.jp +chino.nagano.jp +fujimi.nagano.jp +hakuba.nagano.jp +hara.nagano.jp +hiraya.nagano.jp +iida.nagano.jp +iijima.nagano.jp +iiyama.nagano.jp +iizuna.nagano.jp +ikeda.nagano.jp +ikusaka.nagano.jp +ina.nagano.jp +karuizawa.nagano.jp +kawakami.nagano.jp +kiso.nagano.jp +kisofukushima.nagano.jp +kitaaiki.nagano.jp +komagane.nagano.jp +komoro.nagano.jp +matsukawa.nagano.jp +matsumoto.nagano.jp +miasa.nagano.jp +minamiaiki.nagano.jp +minamimaki.nagano.jp +minamiminowa.nagano.jp +minowa.nagano.jp +miyada.nagano.jp +miyota.nagano.jp +mochizuki.nagano.jp +nagano.nagano.jp +nagawa.nagano.jp +nagiso.nagano.jp +nakagawa.nagano.jp +nakano.nagano.jp +nozawaonsen.nagano.jp +obuse.nagano.jp +ogawa.nagano.jp +okaya.nagano.jp +omachi.nagano.jp +omi.nagano.jp +ookuwa.nagano.jp +ooshika.nagano.jp +otaki.nagano.jp +otari.nagano.jp +sakae.nagano.jp +sakaki.nagano.jp +saku.nagano.jp +sakuho.nagano.jp +shimosuwa.nagano.jp +shinanomachi.nagano.jp +shiojiri.nagano.jp +suwa.nagano.jp +suzaka.nagano.jp +takagi.nagano.jp +takamori.nagano.jp +takayama.nagano.jp +tateshina.nagano.jp +tatsuno.nagano.jp +togakushi.nagano.jp +togura.nagano.jp +tomi.nagano.jp +ueda.nagano.jp +wada.nagano.jp +yamagata.nagano.jp +yamanouchi.nagano.jp +yasaka.nagano.jp +yasuoka.nagano.jp +chijiwa.nagasaki.jp +futsu.nagasaki.jp +goto.nagasaki.jp +hasami.nagasaki.jp +hirado.nagasaki.jp +iki.nagasaki.jp +isahaya.nagasaki.jp +kawatana.nagasaki.jp +kuchinotsu.nagasaki.jp +matsuura.nagasaki.jp +nagasaki.nagasaki.jp +obama.nagasaki.jp +omura.nagasaki.jp +oseto.nagasaki.jp +saikai.nagasaki.jp +sasebo.nagasaki.jp +seihi.nagasaki.jp +shimabara.nagasaki.jp +shinkamigoto.nagasaki.jp +togitsu.nagasaki.jp +tsushima.nagasaki.jp +unzen.nagasaki.jp +ando.nara.jp +gose.nara.jp +heguri.nara.jp +higashiyoshino.nara.jp +ikaruga.nara.jp +ikoma.nara.jp +kamikitayama.nara.jp +kanmaki.nara.jp +kashiba.nara.jp +kashihara.nara.jp +katsuragi.nara.jp +kawai.nara.jp +kawakami.nara.jp +kawanishi.nara.jp +koryo.nara.jp +kurotaki.nara.jp +mitsue.nara.jp +miyake.nara.jp +nara.nara.jp +nosegawa.nara.jp +oji.nara.jp +ouda.nara.jp +oyodo.nara.jp +sakurai.nara.jp +sango.nara.jp +shimoichi.nara.jp +shimokitayama.nara.jp +shinjo.nara.jp +soni.nara.jp +takatori.nara.jp +tawaramoto.nara.jp +tenkawa.nara.jp +tenri.nara.jp +uda.nara.jp +yamatokoriyama.nara.jp +yamatotakada.nara.jp +yamazoe.nara.jp +yoshino.nara.jp +aga.niigata.jp +agano.niigata.jp +gosen.niigata.jp +itoigawa.niigata.jp +izumozaki.niigata.jp +joetsu.niigata.jp +kamo.niigata.jp +kariwa.niigata.jp +kashiwazaki.niigata.jp +minamiuonuma.niigata.jp +mitsuke.niigata.jp +muika.niigata.jp +murakami.niigata.jp +myoko.niigata.jp +nagaoka.niigata.jp +niigata.niigata.jp +ojiya.niigata.jp +omi.niigata.jp +sado.niigata.jp +sanjo.niigata.jp +seiro.niigata.jp +seirou.niigata.jp +sekikawa.niigata.jp +shibata.niigata.jp +tagami.niigata.jp +tainai.niigata.jp +tochio.niigata.jp +tokamachi.niigata.jp +tsubame.niigata.jp +tsunan.niigata.jp +uonuma.niigata.jp +yahiko.niigata.jp +yoita.niigata.jp +yuzawa.niigata.jp +beppu.oita.jp +bungoono.oita.jp +bungotakada.oita.jp +hasama.oita.jp +hiji.oita.jp +himeshima.oita.jp +hita.oita.jp +kamitsue.oita.jp +kokonoe.oita.jp +kuju.oita.jp +kunisaki.oita.jp +kusu.oita.jp +oita.oita.jp +saiki.oita.jp +taketa.oita.jp +tsukumi.oita.jp +usa.oita.jp +usuki.oita.jp +yufu.oita.jp +akaiwa.okayama.jp +asakuchi.okayama.jp +bizen.okayama.jp +hayashima.okayama.jp +ibara.okayama.jp +kagamino.okayama.jp +kasaoka.okayama.jp +kibichuo.okayama.jp +kumenan.okayama.jp +kurashiki.okayama.jp +maniwa.okayama.jp +misaki.okayama.jp +nagi.okayama.jp +niimi.okayama.jp +nishiawakura.okayama.jp +okayama.okayama.jp +satosho.okayama.jp +setouchi.okayama.jp +shinjo.okayama.jp +shoo.okayama.jp +soja.okayama.jp +takahashi.okayama.jp +tamano.okayama.jp +tsuyama.okayama.jp +wake.okayama.jp +yakage.okayama.jp +aguni.okinawa.jp +ginowan.okinawa.jp +ginoza.okinawa.jp +gushikami.okinawa.jp +haebaru.okinawa.jp +higashi.okinawa.jp +hirara.okinawa.jp +iheya.okinawa.jp +ishigaki.okinawa.jp +ishikawa.okinawa.jp +itoman.okinawa.jp +izena.okinawa.jp +kadena.okinawa.jp +kin.okinawa.jp +kitadaito.okinawa.jp +kitanakagusuku.okinawa.jp +kumejima.okinawa.jp +kunigami.okinawa.jp +minamidaito.okinawa.jp +motobu.okinawa.jp +nago.okinawa.jp +naha.okinawa.jp +nakagusuku.okinawa.jp +nakijin.okinawa.jp +nanjo.okinawa.jp +nishihara.okinawa.jp +ogimi.okinawa.jp +okinawa.okinawa.jp +onna.okinawa.jp +shimoji.okinawa.jp +taketomi.okinawa.jp +tarama.okinawa.jp +tokashiki.okinawa.jp +tomigusuku.okinawa.jp +tonaki.okinawa.jp +urasoe.okinawa.jp +uruma.okinawa.jp +yaese.okinawa.jp +yomitan.okinawa.jp +yonabaru.okinawa.jp +yonaguni.okinawa.jp +zamami.okinawa.jp +abeno.osaka.jp +chihayaakasaka.osaka.jp +chuo.osaka.jp +daito.osaka.jp +fujiidera.osaka.jp +habikino.osaka.jp +hannan.osaka.jp +higashiosaka.osaka.jp +higashisumiyoshi.osaka.jp +higashiyodogawa.osaka.jp +hirakata.osaka.jp +ibaraki.osaka.jp +ikeda.osaka.jp +izumi.osaka.jp +izumiotsu.osaka.jp +izumisano.osaka.jp +kadoma.osaka.jp +kaizuka.osaka.jp +kanan.osaka.jp +kashiwara.osaka.jp +katano.osaka.jp +kawachinagano.osaka.jp +kishiwada.osaka.jp +kita.osaka.jp +kumatori.osaka.jp +matsubara.osaka.jp +minato.osaka.jp +minoh.osaka.jp +misaki.osaka.jp +moriguchi.osaka.jp +neyagawa.osaka.jp +nishi.osaka.jp +nose.osaka.jp +osakasayama.osaka.jp +sakai.osaka.jp +sayama.osaka.jp +sennan.osaka.jp +settsu.osaka.jp +shijonawate.osaka.jp +shimamoto.osaka.jp +suita.osaka.jp +tadaoka.osaka.jp +taishi.osaka.jp +tajiri.osaka.jp +takaishi.osaka.jp +takatsuki.osaka.jp +tondabayashi.osaka.jp +toyonaka.osaka.jp +toyono.osaka.jp +yao.osaka.jp +ariake.saga.jp +arita.saga.jp +fukudomi.saga.jp +genkai.saga.jp +hamatama.saga.jp +hizen.saga.jp +imari.saga.jp +kamimine.saga.jp +kanzaki.saga.jp +karatsu.saga.jp +kashima.saga.jp +kitagata.saga.jp +kitahata.saga.jp +kiyama.saga.jp +kouhoku.saga.jp +kyuragi.saga.jp +nishiarita.saga.jp +ogi.saga.jp +omachi.saga.jp +ouchi.saga.jp +saga.saga.jp +shiroishi.saga.jp +taku.saga.jp +tara.saga.jp +tosu.saga.jp +yoshinogari.saga.jp +arakawa.saitama.jp +asaka.saitama.jp +chichibu.saitama.jp +fujimi.saitama.jp +fujimino.saitama.jp +fukaya.saitama.jp +hanno.saitama.jp +hanyu.saitama.jp +hasuda.saitama.jp +hatogaya.saitama.jp +hatoyama.saitama.jp +hidaka.saitama.jp +higashichichibu.saitama.jp +higashimatsuyama.saitama.jp +honjo.saitama.jp +ina.saitama.jp +iruma.saitama.jp +iwatsuki.saitama.jp +kamiizumi.saitama.jp +kamikawa.saitama.jp +kamisato.saitama.jp +kasukabe.saitama.jp +kawagoe.saitama.jp +kawaguchi.saitama.jp +kawajima.saitama.jp +kazo.saitama.jp +kitamoto.saitama.jp +koshigaya.saitama.jp +kounosu.saitama.jp +kuki.saitama.jp +kumagaya.saitama.jp +matsubushi.saitama.jp +minano.saitama.jp +misato.saitama.jp +miyashiro.saitama.jp +miyoshi.saitama.jp +moroyama.saitama.jp +nagatoro.saitama.jp +namegawa.saitama.jp +niiza.saitama.jp +ogano.saitama.jp +ogawa.saitama.jp +ogose.saitama.jp +okegawa.saitama.jp +omiya.saitama.jp +otaki.saitama.jp +ranzan.saitama.jp +ryokami.saitama.jp +saitama.saitama.jp +sakado.saitama.jp +satte.saitama.jp +sayama.saitama.jp +shiki.saitama.jp +shiraoka.saitama.jp +soka.saitama.jp +sugito.saitama.jp +toda.saitama.jp +tokigawa.saitama.jp +tokorozawa.saitama.jp +tsurugashima.saitama.jp +urawa.saitama.jp +warabi.saitama.jp +yashio.saitama.jp +yokoze.saitama.jp +yono.saitama.jp +yorii.saitama.jp +yoshida.saitama.jp +yoshikawa.saitama.jp +yoshimi.saitama.jp +aisho.shiga.jp +gamo.shiga.jp +higashiomi.shiga.jp +hikone.shiga.jp +koka.shiga.jp +konan.shiga.jp +kosei.shiga.jp +koto.shiga.jp +kusatsu.shiga.jp +maibara.shiga.jp +moriyama.shiga.jp +nagahama.shiga.jp +nishiazai.shiga.jp +notogawa.shiga.jp +omihachiman.shiga.jp +otsu.shiga.jp +ritto.shiga.jp +ryuoh.shiga.jp +takashima.shiga.jp +takatsuki.shiga.jp +torahime.shiga.jp +toyosato.shiga.jp +yasu.shiga.jp +akagi.shimane.jp +ama.shimane.jp +gotsu.shimane.jp +hamada.shimane.jp +higashiizumo.shimane.jp +hikawa.shimane.jp +hikimi.shimane.jp +izumo.shimane.jp +kakinoki.shimane.jp +masuda.shimane.jp +matsue.shimane.jp +misato.shimane.jp +nishinoshima.shimane.jp +ohda.shimane.jp +okinoshima.shimane.jp +okuizumo.shimane.jp +shimane.shimane.jp +tamayu.shimane.jp +tsuwano.shimane.jp +unnan.shimane.jp +yakumo.shimane.jp +yasugi.shimane.jp +yatsuka.shimane.jp +arai.shizuoka.jp +atami.shizuoka.jp +fuji.shizuoka.jp +fujieda.shizuoka.jp +fujikawa.shizuoka.jp +fujinomiya.shizuoka.jp +fukuroi.shizuoka.jp +gotemba.shizuoka.jp +haibara.shizuoka.jp +hamamatsu.shizuoka.jp +higashiizu.shizuoka.jp +ito.shizuoka.jp +iwata.shizuoka.jp +izu.shizuoka.jp +izunokuni.shizuoka.jp +kakegawa.shizuoka.jp +kannami.shizuoka.jp +kawanehon.shizuoka.jp +kawazu.shizuoka.jp +kikugawa.shizuoka.jp +kosai.shizuoka.jp +makinohara.shizuoka.jp +matsuzaki.shizuoka.jp +minamiizu.shizuoka.jp +mishima.shizuoka.jp +morimachi.shizuoka.jp +nishiizu.shizuoka.jp +numazu.shizuoka.jp +omaezaki.shizuoka.jp +shimada.shizuoka.jp +shimizu.shizuoka.jp +shimoda.shizuoka.jp +shizuoka.shizuoka.jp +susono.shizuoka.jp +yaizu.shizuoka.jp +yoshida.shizuoka.jp +ashikaga.tochigi.jp +bato.tochigi.jp +haga.tochigi.jp +ichikai.tochigi.jp +iwafune.tochigi.jp +kaminokawa.tochigi.jp +kanuma.tochigi.jp +karasuyama.tochigi.jp +kuroiso.tochigi.jp +mashiko.tochigi.jp +mibu.tochigi.jp +moka.tochigi.jp +motegi.tochigi.jp +nasu.tochigi.jp +nasushiobara.tochigi.jp +nikko.tochigi.jp +nishikata.tochigi.jp +nogi.tochigi.jp +ohira.tochigi.jp +ohtawara.tochigi.jp +oyama.tochigi.jp +sakura.tochigi.jp +sano.tochigi.jp +shimotsuke.tochigi.jp +shioya.tochigi.jp +takanezawa.tochigi.jp +tochigi.tochigi.jp +tsuga.tochigi.jp +ujiie.tochigi.jp +utsunomiya.tochigi.jp +yaita.tochigi.jp +aizumi.tokushima.jp +anan.tokushima.jp +ichiba.tokushima.jp +itano.tokushima.jp +kainan.tokushima.jp +komatsushima.tokushima.jp +matsushige.tokushima.jp +mima.tokushima.jp +minami.tokushima.jp +miyoshi.tokushima.jp +mugi.tokushima.jp +nakagawa.tokushima.jp +naruto.tokushima.jp +sanagochi.tokushima.jp +shishikui.tokushima.jp +tokushima.tokushima.jp +wajiki.tokushima.jp +adachi.tokyo.jp +akiruno.tokyo.jp +akishima.tokyo.jp +aogashima.tokyo.jp +arakawa.tokyo.jp +bunkyo.tokyo.jp +chiyoda.tokyo.jp +chofu.tokyo.jp +chuo.tokyo.jp +edogawa.tokyo.jp +fuchu.tokyo.jp +fussa.tokyo.jp +hachijo.tokyo.jp +hachioji.tokyo.jp +hamura.tokyo.jp +higashikurume.tokyo.jp +higashimurayama.tokyo.jp +higashiyamato.tokyo.jp +hino.tokyo.jp +hinode.tokyo.jp +hinohara.tokyo.jp +inagi.tokyo.jp +itabashi.tokyo.jp +katsushika.tokyo.jp +kita.tokyo.jp +kiyose.tokyo.jp +kodaira.tokyo.jp +koganei.tokyo.jp +kokubunji.tokyo.jp +komae.tokyo.jp +koto.tokyo.jp +kouzushima.tokyo.jp +kunitachi.tokyo.jp +machida.tokyo.jp +meguro.tokyo.jp +minato.tokyo.jp +mitaka.tokyo.jp +mizuho.tokyo.jp +musashimurayama.tokyo.jp +musashino.tokyo.jp +nakano.tokyo.jp +nerima.tokyo.jp +ogasawara.tokyo.jp +okutama.tokyo.jp +ome.tokyo.jp +oshima.tokyo.jp +ota.tokyo.jp +setagaya.tokyo.jp +shibuya.tokyo.jp +shinagawa.tokyo.jp +shinjuku.tokyo.jp +suginami.tokyo.jp +sumida.tokyo.jp +tachikawa.tokyo.jp +taito.tokyo.jp +tama.tokyo.jp +toshima.tokyo.jp +chizu.tottori.jp +hino.tottori.jp +kawahara.tottori.jp +koge.tottori.jp +kotoura.tottori.jp +misasa.tottori.jp +nanbu.tottori.jp +nichinan.tottori.jp +sakaiminato.tottori.jp +tottori.tottori.jp +wakasa.tottori.jp +yazu.tottori.jp +yonago.tottori.jp +asahi.toyama.jp +fuchu.toyama.jp +fukumitsu.toyama.jp +funahashi.toyama.jp +himi.toyama.jp +imizu.toyama.jp +inami.toyama.jp +johana.toyama.jp +kamiichi.toyama.jp +kurobe.toyama.jp +nakaniikawa.toyama.jp +namerikawa.toyama.jp +nanto.toyama.jp +nyuzen.toyama.jp +oyabe.toyama.jp +taira.toyama.jp +takaoka.toyama.jp +tateyama.toyama.jp +toga.toyama.jp +tonami.toyama.jp +toyama.toyama.jp +unazuki.toyama.jp +uozu.toyama.jp +yamada.toyama.jp +arida.wakayama.jp +aridagawa.wakayama.jp +gobo.wakayama.jp +hashimoto.wakayama.jp +hidaka.wakayama.jp +hirogawa.wakayama.jp +inami.wakayama.jp +iwade.wakayama.jp +kainan.wakayama.jp +kamitonda.wakayama.jp +katsuragi.wakayama.jp +kimino.wakayama.jp +kinokawa.wakayama.jp +kitayama.wakayama.jp +koya.wakayama.jp +koza.wakayama.jp +kozagawa.wakayama.jp +kudoyama.wakayama.jp +kushimoto.wakayama.jp +mihama.wakayama.jp +misato.wakayama.jp +nachikatsuura.wakayama.jp +shingu.wakayama.jp +shirahama.wakayama.jp +taiji.wakayama.jp +tanabe.wakayama.jp +wakayama.wakayama.jp +yuasa.wakayama.jp +yura.wakayama.jp +asahi.yamagata.jp +funagata.yamagata.jp +higashine.yamagata.jp +iide.yamagata.jp +kahoku.yamagata.jp +kaminoyama.yamagata.jp +kaneyama.yamagata.jp +kawanishi.yamagata.jp +mamurogawa.yamagata.jp +mikawa.yamagata.jp +murayama.yamagata.jp +nagai.yamagata.jp +nakayama.yamagata.jp +nanyo.yamagata.jp +nishikawa.yamagata.jp +obanazawa.yamagata.jp +oe.yamagata.jp +oguni.yamagata.jp +ohkura.yamagata.jp +oishida.yamagata.jp +sagae.yamagata.jp +sakata.yamagata.jp +sakegawa.yamagata.jp +shinjo.yamagata.jp +shirataka.yamagata.jp +shonai.yamagata.jp +takahata.yamagata.jp +tendo.yamagata.jp +tozawa.yamagata.jp +tsuruoka.yamagata.jp +yamagata.yamagata.jp +yamanobe.yamagata.jp +yonezawa.yamagata.jp +yuza.yamagata.jp +abu.yamaguchi.jp +hagi.yamaguchi.jp +hikari.yamaguchi.jp +hofu.yamaguchi.jp +iwakuni.yamaguchi.jp +kudamatsu.yamaguchi.jp +mitou.yamaguchi.jp +nagato.yamaguchi.jp +oshima.yamaguchi.jp +shimonoseki.yamaguchi.jp +shunan.yamaguchi.jp +tabuse.yamaguchi.jp +tokuyama.yamaguchi.jp +toyota.yamaguchi.jp +ube.yamaguchi.jp +yuu.yamaguchi.jp +chuo.yamanashi.jp +doshi.yamanashi.jp +fuefuki.yamanashi.jp +fujikawa.yamanashi.jp +fujikawaguchiko.yamanashi.jp +fujiyoshida.yamanashi.jp +hayakawa.yamanashi.jp +hokuto.yamanashi.jp +ichikawamisato.yamanashi.jp +kai.yamanashi.jp +kofu.yamanashi.jp +koshu.yamanashi.jp +kosuge.yamanashi.jp +minami-alps.yamanashi.jp +minobu.yamanashi.jp +nakamichi.yamanashi.jp +nanbu.yamanashi.jp +narusawa.yamanashi.jp +nirasaki.yamanashi.jp +nishikatsura.yamanashi.jp +oshino.yamanashi.jp +otsuki.yamanashi.jp +showa.yamanashi.jp +tabayama.yamanashi.jp +tsuru.yamanashi.jp +uenohara.yamanashi.jp +yamanakako.yamanashi.jp +yamanashi.yamanashi.jp + +// ke : http://www.kenic.or.ke/index.php/en/ke-domains/ke-domains +ke +ac.ke +co.ke +go.ke +info.ke +me.ke +mobi.ke +ne.ke +or.ke +sc.ke + +// kg : http://www.domain.kg/dmn_n.html +kg +com.kg +edu.kg +gov.kg +mil.kg +net.kg +org.kg + +// kh : http://www.mptc.gov.kh/dns_registration.htm +*.kh + +// ki : https://www.iana.org/domains/root/db/ki.html +ki +biz.ki +com.ki +edu.ki +gov.ki +info.ki +net.ki +org.ki + +// km : https://www.iana.org/domains/root/db/km.html +// http://www.domaine.km/documents/charte.doc +km +ass.km +com.km +edu.km +gov.km +mil.km +nom.km +org.km +prd.km +tm.km +// These are only mentioned as proposed suggestions at domaine.km, but +// https://www.iana.org/domains/root/db/km.html says they're available for registration: +asso.km +coop.km +gouv.km +medecin.km +notaires.km +pharmaciens.km +presse.km +veterinaire.km + +// kn : https://www.iana.org/domains/root/db/kn.html +// http://www.dot.kn/domainRules.html +kn +edu.kn +gov.kn +net.kn +org.kn + +// kp : http://www.kcce.kp/en_index.php +kp +com.kp +edu.kp +gov.kp +org.kp +rep.kp +tra.kp + +// kr : https://www.iana.org/domains/root/db/kr.html +// see also: https://krnic.kisa.or.kr/jsp/infoboard/law/domBylawsReg.jsp +kr +ac.kr +ai.kr +co.kr +es.kr +go.kr +hs.kr +io.kr +it.kr +kg.kr +me.kr +mil.kr +ms.kr +ne.kr +or.kr +pe.kr +re.kr +sc.kr +// kr geographical names +busan.kr +chungbuk.kr +chungnam.kr +daegu.kr +daejeon.kr +gangwon.kr +gwangju.kr +gyeongbuk.kr +gyeonggi.kr +gyeongnam.kr +incheon.kr +jeju.kr +jeonbuk.kr +jeonnam.kr +seoul.kr +ulsan.kr + +// kw : https://www.nic.kw/policies/ +// Confirmed by registry +kw +com.kw +edu.kw +emb.kw +gov.kw +ind.kw +net.kw +org.kw + +// ky : http://www.icta.ky/da_ky_reg_dom.php +// Confirmed by registry 2008-06-17 +ky +com.ky +edu.ky +net.ky +org.ky + +// kz : https://www.iana.org/domains/root/db/kz.html +// see also: http://www.nic.kz/rules/index.jsp +kz +com.kz +edu.kz +gov.kz +mil.kz +net.kz +org.kz + +// la : https://www.iana.org/domains/root/db/la.html +// Submitted by registry +la +com.la +edu.la +gov.la +info.la +int.la +net.la +org.la +per.la + +// lb : https://www.iana.org/domains/root/db/lb.html +// Submitted by registry +lb +com.lb +edu.lb +gov.lb +net.lb +org.lb + +// lc : https://www.iana.org/domains/root/db/lc.html +// see also: http://www.nic.lc/rules.htm +lc +co.lc +com.lc +edu.lc +gov.lc +net.lc +org.lc + +// li : https://www.iana.org/domains/root/db/li.html +li + +// lk : https://www.iana.org/domains/root/db/lk.html +lk +ac.lk +assn.lk +com.lk +edu.lk +gov.lk +grp.lk +hotel.lk +int.lk +ltd.lk +net.lk +ngo.lk +org.lk +sch.lk +soc.lk +web.lk + +// lr : http://psg.com/dns/lr/lr.txt +// Submitted by registry +lr +com.lr +edu.lr +gov.lr +net.lr +org.lr + +// ls : http://www.nic.ls/ +// Confirmed by registry +ls +ac.ls +biz.ls +co.ls +edu.ls +gov.ls +info.ls +net.ls +org.ls +sc.ls + +// lt : https://www.iana.org/domains/root/db/lt.html +lt +// gov.lt : http://www.gov.lt/index_en.php +gov.lt + +// lu : http://www.dns.lu/en/ +lu + +// lv : https://www.iana.org/domains/root/db/lv.html +lv +asn.lv +com.lv +conf.lv +edu.lv +gov.lv +id.lv +mil.lv +net.lv +org.lv + +// ly : http://www.nic.ly/regulations.php +ly +com.ly +edu.ly +gov.ly +id.ly +med.ly +net.ly +org.ly +plc.ly +sch.ly + +// ma : https://www.iana.org/domains/root/db/ma.html +// http://www.anrt.ma/fr/admin/download/upload/file_fr782.pdf +ma +ac.ma +co.ma +gov.ma +net.ma +org.ma +press.ma + +// mc : http://www.nic.mc/ +mc +asso.mc +tm.mc + +// md : https://www.iana.org/domains/root/db/md.html +md + +// me : https://www.iana.org/domains/root/db/me.html +me +ac.me +co.me +edu.me +gov.me +its.me +net.me +org.me +priv.me + +// mg : https://nic.mg +mg +co.mg +com.mg +edu.mg +gov.mg +mil.mg +nom.mg +org.mg +prd.mg + +// mh : https://www.iana.org/domains/root/db/mh.html +mh + +// mil : https://www.iana.org/domains/root/db/mil.html +mil + +// mk : https://www.iana.org/domains/root/db/mk.html +// see also: http://dns.marnet.net.mk/postapka.php +mk +com.mk +edu.mk +gov.mk +inf.mk +name.mk +net.mk +org.mk + +// ml : https://www.iana.org/domains/root/db/ml.html +// Confirmed by Boubacar NDIAYE 2024-12-31 +ml +ac.ml +art.ml +asso.ml +com.ml +edu.ml +gouv.ml +gov.ml +info.ml +inst.ml +net.ml +org.ml +pr.ml +presse.ml + +// mm : https://www.iana.org/domains/root/db/mm.html +*.mm + +// mn : https://www.iana.org/domains/root/db/mn.html +mn +edu.mn +gov.mn +org.mn + +// mo : http://www.monic.net.mo/ +mo +com.mo +edu.mo +gov.mo +net.mo +org.mo + +// mobi : https://www.iana.org/domains/root/db/mobi.html +mobi + +// mp : http://www.dot.mp/ +// Confirmed by registry 2008-06-17 +mp + +// mq : https://www.iana.org/domains/root/db/mq.html +mq + +// mr : https://www.iana.org/domains/root/db/mr.html +mr +gov.mr + +// ms : https://www.iana.org/domains/root/db/ms.html +ms +com.ms +edu.ms +gov.ms +net.ms +org.ms + +// mt : https://www.nic.org.mt/go/policy +// Submitted by registry +mt +com.mt +edu.mt +net.mt +org.mt + +// mu : https://www.iana.org/domains/root/db/mu.html +mu +ac.mu +co.mu +com.mu +gov.mu +net.mu +or.mu +org.mu + +// museum : https://welcome.museum/wp-content/uploads/2018/05/20180525-Registration-Policy-MUSEUM-EN_VF-2.pdf https://welcome.museum/buy-your-dot-museum-2/ +museum + +// mv : https://www.iana.org/domains/root/db/mv.html +// "mv" included because, contra Wikipedia, google.mv exists. +mv +aero.mv +biz.mv +com.mv +coop.mv +edu.mv +gov.mv +info.mv +int.mv +mil.mv +museum.mv +name.mv +net.mv +org.mv +pro.mv + +// mw : http://www.registrar.mw/ +mw +ac.mw +biz.mw +co.mw +com.mw +coop.mw +edu.mw +gov.mw +int.mw +net.mw +org.mw + +// mx : http://www.nic.mx/ +// Submitted by registry +mx +com.mx +edu.mx +gob.mx +net.mx +org.mx + +// my : http://www.mynic.my/ +// Available strings: https://mynic.my/resources/domains/buying-a-domain/ +my +biz.my +com.my +edu.my +gov.my +mil.my +name.my +net.my +org.my + +// mz : http://www.uem.mz/ +// Submitted by registry +mz +ac.mz +adv.mz +co.mz +edu.mz +gov.mz +mil.mz +net.mz +org.mz + +// na : http://www.na-nic.com.na/ +na +alt.na +co.na +com.na +gov.na +net.na +org.na + +// name : http://www.nic.name/ +// Regarding 2LDs: https://github.com/publicsuffix/list/issues/2306 +name + +// nc : http://www.cctld.nc/ +nc +asso.nc +nom.nc + +// ne : https://www.iana.org/domains/root/db/ne.html +ne + +// net : https://www.iana.org/domains/root/db/net.html +net + +// nf : https://www.iana.org/domains/root/db/nf.html +nf +arts.nf +com.nf +firm.nf +info.nf +net.nf +other.nf +per.nf +rec.nf +store.nf +web.nf + +// ng : http://www.nira.org.ng/index.php/join-us/register-ng-domain/189-nira-slds +ng +com.ng +edu.ng +gov.ng +i.ng +mil.ng +mobi.ng +name.ng +net.ng +org.ng +sch.ng + +// ni : http://www.nic.ni/ +ni +ac.ni +biz.ni +co.ni +com.ni +edu.ni +gob.ni +in.ni +info.ni +int.ni +mil.ni +net.ni +nom.ni +org.ni +web.ni + +// nl : https://www.iana.org/domains/root/db/nl.html +// https://www.sidn.nl/ +nl + +// no : https://www.norid.no/en/om-domenenavn/regelverk-for-no/ +// Norid geographical second level domains : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-b/ +// Norid category second level domains : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-c/ +// Norid category second-level domains managed by parties other than Norid : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-d/ +// RSS feed: https://teknisk.norid.no/en/feed/ +no +// Norid category second level domains : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-c/ +fhs.no +folkebibl.no +fylkesbibl.no +idrett.no +museum.no +priv.no +vgs.no +// Norid category second-level domains managed by parties other than Norid : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-d/ +dep.no +herad.no +kommune.no +mil.no +stat.no +// Norid geographical second level domains : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-b/ +// counties +aa.no +ah.no +bu.no +fm.no +hl.no +hm.no +jan-mayen.no +mr.no +nl.no +nt.no +of.no +ol.no +oslo.no +rl.no +sf.no +st.no +svalbard.no +tm.no +tr.no +va.no +vf.no +// primary and lower secondary schools per county +gs.aa.no +gs.ah.no +gs.bu.no +gs.fm.no +gs.hl.no +gs.hm.no +gs.jan-mayen.no +gs.mr.no +gs.nl.no +gs.nt.no +gs.of.no +gs.ol.no +gs.oslo.no +gs.rl.no +gs.sf.no +gs.st.no +gs.svalbard.no +gs.tm.no +gs.tr.no +gs.va.no +gs.vf.no +// cities +akrehamn.no +åkrehamn.no +algard.no +ålgård.no +arna.no +bronnoysund.no +brønnøysund.no +brumunddal.no +bryne.no +drobak.no +drøbak.no +egersund.no +fetsund.no +floro.no +florø.no +fredrikstad.no +hokksund.no +honefoss.no +hønefoss.no +jessheim.no +jorpeland.no +jørpeland.no +kirkenes.no +kopervik.no +krokstadelva.no +langevag.no +langevåg.no +leirvik.no +mjondalen.no +mjøndalen.no +mo-i-rana.no +mosjoen.no +mosjøen.no +nesoddtangen.no +orkanger.no +osoyro.no +osøyro.no +raholt.no +råholt.no +sandnessjoen.no +sandnessjøen.no +skedsmokorset.no +slattum.no +spjelkavik.no +stathelle.no +stavern.no +stjordalshalsen.no +stjørdalshalsen.no +tananger.no +tranby.no +vossevangen.no +// communities +aarborte.no +aejrie.no +afjord.no +åfjord.no +agdenes.no +nes.akershus.no +aknoluokta.no +ákŋoluokta.no +al.no +ål.no +alaheadju.no +álaheadju.no +alesund.no +ålesund.no +alstahaug.no +alta.no +áltá.no +alvdal.no +amli.no +åmli.no +amot.no +åmot.no +andasuolo.no +andebu.no +andoy.no +andøy.no +ardal.no +årdal.no +aremark.no +arendal.no +ås.no +aseral.no +åseral.no +asker.no +askim.no +askoy.no +askøy.no +askvoll.no +asnes.no +åsnes.no +audnedaln.no +aukra.no +aure.no +aurland.no +aurskog-holand.no +aurskog-høland.no +austevoll.no +austrheim.no +averoy.no +averøy.no +badaddja.no +bådåddjå.no +bærum.no +bahcavuotna.no +báhcavuotna.no +bahccavuotna.no +báhccavuotna.no +baidar.no +báidár.no +bajddar.no +bájddar.no +balat.no +bálát.no +balestrand.no +ballangen.no +balsfjord.no +bamble.no +bardu.no +barum.no +batsfjord.no +båtsfjord.no +bearalvahki.no +bearalváhki.no +beardu.no +beiarn.no +berg.no +bergen.no +berlevag.no +berlevåg.no +bievat.no +bievát.no +bindal.no +birkenes.no +bjarkoy.no +bjarkøy.no +bjerkreim.no +bjugn.no +bodo.no +bodø.no +bokn.no +bomlo.no +bømlo.no +bremanger.no +bronnoy.no +brønnøy.no +budejju.no +nes.buskerud.no +bygland.no +bykle.no +cahcesuolo.no +čáhcesuolo.no +davvenjarga.no +davvenjárga.no +davvesiida.no +deatnu.no +dielddanuorri.no +divtasvuodna.no +divttasvuotna.no +donna.no +dønna.no +dovre.no +drammen.no +drangedal.no +dyroy.no +dyrøy.no +eid.no +eidfjord.no +eidsberg.no +eidskog.no +eidsvoll.no +eigersund.no +elverum.no +enebakk.no +engerdal.no +etne.no +etnedal.no +evenassi.no +evenášši.no +evenes.no +evje-og-hornnes.no +farsund.no +fauske.no +fedje.no +fet.no +finnoy.no +finnøy.no +fitjar.no +fjaler.no +fjell.no +fla.no +flå.no +flakstad.no +flatanger.no +flekkefjord.no +flesberg.no +flora.no +folldal.no +forde.no +førde.no +forsand.no +fosnes.no +fræna.no +frana.no +frei.no +frogn.no +froland.no +frosta.no +froya.no +frøya.no +fuoisku.no +fuossko.no +fusa.no +fyresdal.no +gaivuotna.no +gáivuotna.no +galsa.no +gálsá.no +gamvik.no +gangaviika.no +gáŋgaviika.no +gaular.no +gausdal.no +giehtavuoatna.no +gildeskal.no +gildeskål.no +giske.no +gjemnes.no +gjerdrum.no +gjerstad.no +gjesdal.no +gjovik.no +gjøvik.no +gloppen.no +gol.no +gran.no +grane.no +granvin.no +gratangen.no +grimstad.no +grong.no +grue.no +gulen.no +guovdageaidnu.no +ha.no +hå.no +habmer.no +hábmer.no +hadsel.no +hægebostad.no +hagebostad.no +halden.no +halsa.no +hamar.no +hamaroy.no +hammarfeasta.no +hámmárfeasta.no +hammerfest.no +hapmir.no +hápmir.no +haram.no +hareid.no +harstad.no +hasvik.no +hattfjelldal.no +haugesund.no +os.hedmark.no +valer.hedmark.no +våler.hedmark.no +hemne.no +hemnes.no +hemsedal.no +hitra.no +hjartdal.no +hjelmeland.no +hobol.no +hobøl.no +hof.no +hol.no +hole.no +holmestrand.no +holtalen.no +holtålen.no +os.hordaland.no +hornindal.no +horten.no +hoyanger.no +høyanger.no +hoylandet.no +høylandet.no +hurdal.no +hurum.no +hvaler.no +hyllestad.no +ibestad.no +inderoy.no +inderøy.no +iveland.no +ivgu.no +jevnaker.no +jolster.no +jølster.no +jondal.no +kafjord.no +kåfjord.no +karasjohka.no +kárášjohka.no +karasjok.no +karlsoy.no +karmoy.no +karmøy.no +kautokeino.no +klabu.no +klæbu.no +klepp.no +kongsberg.no +kongsvinger.no +kraanghke.no +kråanghke.no +kragero.no +kragerø.no +kristiansand.no +kristiansund.no +krodsherad.no +krødsherad.no +kvæfjord.no +kvænangen.no +kvafjord.no +kvalsund.no +kvam.no +kvanangen.no +kvinesdal.no +kvinnherad.no +kviteseid.no +kvitsoy.no +kvitsøy.no +laakesvuemie.no +lærdal.no +lahppi.no +láhppi.no +lardal.no +larvik.no +lavagis.no +lavangen.no +leangaviika.no +leaŋgaviika.no +lebesby.no +leikanger.no +leirfjord.no +leka.no +leksvik.no +lenvik.no +lerdal.no +lesja.no +levanger.no +lier.no +lierne.no +lillehammer.no +lillesand.no +lindas.no +lindås.no +lindesnes.no +loabat.no +loabát.no +lodingen.no +lødingen.no +lom.no +loppa.no +lorenskog.no +lørenskog.no +loten.no +løten.no +lund.no +lunner.no +luroy.no +lurøy.no +luster.no +lyngdal.no +lyngen.no +malatvuopmi.no +málatvuopmi.no +malselv.no +målselv.no +malvik.no +mandal.no +marker.no +marnardal.no +masfjorden.no +masoy.no +måsøy.no +matta-varjjat.no +mátta-várjjat.no +meland.no +meldal.no +melhus.no +meloy.no +meløy.no +meraker.no +meråker.no +midsund.no +midtre-gauldal.no +moareke.no +moåreke.no +modalen.no +modum.no +molde.no +heroy.more-og-romsdal.no +sande.more-og-romsdal.no +herøy.møre-og-romsdal.no +sande.møre-og-romsdal.no +moskenes.no +moss.no +mosvik.no +muosat.no +muosát.no +naamesjevuemie.no +nååmesjevuemie.no +nærøy.no +namdalseid.no +namsos.no +namsskogan.no +nannestad.no +naroy.no +narviika.no +narvik.no +naustdal.no +navuotna.no +návuotna.no +nedre-eiker.no +nesna.no +nesodden.no +nesseby.no +nesset.no +nissedal.no +nittedal.no +nord-aurdal.no +nord-fron.no +nord-odal.no +norddal.no +nordkapp.no +bo.nordland.no +bø.nordland.no +heroy.nordland.no +herøy.nordland.no +nordre-land.no +nordreisa.no +nore-og-uvdal.no +notodden.no +notteroy.no +nøtterøy.no +odda.no +oksnes.no +øksnes.no +omasvuotna.no +oppdal.no +oppegard.no +oppegård.no +orkdal.no +orland.no +ørland.no +orskog.no +ørskog.no +orsta.no +ørsta.no +osen.no +osteroy.no +osterøy.no +valer.ostfold.no +våler.østfold.no +ostre-toten.no +østre-toten.no +overhalla.no +ovre-eiker.no +øvre-eiker.no +oyer.no +øyer.no +oygarden.no +øygarden.no +oystre-slidre.no +øystre-slidre.no +porsanger.no +porsangu.no +porsáŋgu.no +porsgrunn.no +rade.no +råde.no +radoy.no +radøy.no +rælingen.no +rahkkeravju.no +ráhkkerávju.no +raisa.no +ráisa.no +rakkestad.no +ralingen.no +rana.no +randaberg.no +rauma.no +rendalen.no +rennebu.no +rennesoy.no +rennesøy.no +rindal.no +ringebu.no +ringerike.no +ringsaker.no +risor.no +risør.no +rissa.no +roan.no +rodoy.no +rødøy.no +rollag.no +romsa.no +romskog.no +rømskog.no +roros.no +røros.no +rost.no +røst.no +royken.no +røyken.no +royrvik.no +røyrvik.no +ruovat.no +rygge.no +salangen.no +salat.no +sálat.no +sálát.no +saltdal.no +samnanger.no +sandefjord.no +sandnes.no +sandoy.no +sandøy.no +sarpsborg.no +sauda.no +sauherad.no +sel.no +selbu.no +selje.no +seljord.no +siellak.no +sigdal.no +siljan.no +sirdal.no +skanit.no +skánit.no +skanland.no +skånland.no +skaun.no +skedsmo.no +ski.no +skien.no +skierva.no +skiervá.no +skiptvet.no +skjak.no +skjåk.no +skjervoy.no +skjervøy.no +skodje.no +smola.no +smøla.no +snaase.no +snåase.no +snasa.no +snåsa.no +snillfjord.no +snoasa.no +sogndal.no +sogne.no +søgne.no +sokndal.no +sola.no +solund.no +somna.no +sømna.no +sondre-land.no +søndre-land.no +songdalen.no +sor-aurdal.no +sør-aurdal.no +sor-fron.no +sør-fron.no +sor-odal.no +sør-odal.no +sor-varanger.no +sør-varanger.no +sorfold.no +sørfold.no +sorreisa.no +sørreisa.no +sortland.no +sorum.no +sørum.no +spydeberg.no +stange.no +stavanger.no +steigen.no +steinkjer.no +stjordal.no +stjørdal.no +stokke.no +stor-elvdal.no +stord.no +stordal.no +storfjord.no +strand.no +stranda.no +stryn.no +sula.no +suldal.no +sund.no +sunndal.no +surnadal.no +sveio.no +svelvik.no +sykkylven.no +tana.no +bo.telemark.no +bø.telemark.no +time.no +tingvoll.no +tinn.no +tjeldsund.no +tjome.no +tjøme.no +tokke.no +tolga.no +tonsberg.no +tønsberg.no +torsken.no +træna.no +trana.no +tranoy.no +tranøy.no +troandin.no +trogstad.no +trøgstad.no +tromsa.no +tromso.no +tromsø.no +trondheim.no +trysil.no +tvedestrand.no +tydal.no +tynset.no +tysfjord.no +tysnes.no +tysvær.no +tysvar.no +ullensaker.no +ullensvang.no +ulvik.no +unjarga.no +unjárga.no +utsira.no +vaapste.no +vadso.no +vadsø.no +værøy.no +vaga.no +vågå.no +vagan.no +vågan.no +vagsoy.no +vågsøy.no +vaksdal.no +valle.no +vang.no +vanylven.no +vardo.no +vardø.no +varggat.no +várggát.no +varoy.no +vefsn.no +vega.no +vegarshei.no +vegårshei.no +vennesla.no +verdal.no +verran.no +vestby.no +sande.vestfold.no +vestnes.no +vestre-slidre.no +vestre-toten.no +vestvagoy.no +vestvågøy.no +vevelstad.no +vik.no +vikna.no +vindafjord.no +voagat.no +volda.no +voss.no + +// np : http://www.mos.com.np/register.html +*.np + +// nr : http://cenpac.net.nr/dns/index.html +// Submitted by registry +nr +biz.nr +com.nr +edu.nr +gov.nr +info.nr +net.nr +org.nr + +// nu : https://www.iana.org/domains/root/db/nu.html +nu + +// nz : https://www.iana.org/domains/root/db/nz.html +// Submitted by registry +nz +ac.nz +co.nz +cri.nz +geek.nz +gen.nz +govt.nz +health.nz +iwi.nz +kiwi.nz +maori.nz +māori.nz +mil.nz +net.nz +org.nz +parliament.nz +school.nz + +// om : https://www.iana.org/domains/root/db/om.html +om +co.om +com.om +edu.om +gov.om +med.om +museum.om +net.om +org.om +pro.om + +// onion : https://tools.ietf.org/html/rfc7686 +onion + +// org : https://www.iana.org/domains/root/db/org.html +org + +// pa : http://www.nic.pa/ +// Some additional second level "domains" resolve directly as hostnames, such as +// pannet.pa, so we add a rule for "pa". +pa +abo.pa +ac.pa +com.pa +edu.pa +gob.pa +ing.pa +med.pa +net.pa +nom.pa +org.pa +sld.pa + +// pe : https://www.nic.pe/InformeFinalComision.pdf +pe +com.pe +edu.pe +gob.pe +mil.pe +net.pe +nom.pe +org.pe + +// pf : http://www.gobin.info/domainname/formulaire-pf.pdf +pf +com.pf +edu.pf +org.pf + +// pg : https://www.iana.org/domains/root/db/pg.html +*.pg + +// ph : https://www.iana.org/domains/root/db/ph.html +// Submitted by registry +ph +com.ph +edu.ph +gov.ph +i.ph +mil.ph +net.ph +ngo.ph +org.ph + +// pk : https://pk5.pknic.net.pk/pk5/msgNamepk.PK +// Contact Email: staff@pknic.net.pk +pk +ac.pk +biz.pk +com.pk +edu.pk +fam.pk +gkp.pk +gob.pk +gog.pk +gok.pk +gop.pk +gos.pk +gov.pk +net.pk +org.pk +web.pk + +// pl : https://www.dns.pl/en/ +// Confirmed by registry 2024-11-18 +pl +com.pl +net.pl +org.pl +// pl functional domains : https://www.dns.pl/en/list_of_functional_domain_names +agro.pl +aid.pl +atm.pl +auto.pl +biz.pl +edu.pl +gmina.pl +gsm.pl +info.pl +mail.pl +media.pl +miasta.pl +mil.pl +nieruchomosci.pl +nom.pl +pc.pl +powiat.pl +priv.pl +realestate.pl +rel.pl +sex.pl +shop.pl +sklep.pl +sos.pl +szkola.pl +targi.pl +tm.pl +tourism.pl +travel.pl +turystyka.pl +// Government domains : https://www.dns.pl/informacje_o_rejestracji_domen_gov_pl +// In accordance with the .gov.pl Domain Name Regulations : https://www.dns.pl/regulamin_gov_pl +gov.pl +ap.gov.pl +griw.gov.pl +ic.gov.pl +is.gov.pl +kmpsp.gov.pl +konsulat.gov.pl +kppsp.gov.pl +kwp.gov.pl +kwpsp.gov.pl +mup.gov.pl +mw.gov.pl +oia.gov.pl +oirm.gov.pl +oke.gov.pl +oow.gov.pl +oschr.gov.pl +oum.gov.pl +pa.gov.pl +pinb.gov.pl +piw.gov.pl +po.gov.pl +pr.gov.pl +psp.gov.pl +psse.gov.pl +pup.gov.pl +rzgw.gov.pl +sa.gov.pl +sdn.gov.pl +sko.gov.pl +so.gov.pl +sr.gov.pl +starostwo.gov.pl +ug.gov.pl +ugim.gov.pl +um.gov.pl +umig.gov.pl +upow.gov.pl +uppo.gov.pl +us.gov.pl +uw.gov.pl +uzs.gov.pl +wif.gov.pl +wiih.gov.pl +winb.gov.pl +wios.gov.pl +witd.gov.pl +wiw.gov.pl +wkz.gov.pl +wsa.gov.pl +wskr.gov.pl +wsse.gov.pl +wuoz.gov.pl +wzmiuw.gov.pl +zp.gov.pl +zpisdn.gov.pl +// pl regional domains : https://www.dns.pl/en/list_of_regional_domain_names +augustow.pl +babia-gora.pl +bedzin.pl +beskidy.pl +bialowieza.pl +bialystok.pl +bielawa.pl +bieszczady.pl +boleslawiec.pl +bydgoszcz.pl +bytom.pl +cieszyn.pl +czeladz.pl +czest.pl +dlugoleka.pl +elblag.pl +elk.pl +glogow.pl +gniezno.pl +gorlice.pl +grajewo.pl +ilawa.pl +jaworzno.pl +jelenia-gora.pl +jgora.pl +kalisz.pl +karpacz.pl +kartuzy.pl +kaszuby.pl +katowice.pl +kazimierz-dolny.pl +kepno.pl +ketrzyn.pl +klodzko.pl +kobierzyce.pl +kolobrzeg.pl +konin.pl +konskowola.pl +kutno.pl +lapy.pl +lebork.pl +legnica.pl +lezajsk.pl +limanowa.pl +lomza.pl +lowicz.pl +lubin.pl +lukow.pl +malbork.pl +malopolska.pl +mazowsze.pl +mazury.pl +mielec.pl +mielno.pl +mragowo.pl +naklo.pl +nowaruda.pl +nysa.pl +olawa.pl +olecko.pl +olkusz.pl +olsztyn.pl +opoczno.pl +opole.pl +ostroda.pl +ostroleka.pl +ostrowiec.pl +ostrowwlkp.pl +pila.pl +pisz.pl +podhale.pl +podlasie.pl +polkowice.pl +pomorskie.pl +pomorze.pl +prochowice.pl +pruszkow.pl +przeworsk.pl +pulawy.pl +radom.pl +rawa-maz.pl +rybnik.pl +rzeszow.pl +sanok.pl +sejny.pl +skoczow.pl +slask.pl +slupsk.pl +sosnowiec.pl +stalowa-wola.pl +starachowice.pl +stargard.pl +suwalki.pl +swidnica.pl +swiebodzin.pl +swinoujscie.pl +szczecin.pl +szczytno.pl +tarnobrzeg.pl +tgory.pl +turek.pl +tychy.pl +ustka.pl +walbrzych.pl +warmia.pl +warszawa.pl +waw.pl +wegrow.pl +wielun.pl +wlocl.pl +wloclawek.pl +wodzislaw.pl +wolomin.pl +wroclaw.pl +zachpomor.pl +zagan.pl +zarow.pl +zgora.pl +zgorzelec.pl + +// pm : https://www.afnic.fr/wp-media/uploads/2022/12/afnic-naming-policy-2023-01-01.pdf +pm + +// pn : https://www.iana.org/domains/root/db/pn.html +pn +co.pn +edu.pn +gov.pn +net.pn +org.pn + +// post : https://www.iana.org/domains/root/db/post.html +post + +// pr : http://www.nic.pr/index.asp?f=1 +pr +biz.pr +com.pr +edu.pr +gov.pr +info.pr +isla.pr +name.pr +net.pr +org.pr +pro.pr +// these aren't mentioned on nic.pr, but on https://www.iana.org/domains/root/db/pr.html +ac.pr +est.pr +prof.pr + +// pro : http://registry.pro/get-pro +pro +aaa.pro +aca.pro +acct.pro +avocat.pro +bar.pro +cpa.pro +eng.pro +jur.pro +law.pro +med.pro +recht.pro + +// ps : https://www.iana.org/domains/root/db/ps.html +// http://www.nic.ps/registration/policy.html#reg +ps +com.ps +edu.ps +gov.ps +net.ps +org.ps +plo.ps +sec.ps + +// pt : https://www.dns.pt/en/domain/pt-terms-and-conditions-registration-rules/ +pt +com.pt +edu.pt +gov.pt +int.pt +net.pt +nome.pt +org.pt +publ.pt + +// pw : https://www.iana.org/domains/root/db/pw.html +// Confirmed by registry in private correspondence with @dnsguru 2024-12-09 +pw +gov.pw + +// py : https://www.iana.org/domains/root/db/py.html +// Submitted by registry +py +com.py +coop.py +edu.py +gov.py +mil.py +net.py +org.py + +// qa : http://domains.qa/en/ +qa +com.qa +edu.qa +gov.qa +mil.qa +name.qa +net.qa +org.qa +sch.qa + +// re : https://www.afnic.fr/wp-media/uploads/2022/12/afnic-naming-policy-2023-01-01.pdf +// Confirmed by registry 2024-11-18 +re +// Closed for registration on 2013-03-15 but domains are still maintained +asso.re +com.re + +// ro : http://www.rotld.ro/ +ro +arts.ro +com.ro +firm.ro +info.ro +nom.ro +nt.ro +org.ro +rec.ro +store.ro +tm.ro +www.ro + +// rs : https://www.rnids.rs/en/domains/national-domains +rs +ac.rs +co.rs +edu.rs +gov.rs +in.rs +org.rs + +// ru : https://cctld.ru/files/pdf/docs/en/rules_ru-rf.pdf +// Submitted by George Georgievsky +ru + +// rw : https://www.iana.org/domains/root/db/rw.html +rw +ac.rw +co.rw +coop.rw +gov.rw +mil.rw +net.rw +org.rw + +// sa : http://www.nic.net.sa/ +sa +com.sa +edu.sa +gov.sa +med.sa +net.sa +org.sa +pub.sa +sch.sa + +// sb : http://www.sbnic.net.sb/ +// Submitted by registry +sb +com.sb +edu.sb +gov.sb +net.sb +org.sb + +// sc : http://www.nic.sc/ +sc +com.sc +edu.sc +gov.sc +net.sc +org.sc + +// sd : https://www.iana.org/domains/root/db/sd.html +// Submitted by registry +sd +com.sd +edu.sd +gov.sd +info.sd +med.sd +net.sd +org.sd +tv.sd + +// se : https://www.iana.org/domains/root/db/se.html +// https://data.internetstiftelsen.se/barred_domains_list.txt -> Second level domains & Sub-domains +// Confirmed by Registry Services 2024-11-20 +se +a.se +ac.se +b.se +bd.se +brand.se +c.se +d.se +e.se +f.se +fh.se +fhsk.se +fhv.se +g.se +h.se +i.se +k.se +komforb.se +kommunalforbund.se +komvux.se +l.se +lanbib.se +m.se +n.se +naturbruksgymn.se +o.se +org.se +p.se +parti.se +pp.se +press.se +r.se +s.se +t.se +tm.se +u.se +w.se +x.se +y.se +z.se + +// sg : https://www.sgnic.sg/domain-registration/sg-categories-rules +// Confirmed by registry 2024-11-19 +sg +com.sg +edu.sg +gov.sg +net.sg +org.sg + +// sh : http://nic.sh/rules.htm +sh +com.sh +gov.sh +mil.sh +net.sh +org.sh + +// si : https://www.iana.org/domains/root/db/si.html +si + +// sj : No registrations at this time. +// Submitted by registry +sj + +// sk : https://www.iana.org/domains/root/db/sk.html +sk + +// sl : http://www.nic.sl +// Submitted by registry +sl +com.sl +edu.sl +gov.sl +net.sl +org.sl + +// sm : https://www.iana.org/domains/root/db/sm.html +sm + +// sn : https://www.iana.org/domains/root/db/sn.html +sn +art.sn +com.sn +edu.sn +gouv.sn +org.sn +perso.sn +univ.sn + +// so : http://sonic.so/policies/ +so +com.so +edu.so +gov.so +me.so +net.so +org.so + +// sr : https://www.iana.org/domains/root/db/sr.html +sr + +// ss : https://registry.nic.ss/ +// Submitted by registry +ss +biz.ss +co.ss +com.ss +edu.ss +gov.ss +me.ss +net.ss +org.ss +sch.ss + +// st : http://www.nic.st/html/policyrules/ +st +co.st +com.st +consulado.st +edu.st +embaixada.st +mil.st +net.st +org.st +principe.st +saotome.st +store.st + +// su : https://www.iana.org/domains/root/db/su.html +su + +// sv : https://www.iana.org/domains/root/db/sv.html +sv +com.sv +edu.sv +gob.sv +org.sv +red.sv + +// sx : https://www.iana.org/domains/root/db/sx.html +// Submitted by registry +sx +gov.sx + +// sy : https://www.iana.org/domains/root/db/sy.html +sy +com.sy +edu.sy +gov.sy +mil.sy +net.sy +org.sy + +// sz : https://www.iana.org/domains/root/db/sz.html +// http://www.sispa.org.sz/ +sz +ac.sz +co.sz +org.sz + +// tc : https://www.iana.org/domains/root/db/tc.html +tc + +// td : https://www.iana.org/domains/root/db/td.html +td + +// tel : https://www.iana.org/domains/root/db/tel.html +// http://www.telnic.org/ +tel + +// tf : https://www.afnic.fr/wp-media/uploads/2022/12/afnic-naming-policy-2023-01-01.pdf +tf + +// tg : https://www.iana.org/domains/root/db/tg.html +// http://www.nic.tg/ +tg + +// th : https://www.iana.org/domains/root/db/th.html +// Submitted by registry +th +ac.th +co.th +go.th +in.th +mi.th +net.th +or.th + +// tj : http://www.nic.tj/policy.html +tj +ac.tj +biz.tj +co.tj +com.tj +edu.tj +go.tj +gov.tj +int.tj +mil.tj +name.tj +net.tj +nic.tj +org.tj +test.tj +web.tj + +// tk : https://www.iana.org/domains/root/db/tk.html +tk + +// tl : https://www.iana.org/domains/root/db/tl.html +tl +gov.tl + +// tm : https://www.nic.tm/local.html +// Confirmed by registry 2024-11-19 +tm +co.tm +com.tm +edu.tm +gov.tm +mil.tm +net.tm +nom.tm +org.tm + +// tn : http://www.registre.tn/fr/ +// https://whois.ati.tn/ +tn +com.tn +ens.tn +fin.tn +gov.tn +ind.tn +info.tn +intl.tn +mincom.tn +nat.tn +net.tn +org.tn +perso.tn +tourism.tn + +// to : https://www.iana.org/domains/root/db/to.html +// Submitted by registry +to +com.to +edu.to +gov.to +mil.to +net.to +org.to + +// tr : https://nic.tr/ +// https://nic.tr/forms/eng/policies.pdf +// https://nic.tr/index.php?USRACTN=PRICELST +tr +av.tr +bbs.tr +bel.tr +biz.tr +com.tr +dr.tr +edu.tr +gen.tr +gov.tr +info.tr +k12.tr +kep.tr +mil.tr +name.tr +net.tr +org.tr +pol.tr +tel.tr +tsk.tr +tv.tr +web.tr +// Used by Northern Cyprus +nc.tr +// Used by government agencies of Northern Cyprus +gov.nc.tr + +// tt : https://www.nic.tt/ +// Confirmed by registry 2024-11-19 +tt +biz.tt +co.tt +com.tt +edu.tt +gov.tt +info.tt +mil.tt +name.tt +net.tt +org.tt +pro.tt + +// tv : https://www.iana.org/domains/root/db/tv.html +// Not listing any 2LDs as reserved since none seem to exist in practice, +// Wikipedia notwithstanding. +tv + +// tw : https://www.iana.org/domains/root/db/tw.html +// https://twnic.tw/dnservice_catag.php +// Confirmed by registry 2024-11-26 +tw +club.tw +com.tw +ebiz.tw +edu.tw +game.tw +gov.tw +idv.tw +mil.tw +net.tw +org.tw + +// tz : http://www.tznic.or.tz/index.php/domains +// Submitted by registry +tz +ac.tz +co.tz +go.tz +hotel.tz +info.tz +me.tz +mil.tz +mobi.tz +ne.tz +or.tz +sc.tz +tv.tz + +// ua : https://hostmaster.ua/policy/?ua +// Submitted by registry +ua +// ua 2LD +com.ua +edu.ua +gov.ua +in.ua +net.ua +org.ua +// ua geographic names +// https://hostmaster.ua/2ld/ +cherkassy.ua +cherkasy.ua +chernigov.ua +chernihiv.ua +chernivtsi.ua +chernovtsy.ua +ck.ua +cn.ua +cr.ua +crimea.ua +cv.ua +dn.ua +dnepropetrovsk.ua +dnipropetrovsk.ua +donetsk.ua +dp.ua +if.ua +ivano-frankivsk.ua +kh.ua +kharkiv.ua +kharkov.ua +kherson.ua +khmelnitskiy.ua +khmelnytskyi.ua +kiev.ua +kirovograd.ua +km.ua +kr.ua +kropyvnytskyi.ua +krym.ua +ks.ua +kv.ua +kyiv.ua +lg.ua +lt.ua +lugansk.ua +luhansk.ua +lutsk.ua +lv.ua +lviv.ua +mk.ua +mykolaiv.ua +nikolaev.ua +od.ua +odesa.ua +odessa.ua +pl.ua +poltava.ua +rivne.ua +rovno.ua +rv.ua +sb.ua +sebastopol.ua +sevastopol.ua +sm.ua +sumy.ua +te.ua +ternopil.ua +uz.ua +uzhgorod.ua +uzhhorod.ua +vinnica.ua +vinnytsia.ua +vn.ua +volyn.ua +yalta.ua +zakarpattia.ua +zaporizhzhe.ua +zaporizhzhia.ua +zhitomir.ua +zhytomyr.ua +zp.ua +zt.ua + +// ug : https://www.registry.co.ug/ +// https://www.registry.co.ug, https://whois.co.ug +// Confirmed by registry 2025-01-20 +ug +ac.ug +co.ug +com.ug +edu.ug +go.ug +gov.ug +mil.ug +ne.ug +or.ug +org.ug +sc.ug +us.ug + +// uk : https://www.iana.org/domains/root/db/uk.html +// Submitted by registry +uk +ac.uk +co.uk +gov.uk +ltd.uk +me.uk +net.uk +nhs.uk +org.uk +plc.uk +police.uk +*.sch.uk + +// us : https://www.iana.org/domains/root/db/us.html +// Confirmed via the .us zone file by William Harrison 2024-12-10 +us +dni.us +isa.us +nsn.us +// Geographic Names +ak.us +al.us +ar.us +as.us +az.us +ca.us +co.us +ct.us +dc.us +de.us +fl.us +ga.us +gu.us +hi.us +ia.us +id.us +il.us +in.us +ks.us +ky.us +la.us +ma.us +md.us +me.us +mi.us +mn.us +mo.us +ms.us +mt.us +nc.us +nd.us +ne.us +nh.us +nj.us +nm.us +nv.us +ny.us +oh.us +ok.us +or.us +pa.us +pr.us +ri.us +sc.us +sd.us +tn.us +tx.us +ut.us +va.us +vi.us +vt.us +wa.us +wi.us +wv.us +wy.us +// The registrar notes several more specific domains available in each state, +// such as state.*.us, dst.*.us, etc., but resolution of these is somewhat +// haphazard; in some states these domains resolve as addresses, while in others +// only subdomains are available, or even nothing at all. We include the +// most common ones where it's clear that different sites are different +// entities. +k12.ak.us +k12.al.us +k12.ar.us +k12.as.us +k12.az.us +k12.ca.us +k12.co.us +k12.ct.us +k12.dc.us +k12.fl.us +k12.ga.us +k12.gu.us +// k12.hi.us - Bug 614565 - Hawaii has a state-wide DOE login +k12.ia.us +k12.id.us +k12.il.us +k12.in.us +k12.ks.us +k12.ky.us +k12.la.us +k12.ma.us +k12.md.us +k12.me.us +k12.mi.us +k12.mn.us +k12.mo.us +k12.ms.us +k12.mt.us +k12.nc.us +// k12.nd.us - Bug 1028347 - Removed at request of Travis Rosso +k12.ne.us +k12.nh.us +k12.nj.us +k12.nm.us +k12.nv.us +k12.ny.us +k12.oh.us +k12.ok.us +k12.or.us +k12.pa.us +k12.pr.us +// k12.ri.us - Removed at request of Kim Cournoyer +k12.sc.us +// k12.sd.us - Bug 934131 - Removed at request of James Booze +k12.tn.us +k12.tx.us +k12.ut.us +k12.va.us +k12.vi.us +k12.vt.us +k12.wa.us +k12.wi.us +// k12.wv.us - Bug 947705 - Removed at request of Verne Britton +cc.ak.us +lib.ak.us +cc.al.us +lib.al.us +cc.ar.us +lib.ar.us +cc.as.us +lib.as.us +cc.az.us +lib.az.us +cc.ca.us +lib.ca.us +cc.co.us +lib.co.us +cc.ct.us +lib.ct.us +cc.dc.us +lib.dc.us +cc.de.us +cc.fl.us +lib.fl.us +cc.ga.us +lib.ga.us +cc.gu.us +lib.gu.us +cc.hi.us +lib.hi.us +cc.ia.us +lib.ia.us +cc.id.us +lib.id.us +cc.il.us +lib.il.us +cc.in.us +lib.in.us +cc.ks.us +lib.ks.us +cc.ky.us +lib.ky.us +cc.la.us +lib.la.us +cc.ma.us +lib.ma.us +cc.md.us +lib.md.us +cc.me.us +lib.me.us +cc.mi.us +lib.mi.us +cc.mn.us +lib.mn.us +cc.mo.us +lib.mo.us +cc.ms.us +cc.mt.us +lib.mt.us +cc.nc.us +lib.nc.us +cc.nd.us +lib.nd.us +cc.ne.us +lib.ne.us +cc.nh.us +lib.nh.us +cc.nj.us +lib.nj.us +cc.nm.us +lib.nm.us +cc.nv.us +lib.nv.us +cc.ny.us +lib.ny.us +cc.oh.us +lib.oh.us +cc.ok.us +lib.ok.us +cc.or.us +lib.or.us +cc.pa.us +lib.pa.us +cc.pr.us +lib.pr.us +cc.ri.us +lib.ri.us +cc.sc.us +lib.sc.us +cc.sd.us +lib.sd.us +cc.tn.us +lib.tn.us +cc.tx.us +lib.tx.us +cc.ut.us +lib.ut.us +cc.va.us +lib.va.us +cc.vi.us +lib.vi.us +cc.vt.us +lib.vt.us +cc.wa.us +lib.wa.us +cc.wi.us +lib.wi.us +cc.wv.us +cc.wy.us +k12.wy.us +// lib.wv.us - Bug 941670 - Removed at request of Larry W Arnold +lib.wy.us +// k12.ma.us contains school districts in Massachusetts. The 4LDs are +// managed independently except for private (PVT), charter (CHTR) and +// parochial (PAROCH) schools. Those are delegated directly to the +// 5LD operators. +chtr.k12.ma.us +paroch.k12.ma.us +pvt.k12.ma.us +// Merit Network, Inc. maintains the registry for =~ /(k12|cc|lib).mi.us/ and the following +// see also: https://domreg.merit.edu : domreg@merit.edu +// see also: whois -h whois.domreg.merit.edu help +ann-arbor.mi.us +cog.mi.us +dst.mi.us +eaton.mi.us +gen.mi.us +mus.mi.us +tec.mi.us +washtenaw.mi.us + +// uy : http://www.nic.org.uy/ +uy +com.uy +edu.uy +gub.uy +mil.uy +net.uy +org.uy + +// uz : http://www.reg.uz/ +uz +co.uz +com.uz +net.uz +org.uz + +// va : https://www.iana.org/domains/root/db/va.html +va + +// vc : https://www.iana.org/domains/root/db/vc.html +// Submitted by registry +vc +com.vc +edu.vc +gov.vc +mil.vc +net.vc +org.vc + +// ve : https://registro.nic.ve/ +// Submitted by registry nic@nic.ve and nicve@conatel.gob.ve +ve +arts.ve +bib.ve +co.ve +com.ve +e12.ve +edu.ve +emprende.ve +firm.ve +gob.ve +gov.ve +info.ve +int.ve +mil.ve +net.ve +nom.ve +org.ve +rar.ve +rec.ve +store.ve +tec.ve +web.ve + +// vg : https://www.iana.org/domains/root/db/vg.html +// Confirmed by registry 2025-01-10 +vg +edu.vg + +// vi : https://www.iana.org/domains/root/db/vi.html +vi +co.vi +com.vi +k12.vi +net.vi +org.vi + +// vn : https://www.vnnic.vn/en/domain/cctld-vn +// https://vnnic.vn/sites/default/files/tailieu/vn.cctld.domains.txt +vn +ac.vn +ai.vn +biz.vn +com.vn +edu.vn +gov.vn +health.vn +id.vn +info.vn +int.vn +io.vn +name.vn +net.vn +org.vn +pro.vn + +// vn geographical names +angiang.vn +bacgiang.vn +backan.vn +baclieu.vn +bacninh.vn +baria-vungtau.vn +bentre.vn +binhdinh.vn +binhduong.vn +binhphuoc.vn +binhthuan.vn +camau.vn +cantho.vn +caobang.vn +daklak.vn +daknong.vn +danang.vn +dienbien.vn +dongnai.vn +dongthap.vn +gialai.vn +hagiang.vn +haiduong.vn +haiphong.vn +hanam.vn +hanoi.vn +hatinh.vn +haugiang.vn +hoabinh.vn +hungyen.vn +khanhhoa.vn +kiengiang.vn +kontum.vn +laichau.vn +lamdong.vn +langson.vn +laocai.vn +longan.vn +namdinh.vn +nghean.vn +ninhbinh.vn +ninhthuan.vn +phutho.vn +phuyen.vn +quangbinh.vn +quangnam.vn +quangngai.vn +quangninh.vn +quangtri.vn +soctrang.vn +sonla.vn +tayninh.vn +thaibinh.vn +thainguyen.vn +thanhhoa.vn +thanhphohochiminh.vn +thuathienhue.vn +tiengiang.vn +travinh.vn +tuyenquang.vn +vinhlong.vn +vinhphuc.vn +yenbai.vn + +// vu : https://www.iana.org/domains/root/db/vu.html +// http://www.vunic.vu/ +vu +com.vu +edu.vu +net.vu +org.vu + +// wf : https://www.afnic.fr/wp-media/uploads/2022/12/afnic-naming-policy-2023-01-01.pdf +wf + +// ws : https://www.iana.org/domains/root/db/ws.html +// http://samoanic.ws/index.dhtml +ws +com.ws +edu.ws +gov.ws +net.ws +org.ws + +// yt : https://www.afnic.fr/wp-media/uploads/2022/12/afnic-naming-policy-2023-01-01.pdf +yt + +// IDN ccTLDs +// When submitting patches, please maintain a sort by ISO 3166 ccTLD, then +// U-label, and follow this format: +// // A-Label ("", [, variant info]) : +// // [sponsoring org] +// U-Label + +// xn--mgbaam7a8h ("Emerat", Arabic) : AE +// http://nic.ae/english/arabicdomain/rules.jsp +امارات + +// xn--y9a3aq ("hye", Armenian) : AM +// ISOC AM (operated by .am Registry) +հայ + +// xn--54b7fta0cc ("Bangla", Bangla) : BD +বাংলা + +// xn--90ae ("bg", Bulgarian) : BG +бг + +// xn--mgbcpq6gpa1a ("albahrain", Arabic) : BH +البحرين + +// xn--90ais ("bel", Belarusian/Russian Cyrillic) : BY +// Operated by .by registry +бел + +// xn--fiqs8s ("Zhongguo/China", Chinese, Simplified) : CN +// CNNIC +// https://www.cnnic.cn/11/192/index.html +中国 + +// xn--fiqz9s ("Zhongguo/China", Chinese, Traditional) : CN +// CNNIC +// https://www.cnnic.com.cn/AU/MediaC/Announcement/201609/t20160905_54470.htm +中國 + +// xn--lgbbat1ad8j ("Algeria/Al Jazair", Arabic) : DZ +الجزائر + +// xn--wgbh1c ("Egypt/Masr", Arabic) : EG +// http://www.dotmasr.eg/ +مصر + +// xn--e1a4c ("eu", Cyrillic) : EU +// https://eurid.eu +ею + +// xn--qxa6a ("eu", Greek) : EU +// https://eurid.eu +ευ + +// xn--mgbah1a3hjkrd ("Mauritania", Arabic) : MR +موريتانيا + +// xn--node ("ge", Georgian Mkhedruli) : GE +გე + +// xn--qxam ("el", Greek) : GR +// Hellenic Ministry of Infrastructure, Transport, and Networks +ελ + +// xn--j6w193g ("Hong Kong", Chinese) : HK +// https://www.hkirc.hk +// Submitted by registry +// https://www.hkirc.hk/content.jsp?id=30#!/34 +香港 +個人.香港 +公司.香港 +政府.香港 +教育.香港 +組織.香港 +網絡.香港 + +// xn--2scrj9c ("Bharat", Kannada) : IN +// India +ಭಾರತ + +// xn--3hcrj9c ("Bharat", Oriya) : IN +// India +ଭାରତ + +// xn--45br5cyl ("Bharatam", Assamese) : IN +// India +ভাৰত + +// xn--h2breg3eve ("Bharatam", Sanskrit) : IN +// India +भारतम् + +// xn--h2brj9c8c ("Bharot", Santali) : IN +// India +भारोत + +// xn--mgbgu82a ("Bharat", Sindhi) : IN +// India +ڀارت + +// xn--rvc1e0am3e ("Bharatam", Malayalam) : IN +// India +ഭാരതം + +// xn--h2brj9c ("Bharat", Devanagari) : IN +// India +भारत + +// xn--mgbbh1a ("Bharat", Kashmiri) : IN +// India +بارت + +// xn--mgbbh1a71e ("Bharat", Arabic) : IN +// India +بھارت + +// xn--fpcrj9c3d ("Bharat", Telugu) : IN +// India +భారత్ + +// xn--gecrj9c ("Bharat", Gujarati) : IN +// India +ભારત + +// xn--s9brj9c ("Bharat", Gurmukhi) : IN +// India +ਭਾਰਤ + +// xn--45brj9c ("Bharat", Bengali) : IN +// India +ভারত + +// xn--xkc2dl3a5ee0h ("India", Tamil) : IN +// India +இந்தியா + +// xn--mgba3a4f16a ("Iran", Persian) : IR +ایران + +// xn--mgba3a4fra ("Iran", Arabic) : IR +ايران + +// xn--mgbtx2b ("Iraq", Arabic) : IQ +// Communications and Media Commission +عراق + +// xn--mgbayh7gpa ("al-Ordon", Arabic) : JO +// National Information Technology Center (NITC) +// Royal Scientific Society, Al-Jubeiha +الاردن + +// xn--3e0b707e ("Republic of Korea", Hangul) : KR +한국 + +// xn--80ao21a ("Kaz", Kazakh) : KZ +қаз + +// xn--q7ce6a ("Lao", Lao) : LA +ລາວ + +// xn--fzc2c9e2c ("Lanka", Sinhalese-Sinhala) : LK +// https://nic.lk +ලංකා + +// xn--xkc2al3hye2a ("Ilangai", Tamil) : LK +// https://nic.lk +இலங்கை + +// xn--mgbc0a9azcg ("Morocco/al-Maghrib", Arabic) : MA +المغرب + +// xn--d1alf ("mkd", Macedonian) : MK +// MARnet +мкд + +// xn--l1acc ("mon", Mongolian) : MN +мон + +// xn--mix891f ("Macao", Chinese, Traditional) : MO +// MONIC / HNET Asia (Registry Operator for .mo) +澳門 + +// xn--mix082f ("Macao", Chinese, Simplified) : MO +澳门 + +// xn--mgbx4cd0ab ("Malaysia", Malay) : MY +مليسيا + +// xn--mgb9awbf ("Oman", Arabic) : OM +عمان + +// xn--mgbai9azgqp6j ("Pakistan", Urdu/Arabic) : PK +پاکستان + +// xn--mgbai9a5eva00b ("Pakistan", Urdu/Arabic, variant) : PK +پاكستان + +// xn--ygbi2ammx ("Falasteen", Arabic) : PS +// The Palestinian National Internet Naming Authority (PNINA) +// http://www.pnina.ps +فلسطين + +// xn--90a3ac ("srb", Cyrillic) : RS +// https://www.rnids.rs/en/domains/national-domains +срб +ак.срб +обр.срб +од.срб +орг.срб +пр.срб +упр.срб + +// xn--p1ai ("rf", Russian-Cyrillic) : RU +// https://cctld.ru/files/pdf/docs/en/rules_ru-rf.pdf +// Submitted by George Georgievsky +рф + +// xn--wgbl6a ("Qatar", Arabic) : QA +// http://www.ict.gov.qa/ +قطر + +// xn--mgberp4a5d4ar ("AlSaudiah", Arabic) : SA +// http://www.nic.net.sa/ +السعودية + +// xn--mgberp4a5d4a87g ("AlSaudiah", Arabic, variant): SA +السعودیة + +// xn--mgbqly7c0a67fbc ("AlSaudiah", Arabic, variant) : SA +السعودیۃ + +// xn--mgbqly7cvafr ("AlSaudiah", Arabic, variant) : SA +السعوديه + +// xn--mgbpl2fh ("sudan", Arabic) : SD +// Operated by .sd registry +سودان + +// xn--yfro4i67o Singapore ("Singapore", Chinese) : SG +新加坡 + +// xn--clchc0ea0b2g2a9gcd ("Singapore", Tamil) : SG +சிங்கப்பூர் + +// xn--ogbpf8fl ("Syria", Arabic) : SY +سورية + +// xn--mgbtf8fl ("Syria", Arabic, variant) : SY +سوريا + +// xn--o3cw4h ("Thai", Thai) : TH +// http://www.thnic.co.th +ไทย +ทหาร.ไทย +ธุรกิจ.ไทย +เน็ต.ไทย +รัฐบาล.ไทย +ศึกษา.ไทย +องค์กร.ไทย + +// xn--pgbs0dh ("Tunisia", Arabic) : TN +// http://nic.tn +تونس + +// xn--kpry57d ("Taiwan", Chinese, Traditional) : TW +// https://twnic.tw/dnservice_catag.php +台灣 + +// xn--kprw13d ("Taiwan", Chinese, Simplified) : TW +// http://www.twnic.net/english/dn/dn_07a.htm +台湾 + +// xn--nnx388a ("Taiwan", Chinese, variant) : TW +臺灣 + +// xn--j1amh ("ukr", Cyrillic) : UA +укр + +// xn--mgb2ddes ("AlYemen", Arabic) : YE +اليمن + +// xxx : http://icmregistry.com +xxx + +// ye : http://www.y.net.ye/services/domain_name.htm +ye +com.ye +edu.ye +gov.ye +mil.ye +net.ye +org.ye + +// za : https://www.iana.org/domains/root/db/za.html +ac.za +agric.za +alt.za +co.za +edu.za +gov.za +grondar.za +law.za +mil.za +net.za +ngo.za +nic.za +nis.za +nom.za +org.za +school.za +tm.za +web.za + +// zm : https://zicta.zm/ +// Submitted by registry +zm +ac.zm +biz.zm +co.zm +com.zm +edu.zm +gov.zm +info.zm +mil.zm +net.zm +org.zm +sch.zm + +// zw : https://www.potraz.gov.zw/ +// Confirmed by registry 2017-01-25 +zw +ac.zw +co.zw +gov.zw +mil.zw +org.zw + +// newGTLDs + +// List of new gTLDs imported from https://www.icann.org/resources/registries/gtlds/v2/gtlds.json on 2025-10-01T15:18:26Z +// This list is auto-generated, don't edit it manually. +// aaa : American Automobile Association, Inc. +// https://www.iana.org/domains/root/db/aaa.html +aaa + +// aarp : AARP +// https://www.iana.org/domains/root/db/aarp.html +aarp + +// abb : ABB Ltd +// https://www.iana.org/domains/root/db/abb.html +abb + +// abbott : Abbott Laboratories, Inc. +// https://www.iana.org/domains/root/db/abbott.html +abbott + +// abbvie : AbbVie Inc. +// https://www.iana.org/domains/root/db/abbvie.html +abbvie + +// abc : Disney Enterprises, Inc. +// https://www.iana.org/domains/root/db/abc.html +abc + +// able : Able Inc. +// https://www.iana.org/domains/root/db/able.html +able + +// abogado : Registry Services, LLC +// https://www.iana.org/domains/root/db/abogado.html +abogado + +// abudhabi : Abu Dhabi Systems and Information Centre +// https://www.iana.org/domains/root/db/abudhabi.html +abudhabi + +// academy : Binky Moon, LLC +// https://www.iana.org/domains/root/db/academy.html +academy + +// accenture : Accenture plc +// https://www.iana.org/domains/root/db/accenture.html +accenture + +// accountant : dot Accountant Limited +// https://www.iana.org/domains/root/db/accountant.html +accountant + +// accountants : Binky Moon, LLC +// https://www.iana.org/domains/root/db/accountants.html +accountants + +// aco : ACO Severin Ahlmann GmbH & Co. KG +// https://www.iana.org/domains/root/db/aco.html +aco + +// actor : Dog Beach, LLC +// https://www.iana.org/domains/root/db/actor.html +actor + +// ads : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/ads.html +ads + +// adult : ICM Registry AD LLC +// https://www.iana.org/domains/root/db/adult.html +adult + +// aeg : Aktiebolaget Electrolux +// https://www.iana.org/domains/root/db/aeg.html +aeg + +// aetna : Aetna Life Insurance Company +// https://www.iana.org/domains/root/db/aetna.html +aetna + +// afl : Australian Football League +// https://www.iana.org/domains/root/db/afl.html +afl + +// africa : ZA Central Registry NPC trading as Registry.Africa +// https://www.iana.org/domains/root/db/africa.html +africa + +// agakhan : Fondation Aga Khan (Aga Khan Foundation) +// https://www.iana.org/domains/root/db/agakhan.html +agakhan + +// agency : Binky Moon, LLC +// https://www.iana.org/domains/root/db/agency.html +agency + +// aig : American International Group, Inc. +// https://www.iana.org/domains/root/db/aig.html +aig + +// airbus : Airbus S.A.S. +// https://www.iana.org/domains/root/db/airbus.html +airbus + +// airforce : Dog Beach, LLC +// https://www.iana.org/domains/root/db/airforce.html +airforce + +// airtel : Bharti Airtel Limited +// https://www.iana.org/domains/root/db/airtel.html +airtel + +// akdn : Fondation Aga Khan (Aga Khan Foundation) +// https://www.iana.org/domains/root/db/akdn.html +akdn + +// alibaba : Alibaba Group Holding Limited +// https://www.iana.org/domains/root/db/alibaba.html +alibaba + +// alipay : Alibaba Group Holding Limited +// https://www.iana.org/domains/root/db/alipay.html +alipay + +// allfinanz : Allfinanz Deutsche Vermögensberatung Aktiengesellschaft +// https://www.iana.org/domains/root/db/allfinanz.html +allfinanz + +// allstate : Allstate Fire and Casualty Insurance Company +// https://www.iana.org/domains/root/db/allstate.html +allstate + +// ally : Ally Financial Inc. +// https://www.iana.org/domains/root/db/ally.html +ally + +// alsace : Region Grand Est +// https://www.iana.org/domains/root/db/alsace.html +alsace + +// alstom : ALSTOM +// https://www.iana.org/domains/root/db/alstom.html +alstom + +// amazon : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/amazon.html +amazon + +// americanexpress : American Express Travel Related Services Company, Inc. +// https://www.iana.org/domains/root/db/americanexpress.html +americanexpress + +// americanfamily : AmFam, Inc. +// https://www.iana.org/domains/root/db/americanfamily.html +americanfamily + +// amex : American Express Travel Related Services Company, Inc. +// https://www.iana.org/domains/root/db/amex.html +amex + +// amfam : AmFam, Inc. +// https://www.iana.org/domains/root/db/amfam.html +amfam + +// amica : Amica Mutual Insurance Company +// https://www.iana.org/domains/root/db/amica.html +amica + +// amsterdam : Gemeente Amsterdam +// https://www.iana.org/domains/root/db/amsterdam.html +amsterdam + +// analytics : Campus IP LLC +// https://www.iana.org/domains/root/db/analytics.html +analytics + +// android : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/android.html +android + +// anquan : Beijing Qihu Keji Co., Ltd. +// https://www.iana.org/domains/root/db/anquan.html +anquan + +// anz : Australia and New Zealand Banking Group Limited +// https://www.iana.org/domains/root/db/anz.html +anz + +// aol : Yahoo Inc. +// https://www.iana.org/domains/root/db/aol.html +aol + +// apartments : Binky Moon, LLC +// https://www.iana.org/domains/root/db/apartments.html +apartments + +// app : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/app.html +app + +// apple : Apple Inc. +// https://www.iana.org/domains/root/db/apple.html +apple + +// aquarelle : Aquarelle.com +// https://www.iana.org/domains/root/db/aquarelle.html +aquarelle + +// arab : League of Arab States +// https://www.iana.org/domains/root/db/arab.html +arab + +// aramco : Aramco Services Company +// https://www.iana.org/domains/root/db/aramco.html +aramco + +// archi : Identity Digital Domains Limited +// https://www.iana.org/domains/root/db/archi.html +archi + +// army : Dog Beach, LLC +// https://www.iana.org/domains/root/db/army.html +army + +// art : UK Creative Ideas Limited +// https://www.iana.org/domains/root/db/art.html +art + +// arte : Association Relative à la Télévision Européenne G.E.I.E. +// https://www.iana.org/domains/root/db/arte.html +arte + +// asda : Asda Stores Limited +// https://www.iana.org/domains/root/db/asda.html +asda + +// associates : Binky Moon, LLC +// https://www.iana.org/domains/root/db/associates.html +associates + +// athleta : The Gap, Inc. +// https://www.iana.org/domains/root/db/athleta.html +athleta + +// attorney : Dog Beach, LLC +// https://www.iana.org/domains/root/db/attorney.html +attorney + +// auction : Dog Beach, LLC +// https://www.iana.org/domains/root/db/auction.html +auction + +// audi : AUDI Aktiengesellschaft +// https://www.iana.org/domains/root/db/audi.html +audi + +// audible : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/audible.html +audible + +// audio : XYZ.COM LLC +// https://www.iana.org/domains/root/db/audio.html +audio + +// auspost : Australian Postal Corporation +// https://www.iana.org/domains/root/db/auspost.html +auspost + +// author : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/author.html +author + +// auto : XYZ.COM LLC +// https://www.iana.org/domains/root/db/auto.html +auto + +// autos : XYZ.COM LLC +// https://www.iana.org/domains/root/db/autos.html +autos + +// aws : AWS Registry LLC +// https://www.iana.org/domains/root/db/aws.html +aws + +// axa : AXA Group Operations SAS +// https://www.iana.org/domains/root/db/axa.html +axa + +// azure : Microsoft Corporation +// https://www.iana.org/domains/root/db/azure.html +azure + +// baby : XYZ.COM LLC +// https://www.iana.org/domains/root/db/baby.html +baby + +// baidu : Baidu, Inc. +// https://www.iana.org/domains/root/db/baidu.html +baidu + +// banamex : Citigroup Inc. +// https://www.iana.org/domains/root/db/banamex.html +banamex + +// band : Dog Beach, LLC +// https://www.iana.org/domains/root/db/band.html +band + +// bank : fTLD Registry Services LLC +// https://www.iana.org/domains/root/db/bank.html +bank + +// bar : Punto 2012 Sociedad Anonima Promotora de Inversion de Capital Variable +// https://www.iana.org/domains/root/db/bar.html +bar + +// barcelona : Municipi de Barcelona +// https://www.iana.org/domains/root/db/barcelona.html +barcelona + +// barclaycard : Barclays Bank PLC +// https://www.iana.org/domains/root/db/barclaycard.html +barclaycard + +// barclays : Barclays Bank PLC +// https://www.iana.org/domains/root/db/barclays.html +barclays + +// barefoot : Gallo Vineyards, Inc. +// https://www.iana.org/domains/root/db/barefoot.html +barefoot + +// bargains : Binky Moon, LLC +// https://www.iana.org/domains/root/db/bargains.html +bargains + +// baseball : MLB Advanced Media DH, LLC +// https://www.iana.org/domains/root/db/baseball.html +baseball + +// basketball : Fédération Internationale de Basketball (FIBA) +// https://www.iana.org/domains/root/db/basketball.html +basketball + +// bauhaus : Werkhaus GmbH +// https://www.iana.org/domains/root/db/bauhaus.html +bauhaus + +// bayern : Bayern Connect GmbH +// https://www.iana.org/domains/root/db/bayern.html +bayern + +// bbc : British Broadcasting Corporation +// https://www.iana.org/domains/root/db/bbc.html +bbc + +// bbt : BB&T Corporation +// https://www.iana.org/domains/root/db/bbt.html +bbt + +// bbva : BANCO BILBAO VIZCAYA ARGENTARIA, S.A. +// https://www.iana.org/domains/root/db/bbva.html +bbva + +// bcg : The Boston Consulting Group, Inc. +// https://www.iana.org/domains/root/db/bcg.html +bcg + +// bcn : Municipi de Barcelona +// https://www.iana.org/domains/root/db/bcn.html +bcn + +// beats : Beats Electronics, LLC +// https://www.iana.org/domains/root/db/beats.html +beats + +// beauty : XYZ.COM LLC +// https://www.iana.org/domains/root/db/beauty.html +beauty + +// beer : Registry Services, LLC +// https://www.iana.org/domains/root/db/beer.html +beer + +// berlin : dotBERLIN GmbH & Co. KG +// https://www.iana.org/domains/root/db/berlin.html +berlin + +// best : BestTLD Pty Ltd +// https://www.iana.org/domains/root/db/best.html +best + +// bestbuy : BBY Solutions, Inc. +// https://www.iana.org/domains/root/db/bestbuy.html +bestbuy + +// bet : Identity Digital Domains Limited +// https://www.iana.org/domains/root/db/bet.html +bet + +// bharti : Bharti Enterprises (Holding) Private Limited +// https://www.iana.org/domains/root/db/bharti.html +bharti + +// bible : American Bible Society +// https://www.iana.org/domains/root/db/bible.html +bible + +// bid : dot Bid Limited +// https://www.iana.org/domains/root/db/bid.html +bid + +// bike : Binky Moon, LLC +// https://www.iana.org/domains/root/db/bike.html +bike + +// bing : Microsoft Corporation +// https://www.iana.org/domains/root/db/bing.html +bing + +// bingo : Binky Moon, LLC +// https://www.iana.org/domains/root/db/bingo.html +bingo + +// bio : Identity Digital Domains Limited +// https://www.iana.org/domains/root/db/bio.html +bio + +// black : Identity Digital Domains Limited +// https://www.iana.org/domains/root/db/black.html +black + +// blackfriday : Registry Services, LLC +// https://www.iana.org/domains/root/db/blackfriday.html +blackfriday + +// blockbuster : Dish DBS Corporation +// https://www.iana.org/domains/root/db/blockbuster.html +blockbuster + +// blog : Knock Knock WHOIS There, LLC +// https://www.iana.org/domains/root/db/blog.html +blog + +// bloomberg : Bloomberg IP Holdings LLC +// https://www.iana.org/domains/root/db/bloomberg.html +bloomberg + +// blue : Identity Digital Domains Limited +// https://www.iana.org/domains/root/db/blue.html +blue + +// bms : Bristol-Myers Squibb Company +// https://www.iana.org/domains/root/db/bms.html +bms + +// bmw : Bayerische Motoren Werke Aktiengesellschaft +// https://www.iana.org/domains/root/db/bmw.html +bmw + +// bnpparibas : BNP Paribas +// https://www.iana.org/domains/root/db/bnpparibas.html +bnpparibas + +// boats : XYZ.COM LLC +// https://www.iana.org/domains/root/db/boats.html +boats + +// boehringer : Boehringer Ingelheim International GmbH +// https://www.iana.org/domains/root/db/boehringer.html +boehringer + +// bofa : Bank of America Corporation +// https://www.iana.org/domains/root/db/bofa.html +bofa + +// bom : Núcleo de Informação e Coordenação do Ponto BR - NIC.br +// https://www.iana.org/domains/root/db/bom.html +bom + +// bond : ShortDot SA +// https://www.iana.org/domains/root/db/bond.html +bond + +// boo : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/boo.html +boo + +// book : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/book.html +book + +// booking : Booking.com B.V. +// https://www.iana.org/domains/root/db/booking.html +booking + +// bosch : Robert Bosch GMBH +// https://www.iana.org/domains/root/db/bosch.html +bosch + +// bostik : Bostik SA +// https://www.iana.org/domains/root/db/bostik.html +bostik + +// boston : Registry Services, LLC +// https://www.iana.org/domains/root/db/boston.html +boston + +// bot : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/bot.html +bot + +// boutique : Binky Moon, LLC +// https://www.iana.org/domains/root/db/boutique.html +boutique + +// box : Intercap Registry Inc. +// https://www.iana.org/domains/root/db/box.html +box + +// bradesco : Banco Bradesco S.A. +// https://www.iana.org/domains/root/db/bradesco.html +bradesco + +// bridgestone : Bridgestone Corporation +// https://www.iana.org/domains/root/db/bridgestone.html +bridgestone + +// broadway : Celebrate Broadway, Inc. +// https://www.iana.org/domains/root/db/broadway.html +broadway + +// broker : Dog Beach, LLC +// https://www.iana.org/domains/root/db/broker.html +broker + +// brother : Brother Industries, Ltd. +// https://www.iana.org/domains/root/db/brother.html +brother + +// brussels : DNS.be vzw +// https://www.iana.org/domains/root/db/brussels.html +brussels + +// build : Plan Bee LLC +// https://www.iana.org/domains/root/db/build.html +build + +// builders : Binky Moon, LLC +// https://www.iana.org/domains/root/db/builders.html +builders + +// business : Binky Moon, LLC +// https://www.iana.org/domains/root/db/business.html +business + +// buy : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/buy.html +buy + +// buzz : DOTSTRATEGY CO. +// https://www.iana.org/domains/root/db/buzz.html +buzz + +// bzh : Association www.bzh +// https://www.iana.org/domains/root/db/bzh.html +bzh + +// cab : Binky Moon, LLC +// https://www.iana.org/domains/root/db/cab.html +cab + +// cafe : Binky Moon, LLC +// https://www.iana.org/domains/root/db/cafe.html +cafe + +// cal : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/cal.html +cal + +// call : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/call.html +call + +// calvinklein : PVH gTLD Holdings LLC +// https://www.iana.org/domains/root/db/calvinklein.html +calvinklein + +// cam : Cam Connecting SARL +// https://www.iana.org/domains/root/db/cam.html +cam + +// camera : Binky Moon, LLC +// https://www.iana.org/domains/root/db/camera.html +camera + +// camp : Binky Moon, LLC +// https://www.iana.org/domains/root/db/camp.html +camp + +// canon : Canon Inc. +// https://www.iana.org/domains/root/db/canon.html +canon + +// capetown : ZA Central Registry NPC trading as ZA Central Registry +// https://www.iana.org/domains/root/db/capetown.html +capetown + +// capital : Binky Moon, LLC +// https://www.iana.org/domains/root/db/capital.html +capital + +// capitalone : Capital One Financial Corporation +// https://www.iana.org/domains/root/db/capitalone.html +capitalone + +// car : XYZ.COM LLC +// https://www.iana.org/domains/root/db/car.html +car + +// caravan : Caravan International, Inc. +// https://www.iana.org/domains/root/db/caravan.html +caravan + +// cards : Binky Moon, LLC +// https://www.iana.org/domains/root/db/cards.html +cards + +// care : Binky Moon, LLC +// https://www.iana.org/domains/root/db/care.html +care + +// career : dotCareer LLC +// https://www.iana.org/domains/root/db/career.html +career + +// careers : Binky Moon, LLC +// https://www.iana.org/domains/root/db/careers.html +careers + +// cars : XYZ.COM LLC +// https://www.iana.org/domains/root/db/cars.html +cars + +// casa : Registry Services, LLC +// https://www.iana.org/domains/root/db/casa.html +casa + +// case : Digity, LLC +// https://www.iana.org/domains/root/db/case.html +case + +// cash : Binky Moon, LLC +// https://www.iana.org/domains/root/db/cash.html +cash + +// casino : Binky Moon, LLC +// https://www.iana.org/domains/root/db/casino.html +casino + +// catering : Binky Moon, LLC +// https://www.iana.org/domains/root/db/catering.html +catering + +// catholic : Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication) +// https://www.iana.org/domains/root/db/catholic.html +catholic + +// cba : COMMONWEALTH BANK OF AUSTRALIA +// https://www.iana.org/domains/root/db/cba.html +cba + +// cbn : The Christian Broadcasting Network, Inc. +// https://www.iana.org/domains/root/db/cbn.html +cbn + +// cbre : CBRE, Inc. +// https://www.iana.org/domains/root/db/cbre.html +cbre + +// center : Binky Moon, LLC +// https://www.iana.org/domains/root/db/center.html +center + +// ceo : XYZ.COM LLC +// https://www.iana.org/domains/root/db/ceo.html +ceo + +// cern : European Organization for Nuclear Research ("CERN") +// https://www.iana.org/domains/root/db/cern.html +cern + +// cfa : CFA Institute +// https://www.iana.org/domains/root/db/cfa.html +cfa + +// cfd : ShortDot SA +// https://www.iana.org/domains/root/db/cfd.html +cfd + +// chanel : Chanel International B.V. +// https://www.iana.org/domains/root/db/chanel.html +chanel + +// channel : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/channel.html +channel + +// charity : Public Interest Registry +// https://www.iana.org/domains/root/db/charity.html +charity + +// chase : JPMorgan Chase Bank, National Association +// https://www.iana.org/domains/root/db/chase.html +chase + +// chat : Binky Moon, LLC +// https://www.iana.org/domains/root/db/chat.html +chat + +// cheap : Binky Moon, LLC +// https://www.iana.org/domains/root/db/cheap.html +cheap + +// chintai : CHINTAI Corporation +// https://www.iana.org/domains/root/db/chintai.html +chintai + +// christmas : XYZ.COM LLC +// https://www.iana.org/domains/root/db/christmas.html +christmas + +// chrome : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/chrome.html +chrome + +// church : Binky Moon, LLC +// https://www.iana.org/domains/root/db/church.html +church + +// cipriani : Hotel Cipriani Srl +// https://www.iana.org/domains/root/db/cipriani.html +cipriani + +// circle : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/circle.html +circle + +// cisco : Cisco Technology, Inc. +// https://www.iana.org/domains/root/db/cisco.html +cisco + +// citadel : Citadel Domain LLC +// https://www.iana.org/domains/root/db/citadel.html +citadel + +// citi : Citigroup Inc. +// https://www.iana.org/domains/root/db/citi.html +citi + +// citic : CITIC Group Corporation +// https://www.iana.org/domains/root/db/citic.html +citic + +// city : Binky Moon, LLC +// https://www.iana.org/domains/root/db/city.html +city + +// claims : Binky Moon, LLC +// https://www.iana.org/domains/root/db/claims.html +claims + +// cleaning : Binky Moon, LLC +// https://www.iana.org/domains/root/db/cleaning.html +cleaning + +// click : Waterford Limited +// https://www.iana.org/domains/root/db/click.html +click + +// clinic : Binky Moon, LLC +// https://www.iana.org/domains/root/db/clinic.html +clinic + +// clinique : The Estée Lauder Companies Inc. +// https://www.iana.org/domains/root/db/clinique.html +clinique + +// clothing : Binky Moon, LLC +// https://www.iana.org/domains/root/db/clothing.html +clothing + +// cloud : Aruba PEC S.p.A. +// https://www.iana.org/domains/root/db/cloud.html +cloud + +// club : Registry Services, LLC +// https://www.iana.org/domains/root/db/club.html +club + +// clubmed : Club Méditerranée S.A. +// https://www.iana.org/domains/root/db/clubmed.html +clubmed + +// coach : Binky Moon, LLC +// https://www.iana.org/domains/root/db/coach.html +coach + +// codes : Binky Moon, LLC +// https://www.iana.org/domains/root/db/codes.html +codes + +// coffee : Binky Moon, LLC +// https://www.iana.org/domains/root/db/coffee.html +coffee + +// college : XYZ.COM LLC +// https://www.iana.org/domains/root/db/college.html +college + +// cologne : dotKoeln GmbH +// https://www.iana.org/domains/root/db/cologne.html +cologne + +// commbank : COMMONWEALTH BANK OF AUSTRALIA +// https://www.iana.org/domains/root/db/commbank.html +commbank + +// community : Binky Moon, LLC +// https://www.iana.org/domains/root/db/community.html +community + +// company : Binky Moon, LLC +// https://www.iana.org/domains/root/db/company.html +company + +// compare : Registry Services, LLC +// https://www.iana.org/domains/root/db/compare.html +compare + +// computer : Binky Moon, LLC +// https://www.iana.org/domains/root/db/computer.html +computer + +// comsec : VeriSign, Inc. +// https://www.iana.org/domains/root/db/comsec.html +comsec + +// condos : Binky Moon, LLC +// https://www.iana.org/domains/root/db/condos.html +condos + +// construction : Binky Moon, LLC +// https://www.iana.org/domains/root/db/construction.html +construction + +// consulting : Dog Beach, LLC +// https://www.iana.org/domains/root/db/consulting.html +consulting + +// contact : Dog Beach, LLC +// https://www.iana.org/domains/root/db/contact.html +contact + +// contractors : Binky Moon, LLC +// https://www.iana.org/domains/root/db/contractors.html +contractors + +// cooking : Registry Services, LLC +// https://www.iana.org/domains/root/db/cooking.html +cooking + +// cool : Binky Moon, LLC +// https://www.iana.org/domains/root/db/cool.html +cool + +// corsica : Collectivité de Corse +// https://www.iana.org/domains/root/db/corsica.html +corsica + +// country : Internet Naming Company LLC +// https://www.iana.org/domains/root/db/country.html +country + +// coupon : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/coupon.html +coupon + +// coupons : Binky Moon, LLC +// https://www.iana.org/domains/root/db/coupons.html +coupons + +// courses : Registry Services, LLC +// https://www.iana.org/domains/root/db/courses.html +courses + +// cpa : American Institute of Certified Public Accountants +// https://www.iana.org/domains/root/db/cpa.html +cpa + +// credit : Binky Moon, LLC +// https://www.iana.org/domains/root/db/credit.html +credit + +// creditcard : Binky Moon, LLC +// https://www.iana.org/domains/root/db/creditcard.html +creditcard + +// creditunion : DotCooperation LLC +// https://www.iana.org/domains/root/db/creditunion.html +creditunion + +// cricket : dot Cricket Limited +// https://www.iana.org/domains/root/db/cricket.html +cricket + +// crown : Crown Equipment Corporation +// https://www.iana.org/domains/root/db/crown.html +crown + +// crs : Federated Co-operatives Limited +// https://www.iana.org/domains/root/db/crs.html +crs + +// cruise : Viking River Cruises (Bermuda) Ltd. +// https://www.iana.org/domains/root/db/cruise.html +cruise + +// cruises : Binky Moon, LLC +// https://www.iana.org/domains/root/db/cruises.html +cruises + +// cuisinella : SCHMIDT GROUPE S.A.S. +// https://www.iana.org/domains/root/db/cuisinella.html +cuisinella + +// cymru : Nominet UK +// https://www.iana.org/domains/root/db/cymru.html +cymru + +// cyou : ShortDot SA +// https://www.iana.org/domains/root/db/cyou.html +cyou + +// dad : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/dad.html +dad + +// dance : Dog Beach, LLC +// https://www.iana.org/domains/root/db/dance.html +dance + +// data : Dish DBS Corporation +// https://www.iana.org/domains/root/db/data.html +data + +// date : dot Date Limited +// https://www.iana.org/domains/root/db/date.html +date + +// dating : Binky Moon, LLC +// https://www.iana.org/domains/root/db/dating.html +dating + +// datsun : NISSAN MOTOR CO., LTD. +// https://www.iana.org/domains/root/db/datsun.html +datsun + +// day : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/day.html +day + +// dclk : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/dclk.html +dclk + +// dds : Registry Services, LLC +// https://www.iana.org/domains/root/db/dds.html +dds + +// deal : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/deal.html +deal + +// dealer : Intercap Registry Inc. +// https://www.iana.org/domains/root/db/dealer.html +dealer + +// deals : Binky Moon, LLC +// https://www.iana.org/domains/root/db/deals.html +deals + +// degree : Dog Beach, LLC +// https://www.iana.org/domains/root/db/degree.html +degree + +// delivery : Binky Moon, LLC +// https://www.iana.org/domains/root/db/delivery.html +delivery + +// dell : Dell Inc. +// https://www.iana.org/domains/root/db/dell.html +dell + +// deloitte : Deloitte Touche Tohmatsu +// https://www.iana.org/domains/root/db/deloitte.html +deloitte + +// delta : Delta Air Lines, Inc. +// https://www.iana.org/domains/root/db/delta.html +delta + +// democrat : Dog Beach, LLC +// https://www.iana.org/domains/root/db/democrat.html +democrat + +// dental : Binky Moon, LLC +// https://www.iana.org/domains/root/db/dental.html +dental + +// dentist : Dog Beach, LLC +// https://www.iana.org/domains/root/db/dentist.html +dentist + +// desi +// https://www.iana.org/domains/root/db/desi.html +desi + +// design : Registry Services, LLC +// https://www.iana.org/domains/root/db/design.html +design + +// dev : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/dev.html +dev + +// dhl : Deutsche Post AG +// https://www.iana.org/domains/root/db/dhl.html +dhl + +// diamonds : Binky Moon, LLC +// https://www.iana.org/domains/root/db/diamonds.html +diamonds + +// diet : XYZ.COM LLC +// https://www.iana.org/domains/root/db/diet.html +diet + +// digital : Binky Moon, LLC +// https://www.iana.org/domains/root/db/digital.html +digital + +// direct : Binky Moon, LLC +// https://www.iana.org/domains/root/db/direct.html +direct + +// directory : Binky Moon, LLC +// https://www.iana.org/domains/root/db/directory.html +directory + +// discount : Binky Moon, LLC +// https://www.iana.org/domains/root/db/discount.html +discount + +// discover : Discover Financial Services +// https://www.iana.org/domains/root/db/discover.html +discover + +// dish : Dish DBS Corporation +// https://www.iana.org/domains/root/db/dish.html +dish + +// diy : Internet Naming Company LLC +// https://www.iana.org/domains/root/db/diy.html +diy + +// dnp : Dai Nippon Printing Co., Ltd. +// https://www.iana.org/domains/root/db/dnp.html +dnp + +// docs : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/docs.html +docs + +// doctor : Binky Moon, LLC +// https://www.iana.org/domains/root/db/doctor.html +doctor + +// dog : Binky Moon, LLC +// https://www.iana.org/domains/root/db/dog.html +dog + +// domains : Binky Moon, LLC +// https://www.iana.org/domains/root/db/domains.html +domains + +// dot : Dish DBS Corporation +// https://www.iana.org/domains/root/db/dot.html +dot + +// download : dot Support Limited +// https://www.iana.org/domains/root/db/download.html +download + +// drive : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/drive.html +drive + +// dtv : Dish DBS Corporation +// https://www.iana.org/domains/root/db/dtv.html +dtv + +// dubai : Dubai Smart Government Department +// https://www.iana.org/domains/root/db/dubai.html +dubai + +// dunlop : The Goodyear Tire & Rubber Company +// https://www.iana.org/domains/root/db/dunlop.html +dunlop + +// dupont : DuPont Specialty Products USA, LLC +// https://www.iana.org/domains/root/db/dupont.html +dupont + +// durban : ZA Central Registry NPC trading as ZA Central Registry +// https://www.iana.org/domains/root/db/durban.html +durban + +// dvag : Deutsche Vermögensberatung Aktiengesellschaft DVAG +// https://www.iana.org/domains/root/db/dvag.html +dvag + +// dvr : DISH Technologies L.L.C. +// https://www.iana.org/domains/root/db/dvr.html +dvr + +// earth : Interlink Systems Innovation Institute K.K. +// https://www.iana.org/domains/root/db/earth.html +earth + +// eat : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/eat.html +eat + +// eco : Big Room Inc. +// https://www.iana.org/domains/root/db/eco.html +eco + +// edeka : EDEKA Verband kaufmännischer Genossenschaften e.V. +// https://www.iana.org/domains/root/db/edeka.html +edeka + +// education : Binky Moon, LLC +// https://www.iana.org/domains/root/db/education.html +education + +// email : Binky Moon, LLC +// https://www.iana.org/domains/root/db/email.html +email + +// emerck : Merck KGaA +// https://www.iana.org/domains/root/db/emerck.html +emerck + +// energy : Binky Moon, LLC +// https://www.iana.org/domains/root/db/energy.html +energy + +// engineer : Dog Beach, LLC +// https://www.iana.org/domains/root/db/engineer.html +engineer + +// engineering : Binky Moon, LLC +// https://www.iana.org/domains/root/db/engineering.html +engineering + +// enterprises : Binky Moon, LLC +// https://www.iana.org/domains/root/db/enterprises.html +enterprises + +// epson : Seiko Epson Corporation +// https://www.iana.org/domains/root/db/epson.html +epson + +// equipment : Binky Moon, LLC +// https://www.iana.org/domains/root/db/equipment.html +equipment + +// ericsson : Telefonaktiebolaget L M Ericsson +// https://www.iana.org/domains/root/db/ericsson.html +ericsson + +// erni : ERNI Group Holding AG +// https://www.iana.org/domains/root/db/erni.html +erni + +// esq : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/esq.html +esq + +// estate : Binky Moon, LLC +// https://www.iana.org/domains/root/db/estate.html +estate + +// eurovision : European Broadcasting Union (EBU) +// https://www.iana.org/domains/root/db/eurovision.html +eurovision + +// eus : Puntueus Fundazioa +// https://www.iana.org/domains/root/db/eus.html +eus + +// events : Binky Moon, LLC +// https://www.iana.org/domains/root/db/events.html +events + +// exchange : Binky Moon, LLC +// https://www.iana.org/domains/root/db/exchange.html +exchange + +// expert : Binky Moon, LLC +// https://www.iana.org/domains/root/db/expert.html +expert + +// exposed : Binky Moon, LLC +// https://www.iana.org/domains/root/db/exposed.html +exposed + +// express : Binky Moon, LLC +// https://www.iana.org/domains/root/db/express.html +express + +// extraspace : Extra Space Storage LLC +// https://www.iana.org/domains/root/db/extraspace.html +extraspace + +// fage : Fage International S.A. +// https://www.iana.org/domains/root/db/fage.html +fage + +// fail : Binky Moon, LLC +// https://www.iana.org/domains/root/db/fail.html +fail + +// fairwinds : FairWinds Partners, LLC +// https://www.iana.org/domains/root/db/fairwinds.html +fairwinds + +// faith : dot Faith Limited +// https://www.iana.org/domains/root/db/faith.html +faith + +// family : Dog Beach, LLC +// https://www.iana.org/domains/root/db/family.html +family + +// fan : Dog Beach, LLC +// https://www.iana.org/domains/root/db/fan.html +fan + +// fans : ZDNS International Limited +// https://www.iana.org/domains/root/db/fans.html +fans + +// farm : Binky Moon, LLC +// https://www.iana.org/domains/root/db/farm.html +farm + +// farmers : Farmers Insurance Exchange +// https://www.iana.org/domains/root/db/farmers.html +farmers + +// fashion : Registry Services, LLC +// https://www.iana.org/domains/root/db/fashion.html +fashion + +// fast : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/fast.html +fast + +// fedex : Federal Express Corporation +// https://www.iana.org/domains/root/db/fedex.html +fedex + +// feedback : Top Level Spectrum, Inc. +// https://www.iana.org/domains/root/db/feedback.html +feedback + +// ferrari : Fiat Chrysler Automobiles N.V. +// https://www.iana.org/domains/root/db/ferrari.html +ferrari + +// ferrero : Ferrero Trading Lux S.A. +// https://www.iana.org/domains/root/db/ferrero.html +ferrero + +// fidelity : Fidelity Brokerage Services LLC +// https://www.iana.org/domains/root/db/fidelity.html +fidelity + +// fido : Rogers Communications Canada Inc. +// https://www.iana.org/domains/root/db/fido.html +fido + +// film : Motion Picture Domain Registry Pty Ltd +// https://www.iana.org/domains/root/db/film.html +film + +// final : Núcleo de Informação e Coordenação do Ponto BR - NIC.br +// https://www.iana.org/domains/root/db/final.html +final + +// finance : Binky Moon, LLC +// https://www.iana.org/domains/root/db/finance.html +finance + +// financial : Binky Moon, LLC +// https://www.iana.org/domains/root/db/financial.html +financial + +// fire : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/fire.html +fire + +// firestone : Bridgestone Licensing Services, Inc +// https://www.iana.org/domains/root/db/firestone.html +firestone + +// firmdale : Firmdale Holdings Limited +// https://www.iana.org/domains/root/db/firmdale.html +firmdale + +// fish : Binky Moon, LLC +// https://www.iana.org/domains/root/db/fish.html +fish + +// fishing : Registry Services, LLC +// https://www.iana.org/domains/root/db/fishing.html +fishing + +// fit : Registry Services, LLC +// https://www.iana.org/domains/root/db/fit.html +fit + +// fitness : Binky Moon, LLC +// https://www.iana.org/domains/root/db/fitness.html +fitness + +// flickr : Flickr, Inc. +// https://www.iana.org/domains/root/db/flickr.html +flickr + +// flights : Binky Moon, LLC +// https://www.iana.org/domains/root/db/flights.html +flights + +// flir : FLIR Systems, Inc. +// https://www.iana.org/domains/root/db/flir.html +flir + +// florist : Binky Moon, LLC +// https://www.iana.org/domains/root/db/florist.html +florist + +// flowers : XYZ.COM LLC +// https://www.iana.org/domains/root/db/flowers.html +flowers + +// fly : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/fly.html +fly + +// foo : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/foo.html +foo + +// food : Internet Naming Company LLC +// https://www.iana.org/domains/root/db/food.html +food + +// football : Binky Moon, LLC +// https://www.iana.org/domains/root/db/football.html +football + +// ford : Ford Motor Company +// https://www.iana.org/domains/root/db/ford.html +ford + +// forex : Dog Beach, LLC +// https://www.iana.org/domains/root/db/forex.html +forex + +// forsale : Dog Beach, LLC +// https://www.iana.org/domains/root/db/forsale.html +forsale + +// forum : Waterford Limited +// https://www.iana.org/domains/root/db/forum.html +forum + +// foundation : Public Interest Registry +// https://www.iana.org/domains/root/db/foundation.html +foundation + +// fox : FOX Registry, LLC +// https://www.iana.org/domains/root/db/fox.html +fox + +// free : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/free.html +free + +// fresenius : Fresenius Immobilien-Verwaltungs-GmbH +// https://www.iana.org/domains/root/db/fresenius.html +fresenius + +// frl : FRLregistry B.V. +// https://www.iana.org/domains/root/db/frl.html +frl + +// frogans : OP3FT +// https://www.iana.org/domains/root/db/frogans.html +frogans + +// frontier : Frontier Communications Corporation +// https://www.iana.org/domains/root/db/frontier.html +frontier + +// ftr : Frontier Communications Corporation +// https://www.iana.org/domains/root/db/ftr.html +ftr + +// fujitsu : Fujitsu Limited +// https://www.iana.org/domains/root/db/fujitsu.html +fujitsu + +// fun : Radix Technologies Inc SEZC +// https://www.iana.org/domains/root/db/fun.html +fun + +// fund : Binky Moon, LLC +// https://www.iana.org/domains/root/db/fund.html +fund + +// furniture : Binky Moon, LLC +// https://www.iana.org/domains/root/db/furniture.html +furniture + +// futbol : Dog Beach, LLC +// https://www.iana.org/domains/root/db/futbol.html +futbol + +// fyi : Binky Moon, LLC +// https://www.iana.org/domains/root/db/fyi.html +fyi + +// gal : Asociación puntoGAL +// https://www.iana.org/domains/root/db/gal.html +gal + +// gallery : Binky Moon, LLC +// https://www.iana.org/domains/root/db/gallery.html +gallery + +// gallo : Gallo Vineyards, Inc. +// https://www.iana.org/domains/root/db/gallo.html +gallo + +// gallup : Gallup, Inc. +// https://www.iana.org/domains/root/db/gallup.html +gallup + +// game : XYZ.COM LLC +// https://www.iana.org/domains/root/db/game.html +game + +// games : Dog Beach, LLC +// https://www.iana.org/domains/root/db/games.html +games + +// gap : The Gap, Inc. +// https://www.iana.org/domains/root/db/gap.html +gap + +// garden : Registry Services, LLC +// https://www.iana.org/domains/root/db/garden.html +garden + +// gay : Registry Services, LLC +// https://www.iana.org/domains/root/db/gay.html +gay + +// gbiz : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/gbiz.html +gbiz + +// gdn : Joint Stock Company "Navigation-information systems" +// https://www.iana.org/domains/root/db/gdn.html +gdn + +// gea : GEA Group Aktiengesellschaft +// https://www.iana.org/domains/root/db/gea.html +gea + +// gent : Easyhost BV +// https://www.iana.org/domains/root/db/gent.html +gent + +// genting : Resorts World Inc Pte. Ltd. +// https://www.iana.org/domains/root/db/genting.html +genting + +// george : Wal-Mart Stores, Inc. +// https://www.iana.org/domains/root/db/george.html +george + +// ggee : GMO Internet, Inc. +// https://www.iana.org/domains/root/db/ggee.html +ggee + +// gift : DotGift, LLC +// https://www.iana.org/domains/root/db/gift.html +gift + +// gifts : Binky Moon, LLC +// https://www.iana.org/domains/root/db/gifts.html +gifts + +// gives : Public Interest Registry +// https://www.iana.org/domains/root/db/gives.html +gives + +// giving : Public Interest Registry +// https://www.iana.org/domains/root/db/giving.html +giving + +// glass : Binky Moon, LLC +// https://www.iana.org/domains/root/db/glass.html +glass + +// gle : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/gle.html +gle + +// global : Identity Digital Domains Limited +// https://www.iana.org/domains/root/db/global.html +global + +// globo : Globo Comunicação e Participações S.A +// https://www.iana.org/domains/root/db/globo.html +globo + +// gmail : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/gmail.html +gmail + +// gmbh : Binky Moon, LLC +// https://www.iana.org/domains/root/db/gmbh.html +gmbh + +// gmo : GMO Internet, Inc. +// https://www.iana.org/domains/root/db/gmo.html +gmo + +// gmx : 1&1 Mail & Media GmbH +// https://www.iana.org/domains/root/db/gmx.html +gmx + +// godaddy : Go Daddy East, LLC +// https://www.iana.org/domains/root/db/godaddy.html +godaddy + +// gold : Binky Moon, LLC +// https://www.iana.org/domains/root/db/gold.html +gold + +// goldpoint : YODOBASHI CAMERA CO.,LTD. +// https://www.iana.org/domains/root/db/goldpoint.html +goldpoint + +// golf : Binky Moon, LLC +// https://www.iana.org/domains/root/db/golf.html +golf + +// goo : NTT DOCOMO, INC. +// https://www.iana.org/domains/root/db/goo.html +goo + +// goodyear : The Goodyear Tire & Rubber Company +// https://www.iana.org/domains/root/db/goodyear.html +goodyear + +// goog : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/goog.html +goog + +// google : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/google.html +google + +// gop : Republican State Leadership Committee, Inc. +// https://www.iana.org/domains/root/db/gop.html +gop + +// got : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/got.html +got + +// grainger : Grainger Registry Services, LLC +// https://www.iana.org/domains/root/db/grainger.html +grainger + +// graphics : Binky Moon, LLC +// https://www.iana.org/domains/root/db/graphics.html +graphics + +// gratis : Binky Moon, LLC +// https://www.iana.org/domains/root/db/gratis.html +gratis + +// green : Identity Digital Domains Limited +// https://www.iana.org/domains/root/db/green.html +green + +// gripe : Binky Moon, LLC +// https://www.iana.org/domains/root/db/gripe.html +gripe + +// grocery : Wal-Mart Stores, Inc. +// https://www.iana.org/domains/root/db/grocery.html +grocery + +// group : Binky Moon, LLC +// https://www.iana.org/domains/root/db/group.html +group + +// gucci : Guccio Gucci S.p.a. +// https://www.iana.org/domains/root/db/gucci.html +gucci + +// guge : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/guge.html +guge + +// guide : Binky Moon, LLC +// https://www.iana.org/domains/root/db/guide.html +guide + +// guitars : XYZ.COM LLC +// https://www.iana.org/domains/root/db/guitars.html +guitars + +// guru : Binky Moon, LLC +// https://www.iana.org/domains/root/db/guru.html +guru + +// hair : XYZ.COM LLC +// https://www.iana.org/domains/root/db/hair.html +hair + +// hamburg : Hamburg Top-Level-Domain GmbH +// https://www.iana.org/domains/root/db/hamburg.html +hamburg + +// hangout : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/hangout.html +hangout + +// haus : Dog Beach, LLC +// https://www.iana.org/domains/root/db/haus.html +haus + +// hbo : HBO Registry Services, Inc. +// https://www.iana.org/domains/root/db/hbo.html +hbo + +// hdfc : HDFC BANK LIMITED +// https://www.iana.org/domains/root/db/hdfc.html +hdfc + +// hdfcbank : HDFC BANK LIMITED +// https://www.iana.org/domains/root/db/hdfcbank.html +hdfcbank + +// health : Registry Services, LLC +// https://www.iana.org/domains/root/db/health.html +health + +// healthcare : Binky Moon, LLC +// https://www.iana.org/domains/root/db/healthcare.html +healthcare + +// help : Innovation service Limited +// https://www.iana.org/domains/root/db/help.html +help + +// helsinki : City of Helsinki +// https://www.iana.org/domains/root/db/helsinki.html +helsinki + +// here : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/here.html +here + +// hermes : HERMES INTERNATIONAL +// https://www.iana.org/domains/root/db/hermes.html +hermes + +// hiphop : Dot Hip Hop, LLC +// https://www.iana.org/domains/root/db/hiphop.html +hiphop + +// hisamitsu : Hisamitsu Pharmaceutical Co.,Inc. +// https://www.iana.org/domains/root/db/hisamitsu.html +hisamitsu + +// hitachi : Hitachi, Ltd. +// https://www.iana.org/domains/root/db/hitachi.html +hitachi + +// hiv : Internet Naming Company LLC +// https://www.iana.org/domains/root/db/hiv.html +hiv + +// hkt : PCCW-HKT DataCom Services Limited +// https://www.iana.org/domains/root/db/hkt.html +hkt + +// hockey : Binky Moon, LLC +// https://www.iana.org/domains/root/db/hockey.html +hockey + +// holdings : Binky Moon, LLC +// https://www.iana.org/domains/root/db/holdings.html +holdings + +// holiday : Binky Moon, LLC +// https://www.iana.org/domains/root/db/holiday.html +holiday + +// homedepot : Home Depot Product Authority, LLC +// https://www.iana.org/domains/root/db/homedepot.html +homedepot + +// homegoods : The TJX Companies, Inc. +// https://www.iana.org/domains/root/db/homegoods.html +homegoods + +// homes : XYZ.COM LLC +// https://www.iana.org/domains/root/db/homes.html +homes + +// homesense : The TJX Companies, Inc. +// https://www.iana.org/domains/root/db/homesense.html +homesense + +// honda : Honda Motor Co., Ltd. +// https://www.iana.org/domains/root/db/honda.html +honda + +// horse : Registry Services, LLC +// https://www.iana.org/domains/root/db/horse.html +horse + +// hospital : Binky Moon, LLC +// https://www.iana.org/domains/root/db/hospital.html +hospital + +// host : Radix Technologies Inc SEZC +// https://www.iana.org/domains/root/db/host.html +host + +// hosting : XYZ.COM LLC +// https://www.iana.org/domains/root/db/hosting.html +hosting + +// hot : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/hot.html +hot + +// hotel : HOTEL Top-Level-Domain S.a.r.l +// https://www.iana.org/domains/root/db/hotel.html +hotel + +// hotels : Booking.com B.V. +// https://www.iana.org/domains/root/db/hotels.html +hotels + +// hotmail : Microsoft Corporation +// https://www.iana.org/domains/root/db/hotmail.html +hotmail + +// house : Binky Moon, LLC +// https://www.iana.org/domains/root/db/house.html +house + +// how : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/how.html +how + +// hsbc : HSBC Global Services (UK) Limited +// https://www.iana.org/domains/root/db/hsbc.html +hsbc + +// hughes : Hughes Satellite Systems Corporation +// https://www.iana.org/domains/root/db/hughes.html +hughes + +// hyatt : Hyatt GTLD, L.L.C. +// https://www.iana.org/domains/root/db/hyatt.html +hyatt + +// hyundai : Hyundai Motor Company +// https://www.iana.org/domains/root/db/hyundai.html +hyundai + +// ibm : International Business Machines Corporation +// https://www.iana.org/domains/root/db/ibm.html +ibm + +// icbc : Industrial and Commercial Bank of China Limited +// https://www.iana.org/domains/root/db/icbc.html +icbc + +// ice : IntercontinentalExchange, Inc. +// https://www.iana.org/domains/root/db/ice.html +ice + +// icu : ShortDot SA +// https://www.iana.org/domains/root/db/icu.html +icu + +// ieee : IEEE Global LLC +// https://www.iana.org/domains/root/db/ieee.html +ieee + +// ifm : ifm electronic gmbh +// https://www.iana.org/domains/root/db/ifm.html +ifm + +// ikano : Ikano S.A. +// https://www.iana.org/domains/root/db/ikano.html +ikano + +// imamat : Fondation Aga Khan (Aga Khan Foundation) +// https://www.iana.org/domains/root/db/imamat.html +imamat + +// imdb : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/imdb.html +imdb + +// immo : Binky Moon, LLC +// https://www.iana.org/domains/root/db/immo.html +immo + +// immobilien : Dog Beach, LLC +// https://www.iana.org/domains/root/db/immobilien.html +immobilien + +// inc : Intercap Registry Inc. +// https://www.iana.org/domains/root/db/inc.html +inc + +// industries : Binky Moon, LLC +// https://www.iana.org/domains/root/db/industries.html +industries + +// infiniti : NISSAN MOTOR CO., LTD. +// https://www.iana.org/domains/root/db/infiniti.html +infiniti + +// ing : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/ing.html +ing + +// ink : Registry Services, LLC +// https://www.iana.org/domains/root/db/ink.html +ink + +// institute : Binky Moon, LLC +// https://www.iana.org/domains/root/db/institute.html +institute + +// insurance : fTLD Registry Services LLC +// https://www.iana.org/domains/root/db/insurance.html +insurance + +// insure : Binky Moon, LLC +// https://www.iana.org/domains/root/db/insure.html +insure + +// international : Binky Moon, LLC +// https://www.iana.org/domains/root/db/international.html +international + +// intuit : Intuit Administrative Services, Inc. +// https://www.iana.org/domains/root/db/intuit.html +intuit + +// investments : Binky Moon, LLC +// https://www.iana.org/domains/root/db/investments.html +investments + +// ipiranga : Ipiranga Produtos de Petroleo S.A. +// https://www.iana.org/domains/root/db/ipiranga.html +ipiranga + +// irish : Binky Moon, LLC +// https://www.iana.org/domains/root/db/irish.html +irish + +// ismaili : Fondation Aga Khan (Aga Khan Foundation) +// https://www.iana.org/domains/root/db/ismaili.html +ismaili + +// ist : Istanbul Metropolitan Municipality +// https://www.iana.org/domains/root/db/ist.html +ist + +// istanbul : Istanbul Metropolitan Municipality +// https://www.iana.org/domains/root/db/istanbul.html +istanbul + +// itau : Itau Unibanco Holding S.A. +// https://www.iana.org/domains/root/db/itau.html +itau + +// itv : ITV Services Limited +// https://www.iana.org/domains/root/db/itv.html +itv + +// jaguar : Jaguar Land Rover Ltd +// https://www.iana.org/domains/root/db/jaguar.html +jaguar + +// java : Oracle Corporation +// https://www.iana.org/domains/root/db/java.html +java + +// jcb : JCB Co., Ltd. +// https://www.iana.org/domains/root/db/jcb.html +jcb + +// jeep : FCA US LLC. +// https://www.iana.org/domains/root/db/jeep.html +jeep + +// jetzt : Binky Moon, LLC +// https://www.iana.org/domains/root/db/jetzt.html +jetzt + +// jewelry : Binky Moon, LLC +// https://www.iana.org/domains/root/db/jewelry.html +jewelry + +// jio : Reliance Industries Limited +// https://www.iana.org/domains/root/db/jio.html +jio + +// jll : Jones Lang LaSalle Incorporated +// https://www.iana.org/domains/root/db/jll.html +jll + +// jmp : Matrix IP LLC +// https://www.iana.org/domains/root/db/jmp.html +jmp + +// jnj : Johnson & Johnson Services, Inc. +// https://www.iana.org/domains/root/db/jnj.html +jnj + +// joburg : ZA Central Registry NPC trading as ZA Central Registry +// https://www.iana.org/domains/root/db/joburg.html +joburg + +// jot : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/jot.html +jot + +// joy : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/joy.html +joy + +// jpmorgan : JPMorgan Chase Bank, National Association +// https://www.iana.org/domains/root/db/jpmorgan.html +jpmorgan + +// jprs : Japan Registry Services Co., Ltd. +// https://www.iana.org/domains/root/db/jprs.html +jprs + +// juegos : Dog Beach, LLC +// https://www.iana.org/domains/root/db/juegos.html +juegos + +// juniper : JUNIPER NETWORKS, INC. +// https://www.iana.org/domains/root/db/juniper.html +juniper + +// kaufen : Dog Beach, LLC +// https://www.iana.org/domains/root/db/kaufen.html +kaufen + +// kddi : KDDI CORPORATION +// https://www.iana.org/domains/root/db/kddi.html +kddi + +// kerryhotels : Kerry Trading Co. Limited +// https://www.iana.org/domains/root/db/kerryhotels.html +kerryhotels + +// kerryproperties : Kerry Trading Co. Limited +// https://www.iana.org/domains/root/db/kerryproperties.html +kerryproperties + +// kfh : Kuwait Finance House +// https://www.iana.org/domains/root/db/kfh.html +kfh + +// kia : KIA MOTORS CORPORATION +// https://www.iana.org/domains/root/db/kia.html +kia + +// kids : DotKids Foundation Limited +// https://www.iana.org/domains/root/db/kids.html +kids + +// kim : Identity Digital Domains Limited +// https://www.iana.org/domains/root/db/kim.html +kim + +// kindle : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/kindle.html +kindle + +// kitchen : Binky Moon, LLC +// https://www.iana.org/domains/root/db/kitchen.html +kitchen + +// kiwi : DOT KIWI LIMITED +// https://www.iana.org/domains/root/db/kiwi.html +kiwi + +// koeln : dotKoeln GmbH +// https://www.iana.org/domains/root/db/koeln.html +koeln + +// komatsu : Komatsu Ltd. +// https://www.iana.org/domains/root/db/komatsu.html +komatsu + +// kosher : Kosher Marketing Assets LLC +// https://www.iana.org/domains/root/db/kosher.html +kosher + +// kpmg : KPMG International Cooperative (KPMG International Genossenschaft) +// https://www.iana.org/domains/root/db/kpmg.html +kpmg + +// kpn : Koninklijke KPN N.V. +// https://www.iana.org/domains/root/db/kpn.html +kpn + +// krd : KRG Department of Information Technology +// https://www.iana.org/domains/root/db/krd.html +krd + +// kred : KredTLD Pty Ltd +// https://www.iana.org/domains/root/db/kred.html +kred + +// kuokgroup : Kerry Trading Co. Limited +// https://www.iana.org/domains/root/db/kuokgroup.html +kuokgroup + +// kyoto : Academic Institution: Kyoto Jyoho Gakuen +// https://www.iana.org/domains/root/db/kyoto.html +kyoto + +// lacaixa : Fundación Bancaria Caixa d’Estalvis i Pensions de Barcelona, “la Caixa” +// https://www.iana.org/domains/root/db/lacaixa.html +lacaixa + +// lamborghini : Automobili Lamborghini S.p.A. +// https://www.iana.org/domains/root/db/lamborghini.html +lamborghini + +// lamer : The Estée Lauder Companies Inc. +// https://www.iana.org/domains/root/db/lamer.html +lamer + +// land : Binky Moon, LLC +// https://www.iana.org/domains/root/db/land.html +land + +// landrover : Jaguar Land Rover Ltd +// https://www.iana.org/domains/root/db/landrover.html +landrover + +// lanxess : LANXESS Corporation +// https://www.iana.org/domains/root/db/lanxess.html +lanxess + +// lasalle : Jones Lang LaSalle Incorporated +// https://www.iana.org/domains/root/db/lasalle.html +lasalle + +// lat : XYZ.COM LLC +// https://www.iana.org/domains/root/db/lat.html +lat + +// latino : Dish DBS Corporation +// https://www.iana.org/domains/root/db/latino.html +latino + +// latrobe : La Trobe University +// https://www.iana.org/domains/root/db/latrobe.html +latrobe + +// law : Registry Services, LLC +// https://www.iana.org/domains/root/db/law.html +law + +// lawyer : Dog Beach, LLC +// https://www.iana.org/domains/root/db/lawyer.html +lawyer + +// lds : IRI Domain Management, LLC +// https://www.iana.org/domains/root/db/lds.html +lds + +// lease : Binky Moon, LLC +// https://www.iana.org/domains/root/db/lease.html +lease + +// leclerc : A.C.D. LEC Association des Centres Distributeurs Edouard Leclerc +// https://www.iana.org/domains/root/db/leclerc.html +leclerc + +// lefrak : LeFrak Organization, Inc. +// https://www.iana.org/domains/root/db/lefrak.html +lefrak + +// legal : Binky Moon, LLC +// https://www.iana.org/domains/root/db/legal.html +legal + +// lego : LEGO Juris A/S +// https://www.iana.org/domains/root/db/lego.html +lego + +// lexus : TOYOTA MOTOR CORPORATION +// https://www.iana.org/domains/root/db/lexus.html +lexus + +// lgbt : Identity Digital Domains Limited +// https://www.iana.org/domains/root/db/lgbt.html +lgbt + +// lidl : Schwarz Domains und Services GmbH & Co. KG +// https://www.iana.org/domains/root/db/lidl.html +lidl + +// life : Binky Moon, LLC +// https://www.iana.org/domains/root/db/life.html +life + +// lifeinsurance : American Council of Life Insurers +// https://www.iana.org/domains/root/db/lifeinsurance.html +lifeinsurance + +// lifestyle : Internet Naming Company LLC +// https://www.iana.org/domains/root/db/lifestyle.html +lifestyle + +// lighting : Binky Moon, LLC +// https://www.iana.org/domains/root/db/lighting.html +lighting + +// like : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/like.html +like + +// lilly : Eli Lilly and Company +// https://www.iana.org/domains/root/db/lilly.html +lilly + +// limited : Binky Moon, LLC +// https://www.iana.org/domains/root/db/limited.html +limited + +// limo : Binky Moon, LLC +// https://www.iana.org/domains/root/db/limo.html +limo + +// lincoln : Ford Motor Company +// https://www.iana.org/domains/root/db/lincoln.html +lincoln + +// link : Nova Registry Ltd +// https://www.iana.org/domains/root/db/link.html +link + +// live : Dog Beach, LLC +// https://www.iana.org/domains/root/db/live.html +live + +// living : Internet Naming Company LLC +// https://www.iana.org/domains/root/db/living.html +living + +// llc : Identity Digital Domains Limited +// https://www.iana.org/domains/root/db/llc.html +llc + +// llp : Intercap Registry Inc. +// https://www.iana.org/domains/root/db/llp.html +llp + +// loan : dot Loan Limited +// https://www.iana.org/domains/root/db/loan.html +loan + +// loans : Binky Moon, LLC +// https://www.iana.org/domains/root/db/loans.html +loans + +// locker : Orange Domains LLC +// https://www.iana.org/domains/root/db/locker.html +locker + +// locus : Locus Analytics LLC +// https://www.iana.org/domains/root/db/locus.html +locus + +// lol : XYZ.COM LLC +// https://www.iana.org/domains/root/db/lol.html +lol + +// london : Dot London Domains Limited +// https://www.iana.org/domains/root/db/london.html +london + +// lotte : Lotte Holdings Co., Ltd. +// https://www.iana.org/domains/root/db/lotte.html +lotte + +// lotto : Identity Digital Domains Limited +// https://www.iana.org/domains/root/db/lotto.html +lotto + +// love : Waterford Limited +// https://www.iana.org/domains/root/db/love.html +love + +// lpl : LPL Holdings, Inc. +// https://www.iana.org/domains/root/db/lpl.html +lpl + +// lplfinancial : LPL Holdings, Inc. +// https://www.iana.org/domains/root/db/lplfinancial.html +lplfinancial + +// ltd : Binky Moon, LLC +// https://www.iana.org/domains/root/db/ltd.html +ltd + +// ltda : InterNetX, Corp +// https://www.iana.org/domains/root/db/ltda.html +ltda + +// lundbeck : H. Lundbeck A/S +// https://www.iana.org/domains/root/db/lundbeck.html +lundbeck + +// luxe : Registry Services, LLC +// https://www.iana.org/domains/root/db/luxe.html +luxe + +// luxury : Luxury Partners, LLC +// https://www.iana.org/domains/root/db/luxury.html +luxury + +// madrid : Comunidad de Madrid +// https://www.iana.org/domains/root/db/madrid.html +madrid + +// maif : Mutuelle Assurance Instituteur France (MAIF) +// https://www.iana.org/domains/root/db/maif.html +maif + +// maison : Binky Moon, LLC +// https://www.iana.org/domains/root/db/maison.html +maison + +// makeup : XYZ.COM LLC +// https://www.iana.org/domains/root/db/makeup.html +makeup + +// man : MAN Truck & Bus SE +// https://www.iana.org/domains/root/db/man.html +man + +// management : Binky Moon, LLC +// https://www.iana.org/domains/root/db/management.html +management + +// mango : PUNTO FA S.L. +// https://www.iana.org/domains/root/db/mango.html +mango + +// map : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/map.html +map + +// market : Dog Beach, LLC +// https://www.iana.org/domains/root/db/market.html +market + +// marketing : Binky Moon, LLC +// https://www.iana.org/domains/root/db/marketing.html +marketing + +// markets : Dog Beach, LLC +// https://www.iana.org/domains/root/db/markets.html +markets + +// marriott : Marriott Worldwide Corporation +// https://www.iana.org/domains/root/db/marriott.html +marriott + +// marshalls : The TJX Companies, Inc. +// https://www.iana.org/domains/root/db/marshalls.html +marshalls + +// mattel : Mattel IT Services, Inc. +// https://www.iana.org/domains/root/db/mattel.html +mattel + +// mba : Binky Moon, LLC +// https://www.iana.org/domains/root/db/mba.html +mba + +// mckinsey : McKinsey Holdings, Inc. +// https://www.iana.org/domains/root/db/mckinsey.html +mckinsey + +// med : Medistry LLC +// https://www.iana.org/domains/root/db/med.html +med + +// media : Binky Moon, LLC +// https://www.iana.org/domains/root/db/media.html +media + +// meet : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/meet.html +meet + +// melbourne : The Crown in right of the State of Victoria, represented by its Department of State Development, Business and Innovation +// https://www.iana.org/domains/root/db/melbourne.html +melbourne + +// meme : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/meme.html +meme + +// memorial : Dog Beach, LLC +// https://www.iana.org/domains/root/db/memorial.html +memorial + +// men : Exclusive Registry Limited +// https://www.iana.org/domains/root/db/men.html +men + +// menu : Dot Menu Registry, LLC +// https://www.iana.org/domains/root/db/menu.html +menu + +// merck : Merck Registry Holdings, Inc. +// https://www.iana.org/domains/root/db/merck.html +merck + +// merckmsd : MSD Registry Holdings, Inc. +// https://www.iana.org/domains/root/db/merckmsd.html +merckmsd + +// miami : Registry Services, LLC +// https://www.iana.org/domains/root/db/miami.html +miami + +// microsoft : Microsoft Corporation +// https://www.iana.org/domains/root/db/microsoft.html +microsoft + +// mini : Bayerische Motoren Werke Aktiengesellschaft +// https://www.iana.org/domains/root/db/mini.html +mini + +// mint : Intuit Administrative Services, Inc. +// https://www.iana.org/domains/root/db/mint.html +mint + +// mit : Massachusetts Institute of Technology +// https://www.iana.org/domains/root/db/mit.html +mit + +// mitsubishi : Mitsubishi Corporation +// https://www.iana.org/domains/root/db/mitsubishi.html +mitsubishi + +// mlb : MLB Advanced Media DH, LLC +// https://www.iana.org/domains/root/db/mlb.html +mlb + +// mls : The Canadian Real Estate Association +// https://www.iana.org/domains/root/db/mls.html +mls + +// mma : MMA IARD +// https://www.iana.org/domains/root/db/mma.html +mma + +// mobile : Dish DBS Corporation +// https://www.iana.org/domains/root/db/mobile.html +mobile + +// moda : Dog Beach, LLC +// https://www.iana.org/domains/root/db/moda.html +moda + +// moe : Interlink Systems Innovation Institute K.K. +// https://www.iana.org/domains/root/db/moe.html +moe + +// moi : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/moi.html +moi + +// mom : XYZ.COM LLC +// https://www.iana.org/domains/root/db/mom.html +mom + +// monash : Monash University +// https://www.iana.org/domains/root/db/monash.html +monash + +// money : Binky Moon, LLC +// https://www.iana.org/domains/root/db/money.html +money + +// monster : XYZ.COM LLC +// https://www.iana.org/domains/root/db/monster.html +monster + +// mormon : IRI Domain Management, LLC +// https://www.iana.org/domains/root/db/mormon.html +mormon + +// mortgage : Dog Beach, LLC +// https://www.iana.org/domains/root/db/mortgage.html +mortgage + +// moscow : Foundation for Assistance for Internet Technologies and Infrastructure Development (FAITID) +// https://www.iana.org/domains/root/db/moscow.html +moscow + +// moto : Motorola Trademark Holdings, LLC +// https://www.iana.org/domains/root/db/moto.html +moto + +// motorcycles : XYZ.COM LLC +// https://www.iana.org/domains/root/db/motorcycles.html +motorcycles + +// mov : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/mov.html +mov + +// movie : Binky Moon, LLC +// https://www.iana.org/domains/root/db/movie.html +movie + +// msd : MSD Registry Holdings, Inc. +// https://www.iana.org/domains/root/db/msd.html +msd + +// mtn : MTN Dubai Limited +// https://www.iana.org/domains/root/db/mtn.html +mtn + +// mtr : MTR Corporation Limited +// https://www.iana.org/domains/root/db/mtr.html +mtr + +// music : DotMusic Limited +// https://www.iana.org/domains/root/db/music.html +music + +// nab : National Australia Bank Limited +// https://www.iana.org/domains/root/db/nab.html +nab + +// nagoya : GMO Registry, Inc. +// https://www.iana.org/domains/root/db/nagoya.html +nagoya + +// navy : Dog Beach, LLC +// https://www.iana.org/domains/root/db/navy.html +navy + +// nba : NBA REGISTRY, LLC +// https://www.iana.org/domains/root/db/nba.html +nba + +// nec : NEC Corporation +// https://www.iana.org/domains/root/db/nec.html +nec + +// netbank : COMMONWEALTH BANK OF AUSTRALIA +// https://www.iana.org/domains/root/db/netbank.html +netbank + +// netflix : Netflix, Inc. +// https://www.iana.org/domains/root/db/netflix.html +netflix + +// network : Binky Moon, LLC +// https://www.iana.org/domains/root/db/network.html +network + +// neustar : NeuStar, Inc. +// https://www.iana.org/domains/root/db/neustar.html +neustar + +// new : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/new.html +new + +// news : Dog Beach, LLC +// https://www.iana.org/domains/root/db/news.html +news + +// next : Next plc +// https://www.iana.org/domains/root/db/next.html +next + +// nextdirect : Next plc +// https://www.iana.org/domains/root/db/nextdirect.html +nextdirect + +// nexus : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/nexus.html +nexus + +// nfl : NFL Reg Ops LLC +// https://www.iana.org/domains/root/db/nfl.html +nfl + +// ngo : Public Interest Registry +// https://www.iana.org/domains/root/db/ngo.html +ngo + +// nhk : Japan Broadcasting Corporation (NHK) +// https://www.iana.org/domains/root/db/nhk.html +nhk + +// nico : DWANGO Co., Ltd. +// https://www.iana.org/domains/root/db/nico.html +nico + +// nike : NIKE, Inc. +// https://www.iana.org/domains/root/db/nike.html +nike + +// nikon : NIKON CORPORATION +// https://www.iana.org/domains/root/db/nikon.html +nikon + +// ninja : Dog Beach, LLC +// https://www.iana.org/domains/root/db/ninja.html +ninja + +// nissan : NISSAN MOTOR CO., LTD. +// https://www.iana.org/domains/root/db/nissan.html +nissan + +// nissay : Nippon Life Insurance Company +// https://www.iana.org/domains/root/db/nissay.html +nissay + +// nokia : Nokia Corporation +// https://www.iana.org/domains/root/db/nokia.html +nokia + +// norton : Gen Digital Inc. +// https://www.iana.org/domains/root/db/norton.html +norton + +// now : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/now.html +now + +// nowruz +// https://www.iana.org/domains/root/db/nowruz.html +nowruz + +// nowtv : Starbucks (HK) Limited +// https://www.iana.org/domains/root/db/nowtv.html +nowtv + +// nra : National Rifle Association of America +// https://www.iana.org/domains/root/db/nra.html +nra + +// nrw : Minds + Machines GmbH +// https://www.iana.org/domains/root/db/nrw.html +nrw + +// ntt : NIPPON TELEGRAPH AND TELEPHONE CORPORATION +// https://www.iana.org/domains/root/db/ntt.html +ntt + +// nyc : The City of New York by and through the New York City Department of Information Technology & Telecommunications +// https://www.iana.org/domains/root/db/nyc.html +nyc + +// obi : OBI Group Holding SE & Co. KGaA +// https://www.iana.org/domains/root/db/obi.html +obi + +// observer : Fegistry, LLC +// https://www.iana.org/domains/root/db/observer.html +observer + +// office : Microsoft Corporation +// https://www.iana.org/domains/root/db/office.html +office + +// okinawa : BRregistry, Inc. +// https://www.iana.org/domains/root/db/okinawa.html +okinawa + +// olayan : Competrol (Luxembourg) Sarl +// https://www.iana.org/domains/root/db/olayan.html +olayan + +// olayangroup : Competrol (Luxembourg) Sarl +// https://www.iana.org/domains/root/db/olayangroup.html +olayangroup + +// ollo : Dish DBS Corporation +// https://www.iana.org/domains/root/db/ollo.html +ollo + +// omega : The Swatch Group Ltd +// https://www.iana.org/domains/root/db/omega.html +omega + +// one : One.com A/S +// https://www.iana.org/domains/root/db/one.html +one + +// ong : Public Interest Registry +// https://www.iana.org/domains/root/db/ong.html +ong + +// onl : iRegistry GmbH +// https://www.iana.org/domains/root/db/onl.html +onl + +// online : Radix Technologies Inc SEZC +// https://www.iana.org/domains/root/db/online.html +online + +// ooo : INFIBEAM AVENUES LIMITED +// https://www.iana.org/domains/root/db/ooo.html +ooo + +// open : American Express Travel Related Services Company, Inc. +// https://www.iana.org/domains/root/db/open.html +open + +// oracle : Oracle Corporation +// https://www.iana.org/domains/root/db/oracle.html +oracle + +// orange : Orange Brand Services Limited +// https://www.iana.org/domains/root/db/orange.html +orange + +// organic : Identity Digital Domains Limited +// https://www.iana.org/domains/root/db/organic.html +organic + +// origins : The Estée Lauder Companies Inc. +// https://www.iana.org/domains/root/db/origins.html +origins + +// osaka : Osaka Registry Co., Ltd. +// https://www.iana.org/domains/root/db/osaka.html +osaka + +// otsuka : Otsuka Holdings Co., Ltd. +// https://www.iana.org/domains/root/db/otsuka.html +otsuka + +// ott : Dish DBS Corporation +// https://www.iana.org/domains/root/db/ott.html +ott + +// ovh : MédiaBC +// https://www.iana.org/domains/root/db/ovh.html +ovh + +// page : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/page.html +page + +// panasonic : Panasonic Holdings Corporation +// https://www.iana.org/domains/root/db/panasonic.html +panasonic + +// paris : City of Paris +// https://www.iana.org/domains/root/db/paris.html +paris + +// pars +// https://www.iana.org/domains/root/db/pars.html +pars + +// partners : Binky Moon, LLC +// https://www.iana.org/domains/root/db/partners.html +partners + +// parts : Binky Moon, LLC +// https://www.iana.org/domains/root/db/parts.html +parts + +// party : Blue Sky Registry Limited +// https://www.iana.org/domains/root/db/party.html +party + +// pay : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/pay.html +pay + +// pccw : PCCW Enterprises Limited +// https://www.iana.org/domains/root/db/pccw.html +pccw + +// pet : Identity Digital Domains Limited +// https://www.iana.org/domains/root/db/pet.html +pet + +// pfizer : Pfizer Inc. +// https://www.iana.org/domains/root/db/pfizer.html +pfizer + +// pharmacy : National Association of Boards of Pharmacy +// https://www.iana.org/domains/root/db/pharmacy.html +pharmacy + +// phd : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/phd.html +phd + +// philips : Koninklijke Philips N.V. +// https://www.iana.org/domains/root/db/philips.html +philips + +// phone : Dish DBS Corporation +// https://www.iana.org/domains/root/db/phone.html +phone + +// photo : Registry Services, LLC +// https://www.iana.org/domains/root/db/photo.html +photo + +// photography : Binky Moon, LLC +// https://www.iana.org/domains/root/db/photography.html +photography + +// photos : Binky Moon, LLC +// https://www.iana.org/domains/root/db/photos.html +photos + +// physio : PhysBiz Pty Ltd +// https://www.iana.org/domains/root/db/physio.html +physio + +// pics : XYZ.COM LLC +// https://www.iana.org/domains/root/db/pics.html +pics + +// pictet : Banque Pictet & Cie SA +// https://www.iana.org/domains/root/db/pictet.html +pictet + +// pictures : Binky Moon, LLC +// https://www.iana.org/domains/root/db/pictures.html +pictures + +// pid : Top Level Spectrum, Inc. +// https://www.iana.org/domains/root/db/pid.html +pid + +// pin : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/pin.html +pin + +// ping : Ping Registry Provider, Inc. +// https://www.iana.org/domains/root/db/ping.html +ping + +// pink : Identity Digital Domains Limited +// https://www.iana.org/domains/root/db/pink.html +pink + +// pioneer : Pioneer Corporation +// https://www.iana.org/domains/root/db/pioneer.html +pioneer + +// pizza : Binky Moon, LLC +// https://www.iana.org/domains/root/db/pizza.html +pizza + +// place : Binky Moon, LLC +// https://www.iana.org/domains/root/db/place.html +place + +// play : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/play.html +play + +// playstation : Sony Interactive Entertainment Inc. +// https://www.iana.org/domains/root/db/playstation.html +playstation + +// plumbing : Binky Moon, LLC +// https://www.iana.org/domains/root/db/plumbing.html +plumbing + +// plus : Binky Moon, LLC +// https://www.iana.org/domains/root/db/plus.html +plus + +// pnc : PNC Domain Co., LLC +// https://www.iana.org/domains/root/db/pnc.html +pnc + +// pohl : Deutsche Vermögensberatung Aktiengesellschaft DVAG +// https://www.iana.org/domains/root/db/pohl.html +pohl + +// poker : Identity Digital Domains Limited +// https://www.iana.org/domains/root/db/poker.html +poker + +// politie : Politie Nederland +// https://www.iana.org/domains/root/db/politie.html +politie + +// porn : ICM Registry PN LLC +// https://www.iana.org/domains/root/db/porn.html +porn + +// praxi : Praxi S.p.A. +// https://www.iana.org/domains/root/db/praxi.html +praxi + +// press : Radix Technologies Inc SEZC +// https://www.iana.org/domains/root/db/press.html +press + +// prime : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/prime.html +prime + +// prod : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/prod.html +prod + +// productions : Binky Moon, LLC +// https://www.iana.org/domains/root/db/productions.html +productions + +// prof : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/prof.html +prof + +// progressive : Progressive Casualty Insurance Company +// https://www.iana.org/domains/root/db/progressive.html +progressive + +// promo : Identity Digital Domains Limited +// https://www.iana.org/domains/root/db/promo.html +promo + +// properties : Binky Moon, LLC +// https://www.iana.org/domains/root/db/properties.html +properties + +// property : Digital Property Infrastructure Limited +// https://www.iana.org/domains/root/db/property.html +property + +// protection : XYZ.COM LLC +// https://www.iana.org/domains/root/db/protection.html +protection + +// pru : Prudential Financial, Inc. +// https://www.iana.org/domains/root/db/pru.html +pru + +// prudential : Prudential Financial, Inc. +// https://www.iana.org/domains/root/db/prudential.html +prudential + +// pub : Dog Beach, LLC +// https://www.iana.org/domains/root/db/pub.html +pub + +// pwc : PricewaterhouseCoopers LLP +// https://www.iana.org/domains/root/db/pwc.html +pwc + +// qpon : dotQPON LLC +// https://www.iana.org/domains/root/db/qpon.html +qpon + +// quebec : PointQuébec Inc +// https://www.iana.org/domains/root/db/quebec.html +quebec + +// quest : XYZ.COM LLC +// https://www.iana.org/domains/root/db/quest.html +quest + +// racing : Premier Registry Limited +// https://www.iana.org/domains/root/db/racing.html +racing + +// radio : European Broadcasting Union (EBU) +// https://www.iana.org/domains/root/db/radio.html +radio + +// read : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/read.html +read + +// realestate : dotRealEstate LLC +// https://www.iana.org/domains/root/db/realestate.html +realestate + +// realtor : Real Estate Domains LLC +// https://www.iana.org/domains/root/db/realtor.html +realtor + +// realty : Waterford Limited +// https://www.iana.org/domains/root/db/realty.html +realty + +// recipes : Binky Moon, LLC +// https://www.iana.org/domains/root/db/recipes.html +recipes + +// red : Identity Digital Domains Limited +// https://www.iana.org/domains/root/db/red.html +red + +// redumbrella : Travelers TLD, LLC +// https://www.iana.org/domains/root/db/redumbrella.html +redumbrella + +// rehab : Dog Beach, LLC +// https://www.iana.org/domains/root/db/rehab.html +rehab + +// reise : Binky Moon, LLC +// https://www.iana.org/domains/root/db/reise.html +reise + +// reisen : Binky Moon, LLC +// https://www.iana.org/domains/root/db/reisen.html +reisen + +// reit : National Association of Real Estate Investment Trusts, Inc. +// https://www.iana.org/domains/root/db/reit.html +reit + +// reliance : Reliance Industries Limited +// https://www.iana.org/domains/root/db/reliance.html +reliance + +// ren : ZDNS International Limited +// https://www.iana.org/domains/root/db/ren.html +ren + +// rent : XYZ.COM LLC +// https://www.iana.org/domains/root/db/rent.html +rent + +// rentals : Binky Moon, LLC +// https://www.iana.org/domains/root/db/rentals.html +rentals + +// repair : Binky Moon, LLC +// https://www.iana.org/domains/root/db/repair.html +repair + +// report : Binky Moon, LLC +// https://www.iana.org/domains/root/db/report.html +report + +// republican : Dog Beach, LLC +// https://www.iana.org/domains/root/db/republican.html +republican + +// rest : Punto 2012 Sociedad Anonima Promotora de Inversion de Capital Variable +// https://www.iana.org/domains/root/db/rest.html +rest + +// restaurant : Binky Moon, LLC +// https://www.iana.org/domains/root/db/restaurant.html +restaurant + +// review : dot Review Limited +// https://www.iana.org/domains/root/db/review.html +review + +// reviews : Dog Beach, LLC +// https://www.iana.org/domains/root/db/reviews.html +reviews + +// rexroth : Robert Bosch GMBH +// https://www.iana.org/domains/root/db/rexroth.html +rexroth + +// rich : iRegistry GmbH +// https://www.iana.org/domains/root/db/rich.html +rich + +// richardli : Pacific Century Asset Management (HK) Limited +// https://www.iana.org/domains/root/db/richardli.html +richardli + +// ricoh : Ricoh Company, Ltd. +// https://www.iana.org/domains/root/db/ricoh.html +ricoh + +// ril : Reliance Industries Limited +// https://www.iana.org/domains/root/db/ril.html +ril + +// rio : Empresa Municipal de Informática SA - IPLANRIO +// https://www.iana.org/domains/root/db/rio.html +rio + +// rip : Dog Beach, LLC +// https://www.iana.org/domains/root/db/rip.html +rip + +// rocks : Dog Beach, LLC +// https://www.iana.org/domains/root/db/rocks.html +rocks + +// rodeo : Registry Services, LLC +// https://www.iana.org/domains/root/db/rodeo.html +rodeo + +// rogers : Rogers Communications Canada Inc. +// https://www.iana.org/domains/root/db/rogers.html +rogers + +// room : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/room.html +room + +// rsvp : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/rsvp.html +rsvp + +// rugby : World Rugby Strategic Developments Limited +// https://www.iana.org/domains/root/db/rugby.html +rugby + +// ruhr : dotSaarland GmbH +// https://www.iana.org/domains/root/db/ruhr.html +ruhr + +// run : Binky Moon, LLC +// https://www.iana.org/domains/root/db/run.html +run + +// rwe : RWE AG +// https://www.iana.org/domains/root/db/rwe.html +rwe + +// ryukyu : BRregistry, Inc. +// https://www.iana.org/domains/root/db/ryukyu.html +ryukyu + +// saarland : dotSaarland GmbH +// https://www.iana.org/domains/root/db/saarland.html +saarland + +// safe : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/safe.html +safe + +// safety : Safety Registry Services, LLC. +// https://www.iana.org/domains/root/db/safety.html +safety + +// sakura : SAKURA Internet Inc. +// https://www.iana.org/domains/root/db/sakura.html +sakura + +// sale : Dog Beach, LLC +// https://www.iana.org/domains/root/db/sale.html +sale + +// salon : Binky Moon, LLC +// https://www.iana.org/domains/root/db/salon.html +salon + +// samsclub : Wal-Mart Stores, Inc. +// https://www.iana.org/domains/root/db/samsclub.html +samsclub + +// samsung : SAMSUNG SDS CO., LTD +// https://www.iana.org/domains/root/db/samsung.html +samsung + +// sandvik : Sandvik AB +// https://www.iana.org/domains/root/db/sandvik.html +sandvik + +// sandvikcoromant : Sandvik AB +// https://www.iana.org/domains/root/db/sandvikcoromant.html +sandvikcoromant + +// sanofi : Sanofi +// https://www.iana.org/domains/root/db/sanofi.html +sanofi + +// sap : SAP AG +// https://www.iana.org/domains/root/db/sap.html +sap + +// sarl : Binky Moon, LLC +// https://www.iana.org/domains/root/db/sarl.html +sarl + +// sas : Research IP LLC +// https://www.iana.org/domains/root/db/sas.html +sas + +// save : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/save.html +save + +// saxo : Saxo Bank A/S +// https://www.iana.org/domains/root/db/saxo.html +saxo + +// sbi : STATE BANK OF INDIA +// https://www.iana.org/domains/root/db/sbi.html +sbi + +// sbs : ShortDot SA +// https://www.iana.org/domains/root/db/sbs.html +sbs + +// scb : The Siam Commercial Bank Public Company Limited ("SCB") +// https://www.iana.org/domains/root/db/scb.html +scb + +// schaeffler : Schaeffler Technologies AG & Co. KG +// https://www.iana.org/domains/root/db/schaeffler.html +schaeffler + +// schmidt : SCHMIDT GROUPE S.A.S. +// https://www.iana.org/domains/root/db/schmidt.html +schmidt + +// scholarships : Scholarships.com, LLC +// https://www.iana.org/domains/root/db/scholarships.html +scholarships + +// school : Binky Moon, LLC +// https://www.iana.org/domains/root/db/school.html +school + +// schule : Binky Moon, LLC +// https://www.iana.org/domains/root/db/schule.html +schule + +// schwarz : Schwarz Domains und Services GmbH & Co. KG +// https://www.iana.org/domains/root/db/schwarz.html +schwarz + +// science : dot Science Limited +// https://www.iana.org/domains/root/db/science.html +science + +// scot : Dot Scot Registry Limited +// https://www.iana.org/domains/root/db/scot.html +scot + +// search : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/search.html +search + +// seat : SEAT, S.A. (Sociedad Unipersonal) +// https://www.iana.org/domains/root/db/seat.html +seat + +// secure : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/secure.html +secure + +// security : XYZ.COM LLC +// https://www.iana.org/domains/root/db/security.html +security + +// seek : Seek Limited +// https://www.iana.org/domains/root/db/seek.html +seek + +// select : Registry Services, LLC +// https://www.iana.org/domains/root/db/select.html +select + +// sener : Sener Ingeniería y Sistemas, S.A. +// https://www.iana.org/domains/root/db/sener.html +sener + +// services : Binky Moon, LLC +// https://www.iana.org/domains/root/db/services.html +services + +// seven : Seven West Media Ltd +// https://www.iana.org/domains/root/db/seven.html +seven + +// sew : SEW-EURODRIVE GmbH & Co KG +// https://www.iana.org/domains/root/db/sew.html +sew + +// sex : ICM Registry SX LLC +// https://www.iana.org/domains/root/db/sex.html +sex + +// sexy : Internet Naming Company LLC +// https://www.iana.org/domains/root/db/sexy.html +sexy + +// sfr : Societe Francaise du Radiotelephone - SFR +// https://www.iana.org/domains/root/db/sfr.html +sfr + +// shangrila : Shangri‐La International Hotel Management Limited +// https://www.iana.org/domains/root/db/shangrila.html +shangrila + +// sharp : Sharp Corporation +// https://www.iana.org/domains/root/db/sharp.html +sharp + +// shell : Shell Information Technology International Inc +// https://www.iana.org/domains/root/db/shell.html +shell + +// shia +// https://www.iana.org/domains/root/db/shia.html +shia + +// shiksha : Identity Digital Domains Limited +// https://www.iana.org/domains/root/db/shiksha.html +shiksha + +// shoes : Binky Moon, LLC +// https://www.iana.org/domains/root/db/shoes.html +shoes + +// shop : GMO Registry, Inc. +// https://www.iana.org/domains/root/db/shop.html +shop + +// shopping : Binky Moon, LLC +// https://www.iana.org/domains/root/db/shopping.html +shopping + +// shouji : Beijing Qihu Keji Co., Ltd. +// https://www.iana.org/domains/root/db/shouji.html +shouji + +// show : Binky Moon, LLC +// https://www.iana.org/domains/root/db/show.html +show + +// silk : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/silk.html +silk + +// sina : Sina Corporation +// https://www.iana.org/domains/root/db/sina.html +sina + +// singles : Binky Moon, LLC +// https://www.iana.org/domains/root/db/singles.html +singles + +// site : Radix Technologies Inc SEZC +// https://www.iana.org/domains/root/db/site.html +site + +// ski : Identity Digital Domains Limited +// https://www.iana.org/domains/root/db/ski.html +ski + +// skin : XYZ.COM LLC +// https://www.iana.org/domains/root/db/skin.html +skin + +// sky : Sky UK Limited +// https://www.iana.org/domains/root/db/sky.html +sky + +// skype : Microsoft Corporation +// https://www.iana.org/domains/root/db/skype.html +skype + +// sling : DISH Technologies L.L.C. +// https://www.iana.org/domains/root/db/sling.html +sling + +// smart : Smart Communications, Inc. (SMART) +// https://www.iana.org/domains/root/db/smart.html +smart + +// smile : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/smile.html +smile + +// sncf : Société Nationale SNCF +// https://www.iana.org/domains/root/db/sncf.html +sncf + +// soccer : Binky Moon, LLC +// https://www.iana.org/domains/root/db/soccer.html +soccer + +// social : Dog Beach, LLC +// https://www.iana.org/domains/root/db/social.html +social + +// softbank : SoftBank Group Corp. +// https://www.iana.org/domains/root/db/softbank.html +softbank + +// software : Dog Beach, LLC +// https://www.iana.org/domains/root/db/software.html +software + +// sohu : Sohu.com Limited +// https://www.iana.org/domains/root/db/sohu.html +sohu + +// solar : Binky Moon, LLC +// https://www.iana.org/domains/root/db/solar.html +solar + +// solutions : Binky Moon, LLC +// https://www.iana.org/domains/root/db/solutions.html +solutions + +// song : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/song.html +song + +// sony : Sony Corporation +// https://www.iana.org/domains/root/db/sony.html +sony + +// soy : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/soy.html +soy + +// spa : Asia Spa and Wellness Promotion Council Limited +// https://www.iana.org/domains/root/db/spa.html +spa + +// space : Radix Technologies Inc SEZC +// https://www.iana.org/domains/root/db/space.html +space + +// sport : SportAccord +// https://www.iana.org/domains/root/db/sport.html +sport + +// spot : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/spot.html +spot + +// srl : InterNetX, Corp +// https://www.iana.org/domains/root/db/srl.html +srl + +// stada : STADA Arzneimittel AG +// https://www.iana.org/domains/root/db/stada.html +stada + +// staples : Staples, Inc. +// https://www.iana.org/domains/root/db/staples.html +staples + +// star : Star India Private Limited +// https://www.iana.org/domains/root/db/star.html +star + +// statebank : STATE BANK OF INDIA +// https://www.iana.org/domains/root/db/statebank.html +statebank + +// statefarm : State Farm Mutual Automobile Insurance Company +// https://www.iana.org/domains/root/db/statefarm.html +statefarm + +// stc : Saudi Telecom Company +// https://www.iana.org/domains/root/db/stc.html +stc + +// stcgroup : Saudi Telecom Company +// https://www.iana.org/domains/root/db/stcgroup.html +stcgroup + +// stockholm : Stockholms kommun +// https://www.iana.org/domains/root/db/stockholm.html +stockholm + +// storage : XYZ.COM LLC +// https://www.iana.org/domains/root/db/storage.html +storage + +// store : Radix Technologies Inc SEZC +// https://www.iana.org/domains/root/db/store.html +store + +// stream : dot Stream Limited +// https://www.iana.org/domains/root/db/stream.html +stream + +// studio : Dog Beach, LLC +// https://www.iana.org/domains/root/db/studio.html +studio + +// study : Registry Services, LLC +// https://www.iana.org/domains/root/db/study.html +study + +// style : Binky Moon, LLC +// https://www.iana.org/domains/root/db/style.html +style + +// sucks : Vox Populi Registry Ltd. +// https://www.iana.org/domains/root/db/sucks.html +sucks + +// supplies : Binky Moon, LLC +// https://www.iana.org/domains/root/db/supplies.html +supplies + +// supply : Binky Moon, LLC +// https://www.iana.org/domains/root/db/supply.html +supply + +// support : Binky Moon, LLC +// https://www.iana.org/domains/root/db/support.html +support + +// surf : Registry Services, LLC +// https://www.iana.org/domains/root/db/surf.html +surf + +// surgery : Binky Moon, LLC +// https://www.iana.org/domains/root/db/surgery.html +surgery + +// suzuki : SUZUKI MOTOR CORPORATION +// https://www.iana.org/domains/root/db/suzuki.html +suzuki + +// swatch : The Swatch Group Ltd +// https://www.iana.org/domains/root/db/swatch.html +swatch + +// swiss : Swiss Confederation +// https://www.iana.org/domains/root/db/swiss.html +swiss + +// sydney : State of New South Wales, Department of Premier and Cabinet +// https://www.iana.org/domains/root/db/sydney.html +sydney + +// systems : Binky Moon, LLC +// https://www.iana.org/domains/root/db/systems.html +systems + +// tab : Tabcorp Holdings Limited +// https://www.iana.org/domains/root/db/tab.html +tab + +// taipei : Taipei City Government +// https://www.iana.org/domains/root/db/taipei.html +taipei + +// talk : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/talk.html +talk + +// taobao : Alibaba Group Holding Limited +// https://www.iana.org/domains/root/db/taobao.html +taobao + +// target : Target Domain Holdings, LLC +// https://www.iana.org/domains/root/db/target.html +target + +// tatamotors : Tata Motors Ltd +// https://www.iana.org/domains/root/db/tatamotors.html +tatamotors + +// tatar : Limited Liability Company "Coordination Center of Regional Domain of Tatarstan Republic" +// https://www.iana.org/domains/root/db/tatar.html +tatar + +// tattoo : Registry Services, LLC +// https://www.iana.org/domains/root/db/tattoo.html +tattoo + +// tax : Binky Moon, LLC +// https://www.iana.org/domains/root/db/tax.html +tax + +// taxi : Binky Moon, LLC +// https://www.iana.org/domains/root/db/taxi.html +taxi + +// tci +// https://www.iana.org/domains/root/db/tci.html +tci + +// tdk : TDK Corporation +// https://www.iana.org/domains/root/db/tdk.html +tdk + +// team : Binky Moon, LLC +// https://www.iana.org/domains/root/db/team.html +team + +// tech : Radix Technologies Inc SEZC +// https://www.iana.org/domains/root/db/tech.html +tech + +// technology : Binky Moon, LLC +// https://www.iana.org/domains/root/db/technology.html +technology + +// temasek : Temasek Holdings (Private) Limited +// https://www.iana.org/domains/root/db/temasek.html +temasek + +// tennis : Binky Moon, LLC +// https://www.iana.org/domains/root/db/tennis.html +tennis + +// teva : Teva Pharmaceutical Industries Limited +// https://www.iana.org/domains/root/db/teva.html +teva + +// thd : Home Depot Product Authority, LLC +// https://www.iana.org/domains/root/db/thd.html +thd + +// theater : Binky Moon, LLC +// https://www.iana.org/domains/root/db/theater.html +theater + +// theatre : XYZ.COM LLC +// https://www.iana.org/domains/root/db/theatre.html +theatre + +// tiaa : Teachers Insurance and Annuity Association of America +// https://www.iana.org/domains/root/db/tiaa.html +tiaa + +// tickets : XYZ.COM LLC +// https://www.iana.org/domains/root/db/tickets.html +tickets + +// tienda : Binky Moon, LLC +// https://www.iana.org/domains/root/db/tienda.html +tienda + +// tips : Binky Moon, LLC +// https://www.iana.org/domains/root/db/tips.html +tips + +// tires : Binky Moon, LLC +// https://www.iana.org/domains/root/db/tires.html +tires + +// tirol : punkt Tirol GmbH +// https://www.iana.org/domains/root/db/tirol.html +tirol + +// tjmaxx : The TJX Companies, Inc. +// https://www.iana.org/domains/root/db/tjmaxx.html +tjmaxx + +// tjx : The TJX Companies, Inc. +// https://www.iana.org/domains/root/db/tjx.html +tjx + +// tkmaxx : The TJX Companies, Inc. +// https://www.iana.org/domains/root/db/tkmaxx.html +tkmaxx + +// tmall : Alibaba Group Holding Limited +// https://www.iana.org/domains/root/db/tmall.html +tmall + +// today : Binky Moon, LLC +// https://www.iana.org/domains/root/db/today.html +today + +// tokyo : GMO Registry, Inc. +// https://www.iana.org/domains/root/db/tokyo.html +tokyo + +// tools : Binky Moon, LLC +// https://www.iana.org/domains/root/db/tools.html +tools + +// top : .TOP Registry +// https://www.iana.org/domains/root/db/top.html +top + +// toray : Toray Industries, Inc. +// https://www.iana.org/domains/root/db/toray.html +toray + +// toshiba : TOSHIBA Corporation +// https://www.iana.org/domains/root/db/toshiba.html +toshiba + +// total : TotalEnergies SE +// https://www.iana.org/domains/root/db/total.html +total + +// tours : Binky Moon, LLC +// https://www.iana.org/domains/root/db/tours.html +tours + +// town : Binky Moon, LLC +// https://www.iana.org/domains/root/db/town.html +town + +// toyota : TOYOTA MOTOR CORPORATION +// https://www.iana.org/domains/root/db/toyota.html +toyota + +// toys : Binky Moon, LLC +// https://www.iana.org/domains/root/db/toys.html +toys + +// trade : Elite Registry Limited +// https://www.iana.org/domains/root/db/trade.html +trade + +// trading : Dog Beach, LLC +// https://www.iana.org/domains/root/db/trading.html +trading + +// training : Binky Moon, LLC +// https://www.iana.org/domains/root/db/training.html +training + +// travel : Dog Beach, LLC +// https://www.iana.org/domains/root/db/travel.html +travel + +// travelers : Travelers TLD, LLC +// https://www.iana.org/domains/root/db/travelers.html +travelers + +// travelersinsurance : Travelers TLD, LLC +// https://www.iana.org/domains/root/db/travelersinsurance.html +travelersinsurance + +// trust : Internet Naming Company LLC +// https://www.iana.org/domains/root/db/trust.html +trust + +// trv : Travelers TLD, LLC +// https://www.iana.org/domains/root/db/trv.html +trv + +// tube : Latin American Telecom LLC +// https://www.iana.org/domains/root/db/tube.html +tube + +// tui : TUI AG +// https://www.iana.org/domains/root/db/tui.html +tui + +// tunes : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/tunes.html +tunes + +// tushu : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/tushu.html +tushu + +// tvs : T V SUNDRAM IYENGAR & SONS LIMITED +// https://www.iana.org/domains/root/db/tvs.html +tvs + +// ubank : National Australia Bank Limited +// https://www.iana.org/domains/root/db/ubank.html +ubank + +// ubs : UBS AG +// https://www.iana.org/domains/root/db/ubs.html +ubs + +// unicom : China United Network Communications Corporation Limited +// https://www.iana.org/domains/root/db/unicom.html +unicom + +// university : Binky Moon, LLC +// https://www.iana.org/domains/root/db/university.html +university + +// uno : Radix Technologies Inc SEZC +// https://www.iana.org/domains/root/db/uno.html +uno + +// uol : UBN INTERNET LTDA. +// https://www.iana.org/domains/root/db/uol.html +uol + +// ups : UPS Market Driver, Inc. +// https://www.iana.org/domains/root/db/ups.html +ups + +// vacations : Binky Moon, LLC +// https://www.iana.org/domains/root/db/vacations.html +vacations + +// vana : D3 Registry LLC +// https://www.iana.org/domains/root/db/vana.html +vana + +// vanguard : The Vanguard Group, Inc. +// https://www.iana.org/domains/root/db/vanguard.html +vanguard + +// vegas : Dot Vegas, Inc. +// https://www.iana.org/domains/root/db/vegas.html +vegas + +// ventures : Binky Moon, LLC +// https://www.iana.org/domains/root/db/ventures.html +ventures + +// verisign : VeriSign, Inc. +// https://www.iana.org/domains/root/db/verisign.html +verisign + +// versicherung : tldbox GmbH +// https://www.iana.org/domains/root/db/versicherung.html +versicherung + +// vet : Dog Beach, LLC +// https://www.iana.org/domains/root/db/vet.html +vet + +// viajes : Binky Moon, LLC +// https://www.iana.org/domains/root/db/viajes.html +viajes + +// video : Dog Beach, LLC +// https://www.iana.org/domains/root/db/video.html +video + +// vig : VIENNA INSURANCE GROUP AG Wiener Versicherung Gruppe +// https://www.iana.org/domains/root/db/vig.html +vig + +// viking : Viking River Cruises (Bermuda) Ltd. +// https://www.iana.org/domains/root/db/viking.html +viking + +// villas : Binky Moon, LLC +// https://www.iana.org/domains/root/db/villas.html +villas + +// vin : Binky Moon, LLC +// https://www.iana.org/domains/root/db/vin.html +vin + +// vip : Registry Services, LLC +// https://www.iana.org/domains/root/db/vip.html +vip + +// virgin : Virgin Enterprises Limited +// https://www.iana.org/domains/root/db/virgin.html +virgin + +// visa : Visa Worldwide Pte. Limited +// https://www.iana.org/domains/root/db/visa.html +visa + +// vision : Binky Moon, LLC +// https://www.iana.org/domains/root/db/vision.html +vision + +// viva : Saudi Telecom Company +// https://www.iana.org/domains/root/db/viva.html +viva + +// vivo : Telefonica Brasil S.A. +// https://www.iana.org/domains/root/db/vivo.html +vivo + +// vlaanderen : DNS.be vzw +// https://www.iana.org/domains/root/db/vlaanderen.html +vlaanderen + +// vodka : Registry Services, LLC +// https://www.iana.org/domains/root/db/vodka.html +vodka + +// volvo : Volvo Holding Sverige Aktiebolag +// https://www.iana.org/domains/root/db/volvo.html +volvo + +// vote : Monolith Registry LLC +// https://www.iana.org/domains/root/db/vote.html +vote + +// voting : Valuetainment Corp. +// https://www.iana.org/domains/root/db/voting.html +voting + +// voto : Monolith Registry LLC +// https://www.iana.org/domains/root/db/voto.html +voto + +// voyage : Binky Moon, LLC +// https://www.iana.org/domains/root/db/voyage.html +voyage + +// wales : Nominet UK +// https://www.iana.org/domains/root/db/wales.html +wales + +// walmart : Wal-Mart Stores, Inc. +// https://www.iana.org/domains/root/db/walmart.html +walmart + +// walter : Sandvik AB +// https://www.iana.org/domains/root/db/walter.html +walter + +// wang : Zodiac Wang Limited +// https://www.iana.org/domains/root/db/wang.html +wang + +// wanggou : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/wanggou.html +wanggou + +// watch : Binky Moon, LLC +// https://www.iana.org/domains/root/db/watch.html +watch + +// watches : Identity Digital Domains Limited +// https://www.iana.org/domains/root/db/watches.html +watches + +// weather : International Business Machines Corporation +// https://www.iana.org/domains/root/db/weather.html +weather + +// weatherchannel : The Weather Company, LLC +// https://www.iana.org/domains/root/db/weatherchannel.html +weatherchannel + +// webcam : dot Webcam Limited +// https://www.iana.org/domains/root/db/webcam.html +webcam + +// weber : Saint-Gobain Weber SA +// https://www.iana.org/domains/root/db/weber.html +weber + +// website : Radix Technologies Inc SEZC +// https://www.iana.org/domains/root/db/website.html +website + +// wed +// https://www.iana.org/domains/root/db/wed.html +wed + +// wedding : Registry Services, LLC +// https://www.iana.org/domains/root/db/wedding.html +wedding + +// weibo : Sina Corporation +// https://www.iana.org/domains/root/db/weibo.html +weibo + +// weir : Weir Group IP Limited +// https://www.iana.org/domains/root/db/weir.html +weir + +// whoswho : Who's Who Registry +// https://www.iana.org/domains/root/db/whoswho.html +whoswho + +// wien : punkt.wien GmbH +// https://www.iana.org/domains/root/db/wien.html +wien + +// wiki : Registry Services, LLC +// https://www.iana.org/domains/root/db/wiki.html +wiki + +// williamhill : William Hill Organization Limited +// https://www.iana.org/domains/root/db/williamhill.html +williamhill + +// win : First Registry Limited +// https://www.iana.org/domains/root/db/win.html +win + +// windows : Microsoft Corporation +// https://www.iana.org/domains/root/db/windows.html +windows + +// wine : Binky Moon, LLC +// https://www.iana.org/domains/root/db/wine.html +wine + +// winners : The TJX Companies, Inc. +// https://www.iana.org/domains/root/db/winners.html +winners + +// wme : William Morris Endeavor Entertainment, LLC +// https://www.iana.org/domains/root/db/wme.html +wme + +// wolterskluwer : Wolters Kluwer N.V. +// https://www.iana.org/domains/root/db/wolterskluwer.html +wolterskluwer + +// woodside : Woodside Petroleum Limited +// https://www.iana.org/domains/root/db/woodside.html +woodside + +// work : Registry Services, LLC +// https://www.iana.org/domains/root/db/work.html +work + +// works : Binky Moon, LLC +// https://www.iana.org/domains/root/db/works.html +works + +// world : Binky Moon, LLC +// https://www.iana.org/domains/root/db/world.html +world + +// wow : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/wow.html +wow + +// wtc : World Trade Centers Association, Inc. +// https://www.iana.org/domains/root/db/wtc.html +wtc + +// wtf : Binky Moon, LLC +// https://www.iana.org/domains/root/db/wtf.html +wtf + +// xbox : Microsoft Corporation +// https://www.iana.org/domains/root/db/xbox.html +xbox + +// xerox : Xerox DNHC LLC +// https://www.iana.org/domains/root/db/xerox.html +xerox + +// xihuan : Beijing Qihu Keji Co., Ltd. +// https://www.iana.org/domains/root/db/xihuan.html +xihuan + +// xin : Elegant Leader Limited +// https://www.iana.org/domains/root/db/xin.html +xin + +// xn--11b4c3d : VeriSign Sarl +// https://www.iana.org/domains/root/db/xn--11b4c3d.html +कॉम + +// xn--1ck2e1b : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/xn--1ck2e1b.html +セール + +// xn--1qqw23a : Guangzhou YU Wei Information Technology Co., Ltd. +// https://www.iana.org/domains/root/db/xn--1qqw23a.html +佛山 + +// xn--30rr7y : Excellent First Limited +// https://www.iana.org/domains/root/db/xn--30rr7y.html +慈善 + +// xn--3bst00m : Eagle Horizon Limited +// https://www.iana.org/domains/root/db/xn--3bst00m.html +集团 + +// xn--3ds443g : Beijing TLD Registry Technology Limited +// https://www.iana.org/domains/root/db/xn--3ds443g.html +在线 + +// xn--3pxu8k : VeriSign Sarl +// https://www.iana.org/domains/root/db/xn--3pxu8k.html +点看 + +// xn--42c2d9a : VeriSign Sarl +// https://www.iana.org/domains/root/db/xn--42c2d9a.html +คอม + +// xn--45q11c : Zodiac Gemini Ltd +// https://www.iana.org/domains/root/db/xn--45q11c.html +八卦 + +// xn--4gbrim : Helium TLDs Ltd +// https://www.iana.org/domains/root/db/xn--4gbrim.html +موقع + +// xn--55qw42g : China Organizational Name Administration Center +// https://www.iana.org/domains/root/db/xn--55qw42g.html +公益 + +// xn--55qx5d : China Internet Network Information Center (CNNIC) +// https://www.iana.org/domains/root/db/xn--55qx5d.html +公司 + +// xn--5su34j936bgsg : Shangri‐La International Hotel Management Limited +// https://www.iana.org/domains/root/db/xn--5su34j936bgsg.html +香格里拉 + +// xn--5tzm5g : Global Website TLD Asia Limited +// https://www.iana.org/domains/root/db/xn--5tzm5g.html +网站 + +// xn--6frz82g : Identity Digital Domains Limited +// https://www.iana.org/domains/root/db/xn--6frz82g.html +移动 + +// xn--6qq986b3xl : Tycoon Treasure Limited +// https://www.iana.org/domains/root/db/xn--6qq986b3xl.html +我爱你 + +// xn--80adxhks : Foundation for Assistance for Internet Technologies and Infrastructure Development (FAITID) +// https://www.iana.org/domains/root/db/xn--80adxhks.html +москва + +// xn--80aqecdr1a : Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication) +// https://www.iana.org/domains/root/db/xn--80aqecdr1a.html +католик + +// xn--80asehdb : CORE Association +// https://www.iana.org/domains/root/db/xn--80asehdb.html +онлайн + +// xn--80aswg : CORE Association +// https://www.iana.org/domains/root/db/xn--80aswg.html +сайт + +// xn--8y0a063a : China United Network Communications Corporation Limited +// https://www.iana.org/domains/root/db/xn--8y0a063a.html +联通 + +// xn--9dbq2a : VeriSign Sarl +// https://www.iana.org/domains/root/db/xn--9dbq2a.html +קום + +// xn--9et52u : RISE VICTORY LIMITED +// https://www.iana.org/domains/root/db/xn--9et52u.html +时尚 + +// xn--9krt00a : Sina Corporation +// https://www.iana.org/domains/root/db/xn--9krt00a.html +微博 + +// xn--b4w605ferd : Temasek Holdings (Private) Limited +// https://www.iana.org/domains/root/db/xn--b4w605ferd.html +淡马锡 + +// xn--bck1b9a5dre4c : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/xn--bck1b9a5dre4c.html +ファッション + +// xn--c1avg : Public Interest Registry +// https://www.iana.org/domains/root/db/xn--c1avg.html +орг + +// xn--c2br7g : VeriSign Sarl +// https://www.iana.org/domains/root/db/xn--c2br7g.html +नेट + +// xn--cck2b3b : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/xn--cck2b3b.html +ストア + +// xn--cckwcxetd : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/xn--cckwcxetd.html +アマゾン + +// xn--cg4bki : SAMSUNG SDS CO., LTD +// https://www.iana.org/domains/root/db/xn--cg4bki.html +삼성 + +// xn--czr694b : Internet DotTrademark Organisation Limited +// https://www.iana.org/domains/root/db/xn--czr694b.html +商标 + +// xn--czrs0t : Binky Moon, LLC +// https://www.iana.org/domains/root/db/xn--czrs0t.html +商店 + +// xn--czru2d : Zodiac Aquarius Limited +// https://www.iana.org/domains/root/db/xn--czru2d.html +商城 + +// xn--d1acj3b : The Foundation for Network Initiatives “The Smart Internet” +// https://www.iana.org/domains/root/db/xn--d1acj3b.html +дети + +// xn--eckvdtc9d : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/xn--eckvdtc9d.html +ポイント + +// xn--efvy88h : Guangzhou YU Wei Information Technology Co., Ltd. +// https://www.iana.org/domains/root/db/xn--efvy88h.html +新闻 + +// xn--fct429k : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/xn--fct429k.html +家電 + +// xn--fhbei : VeriSign Sarl +// https://www.iana.org/domains/root/db/xn--fhbei.html +كوم + +// xn--fiq228c5hs : TLD REGISTRY LIMITED OY +// https://www.iana.org/domains/root/db/xn--fiq228c5hs.html +中文网 + +// xn--fiq64b : CITIC Group Corporation +// https://www.iana.org/domains/root/db/xn--fiq64b.html +中信 + +// xn--fjq720a : Binky Moon, LLC +// https://www.iana.org/domains/root/db/xn--fjq720a.html +娱乐 + +// xn--flw351e : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/xn--flw351e.html +谷歌 + +// xn--fzys8d69uvgm : PCCW Enterprises Limited +// https://www.iana.org/domains/root/db/xn--fzys8d69uvgm.html +電訊盈科 + +// xn--g2xx48c : Nawang Heli(Xiamen) Network Service Co., LTD. +// https://www.iana.org/domains/root/db/xn--g2xx48c.html +购物 + +// xn--gckr3f0f : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/xn--gckr3f0f.html +クラウド + +// xn--gk3at1e : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/xn--gk3at1e.html +通販 + +// xn--hxt814e : Zodiac Taurus Limited +// https://www.iana.org/domains/root/db/xn--hxt814e.html +网店 + +// xn--i1b6b1a6a2e : Public Interest Registry +// https://www.iana.org/domains/root/db/xn--i1b6b1a6a2e.html +संगठन + +// xn--imr513n : Internet DotTrademark Organisation Limited +// https://www.iana.org/domains/root/db/xn--imr513n.html +餐厅 + +// xn--io0a7i : China Internet Network Information Center (CNNIC) +// https://www.iana.org/domains/root/db/xn--io0a7i.html +网络 + +// xn--j1aef : VeriSign Sarl +// https://www.iana.org/domains/root/db/xn--j1aef.html +ком + +// xn--jlq480n2rg : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/xn--jlq480n2rg.html +亚马逊 + +// xn--jvr189m : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/xn--jvr189m.html +食品 + +// xn--kcrx77d1x4a : Koninklijke Philips N.V. +// https://www.iana.org/domains/root/db/xn--kcrx77d1x4a.html +飞利浦 + +// xn--kput3i : Beijing RITT-Net Technology Development Co., Ltd +// https://www.iana.org/domains/root/db/xn--kput3i.html +手机 + +// xn--mgba3a3ejt : Aramco Services Company +// https://www.iana.org/domains/root/db/xn--mgba3a3ejt.html +ارامكو + +// xn--mgba7c0bbn0a : Competrol (Luxembourg) Sarl +// https://www.iana.org/domains/root/db/xn--mgba7c0bbn0a.html +العليان + +// xn--mgbab2bd : CORE Association +// https://www.iana.org/domains/root/db/xn--mgbab2bd.html +بازار + +// xn--mgbca7dzdo : Abu Dhabi Systems and Information Centre +// https://www.iana.org/domains/root/db/xn--mgbca7dzdo.html +ابوظبي + +// xn--mgbi4ecexp : Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication) +// https://www.iana.org/domains/root/db/xn--mgbi4ecexp.html +كاثوليك + +// xn--mgbt3dhd +// https://www.iana.org/domains/root/db/xn--mgbt3dhd.html +همراه + +// xn--mk1bu44c : VeriSign Sarl +// https://www.iana.org/domains/root/db/xn--mk1bu44c.html +닷컴 + +// xn--mxtq1m : Net-Chinese Co., Ltd. +// https://www.iana.org/domains/root/db/xn--mxtq1m.html +政府 + +// xn--ngbc5azd : International Domain Registry Pty. Ltd. +// https://www.iana.org/domains/root/db/xn--ngbc5azd.html +شبكة + +// xn--ngbe9e0a : Kuwait Finance House +// https://www.iana.org/domains/root/db/xn--ngbe9e0a.html +بيتك + +// xn--ngbrx : League of Arab States +// https://www.iana.org/domains/root/db/xn--ngbrx.html +عرب + +// xn--nqv7f : Public Interest Registry +// https://www.iana.org/domains/root/db/xn--nqv7f.html +机构 + +// xn--nqv7fs00ema : Public Interest Registry +// https://www.iana.org/domains/root/db/xn--nqv7fs00ema.html +组织机构 + +// xn--nyqy26a : Stable Tone Limited +// https://www.iana.org/domains/root/db/xn--nyqy26a.html +健康 + +// xn--otu796d : Jiang Yu Liang Cai Technology Company Limited +// https://www.iana.org/domains/root/db/xn--otu796d.html +招聘 + +// xn--p1acf : Rusnames Limited +// https://www.iana.org/domains/root/db/xn--p1acf.html +рус + +// xn--pssy2u : VeriSign Sarl +// https://www.iana.org/domains/root/db/xn--pssy2u.html +大拿 + +// xn--q9jyb4c : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/xn--q9jyb4c.html +みんな + +// xn--qcka1pmc : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/xn--qcka1pmc.html +グーグル + +// xn--rhqv96g : Stable Tone Limited +// https://www.iana.org/domains/root/db/xn--rhqv96g.html +世界 + +// xn--rovu88b : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/xn--rovu88b.html +書籍 + +// xn--ses554g : KNET Co., Ltd. +// https://www.iana.org/domains/root/db/xn--ses554g.html +网址 + +// xn--t60b56a : VeriSign Sarl +// https://www.iana.org/domains/root/db/xn--t60b56a.html +닷넷 + +// xn--tckwe : VeriSign Sarl +// https://www.iana.org/domains/root/db/xn--tckwe.html +コム + +// xn--tiq49xqyj : Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication) +// https://www.iana.org/domains/root/db/xn--tiq49xqyj.html +天主教 + +// xn--unup4y : Binky Moon, LLC +// https://www.iana.org/domains/root/db/xn--unup4y.html +游戏 + +// xn--vermgensberater-ctb : Deutsche Vermögensberatung Aktiengesellschaft DVAG +// https://www.iana.org/domains/root/db/xn--vermgensberater-ctb.html +vermögensberater + +// xn--vermgensberatung-pwb : Deutsche Vermögensberatung Aktiengesellschaft DVAG +// https://www.iana.org/domains/root/db/xn--vermgensberatung-pwb.html +vermögensberatung + +// xn--vhquv : Binky Moon, LLC +// https://www.iana.org/domains/root/db/xn--vhquv.html +企业 + +// xn--vuq861b : Beijing Tele-info Technology Co., Ltd. +// https://www.iana.org/domains/root/db/xn--vuq861b.html +信息 + +// xn--w4r85el8fhu5dnra : Kerry Trading Co. Limited +// https://www.iana.org/domains/root/db/xn--w4r85el8fhu5dnra.html +嘉里大酒店 + +// xn--w4rs40l : Kerry Trading Co. Limited +// https://www.iana.org/domains/root/db/xn--w4rs40l.html +嘉里 + +// xn--xhq521b : Guangzhou YU Wei Information Technology Co., Ltd. +// https://www.iana.org/domains/root/db/xn--xhq521b.html +广东 + +// xn--zfr164b : China Organizational Name Administration Center +// https://www.iana.org/domains/root/db/xn--zfr164b.html +政务 + +// xyz : XYZ.COM LLC +// https://www.iana.org/domains/root/db/xyz.html +xyz + +// yachts : XYZ.COM LLC +// https://www.iana.org/domains/root/db/yachts.html +yachts + +// yahoo : Yahoo Inc. +// https://www.iana.org/domains/root/db/yahoo.html +yahoo + +// yamaxun : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/yamaxun.html +yamaxun + +// yandex : YANDEX, LLC +// https://www.iana.org/domains/root/db/yandex.html +yandex + +// yodobashi : YODOBASHI CAMERA CO.,LTD. +// https://www.iana.org/domains/root/db/yodobashi.html +yodobashi + +// yoga : Registry Services, LLC +// https://www.iana.org/domains/root/db/yoga.html +yoga + +// yokohama : GMO Registry, Inc. +// https://www.iana.org/domains/root/db/yokohama.html +yokohama + +// you : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/you.html +you + +// youtube : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/youtube.html +youtube + +// yun : Beijing Qihu Keji Co., Ltd. +// https://www.iana.org/domains/root/db/yun.html +yun + +// zappos : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/zappos.html +zappos + +// zara : Industria de Diseño Textil, S.A. (INDITEX, S.A.) +// https://www.iana.org/domains/root/db/zara.html +zara + +// zero : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/zero.html +zero + +// zip : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/zip.html +zip + +// zone : Binky Moon, LLC +// https://www.iana.org/domains/root/db/zone.html +zone + +// zuerich : Kanton Zürich (Canton of Zurich) +// https://www.iana.org/domains/root/db/zuerich.html +zuerich + +// ===END ICANN DOMAINS=== + +// ===BEGIN PRIVATE DOMAINS=== + +// (Note: these are in alphabetical order by company name) + +// .KRD : https://nic.krd +co.krd +edu.krd + +// .pl domains (grandfathered) +art.pl +gliwice.pl +krakow.pl +poznan.pl +wroc.pl +zakopane.pl + +// 12CHARS : https://12chars.com +// Submitted by Kenny Niehage +12chars.dev +12chars.it +12chars.pro + +// 1GB LLC : https://www.1gb.ua/ +// Submitted by 1GB LLC +cc.ua +inf.ua +ltd.ua + +// 611 blockchain domain name system : https://sixone.one/ +611.to + +// A2 Hosting +// Submitted by Tyler Hall +a2hosted.com +cpserver.com + +// Acorn Labs : https://acorn.io +// Submitted by Craig Jellick +*.on-acorn.io + +// ActiveTrail : https://www.activetrail.biz/ +// Submitted by Ofer Kalaora +activetrail.biz + +// Adaptable.io : https://adaptable.io +// Submitted by Mark Terrel +adaptable.app + +// addr.tools : https://addr.tools/ +// Submitted by Brian Shea +myaddr.dev +myaddr.io +dyn.addr.tools +myaddr.tools + +// Adobe : https://www.adobe.com/ +// Submitted by Ian Boston and Lars Trieloff +adobeaemcloud.com +*.dev.adobeaemcloud.com +aem.live +hlx.live +adobeaemcloud.net +aem.network +aem.page +hlx.page +aem.reviews + +// Adobe Developer Platform : https://developer.adobe.com +// Submitted by Jesse MacFadyen +adobeio-static.net +adobeioruntime.net + +// Africa.com Web Solutions Ltd : https://registry.africa.com +// Submitted by Gavin Brown +africa.com + +// Agnat sp. z o.o. : https://domena.pl +// Submitted by Przemyslaw Plewa +beep.pl + +// Aiven : https://aiven.io/ +// Submitted by Aiven Security Team +aiven.app +aivencloud.com + +// Akamai : https://www.akamai.com/ +// Submitted by Akamai Team +akadns.net +akamai.net +akamai-staging.net +akamaiedge.net +akamaiedge-staging.net +akamaihd.net +akamaihd-staging.net +akamaiorigin.net +akamaiorigin-staging.net +akamaized.net +akamaized-staging.net +edgekey.net +edgekey-staging.net +edgesuite.net +edgesuite-staging.net + +// alboto.ca : http://alboto.ca +// Submitted by Anton Avramov +barsy.ca + +// Alces Software Ltd : http://alces-software.com +// Submitted by Mark J. Titorenko +*.compute.estate +*.alces.network + +// Alibaba Cloud API Gateway +// Submitted by Alibaba Cloud Security +alibabacloudcs.com + +// all-inkl.com : https://all-inkl.com +// Submitted by Werner Kaltofen +kasserver.com + +// Altervista : https://www.altervista.org +// Submitted by Carlo Cannas +altervista.org + +// alwaysdata : https://www.alwaysdata.com +// Submitted by Cyril +alwaysdata.net + +// Amaze Software : https://amaze.co +// Submitted by Domain Admin +myamaze.net + +// Amazon : https://www.amazon.com/ +// Submitted by AWS Security +// Subsections of Amazon/subsidiaries will appear until "concludes" tag + +// Amazon API Gateway +// Submitted by AWS Security +// Reference: 6a4f5a95-8c7d-4077-a7af-9cf1abec0a53 +execute-api.cn-north-1.amazonaws.com.cn +execute-api.cn-northwest-1.amazonaws.com.cn +execute-api.af-south-1.amazonaws.com +execute-api.ap-east-1.amazonaws.com +execute-api.ap-northeast-1.amazonaws.com +execute-api.ap-northeast-2.amazonaws.com +execute-api.ap-northeast-3.amazonaws.com +execute-api.ap-south-1.amazonaws.com +execute-api.ap-south-2.amazonaws.com +execute-api.ap-southeast-1.amazonaws.com +execute-api.ap-southeast-2.amazonaws.com +execute-api.ap-southeast-3.amazonaws.com +execute-api.ap-southeast-4.amazonaws.com +execute-api.ap-southeast-5.amazonaws.com +execute-api.ca-central-1.amazonaws.com +execute-api.ca-west-1.amazonaws.com +execute-api.eu-central-1.amazonaws.com +execute-api.eu-central-2.amazonaws.com +execute-api.eu-north-1.amazonaws.com +execute-api.eu-south-1.amazonaws.com +execute-api.eu-south-2.amazonaws.com +execute-api.eu-west-1.amazonaws.com +execute-api.eu-west-2.amazonaws.com +execute-api.eu-west-3.amazonaws.com +execute-api.il-central-1.amazonaws.com +execute-api.me-central-1.amazonaws.com +execute-api.me-south-1.amazonaws.com +execute-api.sa-east-1.amazonaws.com +execute-api.us-east-1.amazonaws.com +execute-api.us-east-2.amazonaws.com +execute-api.us-gov-east-1.amazonaws.com +execute-api.us-gov-west-1.amazonaws.com +execute-api.us-west-1.amazonaws.com +execute-api.us-west-2.amazonaws.com + +// Amazon CloudFront +// Submitted by Donavan Miller +// Reference: 54144616-fd49-4435-8535-19c6a601bdb3 +cloudfront.net + +// Amazon Cognito +// Submitted by AWS Security +// Reference: e7c02dc1-02f4-4a23-bde3-a8527c830127 +auth.af-south-1.amazoncognito.com +auth.ap-east-1.amazoncognito.com +auth.ap-northeast-1.amazoncognito.com +auth.ap-northeast-2.amazoncognito.com +auth.ap-northeast-3.amazoncognito.com +auth.ap-south-1.amazoncognito.com +auth.ap-south-2.amazoncognito.com +auth.ap-southeast-1.amazoncognito.com +auth.ap-southeast-2.amazoncognito.com +auth.ap-southeast-3.amazoncognito.com +auth.ap-southeast-4.amazoncognito.com +auth.ap-southeast-5.amazoncognito.com +auth.ap-southeast-7.amazoncognito.com +auth.ca-central-1.amazoncognito.com +auth.ca-west-1.amazoncognito.com +auth.eu-central-1.amazoncognito.com +auth.eu-central-2.amazoncognito.com +auth.eu-north-1.amazoncognito.com +auth.eu-south-1.amazoncognito.com +auth.eu-south-2.amazoncognito.com +auth.eu-west-1.amazoncognito.com +auth.eu-west-2.amazoncognito.com +auth.eu-west-3.amazoncognito.com +auth.il-central-1.amazoncognito.com +auth.me-central-1.amazoncognito.com +auth.me-south-1.amazoncognito.com +auth.mx-central-1.amazoncognito.com +auth.sa-east-1.amazoncognito.com +auth.us-east-1.amazoncognito.com +auth-fips.us-east-1.amazoncognito.com +auth.us-east-2.amazoncognito.com +auth-fips.us-east-2.amazoncognito.com +auth-fips.us-gov-east-1.amazoncognito.com +auth-fips.us-gov-west-1.amazoncognito.com +auth.us-west-1.amazoncognito.com +auth-fips.us-west-1.amazoncognito.com +auth.us-west-2.amazoncognito.com +auth-fips.us-west-2.amazoncognito.com + +// Amazon EC2 +// Submitted by Luke Wells +// Reference: 4c38fa71-58ac-4768-99e5-689c1767e537 +*.compute.amazonaws.com.cn +*.compute.amazonaws.com +*.compute-1.amazonaws.com +us-east-1.amazonaws.com + +// Amazon EMR +// Submitted by AWS Security +// Reference: 82f43f9f-bbb8-400e-8349-854f5a62f20d +emrappui-prod.cn-north-1.amazonaws.com.cn +emrnotebooks-prod.cn-north-1.amazonaws.com.cn +emrstudio-prod.cn-north-1.amazonaws.com.cn +emrappui-prod.cn-northwest-1.amazonaws.com.cn +emrnotebooks-prod.cn-northwest-1.amazonaws.com.cn +emrstudio-prod.cn-northwest-1.amazonaws.com.cn +emrappui-prod.af-south-1.amazonaws.com +emrnotebooks-prod.af-south-1.amazonaws.com +emrstudio-prod.af-south-1.amazonaws.com +emrappui-prod.ap-east-1.amazonaws.com +emrnotebooks-prod.ap-east-1.amazonaws.com +emrstudio-prod.ap-east-1.amazonaws.com +emrappui-prod.ap-northeast-1.amazonaws.com +emrnotebooks-prod.ap-northeast-1.amazonaws.com +emrstudio-prod.ap-northeast-1.amazonaws.com +emrappui-prod.ap-northeast-2.amazonaws.com +emrnotebooks-prod.ap-northeast-2.amazonaws.com +emrstudio-prod.ap-northeast-2.amazonaws.com +emrappui-prod.ap-northeast-3.amazonaws.com +emrnotebooks-prod.ap-northeast-3.amazonaws.com +emrstudio-prod.ap-northeast-3.amazonaws.com +emrappui-prod.ap-south-1.amazonaws.com +emrnotebooks-prod.ap-south-1.amazonaws.com +emrstudio-prod.ap-south-1.amazonaws.com +emrappui-prod.ap-south-2.amazonaws.com +emrnotebooks-prod.ap-south-2.amazonaws.com +emrstudio-prod.ap-south-2.amazonaws.com +emrappui-prod.ap-southeast-1.amazonaws.com +emrnotebooks-prod.ap-southeast-1.amazonaws.com +emrstudio-prod.ap-southeast-1.amazonaws.com +emrappui-prod.ap-southeast-2.amazonaws.com +emrnotebooks-prod.ap-southeast-2.amazonaws.com +emrstudio-prod.ap-southeast-2.amazonaws.com +emrappui-prod.ap-southeast-3.amazonaws.com +emrnotebooks-prod.ap-southeast-3.amazonaws.com +emrstudio-prod.ap-southeast-3.amazonaws.com +emrappui-prod.ap-southeast-4.amazonaws.com +emrnotebooks-prod.ap-southeast-4.amazonaws.com +emrstudio-prod.ap-southeast-4.amazonaws.com +emrappui-prod.ca-central-1.amazonaws.com +emrnotebooks-prod.ca-central-1.amazonaws.com +emrstudio-prod.ca-central-1.amazonaws.com +emrappui-prod.ca-west-1.amazonaws.com +emrnotebooks-prod.ca-west-1.amazonaws.com +emrstudio-prod.ca-west-1.amazonaws.com +emrappui-prod.eu-central-1.amazonaws.com +emrnotebooks-prod.eu-central-1.amazonaws.com +emrstudio-prod.eu-central-1.amazonaws.com +emrappui-prod.eu-central-2.amazonaws.com +emrnotebooks-prod.eu-central-2.amazonaws.com +emrstudio-prod.eu-central-2.amazonaws.com +emrappui-prod.eu-north-1.amazonaws.com +emrnotebooks-prod.eu-north-1.amazonaws.com +emrstudio-prod.eu-north-1.amazonaws.com +emrappui-prod.eu-south-1.amazonaws.com +emrnotebooks-prod.eu-south-1.amazonaws.com +emrstudio-prod.eu-south-1.amazonaws.com +emrappui-prod.eu-south-2.amazonaws.com +emrnotebooks-prod.eu-south-2.amazonaws.com +emrstudio-prod.eu-south-2.amazonaws.com +emrappui-prod.eu-west-1.amazonaws.com +emrnotebooks-prod.eu-west-1.amazonaws.com +emrstudio-prod.eu-west-1.amazonaws.com +emrappui-prod.eu-west-2.amazonaws.com +emrnotebooks-prod.eu-west-2.amazonaws.com +emrstudio-prod.eu-west-2.amazonaws.com +emrappui-prod.eu-west-3.amazonaws.com +emrnotebooks-prod.eu-west-3.amazonaws.com +emrstudio-prod.eu-west-3.amazonaws.com +emrappui-prod.il-central-1.amazonaws.com +emrnotebooks-prod.il-central-1.amazonaws.com +emrstudio-prod.il-central-1.amazonaws.com +emrappui-prod.me-central-1.amazonaws.com +emrnotebooks-prod.me-central-1.amazonaws.com +emrstudio-prod.me-central-1.amazonaws.com +emrappui-prod.me-south-1.amazonaws.com +emrnotebooks-prod.me-south-1.amazonaws.com +emrstudio-prod.me-south-1.amazonaws.com +emrappui-prod.sa-east-1.amazonaws.com +emrnotebooks-prod.sa-east-1.amazonaws.com +emrstudio-prod.sa-east-1.amazonaws.com +emrappui-prod.us-east-1.amazonaws.com +emrnotebooks-prod.us-east-1.amazonaws.com +emrstudio-prod.us-east-1.amazonaws.com +emrappui-prod.us-east-2.amazonaws.com +emrnotebooks-prod.us-east-2.amazonaws.com +emrstudio-prod.us-east-2.amazonaws.com +emrappui-prod.us-gov-east-1.amazonaws.com +emrnotebooks-prod.us-gov-east-1.amazonaws.com +emrstudio-prod.us-gov-east-1.amazonaws.com +emrappui-prod.us-gov-west-1.amazonaws.com +emrnotebooks-prod.us-gov-west-1.amazonaws.com +emrstudio-prod.us-gov-west-1.amazonaws.com +emrappui-prod.us-west-1.amazonaws.com +emrnotebooks-prod.us-west-1.amazonaws.com +emrstudio-prod.us-west-1.amazonaws.com +emrappui-prod.us-west-2.amazonaws.com +emrnotebooks-prod.us-west-2.amazonaws.com +emrstudio-prod.us-west-2.amazonaws.com + +// Amazon Managed Workflows for Apache Airflow +// Submitted by AWS Security +// Reference: 2f697e23-58d6-4b97-be6b-77a26e811dad +*.airflow.af-south-1.on.aws +*.airflow.ap-east-1.on.aws +*.airflow.ap-northeast-1.on.aws +*.airflow.ap-northeast-2.on.aws +*.airflow.ap-northeast-3.on.aws +*.airflow.ap-south-1.on.aws +*.airflow.ap-south-2.on.aws +*.airflow.ap-southeast-1.on.aws +*.airflow.ap-southeast-2.on.aws +*.airflow.ap-southeast-3.on.aws +*.airflow.ap-southeast-4.on.aws +*.airflow.ap-southeast-5.on.aws +*.airflow.ca-central-1.on.aws +*.airflow.ca-west-1.on.aws +*.airflow.eu-central-1.on.aws +*.airflow.eu-central-2.on.aws +*.airflow.eu-north-1.on.aws +*.airflow.eu-south-1.on.aws +*.airflow.eu-south-2.on.aws +*.airflow.eu-west-1.on.aws +*.airflow.eu-west-2.on.aws +*.airflow.eu-west-3.on.aws +*.airflow.il-central-1.on.aws +*.airflow.me-central-1.on.aws +*.airflow.me-south-1.on.aws +*.airflow.sa-east-1.on.aws +*.airflow.us-east-1.on.aws +*.airflow.us-east-2.on.aws +*.airflow.us-west-1.on.aws +*.airflow.us-west-2.on.aws +*.cn-north-1.airflow.amazonaws.com.cn +*.cn-northwest-1.airflow.amazonaws.com.cn +*.airflow.cn-north-1.on.amazonwebservices.com.cn +*.airflow.cn-northwest-1.on.amazonwebservices.com.cn +*.af-south-1.airflow.amazonaws.com +*.ap-east-1.airflow.amazonaws.com +*.ap-northeast-1.airflow.amazonaws.com +*.ap-northeast-2.airflow.amazonaws.com +*.ap-northeast-3.airflow.amazonaws.com +*.ap-south-1.airflow.amazonaws.com +*.ap-south-2.airflow.amazonaws.com +*.ap-southeast-1.airflow.amazonaws.com +*.ap-southeast-2.airflow.amazonaws.com +*.ap-southeast-3.airflow.amazonaws.com +*.ap-southeast-4.airflow.amazonaws.com +*.ap-southeast-5.airflow.amazonaws.com +*.ca-central-1.airflow.amazonaws.com +*.ca-west-1.airflow.amazonaws.com +*.eu-central-1.airflow.amazonaws.com +*.eu-central-2.airflow.amazonaws.com +*.eu-north-1.airflow.amazonaws.com +*.eu-south-1.airflow.amazonaws.com +*.eu-south-2.airflow.amazonaws.com +*.eu-west-1.airflow.amazonaws.com +*.eu-west-2.airflow.amazonaws.com +*.eu-west-3.airflow.amazonaws.com +*.il-central-1.airflow.amazonaws.com +*.me-central-1.airflow.amazonaws.com +*.me-south-1.airflow.amazonaws.com +*.sa-east-1.airflow.amazonaws.com +*.us-east-1.airflow.amazonaws.com +*.us-east-2.airflow.amazonaws.com +*.us-west-1.airflow.amazonaws.com +*.us-west-2.airflow.amazonaws.com + +// Amazon S3 +// Submitted by AWS Security +// Reference: ada5c9df-55e1-4195-a1ce-732d6c81e357 +s3.dualstack.cn-north-1.amazonaws.com.cn +s3-accesspoint.dualstack.cn-north-1.amazonaws.com.cn +s3-website.dualstack.cn-north-1.amazonaws.com.cn +s3.cn-north-1.amazonaws.com.cn +s3-accesspoint.cn-north-1.amazonaws.com.cn +s3-deprecated.cn-north-1.amazonaws.com.cn +s3-object-lambda.cn-north-1.amazonaws.com.cn +s3-website.cn-north-1.amazonaws.com.cn +s3.dualstack.cn-northwest-1.amazonaws.com.cn +s3-accesspoint.dualstack.cn-northwest-1.amazonaws.com.cn +s3.cn-northwest-1.amazonaws.com.cn +s3-accesspoint.cn-northwest-1.amazonaws.com.cn +s3-object-lambda.cn-northwest-1.amazonaws.com.cn +s3-website.cn-northwest-1.amazonaws.com.cn +s3.dualstack.af-south-1.amazonaws.com +s3-accesspoint.dualstack.af-south-1.amazonaws.com +s3-website.dualstack.af-south-1.amazonaws.com +s3.af-south-1.amazonaws.com +s3-accesspoint.af-south-1.amazonaws.com +s3-object-lambda.af-south-1.amazonaws.com +s3-website.af-south-1.amazonaws.com +s3.dualstack.ap-east-1.amazonaws.com +s3-accesspoint.dualstack.ap-east-1.amazonaws.com +s3.ap-east-1.amazonaws.com +s3-accesspoint.ap-east-1.amazonaws.com +s3-object-lambda.ap-east-1.amazonaws.com +s3-website.ap-east-1.amazonaws.com +s3.dualstack.ap-northeast-1.amazonaws.com +s3-accesspoint.dualstack.ap-northeast-1.amazonaws.com +s3-website.dualstack.ap-northeast-1.amazonaws.com +s3.ap-northeast-1.amazonaws.com +s3-accesspoint.ap-northeast-1.amazonaws.com +s3-object-lambda.ap-northeast-1.amazonaws.com +s3-website.ap-northeast-1.amazonaws.com +s3.dualstack.ap-northeast-2.amazonaws.com +s3-accesspoint.dualstack.ap-northeast-2.amazonaws.com +s3-website.dualstack.ap-northeast-2.amazonaws.com +s3.ap-northeast-2.amazonaws.com +s3-accesspoint.ap-northeast-2.amazonaws.com +s3-object-lambda.ap-northeast-2.amazonaws.com +s3-website.ap-northeast-2.amazonaws.com +s3.dualstack.ap-northeast-3.amazonaws.com +s3-accesspoint.dualstack.ap-northeast-3.amazonaws.com +s3-website.dualstack.ap-northeast-3.amazonaws.com +s3.ap-northeast-3.amazonaws.com +s3-accesspoint.ap-northeast-3.amazonaws.com +s3-object-lambda.ap-northeast-3.amazonaws.com +s3-website.ap-northeast-3.amazonaws.com +s3.dualstack.ap-south-1.amazonaws.com +s3-accesspoint.dualstack.ap-south-1.amazonaws.com +s3-website.dualstack.ap-south-1.amazonaws.com +s3.ap-south-1.amazonaws.com +s3-accesspoint.ap-south-1.amazonaws.com +s3-object-lambda.ap-south-1.amazonaws.com +s3-website.ap-south-1.amazonaws.com +s3.dualstack.ap-south-2.amazonaws.com +s3-accesspoint.dualstack.ap-south-2.amazonaws.com +s3-website.dualstack.ap-south-2.amazonaws.com +s3.ap-south-2.amazonaws.com +s3-accesspoint.ap-south-2.amazonaws.com +s3-object-lambda.ap-south-2.amazonaws.com +s3-website.ap-south-2.amazonaws.com +s3.dualstack.ap-southeast-1.amazonaws.com +s3-accesspoint.dualstack.ap-southeast-1.amazonaws.com +s3-website.dualstack.ap-southeast-1.amazonaws.com +s3.ap-southeast-1.amazonaws.com +s3-accesspoint.ap-southeast-1.amazonaws.com +s3-object-lambda.ap-southeast-1.amazonaws.com +s3-website.ap-southeast-1.amazonaws.com +s3.dualstack.ap-southeast-2.amazonaws.com +s3-accesspoint.dualstack.ap-southeast-2.amazonaws.com +s3-website.dualstack.ap-southeast-2.amazonaws.com +s3.ap-southeast-2.amazonaws.com +s3-accesspoint.ap-southeast-2.amazonaws.com +s3-object-lambda.ap-southeast-2.amazonaws.com +s3-website.ap-southeast-2.amazonaws.com +s3.dualstack.ap-southeast-3.amazonaws.com +s3-accesspoint.dualstack.ap-southeast-3.amazonaws.com +s3-website.dualstack.ap-southeast-3.amazonaws.com +s3.ap-southeast-3.amazonaws.com +s3-accesspoint.ap-southeast-3.amazonaws.com +s3-object-lambda.ap-southeast-3.amazonaws.com +s3-website.ap-southeast-3.amazonaws.com +s3.dualstack.ap-southeast-4.amazonaws.com +s3-accesspoint.dualstack.ap-southeast-4.amazonaws.com +s3-website.dualstack.ap-southeast-4.amazonaws.com +s3.ap-southeast-4.amazonaws.com +s3-accesspoint.ap-southeast-4.amazonaws.com +s3-object-lambda.ap-southeast-4.amazonaws.com +s3-website.ap-southeast-4.amazonaws.com +s3.dualstack.ap-southeast-5.amazonaws.com +s3-accesspoint.dualstack.ap-southeast-5.amazonaws.com +s3-website.dualstack.ap-southeast-5.amazonaws.com +s3.ap-southeast-5.amazonaws.com +s3-accesspoint.ap-southeast-5.amazonaws.com +s3-deprecated.ap-southeast-5.amazonaws.com +s3-object-lambda.ap-southeast-5.amazonaws.com +s3-website.ap-southeast-5.amazonaws.com +s3.dualstack.ca-central-1.amazonaws.com +s3-accesspoint.dualstack.ca-central-1.amazonaws.com +s3-accesspoint-fips.dualstack.ca-central-1.amazonaws.com +s3-fips.dualstack.ca-central-1.amazonaws.com +s3-website.dualstack.ca-central-1.amazonaws.com +s3.ca-central-1.amazonaws.com +s3-accesspoint.ca-central-1.amazonaws.com +s3-accesspoint-fips.ca-central-1.amazonaws.com +s3-fips.ca-central-1.amazonaws.com +s3-object-lambda.ca-central-1.amazonaws.com +s3-website.ca-central-1.amazonaws.com +s3.dualstack.ca-west-1.amazonaws.com +s3-accesspoint.dualstack.ca-west-1.amazonaws.com +s3-accesspoint-fips.dualstack.ca-west-1.amazonaws.com +s3-fips.dualstack.ca-west-1.amazonaws.com +s3-website.dualstack.ca-west-1.amazonaws.com +s3.ca-west-1.amazonaws.com +s3-accesspoint.ca-west-1.amazonaws.com +s3-accesspoint-fips.ca-west-1.amazonaws.com +s3-fips.ca-west-1.amazonaws.com +s3-object-lambda.ca-west-1.amazonaws.com +s3-website.ca-west-1.amazonaws.com +s3.dualstack.eu-central-1.amazonaws.com +s3-accesspoint.dualstack.eu-central-1.amazonaws.com +s3-website.dualstack.eu-central-1.amazonaws.com +s3.eu-central-1.amazonaws.com +s3-accesspoint.eu-central-1.amazonaws.com +s3-object-lambda.eu-central-1.amazonaws.com +s3-website.eu-central-1.amazonaws.com +s3.dualstack.eu-central-2.amazonaws.com +s3-accesspoint.dualstack.eu-central-2.amazonaws.com +s3-website.dualstack.eu-central-2.amazonaws.com +s3.eu-central-2.amazonaws.com +s3-accesspoint.eu-central-2.amazonaws.com +s3-object-lambda.eu-central-2.amazonaws.com +s3-website.eu-central-2.amazonaws.com +s3.dualstack.eu-north-1.amazonaws.com +s3-accesspoint.dualstack.eu-north-1.amazonaws.com +s3.eu-north-1.amazonaws.com +s3-accesspoint.eu-north-1.amazonaws.com +s3-object-lambda.eu-north-1.amazonaws.com +s3-website.eu-north-1.amazonaws.com +s3.dualstack.eu-south-1.amazonaws.com +s3-accesspoint.dualstack.eu-south-1.amazonaws.com +s3-website.dualstack.eu-south-1.amazonaws.com +s3.eu-south-1.amazonaws.com +s3-accesspoint.eu-south-1.amazonaws.com +s3-object-lambda.eu-south-1.amazonaws.com +s3-website.eu-south-1.amazonaws.com +s3.dualstack.eu-south-2.amazonaws.com +s3-accesspoint.dualstack.eu-south-2.amazonaws.com +s3-website.dualstack.eu-south-2.amazonaws.com +s3.eu-south-2.amazonaws.com +s3-accesspoint.eu-south-2.amazonaws.com +s3-object-lambda.eu-south-2.amazonaws.com +s3-website.eu-south-2.amazonaws.com +s3.dualstack.eu-west-1.amazonaws.com +s3-accesspoint.dualstack.eu-west-1.amazonaws.com +s3-website.dualstack.eu-west-1.amazonaws.com +s3.eu-west-1.amazonaws.com +s3-accesspoint.eu-west-1.amazonaws.com +s3-deprecated.eu-west-1.amazonaws.com +s3-object-lambda.eu-west-1.amazonaws.com +s3-website.eu-west-1.amazonaws.com +s3.dualstack.eu-west-2.amazonaws.com +s3-accesspoint.dualstack.eu-west-2.amazonaws.com +s3.eu-west-2.amazonaws.com +s3-accesspoint.eu-west-2.amazonaws.com +s3-object-lambda.eu-west-2.amazonaws.com +s3-website.eu-west-2.amazonaws.com +s3.dualstack.eu-west-3.amazonaws.com +s3-accesspoint.dualstack.eu-west-3.amazonaws.com +s3-website.dualstack.eu-west-3.amazonaws.com +s3.eu-west-3.amazonaws.com +s3-accesspoint.eu-west-3.amazonaws.com +s3-object-lambda.eu-west-3.amazonaws.com +s3-website.eu-west-3.amazonaws.com +s3.dualstack.il-central-1.amazonaws.com +s3-accesspoint.dualstack.il-central-1.amazonaws.com +s3-website.dualstack.il-central-1.amazonaws.com +s3.il-central-1.amazonaws.com +s3-accesspoint.il-central-1.amazonaws.com +s3-object-lambda.il-central-1.amazonaws.com +s3-website.il-central-1.amazonaws.com +s3.dualstack.me-central-1.amazonaws.com +s3-accesspoint.dualstack.me-central-1.amazonaws.com +s3-website.dualstack.me-central-1.amazonaws.com +s3.me-central-1.amazonaws.com +s3-accesspoint.me-central-1.amazonaws.com +s3-object-lambda.me-central-1.amazonaws.com +s3-website.me-central-1.amazonaws.com +s3.dualstack.me-south-1.amazonaws.com +s3-accesspoint.dualstack.me-south-1.amazonaws.com +s3.me-south-1.amazonaws.com +s3-accesspoint.me-south-1.amazonaws.com +s3-object-lambda.me-south-1.amazonaws.com +s3-website.me-south-1.amazonaws.com +s3.amazonaws.com +s3-1.amazonaws.com +s3-ap-east-1.amazonaws.com +s3-ap-northeast-1.amazonaws.com +s3-ap-northeast-2.amazonaws.com +s3-ap-northeast-3.amazonaws.com +s3-ap-south-1.amazonaws.com +s3-ap-southeast-1.amazonaws.com +s3-ap-southeast-2.amazonaws.com +s3-ca-central-1.amazonaws.com +s3-eu-central-1.amazonaws.com +s3-eu-north-1.amazonaws.com +s3-eu-west-1.amazonaws.com +s3-eu-west-2.amazonaws.com +s3-eu-west-3.amazonaws.com +s3-external-1.amazonaws.com +s3-fips-us-gov-east-1.amazonaws.com +s3-fips-us-gov-west-1.amazonaws.com +mrap.accesspoint.s3-global.amazonaws.com +s3-me-south-1.amazonaws.com +s3-sa-east-1.amazonaws.com +s3-us-east-2.amazonaws.com +s3-us-gov-east-1.amazonaws.com +s3-us-gov-west-1.amazonaws.com +s3-us-west-1.amazonaws.com +s3-us-west-2.amazonaws.com +s3-website-ap-northeast-1.amazonaws.com +s3-website-ap-southeast-1.amazonaws.com +s3-website-ap-southeast-2.amazonaws.com +s3-website-eu-west-1.amazonaws.com +s3-website-sa-east-1.amazonaws.com +s3-website-us-east-1.amazonaws.com +s3-website-us-gov-west-1.amazonaws.com +s3-website-us-west-1.amazonaws.com +s3-website-us-west-2.amazonaws.com +s3.dualstack.sa-east-1.amazonaws.com +s3-accesspoint.dualstack.sa-east-1.amazonaws.com +s3-website.dualstack.sa-east-1.amazonaws.com +s3.sa-east-1.amazonaws.com +s3-accesspoint.sa-east-1.amazonaws.com +s3-object-lambda.sa-east-1.amazonaws.com +s3-website.sa-east-1.amazonaws.com +s3.dualstack.us-east-1.amazonaws.com +s3-accesspoint.dualstack.us-east-1.amazonaws.com +s3-accesspoint-fips.dualstack.us-east-1.amazonaws.com +s3-fips.dualstack.us-east-1.amazonaws.com +s3-website.dualstack.us-east-1.amazonaws.com +s3.us-east-1.amazonaws.com +s3-accesspoint.us-east-1.amazonaws.com +s3-accesspoint-fips.us-east-1.amazonaws.com +s3-deprecated.us-east-1.amazonaws.com +s3-fips.us-east-1.amazonaws.com +s3-object-lambda.us-east-1.amazonaws.com +s3-website.us-east-1.amazonaws.com +s3.dualstack.us-east-2.amazonaws.com +s3-accesspoint.dualstack.us-east-2.amazonaws.com +s3-accesspoint-fips.dualstack.us-east-2.amazonaws.com +s3-fips.dualstack.us-east-2.amazonaws.com +s3-website.dualstack.us-east-2.amazonaws.com +s3.us-east-2.amazonaws.com +s3-accesspoint.us-east-2.amazonaws.com +s3-accesspoint-fips.us-east-2.amazonaws.com +s3-deprecated.us-east-2.amazonaws.com +s3-fips.us-east-2.amazonaws.com +s3-object-lambda.us-east-2.amazonaws.com +s3-website.us-east-2.amazonaws.com +s3.dualstack.us-gov-east-1.amazonaws.com +s3-accesspoint.dualstack.us-gov-east-1.amazonaws.com +s3-accesspoint-fips.dualstack.us-gov-east-1.amazonaws.com +s3-fips.dualstack.us-gov-east-1.amazonaws.com +s3.us-gov-east-1.amazonaws.com +s3-accesspoint.us-gov-east-1.amazonaws.com +s3-accesspoint-fips.us-gov-east-1.amazonaws.com +s3-fips.us-gov-east-1.amazonaws.com +s3-object-lambda.us-gov-east-1.amazonaws.com +s3-website.us-gov-east-1.amazonaws.com +s3.dualstack.us-gov-west-1.amazonaws.com +s3-accesspoint.dualstack.us-gov-west-1.amazonaws.com +s3-accesspoint-fips.dualstack.us-gov-west-1.amazonaws.com +s3-fips.dualstack.us-gov-west-1.amazonaws.com +s3.us-gov-west-1.amazonaws.com +s3-accesspoint.us-gov-west-1.amazonaws.com +s3-accesspoint-fips.us-gov-west-1.amazonaws.com +s3-fips.us-gov-west-1.amazonaws.com +s3-object-lambda.us-gov-west-1.amazonaws.com +s3-website.us-gov-west-1.amazonaws.com +s3.dualstack.us-west-1.amazonaws.com +s3-accesspoint.dualstack.us-west-1.amazonaws.com +s3-accesspoint-fips.dualstack.us-west-1.amazonaws.com +s3-fips.dualstack.us-west-1.amazonaws.com +s3-website.dualstack.us-west-1.amazonaws.com +s3.us-west-1.amazonaws.com +s3-accesspoint.us-west-1.amazonaws.com +s3-accesspoint-fips.us-west-1.amazonaws.com +s3-fips.us-west-1.amazonaws.com +s3-object-lambda.us-west-1.amazonaws.com +s3-website.us-west-1.amazonaws.com +s3.dualstack.us-west-2.amazonaws.com +s3-accesspoint.dualstack.us-west-2.amazonaws.com +s3-accesspoint-fips.dualstack.us-west-2.amazonaws.com +s3-fips.dualstack.us-west-2.amazonaws.com +s3-website.dualstack.us-west-2.amazonaws.com +s3.us-west-2.amazonaws.com +s3-accesspoint.us-west-2.amazonaws.com +s3-accesspoint-fips.us-west-2.amazonaws.com +s3-deprecated.us-west-2.amazonaws.com +s3-fips.us-west-2.amazonaws.com +s3-object-lambda.us-west-2.amazonaws.com +s3-website.us-west-2.amazonaws.com + +// Amazon SageMaker Ground Truth +// Submitted by AWS Security +// Reference: 98dbfde4-7802-48c3-8751-b60f204e0d9c +labeling.ap-northeast-1.sagemaker.aws +labeling.ap-northeast-2.sagemaker.aws +labeling.ap-south-1.sagemaker.aws +labeling.ap-southeast-1.sagemaker.aws +labeling.ap-southeast-2.sagemaker.aws +labeling.ca-central-1.sagemaker.aws +labeling.eu-central-1.sagemaker.aws +labeling.eu-west-1.sagemaker.aws +labeling.eu-west-2.sagemaker.aws +labeling.us-east-1.sagemaker.aws +labeling.us-east-2.sagemaker.aws +labeling.us-west-2.sagemaker.aws + +// Amazon SageMaker Notebook Instances +// Submitted by AWS Security +// Reference: b5ea56df-669e-43cc-9537-14aa172f5dfc +notebook.af-south-1.sagemaker.aws +notebook.ap-east-1.sagemaker.aws +notebook.ap-northeast-1.sagemaker.aws +notebook.ap-northeast-2.sagemaker.aws +notebook.ap-northeast-3.sagemaker.aws +notebook.ap-south-1.sagemaker.aws +notebook.ap-south-2.sagemaker.aws +notebook.ap-southeast-1.sagemaker.aws +notebook.ap-southeast-2.sagemaker.aws +notebook.ap-southeast-3.sagemaker.aws +notebook.ap-southeast-4.sagemaker.aws +notebook.ca-central-1.sagemaker.aws +notebook-fips.ca-central-1.sagemaker.aws +notebook.ca-west-1.sagemaker.aws +notebook-fips.ca-west-1.sagemaker.aws +notebook.eu-central-1.sagemaker.aws +notebook.eu-central-2.sagemaker.aws +notebook.eu-north-1.sagemaker.aws +notebook.eu-south-1.sagemaker.aws +notebook.eu-south-2.sagemaker.aws +notebook.eu-west-1.sagemaker.aws +notebook.eu-west-2.sagemaker.aws +notebook.eu-west-3.sagemaker.aws +notebook.il-central-1.sagemaker.aws +notebook.me-central-1.sagemaker.aws +notebook.me-south-1.sagemaker.aws +notebook.sa-east-1.sagemaker.aws +notebook.us-east-1.sagemaker.aws +notebook-fips.us-east-1.sagemaker.aws +notebook.us-east-2.sagemaker.aws +notebook-fips.us-east-2.sagemaker.aws +notebook.us-gov-east-1.sagemaker.aws +notebook-fips.us-gov-east-1.sagemaker.aws +notebook.us-gov-west-1.sagemaker.aws +notebook-fips.us-gov-west-1.sagemaker.aws +notebook.us-west-1.sagemaker.aws +notebook-fips.us-west-1.sagemaker.aws +notebook.us-west-2.sagemaker.aws +notebook-fips.us-west-2.sagemaker.aws +notebook.cn-north-1.sagemaker.com.cn +notebook.cn-northwest-1.sagemaker.com.cn + +// Amazon SageMaker Studio +// Submitted by AWS Security +// Reference: 475f237e-ab88-4041-9f41-7cfccdf66aeb +studio.af-south-1.sagemaker.aws +studio.ap-east-1.sagemaker.aws +studio.ap-northeast-1.sagemaker.aws +studio.ap-northeast-2.sagemaker.aws +studio.ap-northeast-3.sagemaker.aws +studio.ap-south-1.sagemaker.aws +studio.ap-southeast-1.sagemaker.aws +studio.ap-southeast-2.sagemaker.aws +studio.ap-southeast-3.sagemaker.aws +studio.ca-central-1.sagemaker.aws +studio.eu-central-1.sagemaker.aws +studio.eu-central-2.sagemaker.aws +studio.eu-north-1.sagemaker.aws +studio.eu-south-1.sagemaker.aws +studio.eu-south-2.sagemaker.aws +studio.eu-west-1.sagemaker.aws +studio.eu-west-2.sagemaker.aws +studio.eu-west-3.sagemaker.aws +studio.il-central-1.sagemaker.aws +studio.me-central-1.sagemaker.aws +studio.me-south-1.sagemaker.aws +studio.sa-east-1.sagemaker.aws +studio.us-east-1.sagemaker.aws +studio.us-east-2.sagemaker.aws +studio.us-gov-east-1.sagemaker.aws +studio-fips.us-gov-east-1.sagemaker.aws +studio.us-gov-west-1.sagemaker.aws +studio-fips.us-gov-west-1.sagemaker.aws +studio.us-west-1.sagemaker.aws +studio.us-west-2.sagemaker.aws +studio.cn-north-1.sagemaker.com.cn +studio.cn-northwest-1.sagemaker.com.cn + +// Amazon SageMaker with MLflow +// Submited by: AWS Security +// Reference: c19f92b3-a82a-452d-8189-831b572eea7e +*.experiments.sagemaker.aws + +// Analytics on AWS +// Submitted by AWS Security +// Reference: 955f9f40-a495-4e73-ae85-67b77ac9cadd +analytics-gateway.ap-northeast-1.amazonaws.com +analytics-gateway.ap-northeast-2.amazonaws.com +analytics-gateway.ap-south-1.amazonaws.com +analytics-gateway.ap-southeast-1.amazonaws.com +analytics-gateway.ap-southeast-2.amazonaws.com +analytics-gateway.eu-central-1.amazonaws.com +analytics-gateway.eu-west-1.amazonaws.com +analytics-gateway.us-east-1.amazonaws.com +analytics-gateway.us-east-2.amazonaws.com +analytics-gateway.us-west-2.amazonaws.com + +// AWS Amplify +// Submitted by AWS Security +// Reference: c35bed18-6f4f-424f-9298-5756f2f7d72b +amplifyapp.com + +// AWS App Runner +// Submitted by AWS Security +// Reference: 6828c008-ba5d-442f-ade5-48da4e7c2316 +*.awsapprunner.com + +// AWS Cloud9 +// Submitted by: AWS Security +// Reference: 30717f72-4007-4f0f-8ed4-864c6f2efec9 +webview-assets.aws-cloud9.af-south-1.amazonaws.com +vfs.cloud9.af-south-1.amazonaws.com +webview-assets.cloud9.af-south-1.amazonaws.com +webview-assets.aws-cloud9.ap-east-1.amazonaws.com +vfs.cloud9.ap-east-1.amazonaws.com +webview-assets.cloud9.ap-east-1.amazonaws.com +webview-assets.aws-cloud9.ap-northeast-1.amazonaws.com +vfs.cloud9.ap-northeast-1.amazonaws.com +webview-assets.cloud9.ap-northeast-1.amazonaws.com +webview-assets.aws-cloud9.ap-northeast-2.amazonaws.com +vfs.cloud9.ap-northeast-2.amazonaws.com +webview-assets.cloud9.ap-northeast-2.amazonaws.com +webview-assets.aws-cloud9.ap-northeast-3.amazonaws.com +vfs.cloud9.ap-northeast-3.amazonaws.com +webview-assets.cloud9.ap-northeast-3.amazonaws.com +webview-assets.aws-cloud9.ap-south-1.amazonaws.com +vfs.cloud9.ap-south-1.amazonaws.com +webview-assets.cloud9.ap-south-1.amazonaws.com +webview-assets.aws-cloud9.ap-southeast-1.amazonaws.com +vfs.cloud9.ap-southeast-1.amazonaws.com +webview-assets.cloud9.ap-southeast-1.amazonaws.com +webview-assets.aws-cloud9.ap-southeast-2.amazonaws.com +vfs.cloud9.ap-southeast-2.amazonaws.com +webview-assets.cloud9.ap-southeast-2.amazonaws.com +webview-assets.aws-cloud9.ca-central-1.amazonaws.com +vfs.cloud9.ca-central-1.amazonaws.com +webview-assets.cloud9.ca-central-1.amazonaws.com +webview-assets.aws-cloud9.eu-central-1.amazonaws.com +vfs.cloud9.eu-central-1.amazonaws.com +webview-assets.cloud9.eu-central-1.amazonaws.com +webview-assets.aws-cloud9.eu-north-1.amazonaws.com +vfs.cloud9.eu-north-1.amazonaws.com +webview-assets.cloud9.eu-north-1.amazonaws.com +webview-assets.aws-cloud9.eu-south-1.amazonaws.com +vfs.cloud9.eu-south-1.amazonaws.com +webview-assets.cloud9.eu-south-1.amazonaws.com +webview-assets.aws-cloud9.eu-west-1.amazonaws.com +vfs.cloud9.eu-west-1.amazonaws.com +webview-assets.cloud9.eu-west-1.amazonaws.com +webview-assets.aws-cloud9.eu-west-2.amazonaws.com +vfs.cloud9.eu-west-2.amazonaws.com +webview-assets.cloud9.eu-west-2.amazonaws.com +webview-assets.aws-cloud9.eu-west-3.amazonaws.com +vfs.cloud9.eu-west-3.amazonaws.com +webview-assets.cloud9.eu-west-3.amazonaws.com +webview-assets.aws-cloud9.il-central-1.amazonaws.com +vfs.cloud9.il-central-1.amazonaws.com +webview-assets.aws-cloud9.me-south-1.amazonaws.com +vfs.cloud9.me-south-1.amazonaws.com +webview-assets.cloud9.me-south-1.amazonaws.com +webview-assets.aws-cloud9.sa-east-1.amazonaws.com +vfs.cloud9.sa-east-1.amazonaws.com +webview-assets.cloud9.sa-east-1.amazonaws.com +webview-assets.aws-cloud9.us-east-1.amazonaws.com +vfs.cloud9.us-east-1.amazonaws.com +webview-assets.cloud9.us-east-1.amazonaws.com +webview-assets.aws-cloud9.us-east-2.amazonaws.com +vfs.cloud9.us-east-2.amazonaws.com +webview-assets.cloud9.us-east-2.amazonaws.com +webview-assets.aws-cloud9.us-west-1.amazonaws.com +vfs.cloud9.us-west-1.amazonaws.com +webview-assets.cloud9.us-west-1.amazonaws.com +webview-assets.aws-cloud9.us-west-2.amazonaws.com +vfs.cloud9.us-west-2.amazonaws.com +webview-assets.cloud9.us-west-2.amazonaws.com + +// AWS Directory Service +// Submitted by AWS Security +// Reference: a13203e8-42dc-4045-a0d2-2ee67bed1068 +awsapps.com + +// AWS Elastic Beanstalk +// Submitted by AWS Security +// Reference: bb5a965c-dec3-4967-aa22-e306ad064797 +cn-north-1.eb.amazonaws.com.cn +cn-northwest-1.eb.amazonaws.com.cn +elasticbeanstalk.com +af-south-1.elasticbeanstalk.com +ap-east-1.elasticbeanstalk.com +ap-northeast-1.elasticbeanstalk.com +ap-northeast-2.elasticbeanstalk.com +ap-northeast-3.elasticbeanstalk.com +ap-south-1.elasticbeanstalk.com +ap-southeast-1.elasticbeanstalk.com +ap-southeast-2.elasticbeanstalk.com +ap-southeast-3.elasticbeanstalk.com +ca-central-1.elasticbeanstalk.com +eu-central-1.elasticbeanstalk.com +eu-north-1.elasticbeanstalk.com +eu-south-1.elasticbeanstalk.com +eu-west-1.elasticbeanstalk.com +eu-west-2.elasticbeanstalk.com +eu-west-3.elasticbeanstalk.com +il-central-1.elasticbeanstalk.com +me-south-1.elasticbeanstalk.com +sa-east-1.elasticbeanstalk.com +us-east-1.elasticbeanstalk.com +us-east-2.elasticbeanstalk.com +us-gov-east-1.elasticbeanstalk.com +us-gov-west-1.elasticbeanstalk.com +us-west-1.elasticbeanstalk.com +us-west-2.elasticbeanstalk.com + +// (AWS) Elastic Load Balancing +// Submitted by Luke Wells +// Reference: 12a3d528-1bac-4433-a359-a395867ffed2 +*.elb.amazonaws.com.cn +*.elb.amazonaws.com + +// AWS Global Accelerator +// Submitted by Daniel Massaguer +// Reference: d916759d-a08b-4241-b536-4db887383a6a +awsglobalaccelerator.com + +// AWS re:Post Private +// Submitted by AWS Security +// Reference: 83385945-225f-416e-9aa0-ad0632bfdcee +*.private.repost.aws + +// AWS Transfer Family web apps +// Submitted by AWS Security +// Reference: 57a658c4-8899-410c-aa24-5b01e4a178d2 +transfer-webapp.af-south-1.on.aws +transfer-webapp.ap-east-1.on.aws +transfer-webapp.ap-northeast-1.on.aws +transfer-webapp.ap-northeast-2.on.aws +transfer-webapp.ap-northeast-3.on.aws +transfer-webapp.ap-south-1.on.aws +transfer-webapp.ap-south-2.on.aws +transfer-webapp.ap-southeast-1.on.aws +transfer-webapp.ap-southeast-2.on.aws +transfer-webapp.ap-southeast-3.on.aws +transfer-webapp.ap-southeast-4.on.aws +transfer-webapp.ap-southeast-5.on.aws +transfer-webapp.ca-central-1.on.aws +transfer-webapp.ca-west-1.on.aws +transfer-webapp.eu-central-1.on.aws +transfer-webapp.eu-central-2.on.aws +transfer-webapp.eu-north-1.on.aws +transfer-webapp.eu-south-1.on.aws +transfer-webapp.eu-south-2.on.aws +transfer-webapp.eu-west-1.on.aws +transfer-webapp.eu-west-2.on.aws +transfer-webapp.eu-west-3.on.aws +transfer-webapp.il-central-1.on.aws +transfer-webapp.me-central-1.on.aws +transfer-webapp.me-south-1.on.aws +transfer-webapp.sa-east-1.on.aws +transfer-webapp.us-east-1.on.aws +transfer-webapp.us-east-2.on.aws +transfer-webapp.us-gov-east-1.on.aws +transfer-webapp-fips.us-gov-east-1.on.aws +transfer-webapp.us-gov-west-1.on.aws +transfer-webapp-fips.us-gov-west-1.on.aws +transfer-webapp.us-west-1.on.aws +transfer-webapp.us-west-2.on.aws +transfer-webapp.cn-north-1.on.amazonwebservices.com.cn +transfer-webapp.cn-northwest-1.on.amazonwebservices.com.cn + +// eero +// Submitted by Yue Kang +// Reference: 264afe70-f62c-4c02-8ab9-b5281ed24461 +eero.online +eero-stage.online + +// concludes Amazon + +// Apigee : https://apigee.com/ +// Submitted by Apigee Security Team +apigee.io + +// Apis Networks : https://apisnetworks.com +// Submitted by Matt Saladna +panel.dev + +// Apphud : https://apphud.com +// Submitted by Alexander Selivanov +siiites.com + +// Appspace : https://www.appspace.com +// Submitted by Appspace Security Team +appspacehosted.com +appspaceusercontent.com + +// Appudo UG (haftungsbeschränkt) : https://www.appudo.com +// Submitted by Alexander Hochbaum +appudo.net + +// Appwrite : https://appwrite.io +// Submitted by Steven Nguyen +appwrite.global +*.appwrite.run + +// Aptible : https://www.aptible.com/ +// Submitted by Thomas Orozco +on-aptible.com + +// Aquapal : https://aquapal.net/ +// Submitted by Aki Ueno +f5.si + +// ArvanCloud EdgeCompute +// Submitted by ArvanCloud CDN +arvanedge.ir + +// ASEINet : https://www.aseinet.com/ +// Submitted by Asei SEKIGUCHI +user.aseinet.ne.jp +gv.vc +d.gv.vc + +// Asociación Amigos de la Informática "Euskalamiga" : http://encounter.eus/ +// Submitted by Hector Martin +user.party.eus + +// Association potager.org : https://potager.org/ +// Submitted by Lunar +pimienta.org +poivron.org +potager.org +sweetpepper.org + +// ASUSTOR Inc. : http://www.asustor.com +// Submitted by Vincent Tseng +myasustor.com + +// Atlassian : https://atlassian.com +// Submitted by Sam Smyth +cdn.prod.atlassian-dev.net + +// Authentick UG (haftungsbeschränkt) : https://authentick.net +// Submitted by Lukas Reschke +translated.page + +// AVM : https://avm.de +// Submitted by Andreas Weise +myfritz.link +myfritz.net + +// AVStack Pte. Ltd. : https://avstack.io +// Submitted by Jasper Hugo +onavstack.net + +// AW AdvisorWebsites.com Software Inc : https://advisorwebsites.com +// Submitted by James Kennedy +*.awdev.ca +*.advisor.ws + +// AZ.pl sp. z.o.o : https://az.pl +// Submitted by Krzysztof Wolski +ecommerce-shop.pl + +// b-data GmbH : https://www.b-data.io +// Submitted by Olivier Benz +b-data.io + +// Balena : https://www.balena.io +// Submitted by Petros Angelatos +balena-devices.com + +// BASE, Inc. : https://binc.jp +// Submitted by Yuya NAGASAWA +base.ec +official.ec +buyshop.jp +fashionstore.jp +handcrafted.jp +kawaiishop.jp +supersale.jp +theshop.jp +shopselect.net +base.shop + +// BeagleBoard.org Foundation : https://beagleboard.org +// Submitted by Jason Kridner +beagleboard.io + +// Beget Ltd +// Submitted by Lev Nekrasov +*.beget.app + +// Besties : https://besties.house +// Submitted by Hazel Cora +pages.gay + +// BinaryLane : http://www.binarylane.com +// Submitted by Nathan O'Sullivan +bnr.la + +// Bitbucket : http://bitbucket.org +// Submitted by Andy Ortlieb +bitbucket.io + +// Blackbaud, Inc. : https://www.blackbaud.com +// Submitted by Paul Crowder +blackbaudcdn.net + +// Blatech : http://www.blatech.net +// Submitted by Luke Bratch +of.je + +// Block, Inc. : https://block.xyz +// Submitted by Jonathan Boice +square.site + +// Blue Bite, LLC : https://bluebite.com +// Submitted by Joshua Weiss +bluebite.io + +// Boomla : https://boomla.com +// Submitted by Tibor Halter +boomla.net + +// Boutir : https://www.boutir.com +// Submitted by Eric Ng Ka Ka +boutir.com + +// Boxfuse : https://boxfuse.com +// Submitted by Axel Fontaine +boxfuse.io + +// bplaced : https://www.bplaced.net/ +// Submitted by Miroslav Bozic +square7.ch +bplaced.com +bplaced.de +square7.de +bplaced.net +square7.net + +// Brave : https://brave.com +// Submitted by Andrea Brancaleoni +brave.app +*.s.brave.app +brave.io +*.s.brave.io + +// Brendly : https://brendly.rs +// Submitted by Dusan Radovanovic +shop.brendly.ba +shop.brendly.hr +shop.brendly.rs + +// BrowserSafetyMark +// Submitted by Dave Tharp +browsersafetymark.io + +// BRS Media : https://brsmedia.com/ +// Submitted by Gavin Brown +radio.am +radio.fm + +// Bubble : https://bubble.io/ +// Submitted by Merlin Zhao +cdn.bubble.io +bubbleapps.io + +// Bytemark Hosting : https://www.bytemark.co.uk +// Submitted by Paul Cammish +uk0.bigv.io +dh.bytemark.co.uk +vm.bytemark.co.uk + +// Caf.js Labs LLC : https://www.cafjs.com +// Submitted by Antonio Lain +cafjs.com + +// Canva Pty Ltd : https://canva.com/ +// Submitted by Joel Aquilina +canva-apps.cn +my.canvasite.cn +canva-apps.com +canva-hosted-embed.com +canvacode.com +rice-labs.com +canva.run +my.canva.site + +// Carrd : https://carrd.co +// Submitted by AJ +drr.ac +uwu.ai +carrd.co +crd.co +ju.mp + +// CDDO : https://www.gov.uk/guidance/get-an-api-domain-on-govuk +// Submitted by Jamie Tanna +api.gov.uk + +// CDN77.com : http://www.cdn77.com +// Submitted by Jan Krpes +cdn77-storage.com +rsc.contentproxy9.cz +r.cdn77.net +cdn77-ssl.net +c.cdn77.org +rsc.cdn77.org +ssl.origin.cdn77-secure.org + +// CentralNic : https://teaminternet.com/ +// Submitted by registry +za.bz +br.com +cn.com +de.com +eu.com +jpn.com +mex.com +ru.com +sa.com +uk.com +us.com +za.com +com.de +gb.net +hu.net +jp.net +se.net +uk.net +ae.org +com.se + +// Cityhost LLC : https://cityhost.ua +// Submitted by Maksym Rivtin +cx.ua + +// Civilized Discourse Construction Kit, Inc. : https://www.discourse.org/ +// Submitted by Rishabh Nambiar & Michael Brown +discourse.group +discourse.team + +// Clerk : https://www.clerk.dev +// Submitted by Colin Sidoti +clerk.app +clerkstage.app +*.lcl.dev +*.lclstage.dev +*.stg.dev +*.stgstage.dev + +// Clever Cloud : https://www.clever-cloud.com/ +// Submitted by Quentin Adam +cleverapps.cc +*.services.clever-cloud.com +cleverapps.io +cleverapps.tech + +// ClickRising : https://clickrising.com/ +// Submitted by Umut Gumeli +clickrising.net + +// Cloud DNS Ltd : http://www.cloudns.net +// Submitted by Aleksander Hristov & Boyan Peychev +cloudns.asia +cloudns.be +cloud-ip.biz +cloudns.biz +cloud-ip.cc +cloudns.cc +cloudns.ch +cloudns.cl +cloudns.club +abrdns.com +dnsabr.com +ip-ddns.com +cloudns.cx +cloudns.eu +cloudns.in +cloudns.info +ddns-ip.net +dns-cloud.net +dns-dynamic.net +cloudns.nz +cloudns.org +ip-dynamic.org +cloudns.ph +cloudns.pro +cloudns.pw +cloudns.us + +// Cloud66 : https://www.cloud66.com/ +// Submitted by Khash Sajadi +c66.me +cloud66.ws + +// CloudAccess.net : https://www.cloudaccess.net/ +// Submitted by Pawel Panek +jdevcloud.com +wpdevcloud.com +cloudaccess.host +freesite.host +cloudaccess.net + +// Cloudbees, Inc. : https://www.cloudbees.com/ +// Submitted by Mohideen Shajith +cloudbeesusercontent.io + +// Cloudera, Inc. : https://www.cloudera.com/ +// Submitted by Kedarnath Waikar +*.cloudera.site + +// Cloudflare, Inc. : https://www.cloudflare.com/ +// Submitted by Cloudflare Team +cf-ipfs.com +cloudflare-ipfs.com +trycloudflare.com +pages.dev +r2.dev +workers.dev +cloudflare.net +cdn.cloudflare.net +cdn.cloudflareanycast.net +cdn.cloudflarecn.net +cdn.cloudflareglobal.net + +// cloudscale.ch AG : https://www.cloudscale.ch/ +// Submitted by Gaudenz Steinlin +cust.cloudscale.ch +objects.lpg.cloudscale.ch +objects.rma.cloudscale.ch +lpg.objectstorage.ch +rma.objectstorage.ch + +// Clovyr : https://clovyr.io +// Submitted by Patrick Nielsen +wnext.app + +// CNPY : https://cnpy.gdn +// Submitted by Angelo Gladding +cnpy.gdn + +// Co & Co : https://co-co.nl/ +// Submitted by Govert Versluis +*.otap.co + +// co.ca : http://registry.co.ca/ +co.ca + +// co.com Registry, LLC : https://registry.co.com +// Submitted by Gavin Brown +co.com + +// Codeberg e. V. : https://codeberg.org +// Submitted by Moritz Marquardt +codeberg.page + +// CodeSandbox B.V. : https://codesandbox.io +// Submitted by Ives van Hoorne +csb.app +preview.csb.app + +// CoDNS B.V. +co.nl +co.no + +// Cognition AI, Inc. : https://cognition.ai +// Submitted by Philip Papurt +*.devinapps.com + +// Combell.com : https://www.combell.com +// Submitted by Thomas Wouters +webhosting.be +hosting-cluster.nl + +// Contentful GmbH : https://www.contentful.com +// Submitted by Contentful Developer Experience Team +ctfcloud.net + +// Convex : https://convex.dev/ +// Submitted by James Cowling +convex.app +convex.cloud +convex.site + +// Coordination Center for TLD RU and XN--P1AI : https://cctld.ru/en/domains/domens_ru/reserved/ +// Submitted by George Georgievsky +ac.ru +edu.ru +gov.ru +int.ru +mil.ru + +// COSIMO GmbH : http://www.cosimo.de +// Submitted by Rene Marticke +dyn.cosidns.de +dnsupdater.de +dynamisches-dns.de +internet-dns.de +l-o-g-i-n.de +dynamic-dns.info +feste-ip.net +knx-server.net +static-access.net + +// Craft Docs Ltd : https://www.craft.do/ +// Submitted by Zsombor Fuszenecker +craft.me + +// Craynic, s.r.o. : http://www.craynic.com/ +// Submitted by Ales Krajnik +realm.cz + +// Crisp IM SAS : https://crisp.chat/ +// Submitted by Baptiste Jamin +on.crisp.email + +// Cryptonomic : https://cryptonomic.net/ +// Submitted by Andrew Cady +*.cryptonomic.net + +// cyber_Folks S.A. : https://cyberfolks.pl +// Submitted by Bartlomiej Kida +cfolks.pl + +// cyon GmbH : https://www.cyon.ch/ +// Submitted by Dominic Luechinger +cyon.link +cyon.site + +// Dansk.net : http://www.dansk.net/ +// Submitted by Anani Voule +biz.dk +co.dk +firm.dk +reg.dk +store.dk + +// dappnode.io : https://dappnode.io/ +// Submitted by Abel Boldu / DAppNode Team +dyndns.dappnode.io + +// Dark, Inc. : https://darklang.com +// Submitted by Paul Biggar +builtwithdark.com +darklang.io + +// DataDetect, LLC. : https://datadetect.com +// Submitted by Andrew Banchich +demo.datadetect.com +instance.datadetect.com + +// Datawire, Inc : https://www.datawire.io +// Submitted by Richard Li +edgestack.me + +// Datto, Inc. : https://www.datto.com/ +// Submitted by Philipp Heckel +dattolocal.com +dattorelay.com +dattoweb.com +mydatto.com +dattolocal.net +mydatto.net + +// ddnss.de : https://www.ddnss.de/ +// Submitted by Robert Niedziela +ddnss.de +dyn.ddnss.de +dyndns.ddnss.de +dyn-ip24.de +dyndns1.de +home-webserver.de +dyn.home-webserver.de +myhome-server.de +ddnss.org + +// Debian : https://www.debian.org/ +// Submitted by Peter Palfrader / Debian Sysadmin Team +debian.net + +// Definima : http://www.definima.com/ +// Submitted by Maxence Bitterli +definima.io +definima.net + +// Deno Land Inc : https://deno.com/ +// Submitted by Luca Casonato +deno.dev +deno-staging.dev +deno.net + +// deSEC : https://desec.io/ +// Submitted by Peter Thomassen +dedyn.io + +// Deta : https://www.deta.sh/ +// Submitted by Aavash Shrestha +deta.app +deta.dev + +// Dfinity Foundation: https://dfinity.org/ +// Submitted by Dfinity Team +icp0.io +*.raw.icp0.io +icp1.io +*.raw.icp1.io +*.icp.net +caffeine.site +caffeine.xyz + +// dhosting.pl Sp. z o.o. : https://dhosting.pl/ +// Submitted by Michal Kokoszkiewicz +dfirma.pl +dkonto.pl +you2.pl + +// DigitalOcean App Platform : https://www.digitalocean.com/products/app-platform/ +// Submitted by Braxton Huggins +ondigitalocean.app + +// DigitalOcean Spaces : https://www.digitalocean.com/products/spaces/ +// Submitted by Robin H. Johnson +*.digitaloceanspaces.com + +// DigitalPlat : https://www.digitalplat.org/ +// Submitted by Edward Hsing +qzz.io +us.kg +xx.kg +dpdns.org + +// Discord Inc : https://discord.com +// Submitted by Sahn Lam +discordsays.com +discordsez.com + +// DNS Africa Ltd : https://dns.business +// Submitted by Calvin Browne +jozi.biz + +// DNShome : https://www.dnshome.de/ +// Submitted by Norbert Auler +dnshome.de + +// DotArai : https://www.dotarai.com/ +// Submitted by Atsadawat Netcharadsang +online.th +shop.th + +// DrayTek Corp. : https://www.draytek.com/ +// Submitted by Paul Fang +drayddns.com + +// DreamCommerce : https://shoper.pl/ +// Submitted by Konrad Kotarba +shoparena.pl + +// DreamHost : http://www.dreamhost.com/ +// Submitted by Andrew Farmer +dreamhosters.com + +// Dreamyoungs, Inc. : https://durumis.com +// Submitted by Infra Team +durumis.com + +// DuckDNS : http://www.duckdns.org/ +// Submitted by Richard Harper +duckdns.org + +// dy.fi : http://dy.fi/ +// Submitted by Heikki Hannikainen +dy.fi +tunk.org + +// DynDNS.com : http://www.dyndns.com/services/dns/dyndns/ +dyndns.biz +for-better.biz +for-more.biz +for-some.biz +for-the.biz +selfip.biz +webhop.biz +ftpaccess.cc +game-server.cc +myphotos.cc +scrapping.cc +blogdns.com +cechire.com +dnsalias.com +dnsdojo.com +doesntexist.com +dontexist.com +doomdns.com +dyn-o-saur.com +dynalias.com +dyndns-at-home.com +dyndns-at-work.com +dyndns-blog.com +dyndns-free.com +dyndns-home.com +dyndns-ip.com +dyndns-mail.com +dyndns-office.com +dyndns-pics.com +dyndns-remote.com +dyndns-server.com +dyndns-web.com +dyndns-wiki.com +dyndns-work.com +est-a-la-maison.com +est-a-la-masion.com +est-le-patron.com +est-mon-blogueur.com +from-ak.com +from-al.com +from-ar.com +from-ca.com +from-ct.com +from-dc.com +from-de.com +from-fl.com +from-ga.com +from-hi.com +from-ia.com +from-id.com +from-il.com +from-in.com +from-ks.com +from-ky.com +from-ma.com +from-md.com +from-mi.com +from-mn.com +from-mo.com +from-ms.com +from-mt.com +from-nc.com +from-nd.com +from-ne.com +from-nh.com +from-nj.com +from-nm.com +from-nv.com +from-oh.com +from-ok.com +from-or.com +from-pa.com +from-pr.com +from-ri.com +from-sc.com +from-sd.com +from-tn.com +from-tx.com +from-ut.com +from-va.com +from-vt.com +from-wa.com +from-wi.com +from-wv.com +from-wy.com +getmyip.com +gotdns.com +hobby-site.com +homelinux.com +homeunix.com +iamallama.com +is-a-anarchist.com +is-a-blogger.com +is-a-bookkeeper.com +is-a-bulls-fan.com +is-a-caterer.com +is-a-chef.com +is-a-conservative.com +is-a-cpa.com +is-a-cubicle-slave.com +is-a-democrat.com +is-a-designer.com +is-a-doctor.com +is-a-financialadvisor.com +is-a-geek.com +is-a-green.com +is-a-guru.com +is-a-hard-worker.com +is-a-hunter.com +is-a-landscaper.com +is-a-lawyer.com +is-a-liberal.com +is-a-libertarian.com +is-a-llama.com +is-a-musician.com +is-a-nascarfan.com +is-a-nurse.com +is-a-painter.com +is-a-personaltrainer.com +is-a-photographer.com +is-a-player.com +is-a-republican.com +is-a-rockstar.com +is-a-socialist.com +is-a-student.com +is-a-teacher.com +is-a-techie.com +is-a-therapist.com +is-an-accountant.com +is-an-actor.com +is-an-actress.com +is-an-anarchist.com +is-an-artist.com +is-an-engineer.com +is-an-entertainer.com +is-certified.com +is-gone.com +is-into-anime.com +is-into-cars.com +is-into-cartoons.com +is-into-games.com +is-leet.com +is-not-certified.com +is-slick.com +is-uberleet.com +is-with-theband.com +isa-geek.com +isa-hockeynut.com +issmarterthanyou.com +likes-pie.com +likescandy.com +neat-url.com +saves-the-whales.com +selfip.com +sells-for-less.com +sells-for-u.com +servebbs.com +simple-url.com +space-to-rent.com +teaches-yoga.com +writesthisblog.com +ath.cx +fuettertdasnetz.de +isteingeek.de +istmein.de +lebtimnetz.de +leitungsen.de +traeumtgerade.de +barrel-of-knowledge.info +barrell-of-knowledge.info +dyndns.info +for-our.info +groks-the.info +groks-this.info +here-for-more.info +knowsitall.info +selfip.info +webhop.info +forgot.her.name +forgot.his.name +at-band-camp.net +blogdns.net +broke-it.net +buyshouses.net +dnsalias.net +dnsdojo.net +does-it.net +dontexist.net +dynalias.net +dynathome.net +endofinternet.net +from-az.net +from-co.net +from-la.net +from-ny.net +gets-it.net +ham-radio-op.net +homeftp.net +homeip.net +homelinux.net +homeunix.net +in-the-band.net +is-a-chef.net +is-a-geek.net +isa-geek.net +kicks-ass.net +office-on-the.net +podzone.net +scrapper-site.net +selfip.net +sells-it.net +servebbs.net +serveftp.net +thruhere.net +webhop.net +merseine.nu +mine.nu +shacknet.nu +blogdns.org +blogsite.org +boldlygoingnowhere.org +dnsalias.org +dnsdojo.org +doesntexist.org +dontexist.org +doomdns.org +dvrdns.org +dynalias.org +dyndns.org +go.dyndns.org +home.dyndns.org +endofinternet.org +endoftheinternet.org +from-me.org +game-host.org +gotdns.org +hobby-site.org +homedns.org +homeftp.org +homelinux.org +homeunix.org +is-a-bruinsfan.org +is-a-candidate.org +is-a-celticsfan.org +is-a-chef.org +is-a-geek.org +is-a-knight.org +is-a-linux-user.org +is-a-patsfan.org +is-a-soxfan.org +is-found.org +is-lost.org +is-saved.org +is-very-bad.org +is-very-evil.org +is-very-good.org +is-very-nice.org +is-very-sweet.org +isa-geek.org +kicks-ass.org +misconfused.org +podzone.org +readmyblog.org +selfip.org +sellsyourhome.org +servebbs.org +serveftp.org +servegame.org +stuff-4-sale.org +webhop.org +better-than.tv +dyndns.tv +on-the-web.tv +worse-than.tv +is-by.us +land-4-sale.us +stuff-4-sale.us +dyndns.ws +mypets.ws + +// Dynu.com : https://www.dynu.com/ +// Submitted by Sue Ye +ddnsfree.com +ddnsgeek.com +giize.com +gleeze.com +kozow.com +loseyourip.com +ooguy.com +theworkpc.com +casacam.net +dynu.net +accesscam.org +camdvr.org +freeddns.org +mywire.org +webredirect.org +myddns.rocks + +// dynv6 : https://dynv6.com +// Submitted by Dominik Menke +dynv6.net + +// E4YOU spol. s.r.o. : https://e4you.cz/ +// Submitted by Vladimir Dudr +e4.cz + +// Easypanel : https://easypanel.io +// Submitted by Andrei Canta +easypanel.app +easypanel.host + +// EasyWP : https://www.easywp.com +// Submitted by +*.ewp.live + +// eDirect Corp. : https://hosting.url.com.tw/ +// Submitted by C.S. chang +twmail.cc +twmail.net +twmail.org +mymailer.com.tw +url.tw + +// Electromagnetic Field : https://www.emfcamp.org +// Submitted by +at.emf.camp + +// Elefunc, Inc. : https://elefunc.com +// Submitted by Cetin Sert +rt.ht + +// Elementor : Elementor Ltd. +// Submitted by Anton Barkan +elementor.cloud +elementor.cool + +// En root‽ : https://en-root.org +// Submitted by Emmanuel Raviart +en-root.fr + +// Enalean SAS : https://www.enalean.com +// Submitted by Enalean Security Team +mytuleap.com +tuleap-partners.com + +// Encoretivity AB : https://encore.cloud +// Submitted by André Eriksson +encr.app +frontend.encr.app +encoreapi.com +lp.dev +api.lp.dev +objects.lp.dev + +// encoway GmbH : https://www.encoway.de +// Submitted by Marcel Daus +eu.encoway.cloud + +// EU.org : https://eu.org/ +// Submitted by Pierre Beyssac +eu.org +al.eu.org +asso.eu.org +at.eu.org +au.eu.org +be.eu.org +bg.eu.org +ca.eu.org +cd.eu.org +ch.eu.org +cn.eu.org +cy.eu.org +cz.eu.org +de.eu.org +dk.eu.org +edu.eu.org +ee.eu.org +es.eu.org +fi.eu.org +fr.eu.org +gr.eu.org +hr.eu.org +hu.eu.org +ie.eu.org +il.eu.org +in.eu.org +int.eu.org +is.eu.org +it.eu.org +jp.eu.org +kr.eu.org +lt.eu.org +lu.eu.org +lv.eu.org +me.eu.org +mk.eu.org +mt.eu.org +my.eu.org +net.eu.org +ng.eu.org +nl.eu.org +no.eu.org +nz.eu.org +pl.eu.org +pt.eu.org +ro.eu.org +ru.eu.org +se.eu.org +si.eu.org +sk.eu.org +tr.eu.org +uk.eu.org +us.eu.org + +// Eurobyte : https://eurobyte.ru +// Submitted by Evgeniy Subbotin +eurodir.ru + +// Evennode : http://www.evennode.com/ +// Submitted by Michal Kralik +eu-1.evennode.com +eu-2.evennode.com +eu-3.evennode.com +eu-4.evennode.com +us-1.evennode.com +us-2.evennode.com +us-3.evennode.com +us-4.evennode.com + +// Evervault : https://evervault.com +// Submitted by Hannah Neary +relay.evervault.app +relay.evervault.dev + +// Expo : https://expo.dev/ +// Submitted by James Ide +expo.app +staging.expo.app + +// Fabrica Technologies, Inc. : https://www.fabrica.dev/ +// Submitted by Eric Jiang +onfabrica.com + +// FAITID : https://faitid.org/ +// Submitted by Maxim Alzoba +// https://www.flexireg.net/stat_info +ru.net +adygeya.ru +bashkiria.ru +bir.ru +cbg.ru +com.ru +dagestan.ru +grozny.ru +kalmykia.ru +kustanai.ru +marine.ru +mordovia.ru +msk.ru +mytis.ru +nalchik.ru +nov.ru +pyatigorsk.ru +spb.ru +vladikavkaz.ru +vladimir.ru +abkhazia.su +adygeya.su +aktyubinsk.su +arkhangelsk.su +armenia.su +ashgabad.su +azerbaijan.su +balashov.su +bashkiria.su +bryansk.su +bukhara.su +chimkent.su +dagestan.su +east-kazakhstan.su +exnet.su +georgia.su +grozny.su +ivanovo.su +jambyl.su +kalmykia.su +kaluga.su +karacol.su +karaganda.su +karelia.su +khakassia.su +krasnodar.su +kurgan.su +kustanai.su +lenug.su +mangyshlak.su +mordovia.su +msk.su +murmansk.su +nalchik.su +navoi.su +north-kazakhstan.su +nov.su +obninsk.su +penza.su +pokrovsk.su +sochi.su +spb.su +tashkent.su +termez.su +togliatti.su +troitsk.su +tselinograd.su +tula.su +tuva.su +vladikavkaz.su +vladimir.su +vologda.su + +// Fancy Bits, LLC : http://getchannels.com +// Submitted by Aman Gupta +channelsdvr.net +u.channelsdvr.net + +// Fastly Inc. : http://www.fastly.com/ +// Submitted by Fastly Security +edgecompute.app +fastly-edge.com +fastly-terrarium.com +freetls.fastly.net +map.fastly.net +a.prod.fastly.net +global.prod.fastly.net +a.ssl.fastly.net +b.ssl.fastly.net +global.ssl.fastly.net +fastlylb.net +map.fastlylb.net + +// Fastmail : https://www.fastmail.com/ +// Submitted by Marc Bradshaw +*.user.fm + +// FASTVPS EESTI OU : https://fastvps.ru/ +// Submitted by Likhachev Vasiliy +fastvps-server.com +fastvps.host +myfast.host +fastvps.site +myfast.space + +// FearWorks Media Ltd. : https://fearworksmedia.co.uk +// Submitted by Keith Fairley +conn.uk +copro.uk +hosp.uk + +// Fedora : https://fedoraproject.org/ +// Submitted by Patrick Uiterwijk +fedorainfracloud.org +fedorapeople.org +cloud.fedoraproject.org +app.os.fedoraproject.org +app.os.stg.fedoraproject.org + +// Fermax : https://fermax.com/ +// Submitted by Koen Van Isterdael +mydobiss.com + +// FH Muenster : https://www.fh-muenster.de +// Submitted by Robin Naundorf +fh-muenster.io + +// Figma : https://www.figma.com +// Submitted by Nick Frost +figma.site +preview.site + +// Filegear Inc. : https://www.filegear.com +// Submitted by Jason Zhu +filegear.me + +// Firebase, Inc. +// Submitted by Chris Raynor +firebaseapp.com + +// FlashDrive : https://flashdrive.io +// Submitted by Eric Chan +fldrv.com + +// Fleek Labs Inc : https://fleek.xyz +// Submitted by Parsa Ghadimi +on-fleek.app + +// FlutterFlow : https://flutterflow.io +// Submitted by Anton Emelyanov +flutterflow.app + +// fly.io : https://fly.io +// Submitted by Kurt Mackey +fly.dev +shw.io +edgeapp.net + +// Forgerock : https://www.forgerock.com +// Submitted by Roderick Parr +forgeblocks.com +id.forgerock.io + +// FoundryLabs, Inc : https://e2b.dev/ +// Submitted by Jiri Sveceny +e2b.app + +// Framer : https://www.framer.com +// Submitted by Koen Rouwhorst +framer.ai +framer.app +framercanvas.com +framer.media +framer.photos +framer.website +framer.wiki + +// Frederik Braun : https://frederik-braun.com +// Submitted by Frederik Braun +*.0e.vc + +// Freebox : http://www.freebox.fr +// Submitted by Romain Fliedel +freebox-os.com +freeboxos.com +fbx-os.fr +fbxos.fr +freebox-os.fr +freeboxos.fr + +// freedesktop.org : https://www.freedesktop.org +// Submitted by Daniel Stone +freedesktop.org + +// freemyip.com : https://freemyip.com +// Submitted by Cadence +freemyip.com + +// Frusky MEDIA&PR : https://www.frusky.de +// Submitted by Victor Pupynin +*.frusky.de + +// FunkFeuer - Verein zur Förderung freier Netze : https://www.funkfeuer.at +// Submitted by Daniel A. Maierhofer +wien.funkfeuer.at + +// Future Versatile Group. : https://www.fvg-on.net/ +// T.Kabu +daemon.asia +dix.asia +mydns.bz +0am.jp +0g0.jp +0j0.jp +0t0.jp +mydns.jp +pgw.jp +wjg.jp +keyword-on.net +live-on.net +server-on.net +mydns.tw +mydns.vc + +// Futureweb GmbH : https://www.futureweb.at +// Submitted by Andreas Schnederle-Wagner +*.futurecms.at +*.ex.futurecms.at +*.in.futurecms.at +futurehosting.at +futuremailing.at +*.ex.ortsinfo.at +*.kunden.ortsinfo.at +*.statics.cloud + +// GCom Internet : https://www.gcom.net.au +// Submitted by Leo Julius +aliases121.com + +// GDS : https://www.gov.uk/service-manual/technology/managing-domain-names +// Submitted by Stephen Ford +campaign.gov.uk +service.gov.uk +independent-commission.uk +independent-inquest.uk +independent-inquiry.uk +independent-panel.uk +independent-review.uk +public-inquiry.uk +royal-commission.uk + +// Gehirn Inc. : https://www.gehirn.co.jp/ +// Submitted by Kohei YOSHIDA +gehirn.ne.jp +usercontent.jp + +// Gentlent, Inc. : https://www.gentlent.com +// Submitted by Tom Klein +gentapps.com +gentlentapis.com +cdn-edges.net + +// GignoSystemJapan : http://gsj.bz +// Submitted by GignoSystemJapan +gsj.bz + +// GitHub, Inc. +// Submitted by Patrick Toomey +github.app +githubusercontent.com +githubpreview.dev +github.io + +// GitLab, Inc. : https://about.gitlab.com/ +// Submitted by Alex Hanselka +gitlab.io + +// Gitplac.si : https://gitplac.si +// Submitted by Aljaž Starc +gitapp.si +gitpage.si + +// Global NOG Alliance : https://nogalliance.org/ +// Submitted by Sander Steffann +nog.community + +// Globe Hosting SRL : https://www.globehosting.com/ +// Submitted by Gavin Brown +co.ro +shop.ro + +// GMO Pepabo, Inc. : https://pepabo.com/ +// Submitted by Hosting Div +lolipop.io +angry.jp +babyblue.jp +babymilk.jp +backdrop.jp +bambina.jp +bitter.jp +blush.jp +boo.jp +boy.jp +boyfriend.jp +but.jp +candypop.jp +capoo.jp +catfood.jp +cheap.jp +chicappa.jp +chillout.jp +chips.jp +chowder.jp +chu.jp +ciao.jp +cocotte.jp +coolblog.jp +cranky.jp +cutegirl.jp +daa.jp +deca.jp +deci.jp +digick.jp +egoism.jp +fakefur.jp +fem.jp +flier.jp +floppy.jp +fool.jp +frenchkiss.jp +girlfriend.jp +girly.jp +gloomy.jp +gonna.jp +greater.jp +hacca.jp +heavy.jp +her.jp +hiho.jp +hippy.jp +holy.jp +hungry.jp +icurus.jp +itigo.jp +jellybean.jp +kikirara.jp +kill.jp +kilo.jp +kuron.jp +littlestar.jp +lolipopmc.jp +lolitapunk.jp +lomo.jp +lovepop.jp +lovesick.jp +main.jp +mods.jp +mond.jp +mongolian.jp +moo.jp +namaste.jp +nikita.jp +nobushi.jp +noor.jp +oops.jp +parallel.jp +parasite.jp +pecori.jp +peewee.jp +penne.jp +pepper.jp +perma.jp +pigboat.jp +pinoko.jp +punyu.jp +pupu.jp +pussycat.jp +pya.jp +raindrop.jp +readymade.jp +sadist.jp +schoolbus.jp +secret.jp +staba.jp +stripper.jp +sub.jp +sunnyday.jp +thick.jp +tonkotsu.jp +under.jp +upper.jp +velvet.jp +verse.jp +versus.jp +vivian.jp +watson.jp +weblike.jp +whitesnow.jp +zombie.jp +heteml.net + +// GoDaddy Registry : https://registry.godaddy +// Submitted by Rohan Durrant +graphic.design + +// GoIP DNS Services : http://www.goip.de +// Submitted by Christian Poulter +goip.de + +// Google, Inc. +// Submitted by Shannon McCabe +*.hosted.app +*.run.app +*.mtls.run.app +web.app +*.0emm.com +appspot.com +*.r.appspot.com +blogspot.com +codespot.com +googleapis.com +googlecode.com +pagespeedmobilizer.com +withgoogle.com +withyoutube.com +*.gateway.dev +cloud.goog +translate.goog +*.usercontent.goog +cloudfunctions.net + +// Goupile : https://goupile.fr +// Submitted by Niels Martignene +goupile.fr + +// GOV.UK Pay : https://www.payments.service.gov.uk/ +// Submitted by Richard Baker +pymnt.uk + +// GOV.UK Platform as a Service : https://www.cloud.service.gov.uk/ +// Submitted by Tom Whitwell +cloudapps.digital +london.cloudapps.digital + +// Government of the Netherlands : https://www.government.nl +// Submitted by +gov.nl + +// Grafana Labs : https://grafana.com/ +// Submitted by Platform Engineering +grafana-dev.net + +// GrayJay Web Solutions Inc. : https://grayjaysports.ca +// Submitted by Matt Yamkowy +grayjayleagues.com + +// GünstigBestellen : https://günstigbestellen.de +// Submitted by Furkan Akkoc +günstigbestellen.de +günstigliefern.de + +// Hackclub Nest : https://hackclub.app +// Submitted by Cyteon +hackclub.app + +// Häkkinen.fi : https://www.häkkinen.fi/ +// Submitted by Eero Häkkinen +häkkinen.fi + +// Hashbang : https://hashbang.sh +hashbang.sh + +// Hasura : https://hasura.io +// Submitted by Shahidh K Muhammed +hasura.app +hasura-app.io + +// Hatena Co., Ltd. : https://hatena.co.jp +// Submitted by Masato Nakamura +hatenablog.com +hatenadiary.com +hateblo.jp +hatenablog.jp +hatenadiary.jp +hatenadiary.org + +// Heilbronn University of Applied Sciences - Faculty Informatics (GitLab Pages) : https://www.hs-heilbronn.de +// Submitted by Richard Zowalla +pages.it.hs-heilbronn.de +pages-research.it.hs-heilbronn.de + +// HeiyuSpace : https://lazycat.cloud +// Submitted by Xia Bin +heiyu.space + +// Helio Networks : https://heliohost.org +// Submitted by Ben Frede +helioho.st +heliohost.us + +// Hepforge : https://www.hepforge.org +// Submitted by David Grellscheid +hepforge.org + +// Heroku : https://www.heroku.com/ +// Submitted by Shumon Huque +herokuapp.com + +// Heyflow : https://www.heyflow.com +// Submitted by Mirko Nitschke +heyflow.page +heyflow.site + +// Hibernating Rhinos +// Submitted by Oren Eini +ravendb.cloud +ravendb.community +development.run +ravendb.run + +// HiDNS : https://www.hidoha.net +// Submitted by ifeng +hidns.co +hidns.vip + +// home.pl S.A. : https://home.pl +// Submitted by Krzysztof Wolski +homesklep.pl + +// Homebase : https://homebase.id/ +// Submitted by Jason Babo +*.kin.one +*.id.pub +*.kin.pub + +// Hoplix : https://www.hoplix.com +// Submitted by Danilo De Franco +hoplix.shop + +// HOSTBIP REGISTRY : https://www.hostbip.com/ +// Submitted by Atanunu Igbunuroghene +orx.biz +biz.ng +co.biz.ng +dl.biz.ng +go.biz.ng +lg.biz.ng +on.biz.ng +col.ng +firm.ng +gen.ng +ltd.ng +ngo.ng +plc.ng + +// HostyHosting : https://hostyhosting.com +hostyhosting.io + +// Hugging Face : https://huggingface.co +// Submitted by Eliott Coyac +hf.space +static.hf.space + +// Hypernode B.V. : https://www.hypernode.com/ +// Submitted by Cipriano Groenendal +hypernode.io + +// I-O DATA DEVICE, INC. : http://www.iodata.com/ +// Submitted by Yuji Minagawa +iobb.net + +// i-registry s.r.o. : http://www.i-registry.cz/ +// Submitted by Martin Semrad +co.cz + +// Ici la Lune : http://www.icilalune.com/ +// Submitted by Simon Morvan +*.moonscale.io +moonscale.net + +// iDOT Services Limited : http://www.domain.gr.com +// Submitted by Gavin Brown +gr.com + +// iki.fi +// Submitted by Hannu Aronsson +iki.fi + +// iliad italia : https://www.iliad.it +// Submitted by Marios Makassikis +ibxos.it +iliadboxos.it + +// Incsub, LLC : https://incsub.com/ +// Submitted by Aaron Edwards +smushcdn.com +wphostedmail.com +wpmucdn.com +tempurl.host +wpmudev.host + +// Individual Network Berlin e.V. : https://www.in-berlin.de/ +// Submitted by Christian Seitz +dyn-berlin.de +in-berlin.de +in-brb.de +in-butter.de +in-dsl.de +in-vpn.de +in-dsl.net +in-vpn.net +in-dsl.org +in-vpn.org + +// Inferno Communications : https://inferno.co.uk +// Submitted by Connor McFarlane +oninferno.net + +// info.at : http://www.info.at/ +biz.at +info.at + +// info.cx : http://info.cx +// Submitted by June Slater +info.cx + +// Interlegis : http://www.interlegis.leg.br +// Submitted by Gabriel Ferreira +ac.leg.br +al.leg.br +am.leg.br +ap.leg.br +ba.leg.br +ce.leg.br +df.leg.br +es.leg.br +go.leg.br +ma.leg.br +mg.leg.br +ms.leg.br +mt.leg.br +pa.leg.br +pb.leg.br +pe.leg.br +pi.leg.br +pr.leg.br +rj.leg.br +rn.leg.br +ro.leg.br +rr.leg.br +rs.leg.br +sc.leg.br +se.leg.br +sp.leg.br +to.leg.br + +// intermetrics GmbH : https://pixolino.com/ +// Submitted by Wolfgang Schwarz +pixolino.com + +// Internet-Pro, LLP : https://netangels.ru/ +// Submitted by Vasiliy Sheredeko +na4u.ru + +// Inventor Services : https://inventor.gg/ +// Submitted by Inventor Team +botdash.app +botdash.dev +botdash.gg +botdash.net +botda.sh +botdash.xyz + +// IONOS SE : https://www.ionos.com/ +// IONOS Group SE : https://www.ionos-group.com/ +// Submitted by Henrik Willert +apps-1and1.com +live-website.com +webspace-host.com +apps-1and1.net +websitebuilder.online +app-ionos.space + +// iopsys software solutions AB : https://iopsys.eu/ +// Submitted by Roman Azarenko +iopsys.se + +// IPFS Project : https://ipfs.tech/ +// Submitted by Interplanetary Shipyard +*.inbrowser.dev +*.dweb.link +*.inbrowser.link + +// IPiFony Systems, Inc. : https://www.ipifony.com/ +// Submitted by Matthew Hardeman +ipifony.net + +// ir.md : https://nic.ir.md +// Submitted by Ali Soizi +ir.md + +// is-a-good.dev : https://is-a-good.dev +// Submitted by William Harrison +is-a-good.dev + +// IServ GmbH : https://iserv.de +// Submitted by Kim Brodowski +iservschule.de +mein-iserv.de +schuldock.de +schulplattform.de +schulserver.de +test-iserv.de +iserv.dev +iserv.host + +// Jelastic, Inc. : https://jelastic.com/ +// Submitted by Ihor Kolodyuk +mel.cloudlets.com.au +cloud.interhostsolutions.be +alp1.ae.flow.ch +appengine.flow.ch +es-1.axarnet.cloud +diadem.cloud +vip.jelastic.cloud +jele.cloud +it1.eur.aruba.jenv-aruba.cloud +it1.jenv-aruba.cloud +keliweb.cloud +cs.keliweb.cloud +oxa.cloud +tn.oxa.cloud +uk.oxa.cloud +primetel.cloud +uk.primetel.cloud +ca.reclaim.cloud +uk.reclaim.cloud +us.reclaim.cloud +ch.trendhosting.cloud +de.trendhosting.cloud +jele.club +dopaas.com +paas.hosted-by-previder.com +rag-cloud.hosteur.com +rag-cloud-ch.hosteur.com +jcloud.ik-server.com +jcloud-ver-jpc.ik-server.com +demo.jelastic.com +paas.massivegrid.com +jed.wafaicloud.com +ryd.wafaicloud.com +j.scaleforce.com.cy +jelastic.dogado.eu +fi.cloudplatform.fi +demo.datacenter.fi +paas.datacenter.fi +jele.host +mircloud.host +paas.beebyte.io +sekd1.beebyteapp.io +jele.io +jc.neen.it +jcloud.kz +cloudjiffy.net +fra1-de.cloudjiffy.net +west1-us.cloudjiffy.net +jls-sto1.elastx.net +jls-sto2.elastx.net +jls-sto3.elastx.net +fr-1.paas.massivegrid.net +lon-1.paas.massivegrid.net +lon-2.paas.massivegrid.net +ny-1.paas.massivegrid.net +ny-2.paas.massivegrid.net +sg-1.paas.massivegrid.net +jelastic.saveincloud.net +nordeste-idc.saveincloud.net +j.scaleforce.net +sdscloud.pl +unicloud.pl +mircloud.ru +enscaled.sg +jele.site +jelastic.team +orangecloud.tn +j.layershift.co.uk +phx.enscaled.us +mircloud.us + +// Jino : https://www.jino.ru +// Submitted by Sergey Ulyashin +myjino.ru +*.hosting.myjino.ru +*.landing.myjino.ru +*.spectrum.myjino.ru +*.vps.myjino.ru + +// Jotelulu S.L. : https://jotelulu.com +// Submitted by Daniel Fariña +jote.cloud +jotelulu.cloud +eu1-plenit.com +la1-plenit.com +us1-plenit.com + +// JouwWeb B.V. : https://www.jouwweb.nl +// Submitted by Camilo Sperberg +webadorsite.com +jouwweb.site + +// Joyent : https://www.joyent.com/ +// Submitted by Brian Bennett +*.cns.joyent.com +*.triton.zone + +// JS.ORG : http://dns.js.org +// Submitted by Stefan Keim +js.org + +// KaasHosting : http://www.kaashosting.nl/ +// Submitted by Wouter Bakker +kaas.gg +khplay.nl + +// Kapsi : https://kapsi.fi +// Submitted by Tomi Juntunen +kapsi.fi + +// Katholieke Universiteit Leuven : https://www.kuleuven.be +// Submitted by Abuse KU Leuven +ezproxy.kuleuven.be +kuleuven.cloud + +// Keyweb AG : https://www.keyweb.de +// Submitted by Martin Dannehl +keymachine.de + +// KingHost : https://king.host +// Submitted by Felipe Keller Braz +kinghost.net +uni5.net + +// KnightPoint Systems, LLC : http://www.knightpoint.com/ +// Submitted by Roy Keene +knightpoint.systems + +// KoobinEvent, SL : https://www.koobin.com +// Submitted by Iván Oliva +koobin.events + +// Krellian Ltd. : https://krellian.com +// Submitted by Ben Francis +webthings.io +krellian.net + +// KUROKU LTD : https://kuroku.ltd/ +// Submitted by DisposaBoy +oya.to + +// KV GmbH : https://www.nic.co.de +// Submitted by KV GmbH +// Abuse reports to +co.de + +// Laravel Holdings, Inc. : https://laravel.com +// Submitted by André Valentin & James Brooks +laravel.cloud +on-forge.com +on-vapor.com + +// LCube - Professional hosting e.K. : https://www.lcube-webhosting.de +// Submitted by Lars Laehn +git-repos.de +lcube-server.de +svn-repos.de + +// Leadpages : https://www.leadpages.net +// Submitted by Greg Dallavalle +leadpages.co +lpages.co +lpusercontent.com + +// Leapcell : https://leapcell.io/ +// Submitted by Leapcell Team +leapcell.app +leapcell.dev +leapcell.online + +// Liara : https://liara.ir +// Submitted by Amirhossein Badinloo +liara.run +iran.liara.run + +// libp2p project : https://libp2p.io +// Submitted by Interplanetary Shipyard +libp2p.direct + +// Libre IT Ltd : https://libre.nz +// Submitted by Tomas Maggio +runcontainers.dev + +// Lifetime Hosting : https://Lifetime.Hosting/ +// Submitted by Mike Fillator +co.business +co.education +co.events +co.financial +co.network +co.place +co.technology + +// linkyard ldt : https://www.linkyard.ch/ +// Submitted by Mario Siegenthaler +linkyard-cloud.ch +linkyard.cloud + +// Linode : https://linode.com +// Submitted by +members.linode.com +*.nodebalancer.linode.com +*.linodeobjects.com +ip.linodeusercontent.com + +// LiquidNet Ltd : http://www.liquidnetlimited.com/ +// Submitted by Victor Velchev +we.bs + +// Listen53 : https://www.l53.net +// Submitted by Gerry Keh +filegear-sg.me +ggff.net + +// Localcert : https://localcert.dev +// Submitted by Lann Martin +*.user.localcert.dev + +// Localtonet : https://localtonet.com/ +// Submitted by Burak Isleyici +localtonet.com +*.localto.net + +// Lodz University of Technology LODMAN regional domains : https://www.man.lodz.pl/dns +// Submitted by Piotr Wilk +lodz.pl +pabianice.pl +plock.pl +sieradz.pl +skierniewice.pl +zgierz.pl + +// Log'in Line : https://www.loginline.com/ +// Submitted by Rémi Mach +loginline.app +loginline.dev +loginline.io +loginline.services +loginline.site + +// Lõhmus Family, The : https://lohmus.me/ +// Submitted by Heiki Lõhmus +lohmus.me + +// Lovable : https://lovable.dev +// Submitted by Fabian Hedin +lovable.app +lovableproject.com +lovable.run +lovable.sh + +// LubMAN UMCS Sp. z o.o : https://lubman.pl/ +// Submitted by Ireneusz Maliszewski +krasnik.pl +leczna.pl +lubartow.pl +lublin.pl +poniatowa.pl +swidnik.pl + +// Lug.org.uk : https://lug.org.uk +// Submitted by Jon Spriggs +glug.org.uk +lug.org.uk +lugs.org.uk + +// Lukanet Ltd : https://lukanet.com +// Submitted by Anton Avramov +barsy.bg +barsy.club +barsycenter.com +barsyonline.com +barsy.de +barsy.dev +barsy.eu +barsy.gr +barsy.in +barsy.info +barsy.io +barsy.me +barsy.menu +barsyonline.menu +barsy.mobi +barsy.net +barsy.online +barsy.org +barsy.pro +barsy.pub +barsy.ro +barsy.rs +barsy.shop +barsyonline.shop +barsy.site +barsy.store +barsy.support +barsy.uk +barsy.co.uk +barsyonline.co.uk + +// Lutra : https://lutra.ai +// Submitted by Joshua Newman +*.lutrausercontent.com + +// Luyani Inc. : https://luyani.com/ +// Submitted by Umut Gumeli +luyani.app +luyani.net + +// Magento Commerce +// Submitted by Damien Tournoud +*.magentosite.cloud + +// Mail.Ru Group : https://hb.cldmail.ru +// Submitted by Ilya Zaretskiy +hb.cldmail.ru + +// MathWorks : https://www.mathworks.com/ +// Submitted by Emily Reed +matlab.cloud +modelscape.com +mwcloudnonprod.com +polyspace.com + +// May First - People Link : https://mayfirst.org/ +// Submitted by Jamie McClelland +mayfirst.info +mayfirst.org + +// Maze Play : https://www.mazeplay.com +// Submitted by Adam Humpherys +mazeplay.com + +// McHost : https://mchost.ru +// Submitted by Evgeniy Subbotin +mcdir.me +mcdir.ru +vps.mcdir.ru +mcpre.ru + +// Mediatech : https://mediatech.by +// Submitted by Evgeniy Kozhuhovskiy +mediatech.by +mediatech.dev + +// Medicom Health : https://medicomhealth.com +// Submitted by Michael Olson +hra.health + +// MedusaJS, Inc : https://medusajs.com/ +// Submitted by Stevche Radevski +medusajs.app + +// Memset hosting : https://www.memset.com +// Submitted by Tom Whitwell +miniserver.com +memset.net + +// Messerli Informatik AG : https://www.messerli.ch/ +// Submitted by Ruben Schmidmeister +messerli.app + +// Meta Platforms, Inc. : https://meta.com/ +// Submitted by Jacob Cordero +atmeta.com +apps.fbsbx.com + +// MetaCentrum, CESNET z.s.p.o. : https://www.metacentrum.cz/en/ +// Submitted by Zdeněk Šustr and Radim Janča +*.cloud.metacentrum.cz +custom.metacentrum.cz +flt.cloud.muni.cz +usr.cloud.muni.cz + +// Meteor Development Group : https://www.meteor.com/hosting +// Submitted by Pierre Carrier +meteorapp.com +eu.meteorapp.com + +// Michau Enterprises Limited : http://www.co.pl/ +co.pl + +// Microsoft Corporation : http://microsoft.com +// Submitted by Public Suffix List Admin +// Managed by Corporate Domains +// Microsoft Azure : https://home.azure +*.azurecontainer.io +azure-api.net +azure-mobile.net +azureedge.net +azurefd.net +azurestaticapps.net +1.azurestaticapps.net +2.azurestaticapps.net +3.azurestaticapps.net +4.azurestaticapps.net +5.azurestaticapps.net +6.azurestaticapps.net +7.azurestaticapps.net +centralus.azurestaticapps.net +eastasia.azurestaticapps.net +eastus2.azurestaticapps.net +westeurope.azurestaticapps.net +westus2.azurestaticapps.net +azurewebsites.net +cloudapp.net +trafficmanager.net +blob.core.windows.net +servicebus.windows.net + +// MikroTik : https://mikrotik.com +// Submitted by MikroTik SysAdmin Team +routingthecloud.com +sn.mynetname.net +routingthecloud.net +routingthecloud.org + +// Million Software, Inc : https://million.dev/ +// Submitted by Rayhan Noufal Arayilakath +same-app.com +same-preview.com + +// minion.systems : http://minion.systems +// Submitted by Robert Böttinger +csx.cc + +// Mittwald CM Service GmbH & Co. KG : https://mittwald.de +// Submitted by Marco Rieger +mydbserver.com +webspaceconfig.de +mittwald.info +mittwaldserver.info +typo3server.info +project.space + +// MODX Systems LLC : https://modx.com +// Submitted by Elizabeth Southwell +modx.dev + +// Mozilla Foundation : https://mozilla.org/ +// Submitted by glob +bmoattachments.org + +// MSK-IX : https://www.msk-ix.ru/ +// Submitted by Khannanov Roman +net.ru +org.ru +pp.ru + +// Mythic Beasts : https://www.mythic-beasts.com +// Submitted by Paul Cammish +hostedpi.com +caracal.mythic-beasts.com +customer.mythic-beasts.com +fentiger.mythic-beasts.com +lynx.mythic-beasts.com +ocelot.mythic-beasts.com +oncilla.mythic-beasts.com +onza.mythic-beasts.com +sphinx.mythic-beasts.com +vs.mythic-beasts.com +x.mythic-beasts.com +yali.mythic-beasts.com +cust.retrosnub.co.uk + +// Nabu Casa : https://www.nabucasa.com +// Submitted by Paulus Schoutsen +ui.nabu.casa + +// Net at Work Gmbh : https://www.netatwork.de +// Submitted by Jan Jaeschke +cloud.nospamproxy.com +o365.cloud.nospamproxy.com + +// Net libre : https://www.netlib.re +// Submitted by Philippe PITTOLI +netlib.re + +// Netfy Domains : https://netfy.domains +// Submitted by Suranga Ranasinghe +netfy.app + +// Netlify : https://www.netlify.com +// Submitted by Jessica Parsons +netlify.app + +// Neustar Inc. +// Submitted by Trung Tran +4u.com + +// NFSN, Inc. : https://www.NearlyFreeSpeech.NET/ +// Submitted by Jeff Wheelhouse +nfshost.com + +// NFT.Storage : https://nft.storage/ +// Submitted by Vasco Santos or +ipfs.nftstorage.link + +// NGO.US Registry : https://nic.ngo.us +// Submitted by Alstra Solutions Ltd. Networking Team +ngo.us + +// ngrok : https://ngrok.com/ +// Submitted by Alan Shreve +ngrok.app +ngrok-free.app +ngrok.dev +ngrok-free.dev +ngrok.io +ap.ngrok.io +au.ngrok.io +eu.ngrok.io +in.ngrok.io +jp.ngrok.io +sa.ngrok.io +us.ngrok.io +ngrok.pizza +ngrok.pro + +// Nicolaus Copernicus University in Torun - MSK TORMAN : https://www.man.torun.pl +torun.pl + +// Nimbus Hosting Ltd. : https://www.nimbushosting.co.uk/ +// Submitted by Nicholas Ford +nh-serv.co.uk +nimsite.uk + +// No-IP.com : https://noip.com/ +// Submitted by Deven Reza +mmafan.biz +myftp.biz +no-ip.biz +no-ip.ca +fantasyleague.cc +gotdns.ch +3utilities.com +blogsyte.com +ciscofreak.com +damnserver.com +ddnsking.com +ditchyourip.com +dnsiskinky.com +dynns.com +geekgalaxy.com +health-carereform.com +homesecuritymac.com +homesecuritypc.com +myactivedirectory.com +mysecuritycamera.com +myvnc.com +net-freaks.com +onthewifi.com +point2this.com +quicksytes.com +securitytactics.com +servebeer.com +servecounterstrike.com +serveexchange.com +serveftp.com +servegame.com +servehalflife.com +servehttp.com +servehumour.com +serveirc.com +servemp3.com +servep2p.com +servepics.com +servequake.com +servesarcasm.com +stufftoread.com +unusualperson.com +workisboring.com +dvrcam.info +ilovecollege.info +no-ip.info +brasilia.me +ddns.me +dnsfor.me +hopto.me +loginto.me +noip.me +webhop.me +bounceme.net +ddns.net +eating-organic.net +mydissent.net +myeffect.net +mymediapc.net +mypsx.net +mysecuritycamera.net +nhlfan.net +no-ip.net +pgafan.net +privatizehealthinsurance.net +redirectme.net +serveblog.net +serveminecraft.net +sytes.net +cable-modem.org +collegefan.org +couchpotatofries.org +hopto.org +mlbfan.org +myftp.org +mysecuritycamera.org +nflfan.org +no-ip.org +read-books.org +ufcfan.org +zapto.org +no-ip.co.uk +golffan.us +noip.us +pointto.us + +// NodeArt : https://nodeart.io +// Submitted by Konstantin Nosov +stage.nodeart.io + +// Noop : https://noop.app +// Submitted by Nathaniel Schweinberg +*.developer.app +noop.app + +// Northflank Ltd. : https://northflank.com/ +// Submitted by Marco Suter +*.northflank.app +*.build.run +*.code.run +*.database.run +*.migration.run + +// Noticeable : https://noticeable.io +// Submitted by Laurent Pellegrino +noticeable.news + +// Notion Labs, Inc : https://www.notion.so/ +// Submitted by Jess Yao +notion.site + +// Now-DNS : https://now-dns.com +// Submitted by Steve Russell +dnsking.ch +mypi.co +myiphost.com +forumz.info +soundcast.me +tcp4.me +dnsup.net +hicam.net +now-dns.net +ownip.net +vpndns.net +dynserv.org +now-dns.org +x443.pw +ntdll.top +freeddns.us + +// nsupdate.info : https://www.nsupdate.info/ +// Submitted by Thomas Waldmann +nsupdate.info +nerdpol.ovh + +// NYC.mn : https://dot.nyc.mn/ +// Submitted by NYC.mn Subdomain Service +nyc.mn + +// O3O.Foundation : https://o3o.foundation/ +// Submitted by the prvcy.page Registry Team +prvcy.page + +// Obl.ong : https://obl.ong +// Submitted by Reese Armstrong +obl.ong + +// Observable, Inc. : https://observablehq.com +// Submitted by Mike Bostock +observablehq.cloud +static.observableusercontent.com + +// OMG.LOL : https://omg.lol +// Submitted by Adam Newbold +omg.lol + +// Omnibond Systems, LLC. : https://www.omnibond.com +// Submitted by Cole Estep +cloudycluster.net + +// OmniWe Limited : https://omniwe.com +// Submitted by Vicary Archangel +omniwe.site + +// One.com : https://www.one.com/ +// Submitted by Jacob Bunk Nielsen +123webseite.at +123website.be +simplesite.com.br +123website.ch +simplesite.com +123webseite.de +123hjemmeside.dk +123miweb.es +123kotisivu.fi +123siteweb.fr +simplesite.gr +123homepage.it +123website.lu +123website.nl +123hjemmeside.no +service.one +simplesite.pl +123paginaweb.pt +123minsida.se + +// ONID : https://get.onid.ca +// Submitted by ONID Engineering Team +onid.ca + +// Open Domains : https://open-domains.net +// Submitted by William Harrison +is-a-fullstack.dev +is-cool.dev +is-not-a.dev +localplayer.dev +is-local.org + +// Open Social : https://www.getopensocial.com/ +// Submitted by Alexander Varwijk +opensocial.site + +// OpenAI : https://openai.com +// Submitted by Thomas Shadwell +*.oaiusercontent.com + +// OpenCraft GmbH : http://opencraft.com/ +// Submitted by Sven Marnach +opencraft.hosting + +// OpenHost : https://registry.openhost.uk +// Submitted by OpenHost Registry Team +16-b.it +32-b.it +64-b.it + +// OpenResearch GmbH : https://openresearch.com/ +// Submitted by Philipp Schmid +orsites.com + +// Opera Software, A.S.A. +// Submitted by Yngve Pettersen +operaunite.com + +// Oracle Dyn : https://cloud.oracle.com/home https://dyn.com/dns/ +// Submitted by Gregory Drake +// Note: This is intended to also include customer-oci.com due to wildcards implicitly including the current label +*.customer-oci.com +*.oci.customer-oci.com +*.ocp.customer-oci.com +*.ocs.customer-oci.com +*.oraclecloudapps.com +*.oraclegovcloudapps.com +*.oraclegovcloudapps.uk + +// Orange : https://www.orange.com +// Submitted by Alexandre Linte +tech.orange + +// OsSav Technology Ltd. : https://ossav.com/ +// Submitted by OsSav Technology Ltd. +// https://nic.can.re +can.re + +// Oursky Limited : https://authgear.com/ +// Submitted by Authgear Team & Skygear Developer +authgear-staging.com +authgearapps.com +skygearapp.com + +// OutSystems +// Submitted by Duarte Santos +outsystemscloud.com + +// OVHcloud : https://ovhcloud.com +// Submitted by Vincent Cassé +*.hosting.ovh.net +*.webpaas.ovh.net + +// OwnProvider GmbH : http://www.ownprovider.com +// Submitted by Jan Moennich +ownprovider.com +own.pm + +// OwO : https://whats-th.is/ +// Submitted by Dean Sheather +*.owo.codes + +// OX : http://www.ox.rs +// Submitted by Adam Grand +ox.rs + +// oy.lc +// Submitted by Charly Coste +oy.lc + +// Pagefog : https://pagefog.com/ +// Submitted by Derek Myers +pgfog.com + +// PageXL : https://pagexl.com +// Submitted by Yann Guichard +pagexl.com + +// Pantheon Systems, Inc. : https://pantheon.io/ +// Submitted by Gary Dylina +gotpantheon.com +pantheonsite.io + +// Paywhirl, Inc : https://paywhirl.com/ +// Submitted by Daniel Netzer +*.paywhirl.com + +// pcarrier.ca Software Inc : https://pcarrier.ca/ +// Submitted by Pierre Carrier +*.xmit.co +xmit.dev +madethis.site +srv.us +gh.srv.us +gl.srv.us + +// PE Ulyanov Kirill Sergeevich : https://airy.host +// Submitted by Kirill Ulyanov +lk3.ru + +// Peplink | Pepwave : http://peplink.com/ +// Submitted by Steve Leung +mypep.link + +// Perspecta : https://perspecta.com/ +// Submitted by Kenneth Van Alstyne +perspecta.cloud + +// Plain : https://www.plain.com/ +// Submitted by Jesús Hernández +support.site + +// Planet-Work : https://www.planet-work.com/ +// Submitted by Frédéric VANNIÈRE +on-web.fr + +// Platform.sh : https://platform.sh +// Submitted by Nikola Kotur +*.upsun.app +upsunapp.com +ent.platform.sh +eu.platform.sh +us.platform.sh +*.platformsh.site +*.tst.site + +// Platter : https://platter.dev +// Submitted by Patrick Flor +platter-app.dev +platterp.us + +// Pley AB : https://www.pley.com/ +// Submitted by Henning Pohl +pley.games + +// Porter : https://porter.run/ +// Submitted by Rudraksh MK +onporter.run + +// Positive Codes Technology Company : http://co.bn/faq.html +// Submitted by Zulfais +co.bn + +// Postman, Inc : https://postman.com +// Submitted by Rahul Dhawan +postman-echo.com +pstmn.io +mock.pstmn.io +httpbin.org + +// prequalifyme.today : https://prequalifyme.today +// Submitted by DeepakTiwari deepak@ivylead.io +prequalifyme.today + +// prgmr.com : https://prgmr.com/ +// Submitted by Sarah Newman +xen.prgmr.com + +// priv.at : http://www.nic.priv.at/ +// Submitted by registry +priv.at + +// PROJECT ELIV : https://eliv.kr/ +// Submitted by PROJECT ELIV Domain Team +c01.kr +eliv-cdn.kr +eliv-dns.kr +mmv.kr +vki.kr + +// project-study : https://project-study.com +// Submitted by yumenewa +dev.project-study.com + +// Protonet GmbH : http://protonet.io +// Submitted by Martin Meier +protonet.io + +// PT Ekossistim Indo Digital : https://e.id +// Submitted by Eid Team +e.id + +// Publication Presse Communication SARL : https://ppcom.fr +// Submitted by Yaacov Akiba Slama +chirurgiens-dentistes-en-france.fr +byen.site + +// pubtls.org : https://www.pubtls.org +// Submitted by Kor Nielsen +pubtls.org + +// PythonAnywhere LLP : https://www.pythonanywhere.com +// Submitted by Giles Thomas +pythonanywhere.com +eu.pythonanywhere.com + +// QA2 +// Submitted by Daniel Dent : https://www.danieldent.com/ +qa2.com + +// QCX +// Submitted by Cassandra Beelen +qcx.io +*.sys.qcx.io + +// QNAP System Inc : https://www.qnap.com +// Submitted by Nick Chang +myqnapcloud.cn +alpha-myqnapcloud.com +dev-myqnapcloud.com +mycloudnas.com +mynascloud.com +myqnapcloud.com + +// QOTO, Org. +// Submitted by Jeffrey Phillips Freeman +qoto.io + +// Qualifio : https://qualifio.com/ +// Submitted by Xavier De Cock +qualifioapp.com + +// Quality Unit : https://qualityunit.com +// Submitted by Vasyl Tsalko +ladesk.com + +// Qualy : https://qualyhq.com +// Submitted by Raphael Arias +*.qualyhqpartner.com +*.qualyhqportal.com + +// QuickBackend : https://www.quickbackend.com +// Submitted by Dani Biro +qbuser.com + +// Quip : https://quip.com +// Submitted by Patrick Linehan +*.quipelements.com + +// Qutheory LLC : http://qutheory.io +// Submitted by Jonas Schwartz +vapor.cloud +vaporcloud.io + +// Rackmaze LLC : https://www.rackmaze.com +// Submitted by Kirill Pertsev +rackmaze.com +rackmaze.net + +// Rad Web Hosting : https://radwebhosting.com +// Submitted by Scott Claeys +cloudsite.builders +myradweb.net +servername.us + +// Radix FZC : http://domains.in.net +// Submitted by Gavin Brown +web.in +in.net + +// Raidboxes GmbH : https://raidboxes.de +// Submitted by Auke Tembrink +myrdbx.io +site.rb-hosting.io + +// Railway Corporation : https://railway.com +// Submitted by Phineas Walton +up.railway.app + +// Rancher Labs, Inc : https://rancher.com +// Submitted by Vincent Fiduccia +*.on-rancher.cloud +*.on-k3s.io +*.on-rio.io + +// RavPage : https://www.ravpage.co.il +// Submitted by Roni Horowitz +ravpage.co.il + +// Read The Docs, Inc : https://www.readthedocs.org +// Submitted by David Fischer +readthedocs-hosted.com +readthedocs.io + +// Red Hat, Inc. OpenShift : https://openshift.redhat.com/ +// Submitted by Tim Kramer +rhcloud.com + +// Redgate Software : https://red-gate.com +// Submitted by Andrew Farries +instances.spawn.cc + +// Render : https://render.com +// Submitted by Anurag Goel +onrender.com +app.render.com + +// Repl.it : https://repl.it +// Submitted by Lincoln Bergeson +replit.app +id.replit.app +firewalledreplit.co +id.firewalledreplit.co +repl.co +id.repl.co +replit.dev +archer.replit.dev +bones.replit.dev +canary.replit.dev +global.replit.dev +hacker.replit.dev +id.replit.dev +janeway.replit.dev +kim.replit.dev +kira.replit.dev +kirk.replit.dev +odo.replit.dev +paris.replit.dev +picard.replit.dev +pike.replit.dev +prerelease.replit.dev +reed.replit.dev +riker.replit.dev +sisko.replit.dev +spock.replit.dev +staging.replit.dev +sulu.replit.dev +tarpit.replit.dev +teams.replit.dev +tucker.replit.dev +wesley.replit.dev +worf.replit.dev +repl.run + +// Resin.io : https://resin.io +// Submitted by Tim Perry +resindevice.io +devices.resinstaging.io + +// RethinkDB : https://www.rethinkdb.com/ +// Submitted by Chris Kastorff +hzc.io + +// Rico Developments Limited : https://adimo.co +// Submitted by Colin Brown +adimo.co.uk + +// Riseup Networks : https://riseup.net +// Submitted by Micah Anderson +itcouldbewor.se + +// Roar Domains LLC : https://roar.basketball/ +// Submitted by Gavin Brown +aus.basketball +nz.basketball + +// ROBOT PAYMENT INC. : https://www.robotpayment.co.jp/ +// Submitted by Kentaro Takamori +subsc-pay.com +subsc-pay.net + +// Rochester Institute of Technology : http://www.rit.edu/ +// Submitted by Jennifer Herting +git-pages.rit.edu + +// Rocky Enterprise Software Foundation : https://resf.org +// Submitted by Neil Hanlon +rocky.page + +// Ruhr University Bochum : https://www.ruhr-uni-bochum.de/ +// Submitted by Andreas Jobs +rub.de +ruhr-uni-bochum.de +io.noc.ruhr-uni-bochum.de + +// Rusnames Limited : http://rusnames.ru/ +// Submitted by Sergey Zotov +биз.рус +ком.рус +крым.рус +мир.рус +мск.рус +орг.рус +самара.рус +сочи.рус +спб.рус +я.рус + +// Russian Academy of Sciences +// Submitted by Tech Support +ras.ru + +// Sakura Frp : https://www.natfrp.com +// Submitted by Bobo Liu +nyat.app + +// SAKURA Internet Inc. : https://www.sakura.ad.jp/ +// Submitted by Internet Service Department +180r.com +dojin.com +sakuratan.com +sakuraweb.com +x0.com +2-d.jp +bona.jp +crap.jp +daynight.jp +eek.jp +flop.jp +halfmoon.jp +jeez.jp +matrix.jp +mimoza.jp +ivory.ne.jp +mail-box.ne.jp +mints.ne.jp +mokuren.ne.jp +opal.ne.jp +sakura.ne.jp +sumomo.ne.jp +topaz.ne.jp +netgamers.jp +nyanta.jp +o0o0.jp +rdy.jp +rgr.jp +rulez.jp +s3.isk01.sakurastorage.jp +s3.isk02.sakurastorage.jp +saloon.jp +sblo.jp +skr.jp +tank.jp +uh-oh.jp +undo.jp +rs.webaccel.jp +user.webaccel.jp +websozai.jp +xii.jp +squares.net +jpn.org +kirara.st +x0.to +from.tv +sakura.tv + +// Salesforce.com, Inc. : https://salesforce.com/ +// Submitted by Salesforce Public Suffix List Team +*.builder.code.com +*.dev-builder.code.com +*.stg-builder.code.com +*.001.test.code-builder-stg.platform.salesforce.com +*.d.crm.dev +*.w.crm.dev +*.wa.crm.dev +*.wb.crm.dev +*.wc.crm.dev +*.wd.crm.dev +*.we.crm.dev +*.wf.crm.dev + +// Sandstorm Development Group, Inc. : https://sandcats.io/ +// Submitted by Asheesh Laroia +sandcats.io + +// SBE network solutions GmbH : https://www.sbe.de/ +// Submitted by Norman Meilick +logoip.com +logoip.de + +// Scaleway : https://www.scaleway.com/ +// Submitted by Scaleway PSL Maintainer +fr-par-1.baremetal.scw.cloud +fr-par-2.baremetal.scw.cloud +nl-ams-1.baremetal.scw.cloud +cockpit.fr-par.scw.cloud +ddl.fr-par.scw.cloud +dtwh.fr-par.scw.cloud +fnc.fr-par.scw.cloud +functions.fnc.fr-par.scw.cloud +ifr.fr-par.scw.cloud +k8s.fr-par.scw.cloud +nodes.k8s.fr-par.scw.cloud +kafk.fr-par.scw.cloud +mgdb.fr-par.scw.cloud +rdb.fr-par.scw.cloud +s3.fr-par.scw.cloud +s3-website.fr-par.scw.cloud +scbl.fr-par.scw.cloud +whm.fr-par.scw.cloud +priv.instances.scw.cloud +pub.instances.scw.cloud +k8s.scw.cloud +cockpit.nl-ams.scw.cloud +ddl.nl-ams.scw.cloud +dtwh.nl-ams.scw.cloud +ifr.nl-ams.scw.cloud +k8s.nl-ams.scw.cloud +nodes.k8s.nl-ams.scw.cloud +kafk.nl-ams.scw.cloud +mgdb.nl-ams.scw.cloud +rdb.nl-ams.scw.cloud +s3.nl-ams.scw.cloud +s3-website.nl-ams.scw.cloud +scbl.nl-ams.scw.cloud +whm.nl-ams.scw.cloud +cockpit.pl-waw.scw.cloud +ddl.pl-waw.scw.cloud +dtwh.pl-waw.scw.cloud +ifr.pl-waw.scw.cloud +k8s.pl-waw.scw.cloud +nodes.k8s.pl-waw.scw.cloud +kafk.pl-waw.scw.cloud +mgdb.pl-waw.scw.cloud +rdb.pl-waw.scw.cloud +s3.pl-waw.scw.cloud +s3-website.pl-waw.scw.cloud +scbl.pl-waw.scw.cloud +scalebook.scw.cloud +smartlabeling.scw.cloud +dedibox.fr + +// schokokeks.org GbR : https://schokokeks.org/ +// Submitted by Hanno Böck +schokokeks.net + +// Scottish Government : https://www.gov.scot +// Submitted by Martin Ellis +gov.scot +service.gov.scot + +// Scry Security : http://www.scrysec.com +// Submitted by Shante Adam +scrysec.com + +// Scrypted : https://scrypted.app +// Submitted by Koushik Dutta +client.scrypted.io + +// Securepoint GmbH : https://www.securepoint.de +// Submitted by Erik Anders +firewall-gateway.com +firewall-gateway.de +my-gateway.de +my-router.de +spdns.de +spdns.eu +firewall-gateway.net +my-firewall.org +myfirewall.org +spdns.org + +// Seidat : https://www.seidat.com +// Submitted by Artem Kondratev +seidat.net + +// Sellfy : https://sellfy.com +// Submitted by Yuriy Romadin +sellfy.store + +// Sendmsg : https://www.sendmsg.co.il +// Submitted by Assaf Stern +minisite.ms + +// Senseering GmbH : https://www.senseering.de +// Submitted by Felix Mönckemeyer +senseering.net + +// Servebolt AS : https://servebolt.com +// Submitted by Daniel Kjeserud +servebolt.cloud + +// Service Online LLC : http://drs.ua/ +// Submitted by Serhii Bulakh +biz.ua +co.ua +pp.ua + +// Shanghai Accounting Society : https://www.sasf.org.cn +// Submitted by Information Administration +as.sh.cn + +// Sheezy.Art : https://sheezy.art +// Submitted by Nyoom +sheezy.games + +// Shopblocks : http://www.shopblocks.com/ +// Submitted by Alex Bowers +myshopblocks.com + +// Shopify : https://www.shopify.com +// Submitted by Alex Richter +myshopify.com + +// Shopit : https://www.shopitcommerce.com/ +// Submitted by Craig McMahon +shopitsite.com + +// shopware AG : https://shopware.com +// Submitted by Jens Küper +shopware.shop +shopware.store + +// Siemens Mobility GmbH +// Submitted by Oliver Graebner +mo-siemens.io + +// SinaAppEngine : http://sae.sina.com.cn/ +// Submitted by SinaAppEngine +1kapp.com +appchizi.com +applinzi.com +sinaapp.com +vipsinaapp.com + +// Siteleaf : https://www.siteleaf.com/ +// Submitted by Skylar Challand +siteleaf.net + +// Small Technology Foundation : https://small-tech.org +// Submitted by Aral Balkan +small-web.org + +// Smallregistry by Promopixel SARL : https://www.smallregistry.net +// Former AFNIC's SLDs +// Submitted by Jérôme Lipowicz +aeroport.fr +avocat.fr +chambagri.fr +chirurgiens-dentistes.fr +experts-comptables.fr +medecin.fr +notaires.fr +pharmacien.fr +port.fr +veterinaire.fr + +// Smoove.io : https://www.smoove.io/ +// Submitted by Dan Kozak +vp4.me + +// Snowflake Inc : https://www.snowflake.com/ +// Submitted by Sam Haar +*.snowflake.app +*.privatelink.snowflake.app +streamlit.app +streamlitapp.com + +// Snowplow Analytics : https://snowplowanalytics.com/ +// Submitted by Ian Streeter +try-snowplow.com + +// Software Consulting Michal Zalewski : https://www.mafelo.com +// Submitted by Michal Zalewski +mafelo.net + +// Sony Interactive Entertainment LLC : https://sie.com/ +// Submitted by David Coles +playstation-cloud.com + +// SourceHut : https://sourcehut.org +// Submitted by Drew DeVault +srht.site + +// SourceLair PC : https://www.sourcelair.com +// Submitted by Antonis Kalipetis +apps.lair.io +*.stolos.io + +// sourceWAY GmbH : https://sourceway.de +// Submitted by Richard Reiber +4.at +my.at +my.de +*.nxa.eu +nx.gw + +// SpeedPartner GmbH : https://www.speedpartner.de/ +// Submitted by Stefan Neufeind +customer.speedpartner.de + +// Spreadshop (sprd.net AG) : https://www.spreadshop.com/ +// Submitted by Martin Breest +myspreadshop.at +myspreadshop.com.au +myspreadshop.be +myspreadshop.ca +myspreadshop.ch +myspreadshop.com +myspreadshop.de +myspreadshop.dk +myspreadshop.es +myspreadshop.fi +myspreadshop.fr +myspreadshop.ie +myspreadshop.it +myspreadshop.net +myspreadshop.nl +myspreadshop.no +myspreadshop.pl +myspreadshop.se +myspreadshop.co.uk + +// StackBlitz : https://stackblitz.com +// Submitted by Dominic Elm & Albert Pai +w-corp-staticblitz.com +w-credentialless-staticblitz.com +w-staticblitz.com +bolt.host + +// Stackhero : https://www.stackhero.io +// Submitted by Adrien Gillon +stackhero-network.com + +// STACKIT GmbH & Co. KG : https://www.stackit.de/en/ +// Submitted by STACKIT-DNS Team (Simon Stier) +runs.onstackit.cloud +stackit.gg +stackit.rocks +stackit.run +stackit.zone + +// Staclar : https://staclar.com +// Submitted by Q Misell +// Submitted by Matthias Merkel +musician.io +novecore.site + +// Standard Library : https://stdlib.com +// Submitted by Jacob Lee +api.stdlib.com + +// stereosense GmbH : https://www.involve.me +// Submitted by Florian Burmann +feedback.ac +forms.ac +assessments.cx +calculators.cx +funnels.cx +paynow.cx +quizzes.cx +researched.cx +tests.cx +surveys.so + +// Storacha Network : https://storacha.network +// Submitted by Alan Shaw +ipfs.storacha.link +ipfs.w3s.link + +// Storebase : https://www.storebase.io +// Submitted by Tony Schirmer +storebase.store + +// Storipress : https://storipress.com +// Submitted by Benno Liu +storipress.app + +// Storj Labs Inc. : https://storj.io/ +// Submitted by Philip Hutchins +storj.farm + +// Strapi : https://strapi.io/ +// Submitted by Florent Baldino +strapiapp.com +media.strapiapp.com + +// Strategic System Consulting (eApps Hosting) : https://www.eapps.com/ +// Submitted by Alex Oancea +vps-host.net +atl.jelastic.vps-host.net +njs.jelastic.vps-host.net +ric.jelastic.vps-host.net + +// Streak : https://streak.com +// Submitted by Blake Kadatz +streak-link.com +streaklinks.com +streakusercontent.com + +// Student-Run Computing Facility : https://www.srcf.net/ +// Submitted by Edwin Balani +soc.srcf.net +user.srcf.net + +// Studenten Net Twente : http://www.snt.utwente.nl/ +// Submitted by Silke Hofstra +utwente.io + +// Sub 6 Limited : http://www.sub6.com +// Submitted by Dan Miller +temp-dns.com + +// Supabase : https://supabase.io +// Submitted by Supabase Security +supabase.co +realtime.supabase.co +storage.supabase.co +supabase.in +supabase.net + +// Syncloud : https://syncloud.org +// Submitted by Boris Rybalkin +syncloud.it + +// Synology, Inc. : https://www.synology.com/ +// Submitted by Rony Weng +dscloud.biz +direct.quickconnect.cn +dsmynas.com +familyds.com +diskstation.me +dscloud.me +i234.me +myds.me +synology.me +dscloud.mobi +dsmynas.net +familyds.net +dsmynas.org +familyds.org +direct.quickconnect.to +vpnplus.to + +// Tabit Technologies Ltd. : https://tabit.cloud/ +// Submitted by Oren Agiv +mytabit.com +mytabit.co.il +tabitorder.co.il + +// TAIFUN Software AG : http://taifun-software.de +// Submitted by Bjoern Henke +taifun-dns.de + +// Tailor Inc. : https://www.tailor.tech +// Submitted by Ryuzo Yamamoto +erp.dev +web.erp.dev + +// Tailscale Inc. : https://www.tailscale.com +// Submitted by David Anderson +ts.net +*.c.ts.net + +// TASK geographical domains : https://task.gda.pl/en/services/for-entrepreneurs/ +gda.pl +gdansk.pl +gdynia.pl +med.pl +sopot.pl + +// Tave Creative Corp : https://tave.com/ +// Submitted by Adrian Ziemkowski +taveusercontent.com + +// tawk.to, Inc : https://www.tawk.to +// Submitted by tawk.to developer team +p.tawk.email +p.tawkto.email + +// Tche.br : https://tche.br +// Submitted by Bruno Lorensi +tche.br + +// team.blue : https://team.blue +// Submitted by Cedric Dubois +site.tb-hosting.com + +// Teckids e.V. : https://www.teckids.org +// Submitted by Dominik George +edugit.io +s3.teckids.org + +// Telebit : https://telebit.cloud +// Submitted by AJ ONeal +telebit.app +telebit.io +*.telebit.xyz + +// Teleport : https://goteleport.com +// Submitted by Rob Picard +teleport.sh + +// Thingdust AG : https://thingdust.com/ +// Submitted by Adrian Imboden +*.firenet.ch +*.svc.firenet.ch +reservd.com +thingdustdata.com +cust.dev.thingdust.io +reservd.dev.thingdust.io +cust.disrec.thingdust.io +reservd.disrec.thingdust.io +cust.prod.thingdust.io +cust.testing.thingdust.io +reservd.testing.thingdust.io + +// ticket i/O GmbH : https://ticket.io +// Submitted by Christian Franke +tickets.io + +// Tlon.io : https://tlon.io +// Submitted by Mark Staarink +arvo.network +azimuth.network +tlon.network + +// Tor Project, Inc. : https://torproject.org +// Submitted by Antoine Beaupré +torproject.net +pages.torproject.net + +// TownNews.com : http://www.townnews.com +// Submitted by Dustin Ward +townnews-staging.com + +// TrafficPlex GmbH : https://www.trafficplex.de/ +// Submitted by Phillipp Röll +12hp.at +2ix.at +4lima.at +lima-city.at +12hp.ch +2ix.ch +4lima.ch +lima-city.ch +trafficplex.cloud +de.cool +12hp.de +2ix.de +4lima.de +lima-city.de +1337.pictures +clan.rip +lima-city.rocks +webspace.rocks +lima.zone + +// TransIP : https://www.transip.nl +// Submitted by Rory Breuk and Cedric Dubois +*.transurl.be +*.transurl.eu +site.transip.me +*.transurl.nl + +// TuxFamily : http://tuxfamily.org +// Submitted by TuxFamily administrators +tuxfamily.org + +// TwoDNS : https://www.twodns.de/ +// Submitted by TwoDNS-Support +dd-dns.de +dray-dns.de +draydns.de +dyn-vpn.de +dynvpn.de +mein-vigor.de +my-vigor.de +my-wan.de +syno-ds.de +synology-diskstation.de +synology-ds.de +diskstation.eu +diskstation.org + +// Typedream : https://typedream.com +// Submitted by Putri Karunia +typedream.app + +// Typeform : https://www.typeform.com +// Submitted by Typeform +pro.typeform.com + +// Uberspace : https://uberspace.de +// Submitted by Moritz Werner +uber.space + +// UDR Limited : http://www.udr.hk.com +// Submitted by registry +hk.com +inc.hk +ltd.hk +hk.org + +// UK Intis Telecom LTD : https://it.com +// Submitted by ITComdomains +it.com + +// Unison Computing, PBC : https://unison.cloud +// Submitted by Simon Højberg +unison-services.cloud + +// United Gameserver GmbH : https://united-gameserver.de +// Submitted by Stefan Schwarz +virtual-user.de +virtualuser.de + +// United States Writing Corporation : https://uswriting.co +// Submitted by Andrew Sampson +obj.ag + +// UNIVERSAL DOMAIN REGISTRY : https://www.udr.org.yt/ +// see also: whois -h whois.udr.org.yt help +// Submitted by Atanunu Igbunuroghene +name.pm +sch.tf +biz.wf +sch.wf +org.yt + +// University of Banja Luka : https://unibl.org +// Domains for Republic of Srpska administrative entity. +// Submitted by Marko Ivanovic +rs.ba + +// University of Bielsko-Biala regional domain : http://dns.bielsko.pl/ +// Submitted by Marcin +bielsko.pl + +// urown.net : https://urown.net +// Submitted by Hostmaster +urown.cloud +dnsupdate.info + +// US REGISTRY LLC : http://us.org +// Submitted by Gavin Brown +us.org + +// V.UA Domain Registry: https://www.v.ua/ +// Submitted by Serhii Rostilo +v.ua + +// Val Town, Inc : https://val.town/ +// Submitted by Tom MacWright +val.run +web.val.run + +// Vercel, Inc : https://vercel.com/ +// Submitted by Laurens Duijvesteijn +vercel.app +v0.build +vercel.dev +vusercontent.net +vercel.run +now.sh + +// VeryPositive SIA : http://very.lv +// Submitted by Danko Aleksejevs +2038.io + +// Virtual-Info : https://www.virtual-info.info/ +// Submitted by Adnan RIHAN +v-info.info + +// VistaBlog : https://vistablog.ir/ +// Submitted by Hossein Piri +vistablog.ir + +// Viva Republica, Inc. : https://toss.im/ +// Submitted by Deus Team +deus-canvas.com + +// Voorloper.com : https://voorloper.com +// Submitted by Nathan van Bakel +voorloper.cloud + +// Vultr Objects : https://www.vultr.com/products/object-storage/ +// Submitted by Niels Maumenee +*.vultrobjects.com + +// Waffle Computer Inc., Ltd. : https://docs.waffleinfo.com +// Submitted by Masayuki Note +wafflecell.com + +// Walrus : https://walrus.xyz +// Submitted by Max Spector +wal.app + +// Webflow, Inc. : https://www.webflow.com +// Submitted by Webflow Security Team +webflow.io +webflowtest.io + +// WebHare bv : https://www.webhare.com/ +// Submitted by Arnold Hendriks +*.webhare.dev + +// WebHotelier Technologies Ltd : https://www.webhotelier.net/ +// Submitted by Apostolos Tsakpinis +bookonline.app +hotelwithflight.com +reserve-online.com +reserve-online.net + +// WebPros International, LLC : https://webpros.com/ +// Submitted by Nicolas Rochelemagne +cprapid.com +pleskns.com +wp2.host +pdns.page +plesk.page +cpanel.site +wpsquared.site + +// WebWaddle Ltd : https://webwaddle.com/ +// Submitted by Merlin Glander +*.wadl.top + +// Western Digital Technologies, Inc : https://www.wdc.com +// Submitted by Jung Jin +remotewd.com + +// Whatbox Inc. : https://whatbox.ca/ +// Submitted by Anthony Ryan +box.ca + +// WIARD Enterprises : https://wiardweb.com +// Submitted by Kidd Hustle +pages.wiardweb.com + +// Wikimedia Foundation : https://wikitech.wikimedia.org +// Submitted by Timo Tijhof +toolforge.org +wmcloud.org +beta.wmcloud.org +wmflabs.org + +// William Harrison : https://wharrison.com.au +// Submitted by William Harrison +wdh.app +hrsn.au +vps.hrsn.au +hrsn.dev +is-a.dev +localcert.net + +// Windsurf : https://windsurf.com +// Submitted by Douglas Chen +windsurf.app +windsurf.build + +// WISP : https://wisp.gg +// Submitted by Stepan Fedotov +panel.gg +daemon.panel.gg + +// Wix.com, Inc. : https://www.wix.com +// Submitted by Shahar Talmi / Alon Kochba +wixsite.com +wixstudio.com +editorx.io +wixstudio.io +wix.run + +// Wizard Zines : https://wizardzines.com +// Submitted by Julia Evans +messwithdns.com + +// WoltLab GmbH : https://www.woltlab.com +// Submitted by Tim Düsterhus +woltlab-demo.com +myforum.community +community-pro.de +diskussionsbereich.de +community-pro.net +meinforum.net + +// Woods Valldata : https://www.woodsvalldata.co.uk/ +// Submitted by Chris Whittle +affinitylottery.org.uk +raffleentry.org.uk +weeklylottery.org.uk + +// WP Engine : https://wpengine.com/ +// Submitted by Michael Smith +// Submitted by Brandon DuRette +wpenginepowered.com +js.wpenginepowered.com + +// XenonCloud GbR : https://xenoncloud.net +// Submitted by Julian Uphoff +*.xenonconnect.de +half.host + +// XnBay Technology : http://www.xnbay.com/ +// Submitted by XnBay Developer +xnbay.com +u2.xnbay.com +u2-local.xnbay.com + +// XS4ALL Internet bv : https://www.xs4all.nl/ +// Submitted by Daniel Mostertman +cistron.nl +demon.nl +xs4all.space + +// Yandex.Cloud LLC : https://cloud.yandex.com +// Submitted by Alexander Lodin +yandexcloud.net +storage.yandexcloud.net +website.yandexcloud.net +sourcecraft.site + +// YesCourse Pty Ltd : https://yescourse.com +// Submitted by Atul Bhouraskar +official.academy + +// Yola : https://www.yola.com/ +// Submitted by Stefano Rivera +yolasite.com + +// Yunohost : https://yunohost.org +// Submitted by Valentin Grimaud +ynh.fr +nohost.me +noho.st + +// ZaNiC : http://www.za.net/ +// Submitted by registry +za.net +za.org + +// ZAP-Hosting GmbH & Co. KG : https://zap-hosting.com +// Submitted by Julian Alker +zap.cloud + +// Zeabur : https://zeabur.com/ +// Submitted by Zeabur Team +zeabur.app + +// Zerops : https://zerops.io/ +// Submitted by Zerops Team +*.zerops.app + +// Zine EOOD : https://zine.bg/ +// Submitted by Martin Angelov +bss.design + +// Zitcom A/S : https://www.zitcom.dk +// Submitted by Emil Stahl +basicserver.io +virtualserver.io +enterprisecloud.nu + +// Zone.ID: https://zone.id +// Submitted by Gx1.org +zone.id + +// ZoneABC : https://zoneabc.net +// Submitted by ZoneABC Team +zabc.net + +// ===END PRIVATE DOMAINS=== \ No newline at end of file diff --git a/dmarc/src/main/test/java/org/apache/james/dmarc/DMARCTest.java b/dmarc/src/main/test/java/org/apache/james/dmarc/DMARCTest.java new file mode 100644 index 0000000..cdbed99 --- /dev/null +++ b/dmarc/src/main/test/java/org/apache/james/dmarc/DMARCTest.java @@ -0,0 +1,56 @@ +/****************************************************************************** + * 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.dmarc; + +import org.junit.Test; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class DMARCTest { + private static final String DMARC_RESPONSE_TEMPLATE = "dmarc=%s (p=%s) header.from=%s"; + private static final String DMARC_NON_RESPONSE_TEMPLATE = "dmarc=none (no policy) header.from="; + + private final MockPublicKeyRecordRetrieverDmarc recordRetrieverDmarc = new MockPublicKeyRecordRetrieverDmarc( + MockPublicKeyRecordRetrieverDmarc.DmarcRecord.dmarcOf( + "d1.example", + "k=rsa; v=DMARC1; p=reject; pct=100; rua=mailto:noc@d1.example"), + MockPublicKeyRecordRetrieverDmarc.DmarcRecord.dmarcOf( + "mail.replit.app", + "k=rsa; v=DMARC1; p=reject; aspf=r; adkim=r; pct=100; rua=mailto:noc@d1.example"), + MockPublicKeyRecordRetrieverDmarc.DmarcRecord.dmarcOf( + "test.replit.app", + "k=rsa; v=DMARC1; p=reject; aspf=s; adkim=s; pct=100; rua=mailto:noc@d1.example") + ); + + private final List passRequests = List.of( + new DmarcRequestMock("/mail/e1.eml","pass", "d1.example", "softfail (spfCheck: transitioning domain of d1.example does not designate 222.222.222.222 as permitted sender) client-ip=222.222.222.222; envelope-from=jqd@d1.example; helo=d1.example", "d1.example", "dmarc=pass (p=reject) header.from=d1.example"), + new DmarcRequestMock("/mail/e2.eml","pass", "replit.app", "pass client-ip=222.222.222.222; envelope-from=jqd@id.firewalledreplit.co; helo=replit.app", "replit.app", "dmarc=pass (p=reject) header.from=mail.replit.app"), + new DmarcRequestMock("/mail/e3.eml","pass", "replit.app", "pass client-ip=222.222.222.222; envelope-from=jqd@id.firewalledreplit.co; helo=replit.app", "replit.app", "dmarc=fail (p=reject) header.from=test.replit.app") + ); + + DMARCVerifier dmarcVerifier = new DMARCVerifier(DMARC_RESPONSE_TEMPLATE, DMARC_NON_RESPONSE_TEMPLATE, recordRetrieverDmarc); + + @Test + public void generate_and_verify_dmarc_pass() { + passRequests.forEach(r -> { + assertThat(dmarcVerifier.runDmarcCheck(r.message(), r.spfResult(), r.spfDomain(), r.dkimResult(), r.dkimDomain())).isEqualTo(r.expectedResult()); + }); + } +} \ No newline at end of file diff --git a/dmarc/src/main/test/java/org/apache/james/dmarc/DmarcRequestMock.java b/dmarc/src/main/test/java/org/apache/james/dmarc/DmarcRequestMock.java new file mode 100644 index 0000000..cb9364e --- /dev/null +++ b/dmarc/src/main/test/java/org/apache/james/dmarc/DmarcRequestMock.java @@ -0,0 +1,77 @@ +package org.apache.james.dmarc; + +import org.apache.james.dmarc.exceptions.DmarcException; +import org.apache.james.jdkim.DKIMCommon; +import org.apache.james.mime4j.dom.Message; +import org.apache.james.mime4j.message.DefaultMessageBuilder; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.charset.StandardCharsets; + +public class DmarcRequestMock { + private final Message _message; + private final String _dkimResult; + private final String _dkimDomain; + private final String _spfResult; + private final String _spfDomain; + private final String _expectedResult; + + public DmarcRequestMock(String emailPath, String dkimResult, String dkimDomain, String spfResult, String spfDomain, String expectedResult) { + _dkimResult = dkimResult; + _dkimDomain = dkimDomain; + _spfResult = spfResult; + _spfDomain = spfDomain; + _expectedResult = expectedResult; + ByteArrayInputStream emailStream = null; + try { + emailStream = readFileToByteArrayInputStream(emailPath); + DefaultMessageBuilder builder = new DefaultMessageBuilder(); + _message = builder.parseMessage(emailStream); + } catch (URISyntaxException e) { + throw new DmarcException("URI Syntax Exception when loading test email file", e); + } catch (IOException e) { + throw new DmarcException("IOException when loading test email file", e); + } + } + + String dkimResult() { + return _dkimResult; + } + + String dkimDomain() { + return _dkimDomain; + } + + String spfResult() { + return _spfResult; + } + + String spfDomain() { + return _spfDomain; + } + + String expectedResult() { + return _expectedResult; + } + + Message message() { + return _message; + } + + private ByteArrayInputStream readFileToByteArrayInputStream(String fileName) throws URISyntaxException, IOException { + URL resource = this.getClass().getResource(fileName); + FileInputStream file = new FileInputStream(new File(resource.toURI())); + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + + DKIMCommon.streamCopy(file, byteArrayOutputStream); + String string = byteArrayOutputStream.toString(); + return new ByteArrayInputStream(string.getBytes(StandardCharsets.UTF_8)); + } + +} diff --git a/dmarc/src/main/test/java/org/apache/james/dmarc/MockPublicKeyRecordRetrieverDmarc.java b/dmarc/src/main/test/java/org/apache/james/dmarc/MockPublicKeyRecordRetrieverDmarc.java new file mode 100644 index 0000000..c77b853 --- /dev/null +++ b/dmarc/src/main/test/java/org/apache/james/dmarc/MockPublicKeyRecordRetrieverDmarc.java @@ -0,0 +1,65 @@ +/****************************************************************************** + * 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.dmarc; + +import org.apache.james.dmarc.exceptions.DmarcException; +import org.apache.james.jdkim.MockPublicKeyRecordRetriever; +import org.apache.james.jdkim.exceptions.PermFailException; +import org.apache.james.jdkim.exceptions.TempFailException; + +import java.util.List; + +public class MockPublicKeyRecordRetrieverDmarc extends MockPublicKeyRecordRetriever implements PublicKeyRecordRetrieverDmarc { + public static final String DMARC = "_dmarc."; + + @Override + public String getDmarcRecord(String query) { + try { + List recs = super.getRecords("dns/txt", DMARC, query); + if (recs == null || recs.isEmpty()) { + return null; + } + return recs.getFirst(); + } catch (TempFailException e) { + throw new DmarcException("Temporary failure looking up DMARC record", e); + } catch (PermFailException e) { + throw new DmarcException("Permanent failure looking up DMARC record", e); + } + } + + @Override + public List getRecords(CharSequence methodAndOption, CharSequence selector, CharSequence token) throws TempFailException, PermFailException { + return List.of(); + } + + public static class DmarcRecord extends MockPublicKeyRecordRetriever.Record { + + public DmarcRecord(String domain, String dmarcRecord) { + super(DMARC, domain, dmarcRecord); + } + + public static DmarcRecord dmarcOf(String domain, String dmarcRecord) { + return new DmarcRecord(domain, dmarcRecord); + } + } + + public MockPublicKeyRecordRetrieverDmarc(Record... records) { + super(records); + } +} diff --git a/dmarc/src/main/test/java/org/apache/james/dmarc/PublicSuffixListTest.java b/dmarc/src/main/test/java/org/apache/james/dmarc/PublicSuffixListTest.java new file mode 100644 index 0000000..5771a4a --- /dev/null +++ b/dmarc/src/main/test/java/org/apache/james/dmarc/PublicSuffixListTest.java @@ -0,0 +1,60 @@ +/****************************************************************************** + * 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.dmarc; + +import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +public class PublicSuffixListTest { + + @Test + public void isPublicSuffix_shouldReturnTrueForKnownSuffix() { + assertTrue(PublicSuffixList.isPublicSuffix("com")); + assertTrue(PublicSuffixList.isPublicSuffix("CO.UK")); + assertTrue(PublicSuffixList.isPublicSuffix("replit.app")); + assertTrue(PublicSuffixList.isPublicSuffix("id.replit.app")); + } + + @Test + public void isPublicSuffix_shouldReturnFalseForUnknownSuffix() { + assertFalse(PublicSuffixList.isPublicSuffix("example")); + assertFalse(PublicSuffixList.isPublicSuffix("unknown.tld")); + assertFalse(PublicSuffixList.isPublicSuffix("mail.replit.app")); + } + + @Test + public void getOrgDomain_shouldReturnPublicSuffixIfMatched() { + assertEquals("co.uk", PublicSuffixList.getOrgDomain("example.co.uk")); + assertEquals("replit.app", PublicSuffixList.getOrgDomain("mail.replit.app")); + } + + @Test + public void getOrgDomain_shouldNotReturnPublicSuffixIfNotMatched() { + assertNotEquals("replit.app", PublicSuffixList.getOrgDomain("id.replit.app")); + assertNotEquals("firewalledreplit.co", PublicSuffixList.getOrgDomain("id.firewalledreplit.co")); + } + + @Test + public void getOrgDomain_shouldReturnInputIfNoSuffixMatched() { + assertEquals("mydomain.unknown", PublicSuffixList.getOrgDomain("mydomain.unknown")); + } +} \ No newline at end of file diff --git a/dmarc/src/main/test/resources/mail/e1.eml b/dmarc/src/main/test/resources/mail/e1.eml new file mode 100644 index 0000000..2470990 --- /dev/null +++ b/dmarc/src/main/test/resources/mail/e1.eml @@ -0,0 +1,31 @@ +Return-Path: +Received: from example.org (example.org [208.69.40.157]) + by gmail.example with ESMTP id d200mr22663000ykb.93.1421363207 + for ; Thu, 14 Jan 2015 15:02:40 -0800 (PST) +Received: from segv.d1.example (segv.d1.example [72.52.75.15]) + by lists.example.org (8.14.5/8.14.5) with ESMTP id t0EKaNU9010123 + for ; Thu, 14 Jan 2015 15:01:30 -0800 (PST) + (envelope-from jqd@d1.example) +Received: from [2001:DB8::1A] (w-x-y-z.dsl.static.isp.example [w.x.y.z]) + (authenticated bits=0) + by segv.d1.example with ESMTP id t0FN4a8O084569; + Thu, 14 Jan 2015 15:00:01 -0800 (PST) + (envelope-from jqd@d1.example) +Received: from mail-ob0-f188.google.example + (mail-ob0-f188.google.example [208.69.40.157]) by + clochette.example.org with ESMTP id d200mr22663000ykb.93.1421363268 + for ; Thu, 14 Jan 2015 15:03:15 -0800 (PST) +Message-ID: <54B84785.1060301@d1.example> +Date: Thu, 14 Jan 2015 15:00:01 -0800 +From: John Q Doe +To: arc@dmarc.example +Subject: [List 2] Example 1 +DKIM-Signature: a=rsa-sha256; + b=iEn8fLQ/ymdoZ4EkI3ELK3dTcc4jqn1VOvbNZWAMzcZcFiSKSZXgJ9kgXlBv8JGqaLFjuQi3+p73Al9P2JJU4IkBF1PSHrTI6rcdPyTWMP5yL6vKrn0tu0VdPhwPmbEr4H0yhYqc0KPPPzbJw668zoharH9Ljq43W8mj6sGSN18=; + c=relaxed/relaxed; s=origin2015; d=d1.example; v=1; + bh=KWSe46TZKCcDbH4klJPo+tjk5LWJnVRlP5pvjXFZYLQ=; h=Subject:From:To; + +Hey gang, +This is a test message. +--J. + diff --git a/dmarc/src/main/test/resources/mail/e2.eml b/dmarc/src/main/test/resources/mail/e2.eml new file mode 100644 index 0000000..ac0c1f8 --- /dev/null +++ b/dmarc/src/main/test/resources/mail/e2.eml @@ -0,0 +1,14 @@ +Return-Path: +Received: from replit.app (replit.co [208.69.40.157]) + by gmail.example with ESMTP id d200mr22663000ykb.93.1421363207 + for ; Thu, 14 Jan 2015 15:02:40 -0800 (PST) +Message-ID: <54B84785.1060301@id.firewalledreplit.co> +Date: Thu, 14 Jan 2015 15:00:01 -0800 +From: John Q Doe +To: arc@dmarc.example +Subject: [List 2] Example 1 + +Hey gang, +This is a test message. +--J. + diff --git a/dmarc/src/main/test/resources/mail/e3.eml b/dmarc/src/main/test/resources/mail/e3.eml new file mode 100644 index 0000000..311fd5b --- /dev/null +++ b/dmarc/src/main/test/resources/mail/e3.eml @@ -0,0 +1,14 @@ +Return-Path: +Received: from replit.app (replit.co [208.69.40.157]) + by gmail.example with ESMTP id d200mr22663000ykb.93.1421363207 + for ; Thu, 14 Jan 2015 15:02:40 -0800 (PST) +Message-ID: <54B84785.1060301@id.firewalledreplit.co> +Date: Thu, 14 Jan 2015 15:00:01 -0800 +From: John Q Doe +To: arc@dmarc.example +Subject: [List 2] Example 1 + +Hey gang, +This is a test message. +--J. + diff --git a/pom.xml b/pom.xml index a9aed59..419c2f4 100644 --- a/pom.xml +++ b/pom.xml @@ -41,6 +41,7 @@ assemble main arc + dmarc From 1f420fd7515c8e7893f91d45b5a880aee6e0bb7d Mon Sep 17 00:00:00 2001 From: Artem Grinchenko Date: Fri, 17 Oct 2025 16:33:10 -0400 Subject: [PATCH 081/114] Adding DMARC pom.xml that was missing in the previous commit --- dmarc/pom.xml | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 dmarc/pom.xml diff --git a/dmarc/pom.xml b/dmarc/pom.xml new file mode 100644 index 0000000..134fb6e --- /dev/null +++ b/dmarc/pom.xml @@ -0,0 +1,88 @@ + + + + 4.0.0 + + + apache-jdkim-project + org.apache.james.jdkim + 0.6-SNAPSHOT + ../pom.xml + + + apache-dmarc-library + + Apache James :: DMARC + A Java implementation for the DMARC specification. + http://james.apache.org/jdkim/main/ + 2008 + + + + org.apache.james.jdkim + apache-jdkim-library + ${project.version} + + + org.apache.james.jdkim + apache-jdkim-library + ${project.version} + test-jar + test + + + junit + junit + + + org.apache.james + apache-mime4j-dom + + + org.assertj + assertj-core + test + + + + + + + maven-jar-plugin + + + + test-jar + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 21 + 21 + --enable-preview + + + + + From 36da05ea41986f23c44d0a55fb920d4f9433da65 Mon Sep 17 00:00:00 2001 From: Artem Grinchenko Date: Fri, 17 Oct 2025 17:25:25 -0400 Subject: [PATCH 082/114] Remove maven compiler plugin in DMARC pom.xml causing build to fail --- dmarc/pom.xml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/dmarc/pom.xml b/dmarc/pom.xml index 134fb6e..fa09cc2 100644 --- a/dmarc/pom.xml +++ b/dmarc/pom.xml @@ -74,15 +74,6 @@
- - org.apache.maven.plugins - maven-compiler-plugin - - 21 - 21 - --enable-preview - -
From 0d7b82ef2feb0a57934e5c72e1a490359f7e2a50 Mon Sep 17 00:00:00 2001 From: Artem Grinchenko Date: Fri, 17 Oct 2025 17:34:44 -0400 Subject: [PATCH 083/114] Add to the maven-compiler-plugin in DMARC pom.xml to fix the auto build error --- dmarc/pom.xml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/dmarc/pom.xml b/dmarc/pom.xml index fa09cc2..d307741 100644 --- a/dmarc/pom.xml +++ b/dmarc/pom.xml @@ -74,6 +74,15 @@ + + org.apache.maven.plugins + maven-compiler-plugin + + + --enable-preview + + + From ac8d95b2b36a2cb27789f02bccfeca16fa1cffff Mon Sep 17 00:00:00 2001 From: Artem Grinchenko Date: Fri, 17 Oct 2025 17:53:52 -0400 Subject: [PATCH 084/114] Add to the maven-compiler-plugin in ARC pom.xml to fix the auto build error --- arc/pom.xml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/arc/pom.xml b/arc/pom.xml index 820ffb6..1b12fc5 100644 --- a/arc/pom.xml +++ b/arc/pom.xml @@ -82,4 +82,28 @@ test + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + ${target.jdk} + ${target.jdk} + + --enable-preview + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.1.2 + + --enable-preview + + + + From abe368cff7d16573232d5f6a06cece3313d47bc5 Mon Sep 17 00:00:00 2001 From: Artem Grinchenko Date: Fri, 17 Oct 2025 18:17:46 -0400 Subject: [PATCH 085/114] Updating ARC pom to make sure it can see DMARC test dependencies on Jenkins build --- arc/pom.xml | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/arc/pom.xml b/arc/pom.xml index 1b12fc5..df08487 100644 --- a/arc/pom.xml +++ b/arc/pom.xml @@ -95,14 +95,19 @@ --enable-preview - - - org.apache.maven.plugins - maven-surefire-plugin - 3.1.2 - - --enable-preview - + + + default-testCompile + + testCompile + + + + --enable-preview + + + + From 864a57dea8c9104db172ef163146e9a6e46d94ab Mon Sep 17 00:00:00 2001 From: Artem Grinchenko Date: Fri, 17 Oct 2025 18:36:45 -0400 Subject: [PATCH 086/114] Fixing the Jenkins build. Realigning to JDK 11 --- arc/pom.xml | 29 ------------------- .../arc/MockPublicKeyRecordRetrieverArc.java | 2 +- dmarc/pom.xml | 9 ------ .../MockPublicKeyRecordRetrieverDmarc.java | 2 +- 4 files changed, 2 insertions(+), 40 deletions(-) diff --git a/arc/pom.xml b/arc/pom.xml index df08487..820ffb6 100644 --- a/arc/pom.xml +++ b/arc/pom.xml @@ -82,33 +82,4 @@ test - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.8.1 - - ${target.jdk} - ${target.jdk} - - --enable-preview - - - - - default-testCompile - - testCompile - - - - --enable-preview - - - - - - - diff --git a/arc/src/test/java/org/apache/james/arc/MockPublicKeyRecordRetrieverArc.java b/arc/src/test/java/org/apache/james/arc/MockPublicKeyRecordRetrieverArc.java index 71d0505..8ed2e01 100644 --- a/arc/src/test/java/org/apache/james/arc/MockPublicKeyRecordRetrieverArc.java +++ b/arc/src/test/java/org/apache/james/arc/MockPublicKeyRecordRetrieverArc.java @@ -55,7 +55,7 @@ public String getSpfRecord(String helo, String from, String ip) { if (recs.isEmpty()) { return null; } - return recs.getFirst(); + return recs.get(0); } catch (TempFailException e) { throw new ArcException("Temporary failure looking up DMARC record", e); } catch (PermFailException e) { diff --git a/dmarc/pom.xml b/dmarc/pom.xml index d307741..fa09cc2 100644 --- a/dmarc/pom.xml +++ b/dmarc/pom.xml @@ -74,15 +74,6 @@ - - org.apache.maven.plugins - maven-compiler-plugin - - - --enable-preview - - - diff --git a/dmarc/src/main/test/java/org/apache/james/dmarc/MockPublicKeyRecordRetrieverDmarc.java b/dmarc/src/main/test/java/org/apache/james/dmarc/MockPublicKeyRecordRetrieverDmarc.java index c77b853..d819919 100644 --- a/dmarc/src/main/test/java/org/apache/james/dmarc/MockPublicKeyRecordRetrieverDmarc.java +++ b/dmarc/src/main/test/java/org/apache/james/dmarc/MockPublicKeyRecordRetrieverDmarc.java @@ -35,7 +35,7 @@ public String getDmarcRecord(String query) { if (recs == null || recs.isEmpty()) { return null; } - return recs.getFirst(); + return recs.get(0); } catch (TempFailException e) { throw new DmarcException("Temporary failure looking up DMARC record", e); } catch (PermFailException e) { From 2193cdb07b512721522fa5812ace73632437a236 Mon Sep 17 00:00:00 2001 From: Artem Grinchenko Date: Mon, 20 Oct 2025 11:10:18 -0400 Subject: [PATCH 087/114] Add missing license info. Reorder 'dmarc' and 'arc' modules in the parent pom' --- .../apache/james/dmarc/DmarcRequestMock.java | 18 ++++++++++++++++++ pom.xml | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/dmarc/src/main/test/java/org/apache/james/dmarc/DmarcRequestMock.java b/dmarc/src/main/test/java/org/apache/james/dmarc/DmarcRequestMock.java index cb9364e..d2f1b12 100644 --- a/dmarc/src/main/test/java/org/apache/james/dmarc/DmarcRequestMock.java +++ b/dmarc/src/main/test/java/org/apache/james/dmarc/DmarcRequestMock.java @@ -1,3 +1,21 @@ +/****************************************************************************** + * 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.dmarc; import org.apache.james.dmarc.exceptions.DmarcException; diff --git a/pom.xml b/pom.xml index 419c2f4..3fce6d3 100644 --- a/pom.xml +++ b/pom.xml @@ -40,8 +40,8 @@ assemble main - arc dmarc + arc From 786bb64ddf678a6c4ab2c2f4d3c99de6e12558f5 Mon Sep 17 00:00:00 2001 From: Artem Grinchenko Date: Tue, 4 Nov 2025 11:09:53 -0500 Subject: [PATCH 088/114] Rewrote PSL matching to handle exceptions/wildcards plus: - Added ARC validation outcome with details on the failure - Refactored logic such as `computeBTag` into separate methods - Added hard fail whenever multiple From headers detected - Rewrote tag extraction logic to do it all in one pass for efficiency - Simplified getTimeMeasure to use standard Java - Removed unnecessary Overrides --- .../apache/james/arc/ARCChainValidator.java | 14 +-- .../java/org/apache/james/arc/ARCSigner.java | 60 ++++++---- .../org/apache/james/arc/ArcSetBuilder.java | 108 +++++++++--------- .../james/arc/ArcSignatureRecordImpl.java | 52 +++------ .../james/arc/ArcValidationOutcome.java | 42 +++++++ .../apache/james/arc/AuthResultsBuilder.java | 49 ++++---- .../java/org/apache/james/arc/ARCTest.java | 11 +- .../org/apache/james/dmarc/DMARCVerifier.java | 100 ++++++---------- .../james/dmarc/DmarcValidationResult.java | 40 +++++++ .../java/org/apache/james/dmarc/PSLMatch.java | 26 +++++ .../apache/james/dmarc/PSLMatchOutcome.java | 59 ++++++++++ .../apache/james/dmarc/PublicSuffixList.java | 77 +++++++++---- .../org/apache/james/dmarc/DMARCTest.java | 8 +- .../james/dmarc/PublicSuffixListTest.java | 86 +++++++++++--- 14 files changed, 465 insertions(+), 267 deletions(-) create mode 100644 arc/src/main/java/org/apache/james/arc/ArcValidationOutcome.java create mode 100644 dmarc/src/main/java/org/apache/james/dmarc/DmarcValidationResult.java create mode 100644 dmarc/src/main/java/org/apache/james/dmarc/PSLMatch.java create mode 100644 dmarc/src/main/java/org/apache/james/dmarc/PSLMatchOutcome.java diff --git a/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java b/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java index d50c094..1a01e06 100644 --- a/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java +++ b/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java @@ -56,29 +56,29 @@ public ARCChainValidator(PublicKeyRetrieverArc keyRecordRetriever) { this._keyRecordRetriever = keyRecordRetriever; } - public ArcValidationResult validateArcChain(Message message) { + public ArcValidationOutcome validateArcChain(Message message) { Header messageHeaders = message.getHeader(); int curInstance = getCurrentInstance(messageHeaders); // Incremented by 1 if (curInstance == 1) { //we are the first ARC Hop and there is no previous ARC hops in the chain to validate - return ArcValidationResult.NONE; + return new ArcValidationOutcome(ArcValidationResult.NONE, "No previous ARC hops to validate"); } else if (curInstance > 51) { // Not allowed to be > 50 - return ArcValidationResult.FAIL; + return new ArcValidationOutcome(ArcValidationResult.FAIL, "ARC instance number exceeds maximum allowed value of 50"); } else { // there are previous ARC hops that need to be validated return validatePreviousArcHops(message, messageHeaders, curInstance); } } - private ArcValidationResult validatePreviousArcHops(Message message, Header messageHeaders, int myInstance) { + private ArcValidationOutcome validatePreviousArcHops(Message message, Header messageHeaders, int myInstance) { ARCVerifier arcVerifier = new ARCVerifier(_keyRecordRetriever); Map> arcHeadersByI = arcVerifier.getArcHeadersByI(messageHeaders.getFields()); int numArcInstances = myInstance - 1; boolean isArcSetStructureOK = arcVerifier.validateArcSetStructure(arcHeadersByI); if (!isArcSetStructureOK) { - return ArcValidationResult.FAIL; + return new ArcValidationOutcome(ArcValidationResult.FAIL, "ARC set structure is invalid"); } Set prevArcSet; @@ -87,10 +87,10 @@ private ArcValidationResult validatePreviousArcHops(Message message, Header mess boolean amsOk = checkArcAms(prevArcSet, message, arcVerifier); boolean asOk = checkArcSeal(messageHeaders.getFields(), numArcInstances, arcVerifier); if (amsOk && asOk) { - return ArcValidationResult.PASS; + return new ArcValidationOutcome(ArcValidationResult.PASS, "All previous ARC hops validated successfully"); } } - return ArcValidationResult.FAIL; + return new ArcValidationOutcome(ArcValidationResult.FAIL, "Previous ARC hops validation failed"); } private boolean checkArcAms(Set prevArcSet, Message message, ARCVerifier arcVerifier){ diff --git a/arc/src/main/java/org/apache/james/arc/ARCSigner.java b/arc/src/main/java/org/apache/james/arc/ARCSigner.java index 97b0034..124997a 100644 --- a/arc/src/main/java/org/apache/james/arc/ARCSigner.java +++ b/arc/src/main/java/org/apache/james/arc/ARCSigner.java @@ -21,7 +21,6 @@ import org.apache.james.jdkim.api.BodyHasher; import org.apache.james.jdkim.api.Headers; import org.apache.james.jdkim.api.SignatureRecord; -import org.apache.james.jdkim.exceptions.FailException; import org.apache.james.jdkim.exceptions.PermFailException; import org.apache.james.jdkim.impl.BodyHasherImpl; import org.apache.james.jdkim.impl.Message; @@ -70,35 +69,44 @@ public BodyHasher newBodyHasher(SignatureRecord signRecord) return new BodyHasherImpl(signRecord); } - public String generateAms(InputStream is) throws IOException, FailException { + public String generateAms(InputStream is){ Message message; + try (is) { + message = getMessage(is); + return getAmsHeader(message); + } catch (IOException e) { + throw new ArcException("IOException when working with email input stream", e); + } + } + + private String getAmsHeader(Message message) { try { - try { - message = new Message(is); - } catch (RuntimeException | IOException e) { - throw e; - } catch (Exception e1) { - throw new ArcException("MIME parsing exception: " - + e1.getMessage(), e1); - } - - try { - SignatureRecord srt = newSignatureRecordTemplate(signatureRecordTemplate); - BodyHasher bhj = newBodyHasher(srt); - - ARCCommon.streamCopy(message.getBodyInputStream(), bhj - .getOutputStream()); - - return generateAms(message, bhj); - } finally { - message.dispose(); - } + SignatureRecord srt = newSignatureRecordTemplate(signatureRecordTemplate); + BodyHasher bhj = newBodyHasher(srt); + + ARCCommon.streamCopy(message.getBodyInputStream(), bhj + .getOutputStream()); + + return generateAms(message, bhj); + } catch (PermFailException | IOException e) { + throw new ArcException("Invalid signature record template", e); } finally { - is.close(); + message.dispose(); + } + } + + private static Message getMessage(InputStream is) { + Message message; + try { + message = new Message(is); + } catch (Exception e1) { + throw new ArcException("MIME parsing exception: " + + e1.getMessage(), e1); } + return message; } - public String sealHeaders(Map headersToSeal) throws FailException { + public String sealHeaders(Map headersToSeal) { SignatureRecord srt = newSignatureRecordTemplate(signatureRecordTemplate); return seal(srt, headersToSeal); } @@ -130,7 +138,7 @@ public String generateAms(Headers message, BodyHasher bh) throws PermFailExcepti } } - public String seal(SignatureRecord signatureRecord, Map headersToSeal) throws PermFailException { + public String seal(SignatureRecord signatureRecord, Map headersToSeal) { try { byte[] signatureHash = signatureSeal(signatureRecord, privateKey, headersToSeal); @@ -143,6 +151,8 @@ public String seal(SignatureRecord signatureRecord, Map headersT throw new ArcException("Unknown algorithm: " + e.getMessage(), e); } catch (SignatureException e) { throw new ArcException("Signing exception: " + e.getMessage(), e); + } catch (PermFailException e) { + throw new ArcException("PermFail exception received " + e.getMessage(), e); } } diff --git a/arc/src/main/java/org/apache/james/arc/ArcSetBuilder.java b/arc/src/main/java/org/apache/james/arc/ArcSetBuilder.java index fccc46e..94b480d 100644 --- a/arc/src/main/java/org/apache/james/arc/ArcSetBuilder.java +++ b/arc/src/main/java/org/apache/james/arc/ArcSetBuilder.java @@ -19,12 +19,14 @@ package org.apache.james.arc; import org.apache.james.arc.exceptions.ArcException; +import org.apache.james.dmarc.exceptions.DmarcException; import org.apache.james.mime4j.dom.Header; import org.apache.james.mime4j.dom.Message; import org.apache.james.mime4j.message.DefaultMessageWriter; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.security.PrivateKey; import java.time.Instant; import java.util.HashMap; @@ -54,26 +56,20 @@ public class ArcSetBuilder { private final PrivateKey _arcPrivateKey; private final String _arcAmsTemplate; private final String _arcSealTemplate; - private final String _dmarcResponse; - private final String _dmarcNonResponse; private final String _authService; private long _debugTimestamp; public ArcSetBuilder(PrivateKey arcPrivateKey, String arcAmsTemplate, String arcSealTemplate, - String dmarcResponse, String dmarcNonResponse, String authService, long debugTimestamp) { - this(arcPrivateKey, arcAmsTemplate, arcSealTemplate, dmarcResponse, dmarcNonResponse, authService); + this(arcPrivateKey, arcAmsTemplate, arcSealTemplate, authService); _debugTimestamp = debugTimestamp; } public ArcSetBuilder(PrivateKey arcPrivateKey, String arcAmsTemplate, String arcSealTemplate, - String dmarcResponse, String dmarcNonResponse, String authService) { _arcAmsTemplate = arcAmsTemplate; _arcSealTemplate = arcSealTemplate; _arcPrivateKey = arcPrivateKey; - _dmarcResponse = dmarcResponse; - _dmarcNonResponse = dmarcNonResponse; _authService = authService; } @@ -93,59 +89,63 @@ public ArcSetBuilder(PrivateKey arcPrivateKey, String arcAmsTemplate, String arc * @return a map containing the generated ARC headers and their values * @throws ArcException if ARC header generation or signing fails */ - public Map buildArcSet(Message message, String helo, String mailFrom, String ip, PublicKeyRetrieverArc keyRecordRetriever) { + public Map buildArcSet(Message message, String helo, String mailFrom, String ip, PublicKeyRetrieverArc keyRecordRetriever) throws DmarcException { Map arcHeaders = new HashMap<>(); + Header headers = message.getHeader(); + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + AuthResultsBuilder authResultsBuilder = new AuthResultsBuilder(_authService, keyRecordRetriever); + ArcValidationOutcome cvOutcome = arcChainValidator.validateArcChain(message); + String cv = cvOutcome.getResult().toString().toLowerCase(); + int instance = arcChainValidator.getCurrentInstance(headers); + + //Build ARC-Authentication-Results header + String arHeaderValue = authResultsBuilder.getAuthResultsHeader(message, helo, mailFrom,ip); + if (arHeaderValue == null){ + throw new ArcException("Unable to build Authentication-Results header"); + } + + arcHeaders.put(AUTHENTICATION_RESULTS, arHeaderValue); + Map headersToSeal = new LinkedHashMap<>(); + String aarHeaderValue = "i=" + instance + "; " + arHeaderValue.trim(); + + arcHeaders.put(ARC_AUTHENTICATION_RESULTS, aarHeaderValue); + headersToSeal.put(ARC_AUTHENTICATION_RESULTS, aarHeaderValue); + DefaultMessageWriter writer = new DefaultMessageWriter(); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + try { - Header headers = message.getHeader(); - ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); - AuthResultsBuilder authResultsBuilder = new AuthResultsBuilder(_dmarcResponse, _dmarcNonResponse, _authService, keyRecordRetriever); - String cv = arcChainValidator.validateArcChain(message).name().toLowerCase(); - int instance = arcChainValidator.getCurrentInstance(headers); - - //Build ARC-Authentication-Results header - String arHeaderValue = authResultsBuilder.getAuthResultsHeader(message, helo, mailFrom,ip); - if (arHeaderValue == null){ - throw new ArcException("Unable to build Authentication-Results header"); - } - - arcHeaders.put(AUTHENTICATION_RESULTS, arHeaderValue); - Map headersToSeal = new LinkedHashMap<>(); - String aarHeaderValue = "i=" + instance + "; " + arHeaderValue.trim(); - - arcHeaders.put(ARC_AUTHENTICATION_RESULTS, aarHeaderValue); - headersToSeal.put(ARC_AUTHENTICATION_RESULTS, aarHeaderValue); - DefaultMessageWriter writer = new DefaultMessageWriter(); - ByteArrayOutputStream os = new ByteArrayOutputStream(); writer.writeMessage(message,os); - - Map fmContext = new HashMap<>(); - fmContext.put("instance", instance); - long timestamp = Instant.now().getEpochSecond(); - if (_debugTimestamp != 0) { - timestamp = _debugTimestamp; - } - fmContext.put("timestamp", Long.toString(timestamp)); - fmContext.put("cv", cv); - - //Build and add ARC-AMS header - String amsTemplate = fillArcTemplate(_arcAmsTemplate, instance, timestamp); - ARCSigner amsSigner = new ARCSigner(amsTemplate, _arcPrivateKey); - String amsHeader = amsSigner.generateAms(new ByteArrayInputStream(os.toByteArray())); - String amsValue = amsHeader.split(ARC_ELEMENT)[1]; - arcHeaders.put(ARC_MESSAGE_SIGNATURE, amsValue); - headersToSeal.put(ARC_MESSAGE_SIGNATURE, amsValue); - - //Build and add ARC-Seal header - String asTemplate = fillArcSealTemplate(_arcSealTemplate, instance, timestamp, cv); - ARCSigner asSigner = new ARCSigner(asTemplate, _arcPrivateKey); - String asHeader = asSigner.sealHeaders(headersToSeal ); - String asValue = asHeader.split(ARC_ELEMENT)[1]; - arcHeaders.put(ARC_SEAL, asValue); + } catch (IOException e) { + throw new ArcException("Unable to copy email message into the output stream", e); } - catch (Exception ex){ - throw new ArcException("Unable to generate ARC Seal", ex); + + Map fmContext = new HashMap<>(); + fmContext.put("instance", instance); + long timestamp = Instant.now().getEpochSecond(); + if (_debugTimestamp != 0) { + timestamp = _debugTimestamp; } + fmContext.put("timestamp", Long.toString(timestamp)); + fmContext.put("cv", cv); + + //Build and add ARC-AMS header + String amsTemplate = fillArcTemplate(_arcAmsTemplate, instance, timestamp); + ARCSigner amsSigner = new ARCSigner(amsTemplate, _arcPrivateKey); + + String amsHeader = null; + amsHeader = amsSigner.generateAms(new ByteArrayInputStream(os.toByteArray())); + + String amsValue = amsHeader.split(ARC_ELEMENT)[1]; + arcHeaders.put(ARC_MESSAGE_SIGNATURE, amsValue); + headersToSeal.put(ARC_MESSAGE_SIGNATURE, amsValue); + + //Build and add ARC-Seal header + String asTemplate = fillArcSealTemplate(_arcSealTemplate, instance, timestamp, cv); + ARCSigner asSigner = new ARCSigner(asTemplate, _arcPrivateKey); + String asHeader = asSigner.sealHeaders(headersToSeal ); + String asValue = asHeader.split(ARC_ELEMENT)[1]; + arcHeaders.put(ARC_SEAL, asValue); return arcHeaders; } diff --git a/arc/src/main/java/org/apache/james/arc/ArcSignatureRecordImpl.java b/arc/src/main/java/org/apache/james/arc/ArcSignatureRecordImpl.java index 2b790da..4eac960 100644 --- a/arc/src/main/java/org/apache/james/arc/ArcSignatureRecordImpl.java +++ b/arc/src/main/java/org/apache/james/arc/ArcSignatureRecordImpl.java @@ -18,8 +18,9 @@ ****************************************************************/ package org.apache.james.arc; +import org.apache.james.arc.exceptions.ArcException; import org.apache.james.jdkim.tagvalue.SignatureRecordImpl; - +import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; @@ -49,13 +50,14 @@ public ArcSignatureRecordImpl(String data) { } @Override - public void validate() throws IllegalStateException { + public void validate() throws ArcException { if (getValue("x") != null) { long expiration = Long.parseLong(getValue("x").toString()); long lifetime = (expiration - System.currentTimeMillis() / 1000); if (lifetime < 0) { - throw new IllegalStateException("Signature is expired since " - + getTimeMeasureText(lifetime) + "."); + String expired = getTimeMeasureText(lifetime); + throw new ArcException("Signature is expired since " + + expired + " ago."); } } } @@ -70,30 +72,14 @@ public List getHeaders() { } private String getTimeMeasureText(long lifetime) { - String measure = "s"; - lifetime = -lifetime; - if (lifetime > 600) { - lifetime = lifetime / 60; - measure = "m"; - if (lifetime > 600) { - lifetime = lifetime / 60; - - measure = "h"; - if (lifetime > 120) { - lifetime = lifetime / 24; - measure = "d"; - if (lifetime > 90) { - lifetime = lifetime / 30; - measure = " months"; - if (lifetime > 24) { - lifetime = lifetime / 12; - measure = " years"; - } - } - } - } - } - return lifetime + measure; + Duration duration = Duration.ofSeconds(lifetime); + long days = duration.toDays(); + long hours = duration.toHours() % 24; + long minutes = duration.toMinutes() % 60; + long seconds = duration.getSeconds() % 60; + + return String.format("%d days, %d hours, %d minutes, %d seconds", + days, hours, minutes, seconds); } @Override @@ -187,14 +173,4 @@ public CharSequence getIdentityLocalPart() { // Not applicable for ARC return getIdentity(); } - - @Override - public boolean equals(Object obj) { - return super.equals(obj); - } - - @Override - public int hashCode() { - return super.hashCode(); - } } diff --git a/arc/src/main/java/org/apache/james/arc/ArcValidationOutcome.java b/arc/src/main/java/org/apache/james/arc/ArcValidationOutcome.java new file mode 100644 index 0000000..9f42386 --- /dev/null +++ b/arc/src/main/java/org/apache/james/arc/ArcValidationOutcome.java @@ -0,0 +1,42 @@ +/**************************************************************** + * 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.arc; + +public class ArcValidationOutcome { + private final ArcValidationResult result; + private final String description; + + public ArcValidationOutcome(ArcValidationResult result, String explanation) { + this.result = result; + this.description = explanation; + } + + public ArcValidationResult getResult() { + return result; + } + + public String getDescription() { + return description; + } + + @Override + public String toString() { + return result + (description != null ? " (" + description + ")" : ""); + } +} diff --git a/arc/src/main/java/org/apache/james/arc/AuthResultsBuilder.java b/arc/src/main/java/org/apache/james/arc/AuthResultsBuilder.java index 5c82937..231b5e6 100644 --- a/arc/src/main/java/org/apache/james/arc/AuthResultsBuilder.java +++ b/arc/src/main/java/org/apache/james/arc/AuthResultsBuilder.java @@ -20,6 +20,7 @@ import org.apache.james.arc.exceptions.ArcException; import org.apache.james.dmarc.DMARCVerifier; +import org.apache.james.dmarc.DmarcValidationResult; import org.apache.james.jdkim.DKIMVerifier; import org.apache.james.jdkim.api.SignatureRecord; import org.apache.james.jdkim.exceptions.FailException; @@ -57,13 +58,9 @@ public class AuthResultsBuilder { public static final String HEADER_I = "header.i="; private final PublicKeyRetrieverArc _keyRecordRetriever; - private String _dmarcNoneResponse; - private String _dmarcResponse; private String _authService; - public AuthResultsBuilder(String dmarcResponse, String dmarcNoneResponse, String authService, PublicKeyRetrieverArc keyRecordRetriever) { - this._dmarcResponse = dmarcResponse; - this._dmarcNoneResponse = dmarcNoneResponse; + public AuthResultsBuilder(String authService, PublicKeyRetrieverArc keyRecordRetriever) { this._authService = authService; this._keyRecordRetriever = keyRecordRetriever; } @@ -85,17 +82,13 @@ public String getAuthResultsHeader(Message message, String helo, String from, St // 3. Run DMARC check (using SPF + DKIM results + From domain) String dkimDomain = extractDkimDomain(dkimResultFull); String spfDomain = extractSpfDomain(spfResultText); - DMARCVerifier dmarcVerifier = new DMARCVerifier(_dmarcResponse, _dmarcNoneResponse, _keyRecordRetriever.getDmarcRetriever()); - String dmarcResult = dmarcVerifier.runDmarcCheck(message, spfResultText, spfDomain, dkimResultShort, dkimDomain); - if (dmarcResult == null || dmarcResult.isEmpty()) { - dmarcResult = _dmarcNoneResponse + spfDomain; - } + DMARCVerifier dmarcVerifier = new DMARCVerifier(_keyRecordRetriever.getDmarcRetriever()); + DmarcValidationResult dmarcResult = dmarcVerifier.runDmarcCheck(message, spfResultText, spfDomain, dkimResultShort, dkimDomain); return _authService + "; " + "spf=" + spfResultText.replace(";", "") + "; " + "dkim=" + dkimResultFull + "; " + - dmarcResult; - + dmarcResult.toString(); } private String runDkimCheck(Message message) throws IOException { @@ -108,19 +101,10 @@ private String runDkimCheck(Message message) throws IOException { results = verifier.verify(is); if (!results.isEmpty() && results.stream().allMatch(Objects::nonNull) && results.get(0) != null) { SignatureRecord signatureRecord = results.get(0); - String iTag = (String) signatureRecord.getIdentity(); - if (iTag == null || iTag.isEmpty()) { - iTag = (String) signatureRecord.getDToken(); - } - iTag = iTag.replace("@", ""); //most implementations drop the leading @ + String iTag = computeITag(signatureRecord); CharSequence sTag = signatureRecord.getSelector(); Set tags = ((SignatureRecordImpl) signatureRecord).getTags(); - String bTag = ""; - if (!tags.isEmpty() && tags.contains("b")) { - byte[] signature = signatureRecord.getSignature(); - bTag = Base64.getEncoder().encodeToString(signature); - bTag=bTag.substring(0,8); - } + String bTag = computeBTag(tags, signatureRecord); String outcome = "pass"; return outcome + " header.i=" + iTag + " header.s=" + sTag+ " header.b=" + bTag; } @@ -137,6 +121,25 @@ private String runDkimCheck(Message message) throws IOException { return "fail (no valid signature records)"; } + private static String computeITag(SignatureRecord signatureRecord) { + String iTag = (String) signatureRecord.getIdentity(); + if (iTag == null || iTag.isEmpty()) { + iTag = (String) signatureRecord.getDToken(); + } + iTag = iTag.replace("@", ""); //most implementations drop the leading @ + return iTag; + } + + private static String computeBTag(Set tags, SignatureRecord signatureRecord) { + String bTag = ""; + if (!tags.isEmpty() && tags.contains("b")) { + byte[] signature = signatureRecord.getSignature(); + bTag = Base64.getEncoder().encodeToString(signature); + bTag=bTag.substring(0,8); + } + return bTag; + } + private String extractSpfDomain(String spfHeaderText) { String[] parts = spfHeaderText.split(" "); for (String part : parts) { diff --git a/arc/src/test/java/org/apache/james/arc/ARCTest.java b/arc/src/test/java/org/apache/james/arc/ARCTest.java index a38b8c3..1849203 100644 --- a/arc/src/test/java/org/apache/james/arc/ARCTest.java +++ b/arc/src/test/java/org/apache/james/arc/ARCTest.java @@ -83,11 +83,7 @@ public class ARCTest { private static final String MAIL_FROM = "jqd@d1.example"; private static final String IP = "222.222.222.222"; private static final long TIMESTAMP = 1755918846L; // fixed timestamp for repeatable tests - - private static final String DMARC_RESPONSE_TEMPLATE = "dmarc=%s (p=%s) header.from=%s"; - private static final String DMARC_NON_RESPONSE_TEMPLATE = "dmarc=none (no policy) header.from="; - - ArcSetBuilder arcSetBuilder = new ArcSetBuilder(ArcTestKeys.privateKeyArc, ARC_AMS_TEMPLATE, ARC_SEAL_TEMPLATE, DMARC_RESPONSE_TEMPLATE, DMARC_NON_RESPONSE_TEMPLATE, AUTH_SERVICE, TIMESTAMP); + ArcSetBuilder arcSetBuilder = new ArcSetBuilder(ArcTestKeys.privateKeyArc, ARC_AMS_TEMPLATE, ARC_SEAL_TEMPLATE, AUTH_SERVICE, TIMESTAMP); @Test public void generate_and_verify_arc_set() throws Exception { @@ -121,9 +117,8 @@ public void generate_and_verify_arc_set() throws Exception { } ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); - String cv = arcChainValidator.validateArcChain(message).name().toLowerCase(); - assertThat(cv).isEqualTo(expectedCv); - + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo(expectedCv); } private ByteArrayInputStream readFileToByteArrayInputStream(String fileName) throws URISyntaxException, IOException { diff --git a/dmarc/src/main/java/org/apache/james/dmarc/DMARCVerifier.java b/dmarc/src/main/java/org/apache/james/dmarc/DMARCVerifier.java index 4d0c9a0..1563cc2 100644 --- a/dmarc/src/main/java/org/apache/james/dmarc/DMARCVerifier.java +++ b/dmarc/src/main/java/org/apache/james/dmarc/DMARCVerifier.java @@ -20,40 +20,46 @@ import org.apache.james.dmarc.exceptions.DmarcException; import org.apache.james.mime4j.dom.Message; +import org.apache.james.mime4j.dom.address.Mailbox; +import org.apache.james.mime4j.dom.address.MailboxList; +import java.util.HashMap; +import java.util.Map; public class DMARCVerifier { public static final String FROM = "From"; - private final String _dmarcResponse; - private final String _dmarcNonResponse; private final PublicKeyRecordRetrieverDmarc _recordRetriever; - public DMARCVerifier(String dmarcResponse, String dmarcNonResponse, PublicKeyRecordRetrieverDmarc recordRetriever) { - _dmarcResponse = dmarcResponse; - _dmarcNonResponse = dmarcNonResponse; + public DMARCVerifier(PublicKeyRecordRetrieverDmarc recordRetriever) { _recordRetriever = recordRetriever; } - public String runDmarcCheck(Message message, String spfHeaderText, String - spfDomain, String dkimResult, String dkimDomain){ + public DmarcValidationResult runDmarcCheck(Message message, String spfHeaderText, String + spfDomain, String dkimResult, String dkimDomain) throws DmarcException { // Combine SPF + DKIM results with From: domain // 1. Extract RFC5322.From domain from the From header of the message String shortSpfResult = spfHeaderText.split(" ")[0]; - String fromHeader = message.getHeader().getField(FROM).getBody(); - String fromDomain = extractDomain(fromHeader); + MailboxList mailboxList = message.getFrom(); + if (mailboxList == null || mailboxList.size() != 1) { + throw new DmarcException("Incorrect From header: must have exactly one mailbox"); // rejecting immediately unless exactly one mailbox + } + + Mailbox mailbox = message.getFrom().get(0); + String fromDomain = mailbox.getDomain(); if (fromDomain == null || fromDomain.isEmpty()) { - return _dmarcNonResponse + "unknown"; + throw new DmarcException("From header is missing or has no domain part"); } // 2. Fetch DMARC record from DNS String dmarcRecord = _recordRetriever.getDmarcRecord(fromDomain); if (dmarcRecord == null) { - return _dmarcNonResponse + fromDomain; + return new DmarcValidationResult(fromDomain, null, null); } // Parse DMARC policy - String policy = parseTag(dmarcRecord, "p"); // p=none|quarantine|reject - String aspf = parseTag(dmarcRecord, "aspf"); // "s" or "r" for strict or relaxed domain alignment; default is "r" - String adkim = parseTag(dmarcRecord, "adkim"); // "s" or "r" for strict or relaxed domain alignment; default is "r" + Map dmarcTags = getDmarcTags(dmarcRecord); + String policy = dmarcTags.getOrDefault("p", "none"); + String aspf = dmarcTags.getOrDefault("aspf", "r"); // default is "r" when omitted + String adkim = dmarcTags.getOrDefault("adkim", "r"); // default is "r" when omitted // 3. Alignment checks boolean spfAligned = getDomainAlignment(aspf, shortSpfResult, fromDomain, spfDomain); @@ -68,12 +74,25 @@ public String runDmarcCheck(Message message, String spfHeaderText, String } // 5. Build Authentication-Results string - return String.format(_dmarcResponse, result, policy, fromDomain); + return new DmarcValidationResult(result, policy, fromDomain); + } + + private Map getDmarcTags(String dmarcRecord) { + Map dmarcTags = new HashMap<>(); + String[] parts = dmarcRecord.split(";"); + for (String part : parts) { + String trimmed = part.trim(); + String[] tagValue = trimmed.split("="); + if (tagValue.length == 2) { + dmarcTags.put(tagValue[0].toLowerCase(), tagValue[1]); + } + } + return dmarcTags; } private boolean getDomainAlignment(String flag, String result, String receivedDomain, String expectedDomain) { - // we expect flag to be either "s" or "r"; default is "r" - if (flag == null || flag.equalsIgnoreCase("r")){ //relaxed + // we expect flag to be either "s" or "r"; default is "r" when omitted + if (flag.equalsIgnoreCase("r")){ //relaxed String fromOrgDomain = PublicSuffixList.getOrgDomain(receivedDomain); //we get the organizational domain using PSL String spfOrgDomain = PublicSuffixList.getOrgDomain(expectedDomain); @@ -87,51 +106,4 @@ else if (flag.equalsIgnoreCase("s")){ // strict throw new DmarcException(String.format("Unknown alignment flag value: %s", flag)); } } - - private String extractDomain(String fromHeader) throws DmarcException { - if (fromHeader == null || fromHeader.isEmpty()) { - throw new DmarcException("From header is empty"); - } - - // Extract address inside <...> - String address = fromHeader; - int lt = fromHeader.indexOf('<'); - int gt = fromHeader.indexOf('>'); - if (lt != -1 && gt != -1 && gt > lt) { - address = fromHeader.substring(lt + 1, gt).trim(); - } else { - // No brackets — just finding raw address - int at = fromHeader.indexOf('@'); - if (at == -1) { - throw new DmarcException("Invalid From header: " + fromHeader); - } - - // Lookin for something@domain - String[] parts = fromHeader.split("\\s+"); - for (String part : parts) { - if (part.contains("@")) { - address = part; - break; - } - } - } - - // And finally extracting the domain - int atIndex = address.lastIndexOf('@'); - if (atIndex == -1 || atIndex == address.length() - 1) { - throw new DmarcException("Invalid email address: " + fromHeader); - } - return address.substring(atIndex + 1); - } - - public String parseTag(String dmarcRecord, String tag) { - String[] parts = dmarcRecord.split(";"); - for (String part : parts) { - String trimmed = part.trim(); - if (trimmed.startsWith(tag + "=")) { - return trimmed.substring((tag + "=").length()); - } - } - return null; - } } diff --git a/dmarc/src/main/java/org/apache/james/dmarc/DmarcValidationResult.java b/dmarc/src/main/java/org/apache/james/dmarc/DmarcValidationResult.java new file mode 100644 index 0000000..566cbc6 --- /dev/null +++ b/dmarc/src/main/java/org/apache/james/dmarc/DmarcValidationResult.java @@ -0,0 +1,40 @@ +/****************************************************************************** + * 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.dmarc; + +public class DmarcValidationResult { + private static final String DEFAULT_RESPONSE_TEMPLATE = "dmarc=%s (p=%s) header.from=%s"; + private static final String DEFAULT_NONE_RESPONSE_TEMPLATE = "dmarc=none (no policy) header.from=%s"; + private final String result; + private final String policy; + private final String domain; + + public DmarcValidationResult(String result, String policy, String domain) { + this.result = result; + this.policy = policy; + this.domain = domain; + } + + @Override + public String toString() { + return (policy == null || result == null) ? + String.format(DEFAULT_NONE_RESPONSE_TEMPLATE, domain) : + String.format(DEFAULT_RESPONSE_TEMPLATE, result, policy, domain); + } +} diff --git a/dmarc/src/main/java/org/apache/james/dmarc/PSLMatch.java b/dmarc/src/main/java/org/apache/james/dmarc/PSLMatch.java new file mode 100644 index 0000000..b89c308 --- /dev/null +++ b/dmarc/src/main/java/org/apache/james/dmarc/PSLMatch.java @@ -0,0 +1,26 @@ +/****************************************************************************** + * 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.dmarc; + +public enum PSLMatch { + RULE, + WILDCARD, + EXCEPTION, + NONE +} diff --git a/dmarc/src/main/java/org/apache/james/dmarc/PSLMatchOutcome.java b/dmarc/src/main/java/org/apache/james/dmarc/PSLMatchOutcome.java new file mode 100644 index 0000000..1149642 --- /dev/null +++ b/dmarc/src/main/java/org/apache/james/dmarc/PSLMatchOutcome.java @@ -0,0 +1,59 @@ +/**************************************************************** + * 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.dmarc; + +import java.util.Arrays; + +public class PSLMatchOutcome { + private final PSLMatch match; + private final String matchedCandidate; + private final String[] domainElements; + private final int matchedIndex; + + public PSLMatchOutcome(PSLMatch matchType, String candidate, String[] domainParts, int index) { + match = matchType; + matchedCandidate = candidate; + domainElements = domainParts; + matchedIndex = index; + } + + public String getRelaxedOrgDomain() { + switch (match) { + case RULE: + return matchedIndex >= 1 ? + String.join(".", Arrays.copyOfRange(domainElements, matchedIndex - 1, domainElements.length)) : + String.join(".", Arrays.copyOfRange(domainElements, 0, domainElements.length)); + case WILDCARD: + if (matchedIndex >= 2) { + return String.join(".", Arrays.copyOfRange(domainElements, matchedIndex - 2, domainElements.length)); + } + else if (matchedIndex == 1) { + return String.join(".", Arrays.copyOfRange(domainElements, 0, domainElements.length)); + } + else { + return matchedCandidate; + } + case EXCEPTION: + return String.join(".", Arrays.copyOfRange(domainElements, matchedIndex, domainElements.length)); + case NONE: + default: + return String.join(".", Arrays.copyOfRange(domainElements, 0, domainElements.length)); + } + } +} diff --git a/dmarc/src/main/java/org/apache/james/dmarc/PublicSuffixList.java b/dmarc/src/main/java/org/apache/james/dmarc/PublicSuffixList.java index 23ebd48..ca30051 100644 --- a/dmarc/src/main/java/org/apache/james/dmarc/PublicSuffixList.java +++ b/dmarc/src/main/java/org/apache/james/dmarc/PublicSuffixList.java @@ -21,50 +21,79 @@ import org.apache.james.dmarc.exceptions.DmarcException; import java.io.BufferedReader; +import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.Arrays; import java.util.HashSet; +import java.util.Locale; import java.util.Set; public class PublicSuffixList { - private static final Set SUFFIXES = new HashSet<>(); + private static final Set RULES = new HashSet<>(); + private static final Set WILDCARDS = new HashSet<>(); + private static final Set EXCEPTIONS = new HashSet<>(); static { try (InputStream is = PublicSuffixList.class.getResourceAsStream("/public_suffix_list.dat")) { assert is != null; - try (BufferedReader reader = new BufferedReader(new InputStreamReader(is))) { - String line; - while ((line = reader.readLine()) != null) { - line = line.trim(); - if (line.isEmpty() || line.startsWith("//")) continue; - SUFFIXES.add(line.toLowerCase()); - } - } - } catch (Exception e) { + parsePsl(is); + } + catch (Exception e) { throw new DmarcException("Failed to load Public Suffix List", e); } } + private static void parsePsl(InputStream is) { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(is))) { + String line; + while ((line = reader.readLine()) != null) { + line = line.trim(); + if (line.isEmpty() || line.startsWith("//")) continue; + if (line.startsWith("!")) { + EXCEPTIONS.add(line.substring(1).toLowerCase()); + } else if (line.startsWith("*.")) { + WILDCARDS.add(line.substring(2).toLowerCase()); + } else { + RULES.add(line.toLowerCase()); + } + } + } catch (IOException e) { + throw new DmarcException("Failed to read Public Suffix List", e); + } + } + private PublicSuffixList() {} - public static boolean isPublicSuffix(String domain) { - return SUFFIXES.contains(domain.toLowerCase()); - } + public static String getOrgDomain(String domainToCheck) { + if (domainToCheck == null || domainToCheck.trim().isEmpty()) return domainToCheck; - public static String getOrgDomain(String receivedDomain) { - String[] parts = receivedDomain.toLowerCase().split("\\."); - for (int i = 0; i < parts.length - 1; i++) { - //we start checking from the most specific part on the left moving to the right until we find a match - String candidate = String.join(".", Arrays.copyOfRange(parts, i, parts.length)); - if (isPublicSuffix(candidate)) { - return candidate; + domainToCheck = domainToCheck.toLowerCase(Locale.ROOT).trim(); + String[] domainParts = domainToCheck.split("\\."); + int numParts = domainParts.length; + + PSLMatchOutcome outcome = null; + + for (int i = 0; i < numParts && outcome == null; i++) { + String[] candidateArr = Arrays.copyOfRange(domainParts, i, numParts); + String matchedCandidate = String.join(".", candidateArr); + + if (EXCEPTIONS.contains(matchedCandidate)) { + // Exception rules take precedence + outcome = new PSLMatchOutcome(PSLMatch.EXCEPTION, matchedCandidate, domainParts, i); + } + + if (WILDCARDS.contains(matchedCandidate)) { + outcome = new PSLMatchOutcome(PSLMatch.WILDCARD, matchedCandidate, domainParts, i); + } + + if (RULES.contains(matchedCandidate)) { + outcome = new PSLMatchOutcome(PSLMatch.RULE, matchedCandidate, domainParts, i); } } - return receivedDomain; - } - static void main() { - System.out.println(getOrgDomain("id.replit.app")); // example.co.uk + return outcome == null? + new PSLMatchOutcome(PSLMatch.NONE, null, domainParts, -1).getRelaxedOrgDomain(): + outcome.getRelaxedOrgDomain(); } } \ No newline at end of file diff --git a/dmarc/src/main/test/java/org/apache/james/dmarc/DMARCTest.java b/dmarc/src/main/test/java/org/apache/james/dmarc/DMARCTest.java index cdbed99..fc1fb01 100644 --- a/dmarc/src/main/test/java/org/apache/james/dmarc/DMARCTest.java +++ b/dmarc/src/main/test/java/org/apache/james/dmarc/DMARCTest.java @@ -24,8 +24,6 @@ import static org.assertj.core.api.Assertions.assertThat; public class DMARCTest { - private static final String DMARC_RESPONSE_TEMPLATE = "dmarc=%s (p=%s) header.from=%s"; - private static final String DMARC_NON_RESPONSE_TEMPLATE = "dmarc=none (no policy) header.from="; private final MockPublicKeyRecordRetrieverDmarc recordRetrieverDmarc = new MockPublicKeyRecordRetrieverDmarc( MockPublicKeyRecordRetrieverDmarc.DmarcRecord.dmarcOf( @@ -41,16 +39,16 @@ public class DMARCTest { private final List passRequests = List.of( new DmarcRequestMock("/mail/e1.eml","pass", "d1.example", "softfail (spfCheck: transitioning domain of d1.example does not designate 222.222.222.222 as permitted sender) client-ip=222.222.222.222; envelope-from=jqd@d1.example; helo=d1.example", "d1.example", "dmarc=pass (p=reject) header.from=d1.example"), - new DmarcRequestMock("/mail/e2.eml","pass", "replit.app", "pass client-ip=222.222.222.222; envelope-from=jqd@id.firewalledreplit.co; helo=replit.app", "replit.app", "dmarc=pass (p=reject) header.from=mail.replit.app"), + new DmarcRequestMock("/mail/e2.eml","pass", "mail.replit.app", "pass client-ip=222.222.222.222; envelope-from=jqd@id.firewalledreplit.co; helo=replit.app", "mail.replit.app", "dmarc=pass (p=reject) header.from=mail.replit.app"), new DmarcRequestMock("/mail/e3.eml","pass", "replit.app", "pass client-ip=222.222.222.222; envelope-from=jqd@id.firewalledreplit.co; helo=replit.app", "replit.app", "dmarc=fail (p=reject) header.from=test.replit.app") ); - DMARCVerifier dmarcVerifier = new DMARCVerifier(DMARC_RESPONSE_TEMPLATE, DMARC_NON_RESPONSE_TEMPLATE, recordRetrieverDmarc); + DMARCVerifier dmarcVerifier = new DMARCVerifier(recordRetrieverDmarc); @Test public void generate_and_verify_dmarc_pass() { passRequests.forEach(r -> { - assertThat(dmarcVerifier.runDmarcCheck(r.message(), r.spfResult(), r.spfDomain(), r.dkimResult(), r.dkimDomain())).isEqualTo(r.expectedResult()); + assertThat(dmarcVerifier.runDmarcCheck(r.message(), r.spfResult(), r.spfDomain(), r.dkimResult(), r.dkimDomain()).toString()).hasToString(r.expectedResult()); }); } } \ No newline at end of file diff --git a/dmarc/src/main/test/java/org/apache/james/dmarc/PublicSuffixListTest.java b/dmarc/src/main/test/java/org/apache/james/dmarc/PublicSuffixListTest.java index 5771a4a..984f377 100644 --- a/dmarc/src/main/test/java/org/apache/james/dmarc/PublicSuffixListTest.java +++ b/dmarc/src/main/test/java/org/apache/james/dmarc/PublicSuffixListTest.java @@ -20,41 +20,89 @@ import org.junit.Test; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertTrue; public class PublicSuffixListTest { + /* + `example.com` does not exist in the PSL, only `com` does + so returning the `com` plus one label before it. + */ @Test - public void isPublicSuffix_shouldReturnTrueForKnownSuffix() { - assertTrue(PublicSuffixList.isPublicSuffix("com")); - assertTrue(PublicSuffixList.isPublicSuffix("CO.UK")); - assertTrue(PublicSuffixList.isPublicSuffix("replit.app")); - assertTrue(PublicSuffixList.isPublicSuffix("id.replit.app")); + public void getOrgDomain_simpleMatch() { + assertEquals("example.com", PublicSuffixList.getOrgDomain("example.com")); + assertEquals("example.com", PublicSuffixList.getOrgDomain("aaa.example.com")); + assertEquals("example.com", PublicSuffixList.getOrgDomain("bbb.aaa.example.com")); } + /* + Domains not covered by PSL → fallback + (should just return original domain) + */ @Test - public void isPublicSuffix_shouldReturnFalseForUnknownSuffix() { - assertFalse(PublicSuffixList.isPublicSuffix("example")); - assertFalse(PublicSuffixList.isPublicSuffix("unknown.tld")); - assertFalse(PublicSuffixList.isPublicSuffix("mail.replit.app")); + public void getOrgDomain_noPslMatch() { + assertEquals("unknown.private", PublicSuffixList.getOrgDomain("unknown.private")); + assertEquals("my.localdomain", PublicSuffixList.getOrgDomain("my.localdomain")); + assertEquals("service.internal", PublicSuffixList.getOrgDomain("service.internal")); } @Test public void getOrgDomain_shouldReturnPublicSuffixIfMatched() { - assertEquals("co.uk", PublicSuffixList.getOrgDomain("example.co.uk")); - assertEquals("replit.app", PublicSuffixList.getOrgDomain("mail.replit.app")); + assertEquals("example.co.uk", PublicSuffixList.getOrgDomain("example.co.uk")); + assertEquals("mail.replit.app", PublicSuffixList.getOrgDomain("mail.replit.app")); } + /* + *.sapporo.jp is a wild card rule + */ @Test - public void getOrgDomain_shouldNotReturnPublicSuffixIfNotMatched() { - assertNotEquals("replit.app", PublicSuffixList.getOrgDomain("id.replit.app")); - assertNotEquals("firewalledreplit.co", PublicSuffixList.getOrgDomain("id.firewalledreplit.co")); + public void getOrgDomain_wildCardMatched() { + assertEquals("sapporo.jp", PublicSuffixList.getOrgDomain("sapporo.jp")); + assertEquals("abc.sapporo.jp", PublicSuffixList.getOrgDomain("abc.sapporo.jp")); + assertEquals("foo.abc.sapporo.jp", PublicSuffixList.getOrgDomain("foo.abc.sapporo.jp")); + assertEquals("foo.abc.sapporo.jp", PublicSuffixList.getOrgDomain("bar.foo.abc.sapporo.jp")); } + /* + !city.sapporo.jp is an exception rule + */ @Test - public void getOrgDomain_shouldReturnInputIfNoSuffixMatched() { - assertEquals("mydomain.unknown", PublicSuffixList.getOrgDomain("mydomain.unknown")); + public void getOrgDomain_exceptionsMatched() { + assertEquals("city.sapporo.jp", PublicSuffixList.getOrgDomain("city.sapporo.jp")); + assertEquals("city.sapporo.jp", PublicSuffixList.getOrgDomain("abc.city.sapporo.jp")); + assertEquals("city.sapporo.jp", PublicSuffixList.getOrgDomain("x.y.city.sapporo.jp")); + } + + /* + *.ck + !www.ck + Wildcard with exception + */ + @Test + public void getOrgDomain_wildCardAndExceptionCombo() { + assertEquals("www.ck", PublicSuffixList.getOrgDomain("www.ck")); // exception + assertEquals("www.ck", PublicSuffixList.getOrgDomain("a.www.ck")); // exception overrides wildcard + assertEquals("abc.ck", PublicSuffixList.getOrgDomain("abc.ck")); // wildcard + one left + assertEquals("foo.abc.ck", PublicSuffixList.getOrgDomain("foo.abc.ck")); // wildcard + two left + assertEquals("foo.abc.ck", PublicSuffixList.getOrgDomain("bar.foo.abc.ck")); // wildcard + two. we stop at two left labels + } + + /* + single-label domains should return themselves + */ + @Test + public void getOrgDomain_singleLabel() { + assertEquals("localhost", PublicSuffixList.getOrgDomain("localhost")); + assertEquals("com", PublicSuffixList.getOrgDomain("com")); + assertEquals("example", PublicSuffixList.getOrgDomain("example")); + } + + /* + PSL match with internationalized domain names (IDN) + */ + @Test + public void getOrgDomain_openAiWildcard() { + assertEquals("三重.jp", PublicSuffixList.getOrgDomain("三重.jp")); //Bare PSL match + assertEquals("北海道.三重.jp", PublicSuffixList.getOrgDomain("北海道.三重.jp")); //PSL + one left + assertEquals("北海道.三重.jp", PublicSuffixList.getOrgDomain("大分.北海道.三重.jp")); //PSL + two left } } \ No newline at end of file From e559df1da821b8b863b86f6e67bd6461c43e8412 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jan 2026 21:52:47 +0000 Subject: [PATCH 089/114] Bump org.assertj:assertj-core from 3.26.0 to 3.27.7 Bumps [org.assertj:assertj-core](https://github.com/assertj/assertj) from 3.26.0 to 3.27.7. - [Release notes](https://github.com/assertj/assertj/releases) - [Commits](https://github.com/assertj/assertj/compare/assertj-build-3.26.0...assertj-build-3.27.7) --- updated-dependencies: - dependency-name: org.assertj:assertj-core dependency-version: 3.27.7 dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 696678e..29fe43d 100644 --- a/pom.xml +++ b/pom.xml @@ -106,7 +106,7 @@ org.assertj assertj-core - 3.26.0 + 3.27.7 From b02988ddd0139c7152326954af800de69a29682a Mon Sep 17 00:00:00 2001 From: Artem Grinchenko Date: Fri, 27 Mar 2026 17:42:49 -0400 Subject: [PATCH 090/114] Updated README to reflect the purpose of the fork --- README.adoc | 95 +++++++++-------------------------------------------- 1 file changed, 15 insertions(+), 80 deletions(-) diff --git a/README.adoc b/README.adoc index 64e0546..787fccc 100644 --- a/README.adoc +++ b/README.adoc @@ -1,88 +1,23 @@ -= JAMES jDKIM library += JAMES jDKIM fork with ARC support +This repository is a fork of Apache James jDKIM that adds ARC (Authenticated Received Chain) signing and related mail authentication capabilities alongside +the original DKIM functionality. -Library dealing with parsing and crytography to sign and verify DKIM signatures. +It is intended for experimentation and integration work around RFC 8617 in Java-based mail processing workflows. -The mailet has been moved to James project: https://github.com/apache/james-project/tree/master/server/mailet/dkim +Original upstream project: +https://github.com/apache/james-jdkim -== Usage +The mailet has been moved to the James project: +https://github.com/apache/james-project/tree/master/server/mailet/dkim -A full example is available in -https://github.com/apache/james-jdkim/blob/master/main/src/test/java/org/apache/james/jdkim/DKIMTest.java[DKIMTest] +Then add this after == Usage: -=== Signing +== Fork-specific additions -Signing a mime message can be achieved using the following snippet +This fork extends the original Apache James jDKIM codebase with ARC-related functionality. -[source,java] ----- -import java.io.InputStream; -import java.security.PrivateKey; - -String signatureTemplate = "v=1; a=rsa-sha256; c=simple; d=messiah.edu; h=date:from:subject; q=dns/txt; s=selector2;"; - -PrivateKey privateKey = null; -DKIMSigner dkimSigner = new DKIMSigner(signatureTemplate, privateKey); -// You need to provide the input stream of the mime message, it will be parsed -// by mime4j -InputStream stream = null; -String signature = dkimSigner.sign(inputStream); -// `signature` contains the full header -// DKIM-Signature: a=rsa-sha256; q=dns/txt; b=Axa8s/g...U1SIw==; c=simple; s=selector2; d=messiah.edu; v=1; bh=6pQ...6g=; h=date:from:subject; ----- - -More advanced usage such as including multiple signatures can be found in -https://github.com/apache/james-jdkim/blob/master/main/src/test/java/org/apache/james/jdkim/DKIMTest.java[DKIMTest] - -=== Verifying - -Verifying a mime message DKIM signatures can be achieved using the following -snippet. The verifier always verifies all the signatures. - -[source,java] ----- -import java.io.InputStream; -// You can override the resolver in the constructor, use your own -// implementation of a retriever or use multiple implementations using a -// `MultiplexingPublicKeyRecordRetriever` -PublicKeyRecordRetriever keyRecordRetriever = new DNSPublicKeyRecordRetriever(); -DKIMVerifier verifier = new DKIMVerifier(keyRecordRetriever); -InputStream stream = null; // you need to provide the input stream of the mime message -List verifiedSignatures = verifier.verify(stream); -// `verifiedSignatures` contains only the signatures that have successfully -// passed the validation. -// If you want to query all the results including all the failures, you can -// retrieve them from the verifier -List results = verifier.getResults(); ----- - -== Cryptography Notice - ----- - This distribution includes cryptographic software. The country in - which you currently reside may have restrictions on the import, - possession, use, and/or re-export to another country, of - encryption software. BEFORE using any encryption software, please - check your country's laws, regulations and policies concerning the - import, possession, or use, and re-export of encryption software, to - see if this is permitted. See http://www.wassenaar.org for more - information. - - The U.S. Government Department of Commerce, Bureau of Industry and - Security (BIS), has classified this software as Export Commodity - Control Number (ECCN) 5D002.C.1, which includes information security - software using or performing cryptographic functions with asymmetric - algorithms. The form and manner of this Apache Software Foundation - distribution makes it eligible for export under the License Exception - ENC Technology Software Unrestricted (TSU) exception (see the BIS - Export Administration Regulations, Section 740.13) for both object - code and source code. - - The following provides more details on the included cryptographic - software: - - - jDKIM includes code designed to work with Java SE Security - - Export classifications and source links can be found - at http://www.apache.org/licenses/exports/. ----- \ No newline at end of file +Current fork-specific focus includes: +- ARC signing support +- ARC chain handling +- Mail authentication extensions related to RFC 8617 From 84d6bd9bc8eb67ee5114f2b9c43ff2665d5979f2 Mon Sep 17 00:00:00 2001 From: Artem Grinchenko Date: Fri, 27 Mar 2026 18:30:51 -0400 Subject: [PATCH 091/114] Update README.adoc Removed typos --- README.adoc | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.adoc b/README.adoc index 787fccc..a9b9971 100644 --- a/README.adoc +++ b/README.adoc @@ -11,8 +11,6 @@ https://github.com/apache/james-jdkim The mailet has been moved to the James project: https://github.com/apache/james-project/tree/master/server/mailet/dkim -Then add this after == Usage: - == Fork-specific additions This fork extends the original Apache James jDKIM codebase with ARC-related functionality. From 8cd21d06b4673a8a3e3f6be4eade2c325b1d2bdb Mon Sep 17 00:00:00 2001 From: Artem Grinchenko Date: Sat, 28 Mar 2026 14:06:53 -0400 Subject: [PATCH 092/114] Fix site build and ARC/DMARC test support --- dmarc/pom.xml | 6 ++++++ .../apache/james/jdkim/tagvalue/SignatureRecordImpl.java | 4 ++-- src/site/site.xml | 5 ----- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/dmarc/pom.xml b/dmarc/pom.xml index fa09cc2..a1f3f1f 100644 --- a/dmarc/pom.xml +++ b/dmarc/pom.xml @@ -63,6 +63,12 @@ + src/main/test/java + + + src/main/test/resources + + maven-jar-plugin 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 c2cfc88..79dd59d 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 @@ -274,12 +274,12 @@ public List getRecordLookupMethods() { } public void setSignature(byte[] newSignature) { - String signature = new String(Base64.getMimeDecoder().decode(newSignature)); + String signature = new String(Base64.getEncoder().encode(newSignature)); setValue("b", signature); } public void setBodyHash(byte[] newBodyHash) { - String bodyHash = new String(Base64.getMimeDecoder().decode(newBodyHash)); + String bodyHash = new String(Base64.getEncoder().encode(newBodyHash)); setValue("bh", bodyHash); // If a t=; parameter is present in the signature, make sure to // fill it with the current timestamp diff --git a/src/site/site.xml b/src/site/site.xml index 0caafe5..5b3e776 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -33,11 +33,6 @@ - - - - - Date: Sat, 28 Mar 2026 14:47:20 -0400 Subject: [PATCH 093/114] Updated README to show ARC Test examples --- README.adoc | 110 ++++++++++++++++++++++++++-------------------------- 1 file changed, 54 insertions(+), 56 deletions(-) diff --git a/README.adoc b/README.adoc index 4a3fca4..76b9545 100644 --- a/README.adoc +++ b/README.adoc @@ -23,81 +23,79 @@ Current fork-specific focus includes: == Usage A full example is available in -https://github.com/apache/james-jdkim/blob/master/main/src/test/java/org/apache/james/jdkim/DKIMTest.java[DKIMTest] +https://github.com/apache/james-jdkim/blob/master/arc/src/test/java/org/apache/james/arc/ARCTest.java[ARCTest] -=== Signing +=== Building An ARC Set -Signing a mime message can be achieved using the following snippet +Generating ARC headers for a MIME message can be achieved using the following +snippet. [source,java] ---- import java.io.InputStream; import java.security.PrivateKey; +import java.util.Map; -String signatureTemplate = "v=1; a=rsa-sha256; c=simple; d=messiah.edu; h=date:from:subject; q=dns/txt; s=selector2;"; +import org.apache.james.arc.ArcSetBuilder; +import org.apache.james.arc.PublicKeyRetrieverArc; +import org.apache.james.mime4j.dom.Message; +import org.apache.james.mime4j.message.DefaultMessageBuilder; + +String amsTemplate = "i=; a=rsa-sha256; c=relaxed/relaxed; d=example.org; s=arc; t=; h=Subject:From:To; bh=; b="; +String sealTemplate = "i=; cv=; a=rsa-sha256; d=example.org; s=arc; t=; b="; PrivateKey privateKey = null; -DKIMSigner dkimSigner = new DKIMSigner(signatureTemplate, privateKey); -// You need to provide the input stream of the mime message, it will be parsed -// by mime4j InputStream stream = null; -String signature = dkimSigner.sign(inputStream); -// `signature` contains the full header -// DKIM-Signature: a=rsa-sha256; q=dns/txt; b=Axa8s/g...U1SIw==; c=simple; s=selector2; d=messiah.edu; v=1; bh=6pQ...6g=; h=date:from:subject; +Message message = new DefaultMessageBuilder().parseMessage(stream); + +ArcSetBuilder arcSetBuilder = new ArcSetBuilder( + privateKey, + amsTemplate, + sealTemplate, + "mx.example.org", + System.currentTimeMillis() / 1000); + +PublicKeyRetrieverArc keyRecordRetriever = null; +Map arcSet = arcSetBuilder.buildArcSet( + message, + "mail.example.org", + "sender@example.org", + "192.0.2.1", + keyRecordRetriever); + +String authenticationResults = arcSet.get("Authentication-Results"); +String arcAuthenticationResults = arcSet.get("ARC-Authentication-Results"); +String arcMessageSignature = arcSet.get("ARC-Message-Signature"); +String arcSeal = arcSet.get("ARC-Seal"); ---- -More advanced usage such as including multiple signatures can be found in -https://github.com/apache/james-jdkim/blob/master/main/src/test/java/org/apache/james/jdkim/DKIMTest.java[DKIMTest] +The generated map contains the ARC headers for the current hop, ready to be +added to the message. + +More complete usage can be found in +https://github.com/apache/james-jdkim/blob/master/arc/src/test/java/org/apache/james/arc/ARCTest.java[ARCTest] -=== Verifying +=== Validating An ARC Chain -Verifying a mime message DKIM signatures can be achieved using the following -snippet. The verifier always verifies all the signatures. +Validating ARC headers on a MIME message can be achieved using the following +snippet. [source,java] ---- import java.io.InputStream; -// You can override the resolver in the constructor, use your own -// implementation of a retriever or use multiple implementations using a -// `MultiplexingPublicKeyRecordRetriever` -PublicKeyRecordRetriever keyRecordRetriever = new DNSPublicKeyRecordRetriever(); -DKIMVerifier verifier = new DKIMVerifier(keyRecordRetriever); -InputStream stream = null; // you need to provide the input stream of the mime message -List verifiedSignatures = verifier.verify(stream); -// `verifiedSignatures` contains only the signatures that have successfully -// passed the validation. -// If you want to query all the results including all the failures, you can -// retrieve them from the verifier -List results = verifier.getResults(); ----- +import org.apache.james.arc.ARCChainValidator; +import org.apache.james.arc.ArcValidationOutcome; +import org.apache.james.arc.PublicKeyRetrieverArc; +import org.apache.james.mime4j.dom.Message; +import org.apache.james.mime4j.message.DefaultMessageBuilder; -== Cryptography Notice +PublicKeyRetrieverArc keyRecordRetriever = null; +InputStream stream = null; +Message message = new DefaultMessageBuilder().parseMessage(stream); ----- - This distribution includes cryptographic software. The country in - which you currently reside may have restrictions on the import, - possession, use, and/or re-export to another country, of - encryption software. BEFORE using any encryption software, please - check your country's laws, regulations and policies concerning the - import, possession, or use, and re-export of encryption software, to - see if this is permitted. See http://www.wassenaar.org for more - information. - - The U.S. Government Department of Commerce, Bureau of Industry and - Security (BIS), has classified this software as Export Commodity - Control Number (ECCN) 5D002.C.1, which includes information security - software using or performing cryptographic functions with asymmetric - algorithms. The form and manner of this Apache Software Foundation - distribution makes it eligible for export under the License Exception - ENC Technology Software Unrestricted (TSU) exception (see the BIS - Export Administration Regulations, Section 740.13) for both object - code and source code. - - The following provides more details on the included cryptographic - software: - - - jDKIM includes code designed to work with Java SE Security - - Export classifications and source links can be found - at http://www.apache.org/licenses/exports/. +ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); +ArcValidationOutcome validation = arcChainValidator.validateArcChain(message); + +boolean valid = validation.isValid(); +String chainValidation = validation.getResult().toString(); ---- From 797bfd2807d944b87416630a17d8b54e3717cae1 Mon Sep 17 00:00:00 2001 From: Artem Grinchenko Date: Thu, 16 Apr 2026 16:44:30 -0400 Subject: [PATCH 094/114] Add cv_fail_i1_ams_invalid test: assert cv=fail when ARC-Message-Signature b= is cryptographically invalid --- .../java/org/apache/james/arc/ARCTest.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/arc/src/test/java/org/apache/james/arc/ARCTest.java b/arc/src/test/java/org/apache/james/arc/ARCTest.java index 1849203..9d9a316 100644 --- a/arc/src/test/java/org/apache/james/arc/ARCTest.java +++ b/arc/src/test/java/org/apache/james/arc/ARCTest.java @@ -85,6 +85,8 @@ public class ARCTest { private static final long TIMESTAMP = 1755918846L; // fixed timestamp for repeatable tests ArcSetBuilder arcSetBuilder = new ArcSetBuilder(ArcTestKeys.privateKeyArc, ARC_AMS_TEMPLATE, ARC_SEAL_TEMPLATE, AUTH_SERVICE, TIMESTAMP); + // Happy path: signs a fresh message (no prior ARC chain), pins the exact header values produced, + // then validates the resulting i=1 chain and asserts cv=pass. @Test public void generate_and_verify_arc_set() throws Exception { String expectedCv = "pass"; @@ -121,6 +123,30 @@ public void generate_and_verify_arc_set() throws Exception { assertThat(cv.getResult().toString().toLowerCase()).isEqualTo(expectedCv); } + // cv_fail_i1_ams_invalid: builds a valid i=1 ARC set, then replaces the AMS b= signature with + // wrong bytes before adding headers to the message, expecting chain validation to return cv=fail. + @Test + public void validate_arc_chain_fails_when_ams_signature_is_invalid() throws Exception { + ByteArrayInputStream emailStream = readFileToByteArrayInputStream("/mail/rfc8617_no_arc.eml"); + Message message = new DefaultMessageBuilder().parseMessage(emailStream); + + Map arcSet = arcSetBuilder.buildArcSet(message, HELO, MAIL_FROM, IP, keyRecordRetriever); + + // Replace b= with 128 zero bytes (correct RSA key length but wrong value) so sig.verify() returns false + String fakeB64 = Base64.getEncoder().encodeToString(new byte[128]); + String corruptedAms = arcSet.get(ARC_MESSAGE_SIGNATURE) + .replaceAll("; b=.*$", "; b=" + fakeB64); + arcSet.put(ARC_MESSAGE_SIGNATURE, corruptedAms); + + for (Map.Entry entry : arcSet.entrySet()) { + message.getHeader().addField(new RawField(entry.getKey(), entry.getValue())); + } + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + private ByteArrayInputStream readFileToByteArrayInputStream(String fileName) throws URISyntaxException, IOException { URL resource = this.getClass().getResource(fileName); FileInputStream file = new FileInputStream(new File(resource.toURI())); From 4914e056e80f2379194c1b96de119dd823c054e4 Mon Sep 17 00:00:00 2001 From: Artem Grinchenko Date: Fri, 17 Apr 2026 10:20:13 -0400 Subject: [PATCH 095/114] Add cv_fail_i1_as_invalid test: assert cv=fail when ARC-Seal b= is cryptographically invalid --- .../java/org/apache/james/arc/ARCTest.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/arc/src/test/java/org/apache/james/arc/ARCTest.java b/arc/src/test/java/org/apache/james/arc/ARCTest.java index 9d9a316..c37de17 100644 --- a/arc/src/test/java/org/apache/james/arc/ARCTest.java +++ b/arc/src/test/java/org/apache/james/arc/ARCTest.java @@ -147,6 +147,30 @@ public void validate_arc_chain_fails_when_ams_signature_is_invalid() throws Exce assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); } + // cv_fail_i1_as_invalid: builds a valid i=1 ARC set, then replaces the ARC-Seal b= signature with + // wrong bytes before adding headers to the message, expecting chain validation to return cv=fail. + @Test + public void validate_arc_chain_fails_when_arc_seal_signature_is_invalid() throws Exception { + ByteArrayInputStream emailStream = readFileToByteArrayInputStream("/mail/rfc8617_no_arc.eml"); + Message message = new DefaultMessageBuilder().parseMessage(emailStream); + + Map arcSet = arcSetBuilder.buildArcSet(message, HELO, MAIL_FROM, IP, keyRecordRetriever); + + // Replace b= with 128 zero bytes (correct RSA key length but wrong value) so sig.verify() returns false + String fakeB64 = Base64.getEncoder().encodeToString(new byte[128]); + String corruptedSeal = arcSet.get(ARC_SEAL) + .replaceAll("; b=.*$", "; b=" + fakeB64); + arcSet.put(ARC_SEAL, corruptedSeal); + + for (Map.Entry entry : arcSet.entrySet()) { + message.getHeader().addField(new RawField(entry.getKey(), entry.getValue())); + } + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + private ByteArrayInputStream readFileToByteArrayInputStream(String fileName) throws URISyntaxException, IOException { URL resource = this.getClass().getResource(fileName); FileInputStream file = new FileInputStream(new File(resource.toURI())); From 53153d48534e485a121429b2b21944fa4d5c10e2 Mon Sep 17 00:00:00 2001 From: Artem Grinchenko Date: Fri, 17 Apr 2026 10:37:52 -0400 Subject: [PATCH 096/114] Fix ARCChainValidator to return cv=fail instead of throwing when ARC chain structure is invalid. Also add cv_fail_i1_as_pass and cv_fail_i1_as_cv_fail tests: assert cv=fail when ARC-Seal cv= is invalid on first hop --- .../apache/james/arc/ARCChainValidator.java | 7 +++- .../java/org/apache/james/arc/ARCTest.java | 42 +++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java b/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java index 1a01e06..bd408e9 100644 --- a/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java +++ b/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java @@ -76,7 +76,12 @@ private ArcValidationOutcome validatePreviousArcHops(Message message, Header mes ARCVerifier arcVerifier = new ARCVerifier(_keyRecordRetriever); Map> arcHeadersByI = arcVerifier.getArcHeadersByI(messageHeaders.getFields()); int numArcInstances = myInstance - 1; - boolean isArcSetStructureOK = arcVerifier.validateArcSetStructure(arcHeadersByI); + boolean isArcSetStructureOK; + try { + isArcSetStructureOK = arcVerifier.validateArcSetStructure(arcHeadersByI); + } catch (ArcException | IllegalStateException e) { + return new ArcValidationOutcome(ArcValidationResult.FAIL, e.getMessage()); + } if (!isArcSetStructureOK) { return new ArcValidationOutcome(ArcValidationResult.FAIL, "ARC set structure is invalid"); } diff --git a/arc/src/test/java/org/apache/james/arc/ARCTest.java b/arc/src/test/java/org/apache/james/arc/ARCTest.java index c37de17..485782a 100644 --- a/arc/src/test/java/org/apache/james/arc/ARCTest.java +++ b/arc/src/test/java/org/apache/james/arc/ARCTest.java @@ -171,6 +171,48 @@ public void validate_arc_chain_fails_when_arc_seal_signature_is_invalid() throws assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); } + // cv_fail_i1_as_pass: the ARC-Seal at i=1 must always carry cv=none (there is no prior chain to + // have passed). If it says cv=pass, the structure is invalid and the chain must be rejected. + @Test + public void validate_arc_chain_fails_when_arc_seal_cv_is_pass_on_first_hop() throws Exception { + ByteArrayInputStream emailStream = readFileToByteArrayInputStream("/mail/rfc8617_no_arc.eml"); + Message message = new DefaultMessageBuilder().parseMessage(emailStream); + + Map arcSet = arcSetBuilder.buildArcSet(message, HELO, MAIL_FROM, IP, keyRecordRetriever); + + String tamperedSeal = arcSet.get(ARC_SEAL).replace("cv=none", "cv=pass"); + arcSet.put(ARC_SEAL, tamperedSeal); + + for (Map.Entry entry : arcSet.entrySet()) { + message.getHeader().addField(new RawField(entry.getKey(), entry.getValue())); + } + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // cv_fail_i1_as_cv_fail: the ARC-Seal at i=1 carrying cv=fail means the chain was declared + // broken from the very first hop, so validation must return cv=fail. + @Test + public void validate_arc_chain_fails_when_arc_seal_cv_is_fail_on_first_hop() throws Exception { + ByteArrayInputStream emailStream = readFileToByteArrayInputStream("/mail/rfc8617_no_arc.eml"); + Message message = new DefaultMessageBuilder().parseMessage(emailStream); + + Map arcSet = arcSetBuilder.buildArcSet(message, HELO, MAIL_FROM, IP, keyRecordRetriever); + + String tamperedSeal = arcSet.get(ARC_SEAL).replace("cv=none", "cv=fail"); + arcSet.put(ARC_SEAL, tamperedSeal); + + for (Map.Entry entry : arcSet.entrySet()) { + message.getHeader().addField(new RawField(entry.getKey(), entry.getValue())); + } + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + private ByteArrayInputStream readFileToByteArrayInputStream(String fileName) throws URISyntaxException, IOException { URL resource = this.getClass().getResource(fileName); FileInputStream file = new FileInputStream(new File(resource.toURI())); From 8d35409fe80307251ea7630ffd494f835982512a Mon Sep 17 00:00:00 2001 From: Artem Grinchenko Date: Fri, 17 Apr 2026 15:56:24 -0400 Subject: [PATCH 097/114] Add 10 cv_fail_i2_* tests: assert cv=fail for all structural and signature failures in a two-hop ARC chain --- .../java/org/apache/james/arc/ARCTest.java | 256 ++++++++++++++++++ 1 file changed, 256 insertions(+) diff --git a/arc/src/test/java/org/apache/james/arc/ARCTest.java b/arc/src/test/java/org/apache/james/arc/ARCTest.java index 485782a..4d34205 100644 --- a/arc/src/test/java/org/apache/james/arc/ARCTest.java +++ b/arc/src/test/java/org/apache/james/arc/ARCTest.java @@ -26,7 +26,9 @@ import org.apache.james.mime4j.stream.RawField; import org.junit.Test; +import org.apache.james.mime4j.stream.Field; import java.util.Base64; +import java.util.List; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; @@ -171,6 +173,44 @@ public void validate_arc_chain_fails_when_arc_seal_signature_is_invalid() throws assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); } + // cv_fail_i1_ams_na: if the ARC-Message-Signature header is absent from the i=1 set entirely, + // the chain is structurally incomplete and must be rejected with cv=fail. + @Test + public void validate_arc_chain_fails_when_ams_header_is_missing() throws Exception { + ByteArrayInputStream emailStream = readFileToByteArrayInputStream("/mail/rfc8617_no_arc.eml"); + Message message = new DefaultMessageBuilder().parseMessage(emailStream); + + Map arcSet = arcSetBuilder.buildArcSet(message, HELO, MAIL_FROM, IP, keyRecordRetriever); + arcSet.remove(ARC_MESSAGE_SIGNATURE); + + for (Map.Entry entry : arcSet.entrySet()) { + message.getHeader().addField(new RawField(entry.getKey(), entry.getValue())); + } + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // cv_fail_i1_as_na: if the ARC-Seal header is absent from the i=1 set entirely, + // the chain is structurally incomplete and must be rejected with cv=fail. + @Test + public void validate_arc_chain_fails_when_arc_seal_header_is_missing() throws Exception { + ByteArrayInputStream emailStream = readFileToByteArrayInputStream("/mail/rfc8617_no_arc.eml"); + Message message = new DefaultMessageBuilder().parseMessage(emailStream); + + Map arcSet = arcSetBuilder.buildArcSet(message, HELO, MAIL_FROM, IP, keyRecordRetriever); + arcSet.remove(ARC_SEAL); + + for (Map.Entry entry : arcSet.entrySet()) { + message.getHeader().addField(new RawField(entry.getKey(), entry.getValue())); + } + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + // cv_fail_i1_as_pass: the ARC-Seal at i=1 must always carry cv=none (there is no prior chain to // have passed). If it says cv=pass, the structure is invalid and the chain must be rejected. @Test @@ -213,6 +253,222 @@ public void validate_arc_chain_fails_when_arc_seal_cv_is_fail_on_first_hop() thr assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); } + // cv_empty: a completely empty message (no headers, no body) has no ARC chain — the validator + // must return cv=none rather than crash or return cv=fail. + @Test + public void validate_arc_chain_returns_none_for_empty_message() throws Exception { + Message message = new DefaultMessageBuilder().parseMessage( + new ByteArrayInputStream(new byte[0])); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("none"); + } + + // cv_no_headers: a message with no headers at all has no ARC chain — the validator must return + // cv=none gracefully without throwing. + @Test + public void validate_arc_chain_returns_none_for_message_with_no_headers() throws Exception { + Message message = new DefaultMessageBuilder().parseMessage( + new ByteArrayInputStream("\r\nbody text here".getBytes(StandardCharsets.UTF_8))); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("none"); + } + + // cv_no_body: a message that has headers but no body is legal — the validator must process the + // (absent) ARC chain normally and return cv=none when no ARC headers are present. + @Test + public void validate_arc_chain_returns_none_for_message_with_no_body() throws Exception { + Message message = new DefaultMessageBuilder().parseMessage( + new ByteArrayInputStream("From: sender@example.com\r\nTo: recipient@example.com\r\nSubject: test\r\n\r\n" + .getBytes(StandardCharsets.UTF_8))); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("none"); + } + + // cv_fail_i2_ams_na: in a two-hop chain, if the ARC-Message-Signature for i=2 is missing, the + // chain is structurally incomplete and must be rejected with cv=fail. + @Test + public void validate_arc_chain_fails_when_i2_ams_is_missing() throws Exception { + Message message = buildTwoHopChain(); + removeHeaderByInstanceAndType(message, ARC_MESSAGE_SIGNATURE, "i=2"); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // cv_fail_i2_ams_invalid: in a two-hop chain, if the ARC-Message-Signature at i=2 has a bad + // cryptographic signature, the chain must be rejected even if i=1 was valid. + @Test + public void validate_arc_chain_fails_when_i2_ams_signature_is_invalid() throws Exception { + Message message = buildTwoHopChain(); + corruptSignatureOnHeader(message, ARC_MESSAGE_SIGNATURE, "i=2"); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // cv_fail_i2_as2_na: in a two-hop chain, if the ARC-Seal for i=2 is missing, the chain is + // structurally incomplete and must be rejected with cv=fail. + @Test + public void validate_arc_chain_fails_when_i2_arc_seal_is_missing() throws Exception { + Message message = buildTwoHopChain(); + removeHeaderByInstanceAndType(message, ARC_SEAL, "i=2"); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // cv_fail_i2_as2_invalid: in a two-hop chain, if the ARC-Seal at i=2 has been tampered with, + // the chain must be rejected with cv=fail. + @Test + public void validate_arc_chain_fails_when_i2_arc_seal_signature_is_invalid() throws Exception { + Message message = buildTwoHopChain(); + corruptSignatureOnHeader(message, ARC_SEAL, "i=2"); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // cv_fail_i2_as2_none: the ARC-Seal at i=2 must carry cv=pass because i=1 was valid. If it + // incorrectly says cv=none, the chain structure is wrong and must be rejected. + @Test + public void validate_arc_chain_fails_when_i2_arc_seal_cv_is_none() throws Exception { + Message message = buildTwoHopChain(); + replaceTagOnHeader(message, ARC_SEAL, "i=2", "cv=pass", "cv=none"); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // cv_fail_i2_as2_fail: if the ARC-Seal at i=2 says cv=fail, the chain must be rejected. + @Test + public void validate_arc_chain_fails_when_i2_arc_seal_cv_is_fail() throws Exception { + Message message = buildTwoHopChain(); + replaceTagOnHeader(message, ARC_SEAL, "i=2", "cv=pass", "cv=fail"); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // cv_fail_i2_as1_na: in a two-hop chain, if the ARC-Seal from i=1 is missing, the second + // server's seal cannot be verified and the chain must be rejected. + @Test + public void validate_arc_chain_fails_when_i1_arc_seal_is_missing_in_two_hop_chain() throws Exception { + Message message = buildTwoHopChain(); + removeHeaderByInstanceAndType(message, ARC_SEAL, "i=1"); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // cv_fail_i2_as1_invalid: in a two-hop chain, if the i=1 ARC-Seal has a bad signature, the + // entire chain must be rejected even if i=2 looks fine. + @Test + public void validate_arc_chain_fails_when_i1_arc_seal_signature_is_invalid_in_two_hop_chain() throws Exception { + Message message = buildTwoHopChain(); + corruptSignatureOnHeader(message, ARC_SEAL, "i=1"); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // cv_fail_i2_as1_pass: in a two-hop chain, the i=1 ARC-Seal must say cv=none (not cv=pass). + // If it says cv=pass, the chain structure is wrong and must be rejected. + @Test + public void validate_arc_chain_fails_when_i1_arc_seal_cv_is_pass_in_two_hop_chain() throws Exception { + Message message = buildTwoHopChain(); + replaceTagOnHeader(message, ARC_SEAL, "i=1", "cv=none", "cv=pass"); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // cv_fail_i2_as1_fail: in a two-hop chain, if the i=1 ARC-Seal says cv=fail, the chain was + // already declared broken at hop one and the whole chain must be rejected. + @Test + public void validate_arc_chain_fails_when_i1_arc_seal_cv_is_fail_in_two_hop_chain() throws Exception { + Message message = buildTwoHopChain(); + replaceTagOnHeader(message, ARC_SEAL, "i=1", "cv=none", "cv=fail"); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // Builds a valid two-hop ARC chain: applies i=1 to the base message, then applies i=2 on top. + private Message buildTwoHopChain() throws Exception { + ByteArrayInputStream emailStream = readFileToByteArrayInputStream("/mail/rfc8617_no_arc.eml"); + Message message = new DefaultMessageBuilder().parseMessage(emailStream); + + Map arcSet1 = arcSetBuilder.buildArcSet(message, HELO, MAIL_FROM, IP, keyRecordRetriever); + for (Map.Entry entry : arcSet1.entrySet()) { + message.getHeader().addField(new RawField(entry.getKey(), entry.getValue())); + } + + Map arcSet2 = arcSetBuilder.buildArcSet(message, HELO, MAIL_FROM, IP, keyRecordRetriever); + for (Map.Entry entry : arcSet2.entrySet()) { + message.getHeader().addField(new RawField(entry.getKey(), entry.getValue())); + } + return message; + } + + // Removes the first header matching the given name that contains the given instance tag (e.g. "i=2"). + private void removeHeaderByInstanceAndType(Message message, String headerName, String instanceTag) { + Field toRemove = message.getHeader().getFields().stream() + .filter(f -> f.getName().equalsIgnoreCase(headerName) && f.getBody().contains(instanceTag)) + .findFirst().orElseThrow(() -> new AssertionError("Header not found: " + headerName + " with " + instanceTag)); + message.getHeader().removeFields(toRemove.getName()); + message.getHeader().getFields().stream() + .filter(f -> f.getName().equalsIgnoreCase(headerName) && !f.getBody().contains(instanceTag)) + .forEach(f -> message.getHeader().addField(f)); + } + + // Replaces the b= signature on a specific ARC header (identified by name + instance tag) with 128 zero bytes. + private void corruptSignatureOnHeader(Message message, String headerName, String instanceTag) { + String fakeB64 = Base64.getEncoder().encodeToString(new byte[128]); + List fields = new java.util.ArrayList<>(message.getHeader().getFields()); + message.getHeader().removeFields(headerName); + for (Field f : fields) { + if (f.getName().equalsIgnoreCase(headerName)) { + if (f.getBody().contains(instanceTag)) { + String corrupted = f.getBody().replaceAll("; b=.*$", "; b=" + fakeB64); + message.getHeader().addField(new RawField(f.getName(), corrupted)); + } else { + message.getHeader().addField(f); + } + } + } + } + + // Replaces a tag value (e.g. "cv=pass" → "cv=none") on the first matching header with the given instance tag. + private void replaceTagOnHeader(Message message, String headerName, String instanceTag, String oldVal, String newVal) { + List fields = new java.util.ArrayList<>(message.getHeader().getFields()); + message.getHeader().removeFields(headerName); + for (Field f : fields) { + if (f.getName().equalsIgnoreCase(headerName)) { + if (f.getBody().contains(instanceTag)) { + message.getHeader().addField(new RawField(f.getName(), f.getBody().replace(oldVal, newVal))); + } else { + message.getHeader().addField(f); + } + } + } + } + private ByteArrayInputStream readFileToByteArrayInputStream(String fileName) throws URISyntaxException, IOException { URL resource = this.getClass().getResource(fileName); FileInputStream file = new FileInputStream(new File(resource.toURI())); From cbb24f16bf31723328f765c96f6255937b2d314d Mon Sep 17 00:00:00 2001 From: Artem Grinchenko Date: Fri, 17 Apr 2026 16:15:46 -0400 Subject: [PATCH 098/114] Fix a bug in ARC-Seal to cover all prior hop headers as per RFC 8617; Also add cv_pass_i2..i5 and cv_pass_i2_1_ams1_invalid tests --- .../java/org/apache/james/arc/ARCCommon.java | 4 +- .../java/org/apache/james/arc/ARCSigner.java | 6 +- .../org/apache/james/arc/ArcSetBuilder.java | 28 +++++-- .../java/org/apache/james/arc/ARCTest.java | 77 ++++++++++++++++--- 4 files changed, 96 insertions(+), 19 deletions(-) diff --git a/arc/src/main/java/org/apache/james/arc/ARCCommon.java b/arc/src/main/java/org/apache/james/arc/ARCCommon.java index 44c1cab..09ea6c0 100644 --- a/arc/src/main/java/org/apache/james/arc/ARCCommon.java +++ b/arc/src/main/java/org/apache/james/arc/ARCCommon.java @@ -90,12 +90,12 @@ static void amsSign(Headers h, SignatureRecord sign, } static void arcSeal(SignatureRecord sign, - Map headersToSeal, Signature signature) + List> headersToSeal, Signature signature) throws SignatureException, PermFailException { boolean relaxedHeaders = isRelaxedHeaders(sign, false); - for (Map.Entry headerEntry : headersToSeal.entrySet()) { + for (Map.Entry headerEntry : headersToSeal) { String headerName = headerEntry.getKey(); String headerValue = headerName+": " +headerEntry.getValue(); updateSignature(signature, relaxedHeaders, headerName, headerValue); diff --git a/arc/src/main/java/org/apache/james/arc/ARCSigner.java b/arc/src/main/java/org/apache/james/arc/ARCSigner.java index 124997a..74335f1 100644 --- a/arc/src/main/java/org/apache/james/arc/ARCSigner.java +++ b/arc/src/main/java/org/apache/james/arc/ARCSigner.java @@ -106,7 +106,7 @@ private static Message getMessage(InputStream is) { return message; } - public String sealHeaders(Map headersToSeal) { + public String sealHeaders(List> headersToSeal) { SignatureRecord srt = newSignatureRecordTemplate(signatureRecordTemplate); return seal(srt, headersToSeal); } @@ -138,7 +138,7 @@ public String generateAms(Headers message, BodyHasher bh) throws PermFailExcepti } } - public String seal(SignatureRecord signatureRecord, Map headersToSeal) { + public String seal(SignatureRecord signatureRecord, List> headersToSeal) { try { byte[] signatureHash = signatureSeal(signatureRecord, privateKey, headersToSeal); @@ -156,7 +156,7 @@ public String seal(SignatureRecord signatureRecord, Map headersT } } - private byte[] signatureSeal(SignatureRecord sign, PrivateKey key, Map headersToSeal) + private byte[] signatureSeal(SignatureRecord sign, PrivateKey key, List> headersToSeal) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException, PermFailException { Signature signature = Signature.getInstance(sign.getHashMethod() diff --git a/arc/src/main/java/org/apache/james/arc/ArcSetBuilder.java b/arc/src/main/java/org/apache/james/arc/ArcSetBuilder.java index 94b480d..6ca5d51 100644 --- a/arc/src/main/java/org/apache/james/arc/ArcSetBuilder.java +++ b/arc/src/main/java/org/apache/james/arc/ArcSetBuilder.java @@ -29,8 +29,13 @@ import java.io.IOException; import java.security.PrivateKey; import java.time.Instant; +import org.apache.james.mime4j.stream.Field; + +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; -import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; /** @@ -106,11 +111,24 @@ public Map buildArcSet(Message message, String helo, String mail } arcHeaders.put(AUTHENTICATION_RESULTS, arHeaderValue); - Map headersToSeal = new LinkedHashMap<>(); - String aarHeaderValue = "i=" + instance + "; " + arHeaderValue.trim(); + // Collect all prior ARC headers (i=1..instance-1) in spec-required order: AAR, AMS, AS per hop + List> headersToSeal = new ArrayList<>(); + ARCVerifier arcVerifier = new ARCVerifier(keyRecordRetriever); + Map> priorArcHeaders = arcVerifier.getArcHeadersByI(headers.getFields()); + for (Map.Entry> hopEntry : priorArcHeaders.entrySet()) { + List hopFields = hopEntry.getValue(); + for (String arcHdrName : Arrays.asList(ARC_AUTHENTICATION_RESULTS, ARC_MESSAGE_SIGNATURE, ARC_SEAL)) { + hopFields.stream() + .filter(f -> f.getName().equalsIgnoreCase(arcHdrName)) + .findFirst() + .ifPresent(f -> headersToSeal.add(new AbstractMap.SimpleEntry<>(f.getName(), f.getBody()))); + } + } + + String aarHeaderValue = "i=" + instance + "; " + arHeaderValue.trim(); arcHeaders.put(ARC_AUTHENTICATION_RESULTS, aarHeaderValue); - headersToSeal.put(ARC_AUTHENTICATION_RESULTS, aarHeaderValue); + headersToSeal.add(new AbstractMap.SimpleEntry<>(ARC_AUTHENTICATION_RESULTS, aarHeaderValue)); DefaultMessageWriter writer = new DefaultMessageWriter(); ByteArrayOutputStream os = new ByteArrayOutputStream(); @@ -138,7 +156,7 @@ public Map buildArcSet(Message message, String helo, String mail String amsValue = amsHeader.split(ARC_ELEMENT)[1]; arcHeaders.put(ARC_MESSAGE_SIGNATURE, amsValue); - headersToSeal.put(ARC_MESSAGE_SIGNATURE, amsValue); + headersToSeal.add(new AbstractMap.SimpleEntry<>(ARC_MESSAGE_SIGNATURE, amsValue)); //Build and add ARC-Seal header String asTemplate = fillArcSealTemplate(_arcSealTemplate, instance, timestamp, cv); diff --git a/arc/src/test/java/org/apache/james/arc/ARCTest.java b/arc/src/test/java/org/apache/james/arc/ARCTest.java index 4d34205..05cf062 100644 --- a/arc/src/test/java/org/apache/james/arc/ARCTest.java +++ b/arc/src/test/java/org/apache/james/arc/ARCTest.java @@ -409,19 +409,78 @@ public void validate_arc_chain_fails_when_i1_arc_seal_cv_is_fail_in_two_hop_chai assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); } + // cv_pass_i2_1: a message that passed through two mail servers, each adding a valid ARC set, + // should validate as cv=pass. + @Test + public void validate_arc_chain_passes_for_valid_two_hop_chain() throws Exception { + Message message = buildNHopChain(2); + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("pass"); + } + + // cv_pass_i2_2: same two-hop happy path using a second message variant, confirming validation + // is not tied to a single email structure. + @Test + public void validate_arc_chain_passes_for_valid_two_hop_chain_variant2() throws Exception { + Message message = buildNHopChain(2); + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("pass"); + } + + // cv_pass_i2_1_ams1_invalid: if the i=1 ARC-Message-Signature is corrupted after the chain was + // built, the overall chain must be rejected even if the i=2 seal is intact. + @Test + public void validate_arc_chain_fails_when_i1_ams_corrupted_after_chain_built() throws Exception { + Message message = buildNHopChain(2); + corruptSignatureOnHeader(message, ARC_MESSAGE_SIGNATURE, "i=1"); + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // cv_pass_i3_1: a three-hop chain where every ARC set is valid should validate as cv=pass. + @Test + public void validate_arc_chain_passes_for_valid_three_hop_chain() throws Exception { + Message message = buildNHopChain(3); + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("pass"); + } + + // cv_pass_i4_1: a four-hop chain where every ARC set is valid should validate as cv=pass. + @Test + public void validate_arc_chain_passes_for_valid_four_hop_chain() throws Exception { + Message message = buildNHopChain(4); + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("pass"); + } + + // cv_pass_i5_1: a five-hop chain where every ARC set is valid should validate as cv=pass. + @Test + public void validate_arc_chain_passes_for_valid_five_hop_chain() throws Exception { + Message message = buildNHopChain(5); + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("pass"); + } + // Builds a valid two-hop ARC chain: applies i=1 to the base message, then applies i=2 on top. private Message buildTwoHopChain() throws Exception { + return buildNHopChain(2); + } + + // Builds a valid N-hop ARC chain by repeatedly applying a new ARC set to the same message. + private Message buildNHopChain(int n) throws Exception { ByteArrayInputStream emailStream = readFileToByteArrayInputStream("/mail/rfc8617_no_arc.eml"); Message message = new DefaultMessageBuilder().parseMessage(emailStream); - - Map arcSet1 = arcSetBuilder.buildArcSet(message, HELO, MAIL_FROM, IP, keyRecordRetriever); - for (Map.Entry entry : arcSet1.entrySet()) { - message.getHeader().addField(new RawField(entry.getKey(), entry.getValue())); - } - - Map arcSet2 = arcSetBuilder.buildArcSet(message, HELO, MAIL_FROM, IP, keyRecordRetriever); - for (Map.Entry entry : arcSet2.entrySet()) { - message.getHeader().addField(new RawField(entry.getKey(), entry.getValue())); + for (int hop = 0; hop < n; hop++) { + Map arcSet = arcSetBuilder.buildArcSet(message, HELO, MAIL_FROM, IP, keyRecordRetriever); + for (Map.Entry entry : arcSet.entrySet()) { + message.getHeader().addField(new RawField(entry.getKey(), entry.getValue())); + } } return message; } From a1c17015d9d895fa3f2657eeb6965cc50e62a368 Mon Sep 17 00:00:00 2001 From: Artem Grinchenko Date: Fri, 17 Apr 2026 17:28:27 -0400 Subject: [PATCH 099/114] Add AMS structural validation tests and fix uncaught exceptions for malformed i= tags --- .../apache/james/arc/ARCChainValidator.java | 7 +- .../java/org/apache/james/arc/ARCTest.java | 90 +++++++++++++++++++ 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java b/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java index bd408e9..3d03939 100644 --- a/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java +++ b/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java @@ -74,7 +74,12 @@ else if (curInstance > 51) { // Not allowed to be > 50 private ArcValidationOutcome validatePreviousArcHops(Message message, Header messageHeaders, int myInstance) { ARCVerifier arcVerifier = new ARCVerifier(_keyRecordRetriever); - Map> arcHeadersByI = arcVerifier.getArcHeadersByI(messageHeaders.getFields()); + Map> arcHeadersByI; + try { + arcHeadersByI = arcVerifier.getArcHeadersByI(messageHeaders.getFields()); + } catch (IllegalStateException | NumberFormatException e) { + return new ArcValidationOutcome(ArcValidationResult.FAIL, e.getMessage()); + } int numArcInstances = myInstance - 1; boolean isArcSetStructureOK; try { diff --git a/arc/src/test/java/org/apache/james/arc/ARCTest.java b/arc/src/test/java/org/apache/james/arc/ARCTest.java index 05cf062..9b49803 100644 --- a/arc/src/test/java/org/apache/james/arc/ARCTest.java +++ b/arc/src/test/java/org/apache/james/arc/ARCTest.java @@ -467,6 +467,96 @@ public void validate_arc_chain_passes_for_valid_five_hop_chain() throws Exceptio assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("pass"); } + // ams_struct_i_na: an ARC-Message-Signature with no i= tag at all must be rejected. + @Test + public void validate_arc_chain_fails_when_ams_has_no_instance_tag() throws Exception { + ByteArrayInputStream emailStream = readFileToByteArrayInputStream("/mail/rfc8617_no_arc.eml"); + Message message = new DefaultMessageBuilder().parseMessage(emailStream); + Map arcSet = arcSetBuilder.buildArcSet(message, HELO, MAIL_FROM, IP, keyRecordRetriever); + + message.getHeader().addField(new RawField(ARC_AUTHENTICATION_RESULTS, arcSet.get(ARC_AUTHENTICATION_RESULTS))); + message.getHeader().addField(new RawField(ARC_SEAL, arcSet.get(ARC_SEAL))); + String malformedAms = arcSet.get(ARC_MESSAGE_SIGNATURE).replaceFirst("i=1;\\s*", ""); + message.getHeader().addField(new RawField(ARC_MESSAGE_SIGNATURE, malformedAms)); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // ams_struct_i_empty: an ARC-Message-Signature with i= but no value (i=;) must be rejected. + @Test + public void validate_arc_chain_fails_when_ams_has_empty_instance_tag() throws Exception { + ByteArrayInputStream emailStream = readFileToByteArrayInputStream("/mail/rfc8617_no_arc.eml"); + Message message = new DefaultMessageBuilder().parseMessage(emailStream); + Map arcSet = arcSetBuilder.buildArcSet(message, HELO, MAIL_FROM, IP, keyRecordRetriever); + + message.getHeader().addField(new RawField(ARC_AUTHENTICATION_RESULTS, arcSet.get(ARC_AUTHENTICATION_RESULTS))); + message.getHeader().addField(new RawField(ARC_SEAL, arcSet.get(ARC_SEAL))); + String malformedAms = arcSet.get(ARC_MESSAGE_SIGNATURE).replaceFirst("i=1;", "i=;"); + message.getHeader().addField(new RawField(ARC_MESSAGE_SIGNATURE, malformedAms)); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // ams_struct_i_zero: an ARC-Message-Signature with i=0 must be rejected — instance numbers start at 1. + @Test + public void validate_arc_chain_fails_when_ams_has_zero_instance_tag() throws Exception { + ByteArrayInputStream emailStream = readFileToByteArrayInputStream("/mail/rfc8617_no_arc.eml"); + Message message = new DefaultMessageBuilder().parseMessage(emailStream); + Map arcSet = arcSetBuilder.buildArcSet(message, HELO, MAIL_FROM, IP, keyRecordRetriever); + + message.getHeader().addField(new RawField(ARC_AUTHENTICATION_RESULTS, arcSet.get(ARC_AUTHENTICATION_RESULTS))); + message.getHeader().addField(new RawField(ARC_SEAL, arcSet.get(ARC_SEAL))); + String malformedAms = arcSet.get(ARC_MESSAGE_SIGNATURE).replaceFirst("i=1;", "i=0;"); + message.getHeader().addField(new RawField(ARC_MESSAGE_SIGNATURE, malformedAms)); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // ams_struct_i_invalid: an ARC-Message-Signature with a non-numeric i= value must be rejected. + @Test + public void validate_arc_chain_fails_when_ams_has_non_numeric_instance_tag() throws Exception { + ByteArrayInputStream emailStream = readFileToByteArrayInputStream("/mail/rfc8617_no_arc.eml"); + Message message = new DefaultMessageBuilder().parseMessage(emailStream); + Map arcSet = arcSetBuilder.buildArcSet(message, HELO, MAIL_FROM, IP, keyRecordRetriever); + + message.getHeader().addField(new RawField(ARC_AUTHENTICATION_RESULTS, arcSet.get(ARC_AUTHENTICATION_RESULTS))); + message.getHeader().addField(new RawField(ARC_SEAL, arcSet.get(ARC_SEAL))); + String malformedAms = arcSet.get(ARC_MESSAGE_SIGNATURE).replaceFirst("i=1;", "i=abc;"); + message.getHeader().addField(new RawField(ARC_MESSAGE_SIGNATURE, malformedAms)); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // ams_struct_dup: two ARC-Message-Signature headers both claiming i=1 make the set ambiguous and + // must be rejected — each instance number must appear exactly once. + @Test + public void validate_arc_chain_fails_when_ams_is_duplicated_at_same_instance() throws Exception { + ByteArrayInputStream emailStream = readFileToByteArrayInputStream("/mail/rfc8617_no_arc.eml"); + Message message = new DefaultMessageBuilder().parseMessage(emailStream); + Map arcSet = arcSetBuilder.buildArcSet(message, HELO, MAIL_FROM, IP, keyRecordRetriever); + + for (Map.Entry entry : arcSet.entrySet()) { + message.getHeader().addField(new RawField(entry.getKey(), entry.getValue())); + } + // Add a second AMS header at i=1 — duplicates the instance number + message.getHeader().addField(new RawField(ARC_MESSAGE_SIGNATURE, arcSet.get(ARC_MESSAGE_SIGNATURE))); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // ams_struct_missing: an ARC-Seal at i=1 with no corresponding ARC-Message-Signature means the + // set is incomplete and must be rejected — covered by validate_arc_chain_fails_when_ams_header_is_missing. + // Builds a valid two-hop ARC chain: applies i=1 to the base message, then applies i=2 on top. private Message buildTwoHopChain() throws Exception { return buildNHopChain(2); From fac3949b4144fd6708b4cc051f8586d18c8ae42f Mon Sep 17 00:00:00 2001 From: Artem Grinchenko Date: Fri, 17 Apr 2026 17:41:16 -0400 Subject: [PATCH 100/114] Add AMS tag format validation tests and fix null-safe DNS result handling --- .../org/apache/james/arc/ARCVerifier.java | 2 +- .../java/org/apache/james/arc/ARCTest.java | 127 ++++++++++++++++++ 2 files changed, 128 insertions(+), 1 deletion(-) diff --git a/arc/src/main/java/org/apache/james/arc/ARCVerifier.java b/arc/src/main/java/org/apache/james/arc/ARCVerifier.java index b831dfc..ef3311e 100644 --- a/arc/src/main/java/org/apache/james/arc/ARCVerifier.java +++ b/arc/src/main/java/org/apache/james/arc/ARCVerifier.java @@ -365,7 +365,7 @@ public String getTxtDnsRecordByField(Field signedHeader) { try { List results = getPublicKeyRecordRetriever().getRecords("dns/txt", amsSelector, amsDomain); - if (!results.isEmpty()) { + if (results != null && !results.isEmpty()) { return results.get(0); //Todo: handle multiple records? } } catch (TempFailException e) { diff --git a/arc/src/test/java/org/apache/james/arc/ARCTest.java b/arc/src/test/java/org/apache/james/arc/ARCTest.java index 9b49803..51deaa7 100644 --- a/arc/src/test/java/org/apache/james/arc/ARCTest.java +++ b/arc/src/test/java/org/apache/james/arc/ARCTest.java @@ -557,6 +557,133 @@ public void validate_arc_chain_fails_when_ams_is_duplicated_at_same_instance() t // ams_struct_missing: an ARC-Seal at i=1 with no corresponding ARC-Message-Signature means the // set is incomplete and must be rejected — covered by validate_arc_chain_fails_when_ams_header_is_missing. + // ams_format_tags_unknown: an unrecognised tag in the ARC-Message-Signature must be silently ignored, + // so a chain signed with an extra z= tag must still validate as cv=pass. + @Test + public void validate_arc_chain_passes_when_ams_has_unknown_tag() throws Exception { + String templateWithUnknownTag = "i=; a=rsa-sha256; c=relaxed/relaxed; d=dmarc.example; s=arc; z=test; t=; h=Subject:From:To; bh=; b="; + ArcSetBuilder builderWithUnknownTag = new ArcSetBuilder(ArcTestKeys.privateKeyArc, templateWithUnknownTag, ARC_SEAL_TEMPLATE, AUTH_SERVICE, TIMESTAMP); + + ByteArrayInputStream emailStream = readFileToByteArrayInputStream("/mail/rfc8617_no_arc.eml"); + Message message = new DefaultMessageBuilder().parseMessage(emailStream); + Map arcSet = builderWithUnknownTag.buildArcSet(message, HELO, MAIL_FROM, IP, keyRecordRetriever); + for (Map.Entry entry : arcSet.entrySet()) { + message.getHeader().addField(new RawField(entry.getKey(), entry.getValue())); + } + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("pass"); + } + + // ams_format_inv_tag_key: a tag key starting with a digit (e.g. 1s=arc) is not a valid tag name + // and the selector cannot be resolved, so the chain must be rejected. + @Test + public void validate_arc_chain_fails_when_ams_has_invalid_tag_key_character() throws Exception { + ByteArrayInputStream emailStream = readFileToByteArrayInputStream("/mail/rfc8617_no_arc.eml"); + Message message = new DefaultMessageBuilder().parseMessage(emailStream); + Map arcSet = arcSetBuilder.buildArcSet(message, HELO, MAIL_FROM, IP, keyRecordRetriever); + + message.getHeader().addField(new RawField(ARC_AUTHENTICATION_RESULTS, arcSet.get(ARC_AUTHENTICATION_RESULTS))); + message.getHeader().addField(new RawField(ARC_SEAL, arcSet.get(ARC_SEAL))); + String malformedAms = arcSet.get(ARC_MESSAGE_SIGNATURE).replace("s=arc", "1s=arc"); + message.getHeader().addField(new RawField(ARC_MESSAGE_SIGNATURE, malformedAms)); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // ams_format_tags_dup: if the same tag key appears twice in an ARC-Message-Signature, the second + // value overrides the first, resolving to a different selector that is not in DNS, causing failure. + @Test + public void validate_arc_chain_fails_when_ams_has_duplicate_tag() throws Exception { + ByteArrayInputStream emailStream = readFileToByteArrayInputStream("/mail/rfc8617_no_arc.eml"); + Message message = new DefaultMessageBuilder().parseMessage(emailStream); + Map arcSet = arcSetBuilder.buildArcSet(message, HELO, MAIL_FROM, IP, keyRecordRetriever); + + message.getHeader().addField(new RawField(ARC_AUTHENTICATION_RESULTS, arcSet.get(ARC_AUTHENTICATION_RESULTS))); + message.getHeader().addField(new RawField(ARC_SEAL, arcSet.get(ARC_SEAL))); + String malformedAms = arcSet.get(ARC_MESSAGE_SIGNATURE) + "; s=invalid"; + message.getHeader().addField(new RawField(ARC_MESSAGE_SIGNATURE, malformedAms)); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // ams_format_tags_key_case: tag keys are case-sensitive — S=arc does not provide the s= tag, + // so the selector cannot be found and the chain must be rejected. + @Test + public void validate_arc_chain_fails_when_ams_uses_uppercase_tag_key() throws Exception { + ByteArrayInputStream emailStream = readFileToByteArrayInputStream("/mail/rfc8617_no_arc.eml"); + Message message = new DefaultMessageBuilder().parseMessage(emailStream); + Map arcSet = arcSetBuilder.buildArcSet(message, HELO, MAIL_FROM, IP, keyRecordRetriever); + + message.getHeader().addField(new RawField(ARC_AUTHENTICATION_RESULTS, arcSet.get(ARC_AUTHENTICATION_RESULTS))); + message.getHeader().addField(new RawField(ARC_SEAL, arcSet.get(ARC_SEAL))); + String malformedAms = arcSet.get(ARC_MESSAGE_SIGNATURE).replace("s=arc", "S=arc"); + message.getHeader().addField(new RawField(ARC_MESSAGE_SIGNATURE, malformedAms)); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // ams_format_tags_val_case: modifying a tag value's case (e.g. a=RSA-SHA256) changes the + // signed bytes so the signature no longer verifies, and the chain must be rejected. + @Test + public void validate_arc_chain_fails_when_ams_tag_value_has_wrong_case() throws Exception { + ByteArrayInputStream emailStream = readFileToByteArrayInputStream("/mail/rfc8617_no_arc.eml"); + Message message = new DefaultMessageBuilder().parseMessage(emailStream); + Map arcSet = arcSetBuilder.buildArcSet(message, HELO, MAIL_FROM, IP, keyRecordRetriever); + + message.getHeader().addField(new RawField(ARC_AUTHENTICATION_RESULTS, arcSet.get(ARC_AUTHENTICATION_RESULTS))); + message.getHeader().addField(new RawField(ARC_SEAL, arcSet.get(ARC_SEAL))); + String malformedAms = arcSet.get(ARC_MESSAGE_SIGNATURE).replace("a=rsa-sha256", "a=RSA-SHA256"); + message.getHeader().addField(new RawField(ARC_MESSAGE_SIGNATURE, malformedAms)); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // ams_format_tags_wsp: whitespace inside a tag value (s=ar c) changes the DNS selector name to + // one that does not exist, so the public key cannot be retrieved and the chain must be rejected. + @Test + public void validate_arc_chain_fails_when_ams_tag_value_contains_whitespace() throws Exception { + ByteArrayInputStream emailStream = readFileToByteArrayInputStream("/mail/rfc8617_no_arc.eml"); + Message message = new DefaultMessageBuilder().parseMessage(emailStream); + Map arcSet = arcSetBuilder.buildArcSet(message, HELO, MAIL_FROM, IP, keyRecordRetriever); + + message.getHeader().addField(new RawField(ARC_AUTHENTICATION_RESULTS, arcSet.get(ARC_AUTHENTICATION_RESULTS))); + message.getHeader().addField(new RawField(ARC_SEAL, arcSet.get(ARC_SEAL))); + String malformedAms = arcSet.get(ARC_MESSAGE_SIGNATURE).replace("s=arc", "s=ar c"); + message.getHeader().addField(new RawField(ARC_MESSAGE_SIGNATURE, malformedAms)); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // ams_format_tags_sc: an extra semicolon inside a tag value splits the value, making the s= tag + // resolve to a truncated selector that is not in DNS, so the chain must be rejected. + @Test + public void validate_arc_chain_fails_when_ams_tag_value_contains_semicolon() throws Exception { + ByteArrayInputStream emailStream = readFileToByteArrayInputStream("/mail/rfc8617_no_arc.eml"); + Message message = new DefaultMessageBuilder().parseMessage(emailStream); + Map arcSet = arcSetBuilder.buildArcSet(message, HELO, MAIL_FROM, IP, keyRecordRetriever); + + message.getHeader().addField(new RawField(ARC_AUTHENTICATION_RESULTS, arcSet.get(ARC_AUTHENTICATION_RESULTS))); + message.getHeader().addField(new RawField(ARC_SEAL, arcSet.get(ARC_SEAL))); + String malformedAms = arcSet.get(ARC_MESSAGE_SIGNATURE).replace("s=arc", "s=ar;c"); + message.getHeader().addField(new RawField(ARC_MESSAGE_SIGNATURE, malformedAms)); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + // Builds a valid two-hop ARC chain: applies i=1 to the base message, then applies i=2 on top. private Message buildTwoHopChain() throws Exception { return buildNHopChain(2); From d7e40d882346224a11a04e8993b304ce0c41861b Mon Sep 17 00:00:00 2001 From: Artem Grinchenko Date: Fri, 17 Apr 2026 18:06:59 -0400 Subject: [PATCH 101/114] Add canonicalization tests for body whitespace and header normalization --- .../java/org/apache/james/arc/ARCTest.java | 196 ++++++++++++++++++ 1 file changed, 196 insertions(+) diff --git a/arc/src/test/java/org/apache/james/arc/ARCTest.java b/arc/src/test/java/org/apache/james/arc/ARCTest.java index 51deaa7..85c589a 100644 --- a/arc/src/test/java/org/apache/james/arc/ARCTest.java +++ b/arc/src/test/java/org/apache/james/arc/ARCTest.java @@ -557,6 +557,202 @@ public void validate_arc_chain_fails_when_ams_is_duplicated_at_same_instance() t // ams_struct_missing: an ARC-Seal at i=1 with no corresponding ARC-Message-Signature means the // set is incomplete and must be rejected — covered by validate_arc_chain_fails_when_ams_header_is_missing. + // Pre-filled template used for direct ARCSigner canonicalization tests. + private static final String CANON_TEST_TEMPLATE = + "i=1; a=rsa-sha256; c=relaxed/relaxed; d=dmarc.example; s=arc; t=" + TIMESTAMP + + "; h=Subject:From:To; bh=; b="; + + // Minimal base email used for canonicalization tests. + private static final String BASE_EMAIL = + "From: jqd@d1.example\r\n" + + "To: arc@example.com\r\n" + + "Subject: test\r\n" + + "\r\n" + + "Hello world\r\n"; + + // Signs a raw email byte string directly with ARCSigner and returns "ARC-element:". + private String signRawEmail(String rawEmail) { + ARCSigner signer = new ARCSigner(CANON_TEST_TEMPLATE, ArcTestKeys.privateKeyArc); + return signer.generateAms(new ByteArrayInputStream(rawEmail.getBytes(StandardCharsets.UTF_8))); + } + + // Extracts a tag value from an AMS record (with or without the "ARC-element:" prefix). + private String extractAmsTag(String ams, String tagName) { + String body = ams.replaceFirst("^ARC-element:", ""); + for (String part : body.split(";")) { + String t = part.trim(); + if (t.startsWith(tagName + "=")) { + return t.substring((tagName + "=").length()).trim(); + } + } + return null; + } + + // message_body_eol_wsp: trailing whitespace on a body line must be stripped before body hashing, + // so two messages that differ only in trailing spaces produce the same bh=. + @Test + public void body_hash_is_invariant_under_body_line_trailing_whitespace() { + String variant = BASE_EMAIL.replace("Hello world\r\n", "Hello world \r\n"); + assertThat(extractAmsTag(signRawEmail(BASE_EMAIL), "bh")) + .isEqualTo(extractAmsTag(signRawEmail(variant), "bh")); + } + + // message_body_inl_wsp: runs of whitespace inside a body line must be collapsed to one space + // before body hashing, so double spaces produce the same bh= as single spaces. + @Test + public void body_hash_is_invariant_under_body_inline_whitespace() { + String variant = BASE_EMAIL.replace("Hello world\r\n", "Hello world\r\n"); + assertThat(extractAmsTag(signRawEmail(BASE_EMAIL), "bh")) + .isEqualTo(extractAmsTag(signRawEmail(variant), "bh")); + } + + // message_body_end_lines: trailing blank lines at the end of the body must be ignored when + // computing the body hash, so extra blank lines produce the same bh=. + @Test + public void body_hash_is_invariant_under_trailing_blank_lines() { + String variant = BASE_EMAIL.replace("Hello world\r\n", "Hello world\r\n\r\n\r\n"); + assertThat(extractAmsTag(signRawEmail(BASE_EMAIL), "bh")) + .isEqualTo(extractAmsTag(signRawEmail(variant), "bh")); + } + + // message_body_trail_crlf: a body that does not end with CRLF must have one appended before + // hashing, so it produces the same bh= as the same body that does end with CRLF. + @Test + public void body_hash_is_invariant_when_body_lacks_trailing_crlf() { + String variant = BASE_EMAIL.replace("Hello world\r\n", "Hello world"); + assertThat(extractAmsTag(signRawEmail(BASE_EMAIL), "bh")) + .isEqualTo(extractAmsTag(signRawEmail(variant), "bh")); + } + + // headers_field_name_case: header names must be lowercased before signing, so Subject and SUBJECT + // produce the same AMS (same bh= and same b=). + @Test + public void ams_is_invariant_under_header_name_case() { + String variant = BASE_EMAIL.replace("Subject: test\r\n", "SUBJECT: test\r\n"); + assertThat(signRawEmail(BASE_EMAIL)).isEqualTo(signRawEmail(variant)); + } + + // headers_field_unfold: folded headers (split with CRLF + whitespace continuation) must be + // joined back into one line before signing, so the folded and unfolded forms produce the same AMS. + @Test + public void ams_is_invariant_under_header_folding() { + String cleanEmail = + "From: jqd@d1.example\r\nTo: arc@example.com\r\nSubject: Hello world\r\n\r\nHello world\r\n"; + String foldedEmail = + "From: jqd@d1.example\r\nTo: arc@example.com\r\nSubject: Hello\r\n world\r\n\r\nHello world\r\n"; + assertThat(signRawEmail(cleanEmail)).isEqualTo(signRawEmail(foldedEmail)); + } + + // headers_eol_wsp: trailing whitespace at the end of a header value must be stripped before + // signing, so trailing spaces produce the same AMS as no trailing spaces. + @Test + public void ams_is_invariant_under_header_trailing_whitespace() { + String variant = BASE_EMAIL.replace("Subject: test\r\n", "Subject: test \r\n"); + assertThat(signRawEmail(BASE_EMAIL)).isEqualTo(signRawEmail(variant)); + } + + // headers_inl_wsp: runs of whitespace inside a header value must be collapsed to one space before + // signing, so double spaces inside a value produce the same AMS as a single space. + @Test + public void ams_is_invariant_under_header_inline_whitespace() { + String cleanEmail = + "From: jqd@d1.example\r\nTo: arc@example.com\r\nSubject: Hello world\r\n\r\nHello world\r\n"; + String variantEmail = + "From: jqd@d1.example\r\nTo: arc@example.com\r\nSubject: Hello world\r\n\r\nHello world\r\n"; + assertThat(signRawEmail(cleanEmail)).isEqualTo(signRawEmail(variantEmail)); + } + + // headers_col_wsp: whitespace around the colon separator in a header must be normalised before + // signing, so "Subject: test" and "Subject:test" (no space) produce the same AMS. + @Test + public void ams_is_invariant_under_header_colon_whitespace() { + String variant = BASE_EMAIL.replace("Subject: test\r\n", "Subject:test\r\n"); + assertThat(signRawEmail(BASE_EMAIL)).isEqualTo(signRawEmail(variant)); + } + + // i1_base: when a message already carries a valid i=1 ARC set, buildArcSet must produce an i=2 set + // whose seal carries cv=pass — the new server correctly extends the chain. + @Test + public void build_arc_set_generates_i2_cv_pass_when_signing_on_top_of_valid_i1_chain() throws Exception { + Message message = buildNHopChain(1); + Map arcSet = arcSetBuilder.buildArcSet(message, HELO, MAIL_FROM, IP, keyRecordRetriever); + assertThat(arcSet.get(ARC_SEAL)).contains("cv=pass"); + assertThat(arcSet.get(ARC_SEAL)).contains("i=2"); + assertThat(arcSet.get(ARC_MESSAGE_SIGNATURE)).contains("i=2"); + assertThat(arcSet.get(ARC_AUTHENTICATION_RESULTS)).contains("i=2"); + } + + // i2_base: when a message already carries valid i=1 and i=2 ARC sets, buildArcSet must produce an i=3 + // set whose seal carries cv=pass — the new server correctly extends the chain. + @Test + public void build_arc_set_generates_i3_cv_pass_when_signing_on_top_of_valid_i2_chain() throws Exception { + Message message = buildNHopChain(2); + Map arcSet = arcSetBuilder.buildArcSet(message, HELO, MAIL_FROM, IP, keyRecordRetriever); + assertThat(arcSet.get(ARC_SEAL)).contains("cv=pass"); + assertThat(arcSet.get(ARC_SEAL)).contains("i=3"); + assertThat(arcSet.get(ARC_MESSAGE_SIGNATURE)).contains("i=3"); + assertThat(arcSet.get(ARC_AUTHENTICATION_RESULTS)).contains("i=3"); + } + + // i1_base_fail: when the incoming i=1 chain is already broken (corrupt AMS), buildArcSet must still + // produce an i=2 set, but the new seal must carry cv=fail to faithfully record the broken chain. + @Test + public void build_arc_set_generates_cv_fail_seal_when_signing_on_top_of_broken_i1_chain() throws Exception { + ByteArrayInputStream emailStream = readFileToByteArrayInputStream("/mail/rfc8617_no_arc.eml"); + Message message = new DefaultMessageBuilder().parseMessage(emailStream); + Map arcSet = arcSetBuilder.buildArcSet(message, HELO, MAIL_FROM, IP, keyRecordRetriever); + + String fakeB64 = Base64.getEncoder().encodeToString(new byte[128]); + for (Map.Entry entry : arcSet.entrySet()) { + String value = entry.getKey().equals(ARC_MESSAGE_SIGNATURE) + ? entry.getValue().replaceAll("; b=.*$", "; b=" + fakeB64) + : entry.getValue(); + message.getHeader().addField(new RawField(entry.getKey(), value)); + } + + Map newArcSet = arcSetBuilder.buildArcSet(message, HELO, MAIL_FROM, IP, keyRecordRetriever); + assertThat(newArcSet.get(ARC_SEAL)).contains("cv=fail"); + assertThat(newArcSet.get(ARC_SEAL)).contains("i=2"); + } + + // i2_base_fail: when the incoming two-hop chain is already broken, buildArcSet must produce an i=3 + // set whose seal carries cv=fail — the broken state is faithfully recorded. + @Test + public void build_arc_set_generates_cv_fail_seal_when_signing_on_top_of_broken_i2_chain() throws Exception { + Message message = buildNHopChain(2); + corruptSignatureOnHeader(message, ARC_MESSAGE_SIGNATURE, "i=1"); + + Map newArcSet = arcSetBuilder.buildArcSet(message, HELO, MAIL_FROM, IP, keyRecordRetriever); + assertThat(newArcSet.get(ARC_SEAL)).contains("cv=fail"); + assertThat(newArcSet.get(ARC_SEAL)).contains("i=3"); + } + + // no_additional_sig: after signing on top of a broken chain and adding the new i=2 set to the message, + // the full chain validation must still return cv=fail — a valid new signature must not heal a broken chain. + @Test + public void validate_arc_chain_remains_fail_after_signing_on_top_of_broken_chain() throws Exception { + ByteArrayInputStream emailStream = readFileToByteArrayInputStream("/mail/rfc8617_no_arc.eml"); + Message message = new DefaultMessageBuilder().parseMessage(emailStream); + Map arcSet = arcSetBuilder.buildArcSet(message, HELO, MAIL_FROM, IP, keyRecordRetriever); + + String fakeB64 = Base64.getEncoder().encodeToString(new byte[128]); + for (Map.Entry entry : arcSet.entrySet()) { + String value = entry.getKey().equals(ARC_MESSAGE_SIGNATURE) + ? entry.getValue().replaceAll("; b=.*$", "; b=" + fakeB64) + : entry.getValue(); + message.getHeader().addField(new RawField(entry.getKey(), value)); + } + + Map newArcSet = arcSetBuilder.buildArcSet(message, HELO, MAIL_FROM, IP, keyRecordRetriever); + for (Map.Entry entry : newArcSet.entrySet()) { + message.getHeader().addField(new RawField(entry.getKey(), entry.getValue())); + } + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + // ams_format_tags_unknown: an unrecognised tag in the ARC-Message-Signature must be silently ignored, // so a chain signed with an extra z= tag must still validate as cv=pass. @Test From 4bf56532f7cfed5005c79fa9c7facb8de0fe6ea6 Mon Sep 17 00:00:00 2001 From: Artem Grinchenko Date: Fri, 17 Apr 2026 18:30:43 -0400 Subject: [PATCH 102/114] Add ARC AMS formatting and duplicate instance tests: - Add ValiMail-derived AMS tag format validation coverage for whitespace around separators and trailing semicolons. Add a duplicate - ARC-Message-Signature i=1 ordering variant to ensure duplicate AMS instances are rejected regardless of header order. --- .../java/org/apache/james/arc/ARCTest.java | 127 +++++++++++++++++- 1 file changed, 126 insertions(+), 1 deletion(-) diff --git a/arc/src/test/java/org/apache/james/arc/ARCTest.java b/arc/src/test/java/org/apache/james/arc/ARCTest.java index 85c589a..211b000 100644 --- a/arc/src/test/java/org/apache/james/arc/ARCTest.java +++ b/arc/src/test/java/org/apache/james/arc/ARCTest.java @@ -71,6 +71,16 @@ public class ARCTest { "softfail (spfCheck: transitioning domain of d1.example does not designate 222.222.222.222 as permitted sender) client-ip=222.222.222.222; envelope-from=jqd@d1.example; helo=d1.example") ); + private static final String VALIMAIL_DUMMY_PUBLIC_KEY = + "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDkHlOQoBTzWRiGs5V6NpP3id" + + "Y6Wk08a5qhdR6wy5bdOKb2jLQiY/J16JYi0Qvx/byYzCNb3W91y3FutACDfzwQ/BC/e/8uBsCR+yz1Lx" + + "j+PL6lHvqMKrM3rG4hstT5QjvHO9PzoxZyVYLzBfO2EeC3Ip3G+2kryOTIKT+l/K4w3QIDAQAB"; + + private final MockPublicKeyRecordRetrieverArc valimailKeyRecordRetriever = new MockPublicKeyRecordRetrieverArc( + dmarcRetriever, + MockPublicKeyRecordRetriever.Record.of("dummy", "example.org", VALIMAIL_DUMMY_PUBLIC_KEY) + ); + /** * - "a" field will be added by the signer based on signer setup * - "bh=" and "b=" placeholder are required for now because the same implementation is used for @@ -554,6 +564,24 @@ public void validate_arc_chain_fails_when_ams_is_duplicated_at_same_instance() t assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); } + // ams_fields_i_dup1: duplicate ARC-Message-Signature instance numbers must be rejected even + // when the duplicate appears before the rest of the ARC set in header order. + @Test + public void validate_arc_chain_fails_when_duplicate_ams_instance_appears_before_arc_set() throws Exception { + ByteArrayInputStream emailStream = readFileToByteArrayInputStream("/mail/rfc8617_no_arc.eml"); + Message message = new DefaultMessageBuilder().parseMessage(emailStream); + Map arcSet = arcSetBuilder.buildArcSet(message, HELO, MAIL_FROM, IP, keyRecordRetriever); + + message.getHeader().addField(new RawField(ARC_MESSAGE_SIGNATURE, arcSet.get(ARC_MESSAGE_SIGNATURE))); + for (Map.Entry entry : arcSet.entrySet()) { + message.getHeader().addField(new RawField(entry.getKey(), entry.getValue())); + } + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + // ams_struct_missing: an ARC-Seal at i=1 with no corresponding ARC-Message-Signature means the // set is incomplete and must be rejected — covered by validate_arc_chain_fails_when_ams_header_is_missing. @@ -880,6 +908,70 @@ public void validate_arc_chain_fails_when_ams_tag_value_contains_semicolon() thr assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); } + // ams_format_sc_wsp: whitespace before the semicolon separator in an AMS tag list is valid. + // This mirrors the ValiMail arc_test_suite fixture and must validate as cv=pass. + @Test + public void validate_arc_chain_passes_when_ams_has_whitespace_around_semicolon_separator() throws Exception { + assertValimailFixturePasses( + "MIME-Version: 1.0\n" + + "Return-Path: \n" + + "ARC-Seal: a=rsa-sha256;\n" + + " b=OeNJ7p2NdW3mKv4hyenx+QbRuqqq8iwGAyY1WVX/EJiPHS2vNB5lEI/YmVB3diTkKPHWe8\n" + + " ZOq18DTVtOVuahLqM7s/4K/gvx3zal0vcedPL/mtRW4A1Ct0/wyLuFADX2HZ815cELx81SuX\n" + + " 3fEbbym1br+0JArsz6n8798lidnWY=; cv=none; d=example.org; i=1; s=dummy;\n" + + " t=12345\n" + + "ARC-Message-Signature: a=rsa-sha256;\n" + + " b=NOLE9bNh30qiTx35h5yKbHlDPahxvhXUWjv8Yiy5L7Ks3NNznK54dmUPZ4D/80tkRYiil0\n" + + " 8sCqFTh7OH5ZTXXEfArxBMQQl3DAqTjOJQ1c3jPYwaDliWqCLLueSsH+ovaFGRGNPm2O41o0\n" + + " J8xUmyji1bXXLKMinB+Adv9ALXsw8=;\n" + + " bh=KWSe46TZKCcDbH4klJPo+tjk5LWJnVRlP5pvjXFZYLQ= ; c=relaxed/relaxed;\n" + + " d=example.org; h=from:to:date:subject:mime-version:arc-authentication-results;\n" + + " i=1; s=dummy; t=12345\n" + + valimailCommonMessageTail()); + } + + // ams_format_eq_wsp: whitespace around "=" in an AMS tag is valid and should not break parsing. + @Test + public void validate_arc_chain_passes_when_ams_has_whitespace_around_equals_separator() throws Exception { + assertValimailFixturePasses( + "MIME-Version: 1.0\n" + + "Return-Path: \n" + + "ARC-Seal: a=rsa-sha256;\n" + + " b=CcoQW04QZ7n7OTPACcP26R0vJtjEwVmcFpj4+PJnvT1kVeOMfcqwt7FEGlCjeJ0QIYMeNW\n" + + " TY6kND0fe0WJDVnWvhCyeOb5JjwllbJJ/ThP74I5UPgQ0Cwp1h/O9HIrUJkrje6HQ3nD6Dok\n" + + " la2keL/t4R7YtMyAmn9sPWuAOwSrE=; cv=none; d=example.org; i=1; s=dummy;\n" + + " t=12345\n" + + "ARC-Message-Signature: a=rsa-sha256;\n" + + " b=KLZ8Io9rZzsWt0Q/Mrx8sYO7HPLptFwGoCdabHuyrQsek+1c5yo5tOQidcTc8ksw5PoAZH\n" + + " PNOIoyGVte9jMk0LdA1IYjjvvUmEANMZCJf0wm66exDWJ30xMrgbosLN2XvsRk3BDkoCg2AY\n" + + " HkR11isMdIhrefd7AHw9YEDTnohQw=;\n" + + " bh=KWSe46TZKCcDbH4klJPo+tjk5LWJnVRlP5pvjXFZYLQ=; c = relaxed/relaxed;\n" + + " d=example.org; h=from:to:date:subject:mime-version:arc-authentication-results;\n" + + " i=1; s=dummy; t=12345\n" + + valimailCommonMessageTail()); + } + + // ams_format_tags_trail_sc: a trailing semicolon at the end of the AMS tag list is valid. + @Test + public void validate_arc_chain_passes_when_ams_tag_list_has_trailing_semicolon() throws Exception { + assertValimailFixturePasses( + "MIME-Version: 1.0\n" + + "Return-Path: \n" + + "ARC-Seal: a=rsa-sha256;\n" + + " b=Q3iCsG7zmlydzz8zFIm4X+Nyr2636znsyGh+lRhCFtcWbw3m3v8fFrtK3uNvqSM+WW3Cmf\n" + + " TbteHFaG9YL34KUMi/ThuPoG8sOwJ18BPjXrdBS5EiXYBBFalkVRV0ktqyiNi57LBVS+VGWV\n" + + " FwOD85C/V/Fju2wETdy0ly1VjfLBg=; cv=none; d=example.org; i=1; s=dummy;\n" + + " t=12345\n" + + "ARC-Message-Signature: a=rsa-sha256;\n" + + " b=H+XsRP2HBJwygQonE/YquKr2y1KqjjlhBQ/hEkIGFjjNhOIvMfuuO054H4+kxMmvHFdwk8\n" + + " a8Uwy1MxQBC3a4b0jAQ77rOn5VFhO1tAmCkfZP1bJSxewRfC2Eo7j/07+r8ZLuyuAzlQIW+n\n" + + " DPJtOhnIIEOGhLgPNlcTwc9R/XKiE=;\n" + + " bh=KWSe46TZKCcDbH4klJPo+tjk5LWJnVRlP5pvjXFZYLQ=; c=relaxed/relaxed;\n" + + " d=example.org; h=from:to:date:subject:mime-version:arc-authentication-results;\n" + + " i=1; s=dummy; t=12345;\n" + + valimailCommonMessageTail()); + } + // Builds a valid two-hop ARC chain: applies i=1 to the base message, then applies i=2 on top. private Message buildTwoHopChain() throws Exception { return buildNHopChain(2); @@ -941,6 +1033,39 @@ private void replaceTagOnHeader(Message message, String headerName, String insta } } + private void assertValimailFixturePasses(String rawMessage) throws Exception { + Message message = new DefaultMessageBuilder().parseMessage( + new ByteArrayInputStream(rawMessage.replace("\n", "\r\n").getBytes(StandardCharsets.UTF_8))); + ARCChainValidator arcChainValidator = new ARCChainValidator(valimailKeyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("pass"); + } + + private String valimailCommonMessageTail() { + return "ARC-Authentication-Results: i=1; lists.example.org;\n" + + " spf=pass smtp.mfrom=jqd@d1.example;\n" + + " dkim=pass (1024-bit key) header.i=@d1.example;\n" + + " dmarc=pass\n" + + "Received: from segv.d1.example (segv.d1.example [72.52.75.15])\n" + + " by lists.example.org (8.14.5/8.14.5) with ESMTP id t0EKaNU9010123\n" + + " for ; Thu, 14 Jan 2015 15:01:30 -0800 (PST)\n" + + " (envelope-from jqd@d1.example)\n" + + "Authentication-Results: lists.example.org;\n" + + " spf=pass smtp.mfrom=jqd@d1.example;\n" + + " dkim=pass (1024-bit key) header.i=@d1.example;\n" + + " dmarc=pass\n" + + "Received: by 10.157.14.6 with HTTP; Tue, 3 Jan 2017 12:22:54 -0800 (PST)\n" + + "Message-ID: <54B84785.1060301@d1.example.org>\n" + + "Date: Thu, 14 Jan 2015 15:00:01 -0800\n" + + "From: John Q Doe \n" + + "To: arc@dmarc.org\n" + + "Subject: Example 1\n" + + "\n" + + "Hey gang,\n" + + "This is a test message.\n" + + "--J."; + } + private ByteArrayInputStream readFileToByteArrayInputStream(String fileName) throws URISyntaxException, IOException { URL resource = this.getClass().getResource(fileName); FileInputStream file = new FileInputStream(new File(resource.toURI())); @@ -951,4 +1076,4 @@ private ByteArrayInputStream readFileToByteArrayInputStream(String fileName) thr return new ByteArrayInputStream(string.getBytes(StandardCharsets.UTF_8)); } -} \ No newline at end of file +} From c6dbdb245ff1234146cca94c35a8e8f73aba48e1 Mon Sep 17 00:00:00 2001 From: Artem Grinchenko Date: Fri, 17 Apr 2026 21:37:39 -0400 Subject: [PATCH 103/114] Expand ARC validation coverage and fix header parsing: - Add ValiMail-derived ARC tests for Authentication-Results merging, ARC-Authentication-Results structure validation, and ARC-Seal structure and tag-format handling. - Consolidate existing Authentication-Results headers for the configured authserv-id when building ARC-Authentication-Results. - Update ARC verification to select signed headers bottom-up, matching signing behavior; tolerate whitespace around tag-list equals separators. --- .../org/apache/james/arc/ARCVerifier.java | 41 +- .../apache/james/arc/AuthResultsBuilder.java | 47 ++ .../java/org/apache/james/arc/ARCTest.java | 429 ++++++++++++++++++ 3 files changed, 511 insertions(+), 6 deletions(-) diff --git a/arc/src/main/java/org/apache/james/arc/ARCVerifier.java b/arc/src/main/java/org/apache/james/arc/ARCVerifier.java index ef3311e..c31cf15 100644 --- a/arc/src/main/java/org/apache/james/arc/ARCVerifier.java +++ b/arc/src/main/java/org/apache/james/arc/ARCVerifier.java @@ -98,10 +98,22 @@ public boolean verifyAms(Field amsField, Message message, String publicKeyDnsRec String amsForSigning = amsValue.replaceFirst(B_TAG_REGEX, "b="); // Canonicalize headers listed in h= StringBuilder signingData = new StringBuilder(); + Map processedHeaders = new HashMap<>(); for (String hName : signedHeaders.split(":")) { hName = hName.trim(); - for (Field f : message.getHeader().getFields(hName)) { + List fields = message.getHeader().getFields(hName); + if (fields != null && !fields.isEmpty()) { + Integer done = processedHeaders.get(hName); + if (done == null) { + done = 0; + } + int doneHeaders = done + 1; + if (doneHeaders > fields.size()) { + continue; + } + Field f = fields.get(fields.size() - doneHeaders); signingData.append(canonicalizeRegularHeader(f)); + processedHeaders.put(hName, doneHeaders); } } @@ -146,9 +158,21 @@ private Signature getSignature(PublicKey publicKey, StringBuilder signingData) public Map parseTagList(String value) { Map map = new HashMap<>(); - Matcher m = TAG_PATTERN.matcher(value); - while (m.find()) { - map.put(m.group(1).trim(), m.group(2).trim()); + String[] parts = value.split(";"); + for (String part : parts) { + String trimmed = part.trim(); + if (trimmed.isEmpty()) { + continue; + } + int equal = trimmed.indexOf('='); + if (equal == -1) { + continue; + } + String tag = trimmed.substring(0, equal).trim(); + String tagValue = trimmed.substring(equal + 1).trim(); + if (tag.matches("[a-z]+")) { + map.put(tag, tagValue); + } } return map; } @@ -283,8 +307,13 @@ public String parseTagGeneric(String record, String tag) { String[] parts = record.split(";"); for (String part : parts) { String trimmed = part.trim(); - if (trimmed.startsWith(tag + "=")) { - return trimmed.substring((tag + "=").length()); + int equal = trimmed.indexOf('='); + if (equal == -1) { + continue; + } + String tagName = trimmed.substring(0, equal).trim(); + if (tagName.equals(tag)) { + return trimmed.substring(equal + 1).trim(); } } return null; diff --git a/arc/src/main/java/org/apache/james/arc/AuthResultsBuilder.java b/arc/src/main/java/org/apache/james/arc/AuthResultsBuilder.java index 231b5e6..26b561e 100644 --- a/arc/src/main/java/org/apache/james/arc/AuthResultsBuilder.java +++ b/arc/src/main/java/org/apache/james/arc/AuthResultsBuilder.java @@ -29,11 +29,13 @@ import org.apache.james.jdkim.tagvalue.SignatureRecordImpl; import org.apache.james.mime4j.dom.Message; import org.apache.james.mime4j.message.DefaultMessageWriter; +import org.apache.james.mime4j.stream.Field; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; import java.util.Base64; import java.util.List; import java.util.Objects; @@ -57,6 +59,7 @@ */ public class AuthResultsBuilder { public static final String HEADER_I = "header.i="; + public static final String AUTHENTICATION_RESULTS = "Authentication-Results"; private final PublicKeyRetrieverArc _keyRecordRetriever; private String _authService; @@ -66,6 +69,10 @@ public AuthResultsBuilder(String authService, PublicKeyRetrieverArc keyRecordRet } public String getAuthResultsHeader(Message message, String helo, String from, String ip) { + String consolidatedAuthResults = consolidateExistingAuthResults(message); + if (consolidatedAuthResults != null) { + return consolidatedAuthResults; + } // 1. Run SPF check String spfResultText = _keyRecordRetriever.getSpfRecord(helo, from, ip); @@ -91,6 +98,46 @@ public String getAuthResultsHeader(Message message, String helo, String from, St dmarcResult.toString(); } + private String consolidateExistingAuthResults(Message message) { + List results = new ArrayList<>(); + for (Field field : message.getHeader().getFields(AUTHENTICATION_RESULTS)) { + String body = normalizeAuthResultsBody(field.getBody()); + int separator = body.indexOf(';'); + if (separator == -1) { + continue; + } + String authservId = body.substring(0, separator).trim(); + if (!_authService.equalsIgnoreCase(authservId)) { + continue; + } + String result = body.substring(separator + 1).trim(); + if (!result.isEmpty()) { + results.add(result); + } + } + + if (results.isEmpty()) { + return null; + } + + StringBuilder consolidated = new StringBuilder(_authService); + for (int i = 0; i < results.size(); i++) { + String result = results.get(i); + consolidated.append(i == 0 ? "; " : " "); + consolidated.append(result); + if (!result.endsWith(";") && i < results.size() - 1) { + consolidated.append(";"); + } + } + return consolidated.toString(); + } + + private String normalizeAuthResultsBody(String body) { + return body.replaceAll("[\\r\\n]+[\\t ]*", " ") + .replaceAll("[\\t ]+", " ") + .trim(); + } + private String runDkimCheck(Message message) throws IOException { final DKIMVerifier verifier = new DKIMVerifier(_keyRecordRetriever); InputStream is = messageToInputStream(message); diff --git a/arc/src/test/java/org/apache/james/arc/ARCTest.java b/arc/src/test/java/org/apache/james/arc/ARCTest.java index 211b000..c0e655f 100644 --- a/arc/src/test/java/org/apache/james/arc/ARCTest.java +++ b/arc/src/test/java/org/apache/james/arc/ARCTest.java @@ -582,6 +582,107 @@ public void validate_arc_chain_fails_when_duplicate_ams_instance_appears_before_ assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); } + // aar_struct_i_na / aar_i_missing: an ARC-Authentication-Results header without i= is invalid. + @Test + public void validate_arc_chain_fails_when_aar_has_no_instance_tag() throws Exception { + Message message = buildOneHopChainWithAar("smtp.d1.example; arc=none", true, false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // aar_struct_i_empty: an ARC-Authentication-Results header with empty i= is invalid. + @Test + public void validate_arc_chain_fails_when_aar_has_empty_instance_tag() throws Exception { + Message message = buildOneHopChainWithAar("i=; smtp.d1.example; arc=none", true, false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // aar_struct_i_zero: ARC instance numbers start at 1, so AAR i=0 is invalid. + @Test + public void validate_arc_chain_fails_when_aar_has_zero_instance_tag() throws Exception { + Message message = buildOneHopChainWithAar("i=0; smtp.d1.example; arc=none", true, false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // aar_struct_invalid: AAR instance numbers must be numeric. + @Test + public void validate_arc_chain_fails_when_aar_has_non_numeric_instance_tag() throws Exception { + Message message = buildOneHopChainWithAar("i=abc; smtp.d1.example; arc=none", true, false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // aar_struct_dup: duplicate ARC-Authentication-Results headers for the same instance are invalid. + @Test + public void validate_arc_chain_fails_when_aar_is_duplicated_at_same_instance() throws Exception { + Message message = buildOneHopChainWithAar(null, true, true); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // aar_struct_missing / aar_missing: an ARC set with AMS and AS but no AAR is incomplete. + @Test + public void validate_arc_chain_fails_when_aar_header_is_missing() throws Exception { + Message message = buildOneHopChainWithAar(null, false, false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // aar_i_wrong: an AAR whose i= does not match the rest of its ARC set is invalid. + @Test + public void validate_arc_chain_fails_when_aar_instance_does_not_match_arc_set() throws Exception { + Message message = buildOneHopChainWithAar("i=2; smtp.d1.example; arc=none", true, false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // aar_i_not_prefixed: the AAR i= tag must be the leading ARC instance component. + @Test + public void validate_arc_chain_fails_when_aar_instance_tag_is_not_prefixed() throws Exception { + Message message = buildOneHopChainWithAar("smtp.d1.example; i=1; arc=none", true, false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // aar_i_no_semi: the AAR i= value must be followed by a semicolon separator. + @Test + public void validate_arc_chain_fails_when_aar_instance_tag_has_no_semicolon() throws Exception { + Message message = buildOneHopChainWithAar("i=1 smtp.d1.example; arc=none", true, false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // aar2_missing: in a two-hop chain, the latest ARC set is incomplete if its i=2 AAR is missing. + @Test + public void validate_arc_chain_fails_when_i2_aar_header_is_missing() throws Exception { + Message message = buildNHopChain(2); + removeHeaderByInstanceAndType(message, ARC_AUTHENTICATION_RESULTS, "i=2"); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + // ams_struct_missing: an ARC-Seal at i=1 with no corresponding ARC-Message-Signature means the // set is incomplete and must be rejected — covered by validate_arc_chain_fails_when_ams_header_is_missing. @@ -781,6 +882,43 @@ public void validate_arc_chain_remains_fail_after_signing_on_top_of_broken_chain assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); } + // ar_merged1: multiple Authentication-Results headers for the signing authserv-id must be + // consolidated into one ARC-Authentication-Results header, while other authserv-ids are ignored. + @Test + public void build_arc_set_merges_multiple_authentication_results_for_authserv_id() throws Exception { + Message message = parseRawEmail( + "Authentication-Results: lists.example.org; arc=none\n" + + "Authentication-Results: lists.example.org; spf=pass smtp.mfrom=jqd@d1.example\n" + + "Authentication-Results: lists.example.org; dkim=pass (1024-bit key) header.i=@d1.example\n" + + "Authentication-Results: lists.example.org; dmarc=pass\n" + + "Authentication-Results: nobody.example.org; something=ignored\n" + + basicMessageWithoutAuthenticationResults()); + + Map arcSet = buildArcSetWithAuthService(message, "lists.example.org"); + + assertThat(arcSet.get(ARC_AUTHENTICATION_RESULTS)).isEqualTo( + "i=1; lists.example.org; arc=none; spf=pass smtp.mfrom=jqd@d1.example; " + + "dkim=pass (1024-bit key) header.i=@d1.example; dmarc=pass"); + } + + // ar_merged2: folded Authentication-Results payloads must be unfolded and merged in order. + @Test + public void build_arc_set_merges_folded_authentication_results_for_authserv_id() throws Exception { + Message message = parseRawEmail( + "Authentication-Results: lists.example.org; arc=none;\n" + + " spf=pass smtp.mfrom=jqd@d1.example\n" + + "Authentication-Results: lists.example.org; dkim=pass (1024-bit key) header.i=@d1.example\n" + + "Authentication-Results: lists.example.org; dmarc=pass\n" + + "Authentication-Results: nobody.example.org; something=ignored\n" + + basicMessageWithoutAuthenticationResults()); + + Map arcSet = buildArcSetWithAuthService(message, "lists.example.org"); + + assertThat(arcSet.get(ARC_AUTHENTICATION_RESULTS)).isEqualTo( + "i=1; lists.example.org; arc=none; spf=pass smtp.mfrom=jqd@d1.example; " + + "dkim=pass (1024-bit key) header.i=@d1.example; dmarc=pass"); + } + // ams_format_tags_unknown: an unrecognised tag in the ARC-Message-Signature must be silently ignored, // so a chain signed with an extra z= tag must still validate as cv=pass. @Test @@ -972,11 +1110,226 @@ public void validate_arc_chain_passes_when_ams_tag_list_has_trailing_semicolon() + valimailCommonMessageTail()); } + // as_struct_i_na / as_fields_i_missing: an ARC-Seal without i= is invalid. + @Test + public void validate_arc_chain_fails_when_arc_seal_has_no_instance_tag() throws Exception { + Message message = buildOneHopChainWithSeal( + seal -> seal.replaceFirst("i=1;\\s*", ""), + true, + false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // as_struct_i_empty: an ARC-Seal with empty i= is invalid. + @Test + public void validate_arc_chain_fails_when_arc_seal_has_empty_instance_tag() throws Exception { + Message message = buildOneHopChainWithSeal(seal -> seal.replaceFirst("i=1;", "i=;"), true, false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // as_struct_i_zero: ARC instance numbers start at 1, so AS i=0 is invalid. + @Test + public void validate_arc_chain_fails_when_arc_seal_has_zero_instance_tag() throws Exception { + Message message = buildOneHopChainWithSeal(seal -> seal.replaceFirst("i=1;", "i=0;"), true, false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // as_struct_i_invalid: ARC-Seal instance numbers must be numeric. + @Test + public void validate_arc_chain_fails_when_arc_seal_has_non_numeric_instance_tag() throws Exception { + Message message = buildOneHopChainWithSeal(seal -> seal.replaceFirst("i=1;", "i=abc;"), true, false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // as_struct_dup: duplicate ARC-Seal headers for the same instance are invalid. + @Test + public void validate_arc_chain_fails_when_arc_seal_is_duplicated_at_same_instance() throws Exception { + Message message = buildOneHopChainWithSeal(seal -> seal, true, true); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // as_struct_missing: an ARC set with AAR and AMS but no AS is incomplete. + @Test + public void validate_arc_chain_fails_when_arc_seal_header_is_missing_from_arc_set() throws Exception { + Message message = buildOneHopChainWithSeal(seal -> seal, false, false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // as_format_sc_wsp: whitespace before an AS semicolon separator is valid. + @Test + public void validate_arc_chain_passes_when_arc_seal_has_whitespace_around_semicolon_separator() throws Exception { + assertValimailFixturePasses( + "MIME-Version: 1.0\n" + + "Return-Path: \n" + + "ARC-Seal: a=rsa-sha256;\n" + + " b=sQHCWC9A8lAbvcPG+3jfih4lRJY/A0OI/GBGE4AYHf8u9cgsxOvyCqDWF3mr91HE5PhNh4\n" + + " RZW95NC6qhxEhnXLaXswqco2JXMVR6/rM5Q49bDE2RtlNen7wubw56NoJD2A7IGUSOzHaAiJ\n" + + " QhRTSoyG5OwNBC8+GlugUJi5mmZNU=; cv=none; d=example.org; i=1 ; s=dummy;\n" + + " t=12345\n" + + valimailArcSealFormatCommonTail()); + } + + // as_format_eq_wsp: whitespace around "=" in the AS i= tag is valid. + @Test + public void validate_arc_chain_passes_when_arc_seal_has_whitespace_around_equals_separator() throws Exception { + assertValimailFixturePasses( + "MIME-Version: 1.0\n" + + "Return-Path: \n" + + "ARC-Seal: a=rsa-sha256;\n" + + " b=u4XUza5aJKdMCwCMffAieua1x4N9tZpKlx7UwMcdgV+BuIZc48C3rF8xu6BnoRQCaulZmW\n" + + " 4EYspmshC6cGg+kmYaWR/sbW712Ag8W33enEcoh35XLTg9QHg7zWvftk746RrVFb5Ch8iRsU\n" + + " PJ0gkAieomzXwlqCIBZQD5Yz2LB38=; cv=none; d=example.org; i = 1; s=dummy;\n" + + " t=12345\n" + + valimailArcSealFormatCommonTail()); + } + + // as_format_tags_trail_sc: a trailing semicolon at the end of the AS tag list is valid. + @Test + public void validate_arc_chain_passes_when_arc_seal_tag_list_has_trailing_semicolon() throws Exception { + assertValimailFixturePasses( + "MIME-Version: 1.0\n" + + "Return-Path: \n" + + "ARC-Seal: a=rsa-sha256;\n" + + " b=AcBD4PAxYztV5R8jYyYXKuMBWBRja89F6yBTQVtQ1FFUxQVYGOrFlnh3/r8/YtFt13NELg\n" + + " FpYeY3gnzudk30PoZZvM2MG9h07ByTgl0lSEsRLhN+ZtqoHRq1QGdW8oqOXntI51FbKwBdoe\n" + + " cHtLh18GzKAvazRWzv8//vQInYp/Y=; cv=none; d=example.org; i=1; s=dummy;\n" + + " t=12345;\n" + + valimailArcSealFormatCommonTail()); + } + + // as_format_tags_unknown: an unknown AS tag is valid when it was present at signing time. + @Test + public void validate_arc_chain_passes_when_arc_seal_has_unknown_tag() throws Exception { + assertValimailFixturePasses( + "MIME-Version: 1.0\n" + + "Return-Path: \n" + + "ARC-Seal: a=rsa-sha256;\n" + + " b=FriX6cOxgBHhZwNYHn0KXSWVqwHPNV6sRAKUy9iN1OqwvAK9USwMsg/P08yXrUH8LRaijm\n" + + " msJjp0KUFYiffoQrhsxHwv1hJIGceJZB7lOFeZn7Z5aym4eBp7q7idwNyIaGKL7E0WzVkeAT\n" + + " RQ5LhtOInN23gugfmW6z8MUUvow5Y=; cv=none; d=example.org; i=1; s=dummy;\n" + + " t=12345; w=catparty\n" + + valimailArcSealFormatCommonTail()); + } + + // as_format_inv_tag_key: invalid AS tag keys are rejected. + @Test + public void validate_arc_chain_fails_when_arc_seal_has_invalid_tag_key_character() throws Exception { + Message message = buildOneHopChainWithSeal(seal -> seal.replace("t=1755918846", "_=; t=1755918846"), true, false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // as_format_tags_dup: duplicate AS tags are rejected. + @Test + public void validate_arc_chain_fails_when_arc_seal_has_duplicate_tag() throws Exception { + Message message = buildOneHopChainWithSeal(seal -> seal + "; s=invalid", true, false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // as_format_tags_key_case: AS tag keys are case-sensitive. + @Test + public void validate_arc_chain_fails_when_arc_seal_uses_uppercase_tag_key() throws Exception { + Message message = buildOneHopChainWithSeal(seal -> seal.replace("s=arc", "S=arc"), true, false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // as_format_tags_val_case: AS domain value changes are signature-sensitive and must fail. + @Test + public void validate_arc_chain_fails_when_arc_seal_tag_value_has_wrong_case() throws Exception { + Message message = buildOneHopChainWithSeal(seal -> seal.replace("d=dmarc.example", "d=Dmarc.example"), true, false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // as_format_tags_wsp: invalid whitespace inside an AS tag value must fail. + @Test + public void validate_arc_chain_fails_when_arc_seal_tag_value_contains_whitespace() throws Exception { + Message message = buildOneHopChainWithSeal(seal -> seal.replace("t=1755918846", "t=1755 918846"), true, false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // as_format_tags_sc: an extra semicolon inside the AS tag list must fail. + @Test + public void validate_arc_chain_fails_when_arc_seal_tag_value_contains_semicolon() throws Exception { + Message message = buildOneHopChainWithSeal(seal -> seal.replace("s=arc", "s=arc;"), true, false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + // Builds a valid two-hop ARC chain: applies i=1 to the base message, then applies i=2 on top. private Message buildTwoHopChain() throws Exception { return buildNHopChain(2); } + private Message buildOneHopChainWithSeal(java.util.function.Function sealMutation, + boolean includeSeal, + boolean duplicateSeal) throws Exception { + ByteArrayInputStream emailStream = readFileToByteArrayInputStream("/mail/rfc8617_no_arc.eml"); + Message message = new DefaultMessageBuilder().parseMessage(emailStream); + Map arcSet = arcSetBuilder.buildArcSet(message, HELO, MAIL_FROM, IP, keyRecordRetriever); + + message.getHeader().addField(new RawField(ARC_AUTHENTICATION_RESULTS, arcSet.get(ARC_AUTHENTICATION_RESULTS))); + message.getHeader().addField(new RawField(ARC_MESSAGE_SIGNATURE, arcSet.get(ARC_MESSAGE_SIGNATURE))); + if (includeSeal) { + String seal = sealMutation.apply(arcSet.get(ARC_SEAL)); + message.getHeader().addField(new RawField(ARC_SEAL, seal)); + if (duplicateSeal) { + message.getHeader().addField(new RawField(ARC_SEAL, seal)); + } + } + return message; + } + + private Message buildOneHopChainWithAar(String aarOverride, boolean includeAar, boolean duplicateAar) throws Exception { + ByteArrayInputStream emailStream = readFileToByteArrayInputStream("/mail/rfc8617_no_arc.eml"); + Message message = new DefaultMessageBuilder().parseMessage(emailStream); + Map arcSet = arcSetBuilder.buildArcSet(message, HELO, MAIL_FROM, IP, keyRecordRetriever); + + if (includeAar) { + String aar = aarOverride == null ? arcSet.get(ARC_AUTHENTICATION_RESULTS) : aarOverride; + message.getHeader().addField(new RawField(ARC_AUTHENTICATION_RESULTS, aar)); + if (duplicateAar) { + message.getHeader().addField(new RawField(ARC_AUTHENTICATION_RESULTS, aar)); + } + } + message.getHeader().addField(new RawField(ARC_MESSAGE_SIGNATURE, arcSet.get(ARC_MESSAGE_SIGNATURE))); + message.getHeader().addField(new RawField(ARC_SEAL, arcSet.get(ARC_SEAL))); + return message; + } + // Builds a valid N-hop ARC chain by repeatedly applying a new ARC set to the same message. private Message buildNHopChain(int n) throws Exception { ByteArrayInputStream emailStream = readFileToByteArrayInputStream("/mail/rfc8617_no_arc.eml"); @@ -1041,6 +1394,36 @@ private void assertValimailFixturePasses(String rawMessage) throws Exception { assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("pass"); } + private Map buildArcSetWithAuthService(Message message, String authService) throws Exception { + ArcSetBuilder builder = new ArcSetBuilder( + ArcTestKeys.privateKeyArc, + ARC_AMS_TEMPLATE, + ARC_SEAL_TEMPLATE, + authService, + TIMESTAMP); + return builder.buildArcSet(message, HELO, MAIL_FROM, IP, keyRecordRetriever); + } + + private Message parseRawEmail(String rawMessage) throws Exception { + return new DefaultMessageBuilder().parseMessage( + new ByteArrayInputStream(rawMessage.replace("\n", "\r\n").getBytes(StandardCharsets.UTF_8))); + } + + private String basicMessageWithoutAuthenticationResults() { + return "MIME-Version: 1.0\n" + + "Return-Path: \n" + + "Received: by 10.157.14.6 with HTTP; Tue, 3 Jan 2017 12:22:54 -0800 (PST)\n" + + "Message-ID: <54B84785.1060301@d1.example.org>\n" + + "Date: Thu, 14 Jan 2015 15:00:01 -0800\n" + + "From: John Q Doe \n" + + "To: arc@dmarc.org\n" + + "Subject: Example 1\n" + + "\n" + + "Hey gang,\n" + + "This is a test message.\n" + + "--J."; + } + private String valimailCommonMessageTail() { return "ARC-Authentication-Results: i=1; lists.example.org;\n" + " spf=pass smtp.mfrom=jqd@d1.example;\n" @@ -1066,6 +1449,52 @@ private String valimailCommonMessageTail() { + "--J."; } + private String valimailArcSealFormatCommonTail() { + return "ARC-Message-Signature: a=rsa-sha256;\n" + + " b=SMBCg/tHQkIAIzx7OFir0bMhCxk/zaMOx1nyOSAviXW88ERohOFOXIkBVGe74xfJDSh9ou\n" + + " ryKgNA4XhUt4EybBXOn1dlrMA07dDIUFOUE7n+8QsvX1Drii8aBIpiu+O894oBEDSYcd1R+z\n" + + " sZIdXhOjB/Lt4sTE1h5IT2p3UctgY=;\n" + + " bh=dHN66dCNljBC18wb03I1K6hlBvV0qqsKoDsetl+jxb8=; c=relaxed/relaxed;\n" + + " d=example.org; h=from:to:date:subject:mime-version:arc-authentication-results;\n" + + " i=1; s=dummy; t=12345\n" + + "ARC-Authentication-Results: i=1; lists.example.org;\n" + + " spf=pass smtp.mfrom=jqd@d1.example;\n" + + " dkim=pass (1024-bit key) header.i=@d1.example;\n" + + " dmarc=pass\n" + + "Received: from segv.d1.example (segv.d1.example [72.52.75.15])\n" + + " by lists.example.org (8.14.5/8.14.5) with ESMTP id t0EKaNU9010123\n" + + " for ; Thu, 14 Jan 2015 15:01:30 -0800 (PST)\n" + + " (envelope-from jqd@d1.example)\n" + + "Authentication-Results: lists.example.org;\n" + + " spf=pass smtp.mfrom=jqd@d1.example;\n" + + " dkim=pass (1024-bit key) header.i=@d1.example;\n" + + " dmarc=pass\n" + + "MIME-Version: 1.0\n" + + "Return-Path: \n" + + "Received: by 10.157.52.162 with SMTP id g31csp5274520otc;\n" + + " Tue, 3 Jan 2017 12:32:02 -0800 (PST)\n" + + "X-Received: by 10.36.31.84 with SMTP id d81mr49584685itd.26.1483475522271;\n" + + " Tue, 03 Jan 2017 12:32:02 -0800 (PST)\n" + + "Message-ID: \n" + + "Date: Thu, 5 Jan 2017 14:39:01 -0800\n" + + "From: Gene Q Doe \n" + + "To: arc@dmarc.org\n" + + "Subject: Example 2\n" + + "Content-Type: multipart/alternative; boundary=001a113e15fcdd0f9e0545366e8f\n" + + "\n" + + "--001a113e15fcdd0f9e0545366e8f\n" + + "Content-Type: text/plain; charset=UTF-8\n" + + "\n" + + "This is a test message\n" + + "\n" + + "--001a113e15fcdd0f9e0545366e8f\n" + + "Content-Type: text/html; charset=UTF-8\n" + + "\n" + + "
This is a test message
\n" + + "\n" + + "--001a113e15fcdd0f9e0545366e8f--"; + } + private ByteArrayInputStream readFileToByteArrayInputStream(String fileName) throws URISyntaxException, IOException { URL resource = this.getClass().getResource(fileName); FileInputStream file = new FileInputStream(new File(resource.toURI())); From 3ac3c01e4e1f172beaf625016af59f3667665b47 Mon Sep 17 00:00:00 2001 From: Artem Grinchenko Date: Sat, 18 Apr 2026 17:00:33 -0400 Subject: [PATCH 104/114] Add ARC-Seal field validation tests: - Add ValiMail-derived ARC-Seal field coverage for duplicate instance headers and malformed a=, cv=, d=, s=, and t= tags. Cover the valid case where ARC-Seal omits the optional t= timestamp by building a one-hop chain with a custom seal template. - Make ARC-Seal cv= validation null-safe so missing cv= is handled as cv=fail instead of risking a null dereference. --- .../org/apache/james/arc/ARCVerifier.java | 3 + .../java/org/apache/james/arc/ARCTest.java | 191 ++++++++++++++++++ 2 files changed, 194 insertions(+) diff --git a/arc/src/main/java/org/apache/james/arc/ARCVerifier.java b/arc/src/main/java/org/apache/james/arc/ARCVerifier.java index c31cf15..9414378 100644 --- a/arc/src/main/java/org/apache/james/arc/ARCVerifier.java +++ b/arc/src/main/java/org/apache/james/arc/ARCVerifier.java @@ -242,6 +242,9 @@ private boolean checkCv(List lastArcSet, int instToVerify) { if (arcSealHeader.isPresent()) { Map tags = parseTagList(arcSealHeader.get().getBody()); String lastCv = tags.get("cv"); + if (lastCv == null) { + return false; + } return (instToVerify == 1 && lastCv.equalsIgnoreCase("none")) || (instToVerify > 1 && lastCv.equalsIgnoreCase("pass")); } diff --git a/arc/src/test/java/org/apache/james/arc/ARCTest.java b/arc/src/test/java/org/apache/james/arc/ARCTest.java index c0e655f..ea1ef44 100644 --- a/arc/src/test/java/org/apache/james/arc/ARCTest.java +++ b/arc/src/test/java/org/apache/james/arc/ARCTest.java @@ -1289,11 +1289,202 @@ public void validate_arc_chain_fails_when_arc_seal_tag_value_contains_semicolon( assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); } + // as_fields_i_dup: duplicate ARC-Seal headers at i=1 must be rejected. + @Test + public void validate_arc_chain_fails_when_i1_arc_seal_field_is_duplicated() throws Exception { + Message message = buildOneHopChainWithSeal(seal -> seal, true, true); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // as_fields_i_dup2: duplicate ARC-Seal headers at i=2 must be rejected. + @Test + public void validate_arc_chain_fails_when_i2_arc_seal_field_is_duplicated() throws Exception { + Message message = buildNHopChain(2); + Field seal = message.getHeader().getFields().stream() + .filter(f -> f.getName().equalsIgnoreCase(ARC_SEAL) && f.getBody().contains("i=2")) + .findFirst().orElseThrow(() -> new AssertionError("i=2 ARC-Seal not found")); + message.getHeader().addField(new RawField(ARC_SEAL, seal.getBody())); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // as_fields_a_na: missing ARC-Seal a= changes the sealed data and must be rejected. + @Test + public void validate_arc_chain_fails_when_arc_seal_algorithm_tag_is_missing() throws Exception { + Message message = buildOneHopChainWithSeal(seal -> seal.replaceFirst("a=rsa-sha256;\\s*", ""), true, false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // as_fields_a_empty: empty ARC-Seal a= changes the sealed data and must be rejected. + @Test + public void validate_arc_chain_fails_when_arc_seal_algorithm_tag_is_empty() throws Exception { + Message message = buildOneHopChainWithSeal(seal -> seal.replace("a=rsa-sha256", "a="), true, false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // as_fields_a_sha1: changing ARC-Seal a= to rsa-sha1 invalidates the seal. + @Test + public void validate_arc_chain_fails_when_arc_seal_algorithm_is_sha1() throws Exception { + Message message = buildOneHopChainWithSeal(seal -> seal.replace("a=rsa-sha256", "a=rsa-sha1"), true, false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // as_fields_a_unknown: unknown ARC-Seal algorithms must be rejected. + @Test + public void validate_arc_chain_fails_when_arc_seal_algorithm_is_unknown() throws Exception { + Message message = buildOneHopChainWithSeal(seal -> seal.replace("a=rsa-sha256", "a=ed25519-sha256"), true, false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // as_fields_cv_na: missing ARC-Seal cv= must be rejected. + @Test + public void validate_arc_chain_fails_when_arc_seal_cv_tag_is_missing() throws Exception { + Message message = buildOneHopChainWithSeal(seal -> seal.replaceFirst("cv=none;\\s*", ""), true, false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // as_fields_cv_empty: empty ARC-Seal cv= must be rejected. + @Test + public void validate_arc_chain_fails_when_arc_seal_cv_tag_is_empty() throws Exception { + Message message = buildOneHopChainWithSeal(seal -> seal.replace("cv=none", "cv="), true, false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // as_fields_cv_invalid: invalid ARC-Seal cv= values must be rejected. + @Test + public void validate_arc_chain_fails_when_arc_seal_cv_tag_is_invalid() throws Exception { + Message message = buildOneHopChainWithSeal(seal -> seal.replace("cv=none", "cv=maybe"), true, false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // as_fields_d_na: missing ARC-Seal d= prevents key lookup and must be rejected. + @Test + public void validate_arc_chain_fails_when_arc_seal_domain_tag_is_missing() throws Exception { + Message message = buildOneHopChainWithSeal(seal -> seal.replaceFirst("d=dmarc.example;\\s*", ""), true, false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // as_fields_d_empty: empty ARC-Seal d= prevents key lookup and must be rejected. + @Test + public void validate_arc_chain_fails_when_arc_seal_domain_tag_is_empty() throws Exception { + Message message = buildOneHopChainWithSeal(seal -> seal.replace("d=dmarc.example", "d="), true, false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // as_fields_d_invalid: invalid ARC-Seal d= values must be rejected. + @Test + public void validate_arc_chain_fails_when_arc_seal_domain_tag_is_invalid() throws Exception { + Message message = buildOneHopChainWithSeal(seal -> seal.replace("d=dmarc.example", "d=invalid_domain"), true, false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // as_fields_s_na: missing ARC-Seal s= prevents key lookup and must be rejected. + @Test + public void validate_arc_chain_fails_when_arc_seal_selector_tag_is_missing() throws Exception { + Message message = buildOneHopChainWithSeal(seal -> seal.replaceFirst("s=arc;\\s*", ""), true, false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // as_fields_s_empty: empty ARC-Seal s= prevents key lookup and must be rejected. + @Test + public void validate_arc_chain_fails_when_arc_seal_selector_tag_is_empty() throws Exception { + Message message = buildOneHopChainWithSeal(seal -> seal.replace("s=arc", "s="), true, false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // as_fields_t_na: ARC-Seal t= is optional and a seal generated without it must validate. + @Test + public void validate_arc_chain_passes_when_arc_seal_timestamp_tag_is_missing() throws Exception { + String sealTemplateWithoutTimestamp = "i=; cv=; a=rsa-sha256; d=dmarc.example; s=arc; b="; + Message message = buildOneHopChainWithSealTemplate(sealTemplateWithoutTimestamp); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("pass"); + } + + // as_fields_t_empty: empty ARC-Seal t= must be rejected. + @Test + public void validate_arc_chain_fails_when_arc_seal_timestamp_tag_is_empty() throws Exception { + Message message = buildOneHopChainWithSeal(seal -> seal.replace("t=1755918846", "t="), true, false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // as_fields_t_invalid: invalid ARC-Seal t= values must be rejected. + @Test + public void validate_arc_chain_fails_when_arc_seal_timestamp_tag_is_invalid() throws Exception { + Message message = buildOneHopChainWithSeal(seal -> seal.replace("t=1755918846", "t=abc"), true, false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + // Builds a valid two-hop ARC chain: applies i=1 to the base message, then applies i=2 on top. private Message buildTwoHopChain() throws Exception { return buildNHopChain(2); } + private Message buildOneHopChainWithSealTemplate(String sealTemplate) throws Exception { + ByteArrayInputStream emailStream = readFileToByteArrayInputStream("/mail/rfc8617_no_arc.eml"); + Message message = new DefaultMessageBuilder().parseMessage(emailStream); + ArcSetBuilder builder = new ArcSetBuilder( + ArcTestKeys.privateKeyArc, + ARC_AMS_TEMPLATE, + sealTemplate, + AUTH_SERVICE, + TIMESTAMP); + Map arcSet = builder.buildArcSet(message, HELO, MAIL_FROM, IP, keyRecordRetriever); + for (Map.Entry entry : arcSet.entrySet()) { + message.getHeader().addField(new RawField(entry.getKey(), entry.getValue())); + } + return message; + } + private Message buildOneHopChainWithSeal(java.util.function.Function sealMutation, boolean includeSeal, boolean duplicateSeal) throws Exception { From b5fde465057aebac419a5a1efe1b4eb28500a898 Mon Sep 17 00:00:00 2001 From: Artem Grinchenko Date: Sat, 18 Apr 2026 17:10:14 -0400 Subject: [PATCH 105/114] Add ARC-Seal b= validation test coverage: -Add ARCTest cases for ARC-Seal signature handling, including missing, empty, non-base64, modified, and whitespace-folded b= values. Cover seal integrity failures when signed AAR, AMS, or ARC-Seal fields are changed, and reject invalid ARC-Seal h= usage. -Harden ARC-Seal verification so missing or malformed b= values fail ARC validation instead of throwing, and treat invalid signature bytes as a validation failure. --- .../apache/james/arc/ARCChainValidator.java | 7 +- .../org/apache/james/arc/ARCVerifier.java | 2 +- .../java/org/apache/james/arc/ARCTest.java | 106 ++++++++++++++++++ 3 files changed, 113 insertions(+), 2 deletions(-) diff --git a/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java b/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java index 3d03939..09f1edb 100644 --- a/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java +++ b/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java @@ -143,6 +143,9 @@ private boolean checkArcSeal(List headers, int instToVerify, ARCVerifier Signature sig = Signature.getInstance(SHA256_RSA); sig.initVerify(publicKey); sig.update(data.getBytes(StandardCharsets.UTF_8)); + if (b64 == null || b64.isEmpty()) { + return false; + } byte[] signatureBytes = Base64.getDecoder().decode(b64); retVal = sig.verify(signatureBytes); } catch (NoSuchAlgorithmException e) { @@ -150,8 +153,10 @@ private boolean checkArcSeal(List headers, int instToVerify, ARCVerifier } catch (InvalidKeyException e) { throw new ArcException(String.format("Invalid public key used for %s record", txtDnsRecord), e); + } catch (IllegalArgumentException e) { + return false; } catch (SignatureException e) { - throw new ArcException(String.format("Invalid signature for %s record", txtDnsRecord), e); + return false; } return retVal; } diff --git a/arc/src/main/java/org/apache/james/arc/ARCVerifier.java b/arc/src/main/java/org/apache/james/arc/ARCVerifier.java index 9414378..69e1960 100644 --- a/arc/src/main/java/org/apache/james/arc/ARCVerifier.java +++ b/arc/src/main/java/org/apache/james/arc/ARCVerifier.java @@ -355,7 +355,7 @@ public ArcSealVerifyData buildArcSealSigningData(Map> heade Field asField = as.get(); Map tags = parseTagList(asField.getBody()); String signatureB64 = tags.get("b"); - String b64 = signatureB64.replaceAll("\\s+", "").replace(";", ""); + String b64 = signatureB64 == null ? null : signatureB64.replaceAll("\\s+", "").replace(";", ""); String arcSealBodyClearedB = asField.getBody().replaceAll("\\bb=([^;]*)", "b="); signingData.append(asField.getName().toLowerCase(Locale.ROOT)) .append(":").append(canonicalizeBody(arcSealBodyClearedB)); diff --git a/arc/src/test/java/org/apache/james/arc/ARCTest.java b/arc/src/test/java/org/apache/james/arc/ARCTest.java index ea1ef44..550e0ef 100644 --- a/arc/src/test/java/org/apache/james/arc/ARCTest.java +++ b/arc/src/test/java/org/apache/james/arc/ARCTest.java @@ -1353,6 +1353,91 @@ public void validate_arc_chain_fails_when_arc_seal_algorithm_is_unknown() throws assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); } + // as_fields_b_ignores_wsp: whitespace inside ARC-Seal b= must be ignored during base64 decode. + @Test + public void validate_arc_chain_passes_when_arc_seal_signature_contains_whitespace() throws Exception { + Message message = buildOneHopChainWithSeal(this::insertWhitespaceIntoSealSignature, true, false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("pass"); + } + + // as_fields_b_na: missing ARC-Seal b= leaves no signature to verify and must be rejected. + @Test + public void validate_arc_chain_fails_when_arc_seal_signature_tag_is_missing() throws Exception { + Message message = buildOneHopChainWithSeal(seal -> seal.replaceAll("; b=.*$", ""), true, false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // as_fields_b_empty: empty ARC-Seal b= leaves no signature to verify and must be rejected. + @Test + public void validate_arc_chain_fails_when_arc_seal_signature_tag_is_empty() throws Exception { + Message message = buildOneHopChainWithSeal(seal -> seal.replaceAll("; b=.*$", "; b="), true, false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // as_fields_b_base64: ARC-Seal b= must be base64. + @Test + public void validate_arc_chain_fails_when_arc_seal_signature_is_not_base64() throws Exception { + Message message = buildOneHopChainWithSeal(seal -> seal.replaceAll("; b=.*$", "; b=not-base64!"), true, false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // as_fields_b_mod_sig: a modified ARC-Seal signature must be rejected. + @Test + public void validate_arc_chain_fails_when_arc_seal_signature_is_modified() throws Exception { + Message message = buildOneHopChainWithSeal( + seal -> seal.replaceAll("; b=.*$", "; b=" + Base64.getEncoder().encodeToString(new byte[128])), + true, + false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // as_fields_b_aar1: modifying sealed AAR data must invalidate the ARC-Seal. + @Test + public void validate_arc_chain_fails_when_sealed_aar_data_is_modified() throws Exception { + Message message = buildNHopChain(1); + replaceTagOnHeader(message, ARC_AUTHENTICATION_RESULTS, "i=1", "dmarc=pass", "dmarc=fail"); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // as_fields_b_ams1: modifying sealed AMS data must invalidate the ARC-Seal. + @Test + public void validate_arc_chain_fails_when_sealed_ams_data_is_modified() throws Exception { + Message message = buildNHopChain(1); + replaceTagOnHeader(message, ARC_MESSAGE_SIGNATURE, "i=1", "h=subject : from : to", "h=from : to : subject"); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // as_fields_b_asb1: modifying sealed ARC-Seal data outside b= must invalidate the seal. + @Test + public void validate_arc_chain_fails_when_sealed_arc_seal_data_is_modified() throws Exception { + Message message = buildOneHopChainWithSeal(seal -> seal.replace("t=1755918846", "t=1755918847"), true, false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + // as_fields_cv_na: missing ARC-Seal cv= must be rejected. @Test public void validate_arc_chain_fails_when_arc_seal_cv_tag_is_missing() throws Exception { @@ -1413,6 +1498,16 @@ public void validate_arc_chain_fails_when_arc_seal_domain_tag_is_invalid() throw assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); } + // as_fields_h_present: ARC-Seal must not contain h=; it signs ARC set headers, not h=-listed headers. + @Test + public void validate_arc_chain_fails_when_arc_seal_has_header_list_tag() throws Exception { + Message message = buildOneHopChainWithSeal(seal -> seal.replace("t=1755918846", "h=subject; t=1755918846"), true, false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + // as_fields_s_na: missing ARC-Seal s= prevents key lookup and must be rejected. @Test public void validate_arc_chain_fails_when_arc_seal_selector_tag_is_missing() throws Exception { @@ -1504,6 +1599,17 @@ private Message buildOneHopChainWithSeal(java.util.function.Function Date: Sat, 18 Apr 2026 17:17:09 -0400 Subject: [PATCH 106/114] Add the ARC-Seal b= group test cases: - Added ValiMail public key records for 512/1024/2048-bit selector fixtures. - Hardened RSA key validation in ARCVerifier.java so RSA ARC keys below 1024 bits are rejected. - Updated ARCChainValidator.java so invalid/undersized keys fail ARC validation cleanly instead of escaping as exceptions. --- .../apache/james/arc/ARCChainValidator.java | 13 +- .../org/apache/james/arc/ARCVerifier.java | 6 + .../java/org/apache/james/arc/ARCTest.java | 201 +++++++++++++++++- 3 files changed, 217 insertions(+), 3 deletions(-) diff --git a/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java b/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java index 09f1edb..5a2998a 100644 --- a/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java +++ b/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java @@ -114,7 +114,11 @@ private boolean checkArcAms(Set prevArcSet, Message message, ARCVerifier String txtDnsRecord = arcVerifier.getTxtDnsRecordByField(amsHeader); if (txtDnsRecord == null) return retVal; - retVal = arcVerifier.verifyAms(amsHeader, message, txtDnsRecord); + try { + retVal = arcVerifier.verifyAms(amsHeader, message, txtDnsRecord); + } catch (ArcException | IllegalArgumentException e) { + return false; + } return retVal; } @@ -131,7 +135,12 @@ private boolean checkArcSeal(List headers, int instToVerify, ARCVerifier String txtDnsRecord = arcVerifier.getTxtDnsRecordByField(arcSealHeader); if (txtDnsRecord == null) return retVal; - PublicKey publicKey = arcVerifier.parsePublicKeyFromDns(txtDnsRecord); + PublicKey publicKey; + try { + publicKey = arcVerifier.parsePublicKeyFromDns(txtDnsRecord); + } catch (ArcException | IllegalArgumentException e) { + return false; + } if (publicKey == null) { throw new ArcException(String.format("Unable to parse public key from dns record %s", txtDnsRecord)); } diff --git a/arc/src/main/java/org/apache/james/arc/ARCVerifier.java b/arc/src/main/java/org/apache/james/arc/ARCVerifier.java index 69e1960..b5919a5 100644 --- a/arc/src/main/java/org/apache/james/arc/ARCVerifier.java +++ b/arc/src/main/java/org/apache/james/arc/ARCVerifier.java @@ -32,6 +32,7 @@ import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; +import java.security.interfaces.RSAPublicKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; import java.util.ArrayList; @@ -72,6 +73,7 @@ public class ARCVerifier { public static final String ARC_MESSAGE_SIGNATURE = "ARC-Message-Signature"; public static final String ARC_SEAL = "ARC-Seal"; public static final String SHA256RSA = "SHA256withRSA"; + private static final int MIN_RSA_KEY_BITS = 1024; private static final String DNS_RECORD_TYPE = "_domainkey"; private PublicKeyRetrieverArc _keyRecordRetriever; @@ -210,6 +212,10 @@ public PublicKey parsePublicKeyFromDns(String dnsRecord) { } catch (NoSuchAlgorithmException e) { throw new ArcException("Unsupported algorithm provided when getting public key", e); } + if (pubKey instanceof RSAPublicKey + && ((RSAPublicKey) pubKey).getModulus().bitLength() < MIN_RSA_KEY_BITS) { + throw new ArcException("RSA public key must be at least " + MIN_RSA_KEY_BITS + " bits"); + } return pubKey; } diff --git a/arc/src/test/java/org/apache/james/arc/ARCTest.java b/arc/src/test/java/org/apache/james/arc/ARCTest.java index 550e0ef..5edcaa1 100644 --- a/arc/src/test/java/org/apache/james/arc/ARCTest.java +++ b/arc/src/test/java/org/apache/james/arc/ARCTest.java @@ -75,12 +75,34 @@ public class ARCTest { "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDkHlOQoBTzWRiGs5V6NpP3id" + "Y6Wk08a5qhdR6wy5bdOKb2jLQiY/J16JYi0Qvx/byYzCNb3W91y3FutACDfzwQ/BC/e/8uBsCR+yz1Lx" + "j+PL6lHvqMKrM3rG4hstT5QjvHO9PzoxZyVYLzBfO2EeC3Ip3G+2kryOTIKT+l/K4w3QIDAQAB"; + private static final String VALIMAIL_512_PUBLIC_KEY = + "v=DKIM1; k=rsa; p=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAIWmlgix/84GJ+dfgjm7LTc9EPdfk" + + "ftlgiPpCq4/kbDAZmU0VvYKDljjleJ1dfvS+CGy9U/kk1tG3EeEvb82xAcCAwEAAQ=="; + private static final String VALIMAIL_1024_PUBLIC_KEY = + "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCyBwu6PiaDN87t3DVZ84zIrE" + + "hCoxtFuv7g52oCwAUXTDnXZ+0XHM/rhkm8XSGr1yLsDc1zLGX8IfITY1dL2CzptdgyiX7vgYjzZqG368" + + "C8BtGB5m6nj26NyhSKEdlV7MS9KbASd359ggCeGTT5QjRKEMSauVyVSeapq6ZcpZ9JwQIDAQAB"; + private static final String VALIMAIL_2048_PUBLIC_KEY = + "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv+7VkwpTtICeJFM4Hf" + + "UZsvv2OaA+QMrW9Af1PpTOzVP0uvUFK20lcaxMvt81ia/sGYW4gHp/WUIk0BIQMPVhUeCIuM1mcOQNFS" + + "OflR8pLo916rjEZXpRP/XGo4HwWzdqD2qQeb3+fv1IrzfHiDb9THbamoz05EX7JX+wVSAhdSW/igwhA/" + + "+beuzWR0RDDyGMT1b1Sb/lrGfwSXm7QoZQtj5PRiTX+fsL7WlzL+fBThySwS8ZBZcHcd8iWOSGKZ0gYK" + + "zxyuOf8VCX71C4xDhahN+HXWZFn9TZb+uZX9m+WXM3t+P8CdfxsaOdnVg6imgNDlUWX4ClLTZhco0Kmi" + + "BU+QIDAQAB"; private final MockPublicKeyRecordRetrieverArc valimailKeyRecordRetriever = new MockPublicKeyRecordRetrieverArc( dmarcRetriever, MockPublicKeyRecordRetriever.Record.of("dummy", "example.org", VALIMAIL_DUMMY_PUBLIC_KEY) ); + private final MockPublicKeyRecordRetrieverArc valimailKeySizeRecordRetriever = new MockPublicKeyRecordRetrieverArc( + dmarcRetriever, + MockPublicKeyRecordRetriever.Record.of("dummy", "example.org", VALIMAIL_DUMMY_PUBLIC_KEY), + MockPublicKeyRecordRetriever.Record.of("512", "example.org", VALIMAIL_512_PUBLIC_KEY), + MockPublicKeyRecordRetriever.Record.of("1024", "example.org", VALIMAIL_1024_PUBLIC_KEY), + MockPublicKeyRecordRetriever.Record.of("2048", "example.org", VALIMAIL_2048_PUBLIC_KEY) + ); + /** * - "a" field will be added by the signer based on signer setup * - "bh=" and "b=" placeholder are required for now because the same implementation is used for @@ -1363,6 +1385,122 @@ public void validate_arc_chain_passes_when_arc_seal_signature_contains_whitespac assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("pass"); } + // as_fields_b_1024: ARC-Seal signed with a 1024-bit RSA key must validate. + @Test + public void validate_arc_chain_passes_when_arc_seal_uses_1024_bit_key() throws Exception { + assertValimailFixturePasses( + valimailArcSealKeySizeMessage( + "1024", + "JZIhBQD/1SCIn7IUrIoqCDFZ4k2tDd5joLebC7dCEbEXy6HURnayDygFjEiVwoVjF8XZPo\n" + + " tDSWEVj18YLFQ08HZigNNDmhAdtIAeHs5bTfhz3ZDKGISGSrVbUqvS5QaL2dwaY5V3FhH1QC\n" + + " VEohhbx3rJKMBiFCbQoCRo555WNL0=", + "jCTMZoXkSSVEusJyP9cbvAoKEDLphi95R/yaX9+gWw2t/RduqINzxPSVJZUq8uVCbKdB5F\n" + + " BlBb2m7zbwaq6/oemTqI1tcnRaAt66Z0cyOKfPjRINTm9C8E3hUoI9DzplkwEoqmhR0wOjcJ\n" + + " H6ASJr96Kl5qLu092VFaQYYxkwh2I="), + valimailKeySizeRecordRetriever); + } + + // as_fields_b_2048: ARC-Seal signed with a 2048-bit RSA key must validate. + @Test + public void validate_arc_chain_passes_when_arc_seal_uses_2048_bit_key() throws Exception { + assertValimailFixturePasses( + valimailArcSealKeySizeMessage( + "2048", + "R6I8tV4Y0pBQWId+r4W9L3TDi82iVPot9d+ux5u69ET/VUTQUPFAiRfTBqMKAm0dY1HCdU\n" + + " JZggmlvj9BwZMOO9pFi8O1EXqkJ1CpNtFyNn76Get96owYXh7LlcP/C/a5AmxZMmvKblloh5\n" + + " 1rL2cNWicsp8/y3NS8jO0KWpSis2jK2yMn+r9gJ5gM2sUiBsKDwiYAhFBhjD8SFQOaG6DzLa\n" + + " mJzCw9FkuGdpLfQoNDq2lLQq6APq8GihFJai7o/s8M4FItAMoteuqxIfyYuH60oX4qNOsaIT\n" + + " B/6DnRCFshABODpSHRRIH4EvCu2fYYo6YDIU3VvDH2wOO5fQMcgvUoNw==", + "M0YyrXMDoG5zJ0ZjFzUqFNoDFatu/QxWTjyAH5wPvPRiSqw2Vvd4A1Al8VjYfmgbP4Jd8f\n" + + " TFDZg1kWwLYk2IO/th/P6iYPfyDg5qp6mgao/V8NBW9P/Mqlb+xhkn4R8c44vmen9atIUV3Z\n" + + " 04QzziVeuBxj+NFqxprbxf42Faxv5XymGmW3ZWVhOLEpwfcjy933drLsfZQezhyYlx4klptI\n" + + " v3hKM76++GaIUc1nWXvmkeKKjEQLiUzqxd9Om7SRNArNe/q5xnVIaufxSfZNUtTT/o7Ic1Br\n" + + " t7ZV8qwmj37sYpdZUo6H7QN+dp8E/J0jnbI0ZQU2mv8Gj3FqGOGzKwGQ=="), + valimailKeySizeRecordRetriever); + } + + // as_fields_b_512: ARC-Seal signed with a 512-bit RSA key must be rejected. + @Test + public void validate_arc_chain_fails_when_arc_seal_uses_512_bit_key() throws Exception { + assertValimailFixtureFails( + valimailArcSealKeySizeMessage( + "512", + "DCbMvnfI7UzqahO9GFjYXa7DAcon0abOMQ7mWykqtdkEe+rqeQmsy1/pV9oAeSrT9giBqP\n" + + " +cBNepG4Nycj93KQ==", + "BFnboE5xz5OBBIZeB04CaX0QVCRysZesZNKLQLDbq3ohfHL0eIkMWyt/ZkP3+bg7wVEtyb\n" + + " QfqbbfDRTQYC3GBA=="), + valimailKeySizeRecordRetriever); + } + + // as_fields_b_head_case: ARC-Seal relaxed canonicalization lowercases header names. + @Test + public void validate_arc_chain_passes_when_arc_seal_header_name_case_changes() throws Exception { + assertValimailFixturePasses( + "MIME-Version: 1.0\n" + + "Return-Path: \n" + + "ARC-SEAL: a=rsa-sha256;\n" + + " b=RkKDOauVsqcsTEFv6NVE6J0sxj8LUE4kfwRzs0CvMg/+KOqRDQoFxxJsJkI77EHZqcSgwr\n" + + " QKpt6aKsl2zyUovVhAppT65S0+vo+h3utd3f8jph++1uiAUhVf57PihDC/GcdhyRGa6YNQGh\n" + + " GoArSHaJKb06/qF5OBif8o9lmRC8E=; cv=none; d=example.org; i=1; s=dummy;\n" + + " t=12345\n" + + valimailArcSealFormatCommonTail()); + } + + // as_fields_b_head_unfold: folded ARC-Seal header lines must verify under relaxed canonicalization. + @Test + public void validate_arc_chain_passes_when_arc_seal_signature_header_is_unfolded() throws Exception { + assertValimailFixturePasses( + "MIME-Version: 1.0\n" + + "Return-Path: \n" + + "ARC-Seal: a=rsa-sha256;\n" + + " b=RkKDOauVsqcsTEFv6NVE6J0sxj8LUE4kfwRzs0CvMg/+KOqRDQoFxxJsJkI77EHZqcSgwr QKpt6aKsl2zyUovVhAppT65S0+vo+h3utd3f8jph++1uiAUhVf57PihDC/GcdhyRGa6YNQGh\n" + + " GoArSHaJKb06/qF5OBif8o9lmRC8E=; cv=none; d=example.org; i=1; s=dummy;\n" + + " t=12345\n" + + valimailArcSealFormatCommonTail()); + } + + // as_fields_b_eol_wsp: trailing line whitespace must be stripped during AS verification. + @Test + public void validate_arc_chain_passes_when_arc_seal_signature_has_end_of_line_whitespace() throws Exception { + assertValimailFixturePasses( + "MIME-Version: 1.0\n" + + "Return-Path: \n" + + "ARC-Seal: a=rsa-sha256;\n" + + " b=RkKDOauVsqcsTEFv6NVE6J0sxj8LUE4kfwRzs0CvMg/+KOqRDQoFxxJsJkI77EHZqcSgwr \n" + + " QKpt6aKsl2zyUovVhAppT65S0+vo+h3utd3f8jph++1uiAUhVf57PihDC/GcdhyRGa6YNQGh\n" + + " GoArSHaJKb06/qF5OBif8o9lmRC8E=; cv=none; d=example.org; i=1; s=dummy;\n" + + " t=12345\n" + + valimailArcSealFormatCommonTail()); + } + + // as_fields_b_inl_wsp: repeated inline whitespace must be reduced during AS verification. + @Test + public void validate_arc_chain_passes_when_arc_seal_header_has_extra_inline_whitespace() throws Exception { + assertValimailFixturePasses( + "MIME-Version: 1.0\n" + + "Return-Path: \n" + + "ARC-Seal: a=rsa-sha256;\n" + + " b=RkKDOauVsqcsTEFv6NVE6J0sxj8LUE4kfwRzs0CvMg/+KOqRDQoFxxJsJkI77EHZqcSgwr\n" + + " QKpt6aKsl2zyUovVhAppT65S0+vo+h3utd3f8jph++1uiAUhVf57PihDC/GcdhyRGa6YNQGh\n" + + " GoArSHaJKb06/qF5OBif8o9lmRC8E=; cv=none; d=example.org; i=1; s=dummy;\n" + + " t=12345\n" + + valimailArcSealFormatCommonTail()); + } + + // as_fields_b_col_wsp: whitespace around the ARC-Seal field-name colon must be ignored. + @Test + public void validate_arc_chain_passes_when_arc_seal_header_has_whitespace_after_colon() throws Exception { + assertValimailFixturePasses( + "MIME-Version: 1.0\n" + + "Return-Path: \n" + + "ARC-Seal: a=rsa-sha256;\n" + + " b=RkKDOauVsqcsTEFv6NVE6J0sxj8LUE4kfwRzs0CvMg/+KOqRDQoFxxJsJkI77EHZqcSgwr\n" + + " QKpt6aKsl2zyUovVhAppT65S0+vo+h3utd3f8jph++1uiAUhVf57PihDC/GcdhyRGa6YNQGh\n" + + " GoArSHaJKb06/qF5OBif8o9lmRC8E=; cv=none; d=example.org; i=1; s=dummy;\n" + + " t=12345\n" + + valimailArcSealFormatCommonTail()); + } + // as_fields_b_na: missing ARC-Seal b= leaves no signature to verify and must be rejected. @Test public void validate_arc_chain_fails_when_arc_seal_signature_tag_is_missing() throws Exception { @@ -1684,13 +1822,25 @@ private void replaceTagOnHeader(Message message, String headerName, String insta } private void assertValimailFixturePasses(String rawMessage) throws Exception { + assertValimailFixturePasses(rawMessage, valimailKeyRecordRetriever); + } + + private void assertValimailFixturePasses(String rawMessage, MockPublicKeyRecordRetrieverArc publicKeyRetriever) throws Exception { Message message = new DefaultMessageBuilder().parseMessage( new ByteArrayInputStream(rawMessage.replace("\n", "\r\n").getBytes(StandardCharsets.UTF_8))); - ARCChainValidator arcChainValidator = new ARCChainValidator(valimailKeyRecordRetriever); + ARCChainValidator arcChainValidator = new ARCChainValidator(publicKeyRetriever); ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("pass"); } + private void assertValimailFixtureFails(String rawMessage, MockPublicKeyRecordRetrieverArc publicKeyRetriever) throws Exception { + Message message = new DefaultMessageBuilder().parseMessage( + new ByteArrayInputStream(rawMessage.replace("\n", "\r\n").getBytes(StandardCharsets.UTF_8))); + ARCChainValidator arcChainValidator = new ARCChainValidator(publicKeyRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + private Map buildArcSetWithAuthService(Message message, String authService) throws Exception { ArcSetBuilder builder = new ArcSetBuilder( ArcTestKeys.privateKeyArc, @@ -1792,6 +1942,55 @@ private String valimailArcSealFormatCommonTail() { + "--001a113e15fcdd0f9e0545366e8f--"; } + private String valimailArcSealKeySizeMessage(String selector, String arcSealSignature, String arcMessageSignature) { + return "MIME-Version: 1.0\n" + + "Return-Path: \n" + + "ARC-Seal: a=rsa-sha256;\n" + + " b=" + arcSealSignature + "; cv=none; d=example.org; i=1; s=" + selector + ";\n" + + " t=12345\n" + + "ARC-Message-Signature: a=rsa-sha256;\n" + + " b=" + arcMessageSignature + ";\n" + + " bh=dHN66dCNljBC18wb03I1K6hlBvV0qqsKoDsetl+jxb8=; c=relaxed/relaxed;\n" + + " d=example.org; h=from:to:date:subject:mime-version:arc-authentication-results;\n" + + " i=1; s=" + selector + "; t=12345\n" + + "ARC-Authentication-Results: i=1; lists.example.org;\n" + + " spf=pass smtp.mfrom=jqd@d1.example;\n" + + " dkim=pass (1024-bit key) header.i=@d1.example;\n" + + " dmarc=pass\n" + + "Received: from segv.d1.example (segv.d1.example [72.52.75.15])\n" + + " by lists.example.org (8.14.5/8.14.5) with ESMTP id t0EKaNU9010123\n" + + " for ; Thu, 14 Jan 2015 15:01:30 -0800 (PST)\n" + + " (envelope-from jqd@d1.example)\n" + + "Authentication-Results: lists.example.org;\n" + + " spf=pass smtp.mfrom=jqd@d1.example;\n" + + " dkim=pass (1024-bit key) header.i=@d1.example;\n" + + " dmarc=pass\n" + + "MIME-Version: 1.0\n" + + "Return-Path: \n" + + "Received: by 10.157.52.162 with SMTP id g31csp5274520otc;\n" + + " Tue, 3 Jan 2017 12:32:02 -0800 (PST)\n" + + "X-Received: by 10.36.31.84 with SMTP id d81mr49584685itd.26.1483475522271;\n" + + " Tue, 03 Jan 2017 12:32:02 -0800 (PST)\n" + + "Message-ID: \n" + + "Date: Thu, 5 Jan 2017 14:39:01 -0800\n" + + "From: Gene Q Doe \n" + + "To: arc@dmarc.org\n" + + "Subject: Example 2\n" + + "Content-Type: multipart/alternative; boundary=001a113e15fcdd0f9e0545366e8f\n" + + "\n" + + "--001a113e15fcdd0f9e0545366e8f\n" + + "Content-Type: text/plain; charset=UTF-8\n" + + "\n" + + "This is a test message\n" + + "\n" + + "--001a113e15fcdd0f9e0545366e8f\n" + + "Content-Type: text/html; charset=UTF-8\n" + + "\n" + + "
This is a test message
\n" + + "\n" + + "--001a113e15fcdd0f9e0545366e8f--"; + } + private ByteArrayInputStream readFileToByteArrayInputStream(String fileName) throws URISyntaxException, IOException { URL resource = this.getClass().getResource(fileName); FileInputStream file = new FileInputStream(new File(resource.toURI())); From d51933da850c5cba56b3ae77f991938eff6e7fad Mon Sep 17 00:00:00 2001 From: Artem Grinchenko Date: Sat, 18 Apr 2026 17:30:49 -0400 Subject: [PATCH 107/114] Added more AMS field validation coverage: -Add ARC-Message-Signature tests for duplicate instance handling, algorithm validation, signature b= parsing, and relaxed signed-header canonicalization. Cover missing, empty, unsupported, non-base64, and modified AMS signature values, plus ValiMail fixtures for signed header case, folding, whitespace, and tamper failures. -Reject AMS verification when a=rsa-sha256 is absent or b= is missing or empty so malformed signatures fail validation cleanly. --- .../org/apache/james/arc/ARCVerifier.java | 4 + .../java/org/apache/james/arc/ARCTest.java | 325 ++++++++++++++++++ 2 files changed, 329 insertions(+) diff --git a/arc/src/main/java/org/apache/james/arc/ARCVerifier.java b/arc/src/main/java/org/apache/james/arc/ARCVerifier.java index b5919a5..0c0d3b9 100644 --- a/arc/src/main/java/org/apache/james/arc/ARCVerifier.java +++ b/arc/src/main/java/org/apache/james/arc/ARCVerifier.java @@ -86,9 +86,13 @@ public boolean verifyAms(Field amsField, Message message, String publicKeyDnsRec String amsValue = amsField.getBody(); Map tags = parseTagList(amsValue); + String algorithm = tags.get("a"); String signedHeaders = tags.get("h"); String bodyHash = tags.get("bh"); String signatureB64 = tags.get("b"); + if (!"rsa-sha256".equals(algorithm) || signatureB64 == null || signatureB64.isEmpty()) { + return false; + } String b64 = signatureB64 .replaceAll("\\s+", "") // remove spaces, tabs, newlines .replace(";", ""); // defensive: strip trailing semicolon if present diff --git a/arc/src/test/java/org/apache/james/arc/ARCTest.java b/arc/src/test/java/org/apache/james/arc/ARCTest.java index 5edcaa1..953d2f9 100644 --- a/arc/src/test/java/org/apache/james/arc/ARCTest.java +++ b/arc/src/test/java/org/apache/james/arc/ARCTest.java @@ -604,6 +604,289 @@ public void validate_arc_chain_fails_when_duplicate_ams_instance_appears_before_ assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); } + // ams_fields_i_dup2: duplicate ARC-Message-Signature instance numbers must be rejected when + // the duplicate appears after the existing AMS header. + @Test + public void validate_arc_chain_fails_when_duplicate_ams_instance_appears_after_arc_set() throws Exception { + Message message = buildOneHopChainWithAms(ams -> ams, true); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // ams_fields_a_na: AMS without a= does not declare a supported signature algorithm. + @Test + public void validate_arc_chain_fails_when_ams_algorithm_tag_is_missing() throws Exception { + Message message = buildOneHopChainWithAms(ams -> ams.replaceFirst("a=rsa-sha256;\\s*", ""), false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // ams_fields_a_empty: AMS with an empty a= does not declare a supported signature algorithm. + @Test + public void validate_arc_chain_fails_when_ams_algorithm_tag_is_empty() throws Exception { + Message message = buildOneHopChainWithAms(ams -> ams.replace("a=rsa-sha256", "a="), false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // ams_fields_a_sha1: rsa-sha1 is not supported for AMS verification. + @Test + public void validate_arc_chain_fails_when_ams_algorithm_is_sha1() throws Exception { + Message message = buildOneHopChainWithAms(ams -> ams.replace("a=rsa-sha256", "a=rsa-sha1"), false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // ams_fields_a_unknown: unknown AMS signature algorithms must be rejected. + @Test + public void validate_arc_chain_fails_when_ams_algorithm_is_unknown() throws Exception { + Message message = buildOneHopChainWithAms(ams -> ams.replace("a=rsa-sha256", "a=ed25519-sha256"), false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // ams_fields_b_ignores_wsp: whitespace inside AMS b= must be ignored during base64 decode. + @Test + public void validate_arc_chain_passes_when_ams_signature_contains_whitespace() throws Exception { + assertValimailFixturePasses( + "MIME-Version: 1.0\n" + + "Return-Path: \n" + + "ARC-Seal: a=rsa-sha256;\n" + + " b=L8GsQ6v/7miEWKMGu16QVCPF6IT8j9+DV/ZHzgm86gi5m2JYAq+BlkmiIDofRPW+QzAq85\n" + + " 2UlxwI2NZrhyAKgtM4FKO7+84P1eYwJKh57DZfCyUpqRx1Je2+vzT8ZggXQWYjFEu36MTDFX\n" + + " fRKVqPV3omyP+CFBzjJFFDLehJaPk=; cv=none; d=example.org; i=1; s=dummy;\n" + + " t=12345\n" + + "ARC-Message-Signature: a=rsa-sha256;\n" + + " b=QsRzR /UqwRfVLBc1TnoQomlVw5qi6jp08q8lHpBSl4RehWyHQtY3uOIAGdghDk/mO+/Xpm\n" + + " 9JA5UVrPyDV0f+2q/YAHuwvP11iCkBQkocmFvgTSxN8H+DwFFPrVVUudQYZV7UDDycXoM6UE\n" + + " cdfzLLzVNPOAHEDIi/uzoV4sUqZ18=;\n" + + " bh=KWSe46TZKCcDbH4klJPo+tjk5LWJnVRlP5pvjXFZYLQ=; c=relaxed/relaxed;\n" + + " d=example.org; h=from:to:date:subject:mime-version:arc-authentication-results;\n" + + " i=1; s=dummy; t=12345\n" + + valimailCommonMessageTail()); + } + + // ams_fields_b_na: missing AMS b= leaves no message signature to verify and must be rejected. + @Test + public void validate_arc_chain_fails_when_ams_signature_tag_is_missing() throws Exception { + Message message = buildOneHopChainWithAms(ams -> ams.replaceAll("; b=.*$", ""), false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // ams_fields_b_empty: empty AMS b= leaves no message signature to verify and must be rejected. + @Test + public void validate_arc_chain_fails_when_ams_signature_tag_is_empty() throws Exception { + Message message = buildOneHopChainWithAms(ams -> ams.replaceAll("; b=.*$", "; b="), false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // ams_fields_b_base64: AMS b= must be base64. + @Test + public void validate_arc_chain_fails_when_ams_signature_is_not_base64() throws Exception { + Message message = buildOneHopChainWithAms(ams -> ams.replaceAll("; b=.*$", "; b=not-base64!"), false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // ams_fields_b_mod_sig: a modified AMS signature must be rejected. + @Test + public void validate_arc_chain_fails_when_ams_signature_is_modified() throws Exception { + Message message = buildOneHopChainWithAms( + ams -> ams.replaceAll("; b=.*$", "; b=" + Base64.getEncoder().encodeToString(new byte[128])), + false); + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // ams_fields_b_head_case: AMS relaxed canonicalization lowercases signed header names. + @Test + public void validate_arc_chain_passes_when_signed_header_name_case_changes() throws Exception { + assertValimailFixturePasses(valimailAmsCanonicalizationMessage( + "Received: from segv.d1.example (segv.d1.example [72.52.75.15])\n" + + " by lists.example.org (8.14.5/8.14.5) with ESMTP id t0EKaNU9010123\n" + + " for ; Thu, 14 Jan 2015 15:01:30 -0800 (PST)\n" + + " (envelope-from jqd@d1.example)\n" + + "Authentication-Results: lists.example.org;\n" + + " spf=pass smtp.mfrom=jqd@d1.example;\n" + + " dkim=pass (1024-bit key) header.i=@d1.example;\n" + + " dmarc=pass\n" + + "Received: by 10.157.14.6 with HTTP; Tue, 3 Jan 2017 12:22:54 -0800 (PST)\n" + + "Message-ID: <54B84785.1060301@d1.example.org>\n" + + "Date: Thu, 14 Jan 2015 15:00:01 -0800\n" + + "FROM: John Q Doe \n" + + "To: arc@dmarc.org\n" + + "Subject: Example 1\n" + + "\n" + + "Hey gang,\n" + + "This is a test message.\n" + + "--J.")); + } + + // ams_fields_b_head_unfold: folded signed headers must verify under relaxed canonicalization. + @Test + public void validate_arc_chain_passes_when_signed_header_is_folded() throws Exception { + assertValimailFixturePasses(valimailAmsCanonicalizationMessage( + "Received: from segv.d1.example (segv.d1.example [72.52.75.15])\n" + + " by lists.example.org (8.14.5/8.14.5) with ESMTP id t0EKaNU9010123\n" + + " for ; Thu, 14 Jan 2015 15:01:30 -0800 (PST)\n" + + " (envelope-from jqd@d1.example)\n" + + "Authentication-Results: lists.example.org;\n" + + " spf=pass smtp.mfrom=jqd@d1.example;\n" + + " dkim=pass (1024-bit key) header.i=@d1.example;\n" + + " dmarc=pass\n" + + "Received: by 10.157.14.6 with HTTP; Tue, 3 Jan 2017 12:22:54 -0800 (PST)\n" + + "Message-ID: <54B84785.1060301@d1.example.org>\n" + + "Date: Thu, 14 Jan 2015 15:00:01 -0800\n" + + "From: John Q Doe\n" + + " \n" + + "To: arc@dmarc.org\n" + + "Subject: Example 1\n" + + "\n" + + "Hey gang,\n" + + "This is a test message.\n" + + "--J.")); + } + + // ams_fields_b_eol_wsp: signed-header line-end whitespace must be stripped. + @Test + public void validate_arc_chain_passes_when_signed_header_has_end_of_line_whitespace() throws Exception { + assertValimailFixturePasses(valimailAmsCanonicalizationMessage( + "Received: from segv.d1.example (segv.d1.example [72.52.75.15])\n" + + " by lists.example.org (8.14.5/8.14.5) with ESMTP id t0EKaNU9010123\n" + + " for ; Thu, 14 Jan 2015 15:01:30 -0800 (PST)\n" + + " (envelope-from jqd@d1.example)\n" + + "Authentication-Results: lists.example.org;\n" + + " spf=pass smtp.mfrom=jqd@d1.example;\n" + + " dkim=pass (1024-bit key) header.i=@d1.example;\n" + + " dmarc=pass\n" + + "Received: by 10.157.14.6 with HTTP; Tue, 3 Jan 2017 12:22:54 -0800 (PST)\n" + + "Message-ID: <54B84785.1060301@d1.example.org>\n" + + "Date: Thu, 14 Jan 2015 15:00:01 -0800\n" + + "From: John Q Doe \n" + + "To: arc@dmarc.org\n" + + "Subject: Example 1\n" + + "\n" + + "Hey gang,\n" + + "This is a test message.\n" + + "--J.")); + } + + // ams_fields_b_inl_wsp: repeated inline whitespace in signed headers must be reduced. + @Test + public void validate_arc_chain_passes_when_signed_header_has_extra_inline_whitespace() throws Exception { + assertValimailFixturePasses(valimailAmsCanonicalizationMessage( + "Received: from segv.d1.example (segv.d1.example [72.52.75.15])\n" + + " by lists.example.org (8.14.5/8.14.5) with ESMTP id t0EKaNU9010123\n" + + " for ; Thu, 14 Jan 2015 15:01:30 -0800 (PST)\n" + + " (envelope-from jqd@d1.example)\n" + + "Authentication-Results: lists.example.org;\n" + + " spf=pass smtp.mfrom=jqd@d1.example;\n" + + " dkim=pass (1024-bit key) header.i=@d1.example;\n" + + " dmarc=pass\n" + + "Received: by 10.157.14.6 with HTTP; Tue, 3 Jan 2017 12:22:54 -0800 (PST)\n" + + "Message-ID: <54B84785.1060301@d1.example.org>\n" + + "Date: Thu, 14 Jan 2015 15:00:01 -0800\n" + + "From: John Q Doe \n" + + "To: arc@dmarc.org\n" + + "Subject: Example 1\n" + + "\n" + + "Hey gang,\n" + + "This is a test message.\n" + + "--J.")); + } + + // ams_fields_b_col_wsp: whitespace after signed-header colons must be stripped. + @Test + public void validate_arc_chain_passes_when_signed_headers_have_colon_whitespace() throws Exception { + assertValimailFixturePasses(valimailAmsCanonicalizationMessage( + "Received: from segv.d1.example (segv.d1.example [72.52.75.15])\n" + + " by lists.example.org (8.14.5/8.14.5) with ESMTP id t0EKaNU9010123\n" + + " for ; Thu, 14 Jan 2015 15:01:30 -0800 (PST)\n" + + " (envelope-from jqd@d1.example)\n" + + "Authentication-Results: lists.example.org;\n" + + " spf=pass smtp.mfrom=jqd@d1.example;\n" + + " dkim=pass (1024-bit key) header.i=@d1.example;\n" + + " dmarc=pass\n" + + "Received: by 10.157.14.6 with HTTP; Tue, 3 Jan 2017 12:22:54 -0800 (PST)\n" + + "Message-ID: <54B84785.1060301@d1.example.org>\n" + + "Date: Thu, 14 Jan 2015 15:00:01 -0800\n" + + "From: John Q Doe \n" + + "To: arc@dmarc.org\n" + + "Subject: Example 1\n" + + "\n" + + "Hey gang,\n" + + "This is a test message.\n" + + "--J.")); + } + + // ams_fields_b_mod_headers1: modifying a signed From header must invalidate AMS. + @Test + public void validate_arc_chain_fails_when_signed_from_header_is_modified() throws Exception { + assertValimailFixtureFails(valimailAmsCanonicalizationMessage( + "Received: from segv.d1.example (segv.d1.example [72.52.75.15])\n" + + " by lists.example.org (8.14.5/8.14.5) with ESMTP id t0EKaNU9010123\n" + + " for ; Thu, 14 Jan 2015 15:01:30 -0800 (PST)\n" + + " (envelope-from jqd@d1.example)\n" + + "Authentication-Results: lists.example.org;\n" + + " spf=pass smtp.mfrom=jqd@d1.example;\n" + + " dkim=pass (1024-bit key) header.i=@d1.example;\n" + + " dmarc=pass\n" + + "Received: by 10.157.14.6 with HTTP; Tue, 3 Jan 2017 12:22:54 -0800 (PST)\n" + + "Message-ID: <54B84785.1060301@d1.example.org>\n" + + "Date: Thu, 14 Jan 2015 15:00:01 -0800\n" + + "From: Q Doe \n" + + "To: arc@dmarc.org\n" + + "Subject: Example 1\n" + + "\n" + + "Hey gang,\n" + + "This is a test message.\n" + + "--J.")); + } + + // ams_fields_b_mod_headers2: modifying a signed Subject header must invalidate AMS. + @Test + public void validate_arc_chain_fails_when_signed_subject_header_is_modified() throws Exception { + assertValimailFixtureFails(valimailAmsCanonicalizationMessage( + "Received: from segv.d1.example (segv.d1.example [72.52.75.15])\n" + + " by lists.example.org (8.14.5/8.14.5) with ESMTP id t0EKaNU9010123\n" + + " for ; Thu, 14 Jan 2015 15:01:30 -0800 (PST)\n" + + " (envelope-from jqd@d1.example)\n" + + "Authentication-Results: lists.example.org;\n" + + " spf=pass smtp.mfrom=jqd@d1.example;\n" + + " dkim=pass (1024-bit key) header.i=@d1.example;\n" + + " dmarc=pass\n" + + "Received: by 10.157.14.6 with HTTP; Tue, 3 Jan 2017 12:22:54 -0800 (PST)\n" + + "Message-ID: <54B84785.1060301@d1.example.org>\n" + + "Date: Thu, 14 Jan 2015 15:00:01 -0800\n" + + "From: John Q Doe \n" + + "To: arc@dmarc.org\n" + + "Subject: Example 1 (Mod)\n" + + "\n" + + "Hey gang,\n" + + "This is a test message.\n" + + "--J.")); + } + // aar_struct_i_na / aar_i_missing: an ARC-Authentication-Results header without i= is invalid. @Test public void validate_arc_chain_fails_when_aar_has_no_instance_tag() throws Exception { @@ -1737,6 +2020,22 @@ private Message buildOneHopChainWithSeal(java.util.function.Function amsMutation, + boolean duplicateAms) throws Exception { + ByteArrayInputStream emailStream = readFileToByteArrayInputStream("/mail/rfc8617_no_arc.eml"); + Message message = new DefaultMessageBuilder().parseMessage(emailStream); + Map arcSet = arcSetBuilder.buildArcSet(message, HELO, MAIL_FROM, IP, keyRecordRetriever); + + message.getHeader().addField(new RawField(ARC_AUTHENTICATION_RESULTS, arcSet.get(ARC_AUTHENTICATION_RESULTS))); + String ams = amsMutation.apply(arcSet.get(ARC_MESSAGE_SIGNATURE)); + message.getHeader().addField(new RawField(ARC_MESSAGE_SIGNATURE, ams)); + if (duplicateAms) { + message.getHeader().addField(new RawField(ARC_MESSAGE_SIGNATURE, ams)); + } + message.getHeader().addField(new RawField(ARC_SEAL, arcSet.get(ARC_SEAL))); + return message; + } + private String insertWhitespaceIntoSealSignature(String seal) { java.util.regex.Matcher matcher = java.util.regex.Pattern.compile("; b=([^;]+)$").matcher(seal); if (!matcher.find()) { @@ -1841,6 +2140,10 @@ private void assertValimailFixtureFails(String rawMessage, MockPublicKeyRecordRe assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); } + private void assertValimailFixtureFails(String rawMessage) throws Exception { + assertValimailFixtureFails(rawMessage, valimailKeyRecordRetriever); + } + private Map buildArcSetWithAuthService(Message message, String authService) throws Exception { ArcSetBuilder builder = new ArcSetBuilder( ArcTestKeys.privateKeyArc, @@ -1896,6 +2199,28 @@ private String valimailCommonMessageTail() { + "--J."; } + private String valimailAmsCanonicalizationMessage(String signedMessageTail) { + return "MIME-Version: 1.0\n" + + "Return-Path: \n" + + "ARC-Seal: a=rsa-sha256;\n" + + " b=dOdFEyhrk/tw5wl3vMIogoxhaVsKJkrkEhnAcq2XqOLSQhPpGzhGBJzR7k1sWGokon3TmQ\n" + + " 7TX9zQLO6ikRpwd/pUswiRW5DBupy58fefuclXJAhErsrebfvfiueGyhHXV7C1LyJTztywzn\n" + + " QGG4SCciU/FTlsJ0QANrnLRoadfps=; cv=none; d=example.org; i=1; s=dummy;\n" + + " t=12345\n" + + "ARC-Message-Signature: a=rsa-sha256;\n" + + " b=QsRzR/UqwRfVLBc1TnoQomlVw5qi6jp08q8lHpBSl4RehWyHQtY3uOIAGdghDk/mO+/Xpm\n" + + " 9JA5UVrPyDV0f+2q/YAHuwvP11iCkBQkocmFvgTSxN8H+DwFFPrVVUudQYZV7UDDycXoM6UE\n" + + " cdfzLLzVNPOAHEDIi/uzoV4sUqZ18=;\n" + + " bh=KWSe46TZKCcDbH4klJPo+tjk5LWJnVRlP5pvjXFZYLQ=; c=relaxed/relaxed;\n" + + " d=example.org; h=from:to:date:subject:mime-version:arc-authentication-results;\n" + + " i=1; s=dummy; t=12345\n" + + "ARC-Authentication-Results: i=1; lists.example.org;\n" + + " spf=pass smtp.mfrom=jqd@d1.example;\n" + + " dkim=pass (1024-bit key) header.i=@d1.example;\n" + + " dmarc=pass\n" + + signedMessageTail; + } + private String valimailArcSealFormatCommonTail() { return "ARC-Message-Signature: a=rsa-sha256;\n" + " b=SMBCg/tHQkIAIzx7OFir0bMhCxk/zaMOx1nyOSAviXW88ERohOFOXIkBVGe74xfJDSh9ou\n" From 72c8fe1ca89b2f36810013876b475e679d85b3b9 Mon Sep 17 00:00:00 2001 From: Artem Grinchenko Date: Sat, 18 Apr 2026 17:50:51 -0400 Subject: [PATCH 108/114] Add ARC body hash and key fixture coverage: - Add ValiMail ARC validation coverage for no-chain base messages, single-hop pass fixtures, missing and malformed ARC public keys, and AMS/AS selector-domain independence. - Add AMS bh= validation coverage for whitespace-tolerant body hashes, simple and relaxed body canonicalization, missing/empty/non-base64 body hash tags, modified body hashes, and modified message bodies. - Verify AMS body hashes before accepting ARC-Message-Signature by decoding bh=, applying the AMS body canonicalization mode, and comparing the computed SHA-256 digest. Reject malformed or unsupported body hash inputs cleanly while preserving existing multipart fixture behavior when the Mime4j DOM parser cannot reconstruct a raw multipart body. --- .../org/apache/james/arc/ARCVerifier.java | 143 ++++++- .../java/org/apache/james/arc/ARCTest.java | 375 +++++++++++++++++- 2 files changed, 510 insertions(+), 8 deletions(-) diff --git a/arc/src/main/java/org/apache/james/arc/ARCVerifier.java b/arc/src/main/java/org/apache/james/arc/ARCVerifier.java index 0c0d3b9..5b97fd6 100644 --- a/arc/src/main/java/org/apache/james/arc/ARCVerifier.java +++ b/arc/src/main/java/org/apache/james/arc/ARCVerifier.java @@ -21,13 +21,23 @@ import org.apache.james.arc.exceptions.ArcException; import org.apache.james.jdkim.exceptions.PermFailException; import org.apache.james.jdkim.exceptions.TempFailException; +import org.apache.james.mime4j.dom.Body; +import org.apache.james.mime4j.dom.Entity; import org.apache.james.mime4j.dom.Header; import org.apache.james.mime4j.dom.Message; +import org.apache.james.mime4j.dom.Multipart; +import org.apache.james.mime4j.dom.SingleBody; +import org.apache.james.mime4j.io.EOLConvertingInputStream; +import org.apache.james.mime4j.stream.NameValuePair; import org.apache.james.mime4j.stream.Field; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.KeyFactory; +import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.Signature; @@ -36,6 +46,7 @@ import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; import java.util.ArrayList; +import java.util.Arrays; import java.util.Base64; import java.util.HashMap; import java.util.HashSet; @@ -90,16 +101,20 @@ public boolean verifyAms(Field amsField, Message message, String publicKeyDnsRec String signedHeaders = tags.get("h"); String bodyHash = tags.get("bh"); String signatureB64 = tags.get("b"); - if (!"rsa-sha256".equals(algorithm) || signatureB64 == null || signatureB64.isEmpty()) { + if (!"rsa-sha256".equals(algorithm) || bodyHash == null || bodyHash.isEmpty() + || signatureB64 == null || signatureB64.isEmpty()) { return false; } String b64 = signatureB64 .replaceAll("\\s+", "") // remove spaces, tabs, newlines .replace(";", ""); // defensive: strip trailing semicolon if present - if (signedHeaders == null || bodyHash == null) { + if (signedHeaders == null) { throw new ArcException("AMS missing required tags"); } + if (!verifyAmsBodyHash(tags, message)) { + return false; + } String amsForSigning = amsValue.replaceFirst(B_TAG_REGEX, "b="); // Canonicalize headers listed in h= @@ -145,6 +160,130 @@ public boolean verifyAms(Field amsField, Message message, String publicKeyDnsRec return result; } + private boolean verifyAmsBodyHash(Map tags, Message message) { + String bodyHash = tags.get("bh"); + String bodyCanonicalization = getBodyCanonicalization(tags.get("c")); + if (bodyCanonicalization == null) { + return false; + } + + byte[] expectedBodyHash; + try { + expectedBodyHash = Base64.getDecoder().decode(bodyHash.replaceAll("\\s+", "")); + } catch (IllegalArgumentException e) { + return false; + } + + byte[] computedBodyHash; + try { + if (message.getBody() == null) { + return true; + } + MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); + byte[] bodyBytes = readBodyBytes(message.getBody()); + if (bodyBytes.length == 0 && message.getBody() instanceof Multipart) { + return true; + } + byte[] canonicalizedBody = "relaxed".equals(bodyCanonicalization) + ? canonicalizeRelaxedBody(bodyBytes) + : canonicalizeSimpleBody(bodyBytes); + computedBodyHash = messageDigest.digest(canonicalizedBody); + } catch (IOException | NoSuchAlgorithmException e) { + return false; + } + return Arrays.equals(expectedBodyHash, computedBodyHash); + } + + private byte[] readBodyBytes(Body body) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + writeBody(body, out); + return out.toByteArray(); + } + + private void writeBody(Body body, ByteArrayOutputStream out) throws IOException { + if (body instanceof SingleBody) { + copy(new EOLConvertingInputStream(((SingleBody) body).getInputStream()), out); + } else if (body instanceof Multipart) { + writeMultipart((Multipart) body, out); + } + } + + private void writeMultipart(Multipart multipart, ByteArrayOutputStream out) throws IOException { + String boundary = getBoundary(multipart); + if (boundary == null) { + return; + } + for (Entity part : multipart.getBodyParts()) { + out.write(("--" + boundary + "\r\n").getBytes(StandardCharsets.UTF_8)); + for (Field field : part.getHeader().getFields()) { + out.write((field.getName() + ": " + field.getBody() + "\r\n").getBytes(StandardCharsets.UTF_8)); + } + out.write("\r\n".getBytes(StandardCharsets.UTF_8)); + writeBody(part.getBody(), out); + out.write("\r\n".getBytes(StandardCharsets.UTF_8)); + } + out.write(("--" + boundary + "--").getBytes(StandardCharsets.UTF_8)); + } + + private String getBoundary(Multipart multipart) { + for (NameValuePair parameter : multipart.getContentTypeParameters()) { + if ("boundary".equalsIgnoreCase(parameter.getName())) { + return parameter.getValue(); + } + } + return null; + } + + private void copy(InputStream inputStream, ByteArrayOutputStream out) throws IOException { + try (InputStream in = inputStream) { + byte[] buffer = new byte[2048]; + int read; + while ((read = in.read(buffer)) > 0) { + out.write(buffer, 0, read); + } + } + } + + private byte[] canonicalizeSimpleBody(byte[] body) { + String normalized = new String(body, StandardCharsets.UTF_8).replaceAll("(?\n" + + "Received: by 10.157.52.162 with SMTP id g31csp5274520otc;\n" + + " Tue, 3 Jan 2017 12:32:02 -0800 (PST)\n" + + "X-Received: by 10.36.31.84 with SMTP id d81mr49584685itd.26.1483475522271;\n" + + " Tue, 03 Jan 2017 12:32:02 -0800 (PST)\n" + + "Message-ID: \n" + + "Date: Tue, 3 Jan 2017 12:31:41 -080\n" + + "From: John Q Doe \n" + + "To: arc@dmarc.org\n" + + "Subject: Example 2\n" + + "Content-Type: multipart/alternative; boundary=001a113e15fcdd0f9e0545366e8f\n" + + "\n" + + "--001a113e15fcdd0f9e0545366e8f\n" + + "Content-Type: text/plain; charset=UTF-8\n" + + "\n" + + "This is a test message\n" + + "\n" + + "--001a113e15fcdd0f9e0545366e8f\n" + + "Content-Type: text/html; charset=UTF-8\n" + + "\n" + + "
This is a test message
\n" + + "\n" + + "--001a113e15fcdd0f9e0545366e8f--"; + } + + private String baseMessageOneSignedTail() { + return baseMessageOneSignedHeaders() + baseMessageOneBody(); + } + + private String baseMessageOneSignedHeaders() { + return "Received: from segv.d1.example (segv.d1.example [72.52.75.15])\n" + + " by lists.example.org (8.14.5/8.14.5) with ESMTP id t0EKaNU9010123\n" + + " for ; Thu, 14 Jan 2015 15:01:30 -0800 (PST)\n" + + " (envelope-from jqd@d1.example)\n" + + "Authentication-Results: lists.example.org;\n" + + " spf=pass smtp.mfrom=jqd@d1.example;\n" + + " dkim=pass (1024-bit key) header.i=@d1.example;\n" + + " dmarc=pass\n" + + "Received: by 10.157.14.6 with HTTP; Tue, 3 Jan 2017 12:22:54 -0800 (PST)\n" + + "Message-ID: <54B84785.1060301@d1.example.org>\n" + + "Date: Thu, 14 Jan 2015 15:00:01 -0800\n" + + "From: John Q Doe \n" + + "To: arc@dmarc.org\n" + + "Subject: Example 1\n" + + "\n"; + } + + private String baseMessageOneBody() { + return "Hey gang,\n" + + "This is a test message.\n" + + "--J."; + } + private String valimailCommonMessageTail() { return "ARC-Authentication-Results: i=1; lists.example.org;\n" + " spf=pass smtp.mfrom=jqd@d1.example;\n" @@ -2199,6 +2438,130 @@ private String valimailCommonMessageTail() { + "--J."; } + private String valimailPublicKeyMessage(String arcSealDomain, String arcSealSelector, String arcSealSignature) { + return "MIME-Version: 1.0\n" + + "Return-Path: \n" + + "ARC-Seal: a=rsa-sha256;\n" + + " b=" + arcSealSignature + "; cv=none; d=" + arcSealDomain + "; i=1; s=" + arcSealSelector + ";\n" + + " t=12345\n" + + "ARC-Message-Signature: a=rsa-sha256;\n" + + " b=QsRzR/UqwRfVLBc1TnoQomlVw5qi6jp08q8lHpBSl4RehWyHQtY3uOIAGdghDk/mO+/Xpm\n" + + " 9JA5UVrPyDV0f+2q/YAHuwvP11iCkBQkocmFvgTSxN8H+DwFFPrVVUudQYZV7UDDycXoM6UE\n" + + " cdfzLLzVNPOAHEDIi/uzoV4sUqZ18=;\n" + + " bh=KWSe46TZKCcDbH4klJPo+tjk5LWJnVRlP5pvjXFZYLQ=; c=relaxed/relaxed;\n" + + " d=example.org; h=from:to:date:subject:mime-version:arc-authentication-results;\n" + + " i=1; s=dummy; t=12345\n" + + "ARC-Authentication-Results: i=1; lists.example.org;\n" + + " spf=pass smtp.mfrom=jqd@d1.example;\n" + + " dkim=pass (1024-bit key) header.i=@d1.example;\n" + + " dmarc=pass\n" + + baseMessageOneSignedTail(); + } + + private String valimailSingleHopBaseTwoMessage() { + return "MIME-Version: 1.0\n" + + "Return-Path: \n" + + "ARC-Seal: a=rsa-sha256;\n" + + " b=RkKDOauVsqcsTEFv6NVE6J0sxj8LUE4kfwRzs0CvMg/+KOqRDQoFxxJsJkI77EHZqcSgwr\n" + + " QKpt6aKsl2zyUovVhAppT65S0+vo+h3utd3f8jph++1uiAUhVf57PihDC/GcdhyRGa6YNQGh\n" + + " GoArSHaJKb06/qF5OBif8o9lmRC8E=; cv=none; d=example.org; i=1; s=dummy;\n" + + " t=12345\n" + + "ARC-Message-Signature: a=rsa-sha256;\n" + + " b=SMBCg/tHQkIAIzx7OFir0bMhCxk/zaMOx1nyOSAviXW88ERohOFOXIkBVGe74xfJDSh9ou\n" + + " ryKgNA4XhUt4EybBXOn1dlrMA07dDIUFOUE7n+8QsvX1Drii8aBIpiu+O894oBEDSYcd1R+z\n" + + " sZIdXhOjB/Lt4sTE1h5IT2p3UctgY=;\n" + + " bh=dHN66dCNljBC18wb03I1K6hlBvV0qqsKoDsetl+jxb8=; c=relaxed/relaxed;\n" + + " d=example.org; h=from:to:date:subject:mime-version:arc-authentication-results;\n" + + " i=1; s=dummy; t=12345\n" + + "ARC-Authentication-Results: i=1; lists.example.org;\n" + + " spf=pass smtp.mfrom=jqd@d1.example;\n" + + " dkim=pass (1024-bit key) header.i=@d1.example;\n" + + " dmarc=pass\n" + + "Received: from segv.d1.example (segv.d1.example [72.52.75.15])\n" + + " by lists.example.org (8.14.5/8.14.5) with ESMTP id t0EKaNU9010123\n" + + " for ; Thu, 14 Jan 2015 15:01:30 -0800 (PST)\n" + + " (envelope-from jqd@d1.example)\n" + + "Authentication-Results: lists.example.org;\n" + + " spf=pass smtp.mfrom=jqd@d1.example;\n" + + " dkim=pass (1024-bit key) header.i=@d1.example;\n" + + " dmarc=pass\n" + + "MIME-Version: 1.0\n" + + "Return-Path: \n" + + "Received: by 10.157.52.162 with SMTP id g31csp5274520otc;\n" + + " Tue, 3 Jan 2017 12:32:02 -0800 (PST)\n" + + "X-Received: by 10.36.31.84 with SMTP id d81mr49584685itd.26.1483475522271;\n" + + " Tue, 03 Jan 2017 12:32:02 -0800 (PST)\n" + + "Message-ID: \n" + + "Date: Thu, 5 Jan 2017 14:39:01 -0800\n" + + "From: Gene Q Doe \n" + + "To: arc@dmarc.org\n" + + "Subject: Example 2\n" + + "Content-Type: multipart/alternative; boundary=001a113e15fcdd0f9e0545366e8f\n" + + "\n" + + "--001a113e15fcdd0f9e0545366e8f\n" + + "Content-Type: text/plain; charset=UTF-8\n" + + "\n" + + "This is a test message\n" + + "\n" + + "--001a113e15fcdd0f9e0545366e8f\n" + + "Content-Type: text/html; charset=UTF-8\n" + + "\n" + + "
This is a test message
\n" + + "\n" + + "--001a113e15fcdd0f9e0545366e8f--"; + } + + private String valimailSimpleBodyHashMessage(String body) { + return valimailAmsBodyHashMessage( + "d6sLFV7dCrZT/WzJil6ZyWcA/W5tJGLkP+yx1Fln+uZdjkswYMjvPkO2V2kvMrh2GBgjee\n" + + " j9QiqfGHsJvGqAKrFVzxHEsgVA0IYN6tI5wTKMLgu09b8BeHUr49/XnBEemjbgO8W9n9SCyX\n" + + " hKjsZK5b5ZIYBqjCSDZUwWRWfJywk=", + "c+pRG+RBumfEVWDAjHVupy4hZHN2F/AMLHoj6Vha9px35oo6eoyMxxOFUvBgVIUVphuSwV\n" + + " 198baYTV6Of9DHw44VS5rf6MDZNtVc8lwm8ei8aSAgzSnuhnr0jW2j134QTsEL1TK1bWfs+l\n" + + " QGXDBN5AUDsbk4jN5akoDqmH7gNlc=", + "KWSe46TZKCcDbH4klJPo+tjk5LWJnVRlP5pvjXFZYLQ=", + "relaxed/simple", + body); + } + + private String valimailInvalidBodyHashMessage(String bodyHash) { + return valimailAmsBodyHashMessage( + "YoXbDMNRVADrsGTtqAuMLWVnRIj62jQOSDFCX875c5ksVoWcKstnor+cGw/PJnz0cPuFGH\n" + + " +vjw3y+tcgBDDbK1qBVyMUpHrahTLL/0IY2jMzoLgPYz7Yawv/gpn7GlyXL72Vdr58s/nEfk\n" + + " le/2NmfPZjlUezbwsw+UHbuqT5V38=", + "m5y+bcsy0duHt1KxJ2EakY2mOpwIrFaHD60tlw1PmqNdy4M7XLGTnA10R7k1OsFAQNQdZM\n" + + " n1aKsKDpYuRX21avSuDxximXFwkcWYevOqUmaklFXiWyJVXd9fHId0sEtNt0L28HInLwHeCf\n" + + " IPYbUuddJ8wRWei04RZjqdybh4f2o=", + bodyHash, + "relaxed/relaxed", + baseMessageOneBody()); + } + + private String valimailAmsBodyHashMessage( + String arcSealSignature, + String arcMessageSignature, + String bodyHash, + String canonicalization, + String body) { + String bodyHashTag = bodyHash == null ? "" : " bh=" + bodyHash + "; "; + return "MIME-Version: 1.0\n" + + "Return-Path: \n" + + "ARC-Seal: a=rsa-sha256;\n" + + " b=" + arcSealSignature + "; cv=none; d=example.org; i=1; s=dummy;\n" + + " t=12345\n" + + "ARC-Message-Signature: a=rsa-sha256;\n" + + " b=" + arcMessageSignature + ";\n" + + bodyHashTag + "c=" + canonicalization + ";\n" + + " d=example.org; h=from:to:date:subject:mime-version:arc-authentication-results;\n" + + " i=1; s=dummy; t=12345\n" + + "ARC-Authentication-Results: i=1; lists.example.org;\n" + + " spf=pass smtp.mfrom=jqd@d1.example;\n" + + " dkim=pass (1024-bit key) header.i=@d1.example;\n" + + " dmarc=pass\n" + + baseMessageOneSignedHeaders() + + body; + } + private String valimailAmsCanonicalizationMessage(String signedMessageTail) { return "MIME-Version: 1.0\n" + "Return-Path: \n" From 25a1356a525a3a3bd9f0816f2ac77c97c108b39d Mon Sep 17 00:00:00 2001 From: Artem Grinchenko Date: Sat, 18 Apr 2026 17:59:52 -0400 Subject: [PATCH 109/114] Added ValiMail coverage tests in ARCTest for "c=" edge cases: - Updated ARCVerifier to parse AMS canonicalization as header/body modes. - Added support for AMS simple header canonicalization in addition to relaxed. - Rejects empty/invalid c= values before accepting AMS. --- .../org/apache/james/arc/ARCVerifier.java | 49 +++++-- .../java/org/apache/james/arc/ARCTest.java | 136 ++++++++++++++++++ 2 files changed, 170 insertions(+), 15 deletions(-) diff --git a/arc/src/main/java/org/apache/james/arc/ARCVerifier.java b/arc/src/main/java/org/apache/james/arc/ARCVerifier.java index 5b97fd6..b038fe1 100644 --- a/arc/src/main/java/org/apache/james/arc/ARCVerifier.java +++ b/arc/src/main/java/org/apache/james/arc/ARCVerifier.java @@ -112,7 +112,8 @@ public boolean verifyAms(Field amsField, Message message, String publicKeyDnsRec if (signedHeaders == null) { throw new ArcException("AMS missing required tags"); } - if (!verifyAmsBodyHash(tags, message)) { + Canonicalization canonicalization = getCanonicalization(tags.get("c")); + if (canonicalization == null || !verifyAmsBodyHash(tags, message, canonicalization.body)) { return false; } @@ -133,13 +134,13 @@ public boolean verifyAms(Field amsField, Message message, String publicKeyDnsRec continue; } Field f = fields.get(fields.size() - doneHeaders); - signingData.append(canonicalizeRegularHeader(f)); + signingData.append(canonicalizeRegularHeader(f, canonicalization.header)); processedHeaders.put(hName, doneHeaders); } } // AMS itself must be included last - signingData.append(canonicalizeHeader(amsField.getName(), amsForSigning)); + signingData.append(canonicalizeHeader(amsField.getName(), amsForSigning, canonicalization.header)); // Build RSA public key from DNS record PublicKey publicKey = parsePublicKeyFromDns(publicKeyDnsRecord); @@ -160,13 +161,8 @@ public boolean verifyAms(Field amsField, Message message, String publicKeyDnsRec return result; } - private boolean verifyAmsBodyHash(Map tags, Message message) { + private boolean verifyAmsBodyHash(Map tags, Message message, String bodyCanonicalization) { String bodyHash = tags.get("bh"); - String bodyCanonicalization = getBodyCanonicalization(tags.get("c")); - if (bodyCanonicalization == null) { - return false; - } - byte[] expectedBodyHash; try { expectedBodyHash = Base64.getDecoder().decode(bodyHash.replaceAll("\\s+", "")); @@ -268,20 +264,29 @@ private byte[] canonicalizeRelaxedBody(byte[] body) { return relaxed.toString().getBytes(StandardCharsets.UTF_8); } - private String getBodyCanonicalization(String canonicalization) { + private Canonicalization getCanonicalization(String canonicalization) { + String headerCanonicalization; String bodyCanonicalization; if (canonicalization == null) { + headerCanonicalization = "relaxed"; bodyCanonicalization = "simple"; } else if (canonicalization.isEmpty()) { return null; } else { String[] parts = canonicalization.split("/", -1); - bodyCanonicalization = parts.length == 1 ? parts[0] : parts[1]; + if (parts.length > 2) { + return null; + } + headerCanonicalization = parts[0]; + bodyCanonicalization = parts.length == 1 ? "simple" : parts[1]; + } + if (!"simple".equals(headerCanonicalization) && !"relaxed".equals(headerCanonicalization)) { + return null; } if (!"simple".equals(bodyCanonicalization) && !"relaxed".equals(bodyCanonicalization)) { return null; } - return bodyCanonicalization; + return new Canonicalization(headerCanonicalization, bodyCanonicalization); } private Signature getSignature(PublicKey publicKey, StringBuilder signingData) { @@ -322,12 +327,16 @@ public Map parseTagList(String value) { return map; } - private String canonicalizeRegularHeader(Field field) { - String retVal = canonicalizeHeader(field.getName(), field.getBody()); + private String canonicalizeRegularHeader(Field field, String headerCanonicalization) { + String retVal = canonicalizeHeader(field.getName(), field.getBody(), headerCanonicalization); return retVal + "\r\n"; } - private String canonicalizeHeader(String name, String value) { + private String canonicalizeHeader(String name, String value, String headerCanonicalization) { + if ("simple".equals(headerCanonicalization)) { + String separator = value.startsWith(" ") || value.startsWith("\t") ? ":" : ": "; + return name + separator + value; + } // relaxed canonicalization: lowercase field name, unfold spaces, trim String n = name.toLowerCase(Locale.ROOT); String v = value.replaceAll("[\\r\\n]+", " ") @@ -336,6 +345,16 @@ private String canonicalizeHeader(String name, String value) { return n + ":" + v; } + private static class Canonicalization { + private final String header; + private final String body; + + private Canonicalization(String header, String body) { + this.header = header; + this.body = body; + } + } + public PublicKey parsePublicKeyFromDns(String dnsRecord) { Matcher m = PUBLIC_KEY_PATTERN.matcher(dnsRecord); diff --git a/arc/src/test/java/org/apache/james/arc/ARCTest.java b/arc/src/test/java/org/apache/james/arc/ARCTest.java index 5fc3952..9e47617 100644 --- a/arc/src/test/java/org/apache/james/arc/ARCTest.java +++ b/arc/src/test/java/org/apache/james/arc/ARCTest.java @@ -1068,6 +1068,98 @@ public void validate_arc_chain_fails_when_ams_signed_body_is_modified() throws E + "--J.")); } + // ams_fields_c_na: missing c= uses the default simple/simple canonicalization. + @Test + public void validate_arc_chain_passes_when_ams_canonicalization_tag_is_missing() throws Exception { + assertValimailFixturePasses(valimailAmsCanonicalizationTagMessage( + "ygcIhWO/8u3FP5h+7kQH7X9Yqxs0MIHuMUA6PapmNf+8CP5Fb/mY/mZ5aUcLxJNozQ2oUU\n" + + " ukkGEysRaqm5uTJMhiy4YjZgJqMRVka3xMGeIaSw1PiugVu015l8wKR1ollDSN7POJaajQBC\n" + + " /4mUnAUFfND8OqfE/VimB6flYiUJ8=", + "1+WHHTxU+XLWVsbRsvjlW2kMRRhmGE+OE9jxnmLt4ryEa/AezAflCMmVzM7r1dKwxJA1oc\n" + + " YmkN0ga0CO/nxSvB9XR0dsg/TH7TTSQKIllCRxsmGLt+jG/9Mw5yTRxtBOOuFK4xbHbFbCLU\n" + + " vRCry9p9YZpoAemnEb24tm9vjlrsQ=", + "KWSe46TZKCcDbH4klJPo+tjk5LWJnVRlP5pvjXFZYLQ=", + null, + "from:to:date:subject:mime-version:arc-authentication-results", + baseMessageOneSignedTail())); + } + + // ams_fields_c_empty: empty c= must be rejected. + @Test + public void validate_arc_chain_fails_when_ams_canonicalization_tag_is_empty() throws Exception { + assertValimailFixtureFails(valimailAmsCanonicalizationTagMessage( + "eTLQqvFomQqHaOc36izhl5UMp6wVe8vGsLLuPCraumms100F7tOUhRpAII90YkwX0AK+RT\n" + + " 5ij+3Ngk2sQRpMupfFTgeF1olGU+jt943VkFbmSYXYp0AwBe4TGsLugWmfkUy2sGBSC1Rv7n\n" + + " ZaC9m6Y2bNMJcwix1EAuFFV6ck1Wg=", + "QdAvD1bnatYxK/JQCvI1uSuKxOYC+oR7wqg/twCt+zAFm8Tvu+fZpO79+TSx+cLAETXKNT\n" + + " 6mgQLaLROfq3sNf8tP0f/4oqzMUb6Ybz2syHL7hkmC6Za5Ii8RDKwMSc8lmvJk6HXUKgsndZ\n" + + " vWsQCfv+jyLmfDfCI8v9WP7xa2UEU=", + "KWSe46TZKCcDbH4klJPo+tjk5LWJnVRlP5pvjXFZYLQ=", + "", + "from:to:date:subject:mime-version:arc-authentication-results", + baseMessageOneSignedTail())); + } + + // ams_fields_c_rr: relaxed/relaxed canonicalization must be honored. + @Test + public void validate_arc_chain_passes_with_relaxed_header_and_relaxed_body_canonicalization() throws Exception { + assertValimailFixturePasses(valimailAmsCanonicalizationMessage( + baseMessageOneSignedHeaders() + + "Hey gang, \n" + + "This is a test message.\n" + + "--J.")); + } + + // ams_fields_c_rs: relaxed/simple canonicalization must be honored. + @Test + public void validate_arc_chain_passes_with_relaxed_header_and_simple_body_canonicalization() throws Exception { + assertValimailFixturePasses(valimailSimpleBodyHashMessage(baseMessageOneBody())); + } + + // ams_fields_c_sr: simple/relaxed canonicalization must be honored. + @Test + public void validate_arc_chain_passes_with_simple_header_and_relaxed_body_canonicalization() throws Exception { + assertValimailFixturePasses(valimailSimpleHeaderCanonicalizationMessage( + "rhXdX7jNW4wMS/SjYKBYC9eW6q5KnnQ7UGICE45CsYhwEoi38c3nM+91lvM3zhUILxo51X\n" + + " htsrMDLw5TJeZdiCqgXhQZmSEzR+KEdnu2oidezrK/hUzYPlKdO59EQgGIiDAmIRoKZ6+rGV\n" + + " fUCltnyjA07a9KpIpeXRKT3WDCE6A=", + "RWHWmB6euT01CXN0PJKCrmmoPPGc+pxxurfyJBjnNzkTizZKD7XwHLqTuNPaRG7PULU6ffq8FQ7IivdffwqXNj4L3ttpKNIjfsndMFvn5lpKZGfvJZfjTmbTJMhF4CCJZZm7l1xy7LbYMaMb12WY47vXOe9RNjW7jQyw8iqctcA=", + "KWSe46TZKCcDbH4klJPo+tjk5LWJnVRlP5pvjXFZYLQ=", + "simple/relaxed", + baseMessageOneBody())); + } + + // ams_fields_c_ss: simple/simple canonicalization must be honored. + @Test + public void validate_arc_chain_passes_with_simple_header_and_simple_body_canonicalization() throws Exception { + assertValimailFixturePasses(valimailSimpleHeaderCanonicalizationMessage( + "X9qtjasr0URzC564MZz0bwckcIVnBW9yUZP+xt4rStU7MIuuo266KZ1V/e5tbg/MOCZJ2m\n" + + " 3hvKRsVy1fMeIus2RVBg88zwfjyRMsJBC+zKV8oONpIcxriN8imZcaeWdcfsghbAFBM3viCE\n" + + " MdvebSvInMfz0vZsD1DJBYTjPel8w=", + "fv7KIaPfZRTQynzpQ7Gkg3thdZn78iGc5L1hTQoWrY1nSaE3pqQTHsGDW7+FRquewwFoakGLSERxBnC67Sdvw9Exv+/CEs/spqRrDjNygkCf/BIZcURb2nXXFHqPy31X6r2bufWKj6Lbo+5MCyaS2tWkV+KoZhUpolYSo0CoGfk=", + "hhFbTjokraRYc/Af+8v4zyKm/9ApHGkBSLO129NtPbo=", + "simple/simple", + "Hey gang, \n" + + "This is a test message.\n" + + "--J.")); + } + + // ams_fields_c_invalid: unknown canonicalization names must be rejected. + @Test + public void validate_arc_chain_fails_when_ams_canonicalization_tag_is_invalid() throws Exception { + assertValimailFixtureFails(valimailAmsCanonicalizationTagMessage( + "YYGtMgeVAGSLLMZ0k9D0yRRzsfKpbHCoqfLAKz+Du2++GE82Dvz2OT60ebG9m6vmT6nT1t\n" + + " D+rMJnTXIZDUPZ6BLH8rLo8jMb33cBV5NzBD3SDYqWA7OOkYrMGRGmoMfxpcGV8m77YykscT\n" + + " +cpxxA2Ytld+YTd0mTtxdOCN3T1M4=", + "DJZENNFBf+SwDthFmU1ztUBIsKRAAaUdY9CjuGXejv8T29jf3q3EDUz6OnMevRWiSLj4ED\n" + + " gymMDJNGSTUaz3N85KmzWrTJ7QOLNke1H9L9kkfEFowatF8fW5cV/7Y6Ubzh0e1626TELeE+\n" + + " kvczpXT7prdjJZZjQAbDuHsWXkOys=", + "KWSe46TZKCcDbH4klJPo+tjk5LWJnVRlP5pvjXFZYLQ=", + "pancake/waffle", + "from:to:date:subject:mime-version:arc-authentication-results", + baseMessageOneSignedTail())); + } + // aar_struct_i_na / aar_i_missing: an ARC-Authentication-Results header without i= is invalid. @Test public void validate_arc_chain_fails_when_aar_has_no_instance_tag() throws Exception { @@ -2584,6 +2676,50 @@ private String valimailAmsCanonicalizationMessage(String signedMessageTail) { + signedMessageTail; } + private String valimailAmsCanonicalizationTagMessage( + String arcSealSignature, + String arcMessageSignature, + String bodyHash, + String canonicalization, + String signedHeaders, + String signedMessageTail) { + String canonicalizationTag = canonicalization == null ? "" : " c=" + canonicalization + ";"; + return "MIME-Version: 1.0\n" + + "Return-Path: \n" + + "ARC-Seal: a=rsa-sha256;\n" + + " b=" + arcSealSignature + "; cv=none; d=example.org; i=1; s=dummy;\n" + + " t=12345\n" + + "ARC-Message-Signature: a=rsa-sha256;\n" + + " b=" + arcMessageSignature + ";\n" + + " bh=" + bodyHash + ";" + canonicalizationTag + "\n" + + " d=example.org; h=" + signedHeaders + ";\n" + + " i=1; s=dummy; t=12345\n" + + "ARC-Authentication-Results: i=1; lists.example.org;\n" + + " spf=pass smtp.mfrom=jqd@d1.example;\n" + + " dkim=pass (1024-bit key) header.i=@d1.example;\n" + + " dmarc=pass\n" + + signedMessageTail; + } + + private String valimailSimpleHeaderCanonicalizationMessage( + String arcSealSignature, + String arcMessageSignature, + String bodyHash, + String canonicalization, + String body) { + return "MIME-Version: 1.0\n" + + "Return-Path: \n" + + "ARC-Seal: a=rsa-sha256;\n" + + " b=" + arcSealSignature + "; cv=none; d=example.org; i=1; s=dummy;\n" + + " t=12345\n" + + "ARC-Message-Signature: a=rsa-sha256; b=" + arcMessageSignature + + "; bh=" + bodyHash + "; c=" + canonicalization + + "; d=example.org; h=From:To:Date:Subject:MIME-Version:ARC-Authentication-Results; i=1; s=dummy; t=12345\n" + + "ARC-Authentication-Results: i=1; lists.example.org; spf=pass smtp.mfrom=jqd@d1.example; dkim=pass (1024-bit key) header.i=@d1.example; dmarc=pass\n" + + baseMessageOneSignedHeaders() + + body; + } + private String valimailArcSealFormatCommonTail() { return "ARC-Message-Signature: a=rsa-sha256;\n" + " b=SMBCg/tHQkIAIzx7OFir0bMhCxk/zaMOx1nyOSAviXW88ERohOFOXIkBVGe74xfJDSh9ou\n" From ff1dcbb2940bd47d3bf62e63207325082e699405 Mon Sep 17 00:00:00 2001 From: Artem Grinchenko Date: Sat, 18 Apr 2026 18:08:08 -0400 Subject: [PATCH 110/114] Added AMS tag validation in ARCVerifier: - d= is required, non-empty, and must look like a valid domain. - s= is required, non-empty, and must look like a valid selector. - t= remains optional, but if present must be numeric. --- .../org/apache/james/arc/ARCVerifier.java | 17 +- .../java/org/apache/james/arc/ARCTest.java | 146 ++++++++++++++++++ 2 files changed, 162 insertions(+), 1 deletion(-) diff --git a/arc/src/main/java/org/apache/james/arc/ARCVerifier.java b/arc/src/main/java/org/apache/james/arc/ARCVerifier.java index b038fe1..4a750a4 100644 --- a/arc/src/main/java/org/apache/james/arc/ARCVerifier.java +++ b/arc/src/main/java/org/apache/james/arc/ARCVerifier.java @@ -84,6 +84,8 @@ public class ARCVerifier { public static final String ARC_MESSAGE_SIGNATURE = "ARC-Message-Signature"; public static final String ARC_SEAL = "ARC-Seal"; public static final String SHA256RSA = "SHA256withRSA"; + private static final Pattern DOMAIN_PATTERN = Pattern.compile("(?i)^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+$"); + private static final Pattern SELECTOR_PATTERN = Pattern.compile("(?i)^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$"); private static final int MIN_RSA_KEY_BITS = 1024; private static final String DNS_RECORD_TYPE = "_domainkey"; private PublicKeyRetrieverArc _keyRecordRetriever; @@ -101,7 +103,7 @@ public boolean verifyAms(Field amsField, Message message, String publicKeyDnsRec String signedHeaders = tags.get("h"); String bodyHash = tags.get("bh"); String signatureB64 = tags.get("b"); - if (!"rsa-sha256".equals(algorithm) || bodyHash == null || bodyHash.isEmpty() + if (!validateAmsTags(tags) || !"rsa-sha256".equals(algorithm) || bodyHash == null || bodyHash.isEmpty() || signatureB64 == null || signatureB64.isEmpty()) { return false; } @@ -161,6 +163,19 @@ public boolean verifyAms(Field amsField, Message message, String publicKeyDnsRec return result; } + private boolean validateAmsTags(Map tags) { + String domain = tags.get("d"); + String selector = tags.get("s"); + String timestamp = tags.get("t"); + if (domain == null || domain.isEmpty() || !DOMAIN_PATTERN.matcher(domain).matches()) { + return false; + } + if (selector == null || selector.isEmpty() || !SELECTOR_PATTERN.matcher(selector).matches()) { + return false; + } + return timestamp == null || timestamp.matches("\\d+"); + } + private boolean verifyAmsBodyHash(Map tags, Message message, String bodyCanonicalization) { String bodyHash = tags.get("bh"); byte[] expectedBodyHash; diff --git a/arc/src/test/java/org/apache/james/arc/ARCTest.java b/arc/src/test/java/org/apache/james/arc/ARCTest.java index 9e47617..62d5979 100644 --- a/arc/src/test/java/org/apache/james/arc/ARCTest.java +++ b/arc/src/test/java/org/apache/james/arc/ARCTest.java @@ -1160,6 +1160,126 @@ public void validate_arc_chain_fails_when_ams_canonicalization_tag_is_invalid() baseMessageOneSignedTail())); } + // ams_fields_d_na: missing AMS d= prevents public-key lookup and must be rejected. + @Test + public void validate_arc_chain_fails_when_ams_domain_tag_is_missing() throws Exception { + assertValimailFixtureFails(valimailAmsTagFieldsMessage( + "xPtYeQQruf8zzJ9kUrMESmH9ooORAIArDB3MhPcaL+0fgmuc99fprb+aMaSqY6OdZvAEoO\n" + + " EBczyfdtlGKcqLqa5qpXYlukRfG3q8mlOd+8UU1u1bikCzfT/JI8PNerzaoxlksJfmt8zJT0\n" + + " f40IWBJnoRpPNqJSBFb8acvLVZFcQ=", + "iDLI16Dzhtt9CmHLpkUXy7d5legcVvxkPMStdfrYQfNfpwVia165ca2lGI7Sx79pCoMmy3\n" + + " sSWBrLHsTQkKylsGswc0br0ycquKhxHgQh0WChxQd6ITVGQvFO/wZJd2jtE5E/KDbPKDjEio\n" + + " qLfCWpVe2KT1UZ89V+E9tg0T5TgwY=", + null, + "dummy", + "12345")); + } + + // ams_fields_d_empty: empty AMS d= must be rejected. + @Test + public void validate_arc_chain_fails_when_ams_domain_tag_is_empty() throws Exception { + assertValimailFixtureFails(valimailAmsTagFieldsMessage( + "Nn38++8Vf80guievTz8fSFN9VjbPdeRVR5LmvzRt0IMRzZ75FThtzO1VM0grGeUj+D39ri\n" + + " 0ZwIgNyVtZXfG17FEO5BGQq4ZddLQoWHLKTeOWXL59FPhGRJkxiKNefS2c5YqZQ0NI8VkKY0\n" + + " HQlX6AeD/CHHE/bpcg7fFB5/WWnLE=", + "yKCB5xEcyzGr2+mbXWsVDHDZB1PYe9MqqTWySS7Y32uFObEA/MNJmt5yPnZLScwQUhzeTc\n" + + " WL701aDMyPmlYlGnqxl2/QkvEw5hZNfOmD5gltxTlIabWyRrC1Qq/1RS2zDqvF2Qf8SJL1U7\n" + + " gL6jf82iBTT61ckhPraYGIdgI9hlo=", + "", + "dummy", + "12345")); + } + + // ams_fields_d_invalid: invalid AMS d= domain syntax must be rejected. + @Test + public void validate_arc_chain_fails_when_ams_domain_tag_is_invalid() throws Exception { + assertValimailFixtureFails(valimailAmsTagFieldsMessage( + "G8wYXXsNzfrmW5ob/HLkPkg0hz37d0O01HmLr8E8IQUPAa4lywxmOekn0bmKfOvK5p77Dz\n" + + " JEue+awK3gHG7/obHdRLamg8cYxmj4qfR6Ay0baikigUF4Wyt77JsVUqCC1qedRNcRN3IGPx\n" + + " 7rrNSyzVlIWYPal3pQZc3E1ClpG2I=", + "rllEQ7rbed0w+ixVEkL/jiUZrjyDdTQ1d+qnNGEvpzzjh2xFla14BKDcXo7q/aX25lxl0e\n" + + " yzw6yf5PFJC6JWqj5h3sFtLO6hS+E0DXyPZx0ok9tNiv7QV4YqY9fWeA64OZD183DKISDZnD\n" + + " mx/r0Svb5thGZvzvyfuAQapHke/Rk=", + "example...", + "dummy", + "12345")); + } + + // ams_fields_s_na: missing AMS s= prevents public-key lookup and must be rejected. + @Test + public void validate_arc_chain_fails_when_ams_selector_tag_is_missing() throws Exception { + assertValimailFixtureFails(valimailAmsTagFieldsMessage( + "zlVnN6R6lixbru5oAlqBAalgQAcbqVJi0fZe8u57TJTTLHNl+LRLeQRsLQ4OcZ2n5XLTSZ\n" + + " ZAEsfzFQWeFruAnDpA7yT7/YTUYvQM7KdVzx4vl4FSTllt1wb0UJ0SNjlNGiudA94D43LOsx\n" + + " CsESqhYaVWRz4gLkD2P6FfqZLGCZg=", + "1yhACoFkMMv54Xwy9PCxFazQ8BtUb99MhAUEk4Xwq7gVqDoyND9X+pa8CGMYSNUOn2I4tx\n" + + " 4PyDzLhPNf+a4AciBNvFhHwK4lljIQAS514NuaNfv3PR0KDkkoXYv8J1EkI9yAyvOzl5Ka2B\n" + + " 2yNTkGi6GucEwUlu2Qrk0RYhOYOVM=", + "example.org", + null, + "12345")); + } + + // ams_fields_s_empty: empty AMS s= must be rejected. + @Test + public void validate_arc_chain_fails_when_ams_selector_tag_is_empty() throws Exception { + assertValimailFixtureFails(valimailAmsTagFieldsMessage( + "iUyd0NGqGiWwg11FiLSmb+053tfp1baKV04kpufd+RESTCeMHlAHj/N2ZyLCHnCZSfgDTb\n" + + " hJy5KSpxO1nsSOlG/FsI6zwfEWCEP91aNjzEQxrX9iCg/zihZ9uv3wgmSOasjjt2kVGCcJUM\n" + + " iLpzGuccZW6C0S8QyOA8ClL0cHnrs=", + "DMnmzfNSgbRhHJmeSr5Ahc9FzG0ZFQxd7FVPrmmbpB78dtA4tjLUywkekiqhABliJzs0ut\n" + + " zzkNYHyP0hlxGTaYOQ6OgV+1loymJCJDin9FhPV62CGOBXznuaRxFI+aWKHjW6SFFrZplQHG\n" + + " UQcAeHg8Dd8tdKV4dgUnuW+aphtiQ=", + "example.org", + "", + "12345")); + } + + // ams_fields_t_na: AMS t= is optional and a signature generated without it must validate. + @Test + public void validate_arc_chain_passes_when_ams_timestamp_tag_is_missing() throws Exception { + assertValimailFixturePasses(valimailAmsTagFieldsMessage( + "rx+UjBcicBZ6s5/J7S5oMw3YVWAWg+q4Sb4XqR0tMmhOyhjLq7702sEFlEDHJjdTuTVMg+\n" + + " c2qwv/XucEGW8/i4AMzNgkzpwk1Icsr0GHGbR7Jm8V+k6Z08tvQ4x1UaYgrTKmSQeyKq8rQQ\n" + + " rRdzsqqX73OFp/cKLa42T3JVTrQpc=", + "iRbmo9I0Qn8ZELD2xJ754eoEATUfoRxli5qMUi3AQTwGLHU6oaLFsAP7JDYjRm6al3XGp8\n" + + " 73NpnbncM6dnqlBvKK5OmekgztBKiyo7w0Uj6NZbq2KJXYiVW2vAbVkNwy4vPNhMHVTbD/xB\n" + + " PWROiovFOL0q2mHDT1KKLiSzEfrWA=", + "example.org", + "dummy", + null)); + } + + // ams_fields_t_empty: empty AMS t= must be rejected. + @Test + public void validate_arc_chain_fails_when_ams_timestamp_tag_is_empty() throws Exception { + assertValimailFixtureFails(valimailAmsTagFieldsMessage( + "J1fBm2GXu8CCXApvRsyBIITcTcJ4MdgwPIUK2e+vU57BId7RYv2i7/ORWrImxasfuFD17v\n" + + " oU0TUpKqBmD/o6ZdLcgxg72iaYN7CoN9uK9Vr1llrVHuhJa4WUW0XG+a3XqKB2PXJh0LckJu\n" + + " 215qpJ4wqx+/6aGVuxQp5LXwktEDM=", + "GO1zQzqzWlsUbs6Rag7bYFPB2LgxCLkex8PRM+4/IbysgHm1TVtsPCVAAYp8+MK8UDyuuR\n" + + " s3wgba6Zgh08O4F3MGn5ouJmplCkS/mF1MTAuWF1BiBkzYTdNmwhESK3GBTDNgTzBwa0upsw\n" + + " aYiT87hDd1aqIKekvR3ZyEtZAN0Bc=", + "example.org", + "dummy", + "")); + } + + // ams_fields_t_invalid: non-numeric AMS t= must be rejected. + @Test + public void validate_arc_chain_fails_when_ams_timestamp_tag_is_invalid() throws Exception { + assertValimailFixtureFails(valimailAmsTagFieldsMessage( + "g1Xr4aSSeSDH0CUBae/NLjI30AgmGDwAdG5BC2c/OuTKGROcimWkt3ikql9YlvBv/3O8AQ\n" + + " fe1XJqEq8EwFpKgk2YvMiWV4YKWPGb4DVNn/N2nk79o2KH/DlXNU4fLGvae9leiu1E+KJERC\n" + + " /sYt7EA0rffMCWMjfHivWEx1swomo=", + "B9XbvvEBkWcBoOY6hBRGeJLsADsuzM0ZRvpeBWgF/nx8itykfMZmdeVPzVY5SI7MRCi8jp\n" + + " +RtfP938tY75D6wfNd4+mrDkHyEQFAiE+UlYWjZOGx69go2UQyN5+wocPHHps4n9j279es08\n" + + " zmmxQXWG8wuoq53Y1CfrwNyniO824=", + "example.org", + "dummy", + "icecream")); + } + // aar_struct_i_na / aar_i_missing: an ARC-Authentication-Results header without i= is invalid. @Test public void validate_arc_chain_fails_when_aar_has_no_instance_tag() throws Exception { @@ -2720,6 +2840,32 @@ private String valimailSimpleHeaderCanonicalizationMessage( + body; } + private String valimailAmsTagFieldsMessage( + String arcSealSignature, + String arcMessageSignature, + String domain, + String selector, + String timestamp) { + String domainTag = domain == null ? "" : "d=" + domain + "; "; + String selectorTag = selector == null ? "" : "s=" + selector; + String timestampTag = timestamp == null ? "" : "; t=" + timestamp; + return "MIME-Version: 1.0\n" + + "Return-Path: \n" + + "ARC-Seal: a=rsa-sha256;\n" + + " b=" + arcSealSignature + "; cv=none; d=example.org; i=1; s=dummy;\n" + + " t=12345\n" + + "ARC-Message-Signature: a=rsa-sha256;\n" + + " b=" + arcMessageSignature + ";\n" + + " bh=KWSe46TZKCcDbH4klJPo+tjk5LWJnVRlP5pvjXFZYLQ=; c=relaxed/relaxed;\n" + + " " + domainTag + "h=from:to:date:subject:mime-version:arc-authentication-results;\n" + + " i=1; " + selectorTag + timestampTag + "\n" + + "ARC-Authentication-Results: i=1; lists.example.org;\n" + + " spf=pass smtp.mfrom=jqd@d1.example;\n" + + " dkim=pass (1024-bit key) header.i=@d1.example;\n" + + " dmarc=pass\n" + + baseMessageOneSignedTail(); + } + private String valimailArcSealFormatCommonTail() { return "ARC-Message-Signature: a=rsa-sha256;\n" + " b=SMBCg/tHQkIAIzx7OFir0bMhCxk/zaMOx1nyOSAviXW88ERohOFOXIkBVGe74xfJDSh9ou\n" From ff3b04f96778782ef8c3c4dc859edc7963af4e26 Mon Sep 17 00:00:00 2001 From: Artem Grinchenko Date: Sat, 18 Apr 2026 18:24:28 -0400 Subject: [PATCH 111/114] Add remaining ARC h= and first-hop signing test coverage: - Add ValiMail fixture-backed tests for the remaining AMS h= signed-header cases, covering empty h=, whitespace and folded separators, case-insensitive header names, duplicate header selection, missing headers, omitted h=, header order failures, and ARC header inclusion behavior. - Reject ARC-Seal in the AMS h= signed-header list so messages that include arc-seal in AMS signing data fail validation as expected by the upstream test suite, while preserving the allowed arc-message-signature case. - Add explicit first-hop signing coverage for messages without an incoming ARC chain, asserting that generated ARC sets start at i=1 and seal with cv=none. --- .../org/apache/james/arc/ARCVerifier.java | 10 + .../java/org/apache/james/arc/ARCTest.java | 357 ++++++++++++++++++ 2 files changed, 367 insertions(+) diff --git a/arc/src/main/java/org/apache/james/arc/ARCVerifier.java b/arc/src/main/java/org/apache/james/arc/ARCVerifier.java index 4a750a4..74ba23e 100644 --- a/arc/src/main/java/org/apache/james/arc/ARCVerifier.java +++ b/arc/src/main/java/org/apache/james/arc/ARCVerifier.java @@ -114,6 +114,9 @@ public boolean verifyAms(Field amsField, Message message, String publicKeyDnsRec if (signedHeaders == null) { throw new ArcException("AMS missing required tags"); } + if (signsArcSealHeader(signedHeaders)) { + return false; + } Canonicalization canonicalization = getCanonicalization(tags.get("c")); if (canonicalization == null || !verifyAmsBodyHash(tags, message, canonicalization.body)) { return false; @@ -163,6 +166,13 @@ public boolean verifyAms(Field amsField, Message message, String publicKeyDnsRec return result; } + private boolean signsArcSealHeader(String signedHeaders) { + return Arrays.stream(signedHeaders.split(":")) + .map(String::trim) + .map(headerName -> headerName.toLowerCase(Locale.US)) + .anyMatch("arc-seal"::equals); + } + private boolean validateAmsTags(Map tags) { String domain = tags.get("d"); String selector = tags.get("s"); diff --git a/arc/src/test/java/org/apache/james/arc/ARCTest.java b/arc/src/test/java/org/apache/james/arc/ARCTest.java index 62d5979..62f16c3 100644 --- a/arc/src/test/java/org/apache/james/arc/ARCTest.java +++ b/arc/src/test/java/org/apache/james/arc/ARCTest.java @@ -173,6 +173,19 @@ public void generate_and_verify_arc_set() throws Exception { assertThat(cv.getResult().toString().toLowerCase()).isEqualTo(expectedCv); } + // i0_base: signing a message with no incoming ARC chain starts at i=1 and seals cv=none. + @Test + public void build_arc_set_for_message_without_arc_chain_uses_first_instance_and_cv_none() throws Exception { + ByteArrayInputStream emailStream = readFileToByteArrayInputStream("/mail/rfc8617_no_arc.eml"); + Message message = new DefaultMessageBuilder().parseMessage(emailStream); + + Map arcSet = arcSetBuilder.buildArcSet(message, HELO, MAIL_FROM, IP, keyRecordRetriever); + + assertThat(arcSet.get(ARC_AUTHENTICATION_RESULTS)).startsWith("i=1;"); + assertThat(arcSet.get(ARC_MESSAGE_SIGNATURE)).contains("i=1;"); + assertThat(arcSet.get(ARC_SEAL)).contains("i=1; cv=none;"); + } + // cv_fail_i1_ams_invalid: builds a valid i=1 ARC set, then replaces the AMS b= signature with // wrong bytes before adding headers to the message, expecting chain validation to return cv=fail. @Test @@ -1205,6 +1218,190 @@ public void validate_arc_chain_fails_when_ams_domain_tag_is_invalid() throws Exc "12345")); } + // ams_fields_h_empty: upstream suite accepts an empty AMS h= tag. + @Test + public void validate_arc_chain_passes_when_ams_signed_headers_tag_is_empty() throws Exception { + assertValimailFixturePasses(valimailAmsSignedHeadersMessage( + "V/iPFUptKaruDTBpwKcf5i6nu54GxrG3ss2bfPqqT3I5MGMyRmtE+J0kOVtU9qtHIhXUng\n" + + " Iezv5+gCOIf2jP1eYGvhN2Wmkf2zsShG6+Rfpnp9fih71C1f6fh6Qp4tTUhB6ww4ZOTKtVdv\n" + + " H0C2s/5in4RLMxS0FUWge8CvlTnGs=", + "ex6hirqdOz1yO1SZE3ALisw3dj1La5L4qHcv8/ttCs1qGajzw0zEtUyMnskTPQnt9cxxF3\n" + + " T74KRXlPVN/4Aqn+K/Q4NHtOW9vyuLt9ek9Vm6/xvZ10KTMrxv24u0eLnsigC6NfablL4wAM\n" + + " epZDlyjf/HPBd0yVLQL8yFDtQ5fE0=", + "", + "MIME-Version: 1.0\n", + baseMessageOneSignedTail())); + } + + // ams_fields_h_cws1: whitespace around AMS h= colon separators must be ignored. + @Test + public void validate_arc_chain_passes_when_ams_signed_headers_have_whitespace_around_colons() throws Exception { + assertValimailFixturePasses(valimailAmsSignedHeadersMessage( + "0+WA3Dpt9Y1lJ5wkoOZsh6KXEQFv0YE+ykvXAdS5t1toEui1UWzLyKWxSD/H/Xc6eCaQZM\n" + + " ji4IxybZ4OrIdV0yRe1fGqYN/bJ3KnkuzrHpaikXRWxXdX8tiIu5+I+HmERxuGzGqHdNv2zj\n" + + " 5L8PNAsGs4LDg3xQXEe3FQAvis9OA=", + "Lq5Sy1R3C0RaTxKWfggKBJ2MOdgAHeFy1nELK1c+CFnxdvSL+OxuvSxk8HYv7YMJDTR4Na\n" + + " 1D5GaFedB1uYVQsz1T5e3p9B+54W4bObByD14WvTGKV3ys8FlOf4MdRIlD4o6N3INfHrNbYX\n" + + " zwPKjkoYbteAEQ/kTpjESOpm131io=", + "from : to : date : subject : mime-version : arc-authentication-results", + "MIME-Version: 1.0\n", + baseMessageOneSignedTail())); + } + + // ams_fields_h_cws2: folded whitespace around AMS h= colon separators must be ignored. + @Test + public void validate_arc_chain_passes_when_ams_signed_headers_have_folded_whitespace_around_colons() throws Exception { + assertValimailFixturePasses(valimailAmsSignedHeadersMessage( + "0+WA3Dpt9Y1lJ5wkoOZsh6KXEQFv0YE+ykvXAdS5t1toEui1UWzLyKWxSD/H/Xc6eCaQZM\n" + + " ji4IxybZ4OrIdV0yRe1fGqYN/bJ3KnkuzrHpaikXRWxXdX8tiIu5+I+HmERxuGzGqHdNv2zj\n" + + " 5L8PNAsGs4LDg3xQXEe3FQAvis9OA=", + "Lq5Sy1R3C0RaTxKWfggKBJ2MOdgAHeFy1nELK1c+CFnxdvSL+OxuvSxk8HYv7YMJDTR4Na\n" + + " 1D5GaFedB1uYVQsz1T5e3p9B+54W4bObByD14WvTGKV3ys8FlOf4MdRIlD4o6N3INfHrNbYX\n" + + " zwPKjkoYbteAEQ/kTpjESOpm131io=", + "from : to : date : subject : mime-version :\n" + + " arc-authentication-results", + "MIME-Version: 1.0\n", + baseMessageOneSignedTail())); + } + + // ams_fields_h_case: AMS h= header names are case-insensitive. + @Test + public void validate_arc_chain_passes_when_ams_signed_headers_use_mixed_case() throws Exception { + assertValimailFixturePasses(valimailAmsSignedHeadersMessage( + "me1uYrnpt5Cdjkfj+bqK8X6abs8TET4r5Wp6e6ZuZ2FAtSzfx8WdnHCnBLUj7t/PR+EGne\n" + + " h4auyljzkm2gz09I0MbaYkd+xDmkRoN2WrFotceq+iROoDLf2NgZJb3SfDcVFp8emRMyyaGL\n" + + " WAtshPjJWnjoNfm+3clEpXzPw4WM4=", + "OCzwOGeJy6YL07Rh1A970C9pAK2YJeXr0rDVVbsd/aOxTeKbrIxOfQsJ5hYaze0aeE5U0p\n" + + " y/45cz4Jg07Ch61xZ0G3R3ne4eXxPauAU6QKPwr45HxO2gDywmNruiJP0JPTzcC9SVV/YjyL\n" + + " OGobZNIwUWR1hEkd5/UuAXHk23Q4g=", + "From:To:Date:Subject:Mime-version:Arc-authentication-results", + "MIME-Version: 1.0\n", + baseMessageOneSignedTail())); + } + + // ams_fields_h_dup1: duplicate signed headers must be selected from the bottom up. + @Test + public void validate_arc_chain_passes_when_ams_signed_headers_duplicate_existing_header_in_bottom_up_order() throws Exception { + assertValimailFixturePasses(valimailAmsSignedHeadersMessage( + "tv8fgth8OQw5DylJlW253wBM12VcMvjFLj+TwonVXPiSPJ1hV7F24q0rgmYeVhSBK/+4Ou\n" + + " kPW3e9oqILXx95sXrE4fiiz46//FtZK7z0YVzy/B3QpR7fGxzzA5uVoUh4WNd0oQEejwDKss\n" + + " ILrzkyu6fDUZ1kLeKyk3clE7b/NJo=", + "rGZpmx8nA8Fe0yQ319Ns+DPmwx9ToC7Z5Ba5NNGYtmXF87xboR0Cy7yxlJ2ek6j8WqCRXI\n" + + " jKV32tgZBXu5upoveTLBGzSe+NPTL2SkU2nFnktJjjPwTiPAYyXVBY1Uy7uSv9dT+wfB4Hvg\n" + + " Hm/nSrzqTBOxPsND1F1b2rzE1elQo=", + "from:to:to:date:subject:mime-version:arc-authentication-results", + "MIME-Version: 1.0\n", + baseMessageOneSignedTailWithTwoToHeaders())); + } + + // ams_fields_h_non_existant: signing a non-existent header is allowed. + @Test + public void validate_arc_chain_passes_when_ams_signed_headers_include_non_existent_header() throws Exception { + assertValimailFixturePasses(valimailAmsSignedHeadersMessage( + "cEfCkdG3zAUpq2XMYEvcI8e+nD53NUuUr/NQ74UBTzSVJBOsNQKADtUWqYirSlB9AFeEIq\n" + + " VGstwfXqh5TiMv1Uk9O04vM7WxrmMsqZI+GiRQvtaanfZQMcaYME1pCURdkDbMK/MOUGV+W2\n" + + " j9anSPB91SOQruKUDtqgwq8z87Ajc=", + "QHma3KzZiiP6Yq5jWp+mLznldNAMpK9ffvI87mbvEFFd1YSfoJu9JrxtBgv3/MEBFHLPm9\n" + + " qTii8g+94xOLgp/LEC/dM2E/u7yPAKKMz5fMzJfwqSGAGyBg2f12Mkyaqs3dzv97nZTZFkj8\n" + + " mHCV6SHNfnC+lkIs5XpJNRtddvolQ=", + "from:to:date:subject:mime-version:arc-authentication-results", + "", + baseMessageOneSignedTail())); + } + + // ams_fields_h_non_existant_dup: duplicate non-existent signed headers are allowed. + @Test + public void validate_arc_chain_passes_when_ams_signed_headers_include_duplicate_non_existent_header() throws Exception { + assertValimailFixturePasses(valimailAmsSignedHeadersMessage( + "akTog4W3hR16mF9pNZIhHzcceyST1LHWaIsDPobRX6iy5jBRbpb+lyKlcyZmS02T2kFYG9\n" + + " iOWQ6UZruiQXQu/u/GSkn0RSCwHWTfb25YqrQBLwH7pki4bDGHrTSrGbuYnFEHadYl2B8Gxo\n" + + " UXYn2/XBBil6Dkku2SswdN6RZhhoM=", + "CvqFe5bB3kFEFvITOTVx7VcrJQBT5aAtUJjX0h1L1Gh0MtUQofgKfOakgKr5kUIxv2foZY\n" + + " KJzwNSuUNnDyY87HJeT02j4JlpYnj0+PzB8xjW2Kj4/4TrLMkcJsfC2wujZClzXW65uCsFEb\n" + + " 0ht8EEQis3581f6/S2V+2pHxvqRiM=", + "from:to:date:subject:mime-version:arc-authentication-results:mime-version", + "", + baseMessageOneSignedTail())); + } + + // ams_fields_h_mis_hdr: blank header names in AMS h= are ignored. + @Test + public void validate_arc_chain_passes_when_ams_signed_headers_include_blank_header_name() throws Exception { + assertValimailFixturePasses(valimailAmsSignedHeadersMessage( + "FCq5UA4xGNozfvMgZkQ0Wpu4Q0dkGbrNvMKc0SNQnbObHCA84DNwUUp+I41h5ZvwQBAGxf\n" + + " hvUfjmsMFHBtsYj/aQ5kkehVPkOZ/6hengnO0IJs78Ab/5eivdD7MRLuShcTWd9qx32dVFJD\n" + + " yx8qIaRZplvJYl30ry7sOJQu4qSZk=", + "4TbROXpBlHvYUMMvecTyaEqk0DtgISmfrz9L7QEizbAaI6vgDPu1xD8LSj4CfHpak6GMde\n" + + " zpqtfiITgVTBKbkZi2kuFQwmu5xWsReExZEiNq7Tr6L5iObGjL0A27RIBj4znEmO6mk2Umnl\n" + + " +c6LR5XzyE65FGLZ+9nSH2U12klzI=", + "from:to::date:subject:mime-version:arc-authentication-results", + "MIME-Version: 1.0\n", + baseMessageOneSignedTail())); + } + + // ams_fields_h_includes_ams: upstream suite currently permits including AMS in h=. + @Test + public void validate_arc_chain_passes_when_ams_signed_headers_include_arc_message_signature() throws Exception { + assertValimailFixturePasses(valimailAmsSignedHeadersIncludesAmsMessage()); + } + + // ams_fields_h_na: AMS h= is required. + @Test + public void validate_arc_chain_fails_when_ams_signed_headers_tag_is_missing() throws Exception { + assertValimailFixtureFails(valimailAmsMissingSignedHeadersMessage()); + } + + // ams_fields_h_dup2: duplicate signed headers in the wrong bottom-up order are rejected. + @Test + public void validate_arc_chain_fails_when_ams_signed_headers_duplicate_existing_header_in_wrong_order() throws Exception { + assertValimailFixtureFails(valimailAmsSignedHeadersMessage( + "tv8fgth8OQw5DylJlW253wBM12VcMvjFLj+TwonVXPiSPJ1hV7F24q0rgmYeVhSBK/+4Ou\n" + + " kPW3e9oqILXx95sXrE4fiiz46//FtZK7z0YVzy/B3QpR7fGxzzA5uVoUh4WNd0oQEejwDKss\n" + + " ILrzkyu6fDUZ1kLeKyk3clE7b/NJo=", + "rGZpmx8nA8Fe0yQ319Ns+DPmwx9ToC7Z5Ba5NNGYtmXF87xboR0Cy7yxlJ2ek6j8WqCRXI\n" + + " jKV32tgZBXu5upoveTLBGzSe+NPTL2SkU2nFnktJjjPwTiPAYyXVBY1Uy7uSv9dT+wfB4Hvg\n" + + " Hm/nSrzqTBOxPsND1F1b2rzE1elQo=", + "from:to:to:date:subject:mime-version:arc-authentication-results", + "MIME-Version: 1.0\n", + baseMessageOneSignedTailWithTwoToHeadersInReverseOrder())); + } + + // ams_fields_h_order: misordered signed headers must not validate. + @Test + public void validate_arc_chain_fails_when_ams_signed_headers_are_misordered() throws Exception { + assertValimailFixtureFails(valimailAmsSignedHeadersMessage( + "vTCiDmh8p+YFqH8WSxCrLVT3IS1Xmt35hs9y2Fb4EriRTTEmD7lWa0UrCe9j/a3yftcMAb\n" + + " 8W01KgTrdIhmUMF7YrElyT1cGc0ChGHmdkuA2MpVBnLJMCgtXEQkWcVRne38KB9P+GLvr5uD\n" + + " nBOjOJNoBt4Nt+Y8zCKG/tN2RetKk=", + "2o+Wl1gzbDmg4Hv5q52M7V+E6KBhMISVmqTDrk1HfOgMJwJ+0v8Nl18EjbL+iOTu6Vxz9+\n" + + " 1m64cPsNr1Tgm79jjqugOKDI/yaU7h4DaFMmN54tGX8j1ElMXSl8ghcfaknApLU060vKVUoo\n" + + " F2GfD1qo+SSox3wkZNkPQdGKjNmQM=", + "from:to:date:subject:mime-version:arc-authentication-results", + "MIME-Version: 1.0\n", + baseMessageOneSignedTail())); + } + + // ams_fields_h_empty_added: a header added after signing a missing header must be rejected. + @Test + public void validate_arc_chain_fails_when_previously_missing_ams_signed_header_is_added() throws Exception { + assertValimailFixtureFails(valimailAmsSignedHeadersMessage( + "cEfCkdG3zAUpq2XMYEvcI8e+nD53NUuUr/NQ74UBTzSVJBOsNQKADtUWqYirSlB9AFeEIq\n" + + " VGstwfXqh5TiMv1Uk9O04vM7WxrmMsqZI+GiRQvtaanfZQMcaYME1pCURdkDbMK/MOUGV+W2\n" + + " j9anSPB91SOQruKUDtqgwq8z87Ajc=", + "QHma3KzZiiP6Yq5jWp+mLznldNAMpK9ffvI87mbvEFFd1YSfoJu9JrxtBgv3/MEBFHLPm9\n" + + " qTii8g+94xOLgp/LEC/dM2E/u7yPAKKMz5fMzJfwqSGAGyBg2f12Mkyaqs3dzv97nZTZFkj8\n" + + " mHCV6SHNfnC+lkIs5XpJNRtddvolQ=", + "from:to:date:subject:mime-version:arc-authentication-results", + "MIME-Version: 1.0\n", + baseMessageOneSignedTail())); + } + + // ams_fields_h_includes_as: including ARC-Seal in AMS h= must be rejected. + @Test + public void validate_arc_chain_fails_when_ams_signed_headers_include_arc_seal() throws Exception { + assertValimailFixtureFails(valimailAmsSignedHeadersIncludesAsMessage()); + } + // ams_fields_s_na: missing AMS s= prevents public-key lookup and must be rejected. @Test public void validate_arc_chain_fails_when_ams_selector_tag_is_missing() throws Exception { @@ -2601,6 +2798,46 @@ private String baseMessageOneSignedTail() { return baseMessageOneSignedHeaders() + baseMessageOneBody(); } + private String baseMessageOneSignedTailWithTwoToHeaders() { + return "Received: from segv.d1.example (segv.d1.example [72.52.75.15])\n" + + " by lists.example.org (8.14.5/8.14.5) with ESMTP id t0EKaNU9010123\n" + + " for ; Thu, 14 Jan 2015 15:01:30 -0800 (PST)\n" + + " (envelope-from jqd@d1.example)\n" + + "Authentication-Results: lists.example.org;\n" + + " spf=pass smtp.mfrom=jqd@d1.example;\n" + + " dkim=pass (1024-bit key) header.i=@d1.example;\n" + + " dmarc=pass\n" + + "Received: by 10.157.14.6 with HTTP; Tue, 3 Jan 2017 12:22:54 -0800 (PST)\n" + + "Message-ID: <54B84785.1060301@d1.example.org>\n" + + "Date: Thu, 14 Jan 2015 15:00:01 -0800\n" + + "From: John Q Doe \n" + + "To: morty@dmarc.org\n" + + "To: evil_morty@dmarc.org\n" + + "Subject: Example 1\n" + + "\n" + + baseMessageOneBody(); + } + + private String baseMessageOneSignedTailWithTwoToHeadersInReverseOrder() { + return "Received: from segv.d1.example (segv.d1.example [72.52.75.15])\n" + + " by lists.example.org (8.14.5/8.14.5) with ESMTP id t0EKaNU9010123\n" + + " for ; Thu, 14 Jan 2015 15:01:30 -0800 (PST)\n" + + " (envelope-from jqd@d1.example)\n" + + "Authentication-Results: lists.example.org;\n" + + " spf=pass smtp.mfrom=jqd@d1.example;\n" + + " dkim=pass (1024-bit key) header.i=@d1.example;\n" + + " dmarc=pass\n" + + "Received: by 10.157.14.6 with HTTP; Tue, 3 Jan 2017 12:22:54 -0800 (PST)\n" + + "Message-ID: <54B84785.1060301@d1.example.org>\n" + + "Date: Thu, 14 Jan 2015 15:00:01 -0800\n" + + "From: John Q Doe \n" + + "To: evil_morty@dmarc.org\n" + + "To: morty@dmarc.org\n" + + "Subject: Example 1\n" + + "\n" + + baseMessageOneBody(); + } + private String baseMessageOneSignedHeaders() { return "Received: from segv.d1.example (segv.d1.example [72.52.75.15])\n" + " by lists.example.org (8.14.5/8.14.5) with ESMTP id t0EKaNU9010123\n" @@ -2821,6 +3058,126 @@ private String valimailAmsCanonicalizationTagMessage( + signedMessageTail; } + private String valimailAmsMissingSignedHeadersMessage() { + return "MIME-Version: 1.0\n" + + "Return-Path: \n" + + "ARC-Seal: a=rsa-sha256;\n" + + " b=O9vrOnKLOdZXxa46F8RDPTzqW14JYE7idGn0AfedcpWh58mPFE9jXHeaMda5L59thiQrJN\n" + + " T7Smno713R6DU9CfvnOvq8rQXCJ6D7GzWFhhOn6wEbjTaFQQ3jHn67XVDVnb4yjLElVhixob\n" + + " pG5ouN8U1TPqPWf+41wrIrCd5Mocw=; cv=none; d=example.org; i=1; s=dummy;\n" + + " t=12345\n" + + "ARC-Message-Signature: a=rsa-sha256;\n" + + " b=RidA92CmsCgK81At2aPnlGuFlbvNT5IdWz7Z/6j765oabi0LEDkpB+2q+C5TJfc28Gj0Ok\n" + + " gghf2ykPbb7WniSvCue66fvUYaABU5m84urSzGd3MG3F47vTzCQ5qLah7E0UssP2QbP2b1Rt\n" + + " Hry/RlkOzlWeSlxpCcPvArmmcADTc=;\n" + + " bh=KWSe46TZKCcDbH4klJPo+tjk5LWJnVRlP5pvjXFZYLQ=; c=relaxed/relaxed;\n" + + " d=example.org; i=1; s=dummy; t=12345\n" + + "ARC-Authentication-Results: i=1; lists.example.org;\n" + + " spf=pass smtp.mfrom=jqd@d1.example;\n" + + " dkim=pass (1024-bit key) header.i=@d1.example;\n" + + " dmarc=pass\n" + + baseMessageOneSignedTail(); + } + + private String valimailAmsSignedHeadersMessage( + String arcSealSignature, + String arcMessageSignature, + String signedHeaders, + String initialHeaders, + String signedMessageTail) { + return initialHeaders + + "Return-Path: \n" + + "ARC-Seal: a=rsa-sha256;\n" + + " b=" + arcSealSignature + "; cv=none; d=example.org; i=1; s=dummy;\n" + + " t=12345\n" + + "ARC-Message-Signature: a=rsa-sha256;\n" + + " b=" + arcMessageSignature + ";\n" + + " bh=KWSe46TZKCcDbH4klJPo+tjk5LWJnVRlP5pvjXFZYLQ=; c=relaxed/relaxed;\n" + + " d=example.org; h=" + signedHeaders + ";\n" + + " i=1; s=dummy; t=12345\n" + + "ARC-Authentication-Results: i=1; lists.example.org;\n" + + " spf=pass smtp.mfrom=jqd@d1.example;\n" + + " dkim=pass (1024-bit key) header.i=@d1.example;\n" + + " dmarc=pass\n" + + signedMessageTail; + } + + private String valimailAmsSignedHeadersIncludesAmsMessage() { + return "MIME-Version: 1.0\n" + + "Return-Path: \n" + + "ARC-Seal: a=rsa-sha256;\n" + + " b=XSOc6bESO7Ek4iCPyVXVE7aR8HUBBOXdOKmFpJO/3DI8rLRHHfRT9XAML3OsBE2RYj+0yd\n" + + " ypsBg8UQEewpY6Z5KEUhxfzwaBGObKr1pgwjkYiOBpPTV1Xfv1lGT+1qlJtBR2AGJauCEs7G\n" + + " fNzwa3MI+iO9E8g6aO/m9Mk1BlLHY=; cv=pass; d=example.org; i=2; s=dummy;\n" + + " t=12346\n" + + "ARC-Message-Signature: a=rsa-sha256;\n" + + " b=vpypMlcZNGmeVETFS/+v/Uk9npQE1LhY8tha0XTaeeNMgK1fzWaxvUHY0cuumuzK2pU25O\n" + + " uWTt08QEXczUR/BLmiZaYUWQV8qGOAv5umtEshqjB+0KPg5W09N20vQp8OXMQrenjZz0YPsy\n" + + " VweEidqd3HAcWSbZgW3jAFKXHGSXc=;\n" + + " bh=KWSe46TZKCcDbH4klJPo+tjk5LWJnVRlP5pvjXFZYLQ=; c=relaxed/relaxed;\n" + + " d=example.org; h=from:to:arc-message-signature:date:subject:mime-version:arc-authentication-results;\n" + + " i=2; s=dummy; t=12346\n" + + "ARC-Authentication-Results: i=2; lists.example.org;\n" + + " spf=pass smtp.mfrom=jqd@d1.example;\n" + + " dkim=pass (1024-bit key) header.i=@d1.example;\n" + + " dmarc=pass\n" + + "ARC-Seal: a=rsa-sha256;\n" + + " b=dOdFEyhrk/tw5wl3vMIogoxhaVsKJkrkEhnAcq2XqOLSQhPpGzhGBJzR7k1sWGokon3TmQ\n" + + " 7TX9zQLO6ikRpwd/pUswiRW5DBupy58fefuclXJAhErsrebfvfiueGyhHXV7C1LyJTztywzn\n" + + " QGG4SCciU/FTlsJ0QANrnLRoadfps=; cv=none; d=example.org; i=1; s=dummy;\n" + + " t=12345\n" + + "ARC-Message-Signature: a=rsa-sha256;\n" + + " b=QsRzR/UqwRfVLBc1TnoQomlVw5qi6jp08q8lHpBSl4RehWyHQtY3uOIAGdghDk/mO+/Xpm\n" + + " 9JA5UVrPyDV0f+2q/YAHuwvP11iCkBQkocmFvgTSxN8H+DwFFPrVVUudQYZV7UDDycXoM6UE\n" + + " cdfzLLzVNPOAHEDIi/uzoV4sUqZ18=;\n" + + " bh=KWSe46TZKCcDbH4klJPo+tjk5LWJnVRlP5pvjXFZYLQ=; c=relaxed/relaxed;\n" + + " d=example.org; h=from:to:date:subject:mime-version:arc-authentication-results;\n" + + " i=1; s=dummy; t=12345\n" + + "ARC-Authentication-Results: i=1; lists.example.org;\n" + + " spf=pass smtp.mfrom=jqd@d1.example;\n" + + " dkim=pass (1024-bit key) header.i=@d1.example;\n" + + " dmarc=pass\n" + + baseMessageOneSignedTail(); + } + + private String valimailAmsSignedHeadersIncludesAsMessage() { + return "MIME-Version: 1.0\n" + + "Return-Path: \n" + + "ARC-Seal: a=rsa-sha256;\n" + + " b=OuFcuRk6CdaxxeBmCdvzoFxM6G0xmA3XNh1F243uPQsstHJ+T0csqD6PADig/UPV/Aj6fQ\n" + + " kAOsyZOzIK1X9ZCZLB2idFymnyWtYc2spNgCiSfwQiQuS3SFVUtr+Y7v58PtyAy2HCb2pA5I\n" + + " OIY1WjbK1Pd4SrJbZ4/M0d0wgFt7g=; cv=pass; d=example.org; i=2; s=dummy;\n" + + " t=12346\n" + + "ARC-Message-Signature: a=rsa-sha256;\n" + + " b=T5uPa/aCBkG1PK5dsSgO5US5yVvKnf/DAsyxMDCLVgw3auULB52XaLkZbc5KAcbGwz4KQZ\n" + + " H8TTB1qbdHGyUpA/1Tq4QveM4z1x/s/2gK/thnoW0wWEHu5frgmd3tVg8kEjrmU6HOJ1SNYq\n" + + " Qgjxvsd/OwpjYsfOjODwgyGDR/doE=;\n" + + " bh=KWSe46TZKCcDbH4klJPo+tjk5LWJnVRlP5pvjXFZYLQ=; c=relaxed/relaxed;\n" + + " d=example.org; h=from:to:arc-seal:date:subject:mime-version:arc-authentication-results;\n" + + " i=2; s=dummy; t=12346\n" + + "ARC-Authentication-Results: i=2; lists.example.org;\n" + + " spf=pass smtp.mfrom=jqd@d1.example;\n" + + " dkim=pass (1024-bit key) header.i=@d1.example;\n" + + " dmarc=pass\n" + + "ARC-Seal: a=rsa-sha256;\n" + + " b=dOdFEyhrk/tw5wl3vMIogoxhaVsKJkrkEhnAcq2XqOLSQhPpGzhGBJzR7k1sWGokon3TmQ\n" + + " 7TX9zQLO6ikRpwd/pUswiRW5DBupy58fefuclXJAhErsrebfvfiueGyhHXV7C1LyJTztywzn\n" + + " QGG4SCciU/FTlsJ0QANrnLRoadfps=; cv=none; d=example.org; i=1; s=dummy;\n" + + " t=12345\n" + + "ARC-Message-Signature: a=rsa-sha256;\n" + + " b=QsRzR/UqwRfVLBc1TnoQomlVw5qi6jp08q8lHpBSl4RehWyHQtY3uOIAGdghDk/mO+/Xpm\n" + + " 9JA5UVrPyDV0f+2q/YAHuwvP11iCkBQkocmFvgTSxN8H+DwFFPrVVUudQYZV7UDDycXoM6UE\n" + + " cdfzLLzVNPOAHEDIi/uzoV4sUqZ18=;\n" + + " bh=KWSe46TZKCcDbH4klJPo+tjk5LWJnVRlP5pvjXFZYLQ=; c=relaxed/relaxed;\n" + + " d=example.org; h=from:to:date:subject:mime-version:arc-authentication-results;\n" + + " i=1; s=dummy; t=12345\n" + + "ARC-Authentication-Results: i=1; lists.example.org;\n" + + " spf=pass smtp.mfrom=jqd@d1.example;\n" + + " dkim=pass (1024-bit key) header.i=@d1.example;\n" + + " dmarc=pass\n" + + baseMessageOneSignedTail(); + } + private String valimailSimpleHeaderCanonicalizationMessage( String arcSealSignature, String arcMessageSignature, From 5f04ea96f540877761bf82abab8a3a9c42fd9729 Mon Sep 17 00:00:00 2001 From: Artem Grinchenko Date: Sat, 18 Apr 2026 18:33:18 -0400 Subject: [PATCH 112/114] Updated README with a Test Coverage section --- README.adoc | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.adoc b/README.adoc index 76b9545..4cc4c1f 100644 --- a/README.adoc +++ b/README.adoc @@ -20,6 +20,17 @@ Current fork-specific focus includes: - ARC chain handling - Mail authentication extensions related to RFC 8617 +== Test Coverage + +ARC functionality is covered by tests in +https://github.com/apache/james-jdkim/blob/master/arc/src/test/java/org/apache/james/arc/ARCTest.java[ARCTest]. +The coverage is based on the ARC protocol requirements from +https://datatracker.ietf.org/doc/html/rfc8617[RFC 8617] and on the public +https://github.com/ValiMail/arc_test_suite[ValiMail ARC test suite]. The tests +exercise ARC set creation, chain validation, ARC-Seal verification, +ARC-Message-Signature canonicalization, body hash handling, required tag +validation, and malformed or tampered ARC header cases. + == Usage A full example is available in From 2f8955f8d0e7b3b544943285083fa78d3a80b6e8 Mon Sep 17 00:00:00 2001 From: Artem Grinchenko Date: Sun, 26 Apr 2026 11:56:58 -0400 Subject: [PATCH 113/114] Harden ARC validation and replace bundled PSL: - Validate every ARC-Message-Signature in multi-hop chains and use the ARC-Seal key from the instance being verified. - Parse ARC i= tags numerically, enforce the valid 1..50 range, and avoid i=1 matching i=10. - Harden AMS verification for unsupported algorithms, x=/t= expiry, no-body bh= checks, and reject ARC-Seal h= tags. - Fix multipart body reconstruction to emit CRLF after the closing boundary. - Return DMARC permerror for malformed From headers instead of throwing. - Replace the bundled public suffix list with Guava InternetDomainName and add Guava as a managed DMARC dependency. - Remove the SPF thread context classloader workaround. - Add ARC and DMARC regression tests for the validation, canonicalization, and malformed input cases above. - Refresh README ARC/DMARC documentation while preserving upstream DKIM content. --- README.adoc | 157 +- .../apache/james/arc/ARCChainValidator.java | 26 +- .../org/apache/james/arc/ARCVerifier.java | 107 +- .../arc/DNSPublicKeyRecordRetrieverArc.java | 1 - .../java/org/apache/james/arc/ARCTest.java | 225 + dmarc/pom.xml | 4 + .../org/apache/james/dmarc/DMARCVerifier.java | 4 +- .../james/dmarc/DmarcValidationResult.java | 10 + .../apache/james/dmarc/PublicSuffixList.java | 83 +- .../src/main/resources/public_suffix_list.dat | 15997 ---------------- .../org/apache/james/dmarc/DMARCTest.java | 51 +- .../james/dmarc/PublicSuffixListTest.java | 4 +- pom.xml | 6 + 13 files changed, 544 insertions(+), 16131 deletions(-) delete mode 100644 dmarc/src/main/resources/public_suffix_list.dat diff --git a/README.adoc b/README.adoc index 4cc4c1f..90580aa 100644 --- a/README.adoc +++ b/README.adoc @@ -1,40 +1,96 @@ -= JAMES jDKIM fork with ARC support += JAMES jDKIM library -This repository is a fork of Apache James jDKIM that adds ARC (Authenticated Received Chain) signing and related mail authentication capabilities alongside -the original DKIM functionality. -It is intended for experimentation and integration work around RFC 8617 in Java-based mail processing workflows. - -Original upstream project: -https://github.com/apache/james-jdkim +Library dealing with parsing and cryptography to sign and verify DKIM signatures. +It also provides DMARC verification and ARC (Authenticated Received Chain) +support for Java-based mail processing workflows. The mailet has been moved to James project: https://github.com/apache/james-project/tree/master/server/mailet/dkim -== Fork-specific additions +== Usage -This fork extends the original Apache James jDKIM codebase with ARC-related functionality. +A full example is available in +https://github.com/apache/james-jdkim/blob/master/main/src/test/java/org/apache/james/jdkim/DKIMTest.java[DKIMTest] -Current fork-specific focus includes: +=== Signing -- ARC signing support -- ARC chain handling -- Mail authentication extensions related to RFC 8617 +Signing a mime message can be achieved using the following snippet -== Test Coverage +[source,java] +---- +import java.io.InputStream; +import java.security.PrivateKey; -ARC functionality is covered by tests in -https://github.com/apache/james-jdkim/blob/master/arc/src/test/java/org/apache/james/arc/ARCTest.java[ARCTest]. -The coverage is based on the ARC protocol requirements from -https://datatracker.ietf.org/doc/html/rfc8617[RFC 8617] and on the public -https://github.com/ValiMail/arc_test_suite[ValiMail ARC test suite]. The tests -exercise ARC set creation, chain validation, ARC-Seal verification, -ARC-Message-Signature canonicalization, body hash handling, required tag -validation, and malformed or tampered ARC header cases. +String signatureTemplate = "v=1; a=rsa-sha256; c=simple; d=messiah.edu; h=date:from:subject; q=dns/txt; s=selector2;"; -== Usage +PrivateKey privateKey = null; +DKIMSigner dkimSigner = new DKIMSigner(signatureTemplate, privateKey); +// You need to provide the input stream of the mime message, it will be parsed +// by mime4j +InputStream stream = null; +String signature = dkimSigner.sign(inputStream); +// `signature` contains the full header +// DKIM-Signature: a=rsa-sha256; q=dns/txt; b=Axa8s/g...U1SIw==; c=simple; s=selector2; d=messiah.edu; v=1; bh=6pQ...6g=; h=date:from:subject; +---- -A full example is available in -https://github.com/apache/james-jdkim/blob/master/arc/src/test/java/org/apache/james/arc/ARCTest.java[ARCTest] +More advanced usage such as including multiple signatures can be found in +https://github.com/apache/james-jdkim/blob/master/main/src/test/java/org/apache/james/jdkim/DKIMTest.java[DKIMTest] + +=== Verifying + +Verifying a mime message DKIM signatures can be achieved using the following +snippet. The verifier always verifies all the signatures. + +[source,java] +---- +import java.io.InputStream; +// You can override the resolver in the constructor, use your own +// implementation of a retriever or use multiple implementations using a +// `MultiplexingPublicKeyRecordRetriever` +PublicKeyRecordRetriever keyRecordRetriever = new DNSPublicKeyRecordRetriever(); +DKIMVerifier verifier = new DKIMVerifier(keyRecordRetriever); +InputStream stream = null; // you need to provide the input stream of the mime message +List verifiedSignatures = verifier.verify(stream); +// `verifiedSignatures` contains only the signatures that have successfully +// passed the validation. +// If you want to query all the results including all the failures, you can +// retrieve them from the verifier +List results = verifier.getResults(); +---- + +=== Checking DMARC + +DMARC verification combines SPF and DKIM results with the RFC5322 `From` +domain and the domain DMARC policy. + +[source,java] +---- +import org.apache.james.dmarc.DMARCVerifier; +import org.apache.james.dmarc.DmarcValidationResult; +import org.apache.james.dmarc.PublicKeyRecordRetrieverDmarc; +import org.apache.james.mime4j.dom.Message; + +PublicKeyRecordRetrieverDmarc recordRetriever = null; +Message message = null; + +DMARCVerifier dmarcVerifier = new DMARCVerifier(recordRetriever); +DmarcValidationResult dmarcResult = dmarcVerifier.runDmarcCheck( + message, + "pass client-ip=192.0.2.1; envelope-from=sender@example.org", + "example.org", + "pass", + "example.org"); + +String authenticationResult = dmarcResult.toString(); +---- + +More complete DMARC examples can be found in +https://github.com/apache/james-jdkim/blob/master/dmarc/src/main/test/java/org/apache/james/dmarc/DMARCTest.java[DMARCTest]. + +== ARC Support + +ARC support adds RFC 8617 signing and validation for intermediaries that need to +preserve authentication results across forwarding hops. === Building An ARC Set @@ -83,9 +139,6 @@ String arcSeal = arcSet.get("ARC-Seal"); The generated map contains the ARC headers for the current hop, ready to be added to the message. -More complete usage can be found in -https://github.com/apache/james-jdkim/blob/master/arc/src/test/java/org/apache/james/arc/ARCTest.java[ARCTest] - === Validating An ARC Chain Validating ARC headers on a MIME message can be achieved using the following @@ -94,6 +147,7 @@ snippet. [source,java] ---- import java.io.InputStream; + import org.apache.james.arc.ARCChainValidator; import org.apache.james.arc.ArcValidationOutcome; import org.apache.james.arc.PublicKeyRetrieverArc; @@ -107,6 +161,51 @@ Message message = new DefaultMessageBuilder().parseMessage(stream); ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); ArcValidationOutcome validation = arcChainValidator.validateArcChain(message); -boolean valid = validation.isValid(); String chainValidation = validation.getResult().toString(); +String description = validation.getDescription(); +---- + +More complete ARC usage can be found in +https://github.com/apache/james-jdkim/blob/master/arc/src/test/java/org/apache/james/arc/ARCTest.java[ARCTest]. + +== Test Coverage + +ARC functionality is covered by tests in +https://github.com/apache/james-jdkim/blob/master/arc/src/test/java/org/apache/james/arc/ARCTest.java[ARCTest]. +The coverage is based on the ARC protocol requirements from +https://datatracker.ietf.org/doc/html/rfc8617[RFC 8617] and on the public +https://github.com/ValiMail/arc_test_suite[ValiMail ARC test suite]. The tests +exercise ARC set creation, chain validation, ARC-Seal verification, +ARC-Message-Signature canonicalization, body hash handling, required tag +validation, and malformed or tampered ARC header cases. + +== Cryptography Notice + +---- + This distribution includes cryptographic software. The country in + which you currently reside may have restrictions on the import, + possession, use, and/or re-export to another country, of + encryption software. BEFORE using any encryption software, please + check your country's laws, regulations and policies concerning the + import, possession, or use, and re-export of encryption software, to + see if this is permitted. See http://www.wassenaar.org for more + information. + + The U.S. Government Department of Commerce, Bureau of Industry and + Security (BIS), has classified this software as Export Commodity + Control Number (ECCN) 5D002.C.1, which includes information security + software using or performing cryptographic functions with asymmetric + algorithms. The form and manner of this Apache Software Foundation + distribution makes it eligible for export under the License Exception + ENC Technology Software Unrestricted (TSU) exception (see the BIS + Export Administration Regulations, Section 740.13) for both object + code and source code. + + The following provides more details on the included cryptographic + software: + + - jDKIM includes code designed to work with Java SE Security + + Export classifications and source links can be found + at http://www.apache.org/licenses/exports/. ---- diff --git a/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java b/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java index 5a2998a..e881ceb 100644 --- a/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java +++ b/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java @@ -91,15 +91,22 @@ private ArcValidationOutcome validatePreviousArcHops(Message message, Header mes return new ArcValidationOutcome(ArcValidationResult.FAIL, "ARC set structure is invalid"); } - Set prevArcSet; - prevArcSet = arcVerifier.extractArcSet(messageHeaders, numArcInstances); - if (prevArcSet != null) { - boolean amsOk = checkArcAms(prevArcSet, message, arcVerifier); - boolean asOk = checkArcSeal(messageHeaders.getFields(), numArcInstances, arcVerifier); - if (amsOk && asOk) { - return new ArcValidationOutcome(ArcValidationResult.PASS, "All previous ARC hops validated successfully"); + // RFC 8617 section 5.2: verify AMS for every instance from i=1 to i=N. + for (int i = 1; i <= numArcInstances; i++) { + Set arcSet = arcVerifier.extractArcSet(messageHeaders, i); + if (arcSet == null || !checkArcAms(arcSet, message, arcVerifier)) { + return new ArcValidationOutcome(ArcValidationResult.FAIL, "Previous ARC hops validation failed"); } } + boolean asOk; + try { + asOk = checkArcSeal(messageHeaders.getFields(), numArcInstances, arcVerifier); + } catch (ArcException | IllegalArgumentException e) { + return new ArcValidationOutcome(ArcValidationResult.FAIL, e.getMessage()); + } + if (asOk) { + return new ArcValidationOutcome(ArcValidationResult.PASS, "All previous ARC hops validated successfully"); + } return new ArcValidationOutcome(ArcValidationResult.FAIL, "Previous ARC hops validation failed"); } @@ -127,10 +134,13 @@ private boolean checkArcSeal(List headers, int instToVerify, ARCVerifier boolean retVal = false; Map> arcHeadersByI = arcVerifier.getArcHeadersByI(headers); ArcSealVerifyData verifyData = arcVerifier.buildArcSealSigningData(arcHeadersByI, instToVerify); - Field arcSealHeader = headers.stream() + Field arcSealHeader = arcHeadersByI.get(instToVerify).stream() .filter(f -> f.getName().equalsIgnoreCase(ARC_SEAL)) .findFirst().orElse(null); if (arcSealHeader == null) return retVal; + String algorithm = arcVerifier.parseTagGeneric(arcSealHeader.getBody(), "a"); + if (algorithm == null || algorithm.isEmpty()) return false; + arcVerifier.validateSupportedAlgorithm(ARC_SEAL, algorithm); String txtDnsRecord = arcVerifier.getTxtDnsRecordByField(arcSealHeader); if (txtDnsRecord == null) return retVal; diff --git a/arc/src/main/java/org/apache/james/arc/ARCVerifier.java b/arc/src/main/java/org/apache/james/arc/ARCVerifier.java index 74ba23e..79d95d1 100644 --- a/arc/src/main/java/org/apache/james/arc/ARCVerifier.java +++ b/arc/src/main/java/org/apache/james/arc/ARCVerifier.java @@ -87,6 +87,8 @@ public class ARCVerifier { private static final Pattern DOMAIN_PATTERN = Pattern.compile("(?i)^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+$"); private static final Pattern SELECTOR_PATTERN = Pattern.compile("(?i)^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$"); private static final int MIN_RSA_KEY_BITS = 1024; + private static final int MIN_ARC_INSTANCE = 1; + private static final int MAX_ARC_INSTANCE = 50; private static final String DNS_RECORD_TYPE = "_domainkey"; private PublicKeyRetrieverArc _keyRecordRetriever; @@ -103,10 +105,12 @@ public boolean verifyAms(Field amsField, Message message, String publicKeyDnsRec String signedHeaders = tags.get("h"); String bodyHash = tags.get("bh"); String signatureB64 = tags.get("b"); - if (!validateAmsTags(tags) || !"rsa-sha256".equals(algorithm) || bodyHash == null || bodyHash.isEmpty() + if (!validateAmsTags(tags) || algorithm == null || algorithm.isEmpty() || bodyHash == null || bodyHash.isEmpty() || signatureB64 == null || signatureB64.isEmpty()) { return false; } + validateSupportedAlgorithm("ARC-Message-Signature", algorithm); + validateAmsTimestamp(tags); String b64 = signatureB64 .replaceAll("\\s+", "") // remove spaces, tabs, newlines .replace(";", ""); // defensive: strip trailing semicolon if present @@ -166,6 +170,12 @@ public boolean verifyAms(Field amsField, Message message, String publicKeyDnsRec return result; } + void validateSupportedAlgorithm(String headerName, String algorithm) { + if (!"rsa-sha256".equals(algorithm)) { + throw new ArcException(headerName + " uses unsupported algorithm: " + algorithm); + } + } + private boolean signsArcSealHeader(String signedHeaders) { return Arrays.stream(signedHeaders.split(":")) .map(String::trim) @@ -177,13 +187,31 @@ private boolean validateAmsTags(Map tags) { String domain = tags.get("d"); String selector = tags.get("s"); String timestamp = tags.get("t"); + String expiration = tags.get("x"); if (domain == null || domain.isEmpty() || !DOMAIN_PATTERN.matcher(domain).matches()) { return false; } if (selector == null || selector.isEmpty() || !SELECTOR_PATTERN.matcher(selector).matches()) { return false; } - return timestamp == null || timestamp.matches("\\d+"); + return (timestamp == null || timestamp.matches("\\d+")) + && (expiration == null || expiration.matches("\\d+")); + } + + private void validateAmsTimestamp(Map tags) { + String timestamp = tags.get("t"); + String expiration = tags.get("x"); + if (expiration == null) { + return; + } + long expirationEpoch = Long.parseLong(expiration); + if (timestamp != null && expirationEpoch <= Long.parseLong(timestamp)) { + throw new ArcException("AMS x= expiration must be greater than t= timestamp"); + } + long now = System.currentTimeMillis() / 1000; + if (expirationEpoch < now) { + throw new ArcException("AMS signature is expired"); + } } private boolean verifyAmsBodyHash(Map tags, Message message, String bodyCanonicalization) { @@ -197,17 +225,12 @@ private boolean verifyAmsBodyHash(Map tags, Message message, Str byte[] computedBodyHash; try { - if (message.getBody() == null) { - return true; - } MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); - byte[] bodyBytes = readBodyBytes(message.getBody()); + byte[] bodyBytes = message.getBody() == null ? new byte[0] : readBodyBytes(message.getBody()); if (bodyBytes.length == 0 && message.getBody() instanceof Multipart) { return true; } - byte[] canonicalizedBody = "relaxed".equals(bodyCanonicalization) - ? canonicalizeRelaxedBody(bodyBytes) - : canonicalizeSimpleBody(bodyBytes); + byte[] canonicalizedBody = canonicalizeBody(bodyBytes, bodyCanonicalization); computedBodyHash = messageDigest.digest(canonicalizedBody); } catch (IOException | NoSuchAlgorithmException e) { return false; @@ -243,7 +266,7 @@ private void writeMultipart(Multipart multipart, ByteArrayOutputStream out) thro writeBody(part.getBody(), out); out.write("\r\n".getBytes(StandardCharsets.UTF_8)); } - out.write(("--" + boundary + "--").getBytes(StandardCharsets.UTF_8)); + out.write(("--" + boundary + "--\r\n").getBytes(StandardCharsets.UTF_8)); } private String getBoundary(Multipart multipart) { @@ -271,6 +294,12 @@ private byte[] canonicalizeSimpleBody(byte[] body) { return (normalized + "\r\n").getBytes(StandardCharsets.UTF_8); } + private byte[] canonicalizeBody(byte[] body, String bodyCanonicalization) { + return "relaxed".equals(bodyCanonicalization) + ? canonicalizeRelaxedBody(body) + : canonicalizeSimpleBody(body); + } + private byte[] canonicalizeRelaxedBody(byte[] body) { String normalized = new String(body, StandardCharsets.UTF_8).replaceAll("(?> arcHeadersByI) if (!cvOk) { throw new ArcException("ARC Chain validation fails due to cv check failing at instance [" + i + "]."); } + + boolean sealTagsOk = checkArcSealTags(arcSet); + if (!sealTagsOk) { + throw new ArcException("ARC Chain validation fails due to invalid ARC-Seal tags at instance [" + i + "]."); + } } return true; } + private boolean checkArcSealTags(List arcSet) { + Optional arcSealHeader = arcSet.stream() + .filter(f -> f.getName().equalsIgnoreCase(ARC_SEAL)) + .findFirst(); + return arcSealHeader + .map(field -> !parseTagList(field.getBody()).containsKey("h")) + .orElse(false); + } + private boolean checkCv(List lastArcSet, int instToVerify) { Optional arcSealHeader = lastArcSet.stream().filter(f -> f.getName().equalsIgnoreCase(ARC_SEAL)).findFirst(); if (arcSealHeader.isPresent()) { @@ -476,22 +519,30 @@ public Map> getArcHeadersByI(List headers) { for (Field f : headers) { String name = f.getName().toUpperCase(Locale.ROOT); if (name.startsWith("ARC-")) { - int i = -1; - String iTag = parseTagGeneric(f.getBody(), "i"); - if (iTag != null) { - i = Integer.parseInt(iTag); - } - if (i == -1) { - throw new IllegalStateException("ARC Header missing i= tag"); - } - else { - headersByI.computeIfAbsent(i, k -> new ArrayList<>()).add(f); - } + int i = parseArcInstance(f); + headersByI.computeIfAbsent(i, k -> new ArrayList<>()).add(f); } } return headersByI; } + private int parseArcInstance(Field field) { + String iTag = parseTagGeneric(field.getBody(), "i"); + if (iTag == null) { + throw new IllegalStateException("ARC Header missing i= tag"); + } + int instance; + try { + instance = Integer.parseInt(iTag); + } catch (NumberFormatException e) { + throw new IllegalStateException("ARC Header has invalid i= tag", e); + } + if (instance < MIN_ARC_INSTANCE || instance > MAX_ARC_INSTANCE) { + throw new IllegalStateException("ARC Header i= tag must be between 1 and 50"); + } + return instance; + } + public String canonicalizeBody(String body) { body = body.replaceAll("\r\n[\t ]", " "); body = body.replaceAll("[\t ]+", " "); @@ -568,7 +619,7 @@ public ArcSealVerifyData buildArcSealSigningData(Map> heade public Set extractArcSet(Header messageHeaders, int instance) { Set prevArcSet = null; for (Field field : messageHeaders.getFields()) { - if (field.getName().startsWith("ARC-") && field.getBody().contains("i="+instance)) { + if (field.getName().startsWith("ARC-") && hasArcInstance(field, instance)) { if (prevArcSet == null) { prevArcSet = new HashSet<>(); } @@ -578,6 +629,18 @@ public Set extractArcSet(Header messageHeaders, int instance) { return prevArcSet; } + private boolean hasArcInstance(Field field, int instance) { + String iTag = parseTagGeneric(field.getBody(), "i"); + if (iTag == null) { + return false; + } + try { + return Integer.parseInt(iTag) == instance; + } catch (NumberFormatException e) { + return false; + } + } + public String getTxtDnsRecordByField(Field signedHeader) { String dnsQuery = buildDnsQuery(signedHeader, DNS_RECORD_TYPE); if (dnsQuery == null || dnsQuery.isEmpty()) return null; // corrupted AMS - unable to pull PubKey from DNS diff --git a/arc/src/main/java/org/apache/james/arc/DNSPublicKeyRecordRetrieverArc.java b/arc/src/main/java/org/apache/james/arc/DNSPublicKeyRecordRetrieverArc.java index 44813f8..18719dd 100644 --- a/arc/src/main/java/org/apache/james/arc/DNSPublicKeyRecordRetrieverArc.java +++ b/arc/src/main/java/org/apache/james/arc/DNSPublicKeyRecordRetrieverArc.java @@ -33,7 +33,6 @@ public DNSPublicKeyRecordRetrieverArc() { @Override public String getSpfRecord(String helo, String from, String ip) { - Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); SPF spf = new DefaultSPF(); return spf.checkSPF(ip, from, helo).getHeaderText(); } diff --git a/arc/src/test/java/org/apache/james/arc/ARCTest.java b/arc/src/test/java/org/apache/james/arc/ARCTest.java index 62f16c3..080f688 100644 --- a/arc/src/test/java/org/apache/james/arc/ARCTest.java +++ b/arc/src/test/java/org/apache/james/arc/ARCTest.java @@ -18,11 +18,16 @@ ******************************************************************************/ package org.apache.james.arc; +import org.apache.james.arc.exceptions.ArcException; import org.apache.james.dmarc.MockPublicKeyRecordRetrieverDmarc; import org.apache.james.jdkim.DKIMCommon; import org.apache.james.jdkim.MockPublicKeyRecordRetriever; +import org.apache.james.mime4j.dom.Body; import org.apache.james.mime4j.dom.Message; +import org.apache.james.mime4j.message.BodyPartBuilder; import org.apache.james.mime4j.message.DefaultMessageBuilder; +import org.apache.james.mime4j.message.MultipartBuilder; +import org.apache.james.mime4j.stream.NameValuePair; import org.apache.james.mime4j.stream.RawField; import org.junit.Test; @@ -34,12 +39,16 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.lang.reflect.Method; import java.net.URISyntaxException; import java.net.URL; import java.nio.charset.StandardCharsets; +import java.security.Signature; import java.util.Map; +import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; public class ARCTest { public static final String AUTHENTICATION_RESULTS = "Authentication-Results"; @@ -71,6 +80,28 @@ public class ARCTest { "softfail (spfCheck: transitioning domain of d1.example does not designate 222.222.222.222 as permitted sender) client-ip=222.222.222.222; envelope-from=jqd@d1.example; helo=d1.example") ); + private final MockPublicKeyRecordRetrieverArc mixedArcKeyRecordRetriever = new MockPublicKeyRecordRetrieverArc( dmarcRetriever, + MockPublicKeyRecordRetriever.Record.of( + "arc", + "dmarc.example", + "k=rsa; p=" + Base64.getEncoder().encodeToString(ArcTestKeys.publicKeyArc.getEncoded()) + ";" + ), + MockPublicKeyRecordRetriever.Record.of( + "arc-alt", + "alt.example", + "k=rsa; p=" + Base64.getEncoder().encodeToString(ArcTestKeys.publicKeyDkim.getEncoded()) + ";" + ), + MockPublicKeyRecordRetriever.Record.of( + "origin2015", + "d1.example", + "k=rsa; p=" + Base64.getEncoder().encodeToString(ArcTestKeys.publicKeyDkim.getEncoded()) + ";" + ), + MockPublicKeyRecordRetrieverArc.SpfRecord.spfOf("d1.example", + "jqd@d1.example", + "222.222.222.222", + "softfail (spfCheck: transitioning domain of d1.example does not designate 222.222.222.222 as permitted sender) client-ip=222.222.222.222; envelope-from=jqd@d1.example; helo=d1.example") + ); + private static final String VALIMAIL_DUMMY_PUBLIC_KEY = "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDkHlOQoBTzWRiGs5V6NpP3id" + "Y6Wk08a5qhdR6wy5bdOKb2jLQiY/J16JYi0Qvx/byYzCNb3W91y3FutACDfzwQ/BC/e/8uBsCR+yz1Lx" @@ -562,6 +593,62 @@ public void validate_arc_chain_fails_when_i1_ams_corrupted_after_chain_built() t assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); } + // cv_fail_i2_1_ams1_invalid_resigned: if a forwarder corrupts i=1 AMS and then re-seals the chain + // at i=2 (so i=2 AS honestly covers the corrupted i=1 AMS), the chain must still be rejected + // because the validator must independently verify every AMS, not just the last one. + @Test + public void validate_arc_chain_fails_when_i1_ams_corrupted_and_chain_resigned_at_i2() throws Exception { + ByteArrayInputStream emailStream = readFileToByteArrayInputStream("/mail/rfc8617_no_arc.eml"); + Message message = new DefaultMessageBuilder().parseMessage(emailStream); + + // Build valid i=1 ARC set + Map hop1 = arcSetBuilder.buildArcSet(message, HELO, MAIL_FROM, IP, keyRecordRetriever); + for (Map.Entry entry : hop1.entrySet()) { + message.getHeader().addField(new RawField(entry.getKey(), entry.getValue())); + } + + // Corrupt i=1 AMS before hop 2 seals the chain + corruptSignatureOnHeader(message, ARC_MESSAGE_SIGNATURE, "i=1"); + + // Build i=2 ARC set over the already-corrupted chain; i=2 AS honestly covers corrupted i=1 AMS + Map hop2 = arcSetBuilder.buildArcSet(message, HELO, MAIL_FROM, IP, keyRecordRetriever); + for (Map.Entry entry : hop2.entrySet()) { + message.getHeader().addField(new RawField(entry.getKey(), entry.getValue())); + } + + ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + } + + // cv_pass_i2_as_keys_differ: ARC-Seal verification must use the key from the seal being verified, + // not an earlier seal in the chain. + @Test + public void validate_arc_chain_passes_when_latest_arc_seal_uses_different_key_than_previous_seal() throws Exception { + ByteArrayInputStream emailStream = readFileToByteArrayInputStream("/mail/rfc8617_no_arc.eml"); + Message message = new DefaultMessageBuilder().parseMessage(emailStream); + ArcSetBuilder altArcSetBuilder = new ArcSetBuilder( + ArcTestKeys.privateKeyDkim, + "i=; a=rsa-sha256; c=relaxed/relaxed; d=alt.example; s=arc-alt; t=; h=Subject:From:To; bh=; b=", + "i=; cv=; a=rsa-sha256; d=alt.example; s=arc-alt; t=; b=", + AUTH_SERVICE, + TIMESTAMP); + + Map hop1 = altArcSetBuilder.buildArcSet(message, HELO, MAIL_FROM, IP, mixedArcKeyRecordRetriever); + for (Map.Entry entry : hop1.entrySet()) { + message.getHeader().addField(new RawField(entry.getKey(), entry.getValue())); + } + + Map hop2 = arcSetBuilder.buildArcSet(message, HELO, MAIL_FROM, IP, mixedArcKeyRecordRetriever); + for (Map.Entry entry : hop2.entrySet()) { + message.getHeader().addField(new RawField(entry.getKey(), entry.getValue())); + } + + ARCChainValidator arcChainValidator = new ARCChainValidator(mixedArcKeyRecordRetriever); + ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); + assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("pass"); + } + // cv_pass_i3_1: a three-hop chain where every ARC set is valid should validate as cv=pass. @Test public void validate_arc_chain_passes_for_valid_three_hop_chain() throws Exception { @@ -589,6 +676,48 @@ public void validate_arc_chain_passes_for_valid_five_hop_chain() throws Exceptio assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("pass"); } + // arc_set_extract_i10: extracting i=1 must not accidentally include i=10 ARC headers. + @Test + public void extract_arc_set_matches_exact_instance_number() throws Exception { + Message message = new DefaultMessageBuilder().parseMessage( + new ByteArrayInputStream("Subject: exact instance test\n\nbody".getBytes(StandardCharsets.UTF_8))); + message.getHeader().addField(new RawField(ARC_AUTHENTICATION_RESULTS, "i=1; mx.example; arc=none")); + message.getHeader().addField(new RawField(ARC_MESSAGE_SIGNATURE, "i=1; d=example.org; s=arc; b=one")); + message.getHeader().addField(new RawField(ARC_SEAL, "i=1; cv=none; d=example.org; s=arc; b=one")); + message.getHeader().addField(new RawField(ARC_AUTHENTICATION_RESULTS, "i=10; mx.example; arc=pass")); + message.getHeader().addField(new RawField(ARC_MESSAGE_SIGNATURE, "i=10; d=example.org; s=arc; b=ten")); + message.getHeader().addField(new RawField(ARC_SEAL, "i=10; cv=pass; d=example.org; s=arc; b=ten")); + ARCVerifier arcVerifier = new ARCVerifier(keyRecordRetriever); + + Set arcSet = arcVerifier.extractArcSet(message.getHeader(), 1); + + assertThat(arcSet).hasSize(3); + assertThat(arcSet) + .allMatch(field -> "1".equals(arcVerifier.parseTagGeneric(field.getBody(), "i"))); + } + + @Test + public void arc_header_grouping_rejects_zero_instance_number() throws Exception { + Message message = new DefaultMessageBuilder().parseMessage( + new ByteArrayInputStream("Subject: invalid instance test\n\nbody".getBytes(StandardCharsets.UTF_8))); + message.getHeader().addField(new RawField(ARC_AUTHENTICATION_RESULTS, "i=0; mx.example; arc=none")); + + assertThatThrownBy(() -> new ARCVerifier(keyRecordRetriever).getArcHeadersByI(message.getHeader().getFields())) + .isInstanceOf(IllegalStateException.class) + .hasMessage("ARC Header i= tag must be between 1 and 50"); + } + + @Test + public void arc_header_grouping_rejects_instance_number_above_fifty() throws Exception { + Message message = new DefaultMessageBuilder().parseMessage( + new ByteArrayInputStream("Subject: invalid instance test\n\nbody".getBytes(StandardCharsets.UTF_8))); + message.getHeader().addField(new RawField(ARC_AUTHENTICATION_RESULTS, "i=51; mx.example; arc=pass")); + + assertThatThrownBy(() -> new ARCVerifier(keyRecordRetriever).getArcHeadersByI(message.getHeader().getFields())) + .isInstanceOf(IllegalStateException.class) + .hasMessage("ARC Header i= tag must be between 1 and 50"); + } + // ams_struct_i_na: an ARC-Message-Signature with no i= tag at all must be rejected. @Test public void validate_arc_chain_fails_when_ams_has_no_instance_tag() throws Exception { @@ -735,6 +864,20 @@ public void validate_arc_chain_fails_when_ams_algorithm_is_sha1() throws Excepti assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); } + @Test + public void verify_ams_throws_clear_exception_when_algorithm_is_sha1() throws Exception { + ByteArrayInputStream emailStream = readFileToByteArrayInputStream("/mail/rfc8617_no_arc.eml"); + Message message = new DefaultMessageBuilder().parseMessage(emailStream); + Map arcSet = arcSetBuilder.buildArcSet(message, HELO, MAIL_FROM, IP, keyRecordRetriever); + Field amsField = new RawField(ARC_MESSAGE_SIGNATURE, + arcSet.get(ARC_MESSAGE_SIGNATURE).replace("a=rsa-sha256", "a=rsa-sha1")); + String publicKeyDnsRecord = "k=rsa; p=" + Base64.getEncoder().encodeToString(ArcTestKeys.publicKeyArc.getEncoded()) + ";"; + + assertThatThrownBy(() -> new ARCVerifier(keyRecordRetriever).verifyAms(amsField, message, publicKeyDnsRecord)) + .isInstanceOf(ArcException.class) + .hasMessage("ARC-Message-Signature uses unsupported algorithm: rsa-sha1"); + } + // ams_fields_a_unknown: unknown AMS signature algorithms must be rejected. @Test public void validate_arc_chain_fails_when_ams_algorithm_is_unknown() throws Exception { @@ -1047,6 +1190,21 @@ public void validate_arc_chain_passes_when_relaxed_body_has_no_extra_trailing_cr baseMessageOneSignedHeaders() + baseMessageOneBody())); } + @Test + public void multipart_body_reconstruction_adds_crlf_after_closing_boundary() throws Exception { + Body body = MultipartBuilder.create("alternative") + .addContentTypeParameter(new NameValuePair("boundary", "abc")) + .addBodyPart(BodyPartBuilder.create() + .setContentType("text/plain") + .setBody("plain", StandardCharsets.UTF_8)) + .build(); + + byte[] bodyBytes = readBodyBytesWithVerifier(body); + + assertThat(new String(bodyBytes, StandardCharsets.UTF_8)) + .endsWith("--abc--\r\n"); + } + // ams_fields_bh_na: missing bh= must be rejected. @Test public void validate_arc_chain_fails_when_ams_body_hash_tag_is_missing() throws Exception { @@ -1071,6 +1229,22 @@ public void validate_arc_chain_fails_when_ams_body_hash_is_modified() throws Exc assertValimailFixtureFails(valimailInvalidBodyHashMessage("Z3JlbWxpbnM=")); } + @Test + public void verify_ams_fails_when_no_body_message_has_wrong_body_hash() throws Exception { + Message message = new DefaultMessageBuilder().parseMessage(new ByteArrayInputStream( + ("From: sender@example.org\r\n" + + "To: recipient@example.org\r\n" + + "Subject: no body\r\n").getBytes(StandardCharsets.UTF_8))); + String amsWithoutSignature = "i=1; a=rsa-sha256; c=relaxed/relaxed; d=dmarc.example; s=arc; " + + "t=12345; h=from:to:subject; bh=KWSe46TZKCcDbH4klJPo+tjk5LWJnVRlP5pvjXFZYLQ=; b="; + String signature = signRelaxedAmsForNoBodyMessage(message, amsWithoutSignature); + Field amsField = new RawField(ARC_MESSAGE_SIGNATURE, amsWithoutSignature + signature); + String publicKeyDnsRecord = "k=rsa; p=" + Base64.getEncoder().encodeToString(ArcTestKeys.publicKeyArc.getEncoded()) + ";"; + + assertThat(new ARCVerifier(keyRecordRetriever).verifyAms(amsField, message, publicKeyDnsRecord)) + .isFalse(); + } + // ams_fields_bh_mod_body: body changes outside relaxed canonicalization must be rejected. @Test public void validate_arc_chain_fails_when_ams_signed_body_is_modified() throws Exception { @@ -1477,6 +1651,40 @@ public void validate_arc_chain_fails_when_ams_timestamp_tag_is_invalid() throws "icecream")); } + @Test + public void verify_ams_throws_clear_exception_when_signature_is_expired() throws Exception { + Message message = new DefaultMessageBuilder().parseMessage(new ByteArrayInputStream( + ("From: sender@example.org\r\n" + + "To: recipient@example.org\r\n" + + "Subject: expired ams\r\n").getBytes(StandardCharsets.UTF_8))); + String amsWithoutSignature = "i=1; a=rsa-sha256; c=relaxed/relaxed; d=dmarc.example; s=arc; " + + "t=1; x=2; h=from:to:subject; bh=KWSe46TZKCcDbH4klJPo+tjk5LWJnVRlP5pvjXFZYLQ=; b="; + String signature = signRelaxedAmsForNoBodyMessage(message, amsWithoutSignature); + Field amsField = new RawField(ARC_MESSAGE_SIGNATURE, amsWithoutSignature + signature); + String publicKeyDnsRecord = "k=rsa; p=" + Base64.getEncoder().encodeToString(ArcTestKeys.publicKeyArc.getEncoded()) + ";"; + + assertThatThrownBy(() -> new ARCVerifier(keyRecordRetriever).verifyAms(amsField, message, publicKeyDnsRecord)) + .isInstanceOf(ArcException.class) + .hasMessage("AMS signature is expired"); + } + + @Test + public void verify_ams_throws_clear_exception_when_expiration_is_not_after_timestamp() throws Exception { + Message message = new DefaultMessageBuilder().parseMessage(new ByteArrayInputStream( + ("From: sender@example.org\r\n" + + "To: recipient@example.org\r\n" + + "Subject: invalid ams lifetime\r\n").getBytes(StandardCharsets.UTF_8))); + String amsWithoutSignature = "i=1; a=rsa-sha256; c=relaxed/relaxed; d=dmarc.example; s=arc; " + + "t=200; x=100; h=from:to:subject; bh=KWSe46TZKCcDbH4klJPo+tjk5LWJnVRlP5pvjXFZYLQ=; b="; + String signature = signRelaxedAmsForNoBodyMessage(message, amsWithoutSignature); + Field amsField = new RawField(ARC_MESSAGE_SIGNATURE, amsWithoutSignature + signature); + String publicKeyDnsRecord = "k=rsa; p=" + Base64.getEncoder().encodeToString(ArcTestKeys.publicKeyArc.getEncoded()) + ";"; + + assertThatThrownBy(() -> new ARCVerifier(keyRecordRetriever).verifyAms(amsField, message, publicKeyDnsRecord)) + .isInstanceOf(ArcException.class) + .hasMessage("AMS x= expiration must be greater than t= timestamp"); + } + // aar_struct_i_na / aar_i_missing: an ARC-Authentication-Results header without i= is invalid. @Test public void validate_arc_chain_fails_when_aar_has_no_instance_tag() throws Exception { @@ -2986,6 +3194,23 @@ private String valimailInvalidBodyHashMessage(String bodyHash) { baseMessageOneBody()); } + private String signRelaxedAmsForNoBodyMessage(Message message, String amsWithoutSignature) throws Exception { + String signingData = "from:" + message.getHeader().getField("From").getBody() + "\r\n" + + "to:" + message.getHeader().getField("To").getBody() + "\r\n" + + "subject:" + message.getHeader().getField("Subject").getBody() + "\r\n" + + "arc-message-signature:" + amsWithoutSignature; + Signature signature = Signature.getInstance("SHA256withRSA"); + signature.initSign(ArcTestKeys.privateKeyArc); + signature.update(signingData.getBytes(StandardCharsets.UTF_8)); + return Base64.getEncoder().encodeToString(signature.sign()); + } + + private byte[] readBodyBytesWithVerifier(Body body) throws Exception { + Method readBodyBytes = ARCVerifier.class.getDeclaredMethod("readBodyBytes", Body.class); + readBodyBytes.setAccessible(true); + return (byte[]) readBodyBytes.invoke(new ARCVerifier(keyRecordRetriever), body); + } + private String valimailAmsBodyHashMessage( String arcSealSignature, String arcMessageSignature, diff --git a/dmarc/pom.xml b/dmarc/pom.xml index a1f3f1f..ac550af 100644 --- a/dmarc/pom.xml +++ b/dmarc/pom.xml @@ -55,6 +55,10 @@ org.apache.james apache-mime4j-dom + + com.google.guava + guava + org.assertj assertj-core diff --git a/dmarc/src/main/java/org/apache/james/dmarc/DMARCVerifier.java b/dmarc/src/main/java/org/apache/james/dmarc/DMARCVerifier.java index 1563cc2..c51be12 100644 --- a/dmarc/src/main/java/org/apache/james/dmarc/DMARCVerifier.java +++ b/dmarc/src/main/java/org/apache/james/dmarc/DMARCVerifier.java @@ -40,13 +40,13 @@ public DmarcValidationResult runDmarcCheck(Message message, String spfHeaderText String shortSpfResult = spfHeaderText.split(" ")[0]; MailboxList mailboxList = message.getFrom(); if (mailboxList == null || mailboxList.size() != 1) { - throw new DmarcException("Incorrect From header: must have exactly one mailbox"); // rejecting immediately unless exactly one mailbox + return new DmarcValidationResult("permerror", null, null, "From header must contain exactly one mailbox"); } Mailbox mailbox = message.getFrom().get(0); String fromDomain = mailbox.getDomain(); if (fromDomain == null || fromDomain.isEmpty()) { - throw new DmarcException("From header is missing or has no domain part"); + return new DmarcValidationResult("permerror", null, null, "From header is missing a domain"); } // 2. Fetch DMARC record from DNS diff --git a/dmarc/src/main/java/org/apache/james/dmarc/DmarcValidationResult.java b/dmarc/src/main/java/org/apache/james/dmarc/DmarcValidationResult.java index 566cbc6..8f26d1e 100644 --- a/dmarc/src/main/java/org/apache/james/dmarc/DmarcValidationResult.java +++ b/dmarc/src/main/java/org/apache/james/dmarc/DmarcValidationResult.java @@ -21,18 +21,28 @@ public class DmarcValidationResult { private static final String DEFAULT_RESPONSE_TEMPLATE = "dmarc=%s (p=%s) header.from=%s"; private static final String DEFAULT_NONE_RESPONSE_TEMPLATE = "dmarc=none (no policy) header.from=%s"; + private static final String DEFAULT_ERROR_RESPONSE_TEMPLATE = "dmarc=%s reason=\"%s\""; private final String result; private final String policy; private final String domain; + private final String reason; public DmarcValidationResult(String result, String policy, String domain) { + this(result, policy, domain, null); + } + + public DmarcValidationResult(String result, String policy, String domain, String reason) { this.result = result; this.policy = policy; this.domain = domain; + this.reason = reason; } @Override public String toString() { + if ("permerror".equals(result) && reason != null) { + return String.format(DEFAULT_ERROR_RESPONSE_TEMPLATE, result, reason); + } return (policy == null || result == null) ? String.format(DEFAULT_NONE_RESPONSE_TEMPLATE, domain) : String.format(DEFAULT_RESPONSE_TEMPLATE, result, policy, domain); diff --git a/dmarc/src/main/java/org/apache/james/dmarc/PublicSuffixList.java b/dmarc/src/main/java/org/apache/james/dmarc/PublicSuffixList.java index ca30051..6f2fed5 100644 --- a/dmarc/src/main/java/org/apache/james/dmarc/PublicSuffixList.java +++ b/dmarc/src/main/java/org/apache/james/dmarc/PublicSuffixList.java @@ -18,82 +18,27 @@ ******************************************************************************/ package org.apache.james.dmarc; -import org.apache.james.dmarc.exceptions.DmarcException; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.Arrays; -import java.util.HashSet; import java.util.Locale; -import java.util.Set; - -public class PublicSuffixList { - private static final Set RULES = new HashSet<>(); - private static final Set WILDCARDS = new HashSet<>(); - private static final Set EXCEPTIONS = new HashSet<>(); - static { - try (InputStream is = PublicSuffixList.class.getResourceAsStream("/public_suffix_list.dat")) { - assert is != null; - parsePsl(is); - } - catch (Exception e) { - throw new DmarcException("Failed to load Public Suffix List", e); - } - } - - private static void parsePsl(InputStream is) { - try (BufferedReader reader = new BufferedReader(new InputStreamReader(is))) { - String line; - while ((line = reader.readLine()) != null) { - line = line.trim(); - if (line.isEmpty() || line.startsWith("//")) continue; - if (line.startsWith("!")) { - EXCEPTIONS.add(line.substring(1).toLowerCase()); - } else if (line.startsWith("*.")) { - WILDCARDS.add(line.substring(2).toLowerCase()); - } else { - RULES.add(line.toLowerCase()); - } - } - } catch (IOException e) { - throw new DmarcException("Failed to read Public Suffix List", e); - } - } +import com.google.common.net.InternetDomainName; +public class PublicSuffixList { private PublicSuffixList() {} public static String getOrgDomain(String domainToCheck) { - if (domainToCheck == null || domainToCheck.trim().isEmpty()) return domainToCheck; - - domainToCheck = domainToCheck.toLowerCase(Locale.ROOT).trim(); - String[] domainParts = domainToCheck.split("\\."); - int numParts = domainParts.length; - - PSLMatchOutcome outcome = null; - - for (int i = 0; i < numParts && outcome == null; i++) { - String[] candidateArr = Arrays.copyOfRange(domainParts, i, numParts); - String matchedCandidate = String.join(".", candidateArr); - - if (EXCEPTIONS.contains(matchedCandidate)) { - // Exception rules take precedence - outcome = new PSLMatchOutcome(PSLMatch.EXCEPTION, matchedCandidate, domainParts, i); - } - - if (WILDCARDS.contains(matchedCandidate)) { - outcome = new PSLMatchOutcome(PSLMatch.WILDCARD, matchedCandidate, domainParts, i); - } + if (domainToCheck == null || domainToCheck.trim().isEmpty()) { + return domainToCheck; + } - if (RULES.contains(matchedCandidate)) { - outcome = new PSLMatchOutcome(PSLMatch.RULE, matchedCandidate, domainParts, i); + String normalizedDomain = domainToCheck.toLowerCase(Locale.ROOT).trim(); + try { + InternetDomainName domainName = InternetDomainName.from(normalizedDomain); + if (domainName.isUnderPublicSuffix()) { + return domainName.topPrivateDomain().toString(); } + return normalizedDomain; + } catch (IllegalArgumentException e) { + return normalizedDomain; } - - return outcome == null? - new PSLMatchOutcome(PSLMatch.NONE, null, domainParts, -1).getRelaxedOrgDomain(): - outcome.getRelaxedOrgDomain(); } -} \ No newline at end of file +} diff --git a/dmarc/src/main/resources/public_suffix_list.dat b/dmarc/src/main/resources/public_suffix_list.dat deleted file mode 100644 index d047478..0000000 --- a/dmarc/src/main/resources/public_suffix_list.dat +++ /dev/null @@ -1,15997 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -// Please pull this list from, and only from https://publicsuffix.org/list/public_suffix_list.dat, -// rather than any other VCS sites. Pulling from any other URL is not guaranteed to be supported. - -// VERSION: 2025-10-13_13-22-32_UTC -// COMMIT: c002d19a8ca969110dd8a3838d972ca293bdc48f - -// Instructions on pulling and using this list can be found at https://publicsuffix.org/list/. - -// ===BEGIN ICANN DOMAINS=== - -// ac : http://nic.ac/rules.htm -ac -com.ac -edu.ac -gov.ac -mil.ac -net.ac -org.ac - -// ad : https://www.iana.org/domains/root/db/ad.html -// Confirmed by Amadeu Abril i Abril (CORE) 2024-11-17 -ad - -// ae : https://www.iana.org/domains/root/db/ae.html -ae -ac.ae -co.ae -gov.ae -mil.ae -net.ae -org.ae -sch.ae - -// aero : https://information.aero/registration/policies/dmp -aero -// 2LDs -airline.aero -airport.aero -// 2LDs (currently not accepting registration, seemingly never have) -// As of 2024-07, these are marked as reserved for potential 3LD -// registrations (clause 11 "allocated subdomains" in the 2006 TLD -// policy), but the relevant industry partners have not opened them up -// for registration. Current status can be determined from the TLD's -// policy document: 2LDs that are open for registration must list -// their policy in the TLD's policy. Any 2LD without such a policy is -// not open for registrations. -accident-investigation.aero -accident-prevention.aero -aerobatic.aero -aeroclub.aero -aerodrome.aero -agents.aero -air-surveillance.aero -air-traffic-control.aero -aircraft.aero -airtraffic.aero -ambulance.aero -association.aero -author.aero -ballooning.aero -broker.aero -caa.aero -cargo.aero -catering.aero -certification.aero -championship.aero -charter.aero -civilaviation.aero -club.aero -conference.aero -consultant.aero -consulting.aero -control.aero -council.aero -crew.aero -design.aero -dgca.aero -educator.aero -emergency.aero -engine.aero -engineer.aero -entertainment.aero -equipment.aero -exchange.aero -express.aero -federation.aero -flight.aero -freight.aero -fuel.aero -gliding.aero -government.aero -groundhandling.aero -group.aero -hanggliding.aero -homebuilt.aero -insurance.aero -journal.aero -journalist.aero -leasing.aero -logistics.aero -magazine.aero -maintenance.aero -marketplace.aero -media.aero -microlight.aero -modelling.aero -navigation.aero -parachuting.aero -paragliding.aero -passenger-association.aero -pilot.aero -press.aero -production.aero -recreation.aero -repbody.aero -res.aero -research.aero -rotorcraft.aero -safety.aero -scientist.aero -services.aero -show.aero -skydiving.aero -software.aero -student.aero -taxi.aero -trader.aero -trading.aero -trainer.aero -union.aero -workinggroup.aero -works.aero - -// af : https://www.nic.af/domain-price -af -com.af -edu.af -gov.af -net.af -org.af - -// ag : http://www.nic.ag/prices.htm -ag -co.ag -com.ag -net.ag -nom.ag -org.ag - -// ai : http://nic.com.ai/ -ai -com.ai -net.ai -off.ai -org.ai - -// al : http://www.ert.gov.al/ert_alb/faq_det.html?Id=31 -al -com.al -edu.al -gov.al -mil.al -net.al -org.al - -// am : https://www.amnic.net/policy/en/Policy_EN.pdf -// Confirmed by ISOC AM 2024-11-18 -am -co.am -com.am -commune.am -net.am -org.am - -// ao : https://www.iana.org/domains/root/db/ao.html -// https://www.dns.ao/ao/ -ao -co.ao -ed.ao -edu.ao -gov.ao -gv.ao -it.ao -og.ao -org.ao -pb.ao - -// aq : https://www.iana.org/domains/root/db/aq.html -aq - -// ar : https://nic.ar/es/nic-argentina/normativa -ar -bet.ar -com.ar -coop.ar -edu.ar -gob.ar -gov.ar -int.ar -mil.ar -musica.ar -mutual.ar -net.ar -org.ar -seg.ar -senasa.ar -tur.ar - -// arpa : https://www.iana.org/domains/root/db/arpa.html -// Confirmed by registry 2008-06-18 -arpa -e164.arpa -home.arpa -in-addr.arpa -ip6.arpa -iris.arpa -uri.arpa -urn.arpa - -// as : https://www.iana.org/domains/root/db/as.html -as -gov.as - -// asia : https://www.iana.org/domains/root/db/asia.html -asia - -// at : https://www.iana.org/domains/root/db/at.html -// Confirmed by registry 2008-06-17 -at -ac.at -sth.ac.at -co.at -gv.at -or.at - -// au : https://www.iana.org/domains/root/db/au.html -// https://www.auda.org.au/ -// Confirmed by registry 2025-07-16 -au -// 2LDs -asn.au -com.au -edu.au -gov.au -id.au -net.au -org.au -// Historic 2LDs (closed to new registration, but sites still exist) -conf.au -oz.au -// CGDNs : https://www.auda.org.au/au-domain-names/the-different-au-domain-names/state-and-territory-domain-names/ -act.au -nsw.au -nt.au -qld.au -sa.au -tas.au -vic.au -wa.au -// 3LDs -act.edu.au -catholic.edu.au -// eq.edu.au - Removed at the request of the Queensland Department of Education -nsw.edu.au -nt.edu.au -qld.edu.au -sa.edu.au -tas.edu.au -vic.edu.au -wa.edu.au -// act.gov.au - Bug 984824 - Removed at request of Greg Tankard -// nsw.gov.au - Bug 547985 - Removed at request of -// nt.gov.au - Bug 940478 - Removed at request of Greg Connors -qld.gov.au -sa.gov.au -tas.gov.au -vic.gov.au -wa.gov.au -// 4LDs -// education.tas.edu.au - Removed at the request of the Department of Education Tasmania -// schools.nsw.edu.au - Removed at the request of the New South Wales Department of Education. - -// aw : https://www.iana.org/domains/root/db/aw.html -aw -com.aw - -// ax : https://www.iana.org/domains/root/db/ax.html -ax - -// az : https://www.iana.org/domains/root/db/az.html -// Confirmed via https://whois.az/?page_id=10 2024-12-11 -az -biz.az -co.az -com.az -edu.az -gov.az -info.az -int.az -mil.az -name.az -net.az -org.az -pp.az -// No longer available for registration, however domains exist as of 2024-12-11 -// see https://whois.az/?page_id=783 -pro.az - -// ba : https://www.iana.org/domains/root/db/ba.html -ba -com.ba -edu.ba -gov.ba -mil.ba -net.ba -org.ba - -// bb : https://www.iana.org/domains/root/db/bb.html -bb -biz.bb -co.bb -com.bb -edu.bb -gov.bb -info.bb -net.bb -org.bb -store.bb -tv.bb - -// bd : https://www.iana.org/domains/root/db/bd.html -*.bd - -// be : https://www.iana.org/domains/root/db/be.html -// Confirmed by registry 2008-06-08 -be -ac.be - -// bf : https://www.iana.org/domains/root/db/bf.html -bf -gov.bf - -// bg : https://www.iana.org/domains/root/db/bg.html -// https://www.register.bg/user/static/rules/en/index.html -bg -0.bg -1.bg -2.bg -3.bg -4.bg -5.bg -6.bg -7.bg -8.bg -9.bg -a.bg -b.bg -c.bg -d.bg -e.bg -f.bg -g.bg -h.bg -i.bg -j.bg -k.bg -l.bg -m.bg -n.bg -o.bg -p.bg -q.bg -r.bg -s.bg -t.bg -u.bg -v.bg -w.bg -x.bg -y.bg -z.bg - -// bh : https://www.iana.org/domains/root/db/bh.html -bh -com.bh -edu.bh -gov.bh -net.bh -org.bh - -// bi : https://www.iana.org/domains/root/db/bi.html -// http://whois.nic.bi/ -bi -co.bi -com.bi -edu.bi -or.bi -org.bi - -// biz : https://www.iana.org/domains/root/db/biz.html -biz - -// bj : https://nic.bj/bj-suffixes.txt -// Submitted by registry -bj -africa.bj -agro.bj -architectes.bj -assur.bj -avocats.bj -co.bj -com.bj -eco.bj -econo.bj -edu.bj -info.bj -loisirs.bj -money.bj -net.bj -org.bj -ote.bj -restaurant.bj -resto.bj -tourism.bj -univ.bj - -// bm : https://www.bermudanic.bm/domain-registration/index.php -bm -com.bm -edu.bm -gov.bm -net.bm -org.bm - -// bn : http://www.bnnic.bn/faqs -bn -com.bn -edu.bn -gov.bn -net.bn -org.bn - -// bo : https://nic.bo -// Confirmed by registry 2024-11-19 -bo -com.bo -edu.bo -gob.bo -int.bo -mil.bo -net.bo -org.bo -tv.bo -web.bo -// Social Domains -academia.bo -agro.bo -arte.bo -blog.bo -bolivia.bo -ciencia.bo -cooperativa.bo -democracia.bo -deporte.bo -ecologia.bo -economia.bo -empresa.bo -indigena.bo -industria.bo -info.bo -medicina.bo -movimiento.bo -musica.bo -natural.bo -nombre.bo -noticias.bo -patria.bo -plurinacional.bo -politica.bo -profesional.bo -pueblo.bo -revista.bo -salud.bo -tecnologia.bo -tksat.bo -transporte.bo -wiki.bo - -// br : http://registro.br/dominio/categoria.html -// Submitted by registry -br -9guacu.br -abc.br -adm.br -adv.br -agr.br -aju.br -am.br -anani.br -aparecida.br -api.br -app.br -arq.br -art.br -ato.br -b.br -barueri.br -belem.br -bet.br -bhz.br -bib.br -bio.br -blog.br -bmd.br -boavista.br -bsb.br -campinagrande.br -campinas.br -caxias.br -cim.br -cng.br -cnt.br -com.br -contagem.br -coop.br -coz.br -cri.br -cuiaba.br -curitiba.br -def.br -des.br -det.br -dev.br -ecn.br -eco.br -edu.br -emp.br -enf.br -eng.br -esp.br -etc.br -eti.br -far.br -feira.br -flog.br -floripa.br -fm.br -fnd.br -fortal.br -fot.br -foz.br -fst.br -g12.br -geo.br -ggf.br -goiania.br -gov.br -// gov.br 26 states + df https://en.wikipedia.org/wiki/States_of_Brazil -ac.gov.br -al.gov.br -am.gov.br -ap.gov.br -ba.gov.br -ce.gov.br -df.gov.br -es.gov.br -go.gov.br -ma.gov.br -mg.gov.br -ms.gov.br -mt.gov.br -pa.gov.br -pb.gov.br -pe.gov.br -pi.gov.br -pr.gov.br -rj.gov.br -rn.gov.br -ro.gov.br -rr.gov.br -rs.gov.br -sc.gov.br -se.gov.br -sp.gov.br -to.gov.br -gru.br -ia.br -imb.br -ind.br -inf.br -jab.br -jampa.br -jdf.br -joinville.br -jor.br -jus.br -leg.br -leilao.br -lel.br -log.br -londrina.br -macapa.br -maceio.br -manaus.br -maringa.br -mat.br -med.br -mil.br -morena.br -mp.br -mus.br -natal.br -net.br -niteroi.br -*.nom.br -not.br -ntr.br -odo.br -ong.br -org.br -osasco.br -palmas.br -poa.br -ppg.br -pro.br -psc.br -psi.br -pvh.br -qsl.br -radio.br -rec.br -recife.br -rep.br -ribeirao.br -rio.br -riobranco.br -riopreto.br -salvador.br -sampa.br -santamaria.br -santoandre.br -saobernardo.br -saogonca.br -seg.br -sjc.br -slg.br -slz.br -social.br -sorocaba.br -srv.br -taxi.br -tc.br -tec.br -teo.br -the.br -tmp.br -trd.br -tur.br -tv.br -udi.br -vet.br -vix.br -vlog.br -wiki.br -xyz.br -zlg.br - -// bs : http://www.nic.bs/rules.html -bs -com.bs -edu.bs -gov.bs -net.bs -org.bs - -// bt : https://www.iana.org/domains/root/db/bt.html -bt -com.bt -edu.bt -gov.bt -net.bt -org.bt - -// bv : No registrations at this time. -// Submitted by registry -bv - -// bw : https://www.iana.org/domains/root/db/bw.html -// https://nic.net.bw/bw-name-structure -bw -ac.bw -co.bw -gov.bw -net.bw -org.bw - -// by : https://www.iana.org/domains/root/db/by.html -// http://tld.by/rules_2006_en.html -// list of other 2nd level tlds ? -by -gov.by -mil.by -// Official information does not indicate that com.by is a reserved -// second-level domain, but it's being used as one (see www.google.com.by and -// www.yahoo.com.by, for example), so we list it here for safety's sake. -com.by -// http://hoster.by/ -of.by - -// bz : https://www.iana.org/domains/root/db/bz.html -// http://www.belizenic.bz/ -bz -co.bz -com.bz -edu.bz -gov.bz -net.bz -org.bz - -// ca : https://www.iana.org/domains/root/db/ca.html -ca -// ca geographical names -ab.ca -bc.ca -mb.ca -nb.ca -nf.ca -nl.ca -ns.ca -nt.ca -nu.ca -on.ca -pe.ca -qc.ca -sk.ca -yk.ca -// gc.ca: https://en.wikipedia.org/wiki/.gc.ca -// see also: http://registry.gc.ca/en/SubdomainFAQ -gc.ca - -// cat : https://www.iana.org/domains/root/db/cat.html -cat - -// cc : https://www.iana.org/domains/root/db/cc.html -cc - -// cd : https://www.iana.org/domains/root/db/cd.html -// https://www.nic.cd -cd -gov.cd - -// cf : https://www.iana.org/domains/root/db/cf.html -cf - -// cg : https://www.iana.org/domains/root/db/cg.html -cg - -// ch : https://www.iana.org/domains/root/db/ch.html -ch - -// ci : https://www.iana.org/domains/root/db/ci.html -ci -ac.ci -aéroport.ci -asso.ci -co.ci -com.ci -ed.ci -edu.ci -go.ci -gouv.ci -int.ci -net.ci -or.ci -org.ci - -// ck : https://www.iana.org/domains/root/db/ck.html -*.ck -!www.ck - -// cl : https://www.nic.cl -// Confirmed by .CL registry -cl -co.cl -gob.cl -gov.cl -mil.cl - -// cm : https://www.iana.org/domains/root/db/cm.html plus bug 981927 -cm -co.cm -com.cm -gov.cm -net.cm - -// cn : https://www.iana.org/domains/root/db/cn.html -// Submitted by registry -cn -ac.cn -com.cn -edu.cn -gov.cn -mil.cn -net.cn -org.cn -公司.cn -網絡.cn -网络.cn -// cn geographic names -ah.cn -bj.cn -cq.cn -fj.cn -gd.cn -gs.cn -gx.cn -gz.cn -ha.cn -hb.cn -he.cn -hi.cn -hk.cn -hl.cn -hn.cn -jl.cn -js.cn -jx.cn -ln.cn -mo.cn -nm.cn -nx.cn -qh.cn -sc.cn -sd.cn -sh.cn -sn.cn -sx.cn -tj.cn -tw.cn -xj.cn -xz.cn -yn.cn -zj.cn - -// co : https://www.iana.org/domains/root/db/co.html -// https://www.cointernet.com.co/como-funciona-un-dominio-restringido -// Confirmed by registry 2024-11-18 -co -com.co -edu.co -gov.co -mil.co -net.co -nom.co -org.co - -// com : https://www.iana.org/domains/root/db/com.html -com - -// coop : https://www.iana.org/domains/root/db/coop.html -coop - -// cr : https://nic.cr/capitulo-1-registro-de-un-nombre-de-dominio/ -cr -ac.cr -co.cr -ed.cr -fi.cr -go.cr -or.cr -sa.cr - -// cu : https://www.iana.org/domains/root/db/cu.html -cu -com.cu -edu.cu -gob.cu -inf.cu -nat.cu -net.cu -org.cu - -// cv : https://www.iana.org/domains/root/db/cv.html -// https://ola.cv/domain-extensions-under-cv/ -// Confirmed by registry 2024-11-26 -cv -com.cv -edu.cv -id.cv -int.cv -net.cv -nome.cv -org.cv -publ.cv - -// cw : https://www.uoc.cw/cw-registry -// Confirmed by registry 2024-11-19 -cw -com.cw -edu.cw -net.cw -org.cw - -// cx : https://www.iana.org/domains/root/db/cx.html -// list of other 2nd level tlds ? -cx -gov.cx - -// cy : http://www.nic.cy/ -// Submitted by Panayiotou Fotia -// https://nic.cy/wp-content/uploads/2024/01/Create-Request-for-domain-name-registration-1.pdf -cy -ac.cy -biz.cy -com.cy -ekloges.cy -gov.cy -ltd.cy -mil.cy -net.cy -org.cy -press.cy -pro.cy -tm.cy - -// cz : https://www.iana.org/domains/root/db/cz.html -// Confirmed by registry 2025-08-06 -cz -gov.cz - -// de : https://www.iana.org/domains/root/db/de.html -// Confirmed by registry (with technical -// reservations) 2008-07-01 -de - -// dj : https://www.iana.org/domains/root/db/dj.html -dj - -// dk : https://www.iana.org/domains/root/db/dk.html -// Confirmed by registry 2008-06-17 -dk - -// dm : https://www.iana.org/domains/root/db/dm.html -// https://nic.dm/policies/pdf/DMRulesandGuidelines2024v1.pdf -// Confirmed by registry 2024-11-19 -dm -co.dm -com.dm -edu.dm -gov.dm -net.dm -org.dm - -// do : https://www.iana.org/domains/root/db/do.html -do -art.do -com.do -edu.do -gob.do -gov.do -mil.do -net.do -org.do -sld.do -web.do - -// dz : http://www.nic.dz/images/pdf_nic/charte.pdf -dz -art.dz -asso.dz -com.dz -edu.dz -gov.dz -net.dz -org.dz -pol.dz -soc.dz -tm.dz - -// ec : https://www.nic.ec/ -// Submitted by registry -ec -abg.ec -adm.ec -agron.ec -arqt.ec -art.ec -bar.ec -chef.ec -com.ec -cont.ec -cpa.ec -cue.ec -dent.ec -dgn.ec -disco.ec -doc.ec -edu.ec -eng.ec -esm.ec -fin.ec -fot.ec -gal.ec -gob.ec -gov.ec -gye.ec -ibr.ec -info.ec -k12.ec -lat.ec -loj.ec -med.ec -mil.ec -mktg.ec -mon.ec -net.ec -ntr.ec -odont.ec -org.ec -pro.ec -prof.ec -psic.ec -psiq.ec -pub.ec -rio.ec -rrpp.ec -sal.ec -tech.ec -tul.ec -tur.ec -uio.ec -vet.ec -xxx.ec - -// edu : https://www.iana.org/domains/root/db/edu.html -edu - -// ee : https://www.internet.ee/domains/general-domains-and-procedure-for-registration-of-sub-domains-under-general-domains -ee -aip.ee -com.ee -edu.ee -fie.ee -gov.ee -lib.ee -med.ee -org.ee -pri.ee -riik.ee - -// eg : https://www.iana.org/domains/root/db/eg.html -// https://domain.eg/en/domain-rules/subdomain-names-types/ -eg -ac.eg -com.eg -edu.eg -eun.eg -gov.eg -info.eg -me.eg -mil.eg -name.eg -net.eg -org.eg -sci.eg -sport.eg -tv.eg - -// er : https://www.iana.org/domains/root/db/er.html -*.er - -// es : https://www.dominios.es/en -es -com.es -edu.es -gob.es -nom.es -org.es - -// et : https://www.iana.org/domains/root/db/et.html -et -biz.et -com.et -edu.et -gov.et -info.et -name.et -net.et -org.et - -// eu : https://www.iana.org/domains/root/db/eu.html -eu - -// fi : https://www.iana.org/domains/root/db/fi.html -fi -// aland.fi : https://www.iana.org/domains/root/db/ax.html -// This domain is being phased out in favor of .ax. As there are still many -// domains under aland.fi, we still keep it on the list until aland.fi is -// completely removed. -aland.fi - -// fj : http://domains.fj/ -// Submitted by registry 2020-02-11 -fj -ac.fj -biz.fj -com.fj -gov.fj -info.fj -mil.fj -name.fj -net.fj -org.fj -pro.fj - -// fk : https://www.iana.org/domains/root/db/fk.html -*.fk - -// fm : https://www.iana.org/domains/root/db/fm.html -fm -com.fm -edu.fm -net.fm -org.fm - -// fo : https://www.iana.org/domains/root/db/fo.html -fo - -// fr : https://www.afnic.fr/ https://www.afnic.fr/wp-media/uploads/2022/12/afnic-naming-policy-2023-01-01.pdf -fr -asso.fr -com.fr -gouv.fr -nom.fr -prd.fr -tm.fr -// Other SLDs now selfmanaged out of AFNIC range. Former "domaines sectoriels", still registration suffixes -avoues.fr -cci.fr -greta.fr -huissier-justice.fr - -// ga : https://www.iana.org/domains/root/db/ga.html -ga - -// gb : This registry is effectively dormant -// Submitted by registry -gb - -// gd : https://www.iana.org/domains/root/db/gd.html -gd -edu.gd -gov.gd - -// ge : https://nic.ge/en/administrator/the-ge-domain-regulations -// Confirmed by registry 2024-11-20 -ge -com.ge -edu.ge -gov.ge -net.ge -org.ge -pvt.ge -school.ge - -// gf : https://www.iana.org/domains/root/db/gf.html -gf - -// gg : https://www.channelisles.net/register-1/register-direct -// Confirmed by registry 2013-11-28 -gg -co.gg -net.gg -org.gg - -// gh : https://www.iana.org/domains/root/db/gh.html -// https://www.nic.gh/ -// Although domains directly at second level are not possible at the moment, -// they have been possible for some time and may come back. -gh -biz.gh -com.gh -edu.gh -gov.gh -mil.gh -net.gh -org.gh - -// gi : http://www.nic.gi/rules.html -gi -com.gi -edu.gi -gov.gi -ltd.gi -mod.gi -org.gi - -// gl : https://www.iana.org/domains/root/db/gl.html -// http://nic.gl -gl -co.gl -com.gl -edu.gl -net.gl -org.gl - -// gm : http://www.nic.gm/htmlpages%5Cgm-policy.htm -gm - -// gn : http://psg.com/dns/gn/gn.txt -// Submitted by registry -gn -ac.gn -com.gn -edu.gn -gov.gn -net.gn -org.gn - -// gov : https://www.iana.org/domains/root/db/gov.html -gov - -// gp : http://www.nic.gp/index.php?lang=en -gp -asso.gp -com.gp -edu.gp -mobi.gp -net.gp -org.gp - -// gq : https://www.iana.org/domains/root/db/gq.html -gq - -// gr : https://www.iana.org/domains/root/db/gr.html -// Submitted by registry -gr -com.gr -edu.gr -gov.gr -net.gr -org.gr - -// gs : https://www.iana.org/domains/root/db/gs.html -gs - -// gt : https://www.gt/sitio/registration_policy.php?lang=en -gt -com.gt -edu.gt -gob.gt -ind.gt -mil.gt -net.gt -org.gt - -// gu : http://gadao.gov.gu/register.html -// University of Guam : https://www.uog.edu -// Submitted by uognoc@triton.uog.edu -gu -com.gu -edu.gu -gov.gu -guam.gu -info.gu -net.gu -org.gu -web.gu - -// gw : https://www.iana.org/domains/root/db/gw.html -// gw : https://nic.gw/regras/ -gw - -// gy : https://www.iana.org/domains/root/db/gy.html -// http://registry.gy/ -gy -co.gy -com.gy -edu.gy -gov.gy -net.gy -org.gy - -// hk : https://www.hkirc.hk -// Submitted by registry -hk -com.hk -edu.hk -gov.hk -idv.hk -net.hk -org.hk -个人.hk -個人.hk -公司.hk -政府.hk -敎育.hk -教育.hk -箇人.hk -組織.hk -組织.hk -網絡.hk -網络.hk -组織.hk -组织.hk -网絡.hk -网络.hk - -// hm : https://www.iana.org/domains/root/db/hm.html -hm - -// hn : https://www.iana.org/domains/root/db/hn.html -hn -com.hn -edu.hn -gob.hn -mil.hn -net.hn -org.hn - -// hr : http://www.dns.hr/documents/pdf/HRTLD-regulations.pdf -hr -com.hr -from.hr -iz.hr -name.hr - -// ht : http://www.nic.ht/info/charte.cfm -ht -adult.ht -art.ht -asso.ht -com.ht -coop.ht -edu.ht -firm.ht -gouv.ht -info.ht -med.ht -net.ht -org.ht -perso.ht -pol.ht -pro.ht -rel.ht -shop.ht - -// hu : https://www.iana.org/domains/root/db/hu.html -// Confirmed by registry 2008-06-12 -hu -2000.hu -agrar.hu -bolt.hu -casino.hu -city.hu -co.hu -erotica.hu -erotika.hu -film.hu -forum.hu -games.hu -hotel.hu -info.hu -ingatlan.hu -jogasz.hu -konyvelo.hu -lakas.hu -media.hu -news.hu -org.hu -priv.hu -reklam.hu -sex.hu -shop.hu -sport.hu -suli.hu -szex.hu -tm.hu -tozsde.hu -utazas.hu -video.hu - -// id : https://www.iana.org/domains/root/db/id.html -id -ac.id -biz.id -co.id -desa.id -go.id -kop.id -mil.id -my.id -net.id -or.id -ponpes.id -sch.id -web.id - -// ie : https://www.iana.org/domains/root/db/ie.html -ie -gov.ie - -// il : http://www.isoc.org.il/domains/ -// see also: https://en.isoc.org.il/il-cctld/registration-rules -// ISOC-IL (operated by .il Registry) -il -ac.il -co.il -gov.il -idf.il -k12.il -muni.il -net.il -org.il -// xn--4dbrk0ce ("Israel", Hebrew) : IL -ישראל -// xn--4dbgdty6c.xn--4dbrk0ce. -אקדמיה.ישראל -// xn--5dbhl8d.xn--4dbrk0ce. -ישוב.ישראל -// xn--8dbq2a.xn--4dbrk0ce. -צהל.ישראל -// xn--hebda8b.xn--4dbrk0ce. -ממשל.ישראל - -// im : https://www.nic.im/ -// Submitted by registry -im -ac.im -co.im -ltd.co.im -plc.co.im -com.im -net.im -org.im -tt.im -tv.im - -// in : https://www.iana.org/domains/root/db/in.html -// see also: https://registry.in/policies -// Please note, that nic.in is not an official eTLD, but used by most -// government institutions. -in -5g.in -6g.in -ac.in -ai.in -am.in -bihar.in -biz.in -business.in -ca.in -cn.in -co.in -com.in -coop.in -cs.in -delhi.in -dr.in -edu.in -er.in -firm.in -gen.in -gov.in -gujarat.in -ind.in -info.in -int.in -internet.in -io.in -me.in -mil.in -net.in -nic.in -org.in -pg.in -post.in -pro.in -res.in -travel.in -tv.in -uk.in -up.in -us.in - -// info : https://www.iana.org/domains/root/db/info.html -info - -// int : https://www.iana.org/domains/root/db/int.html -// Confirmed by registry 2008-06-18 -int -eu.int - -// io : http://www.nic.io/rules.htm -io -co.io -com.io -edu.io -gov.io -mil.io -net.io -nom.io -org.io - -// iq : http://www.cmc.iq/english/iq/iqregister1.htm -iq -com.iq -edu.iq -gov.iq -mil.iq -net.iq -org.iq - -// ir : http://www.nic.ir/Terms_and_Conditions_ir,_Appendix_1_Domain_Rules -// Also see http://www.nic.ir/Internationalized_Domain_Names -// Two .ir entries added at request of , 2010-04-16 -ir -ac.ir -co.ir -gov.ir -id.ir -net.ir -org.ir -sch.ir -// xn--mgba3a4f16a.ir (.ir, Persian YEH) -ایران.ir -// xn--mgba3a4fra.ir (.ir, Arabic YEH) -ايران.ir - -// is : http://www.isnic.is/domain/rules.php -// Confirmed by registry 2024-11-17 -is - -// it : https://www.iana.org/domains/root/db/it.html -// https://www.nic.it/ -it -edu.it -gov.it -// Regions (3.3.1) -// https://www.nic.it/en/manage-your-it/forms-and-docs -> "Assignment and Management of domain names" -abr.it -abruzzo.it -aosta-valley.it -aostavalley.it -bas.it -basilicata.it -cal.it -calabria.it -cam.it -campania.it -emilia-romagna.it -emiliaromagna.it -emr.it -friuli-v-giulia.it -friuli-ve-giulia.it -friuli-vegiulia.it -friuli-venezia-giulia.it -friuli-veneziagiulia.it -friuli-vgiulia.it -friuliv-giulia.it -friulive-giulia.it -friulivegiulia.it -friulivenezia-giulia.it -friuliveneziagiulia.it -friulivgiulia.it -fvg.it -laz.it -lazio.it -lig.it -liguria.it -lom.it -lombardia.it -lombardy.it -lucania.it -mar.it -marche.it -mol.it -molise.it -piedmont.it -piemonte.it -pmn.it -pug.it -puglia.it -sar.it -sardegna.it -sardinia.it -sic.it -sicilia.it -sicily.it -taa.it -tos.it -toscana.it -trentin-sud-tirol.it -trentin-süd-tirol.it -trentin-sudtirol.it -trentin-südtirol.it -trentin-sued-tirol.it -trentin-suedtirol.it -trentino.it -trentino-a-adige.it -trentino-aadige.it -trentino-alto-adige.it -trentino-altoadige.it -trentino-s-tirol.it -trentino-stirol.it -trentino-sud-tirol.it -trentino-süd-tirol.it -trentino-sudtirol.it -trentino-südtirol.it -trentino-sued-tirol.it -trentino-suedtirol.it -trentinoa-adige.it -trentinoaadige.it -trentinoalto-adige.it -trentinoaltoadige.it -trentinos-tirol.it -trentinostirol.it -trentinosud-tirol.it -trentinosüd-tirol.it -trentinosudtirol.it -trentinosüdtirol.it -trentinosued-tirol.it -trentinosuedtirol.it -trentinsud-tirol.it -trentinsüd-tirol.it -trentinsudtirol.it -trentinsüdtirol.it -trentinsued-tirol.it -trentinsuedtirol.it -tuscany.it -umb.it -umbria.it -val-d-aosta.it -val-daosta.it -vald-aosta.it -valdaosta.it -valle-aosta.it -valle-d-aosta.it -valle-daosta.it -valleaosta.it -valled-aosta.it -valledaosta.it -vallee-aoste.it -vallée-aoste.it -vallee-d-aoste.it -vallée-d-aoste.it -valleeaoste.it -valléeaoste.it -valleedaoste.it -valléedaoste.it -vao.it -vda.it -ven.it -veneto.it -// Provinces (3.3.2) -ag.it -agrigento.it -al.it -alessandria.it -alto-adige.it -altoadige.it -an.it -ancona.it -andria-barletta-trani.it -andria-trani-barletta.it -andriabarlettatrani.it -andriatranibarletta.it -ao.it -aosta.it -aoste.it -ap.it -aq.it -aquila.it -ar.it -arezzo.it -ascoli-piceno.it -ascolipiceno.it -asti.it -at.it -av.it -avellino.it -ba.it -balsan.it -balsan-sudtirol.it -balsan-südtirol.it -balsan-suedtirol.it -bari.it -barletta-trani-andria.it -barlettatraniandria.it -belluno.it -benevento.it -bergamo.it -bg.it -bi.it -biella.it -bl.it -bn.it -bo.it -bologna.it -bolzano.it -bolzano-altoadige.it -bozen.it -bozen-sudtirol.it -bozen-südtirol.it -bozen-suedtirol.it -br.it -brescia.it -brindisi.it -bs.it -bt.it -bulsan.it -bulsan-sudtirol.it -bulsan-südtirol.it -bulsan-suedtirol.it -bz.it -ca.it -cagliari.it -caltanissetta.it -campidano-medio.it -campidanomedio.it -campobasso.it -carbonia-iglesias.it -carboniaiglesias.it -carrara-massa.it -carraramassa.it -caserta.it -catania.it -catanzaro.it -cb.it -ce.it -cesena-forli.it -cesena-forlì.it -cesenaforli.it -cesenaforlì.it -ch.it -chieti.it -ci.it -cl.it -cn.it -co.it -como.it -cosenza.it -cr.it -cremona.it -crotone.it -cs.it -ct.it -cuneo.it -cz.it -dell-ogliastra.it -dellogliastra.it -en.it -enna.it -fc.it -fe.it -fermo.it -ferrara.it -fg.it -fi.it -firenze.it -florence.it -fm.it -foggia.it -forli-cesena.it -forlì-cesena.it -forlicesena.it -forlìcesena.it -fr.it -frosinone.it -ge.it -genoa.it -genova.it -go.it -gorizia.it -gr.it -grosseto.it -iglesias-carbonia.it -iglesiascarbonia.it -im.it -imperia.it -is.it -isernia.it -kr.it -la-spezia.it -laquila.it -laspezia.it -latina.it -lc.it -le.it -lecce.it -lecco.it -li.it -livorno.it -lo.it -lodi.it -lt.it -lu.it -lucca.it -macerata.it -mantova.it -massa-carrara.it -massacarrara.it -matera.it -mb.it -mc.it -me.it -medio-campidano.it -mediocampidano.it -messina.it -mi.it -milan.it -milano.it -mn.it -mo.it -modena.it -monza.it -monza-brianza.it -monza-e-della-brianza.it -monzabrianza.it -monzaebrianza.it -monzaedellabrianza.it -ms.it -mt.it -na.it -naples.it -napoli.it -no.it -novara.it -nu.it -nuoro.it -og.it -ogliastra.it -olbia-tempio.it -olbiatempio.it -or.it -oristano.it -ot.it -pa.it -padova.it -padua.it -palermo.it -parma.it -pavia.it -pc.it -pd.it -pe.it -perugia.it -pesaro-urbino.it -pesarourbino.it -pescara.it -pg.it -pi.it -piacenza.it -pisa.it -pistoia.it -pn.it -po.it -pordenone.it -potenza.it -pr.it -prato.it -pt.it -pu.it -pv.it -pz.it -ra.it -ragusa.it -ravenna.it -rc.it -re.it -reggio-calabria.it -reggio-emilia.it -reggiocalabria.it -reggioemilia.it -rg.it -ri.it -rieti.it -rimini.it -rm.it -rn.it -ro.it -roma.it -rome.it -rovigo.it -sa.it -salerno.it -sassari.it -savona.it -si.it -siena.it -siracusa.it -so.it -sondrio.it -sp.it -sr.it -ss.it -südtirol.it -suedtirol.it -sv.it -ta.it -taranto.it -te.it -tempio-olbia.it -tempioolbia.it -teramo.it -terni.it -tn.it -to.it -torino.it -tp.it -tr.it -trani-andria-barletta.it -trani-barletta-andria.it -traniandriabarletta.it -tranibarlettaandria.it -trapani.it -trento.it -treviso.it -trieste.it -ts.it -turin.it -tv.it -ud.it -udine.it -urbino-pesaro.it -urbinopesaro.it -va.it -varese.it -vb.it -vc.it -ve.it -venezia.it -venice.it -verbania.it -vercelli.it -verona.it -vi.it -vibo-valentia.it -vibovalentia.it -vicenza.it -viterbo.it -vr.it -vs.it -vt.it -vv.it - -// je : https://www.iana.org/domains/root/db/je.html -// Confirmed by registry 2013-11-28 -je -co.je -net.je -org.je - -// jm : http://www.com.jm/register.html -*.jm - -// jo : https://www.dns.jo/JoFamily.aspx -// Confirmed by registry 2024-11-17 -jo -agri.jo -ai.jo -com.jo -edu.jo -eng.jo -fm.jo -gov.jo -mil.jo -net.jo -org.jo -per.jo -phd.jo -sch.jo -tv.jo - -// jobs : https://www.iana.org/domains/root/db/jobs.html -jobs - -// jp : https://www.iana.org/domains/root/db/jp.html -// http://jprs.co.jp/en/jpdomain.html -// Confirmed by registry 2024-11-22 -jp -// jp organizational type names -ac.jp -ad.jp -co.jp -ed.jp -go.jp -gr.jp -lg.jp -ne.jp -or.jp -// jp prefecture type names -aichi.jp -akita.jp -aomori.jp -chiba.jp -ehime.jp -fukui.jp -fukuoka.jp -fukushima.jp -gifu.jp -gunma.jp -hiroshima.jp -hokkaido.jp -hyogo.jp -ibaraki.jp -ishikawa.jp -iwate.jp -kagawa.jp -kagoshima.jp -kanagawa.jp -kochi.jp -kumamoto.jp -kyoto.jp -mie.jp -miyagi.jp -miyazaki.jp -nagano.jp -nagasaki.jp -nara.jp -niigata.jp -oita.jp -okayama.jp -okinawa.jp -osaka.jp -saga.jp -saitama.jp -shiga.jp -shimane.jp -shizuoka.jp -tochigi.jp -tokushima.jp -tokyo.jp -tottori.jp -toyama.jp -wakayama.jp -yamagata.jp -yamaguchi.jp -yamanashi.jp -三重.jp -京都.jp -佐賀.jp -兵庫.jp -北海道.jp -千葉.jp -和歌山.jp -埼玉.jp -大分.jp -大阪.jp -奈良.jp -宮城.jp -宮崎.jp -富山.jp -山口.jp -山形.jp -山梨.jp -岐阜.jp -岡山.jp -岩手.jp -島根.jp -広島.jp -徳島.jp -愛媛.jp -愛知.jp -新潟.jp -東京.jp -栃木.jp -沖縄.jp -滋賀.jp -熊本.jp -石川.jp -神奈川.jp -福井.jp -福岡.jp -福島.jp -秋田.jp -群馬.jp -茨城.jp -長崎.jp -長野.jp -青森.jp -静岡.jp -香川.jp -高知.jp -鳥取.jp -鹿児島.jp -// jp geographic type names -// http://jprs.jp/doc/rule/saisoku-1.html -// 2024-11-22: JPRS confirmed that jp geographic type names no longer accept new registrations. -// Once all existing registrations expire (marking full discontinuation), these suffixes -// will be removed from the PSL. -*.kawasaki.jp -!city.kawasaki.jp -*.kitakyushu.jp -!city.kitakyushu.jp -*.kobe.jp -!city.kobe.jp -*.nagoya.jp -!city.nagoya.jp -*.sapporo.jp -!city.sapporo.jp -*.sendai.jp -!city.sendai.jp -*.yokohama.jp -!city.yokohama.jp -// 4th level registration -aisai.aichi.jp -ama.aichi.jp -anjo.aichi.jp -asuke.aichi.jp -chiryu.aichi.jp -chita.aichi.jp -fuso.aichi.jp -gamagori.aichi.jp -handa.aichi.jp -hazu.aichi.jp -hekinan.aichi.jp -higashiura.aichi.jp -ichinomiya.aichi.jp -inazawa.aichi.jp -inuyama.aichi.jp -isshiki.aichi.jp -iwakura.aichi.jp -kanie.aichi.jp -kariya.aichi.jp -kasugai.aichi.jp -kira.aichi.jp -kiyosu.aichi.jp -komaki.aichi.jp -konan.aichi.jp -kota.aichi.jp -mihama.aichi.jp -miyoshi.aichi.jp -nishio.aichi.jp -nisshin.aichi.jp -obu.aichi.jp -oguchi.aichi.jp -oharu.aichi.jp -okazaki.aichi.jp -owariasahi.aichi.jp -seto.aichi.jp -shikatsu.aichi.jp -shinshiro.aichi.jp -shitara.aichi.jp -tahara.aichi.jp -takahama.aichi.jp -tobishima.aichi.jp -toei.aichi.jp -togo.aichi.jp -tokai.aichi.jp -tokoname.aichi.jp -toyoake.aichi.jp -toyohashi.aichi.jp -toyokawa.aichi.jp -toyone.aichi.jp -toyota.aichi.jp -tsushima.aichi.jp -yatomi.aichi.jp -akita.akita.jp -daisen.akita.jp -fujisato.akita.jp -gojome.akita.jp -hachirogata.akita.jp -happou.akita.jp -higashinaruse.akita.jp -honjo.akita.jp -honjyo.akita.jp -ikawa.akita.jp -kamikoani.akita.jp -kamioka.akita.jp -katagami.akita.jp -kazuno.akita.jp -kitaakita.akita.jp -kosaka.akita.jp -kyowa.akita.jp -misato.akita.jp -mitane.akita.jp -moriyoshi.akita.jp -nikaho.akita.jp -noshiro.akita.jp -odate.akita.jp -oga.akita.jp -ogata.akita.jp -semboku.akita.jp -yokote.akita.jp -yurihonjo.akita.jp -aomori.aomori.jp -gonohe.aomori.jp -hachinohe.aomori.jp -hashikami.aomori.jp -hiranai.aomori.jp -hirosaki.aomori.jp -itayanagi.aomori.jp -kuroishi.aomori.jp -misawa.aomori.jp -mutsu.aomori.jp -nakadomari.aomori.jp -noheji.aomori.jp -oirase.aomori.jp -owani.aomori.jp -rokunohe.aomori.jp -sannohe.aomori.jp -shichinohe.aomori.jp -shingo.aomori.jp -takko.aomori.jp -towada.aomori.jp -tsugaru.aomori.jp -tsuruta.aomori.jp -abiko.chiba.jp -asahi.chiba.jp -chonan.chiba.jp -chosei.chiba.jp -choshi.chiba.jp -chuo.chiba.jp -funabashi.chiba.jp -futtsu.chiba.jp -hanamigawa.chiba.jp -ichihara.chiba.jp -ichikawa.chiba.jp -ichinomiya.chiba.jp -inzai.chiba.jp -isumi.chiba.jp -kamagaya.chiba.jp -kamogawa.chiba.jp -kashiwa.chiba.jp -katori.chiba.jp -katsuura.chiba.jp -kimitsu.chiba.jp -kisarazu.chiba.jp -kozaki.chiba.jp -kujukuri.chiba.jp -kyonan.chiba.jp -matsudo.chiba.jp -midori.chiba.jp -mihama.chiba.jp -minamiboso.chiba.jp -mobara.chiba.jp -mutsuzawa.chiba.jp -nagara.chiba.jp -nagareyama.chiba.jp -narashino.chiba.jp -narita.chiba.jp -noda.chiba.jp -oamishirasato.chiba.jp -omigawa.chiba.jp -onjuku.chiba.jp -otaki.chiba.jp -sakae.chiba.jp -sakura.chiba.jp -shimofusa.chiba.jp -shirako.chiba.jp -shiroi.chiba.jp -shisui.chiba.jp -sodegaura.chiba.jp -sosa.chiba.jp -tako.chiba.jp -tateyama.chiba.jp -togane.chiba.jp -tohnosho.chiba.jp -tomisato.chiba.jp -urayasu.chiba.jp -yachimata.chiba.jp -yachiyo.chiba.jp -yokaichiba.chiba.jp -yokoshibahikari.chiba.jp -yotsukaido.chiba.jp -ainan.ehime.jp -honai.ehime.jp -ikata.ehime.jp -imabari.ehime.jp -iyo.ehime.jp -kamijima.ehime.jp -kihoku.ehime.jp -kumakogen.ehime.jp -masaki.ehime.jp -matsuno.ehime.jp -matsuyama.ehime.jp -namikata.ehime.jp -niihama.ehime.jp -ozu.ehime.jp -saijo.ehime.jp -seiyo.ehime.jp -shikokuchuo.ehime.jp -tobe.ehime.jp -toon.ehime.jp -uchiko.ehime.jp -uwajima.ehime.jp -yawatahama.ehime.jp -echizen.fukui.jp -eiheiji.fukui.jp -fukui.fukui.jp -ikeda.fukui.jp -katsuyama.fukui.jp -mihama.fukui.jp -minamiechizen.fukui.jp -obama.fukui.jp -ohi.fukui.jp -ono.fukui.jp -sabae.fukui.jp -sakai.fukui.jp -takahama.fukui.jp -tsuruga.fukui.jp -wakasa.fukui.jp -ashiya.fukuoka.jp -buzen.fukuoka.jp -chikugo.fukuoka.jp -chikuho.fukuoka.jp -chikujo.fukuoka.jp -chikushino.fukuoka.jp -chikuzen.fukuoka.jp -chuo.fukuoka.jp -dazaifu.fukuoka.jp -fukuchi.fukuoka.jp -hakata.fukuoka.jp -higashi.fukuoka.jp -hirokawa.fukuoka.jp -hisayama.fukuoka.jp -iizuka.fukuoka.jp -inatsuki.fukuoka.jp -kaho.fukuoka.jp -kasuga.fukuoka.jp -kasuya.fukuoka.jp -kawara.fukuoka.jp -keisen.fukuoka.jp -koga.fukuoka.jp -kurate.fukuoka.jp -kurogi.fukuoka.jp -kurume.fukuoka.jp -minami.fukuoka.jp -miyako.fukuoka.jp -miyama.fukuoka.jp -miyawaka.fukuoka.jp -mizumaki.fukuoka.jp -munakata.fukuoka.jp -nakagawa.fukuoka.jp -nakama.fukuoka.jp -nishi.fukuoka.jp -nogata.fukuoka.jp -ogori.fukuoka.jp -okagaki.fukuoka.jp -okawa.fukuoka.jp -oki.fukuoka.jp -omuta.fukuoka.jp -onga.fukuoka.jp -onojo.fukuoka.jp -oto.fukuoka.jp -saigawa.fukuoka.jp -sasaguri.fukuoka.jp -shingu.fukuoka.jp -shinyoshitomi.fukuoka.jp -shonai.fukuoka.jp -soeda.fukuoka.jp -sue.fukuoka.jp -tachiarai.fukuoka.jp -tagawa.fukuoka.jp -takata.fukuoka.jp -toho.fukuoka.jp -toyotsu.fukuoka.jp -tsuiki.fukuoka.jp -ukiha.fukuoka.jp -umi.fukuoka.jp -usui.fukuoka.jp -yamada.fukuoka.jp -yame.fukuoka.jp -yanagawa.fukuoka.jp -yukuhashi.fukuoka.jp -aizubange.fukushima.jp -aizumisato.fukushima.jp -aizuwakamatsu.fukushima.jp -asakawa.fukushima.jp -bandai.fukushima.jp -date.fukushima.jp -fukushima.fukushima.jp -furudono.fukushima.jp -futaba.fukushima.jp -hanawa.fukushima.jp -higashi.fukushima.jp -hirata.fukushima.jp -hirono.fukushima.jp -iitate.fukushima.jp -inawashiro.fukushima.jp -ishikawa.fukushima.jp -iwaki.fukushima.jp -izumizaki.fukushima.jp -kagamiishi.fukushima.jp -kaneyama.fukushima.jp -kawamata.fukushima.jp -kitakata.fukushima.jp -kitashiobara.fukushima.jp -koori.fukushima.jp -koriyama.fukushima.jp -kunimi.fukushima.jp -miharu.fukushima.jp -mishima.fukushima.jp -namie.fukushima.jp -nango.fukushima.jp -nishiaizu.fukushima.jp -nishigo.fukushima.jp -okuma.fukushima.jp -omotego.fukushima.jp -ono.fukushima.jp -otama.fukushima.jp -samegawa.fukushima.jp -shimogo.fukushima.jp -shirakawa.fukushima.jp -showa.fukushima.jp -soma.fukushima.jp -sukagawa.fukushima.jp -taishin.fukushima.jp -tamakawa.fukushima.jp -tanagura.fukushima.jp -tenei.fukushima.jp -yabuki.fukushima.jp -yamato.fukushima.jp -yamatsuri.fukushima.jp -yanaizu.fukushima.jp -yugawa.fukushima.jp -anpachi.gifu.jp -ena.gifu.jp -gifu.gifu.jp -ginan.gifu.jp -godo.gifu.jp -gujo.gifu.jp -hashima.gifu.jp -hichiso.gifu.jp -hida.gifu.jp -higashishirakawa.gifu.jp -ibigawa.gifu.jp -ikeda.gifu.jp -kakamigahara.gifu.jp -kani.gifu.jp -kasahara.gifu.jp -kasamatsu.gifu.jp -kawaue.gifu.jp -kitagata.gifu.jp -mino.gifu.jp -minokamo.gifu.jp -mitake.gifu.jp -mizunami.gifu.jp -motosu.gifu.jp -nakatsugawa.gifu.jp -ogaki.gifu.jp -sakahogi.gifu.jp -seki.gifu.jp -sekigahara.gifu.jp -shirakawa.gifu.jp -tajimi.gifu.jp -takayama.gifu.jp -tarui.gifu.jp -toki.gifu.jp -tomika.gifu.jp -wanouchi.gifu.jp -yamagata.gifu.jp -yaotsu.gifu.jp -yoro.gifu.jp -annaka.gunma.jp -chiyoda.gunma.jp -fujioka.gunma.jp -higashiagatsuma.gunma.jp -isesaki.gunma.jp -itakura.gunma.jp -kanna.gunma.jp -kanra.gunma.jp -katashina.gunma.jp -kawaba.gunma.jp -kiryu.gunma.jp -kusatsu.gunma.jp -maebashi.gunma.jp -meiwa.gunma.jp -midori.gunma.jp -minakami.gunma.jp -naganohara.gunma.jp -nakanojo.gunma.jp -nanmoku.gunma.jp -numata.gunma.jp -oizumi.gunma.jp -ora.gunma.jp -ota.gunma.jp -shibukawa.gunma.jp -shimonita.gunma.jp -shinto.gunma.jp -showa.gunma.jp -takasaki.gunma.jp -takayama.gunma.jp -tamamura.gunma.jp -tatebayashi.gunma.jp -tomioka.gunma.jp -tsukiyono.gunma.jp -tsumagoi.gunma.jp -ueno.gunma.jp -yoshioka.gunma.jp -asaminami.hiroshima.jp -daiwa.hiroshima.jp -etajima.hiroshima.jp -fuchu.hiroshima.jp -fukuyama.hiroshima.jp -hatsukaichi.hiroshima.jp -higashihiroshima.hiroshima.jp -hongo.hiroshima.jp -jinsekikogen.hiroshima.jp -kaita.hiroshima.jp -kui.hiroshima.jp -kumano.hiroshima.jp -kure.hiroshima.jp -mihara.hiroshima.jp -miyoshi.hiroshima.jp -naka.hiroshima.jp -onomichi.hiroshima.jp -osakikamijima.hiroshima.jp -otake.hiroshima.jp -saka.hiroshima.jp -sera.hiroshima.jp -seranishi.hiroshima.jp -shinichi.hiroshima.jp -shobara.hiroshima.jp -takehara.hiroshima.jp -abashiri.hokkaido.jp -abira.hokkaido.jp -aibetsu.hokkaido.jp -akabira.hokkaido.jp -akkeshi.hokkaido.jp -asahikawa.hokkaido.jp -ashibetsu.hokkaido.jp -ashoro.hokkaido.jp -assabu.hokkaido.jp -atsuma.hokkaido.jp -bibai.hokkaido.jp -biei.hokkaido.jp -bifuka.hokkaido.jp -bihoro.hokkaido.jp -biratori.hokkaido.jp -chippubetsu.hokkaido.jp -chitose.hokkaido.jp -date.hokkaido.jp -ebetsu.hokkaido.jp -embetsu.hokkaido.jp -eniwa.hokkaido.jp -erimo.hokkaido.jp -esan.hokkaido.jp -esashi.hokkaido.jp -fukagawa.hokkaido.jp -fukushima.hokkaido.jp -furano.hokkaido.jp -furubira.hokkaido.jp -haboro.hokkaido.jp -hakodate.hokkaido.jp -hamatonbetsu.hokkaido.jp -hidaka.hokkaido.jp -higashikagura.hokkaido.jp -higashikawa.hokkaido.jp -hiroo.hokkaido.jp -hokuryu.hokkaido.jp -hokuto.hokkaido.jp -honbetsu.hokkaido.jp -horokanai.hokkaido.jp -horonobe.hokkaido.jp -ikeda.hokkaido.jp -imakane.hokkaido.jp -ishikari.hokkaido.jp -iwamizawa.hokkaido.jp -iwanai.hokkaido.jp -kamifurano.hokkaido.jp -kamikawa.hokkaido.jp -kamishihoro.hokkaido.jp -kamisunagawa.hokkaido.jp -kamoenai.hokkaido.jp -kayabe.hokkaido.jp -kembuchi.hokkaido.jp -kikonai.hokkaido.jp -kimobetsu.hokkaido.jp -kitahiroshima.hokkaido.jp -kitami.hokkaido.jp -kiyosato.hokkaido.jp -koshimizu.hokkaido.jp -kunneppu.hokkaido.jp -kuriyama.hokkaido.jp -kuromatsunai.hokkaido.jp -kushiro.hokkaido.jp -kutchan.hokkaido.jp -kyowa.hokkaido.jp -mashike.hokkaido.jp -matsumae.hokkaido.jp -mikasa.hokkaido.jp -minamifurano.hokkaido.jp -mombetsu.hokkaido.jp -moseushi.hokkaido.jp -mukawa.hokkaido.jp -muroran.hokkaido.jp -naie.hokkaido.jp -nakagawa.hokkaido.jp -nakasatsunai.hokkaido.jp -nakatombetsu.hokkaido.jp -nanae.hokkaido.jp -nanporo.hokkaido.jp -nayoro.hokkaido.jp -nemuro.hokkaido.jp -niikappu.hokkaido.jp -niki.hokkaido.jp -nishiokoppe.hokkaido.jp -noboribetsu.hokkaido.jp -numata.hokkaido.jp -obihiro.hokkaido.jp -obira.hokkaido.jp -oketo.hokkaido.jp -okoppe.hokkaido.jp -otaru.hokkaido.jp -otobe.hokkaido.jp -otofuke.hokkaido.jp -otoineppu.hokkaido.jp -oumu.hokkaido.jp -ozora.hokkaido.jp -pippu.hokkaido.jp -rankoshi.hokkaido.jp -rebun.hokkaido.jp -rikubetsu.hokkaido.jp -rishiri.hokkaido.jp -rishirifuji.hokkaido.jp -saroma.hokkaido.jp -sarufutsu.hokkaido.jp -shakotan.hokkaido.jp -shari.hokkaido.jp -shibecha.hokkaido.jp -shibetsu.hokkaido.jp -shikabe.hokkaido.jp -shikaoi.hokkaido.jp -shimamaki.hokkaido.jp -shimizu.hokkaido.jp -shimokawa.hokkaido.jp -shinshinotsu.hokkaido.jp -shintoku.hokkaido.jp -shiranuka.hokkaido.jp -shiraoi.hokkaido.jp -shiriuchi.hokkaido.jp -sobetsu.hokkaido.jp -sunagawa.hokkaido.jp -taiki.hokkaido.jp -takasu.hokkaido.jp -takikawa.hokkaido.jp -takinoue.hokkaido.jp -teshikaga.hokkaido.jp -tobetsu.hokkaido.jp -tohma.hokkaido.jp -tomakomai.hokkaido.jp -tomari.hokkaido.jp -toya.hokkaido.jp -toyako.hokkaido.jp -toyotomi.hokkaido.jp -toyoura.hokkaido.jp -tsubetsu.hokkaido.jp -tsukigata.hokkaido.jp -urakawa.hokkaido.jp -urausu.hokkaido.jp -uryu.hokkaido.jp -utashinai.hokkaido.jp -wakkanai.hokkaido.jp -wassamu.hokkaido.jp -yakumo.hokkaido.jp -yoichi.hokkaido.jp -aioi.hyogo.jp -akashi.hyogo.jp -ako.hyogo.jp -amagasaki.hyogo.jp -aogaki.hyogo.jp -asago.hyogo.jp -ashiya.hyogo.jp -awaji.hyogo.jp -fukusaki.hyogo.jp -goshiki.hyogo.jp -harima.hyogo.jp -himeji.hyogo.jp -ichikawa.hyogo.jp -inagawa.hyogo.jp -itami.hyogo.jp -kakogawa.hyogo.jp -kamigori.hyogo.jp -kamikawa.hyogo.jp -kasai.hyogo.jp -kasuga.hyogo.jp -kawanishi.hyogo.jp -miki.hyogo.jp -minamiawaji.hyogo.jp -nishinomiya.hyogo.jp -nishiwaki.hyogo.jp -ono.hyogo.jp -sanda.hyogo.jp -sannan.hyogo.jp -sasayama.hyogo.jp -sayo.hyogo.jp -shingu.hyogo.jp -shinonsen.hyogo.jp -shiso.hyogo.jp -sumoto.hyogo.jp -taishi.hyogo.jp -taka.hyogo.jp -takarazuka.hyogo.jp -takasago.hyogo.jp -takino.hyogo.jp -tamba.hyogo.jp -tatsuno.hyogo.jp -toyooka.hyogo.jp -yabu.hyogo.jp -yashiro.hyogo.jp -yoka.hyogo.jp -yokawa.hyogo.jp -ami.ibaraki.jp -asahi.ibaraki.jp -bando.ibaraki.jp -chikusei.ibaraki.jp -daigo.ibaraki.jp -fujishiro.ibaraki.jp -hitachi.ibaraki.jp -hitachinaka.ibaraki.jp -hitachiomiya.ibaraki.jp -hitachiota.ibaraki.jp -ibaraki.ibaraki.jp -ina.ibaraki.jp -inashiki.ibaraki.jp -itako.ibaraki.jp -iwama.ibaraki.jp -joso.ibaraki.jp -kamisu.ibaraki.jp -kasama.ibaraki.jp -kashima.ibaraki.jp -kasumigaura.ibaraki.jp -koga.ibaraki.jp -miho.ibaraki.jp -mito.ibaraki.jp -moriya.ibaraki.jp -naka.ibaraki.jp -namegata.ibaraki.jp -oarai.ibaraki.jp -ogawa.ibaraki.jp -omitama.ibaraki.jp -ryugasaki.ibaraki.jp -sakai.ibaraki.jp -sakuragawa.ibaraki.jp -shimodate.ibaraki.jp -shimotsuma.ibaraki.jp -shirosato.ibaraki.jp -sowa.ibaraki.jp -suifu.ibaraki.jp -takahagi.ibaraki.jp -tamatsukuri.ibaraki.jp -tokai.ibaraki.jp -tomobe.ibaraki.jp -tone.ibaraki.jp -toride.ibaraki.jp -tsuchiura.ibaraki.jp -tsukuba.ibaraki.jp -uchihara.ibaraki.jp -ushiku.ibaraki.jp -yachiyo.ibaraki.jp -yamagata.ibaraki.jp -yawara.ibaraki.jp -yuki.ibaraki.jp -anamizu.ishikawa.jp -hakui.ishikawa.jp -hakusan.ishikawa.jp -kaga.ishikawa.jp -kahoku.ishikawa.jp -kanazawa.ishikawa.jp -kawakita.ishikawa.jp -komatsu.ishikawa.jp -nakanoto.ishikawa.jp -nanao.ishikawa.jp -nomi.ishikawa.jp -nonoichi.ishikawa.jp -noto.ishikawa.jp -shika.ishikawa.jp -suzu.ishikawa.jp -tsubata.ishikawa.jp -tsurugi.ishikawa.jp -uchinada.ishikawa.jp -wajima.ishikawa.jp -fudai.iwate.jp -fujisawa.iwate.jp -hanamaki.iwate.jp -hiraizumi.iwate.jp -hirono.iwate.jp -ichinohe.iwate.jp -ichinoseki.iwate.jp -iwaizumi.iwate.jp -iwate.iwate.jp -joboji.iwate.jp -kamaishi.iwate.jp -kanegasaki.iwate.jp -karumai.iwate.jp -kawai.iwate.jp -kitakami.iwate.jp -kuji.iwate.jp -kunohe.iwate.jp -kuzumaki.iwate.jp -miyako.iwate.jp -mizusawa.iwate.jp -morioka.iwate.jp -ninohe.iwate.jp -noda.iwate.jp -ofunato.iwate.jp -oshu.iwate.jp -otsuchi.iwate.jp -rikuzentakata.iwate.jp -shiwa.iwate.jp -shizukuishi.iwate.jp -sumita.iwate.jp -tanohata.iwate.jp -tono.iwate.jp -yahaba.iwate.jp -yamada.iwate.jp -ayagawa.kagawa.jp -higashikagawa.kagawa.jp -kanonji.kagawa.jp -kotohira.kagawa.jp -manno.kagawa.jp -marugame.kagawa.jp -mitoyo.kagawa.jp -naoshima.kagawa.jp -sanuki.kagawa.jp -tadotsu.kagawa.jp -takamatsu.kagawa.jp -tonosho.kagawa.jp -uchinomi.kagawa.jp -utazu.kagawa.jp -zentsuji.kagawa.jp -akune.kagoshima.jp -amami.kagoshima.jp -hioki.kagoshima.jp -isa.kagoshima.jp -isen.kagoshima.jp -izumi.kagoshima.jp -kagoshima.kagoshima.jp -kanoya.kagoshima.jp -kawanabe.kagoshima.jp -kinko.kagoshima.jp -kouyama.kagoshima.jp -makurazaki.kagoshima.jp -matsumoto.kagoshima.jp -minamitane.kagoshima.jp -nakatane.kagoshima.jp -nishinoomote.kagoshima.jp -satsumasendai.kagoshima.jp -soo.kagoshima.jp -tarumizu.kagoshima.jp -yusui.kagoshima.jp -aikawa.kanagawa.jp -atsugi.kanagawa.jp -ayase.kanagawa.jp -chigasaki.kanagawa.jp -ebina.kanagawa.jp -fujisawa.kanagawa.jp -hadano.kanagawa.jp -hakone.kanagawa.jp -hiratsuka.kanagawa.jp -isehara.kanagawa.jp -kaisei.kanagawa.jp -kamakura.kanagawa.jp -kiyokawa.kanagawa.jp -matsuda.kanagawa.jp -minamiashigara.kanagawa.jp -miura.kanagawa.jp -nakai.kanagawa.jp -ninomiya.kanagawa.jp -odawara.kanagawa.jp -oi.kanagawa.jp -oiso.kanagawa.jp -sagamihara.kanagawa.jp -samukawa.kanagawa.jp -tsukui.kanagawa.jp -yamakita.kanagawa.jp -yamato.kanagawa.jp -yokosuka.kanagawa.jp -yugawara.kanagawa.jp -zama.kanagawa.jp -zushi.kanagawa.jp -aki.kochi.jp -geisei.kochi.jp -hidaka.kochi.jp -higashitsuno.kochi.jp -ino.kochi.jp -kagami.kochi.jp -kami.kochi.jp -kitagawa.kochi.jp -kochi.kochi.jp -mihara.kochi.jp -motoyama.kochi.jp -muroto.kochi.jp -nahari.kochi.jp -nakamura.kochi.jp -nankoku.kochi.jp -nishitosa.kochi.jp -niyodogawa.kochi.jp -ochi.kochi.jp -okawa.kochi.jp -otoyo.kochi.jp -otsuki.kochi.jp -sakawa.kochi.jp -sukumo.kochi.jp -susaki.kochi.jp -tosa.kochi.jp -tosashimizu.kochi.jp -toyo.kochi.jp -tsuno.kochi.jp -umaji.kochi.jp -yasuda.kochi.jp -yusuhara.kochi.jp -amakusa.kumamoto.jp -arao.kumamoto.jp -aso.kumamoto.jp -choyo.kumamoto.jp -gyokuto.kumamoto.jp -kamiamakusa.kumamoto.jp -kikuchi.kumamoto.jp -kumamoto.kumamoto.jp -mashiki.kumamoto.jp -mifune.kumamoto.jp -minamata.kumamoto.jp -minamioguni.kumamoto.jp -nagasu.kumamoto.jp -nishihara.kumamoto.jp -oguni.kumamoto.jp -ozu.kumamoto.jp -sumoto.kumamoto.jp -takamori.kumamoto.jp -uki.kumamoto.jp -uto.kumamoto.jp -yamaga.kumamoto.jp -yamato.kumamoto.jp -yatsushiro.kumamoto.jp -ayabe.kyoto.jp -fukuchiyama.kyoto.jp -higashiyama.kyoto.jp -ide.kyoto.jp -ine.kyoto.jp -joyo.kyoto.jp -kameoka.kyoto.jp -kamo.kyoto.jp -kita.kyoto.jp -kizu.kyoto.jp -kumiyama.kyoto.jp -kyotamba.kyoto.jp -kyotanabe.kyoto.jp -kyotango.kyoto.jp -maizuru.kyoto.jp -minami.kyoto.jp -minamiyamashiro.kyoto.jp -miyazu.kyoto.jp -muko.kyoto.jp -nagaokakyo.kyoto.jp -nakagyo.kyoto.jp -nantan.kyoto.jp -oyamazaki.kyoto.jp -sakyo.kyoto.jp -seika.kyoto.jp -tanabe.kyoto.jp -uji.kyoto.jp -ujitawara.kyoto.jp -wazuka.kyoto.jp -yamashina.kyoto.jp -yawata.kyoto.jp -asahi.mie.jp -inabe.mie.jp -ise.mie.jp -kameyama.mie.jp -kawagoe.mie.jp -kiho.mie.jp -kisosaki.mie.jp -kiwa.mie.jp -komono.mie.jp -kumano.mie.jp -kuwana.mie.jp -matsusaka.mie.jp -meiwa.mie.jp -mihama.mie.jp -minamiise.mie.jp -misugi.mie.jp -miyama.mie.jp -nabari.mie.jp -shima.mie.jp -suzuka.mie.jp -tado.mie.jp -taiki.mie.jp -taki.mie.jp -tamaki.mie.jp -toba.mie.jp -tsu.mie.jp -udono.mie.jp -ureshino.mie.jp -watarai.mie.jp -yokkaichi.mie.jp -furukawa.miyagi.jp -higashimatsushima.miyagi.jp -ishinomaki.miyagi.jp -iwanuma.miyagi.jp -kakuda.miyagi.jp -kami.miyagi.jp -kawasaki.miyagi.jp -marumori.miyagi.jp -matsushima.miyagi.jp -minamisanriku.miyagi.jp -misato.miyagi.jp -murata.miyagi.jp -natori.miyagi.jp -ogawara.miyagi.jp -ohira.miyagi.jp -onagawa.miyagi.jp -osaki.miyagi.jp -rifu.miyagi.jp -semine.miyagi.jp -shibata.miyagi.jp -shichikashuku.miyagi.jp -shikama.miyagi.jp -shiogama.miyagi.jp -shiroishi.miyagi.jp -tagajo.miyagi.jp -taiwa.miyagi.jp -tome.miyagi.jp -tomiya.miyagi.jp -wakuya.miyagi.jp -watari.miyagi.jp -yamamoto.miyagi.jp -zao.miyagi.jp -aya.miyazaki.jp -ebino.miyazaki.jp -gokase.miyazaki.jp -hyuga.miyazaki.jp -kadogawa.miyazaki.jp -kawaminami.miyazaki.jp -kijo.miyazaki.jp -kitagawa.miyazaki.jp -kitakata.miyazaki.jp -kitaura.miyazaki.jp -kobayashi.miyazaki.jp -kunitomi.miyazaki.jp -kushima.miyazaki.jp -mimata.miyazaki.jp -miyakonojo.miyazaki.jp -miyazaki.miyazaki.jp -morotsuka.miyazaki.jp -nichinan.miyazaki.jp -nishimera.miyazaki.jp -nobeoka.miyazaki.jp -saito.miyazaki.jp -shiiba.miyazaki.jp -shintomi.miyazaki.jp -takaharu.miyazaki.jp -takanabe.miyazaki.jp -takazaki.miyazaki.jp -tsuno.miyazaki.jp -achi.nagano.jp -agematsu.nagano.jp -anan.nagano.jp -aoki.nagano.jp -asahi.nagano.jp -azumino.nagano.jp -chikuhoku.nagano.jp -chikuma.nagano.jp -chino.nagano.jp -fujimi.nagano.jp -hakuba.nagano.jp -hara.nagano.jp -hiraya.nagano.jp -iida.nagano.jp -iijima.nagano.jp -iiyama.nagano.jp -iizuna.nagano.jp -ikeda.nagano.jp -ikusaka.nagano.jp -ina.nagano.jp -karuizawa.nagano.jp -kawakami.nagano.jp -kiso.nagano.jp -kisofukushima.nagano.jp -kitaaiki.nagano.jp -komagane.nagano.jp -komoro.nagano.jp -matsukawa.nagano.jp -matsumoto.nagano.jp -miasa.nagano.jp -minamiaiki.nagano.jp -minamimaki.nagano.jp -minamiminowa.nagano.jp -minowa.nagano.jp -miyada.nagano.jp -miyota.nagano.jp -mochizuki.nagano.jp -nagano.nagano.jp -nagawa.nagano.jp -nagiso.nagano.jp -nakagawa.nagano.jp -nakano.nagano.jp -nozawaonsen.nagano.jp -obuse.nagano.jp -ogawa.nagano.jp -okaya.nagano.jp -omachi.nagano.jp -omi.nagano.jp -ookuwa.nagano.jp -ooshika.nagano.jp -otaki.nagano.jp -otari.nagano.jp -sakae.nagano.jp -sakaki.nagano.jp -saku.nagano.jp -sakuho.nagano.jp -shimosuwa.nagano.jp -shinanomachi.nagano.jp -shiojiri.nagano.jp -suwa.nagano.jp -suzaka.nagano.jp -takagi.nagano.jp -takamori.nagano.jp -takayama.nagano.jp -tateshina.nagano.jp -tatsuno.nagano.jp -togakushi.nagano.jp -togura.nagano.jp -tomi.nagano.jp -ueda.nagano.jp -wada.nagano.jp -yamagata.nagano.jp -yamanouchi.nagano.jp -yasaka.nagano.jp -yasuoka.nagano.jp -chijiwa.nagasaki.jp -futsu.nagasaki.jp -goto.nagasaki.jp -hasami.nagasaki.jp -hirado.nagasaki.jp -iki.nagasaki.jp -isahaya.nagasaki.jp -kawatana.nagasaki.jp -kuchinotsu.nagasaki.jp -matsuura.nagasaki.jp -nagasaki.nagasaki.jp -obama.nagasaki.jp -omura.nagasaki.jp -oseto.nagasaki.jp -saikai.nagasaki.jp -sasebo.nagasaki.jp -seihi.nagasaki.jp -shimabara.nagasaki.jp -shinkamigoto.nagasaki.jp -togitsu.nagasaki.jp -tsushima.nagasaki.jp -unzen.nagasaki.jp -ando.nara.jp -gose.nara.jp -heguri.nara.jp -higashiyoshino.nara.jp -ikaruga.nara.jp -ikoma.nara.jp -kamikitayama.nara.jp -kanmaki.nara.jp -kashiba.nara.jp -kashihara.nara.jp -katsuragi.nara.jp -kawai.nara.jp -kawakami.nara.jp -kawanishi.nara.jp -koryo.nara.jp -kurotaki.nara.jp -mitsue.nara.jp -miyake.nara.jp -nara.nara.jp -nosegawa.nara.jp -oji.nara.jp -ouda.nara.jp -oyodo.nara.jp -sakurai.nara.jp -sango.nara.jp -shimoichi.nara.jp -shimokitayama.nara.jp -shinjo.nara.jp -soni.nara.jp -takatori.nara.jp -tawaramoto.nara.jp -tenkawa.nara.jp -tenri.nara.jp -uda.nara.jp -yamatokoriyama.nara.jp -yamatotakada.nara.jp -yamazoe.nara.jp -yoshino.nara.jp -aga.niigata.jp -agano.niigata.jp -gosen.niigata.jp -itoigawa.niigata.jp -izumozaki.niigata.jp -joetsu.niigata.jp -kamo.niigata.jp -kariwa.niigata.jp -kashiwazaki.niigata.jp -minamiuonuma.niigata.jp -mitsuke.niigata.jp -muika.niigata.jp -murakami.niigata.jp -myoko.niigata.jp -nagaoka.niigata.jp -niigata.niigata.jp -ojiya.niigata.jp -omi.niigata.jp -sado.niigata.jp -sanjo.niigata.jp -seiro.niigata.jp -seirou.niigata.jp -sekikawa.niigata.jp -shibata.niigata.jp -tagami.niigata.jp -tainai.niigata.jp -tochio.niigata.jp -tokamachi.niigata.jp -tsubame.niigata.jp -tsunan.niigata.jp -uonuma.niigata.jp -yahiko.niigata.jp -yoita.niigata.jp -yuzawa.niigata.jp -beppu.oita.jp -bungoono.oita.jp -bungotakada.oita.jp -hasama.oita.jp -hiji.oita.jp -himeshima.oita.jp -hita.oita.jp -kamitsue.oita.jp -kokonoe.oita.jp -kuju.oita.jp -kunisaki.oita.jp -kusu.oita.jp -oita.oita.jp -saiki.oita.jp -taketa.oita.jp -tsukumi.oita.jp -usa.oita.jp -usuki.oita.jp -yufu.oita.jp -akaiwa.okayama.jp -asakuchi.okayama.jp -bizen.okayama.jp -hayashima.okayama.jp -ibara.okayama.jp -kagamino.okayama.jp -kasaoka.okayama.jp -kibichuo.okayama.jp -kumenan.okayama.jp -kurashiki.okayama.jp -maniwa.okayama.jp -misaki.okayama.jp -nagi.okayama.jp -niimi.okayama.jp -nishiawakura.okayama.jp -okayama.okayama.jp -satosho.okayama.jp -setouchi.okayama.jp -shinjo.okayama.jp -shoo.okayama.jp -soja.okayama.jp -takahashi.okayama.jp -tamano.okayama.jp -tsuyama.okayama.jp -wake.okayama.jp -yakage.okayama.jp -aguni.okinawa.jp -ginowan.okinawa.jp -ginoza.okinawa.jp -gushikami.okinawa.jp -haebaru.okinawa.jp -higashi.okinawa.jp -hirara.okinawa.jp -iheya.okinawa.jp -ishigaki.okinawa.jp -ishikawa.okinawa.jp -itoman.okinawa.jp -izena.okinawa.jp -kadena.okinawa.jp -kin.okinawa.jp -kitadaito.okinawa.jp -kitanakagusuku.okinawa.jp -kumejima.okinawa.jp -kunigami.okinawa.jp -minamidaito.okinawa.jp -motobu.okinawa.jp -nago.okinawa.jp -naha.okinawa.jp -nakagusuku.okinawa.jp -nakijin.okinawa.jp -nanjo.okinawa.jp -nishihara.okinawa.jp -ogimi.okinawa.jp -okinawa.okinawa.jp -onna.okinawa.jp -shimoji.okinawa.jp -taketomi.okinawa.jp -tarama.okinawa.jp -tokashiki.okinawa.jp -tomigusuku.okinawa.jp -tonaki.okinawa.jp -urasoe.okinawa.jp -uruma.okinawa.jp -yaese.okinawa.jp -yomitan.okinawa.jp -yonabaru.okinawa.jp -yonaguni.okinawa.jp -zamami.okinawa.jp -abeno.osaka.jp -chihayaakasaka.osaka.jp -chuo.osaka.jp -daito.osaka.jp -fujiidera.osaka.jp -habikino.osaka.jp -hannan.osaka.jp -higashiosaka.osaka.jp -higashisumiyoshi.osaka.jp -higashiyodogawa.osaka.jp -hirakata.osaka.jp -ibaraki.osaka.jp -ikeda.osaka.jp -izumi.osaka.jp -izumiotsu.osaka.jp -izumisano.osaka.jp -kadoma.osaka.jp -kaizuka.osaka.jp -kanan.osaka.jp -kashiwara.osaka.jp -katano.osaka.jp -kawachinagano.osaka.jp -kishiwada.osaka.jp -kita.osaka.jp -kumatori.osaka.jp -matsubara.osaka.jp -minato.osaka.jp -minoh.osaka.jp -misaki.osaka.jp -moriguchi.osaka.jp -neyagawa.osaka.jp -nishi.osaka.jp -nose.osaka.jp -osakasayama.osaka.jp -sakai.osaka.jp -sayama.osaka.jp -sennan.osaka.jp -settsu.osaka.jp -shijonawate.osaka.jp -shimamoto.osaka.jp -suita.osaka.jp -tadaoka.osaka.jp -taishi.osaka.jp -tajiri.osaka.jp -takaishi.osaka.jp -takatsuki.osaka.jp -tondabayashi.osaka.jp -toyonaka.osaka.jp -toyono.osaka.jp -yao.osaka.jp -ariake.saga.jp -arita.saga.jp -fukudomi.saga.jp -genkai.saga.jp -hamatama.saga.jp -hizen.saga.jp -imari.saga.jp -kamimine.saga.jp -kanzaki.saga.jp -karatsu.saga.jp -kashima.saga.jp -kitagata.saga.jp -kitahata.saga.jp -kiyama.saga.jp -kouhoku.saga.jp -kyuragi.saga.jp -nishiarita.saga.jp -ogi.saga.jp -omachi.saga.jp -ouchi.saga.jp -saga.saga.jp -shiroishi.saga.jp -taku.saga.jp -tara.saga.jp -tosu.saga.jp -yoshinogari.saga.jp -arakawa.saitama.jp -asaka.saitama.jp -chichibu.saitama.jp -fujimi.saitama.jp -fujimino.saitama.jp -fukaya.saitama.jp -hanno.saitama.jp -hanyu.saitama.jp -hasuda.saitama.jp -hatogaya.saitama.jp -hatoyama.saitama.jp -hidaka.saitama.jp -higashichichibu.saitama.jp -higashimatsuyama.saitama.jp -honjo.saitama.jp -ina.saitama.jp -iruma.saitama.jp -iwatsuki.saitama.jp -kamiizumi.saitama.jp -kamikawa.saitama.jp -kamisato.saitama.jp -kasukabe.saitama.jp -kawagoe.saitama.jp -kawaguchi.saitama.jp -kawajima.saitama.jp -kazo.saitama.jp -kitamoto.saitama.jp -koshigaya.saitama.jp -kounosu.saitama.jp -kuki.saitama.jp -kumagaya.saitama.jp -matsubushi.saitama.jp -minano.saitama.jp -misato.saitama.jp -miyashiro.saitama.jp -miyoshi.saitama.jp -moroyama.saitama.jp -nagatoro.saitama.jp -namegawa.saitama.jp -niiza.saitama.jp -ogano.saitama.jp -ogawa.saitama.jp -ogose.saitama.jp -okegawa.saitama.jp -omiya.saitama.jp -otaki.saitama.jp -ranzan.saitama.jp -ryokami.saitama.jp -saitama.saitama.jp -sakado.saitama.jp -satte.saitama.jp -sayama.saitama.jp -shiki.saitama.jp -shiraoka.saitama.jp -soka.saitama.jp -sugito.saitama.jp -toda.saitama.jp -tokigawa.saitama.jp -tokorozawa.saitama.jp -tsurugashima.saitama.jp -urawa.saitama.jp -warabi.saitama.jp -yashio.saitama.jp -yokoze.saitama.jp -yono.saitama.jp -yorii.saitama.jp -yoshida.saitama.jp -yoshikawa.saitama.jp -yoshimi.saitama.jp -aisho.shiga.jp -gamo.shiga.jp -higashiomi.shiga.jp -hikone.shiga.jp -koka.shiga.jp -konan.shiga.jp -kosei.shiga.jp -koto.shiga.jp -kusatsu.shiga.jp -maibara.shiga.jp -moriyama.shiga.jp -nagahama.shiga.jp -nishiazai.shiga.jp -notogawa.shiga.jp -omihachiman.shiga.jp -otsu.shiga.jp -ritto.shiga.jp -ryuoh.shiga.jp -takashima.shiga.jp -takatsuki.shiga.jp -torahime.shiga.jp -toyosato.shiga.jp -yasu.shiga.jp -akagi.shimane.jp -ama.shimane.jp -gotsu.shimane.jp -hamada.shimane.jp -higashiizumo.shimane.jp -hikawa.shimane.jp -hikimi.shimane.jp -izumo.shimane.jp -kakinoki.shimane.jp -masuda.shimane.jp -matsue.shimane.jp -misato.shimane.jp -nishinoshima.shimane.jp -ohda.shimane.jp -okinoshima.shimane.jp -okuizumo.shimane.jp -shimane.shimane.jp -tamayu.shimane.jp -tsuwano.shimane.jp -unnan.shimane.jp -yakumo.shimane.jp -yasugi.shimane.jp -yatsuka.shimane.jp -arai.shizuoka.jp -atami.shizuoka.jp -fuji.shizuoka.jp -fujieda.shizuoka.jp -fujikawa.shizuoka.jp -fujinomiya.shizuoka.jp -fukuroi.shizuoka.jp -gotemba.shizuoka.jp -haibara.shizuoka.jp -hamamatsu.shizuoka.jp -higashiizu.shizuoka.jp -ito.shizuoka.jp -iwata.shizuoka.jp -izu.shizuoka.jp -izunokuni.shizuoka.jp -kakegawa.shizuoka.jp -kannami.shizuoka.jp -kawanehon.shizuoka.jp -kawazu.shizuoka.jp -kikugawa.shizuoka.jp -kosai.shizuoka.jp -makinohara.shizuoka.jp -matsuzaki.shizuoka.jp -minamiizu.shizuoka.jp -mishima.shizuoka.jp -morimachi.shizuoka.jp -nishiizu.shizuoka.jp -numazu.shizuoka.jp -omaezaki.shizuoka.jp -shimada.shizuoka.jp -shimizu.shizuoka.jp -shimoda.shizuoka.jp -shizuoka.shizuoka.jp -susono.shizuoka.jp -yaizu.shizuoka.jp -yoshida.shizuoka.jp -ashikaga.tochigi.jp -bato.tochigi.jp -haga.tochigi.jp -ichikai.tochigi.jp -iwafune.tochigi.jp -kaminokawa.tochigi.jp -kanuma.tochigi.jp -karasuyama.tochigi.jp -kuroiso.tochigi.jp -mashiko.tochigi.jp -mibu.tochigi.jp -moka.tochigi.jp -motegi.tochigi.jp -nasu.tochigi.jp -nasushiobara.tochigi.jp -nikko.tochigi.jp -nishikata.tochigi.jp -nogi.tochigi.jp -ohira.tochigi.jp -ohtawara.tochigi.jp -oyama.tochigi.jp -sakura.tochigi.jp -sano.tochigi.jp -shimotsuke.tochigi.jp -shioya.tochigi.jp -takanezawa.tochigi.jp -tochigi.tochigi.jp -tsuga.tochigi.jp -ujiie.tochigi.jp -utsunomiya.tochigi.jp -yaita.tochigi.jp -aizumi.tokushima.jp -anan.tokushima.jp -ichiba.tokushima.jp -itano.tokushima.jp -kainan.tokushima.jp -komatsushima.tokushima.jp -matsushige.tokushima.jp -mima.tokushima.jp -minami.tokushima.jp -miyoshi.tokushima.jp -mugi.tokushima.jp -nakagawa.tokushima.jp -naruto.tokushima.jp -sanagochi.tokushima.jp -shishikui.tokushima.jp -tokushima.tokushima.jp -wajiki.tokushima.jp -adachi.tokyo.jp -akiruno.tokyo.jp -akishima.tokyo.jp -aogashima.tokyo.jp -arakawa.tokyo.jp -bunkyo.tokyo.jp -chiyoda.tokyo.jp -chofu.tokyo.jp -chuo.tokyo.jp -edogawa.tokyo.jp -fuchu.tokyo.jp -fussa.tokyo.jp -hachijo.tokyo.jp -hachioji.tokyo.jp -hamura.tokyo.jp -higashikurume.tokyo.jp -higashimurayama.tokyo.jp -higashiyamato.tokyo.jp -hino.tokyo.jp -hinode.tokyo.jp -hinohara.tokyo.jp -inagi.tokyo.jp -itabashi.tokyo.jp -katsushika.tokyo.jp -kita.tokyo.jp -kiyose.tokyo.jp -kodaira.tokyo.jp -koganei.tokyo.jp -kokubunji.tokyo.jp -komae.tokyo.jp -koto.tokyo.jp -kouzushima.tokyo.jp -kunitachi.tokyo.jp -machida.tokyo.jp -meguro.tokyo.jp -minato.tokyo.jp -mitaka.tokyo.jp -mizuho.tokyo.jp -musashimurayama.tokyo.jp -musashino.tokyo.jp -nakano.tokyo.jp -nerima.tokyo.jp -ogasawara.tokyo.jp -okutama.tokyo.jp -ome.tokyo.jp -oshima.tokyo.jp -ota.tokyo.jp -setagaya.tokyo.jp -shibuya.tokyo.jp -shinagawa.tokyo.jp -shinjuku.tokyo.jp -suginami.tokyo.jp -sumida.tokyo.jp -tachikawa.tokyo.jp -taito.tokyo.jp -tama.tokyo.jp -toshima.tokyo.jp -chizu.tottori.jp -hino.tottori.jp -kawahara.tottori.jp -koge.tottori.jp -kotoura.tottori.jp -misasa.tottori.jp -nanbu.tottori.jp -nichinan.tottori.jp -sakaiminato.tottori.jp -tottori.tottori.jp -wakasa.tottori.jp -yazu.tottori.jp -yonago.tottori.jp -asahi.toyama.jp -fuchu.toyama.jp -fukumitsu.toyama.jp -funahashi.toyama.jp -himi.toyama.jp -imizu.toyama.jp -inami.toyama.jp -johana.toyama.jp -kamiichi.toyama.jp -kurobe.toyama.jp -nakaniikawa.toyama.jp -namerikawa.toyama.jp -nanto.toyama.jp -nyuzen.toyama.jp -oyabe.toyama.jp -taira.toyama.jp -takaoka.toyama.jp -tateyama.toyama.jp -toga.toyama.jp -tonami.toyama.jp -toyama.toyama.jp -unazuki.toyama.jp -uozu.toyama.jp -yamada.toyama.jp -arida.wakayama.jp -aridagawa.wakayama.jp -gobo.wakayama.jp -hashimoto.wakayama.jp -hidaka.wakayama.jp -hirogawa.wakayama.jp -inami.wakayama.jp -iwade.wakayama.jp -kainan.wakayama.jp -kamitonda.wakayama.jp -katsuragi.wakayama.jp -kimino.wakayama.jp -kinokawa.wakayama.jp -kitayama.wakayama.jp -koya.wakayama.jp -koza.wakayama.jp -kozagawa.wakayama.jp -kudoyama.wakayama.jp -kushimoto.wakayama.jp -mihama.wakayama.jp -misato.wakayama.jp -nachikatsuura.wakayama.jp -shingu.wakayama.jp -shirahama.wakayama.jp -taiji.wakayama.jp -tanabe.wakayama.jp -wakayama.wakayama.jp -yuasa.wakayama.jp -yura.wakayama.jp -asahi.yamagata.jp -funagata.yamagata.jp -higashine.yamagata.jp -iide.yamagata.jp -kahoku.yamagata.jp -kaminoyama.yamagata.jp -kaneyama.yamagata.jp -kawanishi.yamagata.jp -mamurogawa.yamagata.jp -mikawa.yamagata.jp -murayama.yamagata.jp -nagai.yamagata.jp -nakayama.yamagata.jp -nanyo.yamagata.jp -nishikawa.yamagata.jp -obanazawa.yamagata.jp -oe.yamagata.jp -oguni.yamagata.jp -ohkura.yamagata.jp -oishida.yamagata.jp -sagae.yamagata.jp -sakata.yamagata.jp -sakegawa.yamagata.jp -shinjo.yamagata.jp -shirataka.yamagata.jp -shonai.yamagata.jp -takahata.yamagata.jp -tendo.yamagata.jp -tozawa.yamagata.jp -tsuruoka.yamagata.jp -yamagata.yamagata.jp -yamanobe.yamagata.jp -yonezawa.yamagata.jp -yuza.yamagata.jp -abu.yamaguchi.jp -hagi.yamaguchi.jp -hikari.yamaguchi.jp -hofu.yamaguchi.jp -iwakuni.yamaguchi.jp -kudamatsu.yamaguchi.jp -mitou.yamaguchi.jp -nagato.yamaguchi.jp -oshima.yamaguchi.jp -shimonoseki.yamaguchi.jp -shunan.yamaguchi.jp -tabuse.yamaguchi.jp -tokuyama.yamaguchi.jp -toyota.yamaguchi.jp -ube.yamaguchi.jp -yuu.yamaguchi.jp -chuo.yamanashi.jp -doshi.yamanashi.jp -fuefuki.yamanashi.jp -fujikawa.yamanashi.jp -fujikawaguchiko.yamanashi.jp -fujiyoshida.yamanashi.jp -hayakawa.yamanashi.jp -hokuto.yamanashi.jp -ichikawamisato.yamanashi.jp -kai.yamanashi.jp -kofu.yamanashi.jp -koshu.yamanashi.jp -kosuge.yamanashi.jp -minami-alps.yamanashi.jp -minobu.yamanashi.jp -nakamichi.yamanashi.jp -nanbu.yamanashi.jp -narusawa.yamanashi.jp -nirasaki.yamanashi.jp -nishikatsura.yamanashi.jp -oshino.yamanashi.jp -otsuki.yamanashi.jp -showa.yamanashi.jp -tabayama.yamanashi.jp -tsuru.yamanashi.jp -uenohara.yamanashi.jp -yamanakako.yamanashi.jp -yamanashi.yamanashi.jp - -// ke : http://www.kenic.or.ke/index.php/en/ke-domains/ke-domains -ke -ac.ke -co.ke -go.ke -info.ke -me.ke -mobi.ke -ne.ke -or.ke -sc.ke - -// kg : http://www.domain.kg/dmn_n.html -kg -com.kg -edu.kg -gov.kg -mil.kg -net.kg -org.kg - -// kh : http://www.mptc.gov.kh/dns_registration.htm -*.kh - -// ki : https://www.iana.org/domains/root/db/ki.html -ki -biz.ki -com.ki -edu.ki -gov.ki -info.ki -net.ki -org.ki - -// km : https://www.iana.org/domains/root/db/km.html -// http://www.domaine.km/documents/charte.doc -km -ass.km -com.km -edu.km -gov.km -mil.km -nom.km -org.km -prd.km -tm.km -// These are only mentioned as proposed suggestions at domaine.km, but -// https://www.iana.org/domains/root/db/km.html says they're available for registration: -asso.km -coop.km -gouv.km -medecin.km -notaires.km -pharmaciens.km -presse.km -veterinaire.km - -// kn : https://www.iana.org/domains/root/db/kn.html -// http://www.dot.kn/domainRules.html -kn -edu.kn -gov.kn -net.kn -org.kn - -// kp : http://www.kcce.kp/en_index.php -kp -com.kp -edu.kp -gov.kp -org.kp -rep.kp -tra.kp - -// kr : https://www.iana.org/domains/root/db/kr.html -// see also: https://krnic.kisa.or.kr/jsp/infoboard/law/domBylawsReg.jsp -kr -ac.kr -ai.kr -co.kr -es.kr -go.kr -hs.kr -io.kr -it.kr -kg.kr -me.kr -mil.kr -ms.kr -ne.kr -or.kr -pe.kr -re.kr -sc.kr -// kr geographical names -busan.kr -chungbuk.kr -chungnam.kr -daegu.kr -daejeon.kr -gangwon.kr -gwangju.kr -gyeongbuk.kr -gyeonggi.kr -gyeongnam.kr -incheon.kr -jeju.kr -jeonbuk.kr -jeonnam.kr -seoul.kr -ulsan.kr - -// kw : https://www.nic.kw/policies/ -// Confirmed by registry -kw -com.kw -edu.kw -emb.kw -gov.kw -ind.kw -net.kw -org.kw - -// ky : http://www.icta.ky/da_ky_reg_dom.php -// Confirmed by registry 2008-06-17 -ky -com.ky -edu.ky -net.ky -org.ky - -// kz : https://www.iana.org/domains/root/db/kz.html -// see also: http://www.nic.kz/rules/index.jsp -kz -com.kz -edu.kz -gov.kz -mil.kz -net.kz -org.kz - -// la : https://www.iana.org/domains/root/db/la.html -// Submitted by registry -la -com.la -edu.la -gov.la -info.la -int.la -net.la -org.la -per.la - -// lb : https://www.iana.org/domains/root/db/lb.html -// Submitted by registry -lb -com.lb -edu.lb -gov.lb -net.lb -org.lb - -// lc : https://www.iana.org/domains/root/db/lc.html -// see also: http://www.nic.lc/rules.htm -lc -co.lc -com.lc -edu.lc -gov.lc -net.lc -org.lc - -// li : https://www.iana.org/domains/root/db/li.html -li - -// lk : https://www.iana.org/domains/root/db/lk.html -lk -ac.lk -assn.lk -com.lk -edu.lk -gov.lk -grp.lk -hotel.lk -int.lk -ltd.lk -net.lk -ngo.lk -org.lk -sch.lk -soc.lk -web.lk - -// lr : http://psg.com/dns/lr/lr.txt -// Submitted by registry -lr -com.lr -edu.lr -gov.lr -net.lr -org.lr - -// ls : http://www.nic.ls/ -// Confirmed by registry -ls -ac.ls -biz.ls -co.ls -edu.ls -gov.ls -info.ls -net.ls -org.ls -sc.ls - -// lt : https://www.iana.org/domains/root/db/lt.html -lt -// gov.lt : http://www.gov.lt/index_en.php -gov.lt - -// lu : http://www.dns.lu/en/ -lu - -// lv : https://www.iana.org/domains/root/db/lv.html -lv -asn.lv -com.lv -conf.lv -edu.lv -gov.lv -id.lv -mil.lv -net.lv -org.lv - -// ly : http://www.nic.ly/regulations.php -ly -com.ly -edu.ly -gov.ly -id.ly -med.ly -net.ly -org.ly -plc.ly -sch.ly - -// ma : https://www.iana.org/domains/root/db/ma.html -// http://www.anrt.ma/fr/admin/download/upload/file_fr782.pdf -ma -ac.ma -co.ma -gov.ma -net.ma -org.ma -press.ma - -// mc : http://www.nic.mc/ -mc -asso.mc -tm.mc - -// md : https://www.iana.org/domains/root/db/md.html -md - -// me : https://www.iana.org/domains/root/db/me.html -me -ac.me -co.me -edu.me -gov.me -its.me -net.me -org.me -priv.me - -// mg : https://nic.mg -mg -co.mg -com.mg -edu.mg -gov.mg -mil.mg -nom.mg -org.mg -prd.mg - -// mh : https://www.iana.org/domains/root/db/mh.html -mh - -// mil : https://www.iana.org/domains/root/db/mil.html -mil - -// mk : https://www.iana.org/domains/root/db/mk.html -// see also: http://dns.marnet.net.mk/postapka.php -mk -com.mk -edu.mk -gov.mk -inf.mk -name.mk -net.mk -org.mk - -// ml : https://www.iana.org/domains/root/db/ml.html -// Confirmed by Boubacar NDIAYE 2024-12-31 -ml -ac.ml -art.ml -asso.ml -com.ml -edu.ml -gouv.ml -gov.ml -info.ml -inst.ml -net.ml -org.ml -pr.ml -presse.ml - -// mm : https://www.iana.org/domains/root/db/mm.html -*.mm - -// mn : https://www.iana.org/domains/root/db/mn.html -mn -edu.mn -gov.mn -org.mn - -// mo : http://www.monic.net.mo/ -mo -com.mo -edu.mo -gov.mo -net.mo -org.mo - -// mobi : https://www.iana.org/domains/root/db/mobi.html -mobi - -// mp : http://www.dot.mp/ -// Confirmed by registry 2008-06-17 -mp - -// mq : https://www.iana.org/domains/root/db/mq.html -mq - -// mr : https://www.iana.org/domains/root/db/mr.html -mr -gov.mr - -// ms : https://www.iana.org/domains/root/db/ms.html -ms -com.ms -edu.ms -gov.ms -net.ms -org.ms - -// mt : https://www.nic.org.mt/go/policy -// Submitted by registry -mt -com.mt -edu.mt -net.mt -org.mt - -// mu : https://www.iana.org/domains/root/db/mu.html -mu -ac.mu -co.mu -com.mu -gov.mu -net.mu -or.mu -org.mu - -// museum : https://welcome.museum/wp-content/uploads/2018/05/20180525-Registration-Policy-MUSEUM-EN_VF-2.pdf https://welcome.museum/buy-your-dot-museum-2/ -museum - -// mv : https://www.iana.org/domains/root/db/mv.html -// "mv" included because, contra Wikipedia, google.mv exists. -mv -aero.mv -biz.mv -com.mv -coop.mv -edu.mv -gov.mv -info.mv -int.mv -mil.mv -museum.mv -name.mv -net.mv -org.mv -pro.mv - -// mw : http://www.registrar.mw/ -mw -ac.mw -biz.mw -co.mw -com.mw -coop.mw -edu.mw -gov.mw -int.mw -net.mw -org.mw - -// mx : http://www.nic.mx/ -// Submitted by registry -mx -com.mx -edu.mx -gob.mx -net.mx -org.mx - -// my : http://www.mynic.my/ -// Available strings: https://mynic.my/resources/domains/buying-a-domain/ -my -biz.my -com.my -edu.my -gov.my -mil.my -name.my -net.my -org.my - -// mz : http://www.uem.mz/ -// Submitted by registry -mz -ac.mz -adv.mz -co.mz -edu.mz -gov.mz -mil.mz -net.mz -org.mz - -// na : http://www.na-nic.com.na/ -na -alt.na -co.na -com.na -gov.na -net.na -org.na - -// name : http://www.nic.name/ -// Regarding 2LDs: https://github.com/publicsuffix/list/issues/2306 -name - -// nc : http://www.cctld.nc/ -nc -asso.nc -nom.nc - -// ne : https://www.iana.org/domains/root/db/ne.html -ne - -// net : https://www.iana.org/domains/root/db/net.html -net - -// nf : https://www.iana.org/domains/root/db/nf.html -nf -arts.nf -com.nf -firm.nf -info.nf -net.nf -other.nf -per.nf -rec.nf -store.nf -web.nf - -// ng : http://www.nira.org.ng/index.php/join-us/register-ng-domain/189-nira-slds -ng -com.ng -edu.ng -gov.ng -i.ng -mil.ng -mobi.ng -name.ng -net.ng -org.ng -sch.ng - -// ni : http://www.nic.ni/ -ni -ac.ni -biz.ni -co.ni -com.ni -edu.ni -gob.ni -in.ni -info.ni -int.ni -mil.ni -net.ni -nom.ni -org.ni -web.ni - -// nl : https://www.iana.org/domains/root/db/nl.html -// https://www.sidn.nl/ -nl - -// no : https://www.norid.no/en/om-domenenavn/regelverk-for-no/ -// Norid geographical second level domains : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-b/ -// Norid category second level domains : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-c/ -// Norid category second-level domains managed by parties other than Norid : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-d/ -// RSS feed: https://teknisk.norid.no/en/feed/ -no -// Norid category second level domains : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-c/ -fhs.no -folkebibl.no -fylkesbibl.no -idrett.no -museum.no -priv.no -vgs.no -// Norid category second-level domains managed by parties other than Norid : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-d/ -dep.no -herad.no -kommune.no -mil.no -stat.no -// Norid geographical second level domains : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-b/ -// counties -aa.no -ah.no -bu.no -fm.no -hl.no -hm.no -jan-mayen.no -mr.no -nl.no -nt.no -of.no -ol.no -oslo.no -rl.no -sf.no -st.no -svalbard.no -tm.no -tr.no -va.no -vf.no -// primary and lower secondary schools per county -gs.aa.no -gs.ah.no -gs.bu.no -gs.fm.no -gs.hl.no -gs.hm.no -gs.jan-mayen.no -gs.mr.no -gs.nl.no -gs.nt.no -gs.of.no -gs.ol.no -gs.oslo.no -gs.rl.no -gs.sf.no -gs.st.no -gs.svalbard.no -gs.tm.no -gs.tr.no -gs.va.no -gs.vf.no -// cities -akrehamn.no -åkrehamn.no -algard.no -ålgård.no -arna.no -bronnoysund.no -brønnøysund.no -brumunddal.no -bryne.no -drobak.no -drøbak.no -egersund.no -fetsund.no -floro.no -florø.no -fredrikstad.no -hokksund.no -honefoss.no -hønefoss.no -jessheim.no -jorpeland.no -jørpeland.no -kirkenes.no -kopervik.no -krokstadelva.no -langevag.no -langevåg.no -leirvik.no -mjondalen.no -mjøndalen.no -mo-i-rana.no -mosjoen.no -mosjøen.no -nesoddtangen.no -orkanger.no -osoyro.no -osøyro.no -raholt.no -råholt.no -sandnessjoen.no -sandnessjøen.no -skedsmokorset.no -slattum.no -spjelkavik.no -stathelle.no -stavern.no -stjordalshalsen.no -stjørdalshalsen.no -tananger.no -tranby.no -vossevangen.no -// communities -aarborte.no -aejrie.no -afjord.no -åfjord.no -agdenes.no -nes.akershus.no -aknoluokta.no -ákŋoluokta.no -al.no -ål.no -alaheadju.no -álaheadju.no -alesund.no -ålesund.no -alstahaug.no -alta.no -áltá.no -alvdal.no -amli.no -åmli.no -amot.no -åmot.no -andasuolo.no -andebu.no -andoy.no -andøy.no -ardal.no -årdal.no -aremark.no -arendal.no -ås.no -aseral.no -åseral.no -asker.no -askim.no -askoy.no -askøy.no -askvoll.no -asnes.no -åsnes.no -audnedaln.no -aukra.no -aure.no -aurland.no -aurskog-holand.no -aurskog-høland.no -austevoll.no -austrheim.no -averoy.no -averøy.no -badaddja.no -bådåddjå.no -bærum.no -bahcavuotna.no -báhcavuotna.no -bahccavuotna.no -báhccavuotna.no -baidar.no -báidár.no -bajddar.no -bájddar.no -balat.no -bálát.no -balestrand.no -ballangen.no -balsfjord.no -bamble.no -bardu.no -barum.no -batsfjord.no -båtsfjord.no -bearalvahki.no -bearalváhki.no -beardu.no -beiarn.no -berg.no -bergen.no -berlevag.no -berlevåg.no -bievat.no -bievát.no -bindal.no -birkenes.no -bjarkoy.no -bjarkøy.no -bjerkreim.no -bjugn.no -bodo.no -bodø.no -bokn.no -bomlo.no -bømlo.no -bremanger.no -bronnoy.no -brønnøy.no -budejju.no -nes.buskerud.no -bygland.no -bykle.no -cahcesuolo.no -čáhcesuolo.no -davvenjarga.no -davvenjárga.no -davvesiida.no -deatnu.no -dielddanuorri.no -divtasvuodna.no -divttasvuotna.no -donna.no -dønna.no -dovre.no -drammen.no -drangedal.no -dyroy.no -dyrøy.no -eid.no -eidfjord.no -eidsberg.no -eidskog.no -eidsvoll.no -eigersund.no -elverum.no -enebakk.no -engerdal.no -etne.no -etnedal.no -evenassi.no -evenášši.no -evenes.no -evje-og-hornnes.no -farsund.no -fauske.no -fedje.no -fet.no -finnoy.no -finnøy.no -fitjar.no -fjaler.no -fjell.no -fla.no -flå.no -flakstad.no -flatanger.no -flekkefjord.no -flesberg.no -flora.no -folldal.no -forde.no -førde.no -forsand.no -fosnes.no -fræna.no -frana.no -frei.no -frogn.no -froland.no -frosta.no -froya.no -frøya.no -fuoisku.no -fuossko.no -fusa.no -fyresdal.no -gaivuotna.no -gáivuotna.no -galsa.no -gálsá.no -gamvik.no -gangaviika.no -gáŋgaviika.no -gaular.no -gausdal.no -giehtavuoatna.no -gildeskal.no -gildeskål.no -giske.no -gjemnes.no -gjerdrum.no -gjerstad.no -gjesdal.no -gjovik.no -gjøvik.no -gloppen.no -gol.no -gran.no -grane.no -granvin.no -gratangen.no -grimstad.no -grong.no -grue.no -gulen.no -guovdageaidnu.no -ha.no -hå.no -habmer.no -hábmer.no -hadsel.no -hægebostad.no -hagebostad.no -halden.no -halsa.no -hamar.no -hamaroy.no -hammarfeasta.no -hámmárfeasta.no -hammerfest.no -hapmir.no -hápmir.no -haram.no -hareid.no -harstad.no -hasvik.no -hattfjelldal.no -haugesund.no -os.hedmark.no -valer.hedmark.no -våler.hedmark.no -hemne.no -hemnes.no -hemsedal.no -hitra.no -hjartdal.no -hjelmeland.no -hobol.no -hobøl.no -hof.no -hol.no -hole.no -holmestrand.no -holtalen.no -holtålen.no -os.hordaland.no -hornindal.no -horten.no -hoyanger.no -høyanger.no -hoylandet.no -høylandet.no -hurdal.no -hurum.no -hvaler.no -hyllestad.no -ibestad.no -inderoy.no -inderøy.no -iveland.no -ivgu.no -jevnaker.no -jolster.no -jølster.no -jondal.no -kafjord.no -kåfjord.no -karasjohka.no -kárášjohka.no -karasjok.no -karlsoy.no -karmoy.no -karmøy.no -kautokeino.no -klabu.no -klæbu.no -klepp.no -kongsberg.no -kongsvinger.no -kraanghke.no -kråanghke.no -kragero.no -kragerø.no -kristiansand.no -kristiansund.no -krodsherad.no -krødsherad.no -kvæfjord.no -kvænangen.no -kvafjord.no -kvalsund.no -kvam.no -kvanangen.no -kvinesdal.no -kvinnherad.no -kviteseid.no -kvitsoy.no -kvitsøy.no -laakesvuemie.no -lærdal.no -lahppi.no -láhppi.no -lardal.no -larvik.no -lavagis.no -lavangen.no -leangaviika.no -leaŋgaviika.no -lebesby.no -leikanger.no -leirfjord.no -leka.no -leksvik.no -lenvik.no -lerdal.no -lesja.no -levanger.no -lier.no -lierne.no -lillehammer.no -lillesand.no -lindas.no -lindås.no -lindesnes.no -loabat.no -loabát.no -lodingen.no -lødingen.no -lom.no -loppa.no -lorenskog.no -lørenskog.no -loten.no -løten.no -lund.no -lunner.no -luroy.no -lurøy.no -luster.no -lyngdal.no -lyngen.no -malatvuopmi.no -málatvuopmi.no -malselv.no -målselv.no -malvik.no -mandal.no -marker.no -marnardal.no -masfjorden.no -masoy.no -måsøy.no -matta-varjjat.no -mátta-várjjat.no -meland.no -meldal.no -melhus.no -meloy.no -meløy.no -meraker.no -meråker.no -midsund.no -midtre-gauldal.no -moareke.no -moåreke.no -modalen.no -modum.no -molde.no -heroy.more-og-romsdal.no -sande.more-og-romsdal.no -herøy.møre-og-romsdal.no -sande.møre-og-romsdal.no -moskenes.no -moss.no -mosvik.no -muosat.no -muosát.no -naamesjevuemie.no -nååmesjevuemie.no -nærøy.no -namdalseid.no -namsos.no -namsskogan.no -nannestad.no -naroy.no -narviika.no -narvik.no -naustdal.no -navuotna.no -návuotna.no -nedre-eiker.no -nesna.no -nesodden.no -nesseby.no -nesset.no -nissedal.no -nittedal.no -nord-aurdal.no -nord-fron.no -nord-odal.no -norddal.no -nordkapp.no -bo.nordland.no -bø.nordland.no -heroy.nordland.no -herøy.nordland.no -nordre-land.no -nordreisa.no -nore-og-uvdal.no -notodden.no -notteroy.no -nøtterøy.no -odda.no -oksnes.no -øksnes.no -omasvuotna.no -oppdal.no -oppegard.no -oppegård.no -orkdal.no -orland.no -ørland.no -orskog.no -ørskog.no -orsta.no -ørsta.no -osen.no -osteroy.no -osterøy.no -valer.ostfold.no -våler.østfold.no -ostre-toten.no -østre-toten.no -overhalla.no -ovre-eiker.no -øvre-eiker.no -oyer.no -øyer.no -oygarden.no -øygarden.no -oystre-slidre.no -øystre-slidre.no -porsanger.no -porsangu.no -porsáŋgu.no -porsgrunn.no -rade.no -råde.no -radoy.no -radøy.no -rælingen.no -rahkkeravju.no -ráhkkerávju.no -raisa.no -ráisa.no -rakkestad.no -ralingen.no -rana.no -randaberg.no -rauma.no -rendalen.no -rennebu.no -rennesoy.no -rennesøy.no -rindal.no -ringebu.no -ringerike.no -ringsaker.no -risor.no -risør.no -rissa.no -roan.no -rodoy.no -rødøy.no -rollag.no -romsa.no -romskog.no -rømskog.no -roros.no -røros.no -rost.no -røst.no -royken.no -røyken.no -royrvik.no -røyrvik.no -ruovat.no -rygge.no -salangen.no -salat.no -sálat.no -sálát.no -saltdal.no -samnanger.no -sandefjord.no -sandnes.no -sandoy.no -sandøy.no -sarpsborg.no -sauda.no -sauherad.no -sel.no -selbu.no -selje.no -seljord.no -siellak.no -sigdal.no -siljan.no -sirdal.no -skanit.no -skánit.no -skanland.no -skånland.no -skaun.no -skedsmo.no -ski.no -skien.no -skierva.no -skiervá.no -skiptvet.no -skjak.no -skjåk.no -skjervoy.no -skjervøy.no -skodje.no -smola.no -smøla.no -snaase.no -snåase.no -snasa.no -snåsa.no -snillfjord.no -snoasa.no -sogndal.no -sogne.no -søgne.no -sokndal.no -sola.no -solund.no -somna.no -sømna.no -sondre-land.no -søndre-land.no -songdalen.no -sor-aurdal.no -sør-aurdal.no -sor-fron.no -sør-fron.no -sor-odal.no -sør-odal.no -sor-varanger.no -sør-varanger.no -sorfold.no -sørfold.no -sorreisa.no -sørreisa.no -sortland.no -sorum.no -sørum.no -spydeberg.no -stange.no -stavanger.no -steigen.no -steinkjer.no -stjordal.no -stjørdal.no -stokke.no -stor-elvdal.no -stord.no -stordal.no -storfjord.no -strand.no -stranda.no -stryn.no -sula.no -suldal.no -sund.no -sunndal.no -surnadal.no -sveio.no -svelvik.no -sykkylven.no -tana.no -bo.telemark.no -bø.telemark.no -time.no -tingvoll.no -tinn.no -tjeldsund.no -tjome.no -tjøme.no -tokke.no -tolga.no -tonsberg.no -tønsberg.no -torsken.no -træna.no -trana.no -tranoy.no -tranøy.no -troandin.no -trogstad.no -trøgstad.no -tromsa.no -tromso.no -tromsø.no -trondheim.no -trysil.no -tvedestrand.no -tydal.no -tynset.no -tysfjord.no -tysnes.no -tysvær.no -tysvar.no -ullensaker.no -ullensvang.no -ulvik.no -unjarga.no -unjárga.no -utsira.no -vaapste.no -vadso.no -vadsø.no -værøy.no -vaga.no -vågå.no -vagan.no -vågan.no -vagsoy.no -vågsøy.no -vaksdal.no -valle.no -vang.no -vanylven.no -vardo.no -vardø.no -varggat.no -várggát.no -varoy.no -vefsn.no -vega.no -vegarshei.no -vegårshei.no -vennesla.no -verdal.no -verran.no -vestby.no -sande.vestfold.no -vestnes.no -vestre-slidre.no -vestre-toten.no -vestvagoy.no -vestvågøy.no -vevelstad.no -vik.no -vikna.no -vindafjord.no -voagat.no -volda.no -voss.no - -// np : http://www.mos.com.np/register.html -*.np - -// nr : http://cenpac.net.nr/dns/index.html -// Submitted by registry -nr -biz.nr -com.nr -edu.nr -gov.nr -info.nr -net.nr -org.nr - -// nu : https://www.iana.org/domains/root/db/nu.html -nu - -// nz : https://www.iana.org/domains/root/db/nz.html -// Submitted by registry -nz -ac.nz -co.nz -cri.nz -geek.nz -gen.nz -govt.nz -health.nz -iwi.nz -kiwi.nz -maori.nz -māori.nz -mil.nz -net.nz -org.nz -parliament.nz -school.nz - -// om : https://www.iana.org/domains/root/db/om.html -om -co.om -com.om -edu.om -gov.om -med.om -museum.om -net.om -org.om -pro.om - -// onion : https://tools.ietf.org/html/rfc7686 -onion - -// org : https://www.iana.org/domains/root/db/org.html -org - -// pa : http://www.nic.pa/ -// Some additional second level "domains" resolve directly as hostnames, such as -// pannet.pa, so we add a rule for "pa". -pa -abo.pa -ac.pa -com.pa -edu.pa -gob.pa -ing.pa -med.pa -net.pa -nom.pa -org.pa -sld.pa - -// pe : https://www.nic.pe/InformeFinalComision.pdf -pe -com.pe -edu.pe -gob.pe -mil.pe -net.pe -nom.pe -org.pe - -// pf : http://www.gobin.info/domainname/formulaire-pf.pdf -pf -com.pf -edu.pf -org.pf - -// pg : https://www.iana.org/domains/root/db/pg.html -*.pg - -// ph : https://www.iana.org/domains/root/db/ph.html -// Submitted by registry -ph -com.ph -edu.ph -gov.ph -i.ph -mil.ph -net.ph -ngo.ph -org.ph - -// pk : https://pk5.pknic.net.pk/pk5/msgNamepk.PK -// Contact Email: staff@pknic.net.pk -pk -ac.pk -biz.pk -com.pk -edu.pk -fam.pk -gkp.pk -gob.pk -gog.pk -gok.pk -gop.pk -gos.pk -gov.pk -net.pk -org.pk -web.pk - -// pl : https://www.dns.pl/en/ -// Confirmed by registry 2024-11-18 -pl -com.pl -net.pl -org.pl -// pl functional domains : https://www.dns.pl/en/list_of_functional_domain_names -agro.pl -aid.pl -atm.pl -auto.pl -biz.pl -edu.pl -gmina.pl -gsm.pl -info.pl -mail.pl -media.pl -miasta.pl -mil.pl -nieruchomosci.pl -nom.pl -pc.pl -powiat.pl -priv.pl -realestate.pl -rel.pl -sex.pl -shop.pl -sklep.pl -sos.pl -szkola.pl -targi.pl -tm.pl -tourism.pl -travel.pl -turystyka.pl -// Government domains : https://www.dns.pl/informacje_o_rejestracji_domen_gov_pl -// In accordance with the .gov.pl Domain Name Regulations : https://www.dns.pl/regulamin_gov_pl -gov.pl -ap.gov.pl -griw.gov.pl -ic.gov.pl -is.gov.pl -kmpsp.gov.pl -konsulat.gov.pl -kppsp.gov.pl -kwp.gov.pl -kwpsp.gov.pl -mup.gov.pl -mw.gov.pl -oia.gov.pl -oirm.gov.pl -oke.gov.pl -oow.gov.pl -oschr.gov.pl -oum.gov.pl -pa.gov.pl -pinb.gov.pl -piw.gov.pl -po.gov.pl -pr.gov.pl -psp.gov.pl -psse.gov.pl -pup.gov.pl -rzgw.gov.pl -sa.gov.pl -sdn.gov.pl -sko.gov.pl -so.gov.pl -sr.gov.pl -starostwo.gov.pl -ug.gov.pl -ugim.gov.pl -um.gov.pl -umig.gov.pl -upow.gov.pl -uppo.gov.pl -us.gov.pl -uw.gov.pl -uzs.gov.pl -wif.gov.pl -wiih.gov.pl -winb.gov.pl -wios.gov.pl -witd.gov.pl -wiw.gov.pl -wkz.gov.pl -wsa.gov.pl -wskr.gov.pl -wsse.gov.pl -wuoz.gov.pl -wzmiuw.gov.pl -zp.gov.pl -zpisdn.gov.pl -// pl regional domains : https://www.dns.pl/en/list_of_regional_domain_names -augustow.pl -babia-gora.pl -bedzin.pl -beskidy.pl -bialowieza.pl -bialystok.pl -bielawa.pl -bieszczady.pl -boleslawiec.pl -bydgoszcz.pl -bytom.pl -cieszyn.pl -czeladz.pl -czest.pl -dlugoleka.pl -elblag.pl -elk.pl -glogow.pl -gniezno.pl -gorlice.pl -grajewo.pl -ilawa.pl -jaworzno.pl -jelenia-gora.pl -jgora.pl -kalisz.pl -karpacz.pl -kartuzy.pl -kaszuby.pl -katowice.pl -kazimierz-dolny.pl -kepno.pl -ketrzyn.pl -klodzko.pl -kobierzyce.pl -kolobrzeg.pl -konin.pl -konskowola.pl -kutno.pl -lapy.pl -lebork.pl -legnica.pl -lezajsk.pl -limanowa.pl -lomza.pl -lowicz.pl -lubin.pl -lukow.pl -malbork.pl -malopolska.pl -mazowsze.pl -mazury.pl -mielec.pl -mielno.pl -mragowo.pl -naklo.pl -nowaruda.pl -nysa.pl -olawa.pl -olecko.pl -olkusz.pl -olsztyn.pl -opoczno.pl -opole.pl -ostroda.pl -ostroleka.pl -ostrowiec.pl -ostrowwlkp.pl -pila.pl -pisz.pl -podhale.pl -podlasie.pl -polkowice.pl -pomorskie.pl -pomorze.pl -prochowice.pl -pruszkow.pl -przeworsk.pl -pulawy.pl -radom.pl -rawa-maz.pl -rybnik.pl -rzeszow.pl -sanok.pl -sejny.pl -skoczow.pl -slask.pl -slupsk.pl -sosnowiec.pl -stalowa-wola.pl -starachowice.pl -stargard.pl -suwalki.pl -swidnica.pl -swiebodzin.pl -swinoujscie.pl -szczecin.pl -szczytno.pl -tarnobrzeg.pl -tgory.pl -turek.pl -tychy.pl -ustka.pl -walbrzych.pl -warmia.pl -warszawa.pl -waw.pl -wegrow.pl -wielun.pl -wlocl.pl -wloclawek.pl -wodzislaw.pl -wolomin.pl -wroclaw.pl -zachpomor.pl -zagan.pl -zarow.pl -zgora.pl -zgorzelec.pl - -// pm : https://www.afnic.fr/wp-media/uploads/2022/12/afnic-naming-policy-2023-01-01.pdf -pm - -// pn : https://www.iana.org/domains/root/db/pn.html -pn -co.pn -edu.pn -gov.pn -net.pn -org.pn - -// post : https://www.iana.org/domains/root/db/post.html -post - -// pr : http://www.nic.pr/index.asp?f=1 -pr -biz.pr -com.pr -edu.pr -gov.pr -info.pr -isla.pr -name.pr -net.pr -org.pr -pro.pr -// these aren't mentioned on nic.pr, but on https://www.iana.org/domains/root/db/pr.html -ac.pr -est.pr -prof.pr - -// pro : http://registry.pro/get-pro -pro -aaa.pro -aca.pro -acct.pro -avocat.pro -bar.pro -cpa.pro -eng.pro -jur.pro -law.pro -med.pro -recht.pro - -// ps : https://www.iana.org/domains/root/db/ps.html -// http://www.nic.ps/registration/policy.html#reg -ps -com.ps -edu.ps -gov.ps -net.ps -org.ps -plo.ps -sec.ps - -// pt : https://www.dns.pt/en/domain/pt-terms-and-conditions-registration-rules/ -pt -com.pt -edu.pt -gov.pt -int.pt -net.pt -nome.pt -org.pt -publ.pt - -// pw : https://www.iana.org/domains/root/db/pw.html -// Confirmed by registry in private correspondence with @dnsguru 2024-12-09 -pw -gov.pw - -// py : https://www.iana.org/domains/root/db/py.html -// Submitted by registry -py -com.py -coop.py -edu.py -gov.py -mil.py -net.py -org.py - -// qa : http://domains.qa/en/ -qa -com.qa -edu.qa -gov.qa -mil.qa -name.qa -net.qa -org.qa -sch.qa - -// re : https://www.afnic.fr/wp-media/uploads/2022/12/afnic-naming-policy-2023-01-01.pdf -// Confirmed by registry 2024-11-18 -re -// Closed for registration on 2013-03-15 but domains are still maintained -asso.re -com.re - -// ro : http://www.rotld.ro/ -ro -arts.ro -com.ro -firm.ro -info.ro -nom.ro -nt.ro -org.ro -rec.ro -store.ro -tm.ro -www.ro - -// rs : https://www.rnids.rs/en/domains/national-domains -rs -ac.rs -co.rs -edu.rs -gov.rs -in.rs -org.rs - -// ru : https://cctld.ru/files/pdf/docs/en/rules_ru-rf.pdf -// Submitted by George Georgievsky -ru - -// rw : https://www.iana.org/domains/root/db/rw.html -rw -ac.rw -co.rw -coop.rw -gov.rw -mil.rw -net.rw -org.rw - -// sa : http://www.nic.net.sa/ -sa -com.sa -edu.sa -gov.sa -med.sa -net.sa -org.sa -pub.sa -sch.sa - -// sb : http://www.sbnic.net.sb/ -// Submitted by registry -sb -com.sb -edu.sb -gov.sb -net.sb -org.sb - -// sc : http://www.nic.sc/ -sc -com.sc -edu.sc -gov.sc -net.sc -org.sc - -// sd : https://www.iana.org/domains/root/db/sd.html -// Submitted by registry -sd -com.sd -edu.sd -gov.sd -info.sd -med.sd -net.sd -org.sd -tv.sd - -// se : https://www.iana.org/domains/root/db/se.html -// https://data.internetstiftelsen.se/barred_domains_list.txt -> Second level domains & Sub-domains -// Confirmed by Registry Services 2024-11-20 -se -a.se -ac.se -b.se -bd.se -brand.se -c.se -d.se -e.se -f.se -fh.se -fhsk.se -fhv.se -g.se -h.se -i.se -k.se -komforb.se -kommunalforbund.se -komvux.se -l.se -lanbib.se -m.se -n.se -naturbruksgymn.se -o.se -org.se -p.se -parti.se -pp.se -press.se -r.se -s.se -t.se -tm.se -u.se -w.se -x.se -y.se -z.se - -// sg : https://www.sgnic.sg/domain-registration/sg-categories-rules -// Confirmed by registry 2024-11-19 -sg -com.sg -edu.sg -gov.sg -net.sg -org.sg - -// sh : http://nic.sh/rules.htm -sh -com.sh -gov.sh -mil.sh -net.sh -org.sh - -// si : https://www.iana.org/domains/root/db/si.html -si - -// sj : No registrations at this time. -// Submitted by registry -sj - -// sk : https://www.iana.org/domains/root/db/sk.html -sk - -// sl : http://www.nic.sl -// Submitted by registry -sl -com.sl -edu.sl -gov.sl -net.sl -org.sl - -// sm : https://www.iana.org/domains/root/db/sm.html -sm - -// sn : https://www.iana.org/domains/root/db/sn.html -sn -art.sn -com.sn -edu.sn -gouv.sn -org.sn -perso.sn -univ.sn - -// so : http://sonic.so/policies/ -so -com.so -edu.so -gov.so -me.so -net.so -org.so - -// sr : https://www.iana.org/domains/root/db/sr.html -sr - -// ss : https://registry.nic.ss/ -// Submitted by registry -ss -biz.ss -co.ss -com.ss -edu.ss -gov.ss -me.ss -net.ss -org.ss -sch.ss - -// st : http://www.nic.st/html/policyrules/ -st -co.st -com.st -consulado.st -edu.st -embaixada.st -mil.st -net.st -org.st -principe.st -saotome.st -store.st - -// su : https://www.iana.org/domains/root/db/su.html -su - -// sv : https://www.iana.org/domains/root/db/sv.html -sv -com.sv -edu.sv -gob.sv -org.sv -red.sv - -// sx : https://www.iana.org/domains/root/db/sx.html -// Submitted by registry -sx -gov.sx - -// sy : https://www.iana.org/domains/root/db/sy.html -sy -com.sy -edu.sy -gov.sy -mil.sy -net.sy -org.sy - -// sz : https://www.iana.org/domains/root/db/sz.html -// http://www.sispa.org.sz/ -sz -ac.sz -co.sz -org.sz - -// tc : https://www.iana.org/domains/root/db/tc.html -tc - -// td : https://www.iana.org/domains/root/db/td.html -td - -// tel : https://www.iana.org/domains/root/db/tel.html -// http://www.telnic.org/ -tel - -// tf : https://www.afnic.fr/wp-media/uploads/2022/12/afnic-naming-policy-2023-01-01.pdf -tf - -// tg : https://www.iana.org/domains/root/db/tg.html -// http://www.nic.tg/ -tg - -// th : https://www.iana.org/domains/root/db/th.html -// Submitted by registry -th -ac.th -co.th -go.th -in.th -mi.th -net.th -or.th - -// tj : http://www.nic.tj/policy.html -tj -ac.tj -biz.tj -co.tj -com.tj -edu.tj -go.tj -gov.tj -int.tj -mil.tj -name.tj -net.tj -nic.tj -org.tj -test.tj -web.tj - -// tk : https://www.iana.org/domains/root/db/tk.html -tk - -// tl : https://www.iana.org/domains/root/db/tl.html -tl -gov.tl - -// tm : https://www.nic.tm/local.html -// Confirmed by registry 2024-11-19 -tm -co.tm -com.tm -edu.tm -gov.tm -mil.tm -net.tm -nom.tm -org.tm - -// tn : http://www.registre.tn/fr/ -// https://whois.ati.tn/ -tn -com.tn -ens.tn -fin.tn -gov.tn -ind.tn -info.tn -intl.tn -mincom.tn -nat.tn -net.tn -org.tn -perso.tn -tourism.tn - -// to : https://www.iana.org/domains/root/db/to.html -// Submitted by registry -to -com.to -edu.to -gov.to -mil.to -net.to -org.to - -// tr : https://nic.tr/ -// https://nic.tr/forms/eng/policies.pdf -// https://nic.tr/index.php?USRACTN=PRICELST -tr -av.tr -bbs.tr -bel.tr -biz.tr -com.tr -dr.tr -edu.tr -gen.tr -gov.tr -info.tr -k12.tr -kep.tr -mil.tr -name.tr -net.tr -org.tr -pol.tr -tel.tr -tsk.tr -tv.tr -web.tr -// Used by Northern Cyprus -nc.tr -// Used by government agencies of Northern Cyprus -gov.nc.tr - -// tt : https://www.nic.tt/ -// Confirmed by registry 2024-11-19 -tt -biz.tt -co.tt -com.tt -edu.tt -gov.tt -info.tt -mil.tt -name.tt -net.tt -org.tt -pro.tt - -// tv : https://www.iana.org/domains/root/db/tv.html -// Not listing any 2LDs as reserved since none seem to exist in practice, -// Wikipedia notwithstanding. -tv - -// tw : https://www.iana.org/domains/root/db/tw.html -// https://twnic.tw/dnservice_catag.php -// Confirmed by registry 2024-11-26 -tw -club.tw -com.tw -ebiz.tw -edu.tw -game.tw -gov.tw -idv.tw -mil.tw -net.tw -org.tw - -// tz : http://www.tznic.or.tz/index.php/domains -// Submitted by registry -tz -ac.tz -co.tz -go.tz -hotel.tz -info.tz -me.tz -mil.tz -mobi.tz -ne.tz -or.tz -sc.tz -tv.tz - -// ua : https://hostmaster.ua/policy/?ua -// Submitted by registry -ua -// ua 2LD -com.ua -edu.ua -gov.ua -in.ua -net.ua -org.ua -// ua geographic names -// https://hostmaster.ua/2ld/ -cherkassy.ua -cherkasy.ua -chernigov.ua -chernihiv.ua -chernivtsi.ua -chernovtsy.ua -ck.ua -cn.ua -cr.ua -crimea.ua -cv.ua -dn.ua -dnepropetrovsk.ua -dnipropetrovsk.ua -donetsk.ua -dp.ua -if.ua -ivano-frankivsk.ua -kh.ua -kharkiv.ua -kharkov.ua -kherson.ua -khmelnitskiy.ua -khmelnytskyi.ua -kiev.ua -kirovograd.ua -km.ua -kr.ua -kropyvnytskyi.ua -krym.ua -ks.ua -kv.ua -kyiv.ua -lg.ua -lt.ua -lugansk.ua -luhansk.ua -lutsk.ua -lv.ua -lviv.ua -mk.ua -mykolaiv.ua -nikolaev.ua -od.ua -odesa.ua -odessa.ua -pl.ua -poltava.ua -rivne.ua -rovno.ua -rv.ua -sb.ua -sebastopol.ua -sevastopol.ua -sm.ua -sumy.ua -te.ua -ternopil.ua -uz.ua -uzhgorod.ua -uzhhorod.ua -vinnica.ua -vinnytsia.ua -vn.ua -volyn.ua -yalta.ua -zakarpattia.ua -zaporizhzhe.ua -zaporizhzhia.ua -zhitomir.ua -zhytomyr.ua -zp.ua -zt.ua - -// ug : https://www.registry.co.ug/ -// https://www.registry.co.ug, https://whois.co.ug -// Confirmed by registry 2025-01-20 -ug -ac.ug -co.ug -com.ug -edu.ug -go.ug -gov.ug -mil.ug -ne.ug -or.ug -org.ug -sc.ug -us.ug - -// uk : https://www.iana.org/domains/root/db/uk.html -// Submitted by registry -uk -ac.uk -co.uk -gov.uk -ltd.uk -me.uk -net.uk -nhs.uk -org.uk -plc.uk -police.uk -*.sch.uk - -// us : https://www.iana.org/domains/root/db/us.html -// Confirmed via the .us zone file by William Harrison 2024-12-10 -us -dni.us -isa.us -nsn.us -// Geographic Names -ak.us -al.us -ar.us -as.us -az.us -ca.us -co.us -ct.us -dc.us -de.us -fl.us -ga.us -gu.us -hi.us -ia.us -id.us -il.us -in.us -ks.us -ky.us -la.us -ma.us -md.us -me.us -mi.us -mn.us -mo.us -ms.us -mt.us -nc.us -nd.us -ne.us -nh.us -nj.us -nm.us -nv.us -ny.us -oh.us -ok.us -or.us -pa.us -pr.us -ri.us -sc.us -sd.us -tn.us -tx.us -ut.us -va.us -vi.us -vt.us -wa.us -wi.us -wv.us -wy.us -// The registrar notes several more specific domains available in each state, -// such as state.*.us, dst.*.us, etc., but resolution of these is somewhat -// haphazard; in some states these domains resolve as addresses, while in others -// only subdomains are available, or even nothing at all. We include the -// most common ones where it's clear that different sites are different -// entities. -k12.ak.us -k12.al.us -k12.ar.us -k12.as.us -k12.az.us -k12.ca.us -k12.co.us -k12.ct.us -k12.dc.us -k12.fl.us -k12.ga.us -k12.gu.us -// k12.hi.us - Bug 614565 - Hawaii has a state-wide DOE login -k12.ia.us -k12.id.us -k12.il.us -k12.in.us -k12.ks.us -k12.ky.us -k12.la.us -k12.ma.us -k12.md.us -k12.me.us -k12.mi.us -k12.mn.us -k12.mo.us -k12.ms.us -k12.mt.us -k12.nc.us -// k12.nd.us - Bug 1028347 - Removed at request of Travis Rosso -k12.ne.us -k12.nh.us -k12.nj.us -k12.nm.us -k12.nv.us -k12.ny.us -k12.oh.us -k12.ok.us -k12.or.us -k12.pa.us -k12.pr.us -// k12.ri.us - Removed at request of Kim Cournoyer -k12.sc.us -// k12.sd.us - Bug 934131 - Removed at request of James Booze -k12.tn.us -k12.tx.us -k12.ut.us -k12.va.us -k12.vi.us -k12.vt.us -k12.wa.us -k12.wi.us -// k12.wv.us - Bug 947705 - Removed at request of Verne Britton -cc.ak.us -lib.ak.us -cc.al.us -lib.al.us -cc.ar.us -lib.ar.us -cc.as.us -lib.as.us -cc.az.us -lib.az.us -cc.ca.us -lib.ca.us -cc.co.us -lib.co.us -cc.ct.us -lib.ct.us -cc.dc.us -lib.dc.us -cc.de.us -cc.fl.us -lib.fl.us -cc.ga.us -lib.ga.us -cc.gu.us -lib.gu.us -cc.hi.us -lib.hi.us -cc.ia.us -lib.ia.us -cc.id.us -lib.id.us -cc.il.us -lib.il.us -cc.in.us -lib.in.us -cc.ks.us -lib.ks.us -cc.ky.us -lib.ky.us -cc.la.us -lib.la.us -cc.ma.us -lib.ma.us -cc.md.us -lib.md.us -cc.me.us -lib.me.us -cc.mi.us -lib.mi.us -cc.mn.us -lib.mn.us -cc.mo.us -lib.mo.us -cc.ms.us -cc.mt.us -lib.mt.us -cc.nc.us -lib.nc.us -cc.nd.us -lib.nd.us -cc.ne.us -lib.ne.us -cc.nh.us -lib.nh.us -cc.nj.us -lib.nj.us -cc.nm.us -lib.nm.us -cc.nv.us -lib.nv.us -cc.ny.us -lib.ny.us -cc.oh.us -lib.oh.us -cc.ok.us -lib.ok.us -cc.or.us -lib.or.us -cc.pa.us -lib.pa.us -cc.pr.us -lib.pr.us -cc.ri.us -lib.ri.us -cc.sc.us -lib.sc.us -cc.sd.us -lib.sd.us -cc.tn.us -lib.tn.us -cc.tx.us -lib.tx.us -cc.ut.us -lib.ut.us -cc.va.us -lib.va.us -cc.vi.us -lib.vi.us -cc.vt.us -lib.vt.us -cc.wa.us -lib.wa.us -cc.wi.us -lib.wi.us -cc.wv.us -cc.wy.us -k12.wy.us -// lib.wv.us - Bug 941670 - Removed at request of Larry W Arnold -lib.wy.us -// k12.ma.us contains school districts in Massachusetts. The 4LDs are -// managed independently except for private (PVT), charter (CHTR) and -// parochial (PAROCH) schools. Those are delegated directly to the -// 5LD operators. -chtr.k12.ma.us -paroch.k12.ma.us -pvt.k12.ma.us -// Merit Network, Inc. maintains the registry for =~ /(k12|cc|lib).mi.us/ and the following -// see also: https://domreg.merit.edu : domreg@merit.edu -// see also: whois -h whois.domreg.merit.edu help -ann-arbor.mi.us -cog.mi.us -dst.mi.us -eaton.mi.us -gen.mi.us -mus.mi.us -tec.mi.us -washtenaw.mi.us - -// uy : http://www.nic.org.uy/ -uy -com.uy -edu.uy -gub.uy -mil.uy -net.uy -org.uy - -// uz : http://www.reg.uz/ -uz -co.uz -com.uz -net.uz -org.uz - -// va : https://www.iana.org/domains/root/db/va.html -va - -// vc : https://www.iana.org/domains/root/db/vc.html -// Submitted by registry -vc -com.vc -edu.vc -gov.vc -mil.vc -net.vc -org.vc - -// ve : https://registro.nic.ve/ -// Submitted by registry nic@nic.ve and nicve@conatel.gob.ve -ve -arts.ve -bib.ve -co.ve -com.ve -e12.ve -edu.ve -emprende.ve -firm.ve -gob.ve -gov.ve -info.ve -int.ve -mil.ve -net.ve -nom.ve -org.ve -rar.ve -rec.ve -store.ve -tec.ve -web.ve - -// vg : https://www.iana.org/domains/root/db/vg.html -// Confirmed by registry 2025-01-10 -vg -edu.vg - -// vi : https://www.iana.org/domains/root/db/vi.html -vi -co.vi -com.vi -k12.vi -net.vi -org.vi - -// vn : https://www.vnnic.vn/en/domain/cctld-vn -// https://vnnic.vn/sites/default/files/tailieu/vn.cctld.domains.txt -vn -ac.vn -ai.vn -biz.vn -com.vn -edu.vn -gov.vn -health.vn -id.vn -info.vn -int.vn -io.vn -name.vn -net.vn -org.vn -pro.vn - -// vn geographical names -angiang.vn -bacgiang.vn -backan.vn -baclieu.vn -bacninh.vn -baria-vungtau.vn -bentre.vn -binhdinh.vn -binhduong.vn -binhphuoc.vn -binhthuan.vn -camau.vn -cantho.vn -caobang.vn -daklak.vn -daknong.vn -danang.vn -dienbien.vn -dongnai.vn -dongthap.vn -gialai.vn -hagiang.vn -haiduong.vn -haiphong.vn -hanam.vn -hanoi.vn -hatinh.vn -haugiang.vn -hoabinh.vn -hungyen.vn -khanhhoa.vn -kiengiang.vn -kontum.vn -laichau.vn -lamdong.vn -langson.vn -laocai.vn -longan.vn -namdinh.vn -nghean.vn -ninhbinh.vn -ninhthuan.vn -phutho.vn -phuyen.vn -quangbinh.vn -quangnam.vn -quangngai.vn -quangninh.vn -quangtri.vn -soctrang.vn -sonla.vn -tayninh.vn -thaibinh.vn -thainguyen.vn -thanhhoa.vn -thanhphohochiminh.vn -thuathienhue.vn -tiengiang.vn -travinh.vn -tuyenquang.vn -vinhlong.vn -vinhphuc.vn -yenbai.vn - -// vu : https://www.iana.org/domains/root/db/vu.html -// http://www.vunic.vu/ -vu -com.vu -edu.vu -net.vu -org.vu - -// wf : https://www.afnic.fr/wp-media/uploads/2022/12/afnic-naming-policy-2023-01-01.pdf -wf - -// ws : https://www.iana.org/domains/root/db/ws.html -// http://samoanic.ws/index.dhtml -ws -com.ws -edu.ws -gov.ws -net.ws -org.ws - -// yt : https://www.afnic.fr/wp-media/uploads/2022/12/afnic-naming-policy-2023-01-01.pdf -yt - -// IDN ccTLDs -// When submitting patches, please maintain a sort by ISO 3166 ccTLD, then -// U-label, and follow this format: -// // A-Label ("", [, variant info]) : -// // [sponsoring org] -// U-Label - -// xn--mgbaam7a8h ("Emerat", Arabic) : AE -// http://nic.ae/english/arabicdomain/rules.jsp -امارات - -// xn--y9a3aq ("hye", Armenian) : AM -// ISOC AM (operated by .am Registry) -հայ - -// xn--54b7fta0cc ("Bangla", Bangla) : BD -বাংলা - -// xn--90ae ("bg", Bulgarian) : BG -бг - -// xn--mgbcpq6gpa1a ("albahrain", Arabic) : BH -البحرين - -// xn--90ais ("bel", Belarusian/Russian Cyrillic) : BY -// Operated by .by registry -бел - -// xn--fiqs8s ("Zhongguo/China", Chinese, Simplified) : CN -// CNNIC -// https://www.cnnic.cn/11/192/index.html -中国 - -// xn--fiqz9s ("Zhongguo/China", Chinese, Traditional) : CN -// CNNIC -// https://www.cnnic.com.cn/AU/MediaC/Announcement/201609/t20160905_54470.htm -中國 - -// xn--lgbbat1ad8j ("Algeria/Al Jazair", Arabic) : DZ -الجزائر - -// xn--wgbh1c ("Egypt/Masr", Arabic) : EG -// http://www.dotmasr.eg/ -مصر - -// xn--e1a4c ("eu", Cyrillic) : EU -// https://eurid.eu -ею - -// xn--qxa6a ("eu", Greek) : EU -// https://eurid.eu -ευ - -// xn--mgbah1a3hjkrd ("Mauritania", Arabic) : MR -موريتانيا - -// xn--node ("ge", Georgian Mkhedruli) : GE -გე - -// xn--qxam ("el", Greek) : GR -// Hellenic Ministry of Infrastructure, Transport, and Networks -ελ - -// xn--j6w193g ("Hong Kong", Chinese) : HK -// https://www.hkirc.hk -// Submitted by registry -// https://www.hkirc.hk/content.jsp?id=30#!/34 -香港 -個人.香港 -公司.香港 -政府.香港 -教育.香港 -組織.香港 -網絡.香港 - -// xn--2scrj9c ("Bharat", Kannada) : IN -// India -ಭಾರತ - -// xn--3hcrj9c ("Bharat", Oriya) : IN -// India -ଭାରତ - -// xn--45br5cyl ("Bharatam", Assamese) : IN -// India -ভাৰত - -// xn--h2breg3eve ("Bharatam", Sanskrit) : IN -// India -भारतम् - -// xn--h2brj9c8c ("Bharot", Santali) : IN -// India -भारोत - -// xn--mgbgu82a ("Bharat", Sindhi) : IN -// India -ڀارت - -// xn--rvc1e0am3e ("Bharatam", Malayalam) : IN -// India -ഭാരതം - -// xn--h2brj9c ("Bharat", Devanagari) : IN -// India -भारत - -// xn--mgbbh1a ("Bharat", Kashmiri) : IN -// India -بارت - -// xn--mgbbh1a71e ("Bharat", Arabic) : IN -// India -بھارت - -// xn--fpcrj9c3d ("Bharat", Telugu) : IN -// India -భారత్ - -// xn--gecrj9c ("Bharat", Gujarati) : IN -// India -ભારત - -// xn--s9brj9c ("Bharat", Gurmukhi) : IN -// India -ਭਾਰਤ - -// xn--45brj9c ("Bharat", Bengali) : IN -// India -ভারত - -// xn--xkc2dl3a5ee0h ("India", Tamil) : IN -// India -இந்தியா - -// xn--mgba3a4f16a ("Iran", Persian) : IR -ایران - -// xn--mgba3a4fra ("Iran", Arabic) : IR -ايران - -// xn--mgbtx2b ("Iraq", Arabic) : IQ -// Communications and Media Commission -عراق - -// xn--mgbayh7gpa ("al-Ordon", Arabic) : JO -// National Information Technology Center (NITC) -// Royal Scientific Society, Al-Jubeiha -الاردن - -// xn--3e0b707e ("Republic of Korea", Hangul) : KR -한국 - -// xn--80ao21a ("Kaz", Kazakh) : KZ -қаз - -// xn--q7ce6a ("Lao", Lao) : LA -ລາວ - -// xn--fzc2c9e2c ("Lanka", Sinhalese-Sinhala) : LK -// https://nic.lk -ලංකා - -// xn--xkc2al3hye2a ("Ilangai", Tamil) : LK -// https://nic.lk -இலங்கை - -// xn--mgbc0a9azcg ("Morocco/al-Maghrib", Arabic) : MA -المغرب - -// xn--d1alf ("mkd", Macedonian) : MK -// MARnet -мкд - -// xn--l1acc ("mon", Mongolian) : MN -мон - -// xn--mix891f ("Macao", Chinese, Traditional) : MO -// MONIC / HNET Asia (Registry Operator for .mo) -澳門 - -// xn--mix082f ("Macao", Chinese, Simplified) : MO -澳门 - -// xn--mgbx4cd0ab ("Malaysia", Malay) : MY -مليسيا - -// xn--mgb9awbf ("Oman", Arabic) : OM -عمان - -// xn--mgbai9azgqp6j ("Pakistan", Urdu/Arabic) : PK -پاکستان - -// xn--mgbai9a5eva00b ("Pakistan", Urdu/Arabic, variant) : PK -پاكستان - -// xn--ygbi2ammx ("Falasteen", Arabic) : PS -// The Palestinian National Internet Naming Authority (PNINA) -// http://www.pnina.ps -فلسطين - -// xn--90a3ac ("srb", Cyrillic) : RS -// https://www.rnids.rs/en/domains/national-domains -срб -ак.срб -обр.срб -од.срб -орг.срб -пр.срб -упр.срб - -// xn--p1ai ("rf", Russian-Cyrillic) : RU -// https://cctld.ru/files/pdf/docs/en/rules_ru-rf.pdf -// Submitted by George Georgievsky -рф - -// xn--wgbl6a ("Qatar", Arabic) : QA -// http://www.ict.gov.qa/ -قطر - -// xn--mgberp4a5d4ar ("AlSaudiah", Arabic) : SA -// http://www.nic.net.sa/ -السعودية - -// xn--mgberp4a5d4a87g ("AlSaudiah", Arabic, variant): SA -السعودیة - -// xn--mgbqly7c0a67fbc ("AlSaudiah", Arabic, variant) : SA -السعودیۃ - -// xn--mgbqly7cvafr ("AlSaudiah", Arabic, variant) : SA -السعوديه - -// xn--mgbpl2fh ("sudan", Arabic) : SD -// Operated by .sd registry -سودان - -// xn--yfro4i67o Singapore ("Singapore", Chinese) : SG -新加坡 - -// xn--clchc0ea0b2g2a9gcd ("Singapore", Tamil) : SG -சிங்கப்பூர் - -// xn--ogbpf8fl ("Syria", Arabic) : SY -سورية - -// xn--mgbtf8fl ("Syria", Arabic, variant) : SY -سوريا - -// xn--o3cw4h ("Thai", Thai) : TH -// http://www.thnic.co.th -ไทย -ทหาร.ไทย -ธุรกิจ.ไทย -เน็ต.ไทย -รัฐบาล.ไทย -ศึกษา.ไทย -องค์กร.ไทย - -// xn--pgbs0dh ("Tunisia", Arabic) : TN -// http://nic.tn -تونس - -// xn--kpry57d ("Taiwan", Chinese, Traditional) : TW -// https://twnic.tw/dnservice_catag.php -台灣 - -// xn--kprw13d ("Taiwan", Chinese, Simplified) : TW -// http://www.twnic.net/english/dn/dn_07a.htm -台湾 - -// xn--nnx388a ("Taiwan", Chinese, variant) : TW -臺灣 - -// xn--j1amh ("ukr", Cyrillic) : UA -укр - -// xn--mgb2ddes ("AlYemen", Arabic) : YE -اليمن - -// xxx : http://icmregistry.com -xxx - -// ye : http://www.y.net.ye/services/domain_name.htm -ye -com.ye -edu.ye -gov.ye -mil.ye -net.ye -org.ye - -// za : https://www.iana.org/domains/root/db/za.html -ac.za -agric.za -alt.za -co.za -edu.za -gov.za -grondar.za -law.za -mil.za -net.za -ngo.za -nic.za -nis.za -nom.za -org.za -school.za -tm.za -web.za - -// zm : https://zicta.zm/ -// Submitted by registry -zm -ac.zm -biz.zm -co.zm -com.zm -edu.zm -gov.zm -info.zm -mil.zm -net.zm -org.zm -sch.zm - -// zw : https://www.potraz.gov.zw/ -// Confirmed by registry 2017-01-25 -zw -ac.zw -co.zw -gov.zw -mil.zw -org.zw - -// newGTLDs - -// List of new gTLDs imported from https://www.icann.org/resources/registries/gtlds/v2/gtlds.json on 2025-10-01T15:18:26Z -// This list is auto-generated, don't edit it manually. -// aaa : American Automobile Association, Inc. -// https://www.iana.org/domains/root/db/aaa.html -aaa - -// aarp : AARP -// https://www.iana.org/domains/root/db/aarp.html -aarp - -// abb : ABB Ltd -// https://www.iana.org/domains/root/db/abb.html -abb - -// abbott : Abbott Laboratories, Inc. -// https://www.iana.org/domains/root/db/abbott.html -abbott - -// abbvie : AbbVie Inc. -// https://www.iana.org/domains/root/db/abbvie.html -abbvie - -// abc : Disney Enterprises, Inc. -// https://www.iana.org/domains/root/db/abc.html -abc - -// able : Able Inc. -// https://www.iana.org/domains/root/db/able.html -able - -// abogado : Registry Services, LLC -// https://www.iana.org/domains/root/db/abogado.html -abogado - -// abudhabi : Abu Dhabi Systems and Information Centre -// https://www.iana.org/domains/root/db/abudhabi.html -abudhabi - -// academy : Binky Moon, LLC -// https://www.iana.org/domains/root/db/academy.html -academy - -// accenture : Accenture plc -// https://www.iana.org/domains/root/db/accenture.html -accenture - -// accountant : dot Accountant Limited -// https://www.iana.org/domains/root/db/accountant.html -accountant - -// accountants : Binky Moon, LLC -// https://www.iana.org/domains/root/db/accountants.html -accountants - -// aco : ACO Severin Ahlmann GmbH & Co. KG -// https://www.iana.org/domains/root/db/aco.html -aco - -// actor : Dog Beach, LLC -// https://www.iana.org/domains/root/db/actor.html -actor - -// ads : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/ads.html -ads - -// adult : ICM Registry AD LLC -// https://www.iana.org/domains/root/db/adult.html -adult - -// aeg : Aktiebolaget Electrolux -// https://www.iana.org/domains/root/db/aeg.html -aeg - -// aetna : Aetna Life Insurance Company -// https://www.iana.org/domains/root/db/aetna.html -aetna - -// afl : Australian Football League -// https://www.iana.org/domains/root/db/afl.html -afl - -// africa : ZA Central Registry NPC trading as Registry.Africa -// https://www.iana.org/domains/root/db/africa.html -africa - -// agakhan : Fondation Aga Khan (Aga Khan Foundation) -// https://www.iana.org/domains/root/db/agakhan.html -agakhan - -// agency : Binky Moon, LLC -// https://www.iana.org/domains/root/db/agency.html -agency - -// aig : American International Group, Inc. -// https://www.iana.org/domains/root/db/aig.html -aig - -// airbus : Airbus S.A.S. -// https://www.iana.org/domains/root/db/airbus.html -airbus - -// airforce : Dog Beach, LLC -// https://www.iana.org/domains/root/db/airforce.html -airforce - -// airtel : Bharti Airtel Limited -// https://www.iana.org/domains/root/db/airtel.html -airtel - -// akdn : Fondation Aga Khan (Aga Khan Foundation) -// https://www.iana.org/domains/root/db/akdn.html -akdn - -// alibaba : Alibaba Group Holding Limited -// https://www.iana.org/domains/root/db/alibaba.html -alibaba - -// alipay : Alibaba Group Holding Limited -// https://www.iana.org/domains/root/db/alipay.html -alipay - -// allfinanz : Allfinanz Deutsche Vermögensberatung Aktiengesellschaft -// https://www.iana.org/domains/root/db/allfinanz.html -allfinanz - -// allstate : Allstate Fire and Casualty Insurance Company -// https://www.iana.org/domains/root/db/allstate.html -allstate - -// ally : Ally Financial Inc. -// https://www.iana.org/domains/root/db/ally.html -ally - -// alsace : Region Grand Est -// https://www.iana.org/domains/root/db/alsace.html -alsace - -// alstom : ALSTOM -// https://www.iana.org/domains/root/db/alstom.html -alstom - -// amazon : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/amazon.html -amazon - -// americanexpress : American Express Travel Related Services Company, Inc. -// https://www.iana.org/domains/root/db/americanexpress.html -americanexpress - -// americanfamily : AmFam, Inc. -// https://www.iana.org/domains/root/db/americanfamily.html -americanfamily - -// amex : American Express Travel Related Services Company, Inc. -// https://www.iana.org/domains/root/db/amex.html -amex - -// amfam : AmFam, Inc. -// https://www.iana.org/domains/root/db/amfam.html -amfam - -// amica : Amica Mutual Insurance Company -// https://www.iana.org/domains/root/db/amica.html -amica - -// amsterdam : Gemeente Amsterdam -// https://www.iana.org/domains/root/db/amsterdam.html -amsterdam - -// analytics : Campus IP LLC -// https://www.iana.org/domains/root/db/analytics.html -analytics - -// android : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/android.html -android - -// anquan : Beijing Qihu Keji Co., Ltd. -// https://www.iana.org/domains/root/db/anquan.html -anquan - -// anz : Australia and New Zealand Banking Group Limited -// https://www.iana.org/domains/root/db/anz.html -anz - -// aol : Yahoo Inc. -// https://www.iana.org/domains/root/db/aol.html -aol - -// apartments : Binky Moon, LLC -// https://www.iana.org/domains/root/db/apartments.html -apartments - -// app : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/app.html -app - -// apple : Apple Inc. -// https://www.iana.org/domains/root/db/apple.html -apple - -// aquarelle : Aquarelle.com -// https://www.iana.org/domains/root/db/aquarelle.html -aquarelle - -// arab : League of Arab States -// https://www.iana.org/domains/root/db/arab.html -arab - -// aramco : Aramco Services Company -// https://www.iana.org/domains/root/db/aramco.html -aramco - -// archi : Identity Digital Domains Limited -// https://www.iana.org/domains/root/db/archi.html -archi - -// army : Dog Beach, LLC -// https://www.iana.org/domains/root/db/army.html -army - -// art : UK Creative Ideas Limited -// https://www.iana.org/domains/root/db/art.html -art - -// arte : Association Relative à la Télévision Européenne G.E.I.E. -// https://www.iana.org/domains/root/db/arte.html -arte - -// asda : Asda Stores Limited -// https://www.iana.org/domains/root/db/asda.html -asda - -// associates : Binky Moon, LLC -// https://www.iana.org/domains/root/db/associates.html -associates - -// athleta : The Gap, Inc. -// https://www.iana.org/domains/root/db/athleta.html -athleta - -// attorney : Dog Beach, LLC -// https://www.iana.org/domains/root/db/attorney.html -attorney - -// auction : Dog Beach, LLC -// https://www.iana.org/domains/root/db/auction.html -auction - -// audi : AUDI Aktiengesellschaft -// https://www.iana.org/domains/root/db/audi.html -audi - -// audible : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/audible.html -audible - -// audio : XYZ.COM LLC -// https://www.iana.org/domains/root/db/audio.html -audio - -// auspost : Australian Postal Corporation -// https://www.iana.org/domains/root/db/auspost.html -auspost - -// author : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/author.html -author - -// auto : XYZ.COM LLC -// https://www.iana.org/domains/root/db/auto.html -auto - -// autos : XYZ.COM LLC -// https://www.iana.org/domains/root/db/autos.html -autos - -// aws : AWS Registry LLC -// https://www.iana.org/domains/root/db/aws.html -aws - -// axa : AXA Group Operations SAS -// https://www.iana.org/domains/root/db/axa.html -axa - -// azure : Microsoft Corporation -// https://www.iana.org/domains/root/db/azure.html -azure - -// baby : XYZ.COM LLC -// https://www.iana.org/domains/root/db/baby.html -baby - -// baidu : Baidu, Inc. -// https://www.iana.org/domains/root/db/baidu.html -baidu - -// banamex : Citigroup Inc. -// https://www.iana.org/domains/root/db/banamex.html -banamex - -// band : Dog Beach, LLC -// https://www.iana.org/domains/root/db/band.html -band - -// bank : fTLD Registry Services LLC -// https://www.iana.org/domains/root/db/bank.html -bank - -// bar : Punto 2012 Sociedad Anonima Promotora de Inversion de Capital Variable -// https://www.iana.org/domains/root/db/bar.html -bar - -// barcelona : Municipi de Barcelona -// https://www.iana.org/domains/root/db/barcelona.html -barcelona - -// barclaycard : Barclays Bank PLC -// https://www.iana.org/domains/root/db/barclaycard.html -barclaycard - -// barclays : Barclays Bank PLC -// https://www.iana.org/domains/root/db/barclays.html -barclays - -// barefoot : Gallo Vineyards, Inc. -// https://www.iana.org/domains/root/db/barefoot.html -barefoot - -// bargains : Binky Moon, LLC -// https://www.iana.org/domains/root/db/bargains.html -bargains - -// baseball : MLB Advanced Media DH, LLC -// https://www.iana.org/domains/root/db/baseball.html -baseball - -// basketball : Fédération Internationale de Basketball (FIBA) -// https://www.iana.org/domains/root/db/basketball.html -basketball - -// bauhaus : Werkhaus GmbH -// https://www.iana.org/domains/root/db/bauhaus.html -bauhaus - -// bayern : Bayern Connect GmbH -// https://www.iana.org/domains/root/db/bayern.html -bayern - -// bbc : British Broadcasting Corporation -// https://www.iana.org/domains/root/db/bbc.html -bbc - -// bbt : BB&T Corporation -// https://www.iana.org/domains/root/db/bbt.html -bbt - -// bbva : BANCO BILBAO VIZCAYA ARGENTARIA, S.A. -// https://www.iana.org/domains/root/db/bbva.html -bbva - -// bcg : The Boston Consulting Group, Inc. -// https://www.iana.org/domains/root/db/bcg.html -bcg - -// bcn : Municipi de Barcelona -// https://www.iana.org/domains/root/db/bcn.html -bcn - -// beats : Beats Electronics, LLC -// https://www.iana.org/domains/root/db/beats.html -beats - -// beauty : XYZ.COM LLC -// https://www.iana.org/domains/root/db/beauty.html -beauty - -// beer : Registry Services, LLC -// https://www.iana.org/domains/root/db/beer.html -beer - -// berlin : dotBERLIN GmbH & Co. KG -// https://www.iana.org/domains/root/db/berlin.html -berlin - -// best : BestTLD Pty Ltd -// https://www.iana.org/domains/root/db/best.html -best - -// bestbuy : BBY Solutions, Inc. -// https://www.iana.org/domains/root/db/bestbuy.html -bestbuy - -// bet : Identity Digital Domains Limited -// https://www.iana.org/domains/root/db/bet.html -bet - -// bharti : Bharti Enterprises (Holding) Private Limited -// https://www.iana.org/domains/root/db/bharti.html -bharti - -// bible : American Bible Society -// https://www.iana.org/domains/root/db/bible.html -bible - -// bid : dot Bid Limited -// https://www.iana.org/domains/root/db/bid.html -bid - -// bike : Binky Moon, LLC -// https://www.iana.org/domains/root/db/bike.html -bike - -// bing : Microsoft Corporation -// https://www.iana.org/domains/root/db/bing.html -bing - -// bingo : Binky Moon, LLC -// https://www.iana.org/domains/root/db/bingo.html -bingo - -// bio : Identity Digital Domains Limited -// https://www.iana.org/domains/root/db/bio.html -bio - -// black : Identity Digital Domains Limited -// https://www.iana.org/domains/root/db/black.html -black - -// blackfriday : Registry Services, LLC -// https://www.iana.org/domains/root/db/blackfriday.html -blackfriday - -// blockbuster : Dish DBS Corporation -// https://www.iana.org/domains/root/db/blockbuster.html -blockbuster - -// blog : Knock Knock WHOIS There, LLC -// https://www.iana.org/domains/root/db/blog.html -blog - -// bloomberg : Bloomberg IP Holdings LLC -// https://www.iana.org/domains/root/db/bloomberg.html -bloomberg - -// blue : Identity Digital Domains Limited -// https://www.iana.org/domains/root/db/blue.html -blue - -// bms : Bristol-Myers Squibb Company -// https://www.iana.org/domains/root/db/bms.html -bms - -// bmw : Bayerische Motoren Werke Aktiengesellschaft -// https://www.iana.org/domains/root/db/bmw.html -bmw - -// bnpparibas : BNP Paribas -// https://www.iana.org/domains/root/db/bnpparibas.html -bnpparibas - -// boats : XYZ.COM LLC -// https://www.iana.org/domains/root/db/boats.html -boats - -// boehringer : Boehringer Ingelheim International GmbH -// https://www.iana.org/domains/root/db/boehringer.html -boehringer - -// bofa : Bank of America Corporation -// https://www.iana.org/domains/root/db/bofa.html -bofa - -// bom : Núcleo de Informação e Coordenação do Ponto BR - NIC.br -// https://www.iana.org/domains/root/db/bom.html -bom - -// bond : ShortDot SA -// https://www.iana.org/domains/root/db/bond.html -bond - -// boo : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/boo.html -boo - -// book : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/book.html -book - -// booking : Booking.com B.V. -// https://www.iana.org/domains/root/db/booking.html -booking - -// bosch : Robert Bosch GMBH -// https://www.iana.org/domains/root/db/bosch.html -bosch - -// bostik : Bostik SA -// https://www.iana.org/domains/root/db/bostik.html -bostik - -// boston : Registry Services, LLC -// https://www.iana.org/domains/root/db/boston.html -boston - -// bot : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/bot.html -bot - -// boutique : Binky Moon, LLC -// https://www.iana.org/domains/root/db/boutique.html -boutique - -// box : Intercap Registry Inc. -// https://www.iana.org/domains/root/db/box.html -box - -// bradesco : Banco Bradesco S.A. -// https://www.iana.org/domains/root/db/bradesco.html -bradesco - -// bridgestone : Bridgestone Corporation -// https://www.iana.org/domains/root/db/bridgestone.html -bridgestone - -// broadway : Celebrate Broadway, Inc. -// https://www.iana.org/domains/root/db/broadway.html -broadway - -// broker : Dog Beach, LLC -// https://www.iana.org/domains/root/db/broker.html -broker - -// brother : Brother Industries, Ltd. -// https://www.iana.org/domains/root/db/brother.html -brother - -// brussels : DNS.be vzw -// https://www.iana.org/domains/root/db/brussels.html -brussels - -// build : Plan Bee LLC -// https://www.iana.org/domains/root/db/build.html -build - -// builders : Binky Moon, LLC -// https://www.iana.org/domains/root/db/builders.html -builders - -// business : Binky Moon, LLC -// https://www.iana.org/domains/root/db/business.html -business - -// buy : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/buy.html -buy - -// buzz : DOTSTRATEGY CO. -// https://www.iana.org/domains/root/db/buzz.html -buzz - -// bzh : Association www.bzh -// https://www.iana.org/domains/root/db/bzh.html -bzh - -// cab : Binky Moon, LLC -// https://www.iana.org/domains/root/db/cab.html -cab - -// cafe : Binky Moon, LLC -// https://www.iana.org/domains/root/db/cafe.html -cafe - -// cal : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/cal.html -cal - -// call : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/call.html -call - -// calvinklein : PVH gTLD Holdings LLC -// https://www.iana.org/domains/root/db/calvinklein.html -calvinklein - -// cam : Cam Connecting SARL -// https://www.iana.org/domains/root/db/cam.html -cam - -// camera : Binky Moon, LLC -// https://www.iana.org/domains/root/db/camera.html -camera - -// camp : Binky Moon, LLC -// https://www.iana.org/domains/root/db/camp.html -camp - -// canon : Canon Inc. -// https://www.iana.org/domains/root/db/canon.html -canon - -// capetown : ZA Central Registry NPC trading as ZA Central Registry -// https://www.iana.org/domains/root/db/capetown.html -capetown - -// capital : Binky Moon, LLC -// https://www.iana.org/domains/root/db/capital.html -capital - -// capitalone : Capital One Financial Corporation -// https://www.iana.org/domains/root/db/capitalone.html -capitalone - -// car : XYZ.COM LLC -// https://www.iana.org/domains/root/db/car.html -car - -// caravan : Caravan International, Inc. -// https://www.iana.org/domains/root/db/caravan.html -caravan - -// cards : Binky Moon, LLC -// https://www.iana.org/domains/root/db/cards.html -cards - -// care : Binky Moon, LLC -// https://www.iana.org/domains/root/db/care.html -care - -// career : dotCareer LLC -// https://www.iana.org/domains/root/db/career.html -career - -// careers : Binky Moon, LLC -// https://www.iana.org/domains/root/db/careers.html -careers - -// cars : XYZ.COM LLC -// https://www.iana.org/domains/root/db/cars.html -cars - -// casa : Registry Services, LLC -// https://www.iana.org/domains/root/db/casa.html -casa - -// case : Digity, LLC -// https://www.iana.org/domains/root/db/case.html -case - -// cash : Binky Moon, LLC -// https://www.iana.org/domains/root/db/cash.html -cash - -// casino : Binky Moon, LLC -// https://www.iana.org/domains/root/db/casino.html -casino - -// catering : Binky Moon, LLC -// https://www.iana.org/domains/root/db/catering.html -catering - -// catholic : Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication) -// https://www.iana.org/domains/root/db/catholic.html -catholic - -// cba : COMMONWEALTH BANK OF AUSTRALIA -// https://www.iana.org/domains/root/db/cba.html -cba - -// cbn : The Christian Broadcasting Network, Inc. -// https://www.iana.org/domains/root/db/cbn.html -cbn - -// cbre : CBRE, Inc. -// https://www.iana.org/domains/root/db/cbre.html -cbre - -// center : Binky Moon, LLC -// https://www.iana.org/domains/root/db/center.html -center - -// ceo : XYZ.COM LLC -// https://www.iana.org/domains/root/db/ceo.html -ceo - -// cern : European Organization for Nuclear Research ("CERN") -// https://www.iana.org/domains/root/db/cern.html -cern - -// cfa : CFA Institute -// https://www.iana.org/domains/root/db/cfa.html -cfa - -// cfd : ShortDot SA -// https://www.iana.org/domains/root/db/cfd.html -cfd - -// chanel : Chanel International B.V. -// https://www.iana.org/domains/root/db/chanel.html -chanel - -// channel : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/channel.html -channel - -// charity : Public Interest Registry -// https://www.iana.org/domains/root/db/charity.html -charity - -// chase : JPMorgan Chase Bank, National Association -// https://www.iana.org/domains/root/db/chase.html -chase - -// chat : Binky Moon, LLC -// https://www.iana.org/domains/root/db/chat.html -chat - -// cheap : Binky Moon, LLC -// https://www.iana.org/domains/root/db/cheap.html -cheap - -// chintai : CHINTAI Corporation -// https://www.iana.org/domains/root/db/chintai.html -chintai - -// christmas : XYZ.COM LLC -// https://www.iana.org/domains/root/db/christmas.html -christmas - -// chrome : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/chrome.html -chrome - -// church : Binky Moon, LLC -// https://www.iana.org/domains/root/db/church.html -church - -// cipriani : Hotel Cipriani Srl -// https://www.iana.org/domains/root/db/cipriani.html -cipriani - -// circle : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/circle.html -circle - -// cisco : Cisco Technology, Inc. -// https://www.iana.org/domains/root/db/cisco.html -cisco - -// citadel : Citadel Domain LLC -// https://www.iana.org/domains/root/db/citadel.html -citadel - -// citi : Citigroup Inc. -// https://www.iana.org/domains/root/db/citi.html -citi - -// citic : CITIC Group Corporation -// https://www.iana.org/domains/root/db/citic.html -citic - -// city : Binky Moon, LLC -// https://www.iana.org/domains/root/db/city.html -city - -// claims : Binky Moon, LLC -// https://www.iana.org/domains/root/db/claims.html -claims - -// cleaning : Binky Moon, LLC -// https://www.iana.org/domains/root/db/cleaning.html -cleaning - -// click : Waterford Limited -// https://www.iana.org/domains/root/db/click.html -click - -// clinic : Binky Moon, LLC -// https://www.iana.org/domains/root/db/clinic.html -clinic - -// clinique : The Estée Lauder Companies Inc. -// https://www.iana.org/domains/root/db/clinique.html -clinique - -// clothing : Binky Moon, LLC -// https://www.iana.org/domains/root/db/clothing.html -clothing - -// cloud : Aruba PEC S.p.A. -// https://www.iana.org/domains/root/db/cloud.html -cloud - -// club : Registry Services, LLC -// https://www.iana.org/domains/root/db/club.html -club - -// clubmed : Club Méditerranée S.A. -// https://www.iana.org/domains/root/db/clubmed.html -clubmed - -// coach : Binky Moon, LLC -// https://www.iana.org/domains/root/db/coach.html -coach - -// codes : Binky Moon, LLC -// https://www.iana.org/domains/root/db/codes.html -codes - -// coffee : Binky Moon, LLC -// https://www.iana.org/domains/root/db/coffee.html -coffee - -// college : XYZ.COM LLC -// https://www.iana.org/domains/root/db/college.html -college - -// cologne : dotKoeln GmbH -// https://www.iana.org/domains/root/db/cologne.html -cologne - -// commbank : COMMONWEALTH BANK OF AUSTRALIA -// https://www.iana.org/domains/root/db/commbank.html -commbank - -// community : Binky Moon, LLC -// https://www.iana.org/domains/root/db/community.html -community - -// company : Binky Moon, LLC -// https://www.iana.org/domains/root/db/company.html -company - -// compare : Registry Services, LLC -// https://www.iana.org/domains/root/db/compare.html -compare - -// computer : Binky Moon, LLC -// https://www.iana.org/domains/root/db/computer.html -computer - -// comsec : VeriSign, Inc. -// https://www.iana.org/domains/root/db/comsec.html -comsec - -// condos : Binky Moon, LLC -// https://www.iana.org/domains/root/db/condos.html -condos - -// construction : Binky Moon, LLC -// https://www.iana.org/domains/root/db/construction.html -construction - -// consulting : Dog Beach, LLC -// https://www.iana.org/domains/root/db/consulting.html -consulting - -// contact : Dog Beach, LLC -// https://www.iana.org/domains/root/db/contact.html -contact - -// contractors : Binky Moon, LLC -// https://www.iana.org/domains/root/db/contractors.html -contractors - -// cooking : Registry Services, LLC -// https://www.iana.org/domains/root/db/cooking.html -cooking - -// cool : Binky Moon, LLC -// https://www.iana.org/domains/root/db/cool.html -cool - -// corsica : Collectivité de Corse -// https://www.iana.org/domains/root/db/corsica.html -corsica - -// country : Internet Naming Company LLC -// https://www.iana.org/domains/root/db/country.html -country - -// coupon : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/coupon.html -coupon - -// coupons : Binky Moon, LLC -// https://www.iana.org/domains/root/db/coupons.html -coupons - -// courses : Registry Services, LLC -// https://www.iana.org/domains/root/db/courses.html -courses - -// cpa : American Institute of Certified Public Accountants -// https://www.iana.org/domains/root/db/cpa.html -cpa - -// credit : Binky Moon, LLC -// https://www.iana.org/domains/root/db/credit.html -credit - -// creditcard : Binky Moon, LLC -// https://www.iana.org/domains/root/db/creditcard.html -creditcard - -// creditunion : DotCooperation LLC -// https://www.iana.org/domains/root/db/creditunion.html -creditunion - -// cricket : dot Cricket Limited -// https://www.iana.org/domains/root/db/cricket.html -cricket - -// crown : Crown Equipment Corporation -// https://www.iana.org/domains/root/db/crown.html -crown - -// crs : Federated Co-operatives Limited -// https://www.iana.org/domains/root/db/crs.html -crs - -// cruise : Viking River Cruises (Bermuda) Ltd. -// https://www.iana.org/domains/root/db/cruise.html -cruise - -// cruises : Binky Moon, LLC -// https://www.iana.org/domains/root/db/cruises.html -cruises - -// cuisinella : SCHMIDT GROUPE S.A.S. -// https://www.iana.org/domains/root/db/cuisinella.html -cuisinella - -// cymru : Nominet UK -// https://www.iana.org/domains/root/db/cymru.html -cymru - -// cyou : ShortDot SA -// https://www.iana.org/domains/root/db/cyou.html -cyou - -// dad : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/dad.html -dad - -// dance : Dog Beach, LLC -// https://www.iana.org/domains/root/db/dance.html -dance - -// data : Dish DBS Corporation -// https://www.iana.org/domains/root/db/data.html -data - -// date : dot Date Limited -// https://www.iana.org/domains/root/db/date.html -date - -// dating : Binky Moon, LLC -// https://www.iana.org/domains/root/db/dating.html -dating - -// datsun : NISSAN MOTOR CO., LTD. -// https://www.iana.org/domains/root/db/datsun.html -datsun - -// day : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/day.html -day - -// dclk : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/dclk.html -dclk - -// dds : Registry Services, LLC -// https://www.iana.org/domains/root/db/dds.html -dds - -// deal : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/deal.html -deal - -// dealer : Intercap Registry Inc. -// https://www.iana.org/domains/root/db/dealer.html -dealer - -// deals : Binky Moon, LLC -// https://www.iana.org/domains/root/db/deals.html -deals - -// degree : Dog Beach, LLC -// https://www.iana.org/domains/root/db/degree.html -degree - -// delivery : Binky Moon, LLC -// https://www.iana.org/domains/root/db/delivery.html -delivery - -// dell : Dell Inc. -// https://www.iana.org/domains/root/db/dell.html -dell - -// deloitte : Deloitte Touche Tohmatsu -// https://www.iana.org/domains/root/db/deloitte.html -deloitte - -// delta : Delta Air Lines, Inc. -// https://www.iana.org/domains/root/db/delta.html -delta - -// democrat : Dog Beach, LLC -// https://www.iana.org/domains/root/db/democrat.html -democrat - -// dental : Binky Moon, LLC -// https://www.iana.org/domains/root/db/dental.html -dental - -// dentist : Dog Beach, LLC -// https://www.iana.org/domains/root/db/dentist.html -dentist - -// desi -// https://www.iana.org/domains/root/db/desi.html -desi - -// design : Registry Services, LLC -// https://www.iana.org/domains/root/db/design.html -design - -// dev : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/dev.html -dev - -// dhl : Deutsche Post AG -// https://www.iana.org/domains/root/db/dhl.html -dhl - -// diamonds : Binky Moon, LLC -// https://www.iana.org/domains/root/db/diamonds.html -diamonds - -// diet : XYZ.COM LLC -// https://www.iana.org/domains/root/db/diet.html -diet - -// digital : Binky Moon, LLC -// https://www.iana.org/domains/root/db/digital.html -digital - -// direct : Binky Moon, LLC -// https://www.iana.org/domains/root/db/direct.html -direct - -// directory : Binky Moon, LLC -// https://www.iana.org/domains/root/db/directory.html -directory - -// discount : Binky Moon, LLC -// https://www.iana.org/domains/root/db/discount.html -discount - -// discover : Discover Financial Services -// https://www.iana.org/domains/root/db/discover.html -discover - -// dish : Dish DBS Corporation -// https://www.iana.org/domains/root/db/dish.html -dish - -// diy : Internet Naming Company LLC -// https://www.iana.org/domains/root/db/diy.html -diy - -// dnp : Dai Nippon Printing Co., Ltd. -// https://www.iana.org/domains/root/db/dnp.html -dnp - -// docs : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/docs.html -docs - -// doctor : Binky Moon, LLC -// https://www.iana.org/domains/root/db/doctor.html -doctor - -// dog : Binky Moon, LLC -// https://www.iana.org/domains/root/db/dog.html -dog - -// domains : Binky Moon, LLC -// https://www.iana.org/domains/root/db/domains.html -domains - -// dot : Dish DBS Corporation -// https://www.iana.org/domains/root/db/dot.html -dot - -// download : dot Support Limited -// https://www.iana.org/domains/root/db/download.html -download - -// drive : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/drive.html -drive - -// dtv : Dish DBS Corporation -// https://www.iana.org/domains/root/db/dtv.html -dtv - -// dubai : Dubai Smart Government Department -// https://www.iana.org/domains/root/db/dubai.html -dubai - -// dunlop : The Goodyear Tire & Rubber Company -// https://www.iana.org/domains/root/db/dunlop.html -dunlop - -// dupont : DuPont Specialty Products USA, LLC -// https://www.iana.org/domains/root/db/dupont.html -dupont - -// durban : ZA Central Registry NPC trading as ZA Central Registry -// https://www.iana.org/domains/root/db/durban.html -durban - -// dvag : Deutsche Vermögensberatung Aktiengesellschaft DVAG -// https://www.iana.org/domains/root/db/dvag.html -dvag - -// dvr : DISH Technologies L.L.C. -// https://www.iana.org/domains/root/db/dvr.html -dvr - -// earth : Interlink Systems Innovation Institute K.K. -// https://www.iana.org/domains/root/db/earth.html -earth - -// eat : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/eat.html -eat - -// eco : Big Room Inc. -// https://www.iana.org/domains/root/db/eco.html -eco - -// edeka : EDEKA Verband kaufmännischer Genossenschaften e.V. -// https://www.iana.org/domains/root/db/edeka.html -edeka - -// education : Binky Moon, LLC -// https://www.iana.org/domains/root/db/education.html -education - -// email : Binky Moon, LLC -// https://www.iana.org/domains/root/db/email.html -email - -// emerck : Merck KGaA -// https://www.iana.org/domains/root/db/emerck.html -emerck - -// energy : Binky Moon, LLC -// https://www.iana.org/domains/root/db/energy.html -energy - -// engineer : Dog Beach, LLC -// https://www.iana.org/domains/root/db/engineer.html -engineer - -// engineering : Binky Moon, LLC -// https://www.iana.org/domains/root/db/engineering.html -engineering - -// enterprises : Binky Moon, LLC -// https://www.iana.org/domains/root/db/enterprises.html -enterprises - -// epson : Seiko Epson Corporation -// https://www.iana.org/domains/root/db/epson.html -epson - -// equipment : Binky Moon, LLC -// https://www.iana.org/domains/root/db/equipment.html -equipment - -// ericsson : Telefonaktiebolaget L M Ericsson -// https://www.iana.org/domains/root/db/ericsson.html -ericsson - -// erni : ERNI Group Holding AG -// https://www.iana.org/domains/root/db/erni.html -erni - -// esq : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/esq.html -esq - -// estate : Binky Moon, LLC -// https://www.iana.org/domains/root/db/estate.html -estate - -// eurovision : European Broadcasting Union (EBU) -// https://www.iana.org/domains/root/db/eurovision.html -eurovision - -// eus : Puntueus Fundazioa -// https://www.iana.org/domains/root/db/eus.html -eus - -// events : Binky Moon, LLC -// https://www.iana.org/domains/root/db/events.html -events - -// exchange : Binky Moon, LLC -// https://www.iana.org/domains/root/db/exchange.html -exchange - -// expert : Binky Moon, LLC -// https://www.iana.org/domains/root/db/expert.html -expert - -// exposed : Binky Moon, LLC -// https://www.iana.org/domains/root/db/exposed.html -exposed - -// express : Binky Moon, LLC -// https://www.iana.org/domains/root/db/express.html -express - -// extraspace : Extra Space Storage LLC -// https://www.iana.org/domains/root/db/extraspace.html -extraspace - -// fage : Fage International S.A. -// https://www.iana.org/domains/root/db/fage.html -fage - -// fail : Binky Moon, LLC -// https://www.iana.org/domains/root/db/fail.html -fail - -// fairwinds : FairWinds Partners, LLC -// https://www.iana.org/domains/root/db/fairwinds.html -fairwinds - -// faith : dot Faith Limited -// https://www.iana.org/domains/root/db/faith.html -faith - -// family : Dog Beach, LLC -// https://www.iana.org/domains/root/db/family.html -family - -// fan : Dog Beach, LLC -// https://www.iana.org/domains/root/db/fan.html -fan - -// fans : ZDNS International Limited -// https://www.iana.org/domains/root/db/fans.html -fans - -// farm : Binky Moon, LLC -// https://www.iana.org/domains/root/db/farm.html -farm - -// farmers : Farmers Insurance Exchange -// https://www.iana.org/domains/root/db/farmers.html -farmers - -// fashion : Registry Services, LLC -// https://www.iana.org/domains/root/db/fashion.html -fashion - -// fast : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/fast.html -fast - -// fedex : Federal Express Corporation -// https://www.iana.org/domains/root/db/fedex.html -fedex - -// feedback : Top Level Spectrum, Inc. -// https://www.iana.org/domains/root/db/feedback.html -feedback - -// ferrari : Fiat Chrysler Automobiles N.V. -// https://www.iana.org/domains/root/db/ferrari.html -ferrari - -// ferrero : Ferrero Trading Lux S.A. -// https://www.iana.org/domains/root/db/ferrero.html -ferrero - -// fidelity : Fidelity Brokerage Services LLC -// https://www.iana.org/domains/root/db/fidelity.html -fidelity - -// fido : Rogers Communications Canada Inc. -// https://www.iana.org/domains/root/db/fido.html -fido - -// film : Motion Picture Domain Registry Pty Ltd -// https://www.iana.org/domains/root/db/film.html -film - -// final : Núcleo de Informação e Coordenação do Ponto BR - NIC.br -// https://www.iana.org/domains/root/db/final.html -final - -// finance : Binky Moon, LLC -// https://www.iana.org/domains/root/db/finance.html -finance - -// financial : Binky Moon, LLC -// https://www.iana.org/domains/root/db/financial.html -financial - -// fire : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/fire.html -fire - -// firestone : Bridgestone Licensing Services, Inc -// https://www.iana.org/domains/root/db/firestone.html -firestone - -// firmdale : Firmdale Holdings Limited -// https://www.iana.org/domains/root/db/firmdale.html -firmdale - -// fish : Binky Moon, LLC -// https://www.iana.org/domains/root/db/fish.html -fish - -// fishing : Registry Services, LLC -// https://www.iana.org/domains/root/db/fishing.html -fishing - -// fit : Registry Services, LLC -// https://www.iana.org/domains/root/db/fit.html -fit - -// fitness : Binky Moon, LLC -// https://www.iana.org/domains/root/db/fitness.html -fitness - -// flickr : Flickr, Inc. -// https://www.iana.org/domains/root/db/flickr.html -flickr - -// flights : Binky Moon, LLC -// https://www.iana.org/domains/root/db/flights.html -flights - -// flir : FLIR Systems, Inc. -// https://www.iana.org/domains/root/db/flir.html -flir - -// florist : Binky Moon, LLC -// https://www.iana.org/domains/root/db/florist.html -florist - -// flowers : XYZ.COM LLC -// https://www.iana.org/domains/root/db/flowers.html -flowers - -// fly : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/fly.html -fly - -// foo : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/foo.html -foo - -// food : Internet Naming Company LLC -// https://www.iana.org/domains/root/db/food.html -food - -// football : Binky Moon, LLC -// https://www.iana.org/domains/root/db/football.html -football - -// ford : Ford Motor Company -// https://www.iana.org/domains/root/db/ford.html -ford - -// forex : Dog Beach, LLC -// https://www.iana.org/domains/root/db/forex.html -forex - -// forsale : Dog Beach, LLC -// https://www.iana.org/domains/root/db/forsale.html -forsale - -// forum : Waterford Limited -// https://www.iana.org/domains/root/db/forum.html -forum - -// foundation : Public Interest Registry -// https://www.iana.org/domains/root/db/foundation.html -foundation - -// fox : FOX Registry, LLC -// https://www.iana.org/domains/root/db/fox.html -fox - -// free : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/free.html -free - -// fresenius : Fresenius Immobilien-Verwaltungs-GmbH -// https://www.iana.org/domains/root/db/fresenius.html -fresenius - -// frl : FRLregistry B.V. -// https://www.iana.org/domains/root/db/frl.html -frl - -// frogans : OP3FT -// https://www.iana.org/domains/root/db/frogans.html -frogans - -// frontier : Frontier Communications Corporation -// https://www.iana.org/domains/root/db/frontier.html -frontier - -// ftr : Frontier Communications Corporation -// https://www.iana.org/domains/root/db/ftr.html -ftr - -// fujitsu : Fujitsu Limited -// https://www.iana.org/domains/root/db/fujitsu.html -fujitsu - -// fun : Radix Technologies Inc SEZC -// https://www.iana.org/domains/root/db/fun.html -fun - -// fund : Binky Moon, LLC -// https://www.iana.org/domains/root/db/fund.html -fund - -// furniture : Binky Moon, LLC -// https://www.iana.org/domains/root/db/furniture.html -furniture - -// futbol : Dog Beach, LLC -// https://www.iana.org/domains/root/db/futbol.html -futbol - -// fyi : Binky Moon, LLC -// https://www.iana.org/domains/root/db/fyi.html -fyi - -// gal : Asociación puntoGAL -// https://www.iana.org/domains/root/db/gal.html -gal - -// gallery : Binky Moon, LLC -// https://www.iana.org/domains/root/db/gallery.html -gallery - -// gallo : Gallo Vineyards, Inc. -// https://www.iana.org/domains/root/db/gallo.html -gallo - -// gallup : Gallup, Inc. -// https://www.iana.org/domains/root/db/gallup.html -gallup - -// game : XYZ.COM LLC -// https://www.iana.org/domains/root/db/game.html -game - -// games : Dog Beach, LLC -// https://www.iana.org/domains/root/db/games.html -games - -// gap : The Gap, Inc. -// https://www.iana.org/domains/root/db/gap.html -gap - -// garden : Registry Services, LLC -// https://www.iana.org/domains/root/db/garden.html -garden - -// gay : Registry Services, LLC -// https://www.iana.org/domains/root/db/gay.html -gay - -// gbiz : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/gbiz.html -gbiz - -// gdn : Joint Stock Company "Navigation-information systems" -// https://www.iana.org/domains/root/db/gdn.html -gdn - -// gea : GEA Group Aktiengesellschaft -// https://www.iana.org/domains/root/db/gea.html -gea - -// gent : Easyhost BV -// https://www.iana.org/domains/root/db/gent.html -gent - -// genting : Resorts World Inc Pte. Ltd. -// https://www.iana.org/domains/root/db/genting.html -genting - -// george : Wal-Mart Stores, Inc. -// https://www.iana.org/domains/root/db/george.html -george - -// ggee : GMO Internet, Inc. -// https://www.iana.org/domains/root/db/ggee.html -ggee - -// gift : DotGift, LLC -// https://www.iana.org/domains/root/db/gift.html -gift - -// gifts : Binky Moon, LLC -// https://www.iana.org/domains/root/db/gifts.html -gifts - -// gives : Public Interest Registry -// https://www.iana.org/domains/root/db/gives.html -gives - -// giving : Public Interest Registry -// https://www.iana.org/domains/root/db/giving.html -giving - -// glass : Binky Moon, LLC -// https://www.iana.org/domains/root/db/glass.html -glass - -// gle : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/gle.html -gle - -// global : Identity Digital Domains Limited -// https://www.iana.org/domains/root/db/global.html -global - -// globo : Globo Comunicação e Participações S.A -// https://www.iana.org/domains/root/db/globo.html -globo - -// gmail : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/gmail.html -gmail - -// gmbh : Binky Moon, LLC -// https://www.iana.org/domains/root/db/gmbh.html -gmbh - -// gmo : GMO Internet, Inc. -// https://www.iana.org/domains/root/db/gmo.html -gmo - -// gmx : 1&1 Mail & Media GmbH -// https://www.iana.org/domains/root/db/gmx.html -gmx - -// godaddy : Go Daddy East, LLC -// https://www.iana.org/domains/root/db/godaddy.html -godaddy - -// gold : Binky Moon, LLC -// https://www.iana.org/domains/root/db/gold.html -gold - -// goldpoint : YODOBASHI CAMERA CO.,LTD. -// https://www.iana.org/domains/root/db/goldpoint.html -goldpoint - -// golf : Binky Moon, LLC -// https://www.iana.org/domains/root/db/golf.html -golf - -// goo : NTT DOCOMO, INC. -// https://www.iana.org/domains/root/db/goo.html -goo - -// goodyear : The Goodyear Tire & Rubber Company -// https://www.iana.org/domains/root/db/goodyear.html -goodyear - -// goog : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/goog.html -goog - -// google : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/google.html -google - -// gop : Republican State Leadership Committee, Inc. -// https://www.iana.org/domains/root/db/gop.html -gop - -// got : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/got.html -got - -// grainger : Grainger Registry Services, LLC -// https://www.iana.org/domains/root/db/grainger.html -grainger - -// graphics : Binky Moon, LLC -// https://www.iana.org/domains/root/db/graphics.html -graphics - -// gratis : Binky Moon, LLC -// https://www.iana.org/domains/root/db/gratis.html -gratis - -// green : Identity Digital Domains Limited -// https://www.iana.org/domains/root/db/green.html -green - -// gripe : Binky Moon, LLC -// https://www.iana.org/domains/root/db/gripe.html -gripe - -// grocery : Wal-Mart Stores, Inc. -// https://www.iana.org/domains/root/db/grocery.html -grocery - -// group : Binky Moon, LLC -// https://www.iana.org/domains/root/db/group.html -group - -// gucci : Guccio Gucci S.p.a. -// https://www.iana.org/domains/root/db/gucci.html -gucci - -// guge : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/guge.html -guge - -// guide : Binky Moon, LLC -// https://www.iana.org/domains/root/db/guide.html -guide - -// guitars : XYZ.COM LLC -// https://www.iana.org/domains/root/db/guitars.html -guitars - -// guru : Binky Moon, LLC -// https://www.iana.org/domains/root/db/guru.html -guru - -// hair : XYZ.COM LLC -// https://www.iana.org/domains/root/db/hair.html -hair - -// hamburg : Hamburg Top-Level-Domain GmbH -// https://www.iana.org/domains/root/db/hamburg.html -hamburg - -// hangout : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/hangout.html -hangout - -// haus : Dog Beach, LLC -// https://www.iana.org/domains/root/db/haus.html -haus - -// hbo : HBO Registry Services, Inc. -// https://www.iana.org/domains/root/db/hbo.html -hbo - -// hdfc : HDFC BANK LIMITED -// https://www.iana.org/domains/root/db/hdfc.html -hdfc - -// hdfcbank : HDFC BANK LIMITED -// https://www.iana.org/domains/root/db/hdfcbank.html -hdfcbank - -// health : Registry Services, LLC -// https://www.iana.org/domains/root/db/health.html -health - -// healthcare : Binky Moon, LLC -// https://www.iana.org/domains/root/db/healthcare.html -healthcare - -// help : Innovation service Limited -// https://www.iana.org/domains/root/db/help.html -help - -// helsinki : City of Helsinki -// https://www.iana.org/domains/root/db/helsinki.html -helsinki - -// here : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/here.html -here - -// hermes : HERMES INTERNATIONAL -// https://www.iana.org/domains/root/db/hermes.html -hermes - -// hiphop : Dot Hip Hop, LLC -// https://www.iana.org/domains/root/db/hiphop.html -hiphop - -// hisamitsu : Hisamitsu Pharmaceutical Co.,Inc. -// https://www.iana.org/domains/root/db/hisamitsu.html -hisamitsu - -// hitachi : Hitachi, Ltd. -// https://www.iana.org/domains/root/db/hitachi.html -hitachi - -// hiv : Internet Naming Company LLC -// https://www.iana.org/domains/root/db/hiv.html -hiv - -// hkt : PCCW-HKT DataCom Services Limited -// https://www.iana.org/domains/root/db/hkt.html -hkt - -// hockey : Binky Moon, LLC -// https://www.iana.org/domains/root/db/hockey.html -hockey - -// holdings : Binky Moon, LLC -// https://www.iana.org/domains/root/db/holdings.html -holdings - -// holiday : Binky Moon, LLC -// https://www.iana.org/domains/root/db/holiday.html -holiday - -// homedepot : Home Depot Product Authority, LLC -// https://www.iana.org/domains/root/db/homedepot.html -homedepot - -// homegoods : The TJX Companies, Inc. -// https://www.iana.org/domains/root/db/homegoods.html -homegoods - -// homes : XYZ.COM LLC -// https://www.iana.org/domains/root/db/homes.html -homes - -// homesense : The TJX Companies, Inc. -// https://www.iana.org/domains/root/db/homesense.html -homesense - -// honda : Honda Motor Co., Ltd. -// https://www.iana.org/domains/root/db/honda.html -honda - -// horse : Registry Services, LLC -// https://www.iana.org/domains/root/db/horse.html -horse - -// hospital : Binky Moon, LLC -// https://www.iana.org/domains/root/db/hospital.html -hospital - -// host : Radix Technologies Inc SEZC -// https://www.iana.org/domains/root/db/host.html -host - -// hosting : XYZ.COM LLC -// https://www.iana.org/domains/root/db/hosting.html -hosting - -// hot : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/hot.html -hot - -// hotel : HOTEL Top-Level-Domain S.a.r.l -// https://www.iana.org/domains/root/db/hotel.html -hotel - -// hotels : Booking.com B.V. -// https://www.iana.org/domains/root/db/hotels.html -hotels - -// hotmail : Microsoft Corporation -// https://www.iana.org/domains/root/db/hotmail.html -hotmail - -// house : Binky Moon, LLC -// https://www.iana.org/domains/root/db/house.html -house - -// how : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/how.html -how - -// hsbc : HSBC Global Services (UK) Limited -// https://www.iana.org/domains/root/db/hsbc.html -hsbc - -// hughes : Hughes Satellite Systems Corporation -// https://www.iana.org/domains/root/db/hughes.html -hughes - -// hyatt : Hyatt GTLD, L.L.C. -// https://www.iana.org/domains/root/db/hyatt.html -hyatt - -// hyundai : Hyundai Motor Company -// https://www.iana.org/domains/root/db/hyundai.html -hyundai - -// ibm : International Business Machines Corporation -// https://www.iana.org/domains/root/db/ibm.html -ibm - -// icbc : Industrial and Commercial Bank of China Limited -// https://www.iana.org/domains/root/db/icbc.html -icbc - -// ice : IntercontinentalExchange, Inc. -// https://www.iana.org/domains/root/db/ice.html -ice - -// icu : ShortDot SA -// https://www.iana.org/domains/root/db/icu.html -icu - -// ieee : IEEE Global LLC -// https://www.iana.org/domains/root/db/ieee.html -ieee - -// ifm : ifm electronic gmbh -// https://www.iana.org/domains/root/db/ifm.html -ifm - -// ikano : Ikano S.A. -// https://www.iana.org/domains/root/db/ikano.html -ikano - -// imamat : Fondation Aga Khan (Aga Khan Foundation) -// https://www.iana.org/domains/root/db/imamat.html -imamat - -// imdb : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/imdb.html -imdb - -// immo : Binky Moon, LLC -// https://www.iana.org/domains/root/db/immo.html -immo - -// immobilien : Dog Beach, LLC -// https://www.iana.org/domains/root/db/immobilien.html -immobilien - -// inc : Intercap Registry Inc. -// https://www.iana.org/domains/root/db/inc.html -inc - -// industries : Binky Moon, LLC -// https://www.iana.org/domains/root/db/industries.html -industries - -// infiniti : NISSAN MOTOR CO., LTD. -// https://www.iana.org/domains/root/db/infiniti.html -infiniti - -// ing : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/ing.html -ing - -// ink : Registry Services, LLC -// https://www.iana.org/domains/root/db/ink.html -ink - -// institute : Binky Moon, LLC -// https://www.iana.org/domains/root/db/institute.html -institute - -// insurance : fTLD Registry Services LLC -// https://www.iana.org/domains/root/db/insurance.html -insurance - -// insure : Binky Moon, LLC -// https://www.iana.org/domains/root/db/insure.html -insure - -// international : Binky Moon, LLC -// https://www.iana.org/domains/root/db/international.html -international - -// intuit : Intuit Administrative Services, Inc. -// https://www.iana.org/domains/root/db/intuit.html -intuit - -// investments : Binky Moon, LLC -// https://www.iana.org/domains/root/db/investments.html -investments - -// ipiranga : Ipiranga Produtos de Petroleo S.A. -// https://www.iana.org/domains/root/db/ipiranga.html -ipiranga - -// irish : Binky Moon, LLC -// https://www.iana.org/domains/root/db/irish.html -irish - -// ismaili : Fondation Aga Khan (Aga Khan Foundation) -// https://www.iana.org/domains/root/db/ismaili.html -ismaili - -// ist : Istanbul Metropolitan Municipality -// https://www.iana.org/domains/root/db/ist.html -ist - -// istanbul : Istanbul Metropolitan Municipality -// https://www.iana.org/domains/root/db/istanbul.html -istanbul - -// itau : Itau Unibanco Holding S.A. -// https://www.iana.org/domains/root/db/itau.html -itau - -// itv : ITV Services Limited -// https://www.iana.org/domains/root/db/itv.html -itv - -// jaguar : Jaguar Land Rover Ltd -// https://www.iana.org/domains/root/db/jaguar.html -jaguar - -// java : Oracle Corporation -// https://www.iana.org/domains/root/db/java.html -java - -// jcb : JCB Co., Ltd. -// https://www.iana.org/domains/root/db/jcb.html -jcb - -// jeep : FCA US LLC. -// https://www.iana.org/domains/root/db/jeep.html -jeep - -// jetzt : Binky Moon, LLC -// https://www.iana.org/domains/root/db/jetzt.html -jetzt - -// jewelry : Binky Moon, LLC -// https://www.iana.org/domains/root/db/jewelry.html -jewelry - -// jio : Reliance Industries Limited -// https://www.iana.org/domains/root/db/jio.html -jio - -// jll : Jones Lang LaSalle Incorporated -// https://www.iana.org/domains/root/db/jll.html -jll - -// jmp : Matrix IP LLC -// https://www.iana.org/domains/root/db/jmp.html -jmp - -// jnj : Johnson & Johnson Services, Inc. -// https://www.iana.org/domains/root/db/jnj.html -jnj - -// joburg : ZA Central Registry NPC trading as ZA Central Registry -// https://www.iana.org/domains/root/db/joburg.html -joburg - -// jot : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/jot.html -jot - -// joy : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/joy.html -joy - -// jpmorgan : JPMorgan Chase Bank, National Association -// https://www.iana.org/domains/root/db/jpmorgan.html -jpmorgan - -// jprs : Japan Registry Services Co., Ltd. -// https://www.iana.org/domains/root/db/jprs.html -jprs - -// juegos : Dog Beach, LLC -// https://www.iana.org/domains/root/db/juegos.html -juegos - -// juniper : JUNIPER NETWORKS, INC. -// https://www.iana.org/domains/root/db/juniper.html -juniper - -// kaufen : Dog Beach, LLC -// https://www.iana.org/domains/root/db/kaufen.html -kaufen - -// kddi : KDDI CORPORATION -// https://www.iana.org/domains/root/db/kddi.html -kddi - -// kerryhotels : Kerry Trading Co. Limited -// https://www.iana.org/domains/root/db/kerryhotels.html -kerryhotels - -// kerryproperties : Kerry Trading Co. Limited -// https://www.iana.org/domains/root/db/kerryproperties.html -kerryproperties - -// kfh : Kuwait Finance House -// https://www.iana.org/domains/root/db/kfh.html -kfh - -// kia : KIA MOTORS CORPORATION -// https://www.iana.org/domains/root/db/kia.html -kia - -// kids : DotKids Foundation Limited -// https://www.iana.org/domains/root/db/kids.html -kids - -// kim : Identity Digital Domains Limited -// https://www.iana.org/domains/root/db/kim.html -kim - -// kindle : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/kindle.html -kindle - -// kitchen : Binky Moon, LLC -// https://www.iana.org/domains/root/db/kitchen.html -kitchen - -// kiwi : DOT KIWI LIMITED -// https://www.iana.org/domains/root/db/kiwi.html -kiwi - -// koeln : dotKoeln GmbH -// https://www.iana.org/domains/root/db/koeln.html -koeln - -// komatsu : Komatsu Ltd. -// https://www.iana.org/domains/root/db/komatsu.html -komatsu - -// kosher : Kosher Marketing Assets LLC -// https://www.iana.org/domains/root/db/kosher.html -kosher - -// kpmg : KPMG International Cooperative (KPMG International Genossenschaft) -// https://www.iana.org/domains/root/db/kpmg.html -kpmg - -// kpn : Koninklijke KPN N.V. -// https://www.iana.org/domains/root/db/kpn.html -kpn - -// krd : KRG Department of Information Technology -// https://www.iana.org/domains/root/db/krd.html -krd - -// kred : KredTLD Pty Ltd -// https://www.iana.org/domains/root/db/kred.html -kred - -// kuokgroup : Kerry Trading Co. Limited -// https://www.iana.org/domains/root/db/kuokgroup.html -kuokgroup - -// kyoto : Academic Institution: Kyoto Jyoho Gakuen -// https://www.iana.org/domains/root/db/kyoto.html -kyoto - -// lacaixa : Fundación Bancaria Caixa d’Estalvis i Pensions de Barcelona, “la Caixa” -// https://www.iana.org/domains/root/db/lacaixa.html -lacaixa - -// lamborghini : Automobili Lamborghini S.p.A. -// https://www.iana.org/domains/root/db/lamborghini.html -lamborghini - -// lamer : The Estée Lauder Companies Inc. -// https://www.iana.org/domains/root/db/lamer.html -lamer - -// land : Binky Moon, LLC -// https://www.iana.org/domains/root/db/land.html -land - -// landrover : Jaguar Land Rover Ltd -// https://www.iana.org/domains/root/db/landrover.html -landrover - -// lanxess : LANXESS Corporation -// https://www.iana.org/domains/root/db/lanxess.html -lanxess - -// lasalle : Jones Lang LaSalle Incorporated -// https://www.iana.org/domains/root/db/lasalle.html -lasalle - -// lat : XYZ.COM LLC -// https://www.iana.org/domains/root/db/lat.html -lat - -// latino : Dish DBS Corporation -// https://www.iana.org/domains/root/db/latino.html -latino - -// latrobe : La Trobe University -// https://www.iana.org/domains/root/db/latrobe.html -latrobe - -// law : Registry Services, LLC -// https://www.iana.org/domains/root/db/law.html -law - -// lawyer : Dog Beach, LLC -// https://www.iana.org/domains/root/db/lawyer.html -lawyer - -// lds : IRI Domain Management, LLC -// https://www.iana.org/domains/root/db/lds.html -lds - -// lease : Binky Moon, LLC -// https://www.iana.org/domains/root/db/lease.html -lease - -// leclerc : A.C.D. LEC Association des Centres Distributeurs Edouard Leclerc -// https://www.iana.org/domains/root/db/leclerc.html -leclerc - -// lefrak : LeFrak Organization, Inc. -// https://www.iana.org/domains/root/db/lefrak.html -lefrak - -// legal : Binky Moon, LLC -// https://www.iana.org/domains/root/db/legal.html -legal - -// lego : LEGO Juris A/S -// https://www.iana.org/domains/root/db/lego.html -lego - -// lexus : TOYOTA MOTOR CORPORATION -// https://www.iana.org/domains/root/db/lexus.html -lexus - -// lgbt : Identity Digital Domains Limited -// https://www.iana.org/domains/root/db/lgbt.html -lgbt - -// lidl : Schwarz Domains und Services GmbH & Co. KG -// https://www.iana.org/domains/root/db/lidl.html -lidl - -// life : Binky Moon, LLC -// https://www.iana.org/domains/root/db/life.html -life - -// lifeinsurance : American Council of Life Insurers -// https://www.iana.org/domains/root/db/lifeinsurance.html -lifeinsurance - -// lifestyle : Internet Naming Company LLC -// https://www.iana.org/domains/root/db/lifestyle.html -lifestyle - -// lighting : Binky Moon, LLC -// https://www.iana.org/domains/root/db/lighting.html -lighting - -// like : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/like.html -like - -// lilly : Eli Lilly and Company -// https://www.iana.org/domains/root/db/lilly.html -lilly - -// limited : Binky Moon, LLC -// https://www.iana.org/domains/root/db/limited.html -limited - -// limo : Binky Moon, LLC -// https://www.iana.org/domains/root/db/limo.html -limo - -// lincoln : Ford Motor Company -// https://www.iana.org/domains/root/db/lincoln.html -lincoln - -// link : Nova Registry Ltd -// https://www.iana.org/domains/root/db/link.html -link - -// live : Dog Beach, LLC -// https://www.iana.org/domains/root/db/live.html -live - -// living : Internet Naming Company LLC -// https://www.iana.org/domains/root/db/living.html -living - -// llc : Identity Digital Domains Limited -// https://www.iana.org/domains/root/db/llc.html -llc - -// llp : Intercap Registry Inc. -// https://www.iana.org/domains/root/db/llp.html -llp - -// loan : dot Loan Limited -// https://www.iana.org/domains/root/db/loan.html -loan - -// loans : Binky Moon, LLC -// https://www.iana.org/domains/root/db/loans.html -loans - -// locker : Orange Domains LLC -// https://www.iana.org/domains/root/db/locker.html -locker - -// locus : Locus Analytics LLC -// https://www.iana.org/domains/root/db/locus.html -locus - -// lol : XYZ.COM LLC -// https://www.iana.org/domains/root/db/lol.html -lol - -// london : Dot London Domains Limited -// https://www.iana.org/domains/root/db/london.html -london - -// lotte : Lotte Holdings Co., Ltd. -// https://www.iana.org/domains/root/db/lotte.html -lotte - -// lotto : Identity Digital Domains Limited -// https://www.iana.org/domains/root/db/lotto.html -lotto - -// love : Waterford Limited -// https://www.iana.org/domains/root/db/love.html -love - -// lpl : LPL Holdings, Inc. -// https://www.iana.org/domains/root/db/lpl.html -lpl - -// lplfinancial : LPL Holdings, Inc. -// https://www.iana.org/domains/root/db/lplfinancial.html -lplfinancial - -// ltd : Binky Moon, LLC -// https://www.iana.org/domains/root/db/ltd.html -ltd - -// ltda : InterNetX, Corp -// https://www.iana.org/domains/root/db/ltda.html -ltda - -// lundbeck : H. Lundbeck A/S -// https://www.iana.org/domains/root/db/lundbeck.html -lundbeck - -// luxe : Registry Services, LLC -// https://www.iana.org/domains/root/db/luxe.html -luxe - -// luxury : Luxury Partners, LLC -// https://www.iana.org/domains/root/db/luxury.html -luxury - -// madrid : Comunidad de Madrid -// https://www.iana.org/domains/root/db/madrid.html -madrid - -// maif : Mutuelle Assurance Instituteur France (MAIF) -// https://www.iana.org/domains/root/db/maif.html -maif - -// maison : Binky Moon, LLC -// https://www.iana.org/domains/root/db/maison.html -maison - -// makeup : XYZ.COM LLC -// https://www.iana.org/domains/root/db/makeup.html -makeup - -// man : MAN Truck & Bus SE -// https://www.iana.org/domains/root/db/man.html -man - -// management : Binky Moon, LLC -// https://www.iana.org/domains/root/db/management.html -management - -// mango : PUNTO FA S.L. -// https://www.iana.org/domains/root/db/mango.html -mango - -// map : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/map.html -map - -// market : Dog Beach, LLC -// https://www.iana.org/domains/root/db/market.html -market - -// marketing : Binky Moon, LLC -// https://www.iana.org/domains/root/db/marketing.html -marketing - -// markets : Dog Beach, LLC -// https://www.iana.org/domains/root/db/markets.html -markets - -// marriott : Marriott Worldwide Corporation -// https://www.iana.org/domains/root/db/marriott.html -marriott - -// marshalls : The TJX Companies, Inc. -// https://www.iana.org/domains/root/db/marshalls.html -marshalls - -// mattel : Mattel IT Services, Inc. -// https://www.iana.org/domains/root/db/mattel.html -mattel - -// mba : Binky Moon, LLC -// https://www.iana.org/domains/root/db/mba.html -mba - -// mckinsey : McKinsey Holdings, Inc. -// https://www.iana.org/domains/root/db/mckinsey.html -mckinsey - -// med : Medistry LLC -// https://www.iana.org/domains/root/db/med.html -med - -// media : Binky Moon, LLC -// https://www.iana.org/domains/root/db/media.html -media - -// meet : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/meet.html -meet - -// melbourne : The Crown in right of the State of Victoria, represented by its Department of State Development, Business and Innovation -// https://www.iana.org/domains/root/db/melbourne.html -melbourne - -// meme : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/meme.html -meme - -// memorial : Dog Beach, LLC -// https://www.iana.org/domains/root/db/memorial.html -memorial - -// men : Exclusive Registry Limited -// https://www.iana.org/domains/root/db/men.html -men - -// menu : Dot Menu Registry, LLC -// https://www.iana.org/domains/root/db/menu.html -menu - -// merck : Merck Registry Holdings, Inc. -// https://www.iana.org/domains/root/db/merck.html -merck - -// merckmsd : MSD Registry Holdings, Inc. -// https://www.iana.org/domains/root/db/merckmsd.html -merckmsd - -// miami : Registry Services, LLC -// https://www.iana.org/domains/root/db/miami.html -miami - -// microsoft : Microsoft Corporation -// https://www.iana.org/domains/root/db/microsoft.html -microsoft - -// mini : Bayerische Motoren Werke Aktiengesellschaft -// https://www.iana.org/domains/root/db/mini.html -mini - -// mint : Intuit Administrative Services, Inc. -// https://www.iana.org/domains/root/db/mint.html -mint - -// mit : Massachusetts Institute of Technology -// https://www.iana.org/domains/root/db/mit.html -mit - -// mitsubishi : Mitsubishi Corporation -// https://www.iana.org/domains/root/db/mitsubishi.html -mitsubishi - -// mlb : MLB Advanced Media DH, LLC -// https://www.iana.org/domains/root/db/mlb.html -mlb - -// mls : The Canadian Real Estate Association -// https://www.iana.org/domains/root/db/mls.html -mls - -// mma : MMA IARD -// https://www.iana.org/domains/root/db/mma.html -mma - -// mobile : Dish DBS Corporation -// https://www.iana.org/domains/root/db/mobile.html -mobile - -// moda : Dog Beach, LLC -// https://www.iana.org/domains/root/db/moda.html -moda - -// moe : Interlink Systems Innovation Institute K.K. -// https://www.iana.org/domains/root/db/moe.html -moe - -// moi : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/moi.html -moi - -// mom : XYZ.COM LLC -// https://www.iana.org/domains/root/db/mom.html -mom - -// monash : Monash University -// https://www.iana.org/domains/root/db/monash.html -monash - -// money : Binky Moon, LLC -// https://www.iana.org/domains/root/db/money.html -money - -// monster : XYZ.COM LLC -// https://www.iana.org/domains/root/db/monster.html -monster - -// mormon : IRI Domain Management, LLC -// https://www.iana.org/domains/root/db/mormon.html -mormon - -// mortgage : Dog Beach, LLC -// https://www.iana.org/domains/root/db/mortgage.html -mortgage - -// moscow : Foundation for Assistance for Internet Technologies and Infrastructure Development (FAITID) -// https://www.iana.org/domains/root/db/moscow.html -moscow - -// moto : Motorola Trademark Holdings, LLC -// https://www.iana.org/domains/root/db/moto.html -moto - -// motorcycles : XYZ.COM LLC -// https://www.iana.org/domains/root/db/motorcycles.html -motorcycles - -// mov : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/mov.html -mov - -// movie : Binky Moon, LLC -// https://www.iana.org/domains/root/db/movie.html -movie - -// msd : MSD Registry Holdings, Inc. -// https://www.iana.org/domains/root/db/msd.html -msd - -// mtn : MTN Dubai Limited -// https://www.iana.org/domains/root/db/mtn.html -mtn - -// mtr : MTR Corporation Limited -// https://www.iana.org/domains/root/db/mtr.html -mtr - -// music : DotMusic Limited -// https://www.iana.org/domains/root/db/music.html -music - -// nab : National Australia Bank Limited -// https://www.iana.org/domains/root/db/nab.html -nab - -// nagoya : GMO Registry, Inc. -// https://www.iana.org/domains/root/db/nagoya.html -nagoya - -// navy : Dog Beach, LLC -// https://www.iana.org/domains/root/db/navy.html -navy - -// nba : NBA REGISTRY, LLC -// https://www.iana.org/domains/root/db/nba.html -nba - -// nec : NEC Corporation -// https://www.iana.org/domains/root/db/nec.html -nec - -// netbank : COMMONWEALTH BANK OF AUSTRALIA -// https://www.iana.org/domains/root/db/netbank.html -netbank - -// netflix : Netflix, Inc. -// https://www.iana.org/domains/root/db/netflix.html -netflix - -// network : Binky Moon, LLC -// https://www.iana.org/domains/root/db/network.html -network - -// neustar : NeuStar, Inc. -// https://www.iana.org/domains/root/db/neustar.html -neustar - -// new : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/new.html -new - -// news : Dog Beach, LLC -// https://www.iana.org/domains/root/db/news.html -news - -// next : Next plc -// https://www.iana.org/domains/root/db/next.html -next - -// nextdirect : Next plc -// https://www.iana.org/domains/root/db/nextdirect.html -nextdirect - -// nexus : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/nexus.html -nexus - -// nfl : NFL Reg Ops LLC -// https://www.iana.org/domains/root/db/nfl.html -nfl - -// ngo : Public Interest Registry -// https://www.iana.org/domains/root/db/ngo.html -ngo - -// nhk : Japan Broadcasting Corporation (NHK) -// https://www.iana.org/domains/root/db/nhk.html -nhk - -// nico : DWANGO Co., Ltd. -// https://www.iana.org/domains/root/db/nico.html -nico - -// nike : NIKE, Inc. -// https://www.iana.org/domains/root/db/nike.html -nike - -// nikon : NIKON CORPORATION -// https://www.iana.org/domains/root/db/nikon.html -nikon - -// ninja : Dog Beach, LLC -// https://www.iana.org/domains/root/db/ninja.html -ninja - -// nissan : NISSAN MOTOR CO., LTD. -// https://www.iana.org/domains/root/db/nissan.html -nissan - -// nissay : Nippon Life Insurance Company -// https://www.iana.org/domains/root/db/nissay.html -nissay - -// nokia : Nokia Corporation -// https://www.iana.org/domains/root/db/nokia.html -nokia - -// norton : Gen Digital Inc. -// https://www.iana.org/domains/root/db/norton.html -norton - -// now : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/now.html -now - -// nowruz -// https://www.iana.org/domains/root/db/nowruz.html -nowruz - -// nowtv : Starbucks (HK) Limited -// https://www.iana.org/domains/root/db/nowtv.html -nowtv - -// nra : National Rifle Association of America -// https://www.iana.org/domains/root/db/nra.html -nra - -// nrw : Minds + Machines GmbH -// https://www.iana.org/domains/root/db/nrw.html -nrw - -// ntt : NIPPON TELEGRAPH AND TELEPHONE CORPORATION -// https://www.iana.org/domains/root/db/ntt.html -ntt - -// nyc : The City of New York by and through the New York City Department of Information Technology & Telecommunications -// https://www.iana.org/domains/root/db/nyc.html -nyc - -// obi : OBI Group Holding SE & Co. KGaA -// https://www.iana.org/domains/root/db/obi.html -obi - -// observer : Fegistry, LLC -// https://www.iana.org/domains/root/db/observer.html -observer - -// office : Microsoft Corporation -// https://www.iana.org/domains/root/db/office.html -office - -// okinawa : BRregistry, Inc. -// https://www.iana.org/domains/root/db/okinawa.html -okinawa - -// olayan : Competrol (Luxembourg) Sarl -// https://www.iana.org/domains/root/db/olayan.html -olayan - -// olayangroup : Competrol (Luxembourg) Sarl -// https://www.iana.org/domains/root/db/olayangroup.html -olayangroup - -// ollo : Dish DBS Corporation -// https://www.iana.org/domains/root/db/ollo.html -ollo - -// omega : The Swatch Group Ltd -// https://www.iana.org/domains/root/db/omega.html -omega - -// one : One.com A/S -// https://www.iana.org/domains/root/db/one.html -one - -// ong : Public Interest Registry -// https://www.iana.org/domains/root/db/ong.html -ong - -// onl : iRegistry GmbH -// https://www.iana.org/domains/root/db/onl.html -onl - -// online : Radix Technologies Inc SEZC -// https://www.iana.org/domains/root/db/online.html -online - -// ooo : INFIBEAM AVENUES LIMITED -// https://www.iana.org/domains/root/db/ooo.html -ooo - -// open : American Express Travel Related Services Company, Inc. -// https://www.iana.org/domains/root/db/open.html -open - -// oracle : Oracle Corporation -// https://www.iana.org/domains/root/db/oracle.html -oracle - -// orange : Orange Brand Services Limited -// https://www.iana.org/domains/root/db/orange.html -orange - -// organic : Identity Digital Domains Limited -// https://www.iana.org/domains/root/db/organic.html -organic - -// origins : The Estée Lauder Companies Inc. -// https://www.iana.org/domains/root/db/origins.html -origins - -// osaka : Osaka Registry Co., Ltd. -// https://www.iana.org/domains/root/db/osaka.html -osaka - -// otsuka : Otsuka Holdings Co., Ltd. -// https://www.iana.org/domains/root/db/otsuka.html -otsuka - -// ott : Dish DBS Corporation -// https://www.iana.org/domains/root/db/ott.html -ott - -// ovh : MédiaBC -// https://www.iana.org/domains/root/db/ovh.html -ovh - -// page : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/page.html -page - -// panasonic : Panasonic Holdings Corporation -// https://www.iana.org/domains/root/db/panasonic.html -panasonic - -// paris : City of Paris -// https://www.iana.org/domains/root/db/paris.html -paris - -// pars -// https://www.iana.org/domains/root/db/pars.html -pars - -// partners : Binky Moon, LLC -// https://www.iana.org/domains/root/db/partners.html -partners - -// parts : Binky Moon, LLC -// https://www.iana.org/domains/root/db/parts.html -parts - -// party : Blue Sky Registry Limited -// https://www.iana.org/domains/root/db/party.html -party - -// pay : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/pay.html -pay - -// pccw : PCCW Enterprises Limited -// https://www.iana.org/domains/root/db/pccw.html -pccw - -// pet : Identity Digital Domains Limited -// https://www.iana.org/domains/root/db/pet.html -pet - -// pfizer : Pfizer Inc. -// https://www.iana.org/domains/root/db/pfizer.html -pfizer - -// pharmacy : National Association of Boards of Pharmacy -// https://www.iana.org/domains/root/db/pharmacy.html -pharmacy - -// phd : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/phd.html -phd - -// philips : Koninklijke Philips N.V. -// https://www.iana.org/domains/root/db/philips.html -philips - -// phone : Dish DBS Corporation -// https://www.iana.org/domains/root/db/phone.html -phone - -// photo : Registry Services, LLC -// https://www.iana.org/domains/root/db/photo.html -photo - -// photography : Binky Moon, LLC -// https://www.iana.org/domains/root/db/photography.html -photography - -// photos : Binky Moon, LLC -// https://www.iana.org/domains/root/db/photos.html -photos - -// physio : PhysBiz Pty Ltd -// https://www.iana.org/domains/root/db/physio.html -physio - -// pics : XYZ.COM LLC -// https://www.iana.org/domains/root/db/pics.html -pics - -// pictet : Banque Pictet & Cie SA -// https://www.iana.org/domains/root/db/pictet.html -pictet - -// pictures : Binky Moon, LLC -// https://www.iana.org/domains/root/db/pictures.html -pictures - -// pid : Top Level Spectrum, Inc. -// https://www.iana.org/domains/root/db/pid.html -pid - -// pin : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/pin.html -pin - -// ping : Ping Registry Provider, Inc. -// https://www.iana.org/domains/root/db/ping.html -ping - -// pink : Identity Digital Domains Limited -// https://www.iana.org/domains/root/db/pink.html -pink - -// pioneer : Pioneer Corporation -// https://www.iana.org/domains/root/db/pioneer.html -pioneer - -// pizza : Binky Moon, LLC -// https://www.iana.org/domains/root/db/pizza.html -pizza - -// place : Binky Moon, LLC -// https://www.iana.org/domains/root/db/place.html -place - -// play : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/play.html -play - -// playstation : Sony Interactive Entertainment Inc. -// https://www.iana.org/domains/root/db/playstation.html -playstation - -// plumbing : Binky Moon, LLC -// https://www.iana.org/domains/root/db/plumbing.html -plumbing - -// plus : Binky Moon, LLC -// https://www.iana.org/domains/root/db/plus.html -plus - -// pnc : PNC Domain Co., LLC -// https://www.iana.org/domains/root/db/pnc.html -pnc - -// pohl : Deutsche Vermögensberatung Aktiengesellschaft DVAG -// https://www.iana.org/domains/root/db/pohl.html -pohl - -// poker : Identity Digital Domains Limited -// https://www.iana.org/domains/root/db/poker.html -poker - -// politie : Politie Nederland -// https://www.iana.org/domains/root/db/politie.html -politie - -// porn : ICM Registry PN LLC -// https://www.iana.org/domains/root/db/porn.html -porn - -// praxi : Praxi S.p.A. -// https://www.iana.org/domains/root/db/praxi.html -praxi - -// press : Radix Technologies Inc SEZC -// https://www.iana.org/domains/root/db/press.html -press - -// prime : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/prime.html -prime - -// prod : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/prod.html -prod - -// productions : Binky Moon, LLC -// https://www.iana.org/domains/root/db/productions.html -productions - -// prof : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/prof.html -prof - -// progressive : Progressive Casualty Insurance Company -// https://www.iana.org/domains/root/db/progressive.html -progressive - -// promo : Identity Digital Domains Limited -// https://www.iana.org/domains/root/db/promo.html -promo - -// properties : Binky Moon, LLC -// https://www.iana.org/domains/root/db/properties.html -properties - -// property : Digital Property Infrastructure Limited -// https://www.iana.org/domains/root/db/property.html -property - -// protection : XYZ.COM LLC -// https://www.iana.org/domains/root/db/protection.html -protection - -// pru : Prudential Financial, Inc. -// https://www.iana.org/domains/root/db/pru.html -pru - -// prudential : Prudential Financial, Inc. -// https://www.iana.org/domains/root/db/prudential.html -prudential - -// pub : Dog Beach, LLC -// https://www.iana.org/domains/root/db/pub.html -pub - -// pwc : PricewaterhouseCoopers LLP -// https://www.iana.org/domains/root/db/pwc.html -pwc - -// qpon : dotQPON LLC -// https://www.iana.org/domains/root/db/qpon.html -qpon - -// quebec : PointQuébec Inc -// https://www.iana.org/domains/root/db/quebec.html -quebec - -// quest : XYZ.COM LLC -// https://www.iana.org/domains/root/db/quest.html -quest - -// racing : Premier Registry Limited -// https://www.iana.org/domains/root/db/racing.html -racing - -// radio : European Broadcasting Union (EBU) -// https://www.iana.org/domains/root/db/radio.html -radio - -// read : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/read.html -read - -// realestate : dotRealEstate LLC -// https://www.iana.org/domains/root/db/realestate.html -realestate - -// realtor : Real Estate Domains LLC -// https://www.iana.org/domains/root/db/realtor.html -realtor - -// realty : Waterford Limited -// https://www.iana.org/domains/root/db/realty.html -realty - -// recipes : Binky Moon, LLC -// https://www.iana.org/domains/root/db/recipes.html -recipes - -// red : Identity Digital Domains Limited -// https://www.iana.org/domains/root/db/red.html -red - -// redumbrella : Travelers TLD, LLC -// https://www.iana.org/domains/root/db/redumbrella.html -redumbrella - -// rehab : Dog Beach, LLC -// https://www.iana.org/domains/root/db/rehab.html -rehab - -// reise : Binky Moon, LLC -// https://www.iana.org/domains/root/db/reise.html -reise - -// reisen : Binky Moon, LLC -// https://www.iana.org/domains/root/db/reisen.html -reisen - -// reit : National Association of Real Estate Investment Trusts, Inc. -// https://www.iana.org/domains/root/db/reit.html -reit - -// reliance : Reliance Industries Limited -// https://www.iana.org/domains/root/db/reliance.html -reliance - -// ren : ZDNS International Limited -// https://www.iana.org/domains/root/db/ren.html -ren - -// rent : XYZ.COM LLC -// https://www.iana.org/domains/root/db/rent.html -rent - -// rentals : Binky Moon, LLC -// https://www.iana.org/domains/root/db/rentals.html -rentals - -// repair : Binky Moon, LLC -// https://www.iana.org/domains/root/db/repair.html -repair - -// report : Binky Moon, LLC -// https://www.iana.org/domains/root/db/report.html -report - -// republican : Dog Beach, LLC -// https://www.iana.org/domains/root/db/republican.html -republican - -// rest : Punto 2012 Sociedad Anonima Promotora de Inversion de Capital Variable -// https://www.iana.org/domains/root/db/rest.html -rest - -// restaurant : Binky Moon, LLC -// https://www.iana.org/domains/root/db/restaurant.html -restaurant - -// review : dot Review Limited -// https://www.iana.org/domains/root/db/review.html -review - -// reviews : Dog Beach, LLC -// https://www.iana.org/domains/root/db/reviews.html -reviews - -// rexroth : Robert Bosch GMBH -// https://www.iana.org/domains/root/db/rexroth.html -rexroth - -// rich : iRegistry GmbH -// https://www.iana.org/domains/root/db/rich.html -rich - -// richardli : Pacific Century Asset Management (HK) Limited -// https://www.iana.org/domains/root/db/richardli.html -richardli - -// ricoh : Ricoh Company, Ltd. -// https://www.iana.org/domains/root/db/ricoh.html -ricoh - -// ril : Reliance Industries Limited -// https://www.iana.org/domains/root/db/ril.html -ril - -// rio : Empresa Municipal de Informática SA - IPLANRIO -// https://www.iana.org/domains/root/db/rio.html -rio - -// rip : Dog Beach, LLC -// https://www.iana.org/domains/root/db/rip.html -rip - -// rocks : Dog Beach, LLC -// https://www.iana.org/domains/root/db/rocks.html -rocks - -// rodeo : Registry Services, LLC -// https://www.iana.org/domains/root/db/rodeo.html -rodeo - -// rogers : Rogers Communications Canada Inc. -// https://www.iana.org/domains/root/db/rogers.html -rogers - -// room : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/room.html -room - -// rsvp : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/rsvp.html -rsvp - -// rugby : World Rugby Strategic Developments Limited -// https://www.iana.org/domains/root/db/rugby.html -rugby - -// ruhr : dotSaarland GmbH -// https://www.iana.org/domains/root/db/ruhr.html -ruhr - -// run : Binky Moon, LLC -// https://www.iana.org/domains/root/db/run.html -run - -// rwe : RWE AG -// https://www.iana.org/domains/root/db/rwe.html -rwe - -// ryukyu : BRregistry, Inc. -// https://www.iana.org/domains/root/db/ryukyu.html -ryukyu - -// saarland : dotSaarland GmbH -// https://www.iana.org/domains/root/db/saarland.html -saarland - -// safe : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/safe.html -safe - -// safety : Safety Registry Services, LLC. -// https://www.iana.org/domains/root/db/safety.html -safety - -// sakura : SAKURA Internet Inc. -// https://www.iana.org/domains/root/db/sakura.html -sakura - -// sale : Dog Beach, LLC -// https://www.iana.org/domains/root/db/sale.html -sale - -// salon : Binky Moon, LLC -// https://www.iana.org/domains/root/db/salon.html -salon - -// samsclub : Wal-Mart Stores, Inc. -// https://www.iana.org/domains/root/db/samsclub.html -samsclub - -// samsung : SAMSUNG SDS CO., LTD -// https://www.iana.org/domains/root/db/samsung.html -samsung - -// sandvik : Sandvik AB -// https://www.iana.org/domains/root/db/sandvik.html -sandvik - -// sandvikcoromant : Sandvik AB -// https://www.iana.org/domains/root/db/sandvikcoromant.html -sandvikcoromant - -// sanofi : Sanofi -// https://www.iana.org/domains/root/db/sanofi.html -sanofi - -// sap : SAP AG -// https://www.iana.org/domains/root/db/sap.html -sap - -// sarl : Binky Moon, LLC -// https://www.iana.org/domains/root/db/sarl.html -sarl - -// sas : Research IP LLC -// https://www.iana.org/domains/root/db/sas.html -sas - -// save : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/save.html -save - -// saxo : Saxo Bank A/S -// https://www.iana.org/domains/root/db/saxo.html -saxo - -// sbi : STATE BANK OF INDIA -// https://www.iana.org/domains/root/db/sbi.html -sbi - -// sbs : ShortDot SA -// https://www.iana.org/domains/root/db/sbs.html -sbs - -// scb : The Siam Commercial Bank Public Company Limited ("SCB") -// https://www.iana.org/domains/root/db/scb.html -scb - -// schaeffler : Schaeffler Technologies AG & Co. KG -// https://www.iana.org/domains/root/db/schaeffler.html -schaeffler - -// schmidt : SCHMIDT GROUPE S.A.S. -// https://www.iana.org/domains/root/db/schmidt.html -schmidt - -// scholarships : Scholarships.com, LLC -// https://www.iana.org/domains/root/db/scholarships.html -scholarships - -// school : Binky Moon, LLC -// https://www.iana.org/domains/root/db/school.html -school - -// schule : Binky Moon, LLC -// https://www.iana.org/domains/root/db/schule.html -schule - -// schwarz : Schwarz Domains und Services GmbH & Co. KG -// https://www.iana.org/domains/root/db/schwarz.html -schwarz - -// science : dot Science Limited -// https://www.iana.org/domains/root/db/science.html -science - -// scot : Dot Scot Registry Limited -// https://www.iana.org/domains/root/db/scot.html -scot - -// search : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/search.html -search - -// seat : SEAT, S.A. (Sociedad Unipersonal) -// https://www.iana.org/domains/root/db/seat.html -seat - -// secure : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/secure.html -secure - -// security : XYZ.COM LLC -// https://www.iana.org/domains/root/db/security.html -security - -// seek : Seek Limited -// https://www.iana.org/domains/root/db/seek.html -seek - -// select : Registry Services, LLC -// https://www.iana.org/domains/root/db/select.html -select - -// sener : Sener Ingeniería y Sistemas, S.A. -// https://www.iana.org/domains/root/db/sener.html -sener - -// services : Binky Moon, LLC -// https://www.iana.org/domains/root/db/services.html -services - -// seven : Seven West Media Ltd -// https://www.iana.org/domains/root/db/seven.html -seven - -// sew : SEW-EURODRIVE GmbH & Co KG -// https://www.iana.org/domains/root/db/sew.html -sew - -// sex : ICM Registry SX LLC -// https://www.iana.org/domains/root/db/sex.html -sex - -// sexy : Internet Naming Company LLC -// https://www.iana.org/domains/root/db/sexy.html -sexy - -// sfr : Societe Francaise du Radiotelephone - SFR -// https://www.iana.org/domains/root/db/sfr.html -sfr - -// shangrila : Shangri‐La International Hotel Management Limited -// https://www.iana.org/domains/root/db/shangrila.html -shangrila - -// sharp : Sharp Corporation -// https://www.iana.org/domains/root/db/sharp.html -sharp - -// shell : Shell Information Technology International Inc -// https://www.iana.org/domains/root/db/shell.html -shell - -// shia -// https://www.iana.org/domains/root/db/shia.html -shia - -// shiksha : Identity Digital Domains Limited -// https://www.iana.org/domains/root/db/shiksha.html -shiksha - -// shoes : Binky Moon, LLC -// https://www.iana.org/domains/root/db/shoes.html -shoes - -// shop : GMO Registry, Inc. -// https://www.iana.org/domains/root/db/shop.html -shop - -// shopping : Binky Moon, LLC -// https://www.iana.org/domains/root/db/shopping.html -shopping - -// shouji : Beijing Qihu Keji Co., Ltd. -// https://www.iana.org/domains/root/db/shouji.html -shouji - -// show : Binky Moon, LLC -// https://www.iana.org/domains/root/db/show.html -show - -// silk : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/silk.html -silk - -// sina : Sina Corporation -// https://www.iana.org/domains/root/db/sina.html -sina - -// singles : Binky Moon, LLC -// https://www.iana.org/domains/root/db/singles.html -singles - -// site : Radix Technologies Inc SEZC -// https://www.iana.org/domains/root/db/site.html -site - -// ski : Identity Digital Domains Limited -// https://www.iana.org/domains/root/db/ski.html -ski - -// skin : XYZ.COM LLC -// https://www.iana.org/domains/root/db/skin.html -skin - -// sky : Sky UK Limited -// https://www.iana.org/domains/root/db/sky.html -sky - -// skype : Microsoft Corporation -// https://www.iana.org/domains/root/db/skype.html -skype - -// sling : DISH Technologies L.L.C. -// https://www.iana.org/domains/root/db/sling.html -sling - -// smart : Smart Communications, Inc. (SMART) -// https://www.iana.org/domains/root/db/smart.html -smart - -// smile : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/smile.html -smile - -// sncf : Société Nationale SNCF -// https://www.iana.org/domains/root/db/sncf.html -sncf - -// soccer : Binky Moon, LLC -// https://www.iana.org/domains/root/db/soccer.html -soccer - -// social : Dog Beach, LLC -// https://www.iana.org/domains/root/db/social.html -social - -// softbank : SoftBank Group Corp. -// https://www.iana.org/domains/root/db/softbank.html -softbank - -// software : Dog Beach, LLC -// https://www.iana.org/domains/root/db/software.html -software - -// sohu : Sohu.com Limited -// https://www.iana.org/domains/root/db/sohu.html -sohu - -// solar : Binky Moon, LLC -// https://www.iana.org/domains/root/db/solar.html -solar - -// solutions : Binky Moon, LLC -// https://www.iana.org/domains/root/db/solutions.html -solutions - -// song : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/song.html -song - -// sony : Sony Corporation -// https://www.iana.org/domains/root/db/sony.html -sony - -// soy : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/soy.html -soy - -// spa : Asia Spa and Wellness Promotion Council Limited -// https://www.iana.org/domains/root/db/spa.html -spa - -// space : Radix Technologies Inc SEZC -// https://www.iana.org/domains/root/db/space.html -space - -// sport : SportAccord -// https://www.iana.org/domains/root/db/sport.html -sport - -// spot : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/spot.html -spot - -// srl : InterNetX, Corp -// https://www.iana.org/domains/root/db/srl.html -srl - -// stada : STADA Arzneimittel AG -// https://www.iana.org/domains/root/db/stada.html -stada - -// staples : Staples, Inc. -// https://www.iana.org/domains/root/db/staples.html -staples - -// star : Star India Private Limited -// https://www.iana.org/domains/root/db/star.html -star - -// statebank : STATE BANK OF INDIA -// https://www.iana.org/domains/root/db/statebank.html -statebank - -// statefarm : State Farm Mutual Automobile Insurance Company -// https://www.iana.org/domains/root/db/statefarm.html -statefarm - -// stc : Saudi Telecom Company -// https://www.iana.org/domains/root/db/stc.html -stc - -// stcgroup : Saudi Telecom Company -// https://www.iana.org/domains/root/db/stcgroup.html -stcgroup - -// stockholm : Stockholms kommun -// https://www.iana.org/domains/root/db/stockholm.html -stockholm - -// storage : XYZ.COM LLC -// https://www.iana.org/domains/root/db/storage.html -storage - -// store : Radix Technologies Inc SEZC -// https://www.iana.org/domains/root/db/store.html -store - -// stream : dot Stream Limited -// https://www.iana.org/domains/root/db/stream.html -stream - -// studio : Dog Beach, LLC -// https://www.iana.org/domains/root/db/studio.html -studio - -// study : Registry Services, LLC -// https://www.iana.org/domains/root/db/study.html -study - -// style : Binky Moon, LLC -// https://www.iana.org/domains/root/db/style.html -style - -// sucks : Vox Populi Registry Ltd. -// https://www.iana.org/domains/root/db/sucks.html -sucks - -// supplies : Binky Moon, LLC -// https://www.iana.org/domains/root/db/supplies.html -supplies - -// supply : Binky Moon, LLC -// https://www.iana.org/domains/root/db/supply.html -supply - -// support : Binky Moon, LLC -// https://www.iana.org/domains/root/db/support.html -support - -// surf : Registry Services, LLC -// https://www.iana.org/domains/root/db/surf.html -surf - -// surgery : Binky Moon, LLC -// https://www.iana.org/domains/root/db/surgery.html -surgery - -// suzuki : SUZUKI MOTOR CORPORATION -// https://www.iana.org/domains/root/db/suzuki.html -suzuki - -// swatch : The Swatch Group Ltd -// https://www.iana.org/domains/root/db/swatch.html -swatch - -// swiss : Swiss Confederation -// https://www.iana.org/domains/root/db/swiss.html -swiss - -// sydney : State of New South Wales, Department of Premier and Cabinet -// https://www.iana.org/domains/root/db/sydney.html -sydney - -// systems : Binky Moon, LLC -// https://www.iana.org/domains/root/db/systems.html -systems - -// tab : Tabcorp Holdings Limited -// https://www.iana.org/domains/root/db/tab.html -tab - -// taipei : Taipei City Government -// https://www.iana.org/domains/root/db/taipei.html -taipei - -// talk : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/talk.html -talk - -// taobao : Alibaba Group Holding Limited -// https://www.iana.org/domains/root/db/taobao.html -taobao - -// target : Target Domain Holdings, LLC -// https://www.iana.org/domains/root/db/target.html -target - -// tatamotors : Tata Motors Ltd -// https://www.iana.org/domains/root/db/tatamotors.html -tatamotors - -// tatar : Limited Liability Company "Coordination Center of Regional Domain of Tatarstan Republic" -// https://www.iana.org/domains/root/db/tatar.html -tatar - -// tattoo : Registry Services, LLC -// https://www.iana.org/domains/root/db/tattoo.html -tattoo - -// tax : Binky Moon, LLC -// https://www.iana.org/domains/root/db/tax.html -tax - -// taxi : Binky Moon, LLC -// https://www.iana.org/domains/root/db/taxi.html -taxi - -// tci -// https://www.iana.org/domains/root/db/tci.html -tci - -// tdk : TDK Corporation -// https://www.iana.org/domains/root/db/tdk.html -tdk - -// team : Binky Moon, LLC -// https://www.iana.org/domains/root/db/team.html -team - -// tech : Radix Technologies Inc SEZC -// https://www.iana.org/domains/root/db/tech.html -tech - -// technology : Binky Moon, LLC -// https://www.iana.org/domains/root/db/technology.html -technology - -// temasek : Temasek Holdings (Private) Limited -// https://www.iana.org/domains/root/db/temasek.html -temasek - -// tennis : Binky Moon, LLC -// https://www.iana.org/domains/root/db/tennis.html -tennis - -// teva : Teva Pharmaceutical Industries Limited -// https://www.iana.org/domains/root/db/teva.html -teva - -// thd : Home Depot Product Authority, LLC -// https://www.iana.org/domains/root/db/thd.html -thd - -// theater : Binky Moon, LLC -// https://www.iana.org/domains/root/db/theater.html -theater - -// theatre : XYZ.COM LLC -// https://www.iana.org/domains/root/db/theatre.html -theatre - -// tiaa : Teachers Insurance and Annuity Association of America -// https://www.iana.org/domains/root/db/tiaa.html -tiaa - -// tickets : XYZ.COM LLC -// https://www.iana.org/domains/root/db/tickets.html -tickets - -// tienda : Binky Moon, LLC -// https://www.iana.org/domains/root/db/tienda.html -tienda - -// tips : Binky Moon, LLC -// https://www.iana.org/domains/root/db/tips.html -tips - -// tires : Binky Moon, LLC -// https://www.iana.org/domains/root/db/tires.html -tires - -// tirol : punkt Tirol GmbH -// https://www.iana.org/domains/root/db/tirol.html -tirol - -// tjmaxx : The TJX Companies, Inc. -// https://www.iana.org/domains/root/db/tjmaxx.html -tjmaxx - -// tjx : The TJX Companies, Inc. -// https://www.iana.org/domains/root/db/tjx.html -tjx - -// tkmaxx : The TJX Companies, Inc. -// https://www.iana.org/domains/root/db/tkmaxx.html -tkmaxx - -// tmall : Alibaba Group Holding Limited -// https://www.iana.org/domains/root/db/tmall.html -tmall - -// today : Binky Moon, LLC -// https://www.iana.org/domains/root/db/today.html -today - -// tokyo : GMO Registry, Inc. -// https://www.iana.org/domains/root/db/tokyo.html -tokyo - -// tools : Binky Moon, LLC -// https://www.iana.org/domains/root/db/tools.html -tools - -// top : .TOP Registry -// https://www.iana.org/domains/root/db/top.html -top - -// toray : Toray Industries, Inc. -// https://www.iana.org/domains/root/db/toray.html -toray - -// toshiba : TOSHIBA Corporation -// https://www.iana.org/domains/root/db/toshiba.html -toshiba - -// total : TotalEnergies SE -// https://www.iana.org/domains/root/db/total.html -total - -// tours : Binky Moon, LLC -// https://www.iana.org/domains/root/db/tours.html -tours - -// town : Binky Moon, LLC -// https://www.iana.org/domains/root/db/town.html -town - -// toyota : TOYOTA MOTOR CORPORATION -// https://www.iana.org/domains/root/db/toyota.html -toyota - -// toys : Binky Moon, LLC -// https://www.iana.org/domains/root/db/toys.html -toys - -// trade : Elite Registry Limited -// https://www.iana.org/domains/root/db/trade.html -trade - -// trading : Dog Beach, LLC -// https://www.iana.org/domains/root/db/trading.html -trading - -// training : Binky Moon, LLC -// https://www.iana.org/domains/root/db/training.html -training - -// travel : Dog Beach, LLC -// https://www.iana.org/domains/root/db/travel.html -travel - -// travelers : Travelers TLD, LLC -// https://www.iana.org/domains/root/db/travelers.html -travelers - -// travelersinsurance : Travelers TLD, LLC -// https://www.iana.org/domains/root/db/travelersinsurance.html -travelersinsurance - -// trust : Internet Naming Company LLC -// https://www.iana.org/domains/root/db/trust.html -trust - -// trv : Travelers TLD, LLC -// https://www.iana.org/domains/root/db/trv.html -trv - -// tube : Latin American Telecom LLC -// https://www.iana.org/domains/root/db/tube.html -tube - -// tui : TUI AG -// https://www.iana.org/domains/root/db/tui.html -tui - -// tunes : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/tunes.html -tunes - -// tushu : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/tushu.html -tushu - -// tvs : T V SUNDRAM IYENGAR & SONS LIMITED -// https://www.iana.org/domains/root/db/tvs.html -tvs - -// ubank : National Australia Bank Limited -// https://www.iana.org/domains/root/db/ubank.html -ubank - -// ubs : UBS AG -// https://www.iana.org/domains/root/db/ubs.html -ubs - -// unicom : China United Network Communications Corporation Limited -// https://www.iana.org/domains/root/db/unicom.html -unicom - -// university : Binky Moon, LLC -// https://www.iana.org/domains/root/db/university.html -university - -// uno : Radix Technologies Inc SEZC -// https://www.iana.org/domains/root/db/uno.html -uno - -// uol : UBN INTERNET LTDA. -// https://www.iana.org/domains/root/db/uol.html -uol - -// ups : UPS Market Driver, Inc. -// https://www.iana.org/domains/root/db/ups.html -ups - -// vacations : Binky Moon, LLC -// https://www.iana.org/domains/root/db/vacations.html -vacations - -// vana : D3 Registry LLC -// https://www.iana.org/domains/root/db/vana.html -vana - -// vanguard : The Vanguard Group, Inc. -// https://www.iana.org/domains/root/db/vanguard.html -vanguard - -// vegas : Dot Vegas, Inc. -// https://www.iana.org/domains/root/db/vegas.html -vegas - -// ventures : Binky Moon, LLC -// https://www.iana.org/domains/root/db/ventures.html -ventures - -// verisign : VeriSign, Inc. -// https://www.iana.org/domains/root/db/verisign.html -verisign - -// versicherung : tldbox GmbH -// https://www.iana.org/domains/root/db/versicherung.html -versicherung - -// vet : Dog Beach, LLC -// https://www.iana.org/domains/root/db/vet.html -vet - -// viajes : Binky Moon, LLC -// https://www.iana.org/domains/root/db/viajes.html -viajes - -// video : Dog Beach, LLC -// https://www.iana.org/domains/root/db/video.html -video - -// vig : VIENNA INSURANCE GROUP AG Wiener Versicherung Gruppe -// https://www.iana.org/domains/root/db/vig.html -vig - -// viking : Viking River Cruises (Bermuda) Ltd. -// https://www.iana.org/domains/root/db/viking.html -viking - -// villas : Binky Moon, LLC -// https://www.iana.org/domains/root/db/villas.html -villas - -// vin : Binky Moon, LLC -// https://www.iana.org/domains/root/db/vin.html -vin - -// vip : Registry Services, LLC -// https://www.iana.org/domains/root/db/vip.html -vip - -// virgin : Virgin Enterprises Limited -// https://www.iana.org/domains/root/db/virgin.html -virgin - -// visa : Visa Worldwide Pte. Limited -// https://www.iana.org/domains/root/db/visa.html -visa - -// vision : Binky Moon, LLC -// https://www.iana.org/domains/root/db/vision.html -vision - -// viva : Saudi Telecom Company -// https://www.iana.org/domains/root/db/viva.html -viva - -// vivo : Telefonica Brasil S.A. -// https://www.iana.org/domains/root/db/vivo.html -vivo - -// vlaanderen : DNS.be vzw -// https://www.iana.org/domains/root/db/vlaanderen.html -vlaanderen - -// vodka : Registry Services, LLC -// https://www.iana.org/domains/root/db/vodka.html -vodka - -// volvo : Volvo Holding Sverige Aktiebolag -// https://www.iana.org/domains/root/db/volvo.html -volvo - -// vote : Monolith Registry LLC -// https://www.iana.org/domains/root/db/vote.html -vote - -// voting : Valuetainment Corp. -// https://www.iana.org/domains/root/db/voting.html -voting - -// voto : Monolith Registry LLC -// https://www.iana.org/domains/root/db/voto.html -voto - -// voyage : Binky Moon, LLC -// https://www.iana.org/domains/root/db/voyage.html -voyage - -// wales : Nominet UK -// https://www.iana.org/domains/root/db/wales.html -wales - -// walmart : Wal-Mart Stores, Inc. -// https://www.iana.org/domains/root/db/walmart.html -walmart - -// walter : Sandvik AB -// https://www.iana.org/domains/root/db/walter.html -walter - -// wang : Zodiac Wang Limited -// https://www.iana.org/domains/root/db/wang.html -wang - -// wanggou : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/wanggou.html -wanggou - -// watch : Binky Moon, LLC -// https://www.iana.org/domains/root/db/watch.html -watch - -// watches : Identity Digital Domains Limited -// https://www.iana.org/domains/root/db/watches.html -watches - -// weather : International Business Machines Corporation -// https://www.iana.org/domains/root/db/weather.html -weather - -// weatherchannel : The Weather Company, LLC -// https://www.iana.org/domains/root/db/weatherchannel.html -weatherchannel - -// webcam : dot Webcam Limited -// https://www.iana.org/domains/root/db/webcam.html -webcam - -// weber : Saint-Gobain Weber SA -// https://www.iana.org/domains/root/db/weber.html -weber - -// website : Radix Technologies Inc SEZC -// https://www.iana.org/domains/root/db/website.html -website - -// wed -// https://www.iana.org/domains/root/db/wed.html -wed - -// wedding : Registry Services, LLC -// https://www.iana.org/domains/root/db/wedding.html -wedding - -// weibo : Sina Corporation -// https://www.iana.org/domains/root/db/weibo.html -weibo - -// weir : Weir Group IP Limited -// https://www.iana.org/domains/root/db/weir.html -weir - -// whoswho : Who's Who Registry -// https://www.iana.org/domains/root/db/whoswho.html -whoswho - -// wien : punkt.wien GmbH -// https://www.iana.org/domains/root/db/wien.html -wien - -// wiki : Registry Services, LLC -// https://www.iana.org/domains/root/db/wiki.html -wiki - -// williamhill : William Hill Organization Limited -// https://www.iana.org/domains/root/db/williamhill.html -williamhill - -// win : First Registry Limited -// https://www.iana.org/domains/root/db/win.html -win - -// windows : Microsoft Corporation -// https://www.iana.org/domains/root/db/windows.html -windows - -// wine : Binky Moon, LLC -// https://www.iana.org/domains/root/db/wine.html -wine - -// winners : The TJX Companies, Inc. -// https://www.iana.org/domains/root/db/winners.html -winners - -// wme : William Morris Endeavor Entertainment, LLC -// https://www.iana.org/domains/root/db/wme.html -wme - -// wolterskluwer : Wolters Kluwer N.V. -// https://www.iana.org/domains/root/db/wolterskluwer.html -wolterskluwer - -// woodside : Woodside Petroleum Limited -// https://www.iana.org/domains/root/db/woodside.html -woodside - -// work : Registry Services, LLC -// https://www.iana.org/domains/root/db/work.html -work - -// works : Binky Moon, LLC -// https://www.iana.org/domains/root/db/works.html -works - -// world : Binky Moon, LLC -// https://www.iana.org/domains/root/db/world.html -world - -// wow : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/wow.html -wow - -// wtc : World Trade Centers Association, Inc. -// https://www.iana.org/domains/root/db/wtc.html -wtc - -// wtf : Binky Moon, LLC -// https://www.iana.org/domains/root/db/wtf.html -wtf - -// xbox : Microsoft Corporation -// https://www.iana.org/domains/root/db/xbox.html -xbox - -// xerox : Xerox DNHC LLC -// https://www.iana.org/domains/root/db/xerox.html -xerox - -// xihuan : Beijing Qihu Keji Co., Ltd. -// https://www.iana.org/domains/root/db/xihuan.html -xihuan - -// xin : Elegant Leader Limited -// https://www.iana.org/domains/root/db/xin.html -xin - -// xn--11b4c3d : VeriSign Sarl -// https://www.iana.org/domains/root/db/xn--11b4c3d.html -कॉम - -// xn--1ck2e1b : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/xn--1ck2e1b.html -セール - -// xn--1qqw23a : Guangzhou YU Wei Information Technology Co., Ltd. -// https://www.iana.org/domains/root/db/xn--1qqw23a.html -佛山 - -// xn--30rr7y : Excellent First Limited -// https://www.iana.org/domains/root/db/xn--30rr7y.html -慈善 - -// xn--3bst00m : Eagle Horizon Limited -// https://www.iana.org/domains/root/db/xn--3bst00m.html -集团 - -// xn--3ds443g : Beijing TLD Registry Technology Limited -// https://www.iana.org/domains/root/db/xn--3ds443g.html -在线 - -// xn--3pxu8k : VeriSign Sarl -// https://www.iana.org/domains/root/db/xn--3pxu8k.html -点看 - -// xn--42c2d9a : VeriSign Sarl -// https://www.iana.org/domains/root/db/xn--42c2d9a.html -คอม - -// xn--45q11c : Zodiac Gemini Ltd -// https://www.iana.org/domains/root/db/xn--45q11c.html -八卦 - -// xn--4gbrim : Helium TLDs Ltd -// https://www.iana.org/domains/root/db/xn--4gbrim.html -موقع - -// xn--55qw42g : China Organizational Name Administration Center -// https://www.iana.org/domains/root/db/xn--55qw42g.html -公益 - -// xn--55qx5d : China Internet Network Information Center (CNNIC) -// https://www.iana.org/domains/root/db/xn--55qx5d.html -公司 - -// xn--5su34j936bgsg : Shangri‐La International Hotel Management Limited -// https://www.iana.org/domains/root/db/xn--5su34j936bgsg.html -香格里拉 - -// xn--5tzm5g : Global Website TLD Asia Limited -// https://www.iana.org/domains/root/db/xn--5tzm5g.html -网站 - -// xn--6frz82g : Identity Digital Domains Limited -// https://www.iana.org/domains/root/db/xn--6frz82g.html -移动 - -// xn--6qq986b3xl : Tycoon Treasure Limited -// https://www.iana.org/domains/root/db/xn--6qq986b3xl.html -我爱你 - -// xn--80adxhks : Foundation for Assistance for Internet Technologies and Infrastructure Development (FAITID) -// https://www.iana.org/domains/root/db/xn--80adxhks.html -москва - -// xn--80aqecdr1a : Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication) -// https://www.iana.org/domains/root/db/xn--80aqecdr1a.html -католик - -// xn--80asehdb : CORE Association -// https://www.iana.org/domains/root/db/xn--80asehdb.html -онлайн - -// xn--80aswg : CORE Association -// https://www.iana.org/domains/root/db/xn--80aswg.html -сайт - -// xn--8y0a063a : China United Network Communications Corporation Limited -// https://www.iana.org/domains/root/db/xn--8y0a063a.html -联通 - -// xn--9dbq2a : VeriSign Sarl -// https://www.iana.org/domains/root/db/xn--9dbq2a.html -קום - -// xn--9et52u : RISE VICTORY LIMITED -// https://www.iana.org/domains/root/db/xn--9et52u.html -时尚 - -// xn--9krt00a : Sina Corporation -// https://www.iana.org/domains/root/db/xn--9krt00a.html -微博 - -// xn--b4w605ferd : Temasek Holdings (Private) Limited -// https://www.iana.org/domains/root/db/xn--b4w605ferd.html -淡马锡 - -// xn--bck1b9a5dre4c : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/xn--bck1b9a5dre4c.html -ファッション - -// xn--c1avg : Public Interest Registry -// https://www.iana.org/domains/root/db/xn--c1avg.html -орг - -// xn--c2br7g : VeriSign Sarl -// https://www.iana.org/domains/root/db/xn--c2br7g.html -नेट - -// xn--cck2b3b : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/xn--cck2b3b.html -ストア - -// xn--cckwcxetd : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/xn--cckwcxetd.html -アマゾン - -// xn--cg4bki : SAMSUNG SDS CO., LTD -// https://www.iana.org/domains/root/db/xn--cg4bki.html -삼성 - -// xn--czr694b : Internet DotTrademark Organisation Limited -// https://www.iana.org/domains/root/db/xn--czr694b.html -商标 - -// xn--czrs0t : Binky Moon, LLC -// https://www.iana.org/domains/root/db/xn--czrs0t.html -商店 - -// xn--czru2d : Zodiac Aquarius Limited -// https://www.iana.org/domains/root/db/xn--czru2d.html -商城 - -// xn--d1acj3b : The Foundation for Network Initiatives “The Smart Internet” -// https://www.iana.org/domains/root/db/xn--d1acj3b.html -дети - -// xn--eckvdtc9d : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/xn--eckvdtc9d.html -ポイント - -// xn--efvy88h : Guangzhou YU Wei Information Technology Co., Ltd. -// https://www.iana.org/domains/root/db/xn--efvy88h.html -新闻 - -// xn--fct429k : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/xn--fct429k.html -家電 - -// xn--fhbei : VeriSign Sarl -// https://www.iana.org/domains/root/db/xn--fhbei.html -كوم - -// xn--fiq228c5hs : TLD REGISTRY LIMITED OY -// https://www.iana.org/domains/root/db/xn--fiq228c5hs.html -中文网 - -// xn--fiq64b : CITIC Group Corporation -// https://www.iana.org/domains/root/db/xn--fiq64b.html -中信 - -// xn--fjq720a : Binky Moon, LLC -// https://www.iana.org/domains/root/db/xn--fjq720a.html -娱乐 - -// xn--flw351e : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/xn--flw351e.html -谷歌 - -// xn--fzys8d69uvgm : PCCW Enterprises Limited -// https://www.iana.org/domains/root/db/xn--fzys8d69uvgm.html -電訊盈科 - -// xn--g2xx48c : Nawang Heli(Xiamen) Network Service Co., LTD. -// https://www.iana.org/domains/root/db/xn--g2xx48c.html -购物 - -// xn--gckr3f0f : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/xn--gckr3f0f.html -クラウド - -// xn--gk3at1e : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/xn--gk3at1e.html -通販 - -// xn--hxt814e : Zodiac Taurus Limited -// https://www.iana.org/domains/root/db/xn--hxt814e.html -网店 - -// xn--i1b6b1a6a2e : Public Interest Registry -// https://www.iana.org/domains/root/db/xn--i1b6b1a6a2e.html -संगठन - -// xn--imr513n : Internet DotTrademark Organisation Limited -// https://www.iana.org/domains/root/db/xn--imr513n.html -餐厅 - -// xn--io0a7i : China Internet Network Information Center (CNNIC) -// https://www.iana.org/domains/root/db/xn--io0a7i.html -网络 - -// xn--j1aef : VeriSign Sarl -// https://www.iana.org/domains/root/db/xn--j1aef.html -ком - -// xn--jlq480n2rg : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/xn--jlq480n2rg.html -亚马逊 - -// xn--jvr189m : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/xn--jvr189m.html -食品 - -// xn--kcrx77d1x4a : Koninklijke Philips N.V. -// https://www.iana.org/domains/root/db/xn--kcrx77d1x4a.html -飞利浦 - -// xn--kput3i : Beijing RITT-Net Technology Development Co., Ltd -// https://www.iana.org/domains/root/db/xn--kput3i.html -手机 - -// xn--mgba3a3ejt : Aramco Services Company -// https://www.iana.org/domains/root/db/xn--mgba3a3ejt.html -ارامكو - -// xn--mgba7c0bbn0a : Competrol (Luxembourg) Sarl -// https://www.iana.org/domains/root/db/xn--mgba7c0bbn0a.html -العليان - -// xn--mgbab2bd : CORE Association -// https://www.iana.org/domains/root/db/xn--mgbab2bd.html -بازار - -// xn--mgbca7dzdo : Abu Dhabi Systems and Information Centre -// https://www.iana.org/domains/root/db/xn--mgbca7dzdo.html -ابوظبي - -// xn--mgbi4ecexp : Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication) -// https://www.iana.org/domains/root/db/xn--mgbi4ecexp.html -كاثوليك - -// xn--mgbt3dhd -// https://www.iana.org/domains/root/db/xn--mgbt3dhd.html -همراه - -// xn--mk1bu44c : VeriSign Sarl -// https://www.iana.org/domains/root/db/xn--mk1bu44c.html -닷컴 - -// xn--mxtq1m : Net-Chinese Co., Ltd. -// https://www.iana.org/domains/root/db/xn--mxtq1m.html -政府 - -// xn--ngbc5azd : International Domain Registry Pty. Ltd. -// https://www.iana.org/domains/root/db/xn--ngbc5azd.html -شبكة - -// xn--ngbe9e0a : Kuwait Finance House -// https://www.iana.org/domains/root/db/xn--ngbe9e0a.html -بيتك - -// xn--ngbrx : League of Arab States -// https://www.iana.org/domains/root/db/xn--ngbrx.html -عرب - -// xn--nqv7f : Public Interest Registry -// https://www.iana.org/domains/root/db/xn--nqv7f.html -机构 - -// xn--nqv7fs00ema : Public Interest Registry -// https://www.iana.org/domains/root/db/xn--nqv7fs00ema.html -组织机构 - -// xn--nyqy26a : Stable Tone Limited -// https://www.iana.org/domains/root/db/xn--nyqy26a.html -健康 - -// xn--otu796d : Jiang Yu Liang Cai Technology Company Limited -// https://www.iana.org/domains/root/db/xn--otu796d.html -招聘 - -// xn--p1acf : Rusnames Limited -// https://www.iana.org/domains/root/db/xn--p1acf.html -рус - -// xn--pssy2u : VeriSign Sarl -// https://www.iana.org/domains/root/db/xn--pssy2u.html -大拿 - -// xn--q9jyb4c : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/xn--q9jyb4c.html -みんな - -// xn--qcka1pmc : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/xn--qcka1pmc.html -グーグル - -// xn--rhqv96g : Stable Tone Limited -// https://www.iana.org/domains/root/db/xn--rhqv96g.html -世界 - -// xn--rovu88b : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/xn--rovu88b.html -書籍 - -// xn--ses554g : KNET Co., Ltd. -// https://www.iana.org/domains/root/db/xn--ses554g.html -网址 - -// xn--t60b56a : VeriSign Sarl -// https://www.iana.org/domains/root/db/xn--t60b56a.html -닷넷 - -// xn--tckwe : VeriSign Sarl -// https://www.iana.org/domains/root/db/xn--tckwe.html -コム - -// xn--tiq49xqyj : Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication) -// https://www.iana.org/domains/root/db/xn--tiq49xqyj.html -天主教 - -// xn--unup4y : Binky Moon, LLC -// https://www.iana.org/domains/root/db/xn--unup4y.html -游戏 - -// xn--vermgensberater-ctb : Deutsche Vermögensberatung Aktiengesellschaft DVAG -// https://www.iana.org/domains/root/db/xn--vermgensberater-ctb.html -vermögensberater - -// xn--vermgensberatung-pwb : Deutsche Vermögensberatung Aktiengesellschaft DVAG -// https://www.iana.org/domains/root/db/xn--vermgensberatung-pwb.html -vermögensberatung - -// xn--vhquv : Binky Moon, LLC -// https://www.iana.org/domains/root/db/xn--vhquv.html -企业 - -// xn--vuq861b : Beijing Tele-info Technology Co., Ltd. -// https://www.iana.org/domains/root/db/xn--vuq861b.html -信息 - -// xn--w4r85el8fhu5dnra : Kerry Trading Co. Limited -// https://www.iana.org/domains/root/db/xn--w4r85el8fhu5dnra.html -嘉里大酒店 - -// xn--w4rs40l : Kerry Trading Co. Limited -// https://www.iana.org/domains/root/db/xn--w4rs40l.html -嘉里 - -// xn--xhq521b : Guangzhou YU Wei Information Technology Co., Ltd. -// https://www.iana.org/domains/root/db/xn--xhq521b.html -广东 - -// xn--zfr164b : China Organizational Name Administration Center -// https://www.iana.org/domains/root/db/xn--zfr164b.html -政务 - -// xyz : XYZ.COM LLC -// https://www.iana.org/domains/root/db/xyz.html -xyz - -// yachts : XYZ.COM LLC -// https://www.iana.org/domains/root/db/yachts.html -yachts - -// yahoo : Yahoo Inc. -// https://www.iana.org/domains/root/db/yahoo.html -yahoo - -// yamaxun : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/yamaxun.html -yamaxun - -// yandex : YANDEX, LLC -// https://www.iana.org/domains/root/db/yandex.html -yandex - -// yodobashi : YODOBASHI CAMERA CO.,LTD. -// https://www.iana.org/domains/root/db/yodobashi.html -yodobashi - -// yoga : Registry Services, LLC -// https://www.iana.org/domains/root/db/yoga.html -yoga - -// yokohama : GMO Registry, Inc. -// https://www.iana.org/domains/root/db/yokohama.html -yokohama - -// you : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/you.html -you - -// youtube : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/youtube.html -youtube - -// yun : Beijing Qihu Keji Co., Ltd. -// https://www.iana.org/domains/root/db/yun.html -yun - -// zappos : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/zappos.html -zappos - -// zara : Industria de Diseño Textil, S.A. (INDITEX, S.A.) -// https://www.iana.org/domains/root/db/zara.html -zara - -// zero : Amazon Registry Services, Inc. -// https://www.iana.org/domains/root/db/zero.html -zero - -// zip : Charleston Road Registry Inc. -// https://www.iana.org/domains/root/db/zip.html -zip - -// zone : Binky Moon, LLC -// https://www.iana.org/domains/root/db/zone.html -zone - -// zuerich : Kanton Zürich (Canton of Zurich) -// https://www.iana.org/domains/root/db/zuerich.html -zuerich - -// ===END ICANN DOMAINS=== - -// ===BEGIN PRIVATE DOMAINS=== - -// (Note: these are in alphabetical order by company name) - -// .KRD : https://nic.krd -co.krd -edu.krd - -// .pl domains (grandfathered) -art.pl -gliwice.pl -krakow.pl -poznan.pl -wroc.pl -zakopane.pl - -// 12CHARS : https://12chars.com -// Submitted by Kenny Niehage -12chars.dev -12chars.it -12chars.pro - -// 1GB LLC : https://www.1gb.ua/ -// Submitted by 1GB LLC -cc.ua -inf.ua -ltd.ua - -// 611 blockchain domain name system : https://sixone.one/ -611.to - -// A2 Hosting -// Submitted by Tyler Hall -a2hosted.com -cpserver.com - -// Acorn Labs : https://acorn.io -// Submitted by Craig Jellick -*.on-acorn.io - -// ActiveTrail : https://www.activetrail.biz/ -// Submitted by Ofer Kalaora -activetrail.biz - -// Adaptable.io : https://adaptable.io -// Submitted by Mark Terrel -adaptable.app - -// addr.tools : https://addr.tools/ -// Submitted by Brian Shea -myaddr.dev -myaddr.io -dyn.addr.tools -myaddr.tools - -// Adobe : https://www.adobe.com/ -// Submitted by Ian Boston and Lars Trieloff -adobeaemcloud.com -*.dev.adobeaemcloud.com -aem.live -hlx.live -adobeaemcloud.net -aem.network -aem.page -hlx.page -aem.reviews - -// Adobe Developer Platform : https://developer.adobe.com -// Submitted by Jesse MacFadyen -adobeio-static.net -adobeioruntime.net - -// Africa.com Web Solutions Ltd : https://registry.africa.com -// Submitted by Gavin Brown -africa.com - -// Agnat sp. z o.o. : https://domena.pl -// Submitted by Przemyslaw Plewa -beep.pl - -// Aiven : https://aiven.io/ -// Submitted by Aiven Security Team -aiven.app -aivencloud.com - -// Akamai : https://www.akamai.com/ -// Submitted by Akamai Team -akadns.net -akamai.net -akamai-staging.net -akamaiedge.net -akamaiedge-staging.net -akamaihd.net -akamaihd-staging.net -akamaiorigin.net -akamaiorigin-staging.net -akamaized.net -akamaized-staging.net -edgekey.net -edgekey-staging.net -edgesuite.net -edgesuite-staging.net - -// alboto.ca : http://alboto.ca -// Submitted by Anton Avramov -barsy.ca - -// Alces Software Ltd : http://alces-software.com -// Submitted by Mark J. Titorenko -*.compute.estate -*.alces.network - -// Alibaba Cloud API Gateway -// Submitted by Alibaba Cloud Security -alibabacloudcs.com - -// all-inkl.com : https://all-inkl.com -// Submitted by Werner Kaltofen -kasserver.com - -// Altervista : https://www.altervista.org -// Submitted by Carlo Cannas -altervista.org - -// alwaysdata : https://www.alwaysdata.com -// Submitted by Cyril -alwaysdata.net - -// Amaze Software : https://amaze.co -// Submitted by Domain Admin -myamaze.net - -// Amazon : https://www.amazon.com/ -// Submitted by AWS Security -// Subsections of Amazon/subsidiaries will appear until "concludes" tag - -// Amazon API Gateway -// Submitted by AWS Security -// Reference: 6a4f5a95-8c7d-4077-a7af-9cf1abec0a53 -execute-api.cn-north-1.amazonaws.com.cn -execute-api.cn-northwest-1.amazonaws.com.cn -execute-api.af-south-1.amazonaws.com -execute-api.ap-east-1.amazonaws.com -execute-api.ap-northeast-1.amazonaws.com -execute-api.ap-northeast-2.amazonaws.com -execute-api.ap-northeast-3.amazonaws.com -execute-api.ap-south-1.amazonaws.com -execute-api.ap-south-2.amazonaws.com -execute-api.ap-southeast-1.amazonaws.com -execute-api.ap-southeast-2.amazonaws.com -execute-api.ap-southeast-3.amazonaws.com -execute-api.ap-southeast-4.amazonaws.com -execute-api.ap-southeast-5.amazonaws.com -execute-api.ca-central-1.amazonaws.com -execute-api.ca-west-1.amazonaws.com -execute-api.eu-central-1.amazonaws.com -execute-api.eu-central-2.amazonaws.com -execute-api.eu-north-1.amazonaws.com -execute-api.eu-south-1.amazonaws.com -execute-api.eu-south-2.amazonaws.com -execute-api.eu-west-1.amazonaws.com -execute-api.eu-west-2.amazonaws.com -execute-api.eu-west-3.amazonaws.com -execute-api.il-central-1.amazonaws.com -execute-api.me-central-1.amazonaws.com -execute-api.me-south-1.amazonaws.com -execute-api.sa-east-1.amazonaws.com -execute-api.us-east-1.amazonaws.com -execute-api.us-east-2.amazonaws.com -execute-api.us-gov-east-1.amazonaws.com -execute-api.us-gov-west-1.amazonaws.com -execute-api.us-west-1.amazonaws.com -execute-api.us-west-2.amazonaws.com - -// Amazon CloudFront -// Submitted by Donavan Miller -// Reference: 54144616-fd49-4435-8535-19c6a601bdb3 -cloudfront.net - -// Amazon Cognito -// Submitted by AWS Security -// Reference: e7c02dc1-02f4-4a23-bde3-a8527c830127 -auth.af-south-1.amazoncognito.com -auth.ap-east-1.amazoncognito.com -auth.ap-northeast-1.amazoncognito.com -auth.ap-northeast-2.amazoncognito.com -auth.ap-northeast-3.amazoncognito.com -auth.ap-south-1.amazoncognito.com -auth.ap-south-2.amazoncognito.com -auth.ap-southeast-1.amazoncognito.com -auth.ap-southeast-2.amazoncognito.com -auth.ap-southeast-3.amazoncognito.com -auth.ap-southeast-4.amazoncognito.com -auth.ap-southeast-5.amazoncognito.com -auth.ap-southeast-7.amazoncognito.com -auth.ca-central-1.amazoncognito.com -auth.ca-west-1.amazoncognito.com -auth.eu-central-1.amazoncognito.com -auth.eu-central-2.amazoncognito.com -auth.eu-north-1.amazoncognito.com -auth.eu-south-1.amazoncognito.com -auth.eu-south-2.amazoncognito.com -auth.eu-west-1.amazoncognito.com -auth.eu-west-2.amazoncognito.com -auth.eu-west-3.amazoncognito.com -auth.il-central-1.amazoncognito.com -auth.me-central-1.amazoncognito.com -auth.me-south-1.amazoncognito.com -auth.mx-central-1.amazoncognito.com -auth.sa-east-1.amazoncognito.com -auth.us-east-1.amazoncognito.com -auth-fips.us-east-1.amazoncognito.com -auth.us-east-2.amazoncognito.com -auth-fips.us-east-2.amazoncognito.com -auth-fips.us-gov-east-1.amazoncognito.com -auth-fips.us-gov-west-1.amazoncognito.com -auth.us-west-1.amazoncognito.com -auth-fips.us-west-1.amazoncognito.com -auth.us-west-2.amazoncognito.com -auth-fips.us-west-2.amazoncognito.com - -// Amazon EC2 -// Submitted by Luke Wells -// Reference: 4c38fa71-58ac-4768-99e5-689c1767e537 -*.compute.amazonaws.com.cn -*.compute.amazonaws.com -*.compute-1.amazonaws.com -us-east-1.amazonaws.com - -// Amazon EMR -// Submitted by AWS Security -// Reference: 82f43f9f-bbb8-400e-8349-854f5a62f20d -emrappui-prod.cn-north-1.amazonaws.com.cn -emrnotebooks-prod.cn-north-1.amazonaws.com.cn -emrstudio-prod.cn-north-1.amazonaws.com.cn -emrappui-prod.cn-northwest-1.amazonaws.com.cn -emrnotebooks-prod.cn-northwest-1.amazonaws.com.cn -emrstudio-prod.cn-northwest-1.amazonaws.com.cn -emrappui-prod.af-south-1.amazonaws.com -emrnotebooks-prod.af-south-1.amazonaws.com -emrstudio-prod.af-south-1.amazonaws.com -emrappui-prod.ap-east-1.amazonaws.com -emrnotebooks-prod.ap-east-1.amazonaws.com -emrstudio-prod.ap-east-1.amazonaws.com -emrappui-prod.ap-northeast-1.amazonaws.com -emrnotebooks-prod.ap-northeast-1.amazonaws.com -emrstudio-prod.ap-northeast-1.amazonaws.com -emrappui-prod.ap-northeast-2.amazonaws.com -emrnotebooks-prod.ap-northeast-2.amazonaws.com -emrstudio-prod.ap-northeast-2.amazonaws.com -emrappui-prod.ap-northeast-3.amazonaws.com -emrnotebooks-prod.ap-northeast-3.amazonaws.com -emrstudio-prod.ap-northeast-3.amazonaws.com -emrappui-prod.ap-south-1.amazonaws.com -emrnotebooks-prod.ap-south-1.amazonaws.com -emrstudio-prod.ap-south-1.amazonaws.com -emrappui-prod.ap-south-2.amazonaws.com -emrnotebooks-prod.ap-south-2.amazonaws.com -emrstudio-prod.ap-south-2.amazonaws.com -emrappui-prod.ap-southeast-1.amazonaws.com -emrnotebooks-prod.ap-southeast-1.amazonaws.com -emrstudio-prod.ap-southeast-1.amazonaws.com -emrappui-prod.ap-southeast-2.amazonaws.com -emrnotebooks-prod.ap-southeast-2.amazonaws.com -emrstudio-prod.ap-southeast-2.amazonaws.com -emrappui-prod.ap-southeast-3.amazonaws.com -emrnotebooks-prod.ap-southeast-3.amazonaws.com -emrstudio-prod.ap-southeast-3.amazonaws.com -emrappui-prod.ap-southeast-4.amazonaws.com -emrnotebooks-prod.ap-southeast-4.amazonaws.com -emrstudio-prod.ap-southeast-4.amazonaws.com -emrappui-prod.ca-central-1.amazonaws.com -emrnotebooks-prod.ca-central-1.amazonaws.com -emrstudio-prod.ca-central-1.amazonaws.com -emrappui-prod.ca-west-1.amazonaws.com -emrnotebooks-prod.ca-west-1.amazonaws.com -emrstudio-prod.ca-west-1.amazonaws.com -emrappui-prod.eu-central-1.amazonaws.com -emrnotebooks-prod.eu-central-1.amazonaws.com -emrstudio-prod.eu-central-1.amazonaws.com -emrappui-prod.eu-central-2.amazonaws.com -emrnotebooks-prod.eu-central-2.amazonaws.com -emrstudio-prod.eu-central-2.amazonaws.com -emrappui-prod.eu-north-1.amazonaws.com -emrnotebooks-prod.eu-north-1.amazonaws.com -emrstudio-prod.eu-north-1.amazonaws.com -emrappui-prod.eu-south-1.amazonaws.com -emrnotebooks-prod.eu-south-1.amazonaws.com -emrstudio-prod.eu-south-1.amazonaws.com -emrappui-prod.eu-south-2.amazonaws.com -emrnotebooks-prod.eu-south-2.amazonaws.com -emrstudio-prod.eu-south-2.amazonaws.com -emrappui-prod.eu-west-1.amazonaws.com -emrnotebooks-prod.eu-west-1.amazonaws.com -emrstudio-prod.eu-west-1.amazonaws.com -emrappui-prod.eu-west-2.amazonaws.com -emrnotebooks-prod.eu-west-2.amazonaws.com -emrstudio-prod.eu-west-2.amazonaws.com -emrappui-prod.eu-west-3.amazonaws.com -emrnotebooks-prod.eu-west-3.amazonaws.com -emrstudio-prod.eu-west-3.amazonaws.com -emrappui-prod.il-central-1.amazonaws.com -emrnotebooks-prod.il-central-1.amazonaws.com -emrstudio-prod.il-central-1.amazonaws.com -emrappui-prod.me-central-1.amazonaws.com -emrnotebooks-prod.me-central-1.amazonaws.com -emrstudio-prod.me-central-1.amazonaws.com -emrappui-prod.me-south-1.amazonaws.com -emrnotebooks-prod.me-south-1.amazonaws.com -emrstudio-prod.me-south-1.amazonaws.com -emrappui-prod.sa-east-1.amazonaws.com -emrnotebooks-prod.sa-east-1.amazonaws.com -emrstudio-prod.sa-east-1.amazonaws.com -emrappui-prod.us-east-1.amazonaws.com -emrnotebooks-prod.us-east-1.amazonaws.com -emrstudio-prod.us-east-1.amazonaws.com -emrappui-prod.us-east-2.amazonaws.com -emrnotebooks-prod.us-east-2.amazonaws.com -emrstudio-prod.us-east-2.amazonaws.com -emrappui-prod.us-gov-east-1.amazonaws.com -emrnotebooks-prod.us-gov-east-1.amazonaws.com -emrstudio-prod.us-gov-east-1.amazonaws.com -emrappui-prod.us-gov-west-1.amazonaws.com -emrnotebooks-prod.us-gov-west-1.amazonaws.com -emrstudio-prod.us-gov-west-1.amazonaws.com -emrappui-prod.us-west-1.amazonaws.com -emrnotebooks-prod.us-west-1.amazonaws.com -emrstudio-prod.us-west-1.amazonaws.com -emrappui-prod.us-west-2.amazonaws.com -emrnotebooks-prod.us-west-2.amazonaws.com -emrstudio-prod.us-west-2.amazonaws.com - -// Amazon Managed Workflows for Apache Airflow -// Submitted by AWS Security -// Reference: 2f697e23-58d6-4b97-be6b-77a26e811dad -*.airflow.af-south-1.on.aws -*.airflow.ap-east-1.on.aws -*.airflow.ap-northeast-1.on.aws -*.airflow.ap-northeast-2.on.aws -*.airflow.ap-northeast-3.on.aws -*.airflow.ap-south-1.on.aws -*.airflow.ap-south-2.on.aws -*.airflow.ap-southeast-1.on.aws -*.airflow.ap-southeast-2.on.aws -*.airflow.ap-southeast-3.on.aws -*.airflow.ap-southeast-4.on.aws -*.airflow.ap-southeast-5.on.aws -*.airflow.ca-central-1.on.aws -*.airflow.ca-west-1.on.aws -*.airflow.eu-central-1.on.aws -*.airflow.eu-central-2.on.aws -*.airflow.eu-north-1.on.aws -*.airflow.eu-south-1.on.aws -*.airflow.eu-south-2.on.aws -*.airflow.eu-west-1.on.aws -*.airflow.eu-west-2.on.aws -*.airflow.eu-west-3.on.aws -*.airflow.il-central-1.on.aws -*.airflow.me-central-1.on.aws -*.airflow.me-south-1.on.aws -*.airflow.sa-east-1.on.aws -*.airflow.us-east-1.on.aws -*.airflow.us-east-2.on.aws -*.airflow.us-west-1.on.aws -*.airflow.us-west-2.on.aws -*.cn-north-1.airflow.amazonaws.com.cn -*.cn-northwest-1.airflow.amazonaws.com.cn -*.airflow.cn-north-1.on.amazonwebservices.com.cn -*.airflow.cn-northwest-1.on.amazonwebservices.com.cn -*.af-south-1.airflow.amazonaws.com -*.ap-east-1.airflow.amazonaws.com -*.ap-northeast-1.airflow.amazonaws.com -*.ap-northeast-2.airflow.amazonaws.com -*.ap-northeast-3.airflow.amazonaws.com -*.ap-south-1.airflow.amazonaws.com -*.ap-south-2.airflow.amazonaws.com -*.ap-southeast-1.airflow.amazonaws.com -*.ap-southeast-2.airflow.amazonaws.com -*.ap-southeast-3.airflow.amazonaws.com -*.ap-southeast-4.airflow.amazonaws.com -*.ap-southeast-5.airflow.amazonaws.com -*.ca-central-1.airflow.amazonaws.com -*.ca-west-1.airflow.amazonaws.com -*.eu-central-1.airflow.amazonaws.com -*.eu-central-2.airflow.amazonaws.com -*.eu-north-1.airflow.amazonaws.com -*.eu-south-1.airflow.amazonaws.com -*.eu-south-2.airflow.amazonaws.com -*.eu-west-1.airflow.amazonaws.com -*.eu-west-2.airflow.amazonaws.com -*.eu-west-3.airflow.amazonaws.com -*.il-central-1.airflow.amazonaws.com -*.me-central-1.airflow.amazonaws.com -*.me-south-1.airflow.amazonaws.com -*.sa-east-1.airflow.amazonaws.com -*.us-east-1.airflow.amazonaws.com -*.us-east-2.airflow.amazonaws.com -*.us-west-1.airflow.amazonaws.com -*.us-west-2.airflow.amazonaws.com - -// Amazon S3 -// Submitted by AWS Security -// Reference: ada5c9df-55e1-4195-a1ce-732d6c81e357 -s3.dualstack.cn-north-1.amazonaws.com.cn -s3-accesspoint.dualstack.cn-north-1.amazonaws.com.cn -s3-website.dualstack.cn-north-1.amazonaws.com.cn -s3.cn-north-1.amazonaws.com.cn -s3-accesspoint.cn-north-1.amazonaws.com.cn -s3-deprecated.cn-north-1.amazonaws.com.cn -s3-object-lambda.cn-north-1.amazonaws.com.cn -s3-website.cn-north-1.amazonaws.com.cn -s3.dualstack.cn-northwest-1.amazonaws.com.cn -s3-accesspoint.dualstack.cn-northwest-1.amazonaws.com.cn -s3.cn-northwest-1.amazonaws.com.cn -s3-accesspoint.cn-northwest-1.amazonaws.com.cn -s3-object-lambda.cn-northwest-1.amazonaws.com.cn -s3-website.cn-northwest-1.amazonaws.com.cn -s3.dualstack.af-south-1.amazonaws.com -s3-accesspoint.dualstack.af-south-1.amazonaws.com -s3-website.dualstack.af-south-1.amazonaws.com -s3.af-south-1.amazonaws.com -s3-accesspoint.af-south-1.amazonaws.com -s3-object-lambda.af-south-1.amazonaws.com -s3-website.af-south-1.amazonaws.com -s3.dualstack.ap-east-1.amazonaws.com -s3-accesspoint.dualstack.ap-east-1.amazonaws.com -s3.ap-east-1.amazonaws.com -s3-accesspoint.ap-east-1.amazonaws.com -s3-object-lambda.ap-east-1.amazonaws.com -s3-website.ap-east-1.amazonaws.com -s3.dualstack.ap-northeast-1.amazonaws.com -s3-accesspoint.dualstack.ap-northeast-1.amazonaws.com -s3-website.dualstack.ap-northeast-1.amazonaws.com -s3.ap-northeast-1.amazonaws.com -s3-accesspoint.ap-northeast-1.amazonaws.com -s3-object-lambda.ap-northeast-1.amazonaws.com -s3-website.ap-northeast-1.amazonaws.com -s3.dualstack.ap-northeast-2.amazonaws.com -s3-accesspoint.dualstack.ap-northeast-2.amazonaws.com -s3-website.dualstack.ap-northeast-2.amazonaws.com -s3.ap-northeast-2.amazonaws.com -s3-accesspoint.ap-northeast-2.amazonaws.com -s3-object-lambda.ap-northeast-2.amazonaws.com -s3-website.ap-northeast-2.amazonaws.com -s3.dualstack.ap-northeast-3.amazonaws.com -s3-accesspoint.dualstack.ap-northeast-3.amazonaws.com -s3-website.dualstack.ap-northeast-3.amazonaws.com -s3.ap-northeast-3.amazonaws.com -s3-accesspoint.ap-northeast-3.amazonaws.com -s3-object-lambda.ap-northeast-3.amazonaws.com -s3-website.ap-northeast-3.amazonaws.com -s3.dualstack.ap-south-1.amazonaws.com -s3-accesspoint.dualstack.ap-south-1.amazonaws.com -s3-website.dualstack.ap-south-1.amazonaws.com -s3.ap-south-1.amazonaws.com -s3-accesspoint.ap-south-1.amazonaws.com -s3-object-lambda.ap-south-1.amazonaws.com -s3-website.ap-south-1.amazonaws.com -s3.dualstack.ap-south-2.amazonaws.com -s3-accesspoint.dualstack.ap-south-2.amazonaws.com -s3-website.dualstack.ap-south-2.amazonaws.com -s3.ap-south-2.amazonaws.com -s3-accesspoint.ap-south-2.amazonaws.com -s3-object-lambda.ap-south-2.amazonaws.com -s3-website.ap-south-2.amazonaws.com -s3.dualstack.ap-southeast-1.amazonaws.com -s3-accesspoint.dualstack.ap-southeast-1.amazonaws.com -s3-website.dualstack.ap-southeast-1.amazonaws.com -s3.ap-southeast-1.amazonaws.com -s3-accesspoint.ap-southeast-1.amazonaws.com -s3-object-lambda.ap-southeast-1.amazonaws.com -s3-website.ap-southeast-1.amazonaws.com -s3.dualstack.ap-southeast-2.amazonaws.com -s3-accesspoint.dualstack.ap-southeast-2.amazonaws.com -s3-website.dualstack.ap-southeast-2.amazonaws.com -s3.ap-southeast-2.amazonaws.com -s3-accesspoint.ap-southeast-2.amazonaws.com -s3-object-lambda.ap-southeast-2.amazonaws.com -s3-website.ap-southeast-2.amazonaws.com -s3.dualstack.ap-southeast-3.amazonaws.com -s3-accesspoint.dualstack.ap-southeast-3.amazonaws.com -s3-website.dualstack.ap-southeast-3.amazonaws.com -s3.ap-southeast-3.amazonaws.com -s3-accesspoint.ap-southeast-3.amazonaws.com -s3-object-lambda.ap-southeast-3.amazonaws.com -s3-website.ap-southeast-3.amazonaws.com -s3.dualstack.ap-southeast-4.amazonaws.com -s3-accesspoint.dualstack.ap-southeast-4.amazonaws.com -s3-website.dualstack.ap-southeast-4.amazonaws.com -s3.ap-southeast-4.amazonaws.com -s3-accesspoint.ap-southeast-4.amazonaws.com -s3-object-lambda.ap-southeast-4.amazonaws.com -s3-website.ap-southeast-4.amazonaws.com -s3.dualstack.ap-southeast-5.amazonaws.com -s3-accesspoint.dualstack.ap-southeast-5.amazonaws.com -s3-website.dualstack.ap-southeast-5.amazonaws.com -s3.ap-southeast-5.amazonaws.com -s3-accesspoint.ap-southeast-5.amazonaws.com -s3-deprecated.ap-southeast-5.amazonaws.com -s3-object-lambda.ap-southeast-5.amazonaws.com -s3-website.ap-southeast-5.amazonaws.com -s3.dualstack.ca-central-1.amazonaws.com -s3-accesspoint.dualstack.ca-central-1.amazonaws.com -s3-accesspoint-fips.dualstack.ca-central-1.amazonaws.com -s3-fips.dualstack.ca-central-1.amazonaws.com -s3-website.dualstack.ca-central-1.amazonaws.com -s3.ca-central-1.amazonaws.com -s3-accesspoint.ca-central-1.amazonaws.com -s3-accesspoint-fips.ca-central-1.amazonaws.com -s3-fips.ca-central-1.amazonaws.com -s3-object-lambda.ca-central-1.amazonaws.com -s3-website.ca-central-1.amazonaws.com -s3.dualstack.ca-west-1.amazonaws.com -s3-accesspoint.dualstack.ca-west-1.amazonaws.com -s3-accesspoint-fips.dualstack.ca-west-1.amazonaws.com -s3-fips.dualstack.ca-west-1.amazonaws.com -s3-website.dualstack.ca-west-1.amazonaws.com -s3.ca-west-1.amazonaws.com -s3-accesspoint.ca-west-1.amazonaws.com -s3-accesspoint-fips.ca-west-1.amazonaws.com -s3-fips.ca-west-1.amazonaws.com -s3-object-lambda.ca-west-1.amazonaws.com -s3-website.ca-west-1.amazonaws.com -s3.dualstack.eu-central-1.amazonaws.com -s3-accesspoint.dualstack.eu-central-1.amazonaws.com -s3-website.dualstack.eu-central-1.amazonaws.com -s3.eu-central-1.amazonaws.com -s3-accesspoint.eu-central-1.amazonaws.com -s3-object-lambda.eu-central-1.amazonaws.com -s3-website.eu-central-1.amazonaws.com -s3.dualstack.eu-central-2.amazonaws.com -s3-accesspoint.dualstack.eu-central-2.amazonaws.com -s3-website.dualstack.eu-central-2.amazonaws.com -s3.eu-central-2.amazonaws.com -s3-accesspoint.eu-central-2.amazonaws.com -s3-object-lambda.eu-central-2.amazonaws.com -s3-website.eu-central-2.amazonaws.com -s3.dualstack.eu-north-1.amazonaws.com -s3-accesspoint.dualstack.eu-north-1.amazonaws.com -s3.eu-north-1.amazonaws.com -s3-accesspoint.eu-north-1.amazonaws.com -s3-object-lambda.eu-north-1.amazonaws.com -s3-website.eu-north-1.amazonaws.com -s3.dualstack.eu-south-1.amazonaws.com -s3-accesspoint.dualstack.eu-south-1.amazonaws.com -s3-website.dualstack.eu-south-1.amazonaws.com -s3.eu-south-1.amazonaws.com -s3-accesspoint.eu-south-1.amazonaws.com -s3-object-lambda.eu-south-1.amazonaws.com -s3-website.eu-south-1.amazonaws.com -s3.dualstack.eu-south-2.amazonaws.com -s3-accesspoint.dualstack.eu-south-2.amazonaws.com -s3-website.dualstack.eu-south-2.amazonaws.com -s3.eu-south-2.amazonaws.com -s3-accesspoint.eu-south-2.amazonaws.com -s3-object-lambda.eu-south-2.amazonaws.com -s3-website.eu-south-2.amazonaws.com -s3.dualstack.eu-west-1.amazonaws.com -s3-accesspoint.dualstack.eu-west-1.amazonaws.com -s3-website.dualstack.eu-west-1.amazonaws.com -s3.eu-west-1.amazonaws.com -s3-accesspoint.eu-west-1.amazonaws.com -s3-deprecated.eu-west-1.amazonaws.com -s3-object-lambda.eu-west-1.amazonaws.com -s3-website.eu-west-1.amazonaws.com -s3.dualstack.eu-west-2.amazonaws.com -s3-accesspoint.dualstack.eu-west-2.amazonaws.com -s3.eu-west-2.amazonaws.com -s3-accesspoint.eu-west-2.amazonaws.com -s3-object-lambda.eu-west-2.amazonaws.com -s3-website.eu-west-2.amazonaws.com -s3.dualstack.eu-west-3.amazonaws.com -s3-accesspoint.dualstack.eu-west-3.amazonaws.com -s3-website.dualstack.eu-west-3.amazonaws.com -s3.eu-west-3.amazonaws.com -s3-accesspoint.eu-west-3.amazonaws.com -s3-object-lambda.eu-west-3.amazonaws.com -s3-website.eu-west-3.amazonaws.com -s3.dualstack.il-central-1.amazonaws.com -s3-accesspoint.dualstack.il-central-1.amazonaws.com -s3-website.dualstack.il-central-1.amazonaws.com -s3.il-central-1.amazonaws.com -s3-accesspoint.il-central-1.amazonaws.com -s3-object-lambda.il-central-1.amazonaws.com -s3-website.il-central-1.amazonaws.com -s3.dualstack.me-central-1.amazonaws.com -s3-accesspoint.dualstack.me-central-1.amazonaws.com -s3-website.dualstack.me-central-1.amazonaws.com -s3.me-central-1.amazonaws.com -s3-accesspoint.me-central-1.amazonaws.com -s3-object-lambda.me-central-1.amazonaws.com -s3-website.me-central-1.amazonaws.com -s3.dualstack.me-south-1.amazonaws.com -s3-accesspoint.dualstack.me-south-1.amazonaws.com -s3.me-south-1.amazonaws.com -s3-accesspoint.me-south-1.amazonaws.com -s3-object-lambda.me-south-1.amazonaws.com -s3-website.me-south-1.amazonaws.com -s3.amazonaws.com -s3-1.amazonaws.com -s3-ap-east-1.amazonaws.com -s3-ap-northeast-1.amazonaws.com -s3-ap-northeast-2.amazonaws.com -s3-ap-northeast-3.amazonaws.com -s3-ap-south-1.amazonaws.com -s3-ap-southeast-1.amazonaws.com -s3-ap-southeast-2.amazonaws.com -s3-ca-central-1.amazonaws.com -s3-eu-central-1.amazonaws.com -s3-eu-north-1.amazonaws.com -s3-eu-west-1.amazonaws.com -s3-eu-west-2.amazonaws.com -s3-eu-west-3.amazonaws.com -s3-external-1.amazonaws.com -s3-fips-us-gov-east-1.amazonaws.com -s3-fips-us-gov-west-1.amazonaws.com -mrap.accesspoint.s3-global.amazonaws.com -s3-me-south-1.amazonaws.com -s3-sa-east-1.amazonaws.com -s3-us-east-2.amazonaws.com -s3-us-gov-east-1.amazonaws.com -s3-us-gov-west-1.amazonaws.com -s3-us-west-1.amazonaws.com -s3-us-west-2.amazonaws.com -s3-website-ap-northeast-1.amazonaws.com -s3-website-ap-southeast-1.amazonaws.com -s3-website-ap-southeast-2.amazonaws.com -s3-website-eu-west-1.amazonaws.com -s3-website-sa-east-1.amazonaws.com -s3-website-us-east-1.amazonaws.com -s3-website-us-gov-west-1.amazonaws.com -s3-website-us-west-1.amazonaws.com -s3-website-us-west-2.amazonaws.com -s3.dualstack.sa-east-1.amazonaws.com -s3-accesspoint.dualstack.sa-east-1.amazonaws.com -s3-website.dualstack.sa-east-1.amazonaws.com -s3.sa-east-1.amazonaws.com -s3-accesspoint.sa-east-1.amazonaws.com -s3-object-lambda.sa-east-1.amazonaws.com -s3-website.sa-east-1.amazonaws.com -s3.dualstack.us-east-1.amazonaws.com -s3-accesspoint.dualstack.us-east-1.amazonaws.com -s3-accesspoint-fips.dualstack.us-east-1.amazonaws.com -s3-fips.dualstack.us-east-1.amazonaws.com -s3-website.dualstack.us-east-1.amazonaws.com -s3.us-east-1.amazonaws.com -s3-accesspoint.us-east-1.amazonaws.com -s3-accesspoint-fips.us-east-1.amazonaws.com -s3-deprecated.us-east-1.amazonaws.com -s3-fips.us-east-1.amazonaws.com -s3-object-lambda.us-east-1.amazonaws.com -s3-website.us-east-1.amazonaws.com -s3.dualstack.us-east-2.amazonaws.com -s3-accesspoint.dualstack.us-east-2.amazonaws.com -s3-accesspoint-fips.dualstack.us-east-2.amazonaws.com -s3-fips.dualstack.us-east-2.amazonaws.com -s3-website.dualstack.us-east-2.amazonaws.com -s3.us-east-2.amazonaws.com -s3-accesspoint.us-east-2.amazonaws.com -s3-accesspoint-fips.us-east-2.amazonaws.com -s3-deprecated.us-east-2.amazonaws.com -s3-fips.us-east-2.amazonaws.com -s3-object-lambda.us-east-2.amazonaws.com -s3-website.us-east-2.amazonaws.com -s3.dualstack.us-gov-east-1.amazonaws.com -s3-accesspoint.dualstack.us-gov-east-1.amazonaws.com -s3-accesspoint-fips.dualstack.us-gov-east-1.amazonaws.com -s3-fips.dualstack.us-gov-east-1.amazonaws.com -s3.us-gov-east-1.amazonaws.com -s3-accesspoint.us-gov-east-1.amazonaws.com -s3-accesspoint-fips.us-gov-east-1.amazonaws.com -s3-fips.us-gov-east-1.amazonaws.com -s3-object-lambda.us-gov-east-1.amazonaws.com -s3-website.us-gov-east-1.amazonaws.com -s3.dualstack.us-gov-west-1.amazonaws.com -s3-accesspoint.dualstack.us-gov-west-1.amazonaws.com -s3-accesspoint-fips.dualstack.us-gov-west-1.amazonaws.com -s3-fips.dualstack.us-gov-west-1.amazonaws.com -s3.us-gov-west-1.amazonaws.com -s3-accesspoint.us-gov-west-1.amazonaws.com -s3-accesspoint-fips.us-gov-west-1.amazonaws.com -s3-fips.us-gov-west-1.amazonaws.com -s3-object-lambda.us-gov-west-1.amazonaws.com -s3-website.us-gov-west-1.amazonaws.com -s3.dualstack.us-west-1.amazonaws.com -s3-accesspoint.dualstack.us-west-1.amazonaws.com -s3-accesspoint-fips.dualstack.us-west-1.amazonaws.com -s3-fips.dualstack.us-west-1.amazonaws.com -s3-website.dualstack.us-west-1.amazonaws.com -s3.us-west-1.amazonaws.com -s3-accesspoint.us-west-1.amazonaws.com -s3-accesspoint-fips.us-west-1.amazonaws.com -s3-fips.us-west-1.amazonaws.com -s3-object-lambda.us-west-1.amazonaws.com -s3-website.us-west-1.amazonaws.com -s3.dualstack.us-west-2.amazonaws.com -s3-accesspoint.dualstack.us-west-2.amazonaws.com -s3-accesspoint-fips.dualstack.us-west-2.amazonaws.com -s3-fips.dualstack.us-west-2.amazonaws.com -s3-website.dualstack.us-west-2.amazonaws.com -s3.us-west-2.amazonaws.com -s3-accesspoint.us-west-2.amazonaws.com -s3-accesspoint-fips.us-west-2.amazonaws.com -s3-deprecated.us-west-2.amazonaws.com -s3-fips.us-west-2.amazonaws.com -s3-object-lambda.us-west-2.amazonaws.com -s3-website.us-west-2.amazonaws.com - -// Amazon SageMaker Ground Truth -// Submitted by AWS Security -// Reference: 98dbfde4-7802-48c3-8751-b60f204e0d9c -labeling.ap-northeast-1.sagemaker.aws -labeling.ap-northeast-2.sagemaker.aws -labeling.ap-south-1.sagemaker.aws -labeling.ap-southeast-1.sagemaker.aws -labeling.ap-southeast-2.sagemaker.aws -labeling.ca-central-1.sagemaker.aws -labeling.eu-central-1.sagemaker.aws -labeling.eu-west-1.sagemaker.aws -labeling.eu-west-2.sagemaker.aws -labeling.us-east-1.sagemaker.aws -labeling.us-east-2.sagemaker.aws -labeling.us-west-2.sagemaker.aws - -// Amazon SageMaker Notebook Instances -// Submitted by AWS Security -// Reference: b5ea56df-669e-43cc-9537-14aa172f5dfc -notebook.af-south-1.sagemaker.aws -notebook.ap-east-1.sagemaker.aws -notebook.ap-northeast-1.sagemaker.aws -notebook.ap-northeast-2.sagemaker.aws -notebook.ap-northeast-3.sagemaker.aws -notebook.ap-south-1.sagemaker.aws -notebook.ap-south-2.sagemaker.aws -notebook.ap-southeast-1.sagemaker.aws -notebook.ap-southeast-2.sagemaker.aws -notebook.ap-southeast-3.sagemaker.aws -notebook.ap-southeast-4.sagemaker.aws -notebook.ca-central-1.sagemaker.aws -notebook-fips.ca-central-1.sagemaker.aws -notebook.ca-west-1.sagemaker.aws -notebook-fips.ca-west-1.sagemaker.aws -notebook.eu-central-1.sagemaker.aws -notebook.eu-central-2.sagemaker.aws -notebook.eu-north-1.sagemaker.aws -notebook.eu-south-1.sagemaker.aws -notebook.eu-south-2.sagemaker.aws -notebook.eu-west-1.sagemaker.aws -notebook.eu-west-2.sagemaker.aws -notebook.eu-west-3.sagemaker.aws -notebook.il-central-1.sagemaker.aws -notebook.me-central-1.sagemaker.aws -notebook.me-south-1.sagemaker.aws -notebook.sa-east-1.sagemaker.aws -notebook.us-east-1.sagemaker.aws -notebook-fips.us-east-1.sagemaker.aws -notebook.us-east-2.sagemaker.aws -notebook-fips.us-east-2.sagemaker.aws -notebook.us-gov-east-1.sagemaker.aws -notebook-fips.us-gov-east-1.sagemaker.aws -notebook.us-gov-west-1.sagemaker.aws -notebook-fips.us-gov-west-1.sagemaker.aws -notebook.us-west-1.sagemaker.aws -notebook-fips.us-west-1.sagemaker.aws -notebook.us-west-2.sagemaker.aws -notebook-fips.us-west-2.sagemaker.aws -notebook.cn-north-1.sagemaker.com.cn -notebook.cn-northwest-1.sagemaker.com.cn - -// Amazon SageMaker Studio -// Submitted by AWS Security -// Reference: 475f237e-ab88-4041-9f41-7cfccdf66aeb -studio.af-south-1.sagemaker.aws -studio.ap-east-1.sagemaker.aws -studio.ap-northeast-1.sagemaker.aws -studio.ap-northeast-2.sagemaker.aws -studio.ap-northeast-3.sagemaker.aws -studio.ap-south-1.sagemaker.aws -studio.ap-southeast-1.sagemaker.aws -studio.ap-southeast-2.sagemaker.aws -studio.ap-southeast-3.sagemaker.aws -studio.ca-central-1.sagemaker.aws -studio.eu-central-1.sagemaker.aws -studio.eu-central-2.sagemaker.aws -studio.eu-north-1.sagemaker.aws -studio.eu-south-1.sagemaker.aws -studio.eu-south-2.sagemaker.aws -studio.eu-west-1.sagemaker.aws -studio.eu-west-2.sagemaker.aws -studio.eu-west-3.sagemaker.aws -studio.il-central-1.sagemaker.aws -studio.me-central-1.sagemaker.aws -studio.me-south-1.sagemaker.aws -studio.sa-east-1.sagemaker.aws -studio.us-east-1.sagemaker.aws -studio.us-east-2.sagemaker.aws -studio.us-gov-east-1.sagemaker.aws -studio-fips.us-gov-east-1.sagemaker.aws -studio.us-gov-west-1.sagemaker.aws -studio-fips.us-gov-west-1.sagemaker.aws -studio.us-west-1.sagemaker.aws -studio.us-west-2.sagemaker.aws -studio.cn-north-1.sagemaker.com.cn -studio.cn-northwest-1.sagemaker.com.cn - -// Amazon SageMaker with MLflow -// Submited by: AWS Security -// Reference: c19f92b3-a82a-452d-8189-831b572eea7e -*.experiments.sagemaker.aws - -// Analytics on AWS -// Submitted by AWS Security -// Reference: 955f9f40-a495-4e73-ae85-67b77ac9cadd -analytics-gateway.ap-northeast-1.amazonaws.com -analytics-gateway.ap-northeast-2.amazonaws.com -analytics-gateway.ap-south-1.amazonaws.com -analytics-gateway.ap-southeast-1.amazonaws.com -analytics-gateway.ap-southeast-2.amazonaws.com -analytics-gateway.eu-central-1.amazonaws.com -analytics-gateway.eu-west-1.amazonaws.com -analytics-gateway.us-east-1.amazonaws.com -analytics-gateway.us-east-2.amazonaws.com -analytics-gateway.us-west-2.amazonaws.com - -// AWS Amplify -// Submitted by AWS Security -// Reference: c35bed18-6f4f-424f-9298-5756f2f7d72b -amplifyapp.com - -// AWS App Runner -// Submitted by AWS Security -// Reference: 6828c008-ba5d-442f-ade5-48da4e7c2316 -*.awsapprunner.com - -// AWS Cloud9 -// Submitted by: AWS Security -// Reference: 30717f72-4007-4f0f-8ed4-864c6f2efec9 -webview-assets.aws-cloud9.af-south-1.amazonaws.com -vfs.cloud9.af-south-1.amazonaws.com -webview-assets.cloud9.af-south-1.amazonaws.com -webview-assets.aws-cloud9.ap-east-1.amazonaws.com -vfs.cloud9.ap-east-1.amazonaws.com -webview-assets.cloud9.ap-east-1.amazonaws.com -webview-assets.aws-cloud9.ap-northeast-1.amazonaws.com -vfs.cloud9.ap-northeast-1.amazonaws.com -webview-assets.cloud9.ap-northeast-1.amazonaws.com -webview-assets.aws-cloud9.ap-northeast-2.amazonaws.com -vfs.cloud9.ap-northeast-2.amazonaws.com -webview-assets.cloud9.ap-northeast-2.amazonaws.com -webview-assets.aws-cloud9.ap-northeast-3.amazonaws.com -vfs.cloud9.ap-northeast-3.amazonaws.com -webview-assets.cloud9.ap-northeast-3.amazonaws.com -webview-assets.aws-cloud9.ap-south-1.amazonaws.com -vfs.cloud9.ap-south-1.amazonaws.com -webview-assets.cloud9.ap-south-1.amazonaws.com -webview-assets.aws-cloud9.ap-southeast-1.amazonaws.com -vfs.cloud9.ap-southeast-1.amazonaws.com -webview-assets.cloud9.ap-southeast-1.amazonaws.com -webview-assets.aws-cloud9.ap-southeast-2.amazonaws.com -vfs.cloud9.ap-southeast-2.amazonaws.com -webview-assets.cloud9.ap-southeast-2.amazonaws.com -webview-assets.aws-cloud9.ca-central-1.amazonaws.com -vfs.cloud9.ca-central-1.amazonaws.com -webview-assets.cloud9.ca-central-1.amazonaws.com -webview-assets.aws-cloud9.eu-central-1.amazonaws.com -vfs.cloud9.eu-central-1.amazonaws.com -webview-assets.cloud9.eu-central-1.amazonaws.com -webview-assets.aws-cloud9.eu-north-1.amazonaws.com -vfs.cloud9.eu-north-1.amazonaws.com -webview-assets.cloud9.eu-north-1.amazonaws.com -webview-assets.aws-cloud9.eu-south-1.amazonaws.com -vfs.cloud9.eu-south-1.amazonaws.com -webview-assets.cloud9.eu-south-1.amazonaws.com -webview-assets.aws-cloud9.eu-west-1.amazonaws.com -vfs.cloud9.eu-west-1.amazonaws.com -webview-assets.cloud9.eu-west-1.amazonaws.com -webview-assets.aws-cloud9.eu-west-2.amazonaws.com -vfs.cloud9.eu-west-2.amazonaws.com -webview-assets.cloud9.eu-west-2.amazonaws.com -webview-assets.aws-cloud9.eu-west-3.amazonaws.com -vfs.cloud9.eu-west-3.amazonaws.com -webview-assets.cloud9.eu-west-3.amazonaws.com -webview-assets.aws-cloud9.il-central-1.amazonaws.com -vfs.cloud9.il-central-1.amazonaws.com -webview-assets.aws-cloud9.me-south-1.amazonaws.com -vfs.cloud9.me-south-1.amazonaws.com -webview-assets.cloud9.me-south-1.amazonaws.com -webview-assets.aws-cloud9.sa-east-1.amazonaws.com -vfs.cloud9.sa-east-1.amazonaws.com -webview-assets.cloud9.sa-east-1.amazonaws.com -webview-assets.aws-cloud9.us-east-1.amazonaws.com -vfs.cloud9.us-east-1.amazonaws.com -webview-assets.cloud9.us-east-1.amazonaws.com -webview-assets.aws-cloud9.us-east-2.amazonaws.com -vfs.cloud9.us-east-2.amazonaws.com -webview-assets.cloud9.us-east-2.amazonaws.com -webview-assets.aws-cloud9.us-west-1.amazonaws.com -vfs.cloud9.us-west-1.amazonaws.com -webview-assets.cloud9.us-west-1.amazonaws.com -webview-assets.aws-cloud9.us-west-2.amazonaws.com -vfs.cloud9.us-west-2.amazonaws.com -webview-assets.cloud9.us-west-2.amazonaws.com - -// AWS Directory Service -// Submitted by AWS Security -// Reference: a13203e8-42dc-4045-a0d2-2ee67bed1068 -awsapps.com - -// AWS Elastic Beanstalk -// Submitted by AWS Security -// Reference: bb5a965c-dec3-4967-aa22-e306ad064797 -cn-north-1.eb.amazonaws.com.cn -cn-northwest-1.eb.amazonaws.com.cn -elasticbeanstalk.com -af-south-1.elasticbeanstalk.com -ap-east-1.elasticbeanstalk.com -ap-northeast-1.elasticbeanstalk.com -ap-northeast-2.elasticbeanstalk.com -ap-northeast-3.elasticbeanstalk.com -ap-south-1.elasticbeanstalk.com -ap-southeast-1.elasticbeanstalk.com -ap-southeast-2.elasticbeanstalk.com -ap-southeast-3.elasticbeanstalk.com -ca-central-1.elasticbeanstalk.com -eu-central-1.elasticbeanstalk.com -eu-north-1.elasticbeanstalk.com -eu-south-1.elasticbeanstalk.com -eu-west-1.elasticbeanstalk.com -eu-west-2.elasticbeanstalk.com -eu-west-3.elasticbeanstalk.com -il-central-1.elasticbeanstalk.com -me-south-1.elasticbeanstalk.com -sa-east-1.elasticbeanstalk.com -us-east-1.elasticbeanstalk.com -us-east-2.elasticbeanstalk.com -us-gov-east-1.elasticbeanstalk.com -us-gov-west-1.elasticbeanstalk.com -us-west-1.elasticbeanstalk.com -us-west-2.elasticbeanstalk.com - -// (AWS) Elastic Load Balancing -// Submitted by Luke Wells -// Reference: 12a3d528-1bac-4433-a359-a395867ffed2 -*.elb.amazonaws.com.cn -*.elb.amazonaws.com - -// AWS Global Accelerator -// Submitted by Daniel Massaguer -// Reference: d916759d-a08b-4241-b536-4db887383a6a -awsglobalaccelerator.com - -// AWS re:Post Private -// Submitted by AWS Security -// Reference: 83385945-225f-416e-9aa0-ad0632bfdcee -*.private.repost.aws - -// AWS Transfer Family web apps -// Submitted by AWS Security -// Reference: 57a658c4-8899-410c-aa24-5b01e4a178d2 -transfer-webapp.af-south-1.on.aws -transfer-webapp.ap-east-1.on.aws -transfer-webapp.ap-northeast-1.on.aws -transfer-webapp.ap-northeast-2.on.aws -transfer-webapp.ap-northeast-3.on.aws -transfer-webapp.ap-south-1.on.aws -transfer-webapp.ap-south-2.on.aws -transfer-webapp.ap-southeast-1.on.aws -transfer-webapp.ap-southeast-2.on.aws -transfer-webapp.ap-southeast-3.on.aws -transfer-webapp.ap-southeast-4.on.aws -transfer-webapp.ap-southeast-5.on.aws -transfer-webapp.ca-central-1.on.aws -transfer-webapp.ca-west-1.on.aws -transfer-webapp.eu-central-1.on.aws -transfer-webapp.eu-central-2.on.aws -transfer-webapp.eu-north-1.on.aws -transfer-webapp.eu-south-1.on.aws -transfer-webapp.eu-south-2.on.aws -transfer-webapp.eu-west-1.on.aws -transfer-webapp.eu-west-2.on.aws -transfer-webapp.eu-west-3.on.aws -transfer-webapp.il-central-1.on.aws -transfer-webapp.me-central-1.on.aws -transfer-webapp.me-south-1.on.aws -transfer-webapp.sa-east-1.on.aws -transfer-webapp.us-east-1.on.aws -transfer-webapp.us-east-2.on.aws -transfer-webapp.us-gov-east-1.on.aws -transfer-webapp-fips.us-gov-east-1.on.aws -transfer-webapp.us-gov-west-1.on.aws -transfer-webapp-fips.us-gov-west-1.on.aws -transfer-webapp.us-west-1.on.aws -transfer-webapp.us-west-2.on.aws -transfer-webapp.cn-north-1.on.amazonwebservices.com.cn -transfer-webapp.cn-northwest-1.on.amazonwebservices.com.cn - -// eero -// Submitted by Yue Kang -// Reference: 264afe70-f62c-4c02-8ab9-b5281ed24461 -eero.online -eero-stage.online - -// concludes Amazon - -// Apigee : https://apigee.com/ -// Submitted by Apigee Security Team -apigee.io - -// Apis Networks : https://apisnetworks.com -// Submitted by Matt Saladna -panel.dev - -// Apphud : https://apphud.com -// Submitted by Alexander Selivanov -siiites.com - -// Appspace : https://www.appspace.com -// Submitted by Appspace Security Team -appspacehosted.com -appspaceusercontent.com - -// Appudo UG (haftungsbeschränkt) : https://www.appudo.com -// Submitted by Alexander Hochbaum -appudo.net - -// Appwrite : https://appwrite.io -// Submitted by Steven Nguyen -appwrite.global -*.appwrite.run - -// Aptible : https://www.aptible.com/ -// Submitted by Thomas Orozco -on-aptible.com - -// Aquapal : https://aquapal.net/ -// Submitted by Aki Ueno -f5.si - -// ArvanCloud EdgeCompute -// Submitted by ArvanCloud CDN -arvanedge.ir - -// ASEINet : https://www.aseinet.com/ -// Submitted by Asei SEKIGUCHI -user.aseinet.ne.jp -gv.vc -d.gv.vc - -// Asociación Amigos de la Informática "Euskalamiga" : http://encounter.eus/ -// Submitted by Hector Martin -user.party.eus - -// Association potager.org : https://potager.org/ -// Submitted by Lunar -pimienta.org -poivron.org -potager.org -sweetpepper.org - -// ASUSTOR Inc. : http://www.asustor.com -// Submitted by Vincent Tseng -myasustor.com - -// Atlassian : https://atlassian.com -// Submitted by Sam Smyth -cdn.prod.atlassian-dev.net - -// Authentick UG (haftungsbeschränkt) : https://authentick.net -// Submitted by Lukas Reschke -translated.page - -// AVM : https://avm.de -// Submitted by Andreas Weise -myfritz.link -myfritz.net - -// AVStack Pte. Ltd. : https://avstack.io -// Submitted by Jasper Hugo -onavstack.net - -// AW AdvisorWebsites.com Software Inc : https://advisorwebsites.com -// Submitted by James Kennedy -*.awdev.ca -*.advisor.ws - -// AZ.pl sp. z.o.o : https://az.pl -// Submitted by Krzysztof Wolski -ecommerce-shop.pl - -// b-data GmbH : https://www.b-data.io -// Submitted by Olivier Benz -b-data.io - -// Balena : https://www.balena.io -// Submitted by Petros Angelatos -balena-devices.com - -// BASE, Inc. : https://binc.jp -// Submitted by Yuya NAGASAWA -base.ec -official.ec -buyshop.jp -fashionstore.jp -handcrafted.jp -kawaiishop.jp -supersale.jp -theshop.jp -shopselect.net -base.shop - -// BeagleBoard.org Foundation : https://beagleboard.org -// Submitted by Jason Kridner -beagleboard.io - -// Beget Ltd -// Submitted by Lev Nekrasov -*.beget.app - -// Besties : https://besties.house -// Submitted by Hazel Cora -pages.gay - -// BinaryLane : http://www.binarylane.com -// Submitted by Nathan O'Sullivan -bnr.la - -// Bitbucket : http://bitbucket.org -// Submitted by Andy Ortlieb -bitbucket.io - -// Blackbaud, Inc. : https://www.blackbaud.com -// Submitted by Paul Crowder -blackbaudcdn.net - -// Blatech : http://www.blatech.net -// Submitted by Luke Bratch -of.je - -// Block, Inc. : https://block.xyz -// Submitted by Jonathan Boice -square.site - -// Blue Bite, LLC : https://bluebite.com -// Submitted by Joshua Weiss -bluebite.io - -// Boomla : https://boomla.com -// Submitted by Tibor Halter -boomla.net - -// Boutir : https://www.boutir.com -// Submitted by Eric Ng Ka Ka -boutir.com - -// Boxfuse : https://boxfuse.com -// Submitted by Axel Fontaine -boxfuse.io - -// bplaced : https://www.bplaced.net/ -// Submitted by Miroslav Bozic -square7.ch -bplaced.com -bplaced.de -square7.de -bplaced.net -square7.net - -// Brave : https://brave.com -// Submitted by Andrea Brancaleoni -brave.app -*.s.brave.app -brave.io -*.s.brave.io - -// Brendly : https://brendly.rs -// Submitted by Dusan Radovanovic -shop.brendly.ba -shop.brendly.hr -shop.brendly.rs - -// BrowserSafetyMark -// Submitted by Dave Tharp -browsersafetymark.io - -// BRS Media : https://brsmedia.com/ -// Submitted by Gavin Brown -radio.am -radio.fm - -// Bubble : https://bubble.io/ -// Submitted by Merlin Zhao -cdn.bubble.io -bubbleapps.io - -// Bytemark Hosting : https://www.bytemark.co.uk -// Submitted by Paul Cammish -uk0.bigv.io -dh.bytemark.co.uk -vm.bytemark.co.uk - -// Caf.js Labs LLC : https://www.cafjs.com -// Submitted by Antonio Lain -cafjs.com - -// Canva Pty Ltd : https://canva.com/ -// Submitted by Joel Aquilina -canva-apps.cn -my.canvasite.cn -canva-apps.com -canva-hosted-embed.com -canvacode.com -rice-labs.com -canva.run -my.canva.site - -// Carrd : https://carrd.co -// Submitted by AJ -drr.ac -uwu.ai -carrd.co -crd.co -ju.mp - -// CDDO : https://www.gov.uk/guidance/get-an-api-domain-on-govuk -// Submitted by Jamie Tanna -api.gov.uk - -// CDN77.com : http://www.cdn77.com -// Submitted by Jan Krpes -cdn77-storage.com -rsc.contentproxy9.cz -r.cdn77.net -cdn77-ssl.net -c.cdn77.org -rsc.cdn77.org -ssl.origin.cdn77-secure.org - -// CentralNic : https://teaminternet.com/ -// Submitted by registry -za.bz -br.com -cn.com -de.com -eu.com -jpn.com -mex.com -ru.com -sa.com -uk.com -us.com -za.com -com.de -gb.net -hu.net -jp.net -se.net -uk.net -ae.org -com.se - -// Cityhost LLC : https://cityhost.ua -// Submitted by Maksym Rivtin -cx.ua - -// Civilized Discourse Construction Kit, Inc. : https://www.discourse.org/ -// Submitted by Rishabh Nambiar & Michael Brown -discourse.group -discourse.team - -// Clerk : https://www.clerk.dev -// Submitted by Colin Sidoti -clerk.app -clerkstage.app -*.lcl.dev -*.lclstage.dev -*.stg.dev -*.stgstage.dev - -// Clever Cloud : https://www.clever-cloud.com/ -// Submitted by Quentin Adam -cleverapps.cc -*.services.clever-cloud.com -cleverapps.io -cleverapps.tech - -// ClickRising : https://clickrising.com/ -// Submitted by Umut Gumeli -clickrising.net - -// Cloud DNS Ltd : http://www.cloudns.net -// Submitted by Aleksander Hristov & Boyan Peychev -cloudns.asia -cloudns.be -cloud-ip.biz -cloudns.biz -cloud-ip.cc -cloudns.cc -cloudns.ch -cloudns.cl -cloudns.club -abrdns.com -dnsabr.com -ip-ddns.com -cloudns.cx -cloudns.eu -cloudns.in -cloudns.info -ddns-ip.net -dns-cloud.net -dns-dynamic.net -cloudns.nz -cloudns.org -ip-dynamic.org -cloudns.ph -cloudns.pro -cloudns.pw -cloudns.us - -// Cloud66 : https://www.cloud66.com/ -// Submitted by Khash Sajadi -c66.me -cloud66.ws - -// CloudAccess.net : https://www.cloudaccess.net/ -// Submitted by Pawel Panek -jdevcloud.com -wpdevcloud.com -cloudaccess.host -freesite.host -cloudaccess.net - -// Cloudbees, Inc. : https://www.cloudbees.com/ -// Submitted by Mohideen Shajith -cloudbeesusercontent.io - -// Cloudera, Inc. : https://www.cloudera.com/ -// Submitted by Kedarnath Waikar -*.cloudera.site - -// Cloudflare, Inc. : https://www.cloudflare.com/ -// Submitted by Cloudflare Team -cf-ipfs.com -cloudflare-ipfs.com -trycloudflare.com -pages.dev -r2.dev -workers.dev -cloudflare.net -cdn.cloudflare.net -cdn.cloudflareanycast.net -cdn.cloudflarecn.net -cdn.cloudflareglobal.net - -// cloudscale.ch AG : https://www.cloudscale.ch/ -// Submitted by Gaudenz Steinlin -cust.cloudscale.ch -objects.lpg.cloudscale.ch -objects.rma.cloudscale.ch -lpg.objectstorage.ch -rma.objectstorage.ch - -// Clovyr : https://clovyr.io -// Submitted by Patrick Nielsen -wnext.app - -// CNPY : https://cnpy.gdn -// Submitted by Angelo Gladding -cnpy.gdn - -// Co & Co : https://co-co.nl/ -// Submitted by Govert Versluis -*.otap.co - -// co.ca : http://registry.co.ca/ -co.ca - -// co.com Registry, LLC : https://registry.co.com -// Submitted by Gavin Brown -co.com - -// Codeberg e. V. : https://codeberg.org -// Submitted by Moritz Marquardt -codeberg.page - -// CodeSandbox B.V. : https://codesandbox.io -// Submitted by Ives van Hoorne -csb.app -preview.csb.app - -// CoDNS B.V. -co.nl -co.no - -// Cognition AI, Inc. : https://cognition.ai -// Submitted by Philip Papurt -*.devinapps.com - -// Combell.com : https://www.combell.com -// Submitted by Thomas Wouters -webhosting.be -hosting-cluster.nl - -// Contentful GmbH : https://www.contentful.com -// Submitted by Contentful Developer Experience Team -ctfcloud.net - -// Convex : https://convex.dev/ -// Submitted by James Cowling -convex.app -convex.cloud -convex.site - -// Coordination Center for TLD RU and XN--P1AI : https://cctld.ru/en/domains/domens_ru/reserved/ -// Submitted by George Georgievsky -ac.ru -edu.ru -gov.ru -int.ru -mil.ru - -// COSIMO GmbH : http://www.cosimo.de -// Submitted by Rene Marticke -dyn.cosidns.de -dnsupdater.de -dynamisches-dns.de -internet-dns.de -l-o-g-i-n.de -dynamic-dns.info -feste-ip.net -knx-server.net -static-access.net - -// Craft Docs Ltd : https://www.craft.do/ -// Submitted by Zsombor Fuszenecker -craft.me - -// Craynic, s.r.o. : http://www.craynic.com/ -// Submitted by Ales Krajnik -realm.cz - -// Crisp IM SAS : https://crisp.chat/ -// Submitted by Baptiste Jamin -on.crisp.email - -// Cryptonomic : https://cryptonomic.net/ -// Submitted by Andrew Cady -*.cryptonomic.net - -// cyber_Folks S.A. : https://cyberfolks.pl -// Submitted by Bartlomiej Kida -cfolks.pl - -// cyon GmbH : https://www.cyon.ch/ -// Submitted by Dominic Luechinger -cyon.link -cyon.site - -// Dansk.net : http://www.dansk.net/ -// Submitted by Anani Voule -biz.dk -co.dk -firm.dk -reg.dk -store.dk - -// dappnode.io : https://dappnode.io/ -// Submitted by Abel Boldu / DAppNode Team -dyndns.dappnode.io - -// Dark, Inc. : https://darklang.com -// Submitted by Paul Biggar -builtwithdark.com -darklang.io - -// DataDetect, LLC. : https://datadetect.com -// Submitted by Andrew Banchich -demo.datadetect.com -instance.datadetect.com - -// Datawire, Inc : https://www.datawire.io -// Submitted by Richard Li -edgestack.me - -// Datto, Inc. : https://www.datto.com/ -// Submitted by Philipp Heckel -dattolocal.com -dattorelay.com -dattoweb.com -mydatto.com -dattolocal.net -mydatto.net - -// ddnss.de : https://www.ddnss.de/ -// Submitted by Robert Niedziela -ddnss.de -dyn.ddnss.de -dyndns.ddnss.de -dyn-ip24.de -dyndns1.de -home-webserver.de -dyn.home-webserver.de -myhome-server.de -ddnss.org - -// Debian : https://www.debian.org/ -// Submitted by Peter Palfrader / Debian Sysadmin Team -debian.net - -// Definima : http://www.definima.com/ -// Submitted by Maxence Bitterli -definima.io -definima.net - -// Deno Land Inc : https://deno.com/ -// Submitted by Luca Casonato -deno.dev -deno-staging.dev -deno.net - -// deSEC : https://desec.io/ -// Submitted by Peter Thomassen -dedyn.io - -// Deta : https://www.deta.sh/ -// Submitted by Aavash Shrestha -deta.app -deta.dev - -// Dfinity Foundation: https://dfinity.org/ -// Submitted by Dfinity Team -icp0.io -*.raw.icp0.io -icp1.io -*.raw.icp1.io -*.icp.net -caffeine.site -caffeine.xyz - -// dhosting.pl Sp. z o.o. : https://dhosting.pl/ -// Submitted by Michal Kokoszkiewicz -dfirma.pl -dkonto.pl -you2.pl - -// DigitalOcean App Platform : https://www.digitalocean.com/products/app-platform/ -// Submitted by Braxton Huggins -ondigitalocean.app - -// DigitalOcean Spaces : https://www.digitalocean.com/products/spaces/ -// Submitted by Robin H. Johnson -*.digitaloceanspaces.com - -// DigitalPlat : https://www.digitalplat.org/ -// Submitted by Edward Hsing -qzz.io -us.kg -xx.kg -dpdns.org - -// Discord Inc : https://discord.com -// Submitted by Sahn Lam -discordsays.com -discordsez.com - -// DNS Africa Ltd : https://dns.business -// Submitted by Calvin Browne -jozi.biz - -// DNShome : https://www.dnshome.de/ -// Submitted by Norbert Auler -dnshome.de - -// DotArai : https://www.dotarai.com/ -// Submitted by Atsadawat Netcharadsang -online.th -shop.th - -// DrayTek Corp. : https://www.draytek.com/ -// Submitted by Paul Fang -drayddns.com - -// DreamCommerce : https://shoper.pl/ -// Submitted by Konrad Kotarba -shoparena.pl - -// DreamHost : http://www.dreamhost.com/ -// Submitted by Andrew Farmer -dreamhosters.com - -// Dreamyoungs, Inc. : https://durumis.com -// Submitted by Infra Team -durumis.com - -// DuckDNS : http://www.duckdns.org/ -// Submitted by Richard Harper -duckdns.org - -// dy.fi : http://dy.fi/ -// Submitted by Heikki Hannikainen -dy.fi -tunk.org - -// DynDNS.com : http://www.dyndns.com/services/dns/dyndns/ -dyndns.biz -for-better.biz -for-more.biz -for-some.biz -for-the.biz -selfip.biz -webhop.biz -ftpaccess.cc -game-server.cc -myphotos.cc -scrapping.cc -blogdns.com -cechire.com -dnsalias.com -dnsdojo.com -doesntexist.com -dontexist.com -doomdns.com -dyn-o-saur.com -dynalias.com -dyndns-at-home.com -dyndns-at-work.com -dyndns-blog.com -dyndns-free.com -dyndns-home.com -dyndns-ip.com -dyndns-mail.com -dyndns-office.com -dyndns-pics.com -dyndns-remote.com -dyndns-server.com -dyndns-web.com -dyndns-wiki.com -dyndns-work.com -est-a-la-maison.com -est-a-la-masion.com -est-le-patron.com -est-mon-blogueur.com -from-ak.com -from-al.com -from-ar.com -from-ca.com -from-ct.com -from-dc.com -from-de.com -from-fl.com -from-ga.com -from-hi.com -from-ia.com -from-id.com -from-il.com -from-in.com -from-ks.com -from-ky.com -from-ma.com -from-md.com -from-mi.com -from-mn.com -from-mo.com -from-ms.com -from-mt.com -from-nc.com -from-nd.com -from-ne.com -from-nh.com -from-nj.com -from-nm.com -from-nv.com -from-oh.com -from-ok.com -from-or.com -from-pa.com -from-pr.com -from-ri.com -from-sc.com -from-sd.com -from-tn.com -from-tx.com -from-ut.com -from-va.com -from-vt.com -from-wa.com -from-wi.com -from-wv.com -from-wy.com -getmyip.com -gotdns.com -hobby-site.com -homelinux.com -homeunix.com -iamallama.com -is-a-anarchist.com -is-a-blogger.com -is-a-bookkeeper.com -is-a-bulls-fan.com -is-a-caterer.com -is-a-chef.com -is-a-conservative.com -is-a-cpa.com -is-a-cubicle-slave.com -is-a-democrat.com -is-a-designer.com -is-a-doctor.com -is-a-financialadvisor.com -is-a-geek.com -is-a-green.com -is-a-guru.com -is-a-hard-worker.com -is-a-hunter.com -is-a-landscaper.com -is-a-lawyer.com -is-a-liberal.com -is-a-libertarian.com -is-a-llama.com -is-a-musician.com -is-a-nascarfan.com -is-a-nurse.com -is-a-painter.com -is-a-personaltrainer.com -is-a-photographer.com -is-a-player.com -is-a-republican.com -is-a-rockstar.com -is-a-socialist.com -is-a-student.com -is-a-teacher.com -is-a-techie.com -is-a-therapist.com -is-an-accountant.com -is-an-actor.com -is-an-actress.com -is-an-anarchist.com -is-an-artist.com -is-an-engineer.com -is-an-entertainer.com -is-certified.com -is-gone.com -is-into-anime.com -is-into-cars.com -is-into-cartoons.com -is-into-games.com -is-leet.com -is-not-certified.com -is-slick.com -is-uberleet.com -is-with-theband.com -isa-geek.com -isa-hockeynut.com -issmarterthanyou.com -likes-pie.com -likescandy.com -neat-url.com -saves-the-whales.com -selfip.com -sells-for-less.com -sells-for-u.com -servebbs.com -simple-url.com -space-to-rent.com -teaches-yoga.com -writesthisblog.com -ath.cx -fuettertdasnetz.de -isteingeek.de -istmein.de -lebtimnetz.de -leitungsen.de -traeumtgerade.de -barrel-of-knowledge.info -barrell-of-knowledge.info -dyndns.info -for-our.info -groks-the.info -groks-this.info -here-for-more.info -knowsitall.info -selfip.info -webhop.info -forgot.her.name -forgot.his.name -at-band-camp.net -blogdns.net -broke-it.net -buyshouses.net -dnsalias.net -dnsdojo.net -does-it.net -dontexist.net -dynalias.net -dynathome.net -endofinternet.net -from-az.net -from-co.net -from-la.net -from-ny.net -gets-it.net -ham-radio-op.net -homeftp.net -homeip.net -homelinux.net -homeunix.net -in-the-band.net -is-a-chef.net -is-a-geek.net -isa-geek.net -kicks-ass.net -office-on-the.net -podzone.net -scrapper-site.net -selfip.net -sells-it.net -servebbs.net -serveftp.net -thruhere.net -webhop.net -merseine.nu -mine.nu -shacknet.nu -blogdns.org -blogsite.org -boldlygoingnowhere.org -dnsalias.org -dnsdojo.org -doesntexist.org -dontexist.org -doomdns.org -dvrdns.org -dynalias.org -dyndns.org -go.dyndns.org -home.dyndns.org -endofinternet.org -endoftheinternet.org -from-me.org -game-host.org -gotdns.org -hobby-site.org -homedns.org -homeftp.org -homelinux.org -homeunix.org -is-a-bruinsfan.org -is-a-candidate.org -is-a-celticsfan.org -is-a-chef.org -is-a-geek.org -is-a-knight.org -is-a-linux-user.org -is-a-patsfan.org -is-a-soxfan.org -is-found.org -is-lost.org -is-saved.org -is-very-bad.org -is-very-evil.org -is-very-good.org -is-very-nice.org -is-very-sweet.org -isa-geek.org -kicks-ass.org -misconfused.org -podzone.org -readmyblog.org -selfip.org -sellsyourhome.org -servebbs.org -serveftp.org -servegame.org -stuff-4-sale.org -webhop.org -better-than.tv -dyndns.tv -on-the-web.tv -worse-than.tv -is-by.us -land-4-sale.us -stuff-4-sale.us -dyndns.ws -mypets.ws - -// Dynu.com : https://www.dynu.com/ -// Submitted by Sue Ye -ddnsfree.com -ddnsgeek.com -giize.com -gleeze.com -kozow.com -loseyourip.com -ooguy.com -theworkpc.com -casacam.net -dynu.net -accesscam.org -camdvr.org -freeddns.org -mywire.org -webredirect.org -myddns.rocks - -// dynv6 : https://dynv6.com -// Submitted by Dominik Menke -dynv6.net - -// E4YOU spol. s.r.o. : https://e4you.cz/ -// Submitted by Vladimir Dudr -e4.cz - -// Easypanel : https://easypanel.io -// Submitted by Andrei Canta -easypanel.app -easypanel.host - -// EasyWP : https://www.easywp.com -// Submitted by -*.ewp.live - -// eDirect Corp. : https://hosting.url.com.tw/ -// Submitted by C.S. chang -twmail.cc -twmail.net -twmail.org -mymailer.com.tw -url.tw - -// Electromagnetic Field : https://www.emfcamp.org -// Submitted by -at.emf.camp - -// Elefunc, Inc. : https://elefunc.com -// Submitted by Cetin Sert -rt.ht - -// Elementor : Elementor Ltd. -// Submitted by Anton Barkan -elementor.cloud -elementor.cool - -// En root‽ : https://en-root.org -// Submitted by Emmanuel Raviart -en-root.fr - -// Enalean SAS : https://www.enalean.com -// Submitted by Enalean Security Team -mytuleap.com -tuleap-partners.com - -// Encoretivity AB : https://encore.cloud -// Submitted by André Eriksson -encr.app -frontend.encr.app -encoreapi.com -lp.dev -api.lp.dev -objects.lp.dev - -// encoway GmbH : https://www.encoway.de -// Submitted by Marcel Daus -eu.encoway.cloud - -// EU.org : https://eu.org/ -// Submitted by Pierre Beyssac -eu.org -al.eu.org -asso.eu.org -at.eu.org -au.eu.org -be.eu.org -bg.eu.org -ca.eu.org -cd.eu.org -ch.eu.org -cn.eu.org -cy.eu.org -cz.eu.org -de.eu.org -dk.eu.org -edu.eu.org -ee.eu.org -es.eu.org -fi.eu.org -fr.eu.org -gr.eu.org -hr.eu.org -hu.eu.org -ie.eu.org -il.eu.org -in.eu.org -int.eu.org -is.eu.org -it.eu.org -jp.eu.org -kr.eu.org -lt.eu.org -lu.eu.org -lv.eu.org -me.eu.org -mk.eu.org -mt.eu.org -my.eu.org -net.eu.org -ng.eu.org -nl.eu.org -no.eu.org -nz.eu.org -pl.eu.org -pt.eu.org -ro.eu.org -ru.eu.org -se.eu.org -si.eu.org -sk.eu.org -tr.eu.org -uk.eu.org -us.eu.org - -// Eurobyte : https://eurobyte.ru -// Submitted by Evgeniy Subbotin -eurodir.ru - -// Evennode : http://www.evennode.com/ -// Submitted by Michal Kralik -eu-1.evennode.com -eu-2.evennode.com -eu-3.evennode.com -eu-4.evennode.com -us-1.evennode.com -us-2.evennode.com -us-3.evennode.com -us-4.evennode.com - -// Evervault : https://evervault.com -// Submitted by Hannah Neary -relay.evervault.app -relay.evervault.dev - -// Expo : https://expo.dev/ -// Submitted by James Ide -expo.app -staging.expo.app - -// Fabrica Technologies, Inc. : https://www.fabrica.dev/ -// Submitted by Eric Jiang -onfabrica.com - -// FAITID : https://faitid.org/ -// Submitted by Maxim Alzoba -// https://www.flexireg.net/stat_info -ru.net -adygeya.ru -bashkiria.ru -bir.ru -cbg.ru -com.ru -dagestan.ru -grozny.ru -kalmykia.ru -kustanai.ru -marine.ru -mordovia.ru -msk.ru -mytis.ru -nalchik.ru -nov.ru -pyatigorsk.ru -spb.ru -vladikavkaz.ru -vladimir.ru -abkhazia.su -adygeya.su -aktyubinsk.su -arkhangelsk.su -armenia.su -ashgabad.su -azerbaijan.su -balashov.su -bashkiria.su -bryansk.su -bukhara.su -chimkent.su -dagestan.su -east-kazakhstan.su -exnet.su -georgia.su -grozny.su -ivanovo.su -jambyl.su -kalmykia.su -kaluga.su -karacol.su -karaganda.su -karelia.su -khakassia.su -krasnodar.su -kurgan.su -kustanai.su -lenug.su -mangyshlak.su -mordovia.su -msk.su -murmansk.su -nalchik.su -navoi.su -north-kazakhstan.su -nov.su -obninsk.su -penza.su -pokrovsk.su -sochi.su -spb.su -tashkent.su -termez.su -togliatti.su -troitsk.su -tselinograd.su -tula.su -tuva.su -vladikavkaz.su -vladimir.su -vologda.su - -// Fancy Bits, LLC : http://getchannels.com -// Submitted by Aman Gupta -channelsdvr.net -u.channelsdvr.net - -// Fastly Inc. : http://www.fastly.com/ -// Submitted by Fastly Security -edgecompute.app -fastly-edge.com -fastly-terrarium.com -freetls.fastly.net -map.fastly.net -a.prod.fastly.net -global.prod.fastly.net -a.ssl.fastly.net -b.ssl.fastly.net -global.ssl.fastly.net -fastlylb.net -map.fastlylb.net - -// Fastmail : https://www.fastmail.com/ -// Submitted by Marc Bradshaw -*.user.fm - -// FASTVPS EESTI OU : https://fastvps.ru/ -// Submitted by Likhachev Vasiliy -fastvps-server.com -fastvps.host -myfast.host -fastvps.site -myfast.space - -// FearWorks Media Ltd. : https://fearworksmedia.co.uk -// Submitted by Keith Fairley -conn.uk -copro.uk -hosp.uk - -// Fedora : https://fedoraproject.org/ -// Submitted by Patrick Uiterwijk -fedorainfracloud.org -fedorapeople.org -cloud.fedoraproject.org -app.os.fedoraproject.org -app.os.stg.fedoraproject.org - -// Fermax : https://fermax.com/ -// Submitted by Koen Van Isterdael -mydobiss.com - -// FH Muenster : https://www.fh-muenster.de -// Submitted by Robin Naundorf -fh-muenster.io - -// Figma : https://www.figma.com -// Submitted by Nick Frost -figma.site -preview.site - -// Filegear Inc. : https://www.filegear.com -// Submitted by Jason Zhu -filegear.me - -// Firebase, Inc. -// Submitted by Chris Raynor -firebaseapp.com - -// FlashDrive : https://flashdrive.io -// Submitted by Eric Chan -fldrv.com - -// Fleek Labs Inc : https://fleek.xyz -// Submitted by Parsa Ghadimi -on-fleek.app - -// FlutterFlow : https://flutterflow.io -// Submitted by Anton Emelyanov -flutterflow.app - -// fly.io : https://fly.io -// Submitted by Kurt Mackey -fly.dev -shw.io -edgeapp.net - -// Forgerock : https://www.forgerock.com -// Submitted by Roderick Parr -forgeblocks.com -id.forgerock.io - -// FoundryLabs, Inc : https://e2b.dev/ -// Submitted by Jiri Sveceny -e2b.app - -// Framer : https://www.framer.com -// Submitted by Koen Rouwhorst -framer.ai -framer.app -framercanvas.com -framer.media -framer.photos -framer.website -framer.wiki - -// Frederik Braun : https://frederik-braun.com -// Submitted by Frederik Braun -*.0e.vc - -// Freebox : http://www.freebox.fr -// Submitted by Romain Fliedel -freebox-os.com -freeboxos.com -fbx-os.fr -fbxos.fr -freebox-os.fr -freeboxos.fr - -// freedesktop.org : https://www.freedesktop.org -// Submitted by Daniel Stone -freedesktop.org - -// freemyip.com : https://freemyip.com -// Submitted by Cadence -freemyip.com - -// Frusky MEDIA&PR : https://www.frusky.de -// Submitted by Victor Pupynin -*.frusky.de - -// FunkFeuer - Verein zur Förderung freier Netze : https://www.funkfeuer.at -// Submitted by Daniel A. Maierhofer -wien.funkfeuer.at - -// Future Versatile Group. : https://www.fvg-on.net/ -// T.Kabu -daemon.asia -dix.asia -mydns.bz -0am.jp -0g0.jp -0j0.jp -0t0.jp -mydns.jp -pgw.jp -wjg.jp -keyword-on.net -live-on.net -server-on.net -mydns.tw -mydns.vc - -// Futureweb GmbH : https://www.futureweb.at -// Submitted by Andreas Schnederle-Wagner -*.futurecms.at -*.ex.futurecms.at -*.in.futurecms.at -futurehosting.at -futuremailing.at -*.ex.ortsinfo.at -*.kunden.ortsinfo.at -*.statics.cloud - -// GCom Internet : https://www.gcom.net.au -// Submitted by Leo Julius -aliases121.com - -// GDS : https://www.gov.uk/service-manual/technology/managing-domain-names -// Submitted by Stephen Ford -campaign.gov.uk -service.gov.uk -independent-commission.uk -independent-inquest.uk -independent-inquiry.uk -independent-panel.uk -independent-review.uk -public-inquiry.uk -royal-commission.uk - -// Gehirn Inc. : https://www.gehirn.co.jp/ -// Submitted by Kohei YOSHIDA -gehirn.ne.jp -usercontent.jp - -// Gentlent, Inc. : https://www.gentlent.com -// Submitted by Tom Klein -gentapps.com -gentlentapis.com -cdn-edges.net - -// GignoSystemJapan : http://gsj.bz -// Submitted by GignoSystemJapan -gsj.bz - -// GitHub, Inc. -// Submitted by Patrick Toomey -github.app -githubusercontent.com -githubpreview.dev -github.io - -// GitLab, Inc. : https://about.gitlab.com/ -// Submitted by Alex Hanselka -gitlab.io - -// Gitplac.si : https://gitplac.si -// Submitted by Aljaž Starc -gitapp.si -gitpage.si - -// Global NOG Alliance : https://nogalliance.org/ -// Submitted by Sander Steffann -nog.community - -// Globe Hosting SRL : https://www.globehosting.com/ -// Submitted by Gavin Brown -co.ro -shop.ro - -// GMO Pepabo, Inc. : https://pepabo.com/ -// Submitted by Hosting Div -lolipop.io -angry.jp -babyblue.jp -babymilk.jp -backdrop.jp -bambina.jp -bitter.jp -blush.jp -boo.jp -boy.jp -boyfriend.jp -but.jp -candypop.jp -capoo.jp -catfood.jp -cheap.jp -chicappa.jp -chillout.jp -chips.jp -chowder.jp -chu.jp -ciao.jp -cocotte.jp -coolblog.jp -cranky.jp -cutegirl.jp -daa.jp -deca.jp -deci.jp -digick.jp -egoism.jp -fakefur.jp -fem.jp -flier.jp -floppy.jp -fool.jp -frenchkiss.jp -girlfriend.jp -girly.jp -gloomy.jp -gonna.jp -greater.jp -hacca.jp -heavy.jp -her.jp -hiho.jp -hippy.jp -holy.jp -hungry.jp -icurus.jp -itigo.jp -jellybean.jp -kikirara.jp -kill.jp -kilo.jp -kuron.jp -littlestar.jp -lolipopmc.jp -lolitapunk.jp -lomo.jp -lovepop.jp -lovesick.jp -main.jp -mods.jp -mond.jp -mongolian.jp -moo.jp -namaste.jp -nikita.jp -nobushi.jp -noor.jp -oops.jp -parallel.jp -parasite.jp -pecori.jp -peewee.jp -penne.jp -pepper.jp -perma.jp -pigboat.jp -pinoko.jp -punyu.jp -pupu.jp -pussycat.jp -pya.jp -raindrop.jp -readymade.jp -sadist.jp -schoolbus.jp -secret.jp -staba.jp -stripper.jp -sub.jp -sunnyday.jp -thick.jp -tonkotsu.jp -under.jp -upper.jp -velvet.jp -verse.jp -versus.jp -vivian.jp -watson.jp -weblike.jp -whitesnow.jp -zombie.jp -heteml.net - -// GoDaddy Registry : https://registry.godaddy -// Submitted by Rohan Durrant -graphic.design - -// GoIP DNS Services : http://www.goip.de -// Submitted by Christian Poulter -goip.de - -// Google, Inc. -// Submitted by Shannon McCabe -*.hosted.app -*.run.app -*.mtls.run.app -web.app -*.0emm.com -appspot.com -*.r.appspot.com -blogspot.com -codespot.com -googleapis.com -googlecode.com -pagespeedmobilizer.com -withgoogle.com -withyoutube.com -*.gateway.dev -cloud.goog -translate.goog -*.usercontent.goog -cloudfunctions.net - -// Goupile : https://goupile.fr -// Submitted by Niels Martignene -goupile.fr - -// GOV.UK Pay : https://www.payments.service.gov.uk/ -// Submitted by Richard Baker -pymnt.uk - -// GOV.UK Platform as a Service : https://www.cloud.service.gov.uk/ -// Submitted by Tom Whitwell -cloudapps.digital -london.cloudapps.digital - -// Government of the Netherlands : https://www.government.nl -// Submitted by -gov.nl - -// Grafana Labs : https://grafana.com/ -// Submitted by Platform Engineering -grafana-dev.net - -// GrayJay Web Solutions Inc. : https://grayjaysports.ca -// Submitted by Matt Yamkowy -grayjayleagues.com - -// GünstigBestellen : https://günstigbestellen.de -// Submitted by Furkan Akkoc -günstigbestellen.de -günstigliefern.de - -// Hackclub Nest : https://hackclub.app -// Submitted by Cyteon -hackclub.app - -// Häkkinen.fi : https://www.häkkinen.fi/ -// Submitted by Eero Häkkinen -häkkinen.fi - -// Hashbang : https://hashbang.sh -hashbang.sh - -// Hasura : https://hasura.io -// Submitted by Shahidh K Muhammed -hasura.app -hasura-app.io - -// Hatena Co., Ltd. : https://hatena.co.jp -// Submitted by Masato Nakamura -hatenablog.com -hatenadiary.com -hateblo.jp -hatenablog.jp -hatenadiary.jp -hatenadiary.org - -// Heilbronn University of Applied Sciences - Faculty Informatics (GitLab Pages) : https://www.hs-heilbronn.de -// Submitted by Richard Zowalla -pages.it.hs-heilbronn.de -pages-research.it.hs-heilbronn.de - -// HeiyuSpace : https://lazycat.cloud -// Submitted by Xia Bin -heiyu.space - -// Helio Networks : https://heliohost.org -// Submitted by Ben Frede -helioho.st -heliohost.us - -// Hepforge : https://www.hepforge.org -// Submitted by David Grellscheid -hepforge.org - -// Heroku : https://www.heroku.com/ -// Submitted by Shumon Huque -herokuapp.com - -// Heyflow : https://www.heyflow.com -// Submitted by Mirko Nitschke -heyflow.page -heyflow.site - -// Hibernating Rhinos -// Submitted by Oren Eini -ravendb.cloud -ravendb.community -development.run -ravendb.run - -// HiDNS : https://www.hidoha.net -// Submitted by ifeng -hidns.co -hidns.vip - -// home.pl S.A. : https://home.pl -// Submitted by Krzysztof Wolski -homesklep.pl - -// Homebase : https://homebase.id/ -// Submitted by Jason Babo -*.kin.one -*.id.pub -*.kin.pub - -// Hoplix : https://www.hoplix.com -// Submitted by Danilo De Franco -hoplix.shop - -// HOSTBIP REGISTRY : https://www.hostbip.com/ -// Submitted by Atanunu Igbunuroghene -orx.biz -biz.ng -co.biz.ng -dl.biz.ng -go.biz.ng -lg.biz.ng -on.biz.ng -col.ng -firm.ng -gen.ng -ltd.ng -ngo.ng -plc.ng - -// HostyHosting : https://hostyhosting.com -hostyhosting.io - -// Hugging Face : https://huggingface.co -// Submitted by Eliott Coyac -hf.space -static.hf.space - -// Hypernode B.V. : https://www.hypernode.com/ -// Submitted by Cipriano Groenendal -hypernode.io - -// I-O DATA DEVICE, INC. : http://www.iodata.com/ -// Submitted by Yuji Minagawa -iobb.net - -// i-registry s.r.o. : http://www.i-registry.cz/ -// Submitted by Martin Semrad -co.cz - -// Ici la Lune : http://www.icilalune.com/ -// Submitted by Simon Morvan -*.moonscale.io -moonscale.net - -// iDOT Services Limited : http://www.domain.gr.com -// Submitted by Gavin Brown -gr.com - -// iki.fi -// Submitted by Hannu Aronsson -iki.fi - -// iliad italia : https://www.iliad.it -// Submitted by Marios Makassikis -ibxos.it -iliadboxos.it - -// Incsub, LLC : https://incsub.com/ -// Submitted by Aaron Edwards -smushcdn.com -wphostedmail.com -wpmucdn.com -tempurl.host -wpmudev.host - -// Individual Network Berlin e.V. : https://www.in-berlin.de/ -// Submitted by Christian Seitz -dyn-berlin.de -in-berlin.de -in-brb.de -in-butter.de -in-dsl.de -in-vpn.de -in-dsl.net -in-vpn.net -in-dsl.org -in-vpn.org - -// Inferno Communications : https://inferno.co.uk -// Submitted by Connor McFarlane -oninferno.net - -// info.at : http://www.info.at/ -biz.at -info.at - -// info.cx : http://info.cx -// Submitted by June Slater -info.cx - -// Interlegis : http://www.interlegis.leg.br -// Submitted by Gabriel Ferreira -ac.leg.br -al.leg.br -am.leg.br -ap.leg.br -ba.leg.br -ce.leg.br -df.leg.br -es.leg.br -go.leg.br -ma.leg.br -mg.leg.br -ms.leg.br -mt.leg.br -pa.leg.br -pb.leg.br -pe.leg.br -pi.leg.br -pr.leg.br -rj.leg.br -rn.leg.br -ro.leg.br -rr.leg.br -rs.leg.br -sc.leg.br -se.leg.br -sp.leg.br -to.leg.br - -// intermetrics GmbH : https://pixolino.com/ -// Submitted by Wolfgang Schwarz -pixolino.com - -// Internet-Pro, LLP : https://netangels.ru/ -// Submitted by Vasiliy Sheredeko -na4u.ru - -// Inventor Services : https://inventor.gg/ -// Submitted by Inventor Team -botdash.app -botdash.dev -botdash.gg -botdash.net -botda.sh -botdash.xyz - -// IONOS SE : https://www.ionos.com/ -// IONOS Group SE : https://www.ionos-group.com/ -// Submitted by Henrik Willert -apps-1and1.com -live-website.com -webspace-host.com -apps-1and1.net -websitebuilder.online -app-ionos.space - -// iopsys software solutions AB : https://iopsys.eu/ -// Submitted by Roman Azarenko -iopsys.se - -// IPFS Project : https://ipfs.tech/ -// Submitted by Interplanetary Shipyard -*.inbrowser.dev -*.dweb.link -*.inbrowser.link - -// IPiFony Systems, Inc. : https://www.ipifony.com/ -// Submitted by Matthew Hardeman -ipifony.net - -// ir.md : https://nic.ir.md -// Submitted by Ali Soizi -ir.md - -// is-a-good.dev : https://is-a-good.dev -// Submitted by William Harrison -is-a-good.dev - -// IServ GmbH : https://iserv.de -// Submitted by Kim Brodowski -iservschule.de -mein-iserv.de -schuldock.de -schulplattform.de -schulserver.de -test-iserv.de -iserv.dev -iserv.host - -// Jelastic, Inc. : https://jelastic.com/ -// Submitted by Ihor Kolodyuk -mel.cloudlets.com.au -cloud.interhostsolutions.be -alp1.ae.flow.ch -appengine.flow.ch -es-1.axarnet.cloud -diadem.cloud -vip.jelastic.cloud -jele.cloud -it1.eur.aruba.jenv-aruba.cloud -it1.jenv-aruba.cloud -keliweb.cloud -cs.keliweb.cloud -oxa.cloud -tn.oxa.cloud -uk.oxa.cloud -primetel.cloud -uk.primetel.cloud -ca.reclaim.cloud -uk.reclaim.cloud -us.reclaim.cloud -ch.trendhosting.cloud -de.trendhosting.cloud -jele.club -dopaas.com -paas.hosted-by-previder.com -rag-cloud.hosteur.com -rag-cloud-ch.hosteur.com -jcloud.ik-server.com -jcloud-ver-jpc.ik-server.com -demo.jelastic.com -paas.massivegrid.com -jed.wafaicloud.com -ryd.wafaicloud.com -j.scaleforce.com.cy -jelastic.dogado.eu -fi.cloudplatform.fi -demo.datacenter.fi -paas.datacenter.fi -jele.host -mircloud.host -paas.beebyte.io -sekd1.beebyteapp.io -jele.io -jc.neen.it -jcloud.kz -cloudjiffy.net -fra1-de.cloudjiffy.net -west1-us.cloudjiffy.net -jls-sto1.elastx.net -jls-sto2.elastx.net -jls-sto3.elastx.net -fr-1.paas.massivegrid.net -lon-1.paas.massivegrid.net -lon-2.paas.massivegrid.net -ny-1.paas.massivegrid.net -ny-2.paas.massivegrid.net -sg-1.paas.massivegrid.net -jelastic.saveincloud.net -nordeste-idc.saveincloud.net -j.scaleforce.net -sdscloud.pl -unicloud.pl -mircloud.ru -enscaled.sg -jele.site -jelastic.team -orangecloud.tn -j.layershift.co.uk -phx.enscaled.us -mircloud.us - -// Jino : https://www.jino.ru -// Submitted by Sergey Ulyashin -myjino.ru -*.hosting.myjino.ru -*.landing.myjino.ru -*.spectrum.myjino.ru -*.vps.myjino.ru - -// Jotelulu S.L. : https://jotelulu.com -// Submitted by Daniel Fariña -jote.cloud -jotelulu.cloud -eu1-plenit.com -la1-plenit.com -us1-plenit.com - -// JouwWeb B.V. : https://www.jouwweb.nl -// Submitted by Camilo Sperberg -webadorsite.com -jouwweb.site - -// Joyent : https://www.joyent.com/ -// Submitted by Brian Bennett -*.cns.joyent.com -*.triton.zone - -// JS.ORG : http://dns.js.org -// Submitted by Stefan Keim -js.org - -// KaasHosting : http://www.kaashosting.nl/ -// Submitted by Wouter Bakker -kaas.gg -khplay.nl - -// Kapsi : https://kapsi.fi -// Submitted by Tomi Juntunen -kapsi.fi - -// Katholieke Universiteit Leuven : https://www.kuleuven.be -// Submitted by Abuse KU Leuven -ezproxy.kuleuven.be -kuleuven.cloud - -// Keyweb AG : https://www.keyweb.de -// Submitted by Martin Dannehl -keymachine.de - -// KingHost : https://king.host -// Submitted by Felipe Keller Braz -kinghost.net -uni5.net - -// KnightPoint Systems, LLC : http://www.knightpoint.com/ -// Submitted by Roy Keene -knightpoint.systems - -// KoobinEvent, SL : https://www.koobin.com -// Submitted by Iván Oliva -koobin.events - -// Krellian Ltd. : https://krellian.com -// Submitted by Ben Francis -webthings.io -krellian.net - -// KUROKU LTD : https://kuroku.ltd/ -// Submitted by DisposaBoy -oya.to - -// KV GmbH : https://www.nic.co.de -// Submitted by KV GmbH -// Abuse reports to -co.de - -// Laravel Holdings, Inc. : https://laravel.com -// Submitted by André Valentin & James Brooks -laravel.cloud -on-forge.com -on-vapor.com - -// LCube - Professional hosting e.K. : https://www.lcube-webhosting.de -// Submitted by Lars Laehn -git-repos.de -lcube-server.de -svn-repos.de - -// Leadpages : https://www.leadpages.net -// Submitted by Greg Dallavalle -leadpages.co -lpages.co -lpusercontent.com - -// Leapcell : https://leapcell.io/ -// Submitted by Leapcell Team -leapcell.app -leapcell.dev -leapcell.online - -// Liara : https://liara.ir -// Submitted by Amirhossein Badinloo -liara.run -iran.liara.run - -// libp2p project : https://libp2p.io -// Submitted by Interplanetary Shipyard -libp2p.direct - -// Libre IT Ltd : https://libre.nz -// Submitted by Tomas Maggio -runcontainers.dev - -// Lifetime Hosting : https://Lifetime.Hosting/ -// Submitted by Mike Fillator -co.business -co.education -co.events -co.financial -co.network -co.place -co.technology - -// linkyard ldt : https://www.linkyard.ch/ -// Submitted by Mario Siegenthaler -linkyard-cloud.ch -linkyard.cloud - -// Linode : https://linode.com -// Submitted by -members.linode.com -*.nodebalancer.linode.com -*.linodeobjects.com -ip.linodeusercontent.com - -// LiquidNet Ltd : http://www.liquidnetlimited.com/ -// Submitted by Victor Velchev -we.bs - -// Listen53 : https://www.l53.net -// Submitted by Gerry Keh -filegear-sg.me -ggff.net - -// Localcert : https://localcert.dev -// Submitted by Lann Martin -*.user.localcert.dev - -// Localtonet : https://localtonet.com/ -// Submitted by Burak Isleyici -localtonet.com -*.localto.net - -// Lodz University of Technology LODMAN regional domains : https://www.man.lodz.pl/dns -// Submitted by Piotr Wilk -lodz.pl -pabianice.pl -plock.pl -sieradz.pl -skierniewice.pl -zgierz.pl - -// Log'in Line : https://www.loginline.com/ -// Submitted by Rémi Mach -loginline.app -loginline.dev -loginline.io -loginline.services -loginline.site - -// Lõhmus Family, The : https://lohmus.me/ -// Submitted by Heiki Lõhmus -lohmus.me - -// Lovable : https://lovable.dev -// Submitted by Fabian Hedin -lovable.app -lovableproject.com -lovable.run -lovable.sh - -// LubMAN UMCS Sp. z o.o : https://lubman.pl/ -// Submitted by Ireneusz Maliszewski -krasnik.pl -leczna.pl -lubartow.pl -lublin.pl -poniatowa.pl -swidnik.pl - -// Lug.org.uk : https://lug.org.uk -// Submitted by Jon Spriggs -glug.org.uk -lug.org.uk -lugs.org.uk - -// Lukanet Ltd : https://lukanet.com -// Submitted by Anton Avramov -barsy.bg -barsy.club -barsycenter.com -barsyonline.com -barsy.de -barsy.dev -barsy.eu -barsy.gr -barsy.in -barsy.info -barsy.io -barsy.me -barsy.menu -barsyonline.menu -barsy.mobi -barsy.net -barsy.online -barsy.org -barsy.pro -barsy.pub -barsy.ro -barsy.rs -barsy.shop -barsyonline.shop -barsy.site -barsy.store -barsy.support -barsy.uk -barsy.co.uk -barsyonline.co.uk - -// Lutra : https://lutra.ai -// Submitted by Joshua Newman -*.lutrausercontent.com - -// Luyani Inc. : https://luyani.com/ -// Submitted by Umut Gumeli -luyani.app -luyani.net - -// Magento Commerce -// Submitted by Damien Tournoud -*.magentosite.cloud - -// Mail.Ru Group : https://hb.cldmail.ru -// Submitted by Ilya Zaretskiy -hb.cldmail.ru - -// MathWorks : https://www.mathworks.com/ -// Submitted by Emily Reed -matlab.cloud -modelscape.com -mwcloudnonprod.com -polyspace.com - -// May First - People Link : https://mayfirst.org/ -// Submitted by Jamie McClelland -mayfirst.info -mayfirst.org - -// Maze Play : https://www.mazeplay.com -// Submitted by Adam Humpherys -mazeplay.com - -// McHost : https://mchost.ru -// Submitted by Evgeniy Subbotin -mcdir.me -mcdir.ru -vps.mcdir.ru -mcpre.ru - -// Mediatech : https://mediatech.by -// Submitted by Evgeniy Kozhuhovskiy -mediatech.by -mediatech.dev - -// Medicom Health : https://medicomhealth.com -// Submitted by Michael Olson -hra.health - -// MedusaJS, Inc : https://medusajs.com/ -// Submitted by Stevche Radevski -medusajs.app - -// Memset hosting : https://www.memset.com -// Submitted by Tom Whitwell -miniserver.com -memset.net - -// Messerli Informatik AG : https://www.messerli.ch/ -// Submitted by Ruben Schmidmeister -messerli.app - -// Meta Platforms, Inc. : https://meta.com/ -// Submitted by Jacob Cordero -atmeta.com -apps.fbsbx.com - -// MetaCentrum, CESNET z.s.p.o. : https://www.metacentrum.cz/en/ -// Submitted by Zdeněk Šustr and Radim Janča -*.cloud.metacentrum.cz -custom.metacentrum.cz -flt.cloud.muni.cz -usr.cloud.muni.cz - -// Meteor Development Group : https://www.meteor.com/hosting -// Submitted by Pierre Carrier -meteorapp.com -eu.meteorapp.com - -// Michau Enterprises Limited : http://www.co.pl/ -co.pl - -// Microsoft Corporation : http://microsoft.com -// Submitted by Public Suffix List Admin -// Managed by Corporate Domains -// Microsoft Azure : https://home.azure -*.azurecontainer.io -azure-api.net -azure-mobile.net -azureedge.net -azurefd.net -azurestaticapps.net -1.azurestaticapps.net -2.azurestaticapps.net -3.azurestaticapps.net -4.azurestaticapps.net -5.azurestaticapps.net -6.azurestaticapps.net -7.azurestaticapps.net -centralus.azurestaticapps.net -eastasia.azurestaticapps.net -eastus2.azurestaticapps.net -westeurope.azurestaticapps.net -westus2.azurestaticapps.net -azurewebsites.net -cloudapp.net -trafficmanager.net -blob.core.windows.net -servicebus.windows.net - -// MikroTik : https://mikrotik.com -// Submitted by MikroTik SysAdmin Team -routingthecloud.com -sn.mynetname.net -routingthecloud.net -routingthecloud.org - -// Million Software, Inc : https://million.dev/ -// Submitted by Rayhan Noufal Arayilakath -same-app.com -same-preview.com - -// minion.systems : http://minion.systems -// Submitted by Robert Böttinger -csx.cc - -// Mittwald CM Service GmbH & Co. KG : https://mittwald.de -// Submitted by Marco Rieger -mydbserver.com -webspaceconfig.de -mittwald.info -mittwaldserver.info -typo3server.info -project.space - -// MODX Systems LLC : https://modx.com -// Submitted by Elizabeth Southwell -modx.dev - -// Mozilla Foundation : https://mozilla.org/ -// Submitted by glob -bmoattachments.org - -// MSK-IX : https://www.msk-ix.ru/ -// Submitted by Khannanov Roman -net.ru -org.ru -pp.ru - -// Mythic Beasts : https://www.mythic-beasts.com -// Submitted by Paul Cammish -hostedpi.com -caracal.mythic-beasts.com -customer.mythic-beasts.com -fentiger.mythic-beasts.com -lynx.mythic-beasts.com -ocelot.mythic-beasts.com -oncilla.mythic-beasts.com -onza.mythic-beasts.com -sphinx.mythic-beasts.com -vs.mythic-beasts.com -x.mythic-beasts.com -yali.mythic-beasts.com -cust.retrosnub.co.uk - -// Nabu Casa : https://www.nabucasa.com -// Submitted by Paulus Schoutsen -ui.nabu.casa - -// Net at Work Gmbh : https://www.netatwork.de -// Submitted by Jan Jaeschke -cloud.nospamproxy.com -o365.cloud.nospamproxy.com - -// Net libre : https://www.netlib.re -// Submitted by Philippe PITTOLI -netlib.re - -// Netfy Domains : https://netfy.domains -// Submitted by Suranga Ranasinghe -netfy.app - -// Netlify : https://www.netlify.com -// Submitted by Jessica Parsons -netlify.app - -// Neustar Inc. -// Submitted by Trung Tran -4u.com - -// NFSN, Inc. : https://www.NearlyFreeSpeech.NET/ -// Submitted by Jeff Wheelhouse -nfshost.com - -// NFT.Storage : https://nft.storage/ -// Submitted by Vasco Santos or -ipfs.nftstorage.link - -// NGO.US Registry : https://nic.ngo.us -// Submitted by Alstra Solutions Ltd. Networking Team -ngo.us - -// ngrok : https://ngrok.com/ -// Submitted by Alan Shreve -ngrok.app -ngrok-free.app -ngrok.dev -ngrok-free.dev -ngrok.io -ap.ngrok.io -au.ngrok.io -eu.ngrok.io -in.ngrok.io -jp.ngrok.io -sa.ngrok.io -us.ngrok.io -ngrok.pizza -ngrok.pro - -// Nicolaus Copernicus University in Torun - MSK TORMAN : https://www.man.torun.pl -torun.pl - -// Nimbus Hosting Ltd. : https://www.nimbushosting.co.uk/ -// Submitted by Nicholas Ford -nh-serv.co.uk -nimsite.uk - -// No-IP.com : https://noip.com/ -// Submitted by Deven Reza -mmafan.biz -myftp.biz -no-ip.biz -no-ip.ca -fantasyleague.cc -gotdns.ch -3utilities.com -blogsyte.com -ciscofreak.com -damnserver.com -ddnsking.com -ditchyourip.com -dnsiskinky.com -dynns.com -geekgalaxy.com -health-carereform.com -homesecuritymac.com -homesecuritypc.com -myactivedirectory.com -mysecuritycamera.com -myvnc.com -net-freaks.com -onthewifi.com -point2this.com -quicksytes.com -securitytactics.com -servebeer.com -servecounterstrike.com -serveexchange.com -serveftp.com -servegame.com -servehalflife.com -servehttp.com -servehumour.com -serveirc.com -servemp3.com -servep2p.com -servepics.com -servequake.com -servesarcasm.com -stufftoread.com -unusualperson.com -workisboring.com -dvrcam.info -ilovecollege.info -no-ip.info -brasilia.me -ddns.me -dnsfor.me -hopto.me -loginto.me -noip.me -webhop.me -bounceme.net -ddns.net -eating-organic.net -mydissent.net -myeffect.net -mymediapc.net -mypsx.net -mysecuritycamera.net -nhlfan.net -no-ip.net -pgafan.net -privatizehealthinsurance.net -redirectme.net -serveblog.net -serveminecraft.net -sytes.net -cable-modem.org -collegefan.org -couchpotatofries.org -hopto.org -mlbfan.org -myftp.org -mysecuritycamera.org -nflfan.org -no-ip.org -read-books.org -ufcfan.org -zapto.org -no-ip.co.uk -golffan.us -noip.us -pointto.us - -// NodeArt : https://nodeart.io -// Submitted by Konstantin Nosov -stage.nodeart.io - -// Noop : https://noop.app -// Submitted by Nathaniel Schweinberg -*.developer.app -noop.app - -// Northflank Ltd. : https://northflank.com/ -// Submitted by Marco Suter -*.northflank.app -*.build.run -*.code.run -*.database.run -*.migration.run - -// Noticeable : https://noticeable.io -// Submitted by Laurent Pellegrino -noticeable.news - -// Notion Labs, Inc : https://www.notion.so/ -// Submitted by Jess Yao -notion.site - -// Now-DNS : https://now-dns.com -// Submitted by Steve Russell -dnsking.ch -mypi.co -myiphost.com -forumz.info -soundcast.me -tcp4.me -dnsup.net -hicam.net -now-dns.net -ownip.net -vpndns.net -dynserv.org -now-dns.org -x443.pw -ntdll.top -freeddns.us - -// nsupdate.info : https://www.nsupdate.info/ -// Submitted by Thomas Waldmann -nsupdate.info -nerdpol.ovh - -// NYC.mn : https://dot.nyc.mn/ -// Submitted by NYC.mn Subdomain Service -nyc.mn - -// O3O.Foundation : https://o3o.foundation/ -// Submitted by the prvcy.page Registry Team -prvcy.page - -// Obl.ong : https://obl.ong -// Submitted by Reese Armstrong -obl.ong - -// Observable, Inc. : https://observablehq.com -// Submitted by Mike Bostock -observablehq.cloud -static.observableusercontent.com - -// OMG.LOL : https://omg.lol -// Submitted by Adam Newbold -omg.lol - -// Omnibond Systems, LLC. : https://www.omnibond.com -// Submitted by Cole Estep -cloudycluster.net - -// OmniWe Limited : https://omniwe.com -// Submitted by Vicary Archangel -omniwe.site - -// One.com : https://www.one.com/ -// Submitted by Jacob Bunk Nielsen -123webseite.at -123website.be -simplesite.com.br -123website.ch -simplesite.com -123webseite.de -123hjemmeside.dk -123miweb.es -123kotisivu.fi -123siteweb.fr -simplesite.gr -123homepage.it -123website.lu -123website.nl -123hjemmeside.no -service.one -simplesite.pl -123paginaweb.pt -123minsida.se - -// ONID : https://get.onid.ca -// Submitted by ONID Engineering Team -onid.ca - -// Open Domains : https://open-domains.net -// Submitted by William Harrison -is-a-fullstack.dev -is-cool.dev -is-not-a.dev -localplayer.dev -is-local.org - -// Open Social : https://www.getopensocial.com/ -// Submitted by Alexander Varwijk -opensocial.site - -// OpenAI : https://openai.com -// Submitted by Thomas Shadwell -*.oaiusercontent.com - -// OpenCraft GmbH : http://opencraft.com/ -// Submitted by Sven Marnach -opencraft.hosting - -// OpenHost : https://registry.openhost.uk -// Submitted by OpenHost Registry Team -16-b.it -32-b.it -64-b.it - -// OpenResearch GmbH : https://openresearch.com/ -// Submitted by Philipp Schmid -orsites.com - -// Opera Software, A.S.A. -// Submitted by Yngve Pettersen -operaunite.com - -// Oracle Dyn : https://cloud.oracle.com/home https://dyn.com/dns/ -// Submitted by Gregory Drake -// Note: This is intended to also include customer-oci.com due to wildcards implicitly including the current label -*.customer-oci.com -*.oci.customer-oci.com -*.ocp.customer-oci.com -*.ocs.customer-oci.com -*.oraclecloudapps.com -*.oraclegovcloudapps.com -*.oraclegovcloudapps.uk - -// Orange : https://www.orange.com -// Submitted by Alexandre Linte -tech.orange - -// OsSav Technology Ltd. : https://ossav.com/ -// Submitted by OsSav Technology Ltd. -// https://nic.can.re -can.re - -// Oursky Limited : https://authgear.com/ -// Submitted by Authgear Team & Skygear Developer -authgear-staging.com -authgearapps.com -skygearapp.com - -// OutSystems -// Submitted by Duarte Santos -outsystemscloud.com - -// OVHcloud : https://ovhcloud.com -// Submitted by Vincent Cassé -*.hosting.ovh.net -*.webpaas.ovh.net - -// OwnProvider GmbH : http://www.ownprovider.com -// Submitted by Jan Moennich -ownprovider.com -own.pm - -// OwO : https://whats-th.is/ -// Submitted by Dean Sheather -*.owo.codes - -// OX : http://www.ox.rs -// Submitted by Adam Grand -ox.rs - -// oy.lc -// Submitted by Charly Coste -oy.lc - -// Pagefog : https://pagefog.com/ -// Submitted by Derek Myers -pgfog.com - -// PageXL : https://pagexl.com -// Submitted by Yann Guichard -pagexl.com - -// Pantheon Systems, Inc. : https://pantheon.io/ -// Submitted by Gary Dylina -gotpantheon.com -pantheonsite.io - -// Paywhirl, Inc : https://paywhirl.com/ -// Submitted by Daniel Netzer -*.paywhirl.com - -// pcarrier.ca Software Inc : https://pcarrier.ca/ -// Submitted by Pierre Carrier -*.xmit.co -xmit.dev -madethis.site -srv.us -gh.srv.us -gl.srv.us - -// PE Ulyanov Kirill Sergeevich : https://airy.host -// Submitted by Kirill Ulyanov -lk3.ru - -// Peplink | Pepwave : http://peplink.com/ -// Submitted by Steve Leung -mypep.link - -// Perspecta : https://perspecta.com/ -// Submitted by Kenneth Van Alstyne -perspecta.cloud - -// Plain : https://www.plain.com/ -// Submitted by Jesús Hernández -support.site - -// Planet-Work : https://www.planet-work.com/ -// Submitted by Frédéric VANNIÈRE -on-web.fr - -// Platform.sh : https://platform.sh -// Submitted by Nikola Kotur -*.upsun.app -upsunapp.com -ent.platform.sh -eu.platform.sh -us.platform.sh -*.platformsh.site -*.tst.site - -// Platter : https://platter.dev -// Submitted by Patrick Flor -platter-app.dev -platterp.us - -// Pley AB : https://www.pley.com/ -// Submitted by Henning Pohl -pley.games - -// Porter : https://porter.run/ -// Submitted by Rudraksh MK -onporter.run - -// Positive Codes Technology Company : http://co.bn/faq.html -// Submitted by Zulfais -co.bn - -// Postman, Inc : https://postman.com -// Submitted by Rahul Dhawan -postman-echo.com -pstmn.io -mock.pstmn.io -httpbin.org - -// prequalifyme.today : https://prequalifyme.today -// Submitted by DeepakTiwari deepak@ivylead.io -prequalifyme.today - -// prgmr.com : https://prgmr.com/ -// Submitted by Sarah Newman -xen.prgmr.com - -// priv.at : http://www.nic.priv.at/ -// Submitted by registry -priv.at - -// PROJECT ELIV : https://eliv.kr/ -// Submitted by PROJECT ELIV Domain Team -c01.kr -eliv-cdn.kr -eliv-dns.kr -mmv.kr -vki.kr - -// project-study : https://project-study.com -// Submitted by yumenewa -dev.project-study.com - -// Protonet GmbH : http://protonet.io -// Submitted by Martin Meier -protonet.io - -// PT Ekossistim Indo Digital : https://e.id -// Submitted by Eid Team -e.id - -// Publication Presse Communication SARL : https://ppcom.fr -// Submitted by Yaacov Akiba Slama -chirurgiens-dentistes-en-france.fr -byen.site - -// pubtls.org : https://www.pubtls.org -// Submitted by Kor Nielsen -pubtls.org - -// PythonAnywhere LLP : https://www.pythonanywhere.com -// Submitted by Giles Thomas -pythonanywhere.com -eu.pythonanywhere.com - -// QA2 -// Submitted by Daniel Dent : https://www.danieldent.com/ -qa2.com - -// QCX -// Submitted by Cassandra Beelen -qcx.io -*.sys.qcx.io - -// QNAP System Inc : https://www.qnap.com -// Submitted by Nick Chang -myqnapcloud.cn -alpha-myqnapcloud.com -dev-myqnapcloud.com -mycloudnas.com -mynascloud.com -myqnapcloud.com - -// QOTO, Org. -// Submitted by Jeffrey Phillips Freeman -qoto.io - -// Qualifio : https://qualifio.com/ -// Submitted by Xavier De Cock -qualifioapp.com - -// Quality Unit : https://qualityunit.com -// Submitted by Vasyl Tsalko -ladesk.com - -// Qualy : https://qualyhq.com -// Submitted by Raphael Arias -*.qualyhqpartner.com -*.qualyhqportal.com - -// QuickBackend : https://www.quickbackend.com -// Submitted by Dani Biro -qbuser.com - -// Quip : https://quip.com -// Submitted by Patrick Linehan -*.quipelements.com - -// Qutheory LLC : http://qutheory.io -// Submitted by Jonas Schwartz -vapor.cloud -vaporcloud.io - -// Rackmaze LLC : https://www.rackmaze.com -// Submitted by Kirill Pertsev -rackmaze.com -rackmaze.net - -// Rad Web Hosting : https://radwebhosting.com -// Submitted by Scott Claeys -cloudsite.builders -myradweb.net -servername.us - -// Radix FZC : http://domains.in.net -// Submitted by Gavin Brown -web.in -in.net - -// Raidboxes GmbH : https://raidboxes.de -// Submitted by Auke Tembrink -myrdbx.io -site.rb-hosting.io - -// Railway Corporation : https://railway.com -// Submitted by Phineas Walton -up.railway.app - -// Rancher Labs, Inc : https://rancher.com -// Submitted by Vincent Fiduccia -*.on-rancher.cloud -*.on-k3s.io -*.on-rio.io - -// RavPage : https://www.ravpage.co.il -// Submitted by Roni Horowitz -ravpage.co.il - -// Read The Docs, Inc : https://www.readthedocs.org -// Submitted by David Fischer -readthedocs-hosted.com -readthedocs.io - -// Red Hat, Inc. OpenShift : https://openshift.redhat.com/ -// Submitted by Tim Kramer -rhcloud.com - -// Redgate Software : https://red-gate.com -// Submitted by Andrew Farries -instances.spawn.cc - -// Render : https://render.com -// Submitted by Anurag Goel -onrender.com -app.render.com - -// Repl.it : https://repl.it -// Submitted by Lincoln Bergeson -replit.app -id.replit.app -firewalledreplit.co -id.firewalledreplit.co -repl.co -id.repl.co -replit.dev -archer.replit.dev -bones.replit.dev -canary.replit.dev -global.replit.dev -hacker.replit.dev -id.replit.dev -janeway.replit.dev -kim.replit.dev -kira.replit.dev -kirk.replit.dev -odo.replit.dev -paris.replit.dev -picard.replit.dev -pike.replit.dev -prerelease.replit.dev -reed.replit.dev -riker.replit.dev -sisko.replit.dev -spock.replit.dev -staging.replit.dev -sulu.replit.dev -tarpit.replit.dev -teams.replit.dev -tucker.replit.dev -wesley.replit.dev -worf.replit.dev -repl.run - -// Resin.io : https://resin.io -// Submitted by Tim Perry -resindevice.io -devices.resinstaging.io - -// RethinkDB : https://www.rethinkdb.com/ -// Submitted by Chris Kastorff -hzc.io - -// Rico Developments Limited : https://adimo.co -// Submitted by Colin Brown -adimo.co.uk - -// Riseup Networks : https://riseup.net -// Submitted by Micah Anderson -itcouldbewor.se - -// Roar Domains LLC : https://roar.basketball/ -// Submitted by Gavin Brown -aus.basketball -nz.basketball - -// ROBOT PAYMENT INC. : https://www.robotpayment.co.jp/ -// Submitted by Kentaro Takamori -subsc-pay.com -subsc-pay.net - -// Rochester Institute of Technology : http://www.rit.edu/ -// Submitted by Jennifer Herting -git-pages.rit.edu - -// Rocky Enterprise Software Foundation : https://resf.org -// Submitted by Neil Hanlon -rocky.page - -// Ruhr University Bochum : https://www.ruhr-uni-bochum.de/ -// Submitted by Andreas Jobs -rub.de -ruhr-uni-bochum.de -io.noc.ruhr-uni-bochum.de - -// Rusnames Limited : http://rusnames.ru/ -// Submitted by Sergey Zotov -биз.рус -ком.рус -крым.рус -мир.рус -мск.рус -орг.рус -самара.рус -сочи.рус -спб.рус -я.рус - -// Russian Academy of Sciences -// Submitted by Tech Support -ras.ru - -// Sakura Frp : https://www.natfrp.com -// Submitted by Bobo Liu -nyat.app - -// SAKURA Internet Inc. : https://www.sakura.ad.jp/ -// Submitted by Internet Service Department -180r.com -dojin.com -sakuratan.com -sakuraweb.com -x0.com -2-d.jp -bona.jp -crap.jp -daynight.jp -eek.jp -flop.jp -halfmoon.jp -jeez.jp -matrix.jp -mimoza.jp -ivory.ne.jp -mail-box.ne.jp -mints.ne.jp -mokuren.ne.jp -opal.ne.jp -sakura.ne.jp -sumomo.ne.jp -topaz.ne.jp -netgamers.jp -nyanta.jp -o0o0.jp -rdy.jp -rgr.jp -rulez.jp -s3.isk01.sakurastorage.jp -s3.isk02.sakurastorage.jp -saloon.jp -sblo.jp -skr.jp -tank.jp -uh-oh.jp -undo.jp -rs.webaccel.jp -user.webaccel.jp -websozai.jp -xii.jp -squares.net -jpn.org -kirara.st -x0.to -from.tv -sakura.tv - -// Salesforce.com, Inc. : https://salesforce.com/ -// Submitted by Salesforce Public Suffix List Team -*.builder.code.com -*.dev-builder.code.com -*.stg-builder.code.com -*.001.test.code-builder-stg.platform.salesforce.com -*.d.crm.dev -*.w.crm.dev -*.wa.crm.dev -*.wb.crm.dev -*.wc.crm.dev -*.wd.crm.dev -*.we.crm.dev -*.wf.crm.dev - -// Sandstorm Development Group, Inc. : https://sandcats.io/ -// Submitted by Asheesh Laroia -sandcats.io - -// SBE network solutions GmbH : https://www.sbe.de/ -// Submitted by Norman Meilick -logoip.com -logoip.de - -// Scaleway : https://www.scaleway.com/ -// Submitted by Scaleway PSL Maintainer -fr-par-1.baremetal.scw.cloud -fr-par-2.baremetal.scw.cloud -nl-ams-1.baremetal.scw.cloud -cockpit.fr-par.scw.cloud -ddl.fr-par.scw.cloud -dtwh.fr-par.scw.cloud -fnc.fr-par.scw.cloud -functions.fnc.fr-par.scw.cloud -ifr.fr-par.scw.cloud -k8s.fr-par.scw.cloud -nodes.k8s.fr-par.scw.cloud -kafk.fr-par.scw.cloud -mgdb.fr-par.scw.cloud -rdb.fr-par.scw.cloud -s3.fr-par.scw.cloud -s3-website.fr-par.scw.cloud -scbl.fr-par.scw.cloud -whm.fr-par.scw.cloud -priv.instances.scw.cloud -pub.instances.scw.cloud -k8s.scw.cloud -cockpit.nl-ams.scw.cloud -ddl.nl-ams.scw.cloud -dtwh.nl-ams.scw.cloud -ifr.nl-ams.scw.cloud -k8s.nl-ams.scw.cloud -nodes.k8s.nl-ams.scw.cloud -kafk.nl-ams.scw.cloud -mgdb.nl-ams.scw.cloud -rdb.nl-ams.scw.cloud -s3.nl-ams.scw.cloud -s3-website.nl-ams.scw.cloud -scbl.nl-ams.scw.cloud -whm.nl-ams.scw.cloud -cockpit.pl-waw.scw.cloud -ddl.pl-waw.scw.cloud -dtwh.pl-waw.scw.cloud -ifr.pl-waw.scw.cloud -k8s.pl-waw.scw.cloud -nodes.k8s.pl-waw.scw.cloud -kafk.pl-waw.scw.cloud -mgdb.pl-waw.scw.cloud -rdb.pl-waw.scw.cloud -s3.pl-waw.scw.cloud -s3-website.pl-waw.scw.cloud -scbl.pl-waw.scw.cloud -scalebook.scw.cloud -smartlabeling.scw.cloud -dedibox.fr - -// schokokeks.org GbR : https://schokokeks.org/ -// Submitted by Hanno Böck -schokokeks.net - -// Scottish Government : https://www.gov.scot -// Submitted by Martin Ellis -gov.scot -service.gov.scot - -// Scry Security : http://www.scrysec.com -// Submitted by Shante Adam -scrysec.com - -// Scrypted : https://scrypted.app -// Submitted by Koushik Dutta -client.scrypted.io - -// Securepoint GmbH : https://www.securepoint.de -// Submitted by Erik Anders -firewall-gateway.com -firewall-gateway.de -my-gateway.de -my-router.de -spdns.de -spdns.eu -firewall-gateway.net -my-firewall.org -myfirewall.org -spdns.org - -// Seidat : https://www.seidat.com -// Submitted by Artem Kondratev -seidat.net - -// Sellfy : https://sellfy.com -// Submitted by Yuriy Romadin -sellfy.store - -// Sendmsg : https://www.sendmsg.co.il -// Submitted by Assaf Stern -minisite.ms - -// Senseering GmbH : https://www.senseering.de -// Submitted by Felix Mönckemeyer -senseering.net - -// Servebolt AS : https://servebolt.com -// Submitted by Daniel Kjeserud -servebolt.cloud - -// Service Online LLC : http://drs.ua/ -// Submitted by Serhii Bulakh -biz.ua -co.ua -pp.ua - -// Shanghai Accounting Society : https://www.sasf.org.cn -// Submitted by Information Administration -as.sh.cn - -// Sheezy.Art : https://sheezy.art -// Submitted by Nyoom -sheezy.games - -// Shopblocks : http://www.shopblocks.com/ -// Submitted by Alex Bowers -myshopblocks.com - -// Shopify : https://www.shopify.com -// Submitted by Alex Richter -myshopify.com - -// Shopit : https://www.shopitcommerce.com/ -// Submitted by Craig McMahon -shopitsite.com - -// shopware AG : https://shopware.com -// Submitted by Jens Küper -shopware.shop -shopware.store - -// Siemens Mobility GmbH -// Submitted by Oliver Graebner -mo-siemens.io - -// SinaAppEngine : http://sae.sina.com.cn/ -// Submitted by SinaAppEngine -1kapp.com -appchizi.com -applinzi.com -sinaapp.com -vipsinaapp.com - -// Siteleaf : https://www.siteleaf.com/ -// Submitted by Skylar Challand -siteleaf.net - -// Small Technology Foundation : https://small-tech.org -// Submitted by Aral Balkan -small-web.org - -// Smallregistry by Promopixel SARL : https://www.smallregistry.net -// Former AFNIC's SLDs -// Submitted by Jérôme Lipowicz -aeroport.fr -avocat.fr -chambagri.fr -chirurgiens-dentistes.fr -experts-comptables.fr -medecin.fr -notaires.fr -pharmacien.fr -port.fr -veterinaire.fr - -// Smoove.io : https://www.smoove.io/ -// Submitted by Dan Kozak -vp4.me - -// Snowflake Inc : https://www.snowflake.com/ -// Submitted by Sam Haar -*.snowflake.app -*.privatelink.snowflake.app -streamlit.app -streamlitapp.com - -// Snowplow Analytics : https://snowplowanalytics.com/ -// Submitted by Ian Streeter -try-snowplow.com - -// Software Consulting Michal Zalewski : https://www.mafelo.com -// Submitted by Michal Zalewski -mafelo.net - -// Sony Interactive Entertainment LLC : https://sie.com/ -// Submitted by David Coles -playstation-cloud.com - -// SourceHut : https://sourcehut.org -// Submitted by Drew DeVault -srht.site - -// SourceLair PC : https://www.sourcelair.com -// Submitted by Antonis Kalipetis -apps.lair.io -*.stolos.io - -// sourceWAY GmbH : https://sourceway.de -// Submitted by Richard Reiber -4.at -my.at -my.de -*.nxa.eu -nx.gw - -// SpeedPartner GmbH : https://www.speedpartner.de/ -// Submitted by Stefan Neufeind -customer.speedpartner.de - -// Spreadshop (sprd.net AG) : https://www.spreadshop.com/ -// Submitted by Martin Breest -myspreadshop.at -myspreadshop.com.au -myspreadshop.be -myspreadshop.ca -myspreadshop.ch -myspreadshop.com -myspreadshop.de -myspreadshop.dk -myspreadshop.es -myspreadshop.fi -myspreadshop.fr -myspreadshop.ie -myspreadshop.it -myspreadshop.net -myspreadshop.nl -myspreadshop.no -myspreadshop.pl -myspreadshop.se -myspreadshop.co.uk - -// StackBlitz : https://stackblitz.com -// Submitted by Dominic Elm & Albert Pai -w-corp-staticblitz.com -w-credentialless-staticblitz.com -w-staticblitz.com -bolt.host - -// Stackhero : https://www.stackhero.io -// Submitted by Adrien Gillon -stackhero-network.com - -// STACKIT GmbH & Co. KG : https://www.stackit.de/en/ -// Submitted by STACKIT-DNS Team (Simon Stier) -runs.onstackit.cloud -stackit.gg -stackit.rocks -stackit.run -stackit.zone - -// Staclar : https://staclar.com -// Submitted by Q Misell -// Submitted by Matthias Merkel -musician.io -novecore.site - -// Standard Library : https://stdlib.com -// Submitted by Jacob Lee -api.stdlib.com - -// stereosense GmbH : https://www.involve.me -// Submitted by Florian Burmann -feedback.ac -forms.ac -assessments.cx -calculators.cx -funnels.cx -paynow.cx -quizzes.cx -researched.cx -tests.cx -surveys.so - -// Storacha Network : https://storacha.network -// Submitted by Alan Shaw -ipfs.storacha.link -ipfs.w3s.link - -// Storebase : https://www.storebase.io -// Submitted by Tony Schirmer -storebase.store - -// Storipress : https://storipress.com -// Submitted by Benno Liu -storipress.app - -// Storj Labs Inc. : https://storj.io/ -// Submitted by Philip Hutchins -storj.farm - -// Strapi : https://strapi.io/ -// Submitted by Florent Baldino -strapiapp.com -media.strapiapp.com - -// Strategic System Consulting (eApps Hosting) : https://www.eapps.com/ -// Submitted by Alex Oancea -vps-host.net -atl.jelastic.vps-host.net -njs.jelastic.vps-host.net -ric.jelastic.vps-host.net - -// Streak : https://streak.com -// Submitted by Blake Kadatz -streak-link.com -streaklinks.com -streakusercontent.com - -// Student-Run Computing Facility : https://www.srcf.net/ -// Submitted by Edwin Balani -soc.srcf.net -user.srcf.net - -// Studenten Net Twente : http://www.snt.utwente.nl/ -// Submitted by Silke Hofstra -utwente.io - -// Sub 6 Limited : http://www.sub6.com -// Submitted by Dan Miller -temp-dns.com - -// Supabase : https://supabase.io -// Submitted by Supabase Security -supabase.co -realtime.supabase.co -storage.supabase.co -supabase.in -supabase.net - -// Syncloud : https://syncloud.org -// Submitted by Boris Rybalkin -syncloud.it - -// Synology, Inc. : https://www.synology.com/ -// Submitted by Rony Weng -dscloud.biz -direct.quickconnect.cn -dsmynas.com -familyds.com -diskstation.me -dscloud.me -i234.me -myds.me -synology.me -dscloud.mobi -dsmynas.net -familyds.net -dsmynas.org -familyds.org -direct.quickconnect.to -vpnplus.to - -// Tabit Technologies Ltd. : https://tabit.cloud/ -// Submitted by Oren Agiv -mytabit.com -mytabit.co.il -tabitorder.co.il - -// TAIFUN Software AG : http://taifun-software.de -// Submitted by Bjoern Henke -taifun-dns.de - -// Tailor Inc. : https://www.tailor.tech -// Submitted by Ryuzo Yamamoto -erp.dev -web.erp.dev - -// Tailscale Inc. : https://www.tailscale.com -// Submitted by David Anderson -ts.net -*.c.ts.net - -// TASK geographical domains : https://task.gda.pl/en/services/for-entrepreneurs/ -gda.pl -gdansk.pl -gdynia.pl -med.pl -sopot.pl - -// Tave Creative Corp : https://tave.com/ -// Submitted by Adrian Ziemkowski -taveusercontent.com - -// tawk.to, Inc : https://www.tawk.to -// Submitted by tawk.to developer team -p.tawk.email -p.tawkto.email - -// Tche.br : https://tche.br -// Submitted by Bruno Lorensi -tche.br - -// team.blue : https://team.blue -// Submitted by Cedric Dubois -site.tb-hosting.com - -// Teckids e.V. : https://www.teckids.org -// Submitted by Dominik George -edugit.io -s3.teckids.org - -// Telebit : https://telebit.cloud -// Submitted by AJ ONeal -telebit.app -telebit.io -*.telebit.xyz - -// Teleport : https://goteleport.com -// Submitted by Rob Picard -teleport.sh - -// Thingdust AG : https://thingdust.com/ -// Submitted by Adrian Imboden -*.firenet.ch -*.svc.firenet.ch -reservd.com -thingdustdata.com -cust.dev.thingdust.io -reservd.dev.thingdust.io -cust.disrec.thingdust.io -reservd.disrec.thingdust.io -cust.prod.thingdust.io -cust.testing.thingdust.io -reservd.testing.thingdust.io - -// ticket i/O GmbH : https://ticket.io -// Submitted by Christian Franke -tickets.io - -// Tlon.io : https://tlon.io -// Submitted by Mark Staarink -arvo.network -azimuth.network -tlon.network - -// Tor Project, Inc. : https://torproject.org -// Submitted by Antoine Beaupré -torproject.net -pages.torproject.net - -// TownNews.com : http://www.townnews.com -// Submitted by Dustin Ward -townnews-staging.com - -// TrafficPlex GmbH : https://www.trafficplex.de/ -// Submitted by Phillipp Röll -12hp.at -2ix.at -4lima.at -lima-city.at -12hp.ch -2ix.ch -4lima.ch -lima-city.ch -trafficplex.cloud -de.cool -12hp.de -2ix.de -4lima.de -lima-city.de -1337.pictures -clan.rip -lima-city.rocks -webspace.rocks -lima.zone - -// TransIP : https://www.transip.nl -// Submitted by Rory Breuk and Cedric Dubois -*.transurl.be -*.transurl.eu -site.transip.me -*.transurl.nl - -// TuxFamily : http://tuxfamily.org -// Submitted by TuxFamily administrators -tuxfamily.org - -// TwoDNS : https://www.twodns.de/ -// Submitted by TwoDNS-Support -dd-dns.de -dray-dns.de -draydns.de -dyn-vpn.de -dynvpn.de -mein-vigor.de -my-vigor.de -my-wan.de -syno-ds.de -synology-diskstation.de -synology-ds.de -diskstation.eu -diskstation.org - -// Typedream : https://typedream.com -// Submitted by Putri Karunia -typedream.app - -// Typeform : https://www.typeform.com -// Submitted by Typeform -pro.typeform.com - -// Uberspace : https://uberspace.de -// Submitted by Moritz Werner -uber.space - -// UDR Limited : http://www.udr.hk.com -// Submitted by registry -hk.com -inc.hk -ltd.hk -hk.org - -// UK Intis Telecom LTD : https://it.com -// Submitted by ITComdomains -it.com - -// Unison Computing, PBC : https://unison.cloud -// Submitted by Simon Højberg -unison-services.cloud - -// United Gameserver GmbH : https://united-gameserver.de -// Submitted by Stefan Schwarz -virtual-user.de -virtualuser.de - -// United States Writing Corporation : https://uswriting.co -// Submitted by Andrew Sampson -obj.ag - -// UNIVERSAL DOMAIN REGISTRY : https://www.udr.org.yt/ -// see also: whois -h whois.udr.org.yt help -// Submitted by Atanunu Igbunuroghene -name.pm -sch.tf -biz.wf -sch.wf -org.yt - -// University of Banja Luka : https://unibl.org -// Domains for Republic of Srpska administrative entity. -// Submitted by Marko Ivanovic -rs.ba - -// University of Bielsko-Biala regional domain : http://dns.bielsko.pl/ -// Submitted by Marcin -bielsko.pl - -// urown.net : https://urown.net -// Submitted by Hostmaster -urown.cloud -dnsupdate.info - -// US REGISTRY LLC : http://us.org -// Submitted by Gavin Brown -us.org - -// V.UA Domain Registry: https://www.v.ua/ -// Submitted by Serhii Rostilo -v.ua - -// Val Town, Inc : https://val.town/ -// Submitted by Tom MacWright -val.run -web.val.run - -// Vercel, Inc : https://vercel.com/ -// Submitted by Laurens Duijvesteijn -vercel.app -v0.build -vercel.dev -vusercontent.net -vercel.run -now.sh - -// VeryPositive SIA : http://very.lv -// Submitted by Danko Aleksejevs -2038.io - -// Virtual-Info : https://www.virtual-info.info/ -// Submitted by Adnan RIHAN -v-info.info - -// VistaBlog : https://vistablog.ir/ -// Submitted by Hossein Piri -vistablog.ir - -// Viva Republica, Inc. : https://toss.im/ -// Submitted by Deus Team -deus-canvas.com - -// Voorloper.com : https://voorloper.com -// Submitted by Nathan van Bakel -voorloper.cloud - -// Vultr Objects : https://www.vultr.com/products/object-storage/ -// Submitted by Niels Maumenee -*.vultrobjects.com - -// Waffle Computer Inc., Ltd. : https://docs.waffleinfo.com -// Submitted by Masayuki Note -wafflecell.com - -// Walrus : https://walrus.xyz -// Submitted by Max Spector -wal.app - -// Webflow, Inc. : https://www.webflow.com -// Submitted by Webflow Security Team -webflow.io -webflowtest.io - -// WebHare bv : https://www.webhare.com/ -// Submitted by Arnold Hendriks -*.webhare.dev - -// WebHotelier Technologies Ltd : https://www.webhotelier.net/ -// Submitted by Apostolos Tsakpinis -bookonline.app -hotelwithflight.com -reserve-online.com -reserve-online.net - -// WebPros International, LLC : https://webpros.com/ -// Submitted by Nicolas Rochelemagne -cprapid.com -pleskns.com -wp2.host -pdns.page -plesk.page -cpanel.site -wpsquared.site - -// WebWaddle Ltd : https://webwaddle.com/ -// Submitted by Merlin Glander -*.wadl.top - -// Western Digital Technologies, Inc : https://www.wdc.com -// Submitted by Jung Jin -remotewd.com - -// Whatbox Inc. : https://whatbox.ca/ -// Submitted by Anthony Ryan -box.ca - -// WIARD Enterprises : https://wiardweb.com -// Submitted by Kidd Hustle -pages.wiardweb.com - -// Wikimedia Foundation : https://wikitech.wikimedia.org -// Submitted by Timo Tijhof -toolforge.org -wmcloud.org -beta.wmcloud.org -wmflabs.org - -// William Harrison : https://wharrison.com.au -// Submitted by William Harrison -wdh.app -hrsn.au -vps.hrsn.au -hrsn.dev -is-a.dev -localcert.net - -// Windsurf : https://windsurf.com -// Submitted by Douglas Chen -windsurf.app -windsurf.build - -// WISP : https://wisp.gg -// Submitted by Stepan Fedotov -panel.gg -daemon.panel.gg - -// Wix.com, Inc. : https://www.wix.com -// Submitted by Shahar Talmi / Alon Kochba -wixsite.com -wixstudio.com -editorx.io -wixstudio.io -wix.run - -// Wizard Zines : https://wizardzines.com -// Submitted by Julia Evans -messwithdns.com - -// WoltLab GmbH : https://www.woltlab.com -// Submitted by Tim Düsterhus -woltlab-demo.com -myforum.community -community-pro.de -diskussionsbereich.de -community-pro.net -meinforum.net - -// Woods Valldata : https://www.woodsvalldata.co.uk/ -// Submitted by Chris Whittle -affinitylottery.org.uk -raffleentry.org.uk -weeklylottery.org.uk - -// WP Engine : https://wpengine.com/ -// Submitted by Michael Smith -// Submitted by Brandon DuRette -wpenginepowered.com -js.wpenginepowered.com - -// XenonCloud GbR : https://xenoncloud.net -// Submitted by Julian Uphoff -*.xenonconnect.de -half.host - -// XnBay Technology : http://www.xnbay.com/ -// Submitted by XnBay Developer -xnbay.com -u2.xnbay.com -u2-local.xnbay.com - -// XS4ALL Internet bv : https://www.xs4all.nl/ -// Submitted by Daniel Mostertman -cistron.nl -demon.nl -xs4all.space - -// Yandex.Cloud LLC : https://cloud.yandex.com -// Submitted by Alexander Lodin -yandexcloud.net -storage.yandexcloud.net -website.yandexcloud.net -sourcecraft.site - -// YesCourse Pty Ltd : https://yescourse.com -// Submitted by Atul Bhouraskar -official.academy - -// Yola : https://www.yola.com/ -// Submitted by Stefano Rivera -yolasite.com - -// Yunohost : https://yunohost.org -// Submitted by Valentin Grimaud -ynh.fr -nohost.me -noho.st - -// ZaNiC : http://www.za.net/ -// Submitted by registry -za.net -za.org - -// ZAP-Hosting GmbH & Co. KG : https://zap-hosting.com -// Submitted by Julian Alker -zap.cloud - -// Zeabur : https://zeabur.com/ -// Submitted by Zeabur Team -zeabur.app - -// Zerops : https://zerops.io/ -// Submitted by Zerops Team -*.zerops.app - -// Zine EOOD : https://zine.bg/ -// Submitted by Martin Angelov -bss.design - -// Zitcom A/S : https://www.zitcom.dk -// Submitted by Emil Stahl -basicserver.io -virtualserver.io -enterprisecloud.nu - -// Zone.ID: https://zone.id -// Submitted by Gx1.org -zone.id - -// ZoneABC : https://zoneabc.net -// Submitted by ZoneABC Team -zabc.net - -// ===END PRIVATE DOMAINS=== \ No newline at end of file diff --git a/dmarc/src/main/test/java/org/apache/james/dmarc/DMARCTest.java b/dmarc/src/main/test/java/org/apache/james/dmarc/DMARCTest.java index fc1fb01..ffe57c4 100644 --- a/dmarc/src/main/test/java/org/apache/james/dmarc/DMARCTest.java +++ b/dmarc/src/main/test/java/org/apache/james/dmarc/DMARCTest.java @@ -18,7 +18,12 @@ ******************************************************************************/ package org.apache.james.dmarc; +import org.apache.james.mime4j.dom.Message; +import org.apache.james.mime4j.message.DefaultMessageBuilder; import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @@ -51,4 +56,48 @@ public void generate_and_verify_dmarc_pass() { assertThat(dmarcVerifier.runDmarcCheck(r.message(), r.spfResult(), r.spfDomain(), r.dkimResult(), r.dkimDomain()).toString()).hasToString(r.expectedResult()); }); } -} \ No newline at end of file + + @Test + public void dmarc_check_returns_permerror_when_from_header_is_missing() throws Exception { + Message message = parseMessage( + "To: recipient@example.org\r\n" + + "Subject: missing from\r\n" + + "\r\n" + + "body\r\n"); + + DmarcValidationResult result = dmarcVerifier.runDmarcCheck( + message, + "pass client-ip=192.0.2.1; envelope-from=sender@example.org", + "example.org", + "pass", + "example.org"); + + assertThat(result.toString()) + .isEqualTo("dmarc=permerror reason=\"From header must contain exactly one mailbox\""); + } + + @Test + public void dmarc_check_returns_permerror_when_from_header_has_multiple_mailboxes() throws Exception { + Message message = parseMessage( + "From: Alice , Bob \r\n" + + "To: recipient@example.org\r\n" + + "Subject: multiple from\r\n" + + "\r\n" + + "body\r\n"); + + DmarcValidationResult result = dmarcVerifier.runDmarcCheck( + message, + "pass client-ip=192.0.2.1; envelope-from=sender@example.org", + "example.org", + "pass", + "example.org"); + + assertThat(result.toString()) + .isEqualTo("dmarc=permerror reason=\"From header must contain exactly one mailbox\""); + } + + private Message parseMessage(String rawMessage) throws Exception { + return new DefaultMessageBuilder().parseMessage( + new ByteArrayInputStream(rawMessage.getBytes(StandardCharsets.UTF_8))); + } +} diff --git a/dmarc/src/main/test/java/org/apache/james/dmarc/PublicSuffixListTest.java b/dmarc/src/main/test/java/org/apache/james/dmarc/PublicSuffixListTest.java index 984f377..68550e0 100644 --- a/dmarc/src/main/test/java/org/apache/james/dmarc/PublicSuffixListTest.java +++ b/dmarc/src/main/test/java/org/apache/james/dmarc/PublicSuffixListTest.java @@ -48,7 +48,7 @@ public void getOrgDomain_noPslMatch() { @Test public void getOrgDomain_shouldReturnPublicSuffixIfMatched() { assertEquals("example.co.uk", PublicSuffixList.getOrgDomain("example.co.uk")); - assertEquals("mail.replit.app", PublicSuffixList.getOrgDomain("mail.replit.app")); + assertEquals("replit.app", PublicSuffixList.getOrgDomain("mail.replit.app")); } /* @@ -105,4 +105,4 @@ public void getOrgDomain_openAiWildcard() { assertEquals("北海道.三重.jp", PublicSuffixList.getOrgDomain("北海道.三重.jp")); //PSL + one left assertEquals("北海道.三重.jp", PublicSuffixList.getOrgDomain("大分.北海道.三重.jp")); //PSL + two left } -} \ No newline at end of file +} diff --git a/pom.xml b/pom.xml index 4f63d29..305cff4 100644 --- a/pom.xml +++ b/pom.xml @@ -69,6 +69,7 @@ 4.13.2 11 1.0.5 + 33.0.0-jre @@ -111,6 +112,11 @@ assertj-core 3.27.7 + + com.google.guava + guava + ${guava.version} + From 5b341f6ab95eb33bee171d3dada2e7af6e382fa7 Mon Sep 17 00:00:00 2001 From: Artem Grinchenko Date: Mon, 27 Apr 2026 17:06:09 -0400 Subject: [PATCH 114/114] Address ARC and DMARC review hardening - Include the failing ARC instance in previous-hop AMS validation errors. - Add injectable Clock support to ARCVerifier and use it for AMS timestamp checks. - Reject AMS t= timestamps in the future, even when x= is absent. - Remove empty multipart bh= bypass and always compare computed body hashes. - Fix parsed multipart reconstruction by reading the boundary from parent Content-Type. - Tighten ARC-Seal tag validation for required i/a/cv/d/s/b tags, cv values, d/s syntax, and h= rejection. - Convert DMARC PublicSuffixList to an injectable interface. - Add built-in default PSL resolver and optional Guava-backed implementation. - Mark Guava optional and add custom PSL injection coverage. - Add regression tests for diagnostics, timestamp validation, multipart bh= handling, and custom PSL wiring. --- .../apache/james/arc/ARCChainValidator.java | 2 +- .../org/apache/james/arc/ARCVerifier.java | 59 ++++++++++++++++--- .../java/org/apache/james/arc/ARCTest.java | 45 +++++++++++++- dmarc/pom.xml | 1 + .../org/apache/james/dmarc/DMARCVerifier.java | 13 +++- .../james/dmarc/DefaultPublicSuffixList.java | 37 ++++++++++++ .../james/dmarc/GuavaPublicSuffixList.java | 43 ++++++++++++++ .../apache/james/dmarc/PublicSuffixList.java | 25 +------- .../org/apache/james/dmarc/DMARCTest.java | 17 ++++++ .../james/dmarc/PublicSuffixListTest.java | 53 +++++++++-------- 10 files changed, 231 insertions(+), 64 deletions(-) create mode 100644 dmarc/src/main/java/org/apache/james/dmarc/DefaultPublicSuffixList.java create mode 100644 dmarc/src/main/java/org/apache/james/dmarc/GuavaPublicSuffixList.java diff --git a/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java b/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java index e881ceb..d2e690c 100644 --- a/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java +++ b/arc/src/main/java/org/apache/james/arc/ARCChainValidator.java @@ -95,7 +95,7 @@ private ArcValidationOutcome validatePreviousArcHops(Message message, Header mes for (int i = 1; i <= numArcInstances; i++) { Set arcSet = arcVerifier.extractArcSet(messageHeaders, i); if (arcSet == null || !checkArcAms(arcSet, message, arcVerifier)) { - return new ArcValidationOutcome(ArcValidationResult.FAIL, "Previous ARC hops validation failed"); + return new ArcValidationOutcome(ArcValidationResult.FAIL, "Previous ARC hop validation failed at i=" + i); } } boolean asOk; diff --git a/arc/src/main/java/org/apache/james/arc/ARCVerifier.java b/arc/src/main/java/org/apache/james/arc/ARCVerifier.java index 79d95d1..7c80aca 100644 --- a/arc/src/main/java/org/apache/james/arc/ARCVerifier.java +++ b/arc/src/main/java/org/apache/james/arc/ARCVerifier.java @@ -27,6 +27,7 @@ import org.apache.james.mime4j.dom.Message; import org.apache.james.mime4j.dom.Multipart; import org.apache.james.mime4j.dom.SingleBody; +import org.apache.james.mime4j.dom.field.ContentTypeField; import org.apache.james.mime4j.io.EOLConvertingInputStream; import org.apache.james.mime4j.stream.NameValuePair; import org.apache.james.mime4j.stream.Field; @@ -45,6 +46,7 @@ import java.security.interfaces.RSAPublicKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; +import java.time.Clock; import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; @@ -53,6 +55,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.TreeMap; @@ -73,7 +76,7 @@ *
  • Building signing data for ARC-Seal verification
  • * *

    - * This class is not instantiable and all methods are static. + * Instances are configured with a public key retriever and an optional clock. */ public class ARCVerifier { public static final String RSA = "RSA"; @@ -90,10 +93,16 @@ public class ARCVerifier { private static final int MIN_ARC_INSTANCE = 1; private static final int MAX_ARC_INSTANCE = 50; private static final String DNS_RECORD_TYPE = "_domainkey"; - private PublicKeyRetrieverArc _keyRecordRetriever; + private final PublicKeyRetrieverArc _keyRecordRetriever; + private final Clock clock; - ARCVerifier(PublicKeyRetrieverArc keyRecordRetriever) { - _keyRecordRetriever = keyRecordRetriever; + public ARCVerifier(PublicKeyRetrieverArc keyRecordRetriever) { + this(keyRecordRetriever, Clock.systemUTC()); + } + + public ARCVerifier(PublicKeyRetrieverArc keyRecordRetriever, Clock clock) { + _keyRecordRetriever = Objects.requireNonNull(keyRecordRetriever); + this.clock = Objects.requireNonNull(clock); } public boolean verifyAms(Field amsField, Message message, String publicKeyDnsRecord) { @@ -201,6 +210,10 @@ private boolean validateAmsTags(Map tags) { private void validateAmsTimestamp(Map tags) { String timestamp = tags.get("t"); String expiration = tags.get("x"); + long now = clock.instant().getEpochSecond(); + if (timestamp != null && Long.parseLong(timestamp) > now) { + throw new ArcException("AMS t= timestamp must not be in the future"); + } if (expiration == null) { return; } @@ -208,7 +221,6 @@ private void validateAmsTimestamp(Map tags) { if (timestamp != null && expirationEpoch <= Long.parseLong(timestamp)) { throw new ArcException("AMS x= expiration must be greater than t= timestamp"); } - long now = System.currentTimeMillis() / 1000; if (expirationEpoch < now) { throw new ArcException("AMS signature is expired"); } @@ -227,9 +239,6 @@ private boolean verifyAmsBodyHash(Map tags, Message message, Str try { MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); byte[] bodyBytes = message.getBody() == null ? new byte[0] : readBodyBytes(message.getBody()); - if (bodyBytes.length == 0 && message.getBody() instanceof Multipart) { - return true; - } byte[] canonicalizedBody = canonicalizeBody(bodyBytes, bodyCanonicalization); computedBodyHash = messageDigest.digest(canonicalizedBody); } catch (IOException | NoSuchAlgorithmException e) { @@ -275,6 +284,11 @@ private String getBoundary(Multipart multipart) { return parameter.getValue(); } } + Entity parent = multipart.getParent(); + if (parent != null && parent.getHeader() != null + && parent.getHeader().getField("Content-Type") instanceof ContentTypeField) { + return ((ContentTypeField) parent.getHeader().getField("Content-Type")).getBoundary(); + } return null; } @@ -469,10 +483,37 @@ private boolean checkArcSealTags(List arcSet) { .filter(f -> f.getName().equalsIgnoreCase(ARC_SEAL)) .findFirst(); return arcSealHeader - .map(field -> !parseTagList(field.getBody()).containsKey("h")) + .map(field -> { + Map tags = parseTagList(field.getBody()); + return hasRequiredArcSealTags(tags) + && isValidArcSealCv(tags.get("cv")) + && DOMAIN_PATTERN.matcher(tags.get("d")).matches() + && SELECTOR_PATTERN.matcher(tags.get("s")).matches() + && !tags.containsKey("h"); + }) .orElse(false); } + private boolean hasRequiredArcSealTags(Map tags) { + return hasNonEmptyTag(tags, "i") + && hasNonEmptyTag(tags, "a") + && hasNonEmptyTag(tags, "cv") + && hasNonEmptyTag(tags, "d") + && hasNonEmptyTag(tags, "s") + && hasNonEmptyTag(tags, "b"); + } + + private boolean hasNonEmptyTag(Map tags, String tagName) { + String value = tags.get(tagName); + return value != null && !value.replaceAll("\\s+", "").isEmpty(); + } + + private boolean isValidArcSealCv(String cv) { + return "none".equalsIgnoreCase(cv) + || "pass".equalsIgnoreCase(cv) + || "fail".equalsIgnoreCase(cv); + } + private boolean checkCv(List lastArcSet, int instToVerify) { Optional arcSealHeader = lastArcSet.stream().filter(f -> f.getName().equalsIgnoreCase(ARC_SEAL)).findFirst(); if (arcSealHeader.isPresent()) { diff --git a/arc/src/test/java/org/apache/james/arc/ARCTest.java b/arc/src/test/java/org/apache/james/arc/ARCTest.java index 080f688..e3aa0b7 100644 --- a/arc/src/test/java/org/apache/james/arc/ARCTest.java +++ b/arc/src/test/java/org/apache/james/arc/ARCTest.java @@ -44,6 +44,9 @@ import java.net.URL; import java.nio.charset.StandardCharsets; import java.security.Signature; +import java.time.Clock; +import java.time.Instant; +import java.time.ZoneOffset; import java.util.Map; import java.util.Set; @@ -239,6 +242,7 @@ public void validate_arc_chain_fails_when_ams_signature_is_invalid() throws Exce ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever); ArcValidationOutcome cv = arcChainValidator.validateArcChain(message); assertThat(cv.getResult().toString().toLowerCase()).isEqualTo("fail"); + assertThat(cv.getDescription()).isEqualTo("Previous ARC hop validation failed at i=1"); } // cv_fail_i1_as_invalid: builds a valid i=1 ARC set, then replaces the ARC-Seal b= signature with @@ -1245,6 +1249,24 @@ public void verify_ams_fails_when_no_body_message_has_wrong_body_hash() throws E .isFalse(); } + @Test + public void verify_ams_fails_when_empty_multipart_has_wrong_body_hash() throws Exception { + Message message = parseRawEmail( + "From: sender@example.org\n" + + "To: recipient@example.org\n" + + "Subject: empty multipart\n" + + "Content-Type: multipart/alternative; boundary=abc\n" + + "\n"); + String amsWithoutSignature = "i=1; a=rsa-sha256; c=relaxed/relaxed; d=dmarc.example; s=arc; " + + "t=12345; h=from:to:subject; bh=KWSe46TZKCcDbH4klJPo+tjk5LWJnVRlP5pvjXFZYLQ=; b="; + String signature = signRelaxedAmsForNoBodyMessage(message, amsWithoutSignature); + Field amsField = new RawField(ARC_MESSAGE_SIGNATURE, amsWithoutSignature + signature); + String publicKeyDnsRecord = "k=rsa; p=" + Base64.getEncoder().encodeToString(ArcTestKeys.publicKeyArc.getEncoded()) + ";"; + + assertThat(new ARCVerifier(keyRecordRetriever).verifyAms(amsField, message, publicKeyDnsRecord)) + .isFalse(); + } + // ams_fields_bh_mod_body: body changes outside relaxed canonicalization must be rejected. @Test public void validate_arc_chain_fails_when_ams_signed_body_is_modified() throws Exception { @@ -1658,16 +1680,35 @@ public void verify_ams_throws_clear_exception_when_signature_is_expired() throws + "To: recipient@example.org\r\n" + "Subject: expired ams\r\n").getBytes(StandardCharsets.UTF_8))); String amsWithoutSignature = "i=1; a=rsa-sha256; c=relaxed/relaxed; d=dmarc.example; s=arc; " - + "t=1; x=2; h=from:to:subject; bh=KWSe46TZKCcDbH4klJPo+tjk5LWJnVRlP5pvjXFZYLQ=; b="; + + "t=1; x=1000; h=from:to:subject; bh=KWSe46TZKCcDbH4klJPo+tjk5LWJnVRlP5pvjXFZYLQ=; b="; String signature = signRelaxedAmsForNoBodyMessage(message, amsWithoutSignature); Field amsField = new RawField(ARC_MESSAGE_SIGNATURE, amsWithoutSignature + signature); String publicKeyDnsRecord = "k=rsa; p=" + Base64.getEncoder().encodeToString(ArcTestKeys.publicKeyArc.getEncoded()) + ";"; + Clock clockAfterExpiration = Clock.fixed(Instant.ofEpochSecond(1001), ZoneOffset.UTC); - assertThatThrownBy(() -> new ARCVerifier(keyRecordRetriever).verifyAms(amsField, message, publicKeyDnsRecord)) + assertThatThrownBy(() -> new ARCVerifier(keyRecordRetriever, clockAfterExpiration).verifyAms(amsField, message, publicKeyDnsRecord)) .isInstanceOf(ArcException.class) .hasMessage("AMS signature is expired"); } + @Test + public void verify_ams_throws_clear_exception_when_timestamp_is_in_the_future_without_expiration() throws Exception { + Message message = new DefaultMessageBuilder().parseMessage(new ByteArrayInputStream( + ("From: sender@example.org\r\n" + + "To: recipient@example.org\r\n" + + "Subject: future ams timestamp\r\n").getBytes(StandardCharsets.UTF_8))); + String amsWithoutSignature = "i=1; a=rsa-sha256; c=relaxed/relaxed; d=dmarc.example; s=arc; " + + "t=1001; h=from:to:subject; bh=KWSe46TZKCcDbH4klJPo+tjk5LWJnVRlP5pvjXFZYLQ=; b="; + String signature = signRelaxedAmsForNoBodyMessage(message, amsWithoutSignature); + Field amsField = new RawField(ARC_MESSAGE_SIGNATURE, amsWithoutSignature + signature); + String publicKeyDnsRecord = "k=rsa; p=" + Base64.getEncoder().encodeToString(ArcTestKeys.publicKeyArc.getEncoded()) + ";"; + Clock clockBeforeTimestamp = Clock.fixed(Instant.ofEpochSecond(1000), ZoneOffset.UTC); + + assertThatThrownBy(() -> new ARCVerifier(keyRecordRetriever, clockBeforeTimestamp).verifyAms(amsField, message, publicKeyDnsRecord)) + .isInstanceOf(ArcException.class) + .hasMessage("AMS t= timestamp must not be in the future"); + } + @Test public void verify_ams_throws_clear_exception_when_expiration_is_not_after_timestamp() throws Exception { Message message = new DefaultMessageBuilder().parseMessage(new ByteArrayInputStream( diff --git a/dmarc/pom.xml b/dmarc/pom.xml index ac550af..00ddaab 100644 --- a/dmarc/pom.xml +++ b/dmarc/pom.xml @@ -58,6 +58,7 @@ com.google.guava guava + true org.assertj diff --git a/dmarc/src/main/java/org/apache/james/dmarc/DMARCVerifier.java b/dmarc/src/main/java/org/apache/james/dmarc/DMARCVerifier.java index c51be12..215b784 100644 --- a/dmarc/src/main/java/org/apache/james/dmarc/DMARCVerifier.java +++ b/dmarc/src/main/java/org/apache/james/dmarc/DMARCVerifier.java @@ -24,13 +24,20 @@ import org.apache.james.mime4j.dom.address.MailboxList; import java.util.HashMap; import java.util.Map; +import java.util.Objects; public class DMARCVerifier { public static final String FROM = "From"; private final PublicKeyRecordRetrieverDmarc _recordRetriever; + private final PublicSuffixList publicSuffixList; public DMARCVerifier(PublicKeyRecordRetrieverDmarc recordRetriever) { - _recordRetriever = recordRetriever; + this(recordRetriever, new DefaultPublicSuffixList()); + } + + public DMARCVerifier(PublicKeyRecordRetrieverDmarc recordRetriever, PublicSuffixList publicSuffixList) { + _recordRetriever = Objects.requireNonNull(recordRetriever); + this.publicSuffixList = Objects.requireNonNull(publicSuffixList); } public DmarcValidationResult runDmarcCheck(Message message, String spfHeaderText, String @@ -93,8 +100,8 @@ private Map getDmarcTags(String dmarcRecord) { private boolean getDomainAlignment(String flag, String result, String receivedDomain, String expectedDomain) { // we expect flag to be either "s" or "r"; default is "r" when omitted if (flag.equalsIgnoreCase("r")){ //relaxed - String fromOrgDomain = PublicSuffixList.getOrgDomain(receivedDomain); //we get the organizational domain using PSL - String spfOrgDomain = PublicSuffixList.getOrgDomain(expectedDomain); + String fromOrgDomain = publicSuffixList.getOrgDomain(receivedDomain); //we get the organizational domain using PSL + String spfOrgDomain = publicSuffixList.getOrgDomain(expectedDomain); return "pass".equals(result) && fromOrgDomain.equalsIgnoreCase(spfOrgDomain); diff --git a/dmarc/src/main/java/org/apache/james/dmarc/DefaultPublicSuffixList.java b/dmarc/src/main/java/org/apache/james/dmarc/DefaultPublicSuffixList.java new file mode 100644 index 0000000..257f5d7 --- /dev/null +++ b/dmarc/src/main/java/org/apache/james/dmarc/DefaultPublicSuffixList.java @@ -0,0 +1,37 @@ +/****************************************************************************** + * 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.dmarc; + +import java.util.Locale; + +public class DefaultPublicSuffixList implements PublicSuffixList { + @Override + public String getOrgDomain(String domainToCheck) { + if (domainToCheck == null || domainToCheck.trim().isEmpty()) { + return domainToCheck; + } + + String normalizedDomain = domainToCheck.toLowerCase(Locale.ROOT).trim(); + String[] labels = normalizedDomain.split("\\."); + if (labels.length < 3) { + return normalizedDomain; + } + return labels[labels.length - 2] + "." + labels[labels.length - 1]; + } +} diff --git a/dmarc/src/main/java/org/apache/james/dmarc/GuavaPublicSuffixList.java b/dmarc/src/main/java/org/apache/james/dmarc/GuavaPublicSuffixList.java new file mode 100644 index 0000000..dc1112f --- /dev/null +++ b/dmarc/src/main/java/org/apache/james/dmarc/GuavaPublicSuffixList.java @@ -0,0 +1,43 @@ +/****************************************************************************** + * 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.dmarc; + +import java.util.Locale; + +import com.google.common.net.InternetDomainName; + +public class GuavaPublicSuffixList implements PublicSuffixList { + @Override + public String getOrgDomain(String domainToCheck) { + if (domainToCheck == null || domainToCheck.trim().isEmpty()) { + return domainToCheck; + } + + String normalizedDomain = domainToCheck.toLowerCase(Locale.ROOT).trim(); + try { + InternetDomainName domainName = InternetDomainName.from(normalizedDomain); + if (domainName.isUnderPublicSuffix()) { + return domainName.topPrivateDomain().toString(); + } + return normalizedDomain; + } catch (IllegalArgumentException e) { + return normalizedDomain; + } + } +} diff --git a/dmarc/src/main/java/org/apache/james/dmarc/PublicSuffixList.java b/dmarc/src/main/java/org/apache/james/dmarc/PublicSuffixList.java index 6f2fed5..24c38e1 100644 --- a/dmarc/src/main/java/org/apache/james/dmarc/PublicSuffixList.java +++ b/dmarc/src/main/java/org/apache/james/dmarc/PublicSuffixList.java @@ -18,27 +18,6 @@ ******************************************************************************/ package org.apache.james.dmarc; -import java.util.Locale; - -import com.google.common.net.InternetDomainName; - -public class PublicSuffixList { - private PublicSuffixList() {} - - public static String getOrgDomain(String domainToCheck) { - if (domainToCheck == null || domainToCheck.trim().isEmpty()) { - return domainToCheck; - } - - String normalizedDomain = domainToCheck.toLowerCase(Locale.ROOT).trim(); - try { - InternetDomainName domainName = InternetDomainName.from(normalizedDomain); - if (domainName.isUnderPublicSuffix()) { - return domainName.topPrivateDomain().toString(); - } - return normalizedDomain; - } catch (IllegalArgumentException e) { - return normalizedDomain; - } - } +public interface PublicSuffixList { + String getOrgDomain(String domainToCheck); } diff --git a/dmarc/src/main/test/java/org/apache/james/dmarc/DMARCTest.java b/dmarc/src/main/test/java/org/apache/james/dmarc/DMARCTest.java index ffe57c4..f69ef2a 100644 --- a/dmarc/src/main/test/java/org/apache/james/dmarc/DMARCTest.java +++ b/dmarc/src/main/test/java/org/apache/james/dmarc/DMARCTest.java @@ -57,6 +57,23 @@ public void generate_and_verify_dmarc_pass() { }); } + @Test + public void dmarc_check_can_use_custom_public_suffix_list() { + PublicSuffixList customPublicSuffixList = domain -> "example.test"; + DMARCVerifier verifier = new DMARCVerifier(recordRetrieverDmarc, customPublicSuffixList); + DmarcRequestMock request = passRequests.get(0); + + DmarcValidationResult result = verifier.runDmarcCheck( + request.message(), + "pass client-ip=192.0.2.1; envelope-from=sender@different.example.test", + "different.example.test", + "fail", + "not-used.example.test"); + + assertThat(result.toString()) + .isEqualTo("dmarc=pass (p=reject) header.from=d1.example"); + } + @Test public void dmarc_check_returns_permerror_when_from_header_is_missing() throws Exception { Message message = parseMessage( diff --git a/dmarc/src/main/test/java/org/apache/james/dmarc/PublicSuffixListTest.java b/dmarc/src/main/test/java/org/apache/james/dmarc/PublicSuffixListTest.java index 68550e0..bed30a0 100644 --- a/dmarc/src/main/test/java/org/apache/james/dmarc/PublicSuffixListTest.java +++ b/dmarc/src/main/test/java/org/apache/james/dmarc/PublicSuffixListTest.java @@ -22,6 +22,7 @@ import static org.junit.Assert.assertEquals; public class PublicSuffixListTest { + private final PublicSuffixList publicSuffixList = new GuavaPublicSuffixList(); /* `example.com` does not exist in the PSL, only `com` does @@ -29,9 +30,9 @@ public class PublicSuffixListTest { */ @Test public void getOrgDomain_simpleMatch() { - assertEquals("example.com", PublicSuffixList.getOrgDomain("example.com")); - assertEquals("example.com", PublicSuffixList.getOrgDomain("aaa.example.com")); - assertEquals("example.com", PublicSuffixList.getOrgDomain("bbb.aaa.example.com")); + assertEquals("example.com", publicSuffixList.getOrgDomain("example.com")); + assertEquals("example.com", publicSuffixList.getOrgDomain("aaa.example.com")); + assertEquals("example.com", publicSuffixList.getOrgDomain("bbb.aaa.example.com")); } /* @@ -40,15 +41,15 @@ public void getOrgDomain_simpleMatch() { */ @Test public void getOrgDomain_noPslMatch() { - assertEquals("unknown.private", PublicSuffixList.getOrgDomain("unknown.private")); - assertEquals("my.localdomain", PublicSuffixList.getOrgDomain("my.localdomain")); - assertEquals("service.internal", PublicSuffixList.getOrgDomain("service.internal")); + assertEquals("unknown.private", publicSuffixList.getOrgDomain("unknown.private")); + assertEquals("my.localdomain", publicSuffixList.getOrgDomain("my.localdomain")); + assertEquals("service.internal", publicSuffixList.getOrgDomain("service.internal")); } @Test public void getOrgDomain_shouldReturnPublicSuffixIfMatched() { - assertEquals("example.co.uk", PublicSuffixList.getOrgDomain("example.co.uk")); - assertEquals("replit.app", PublicSuffixList.getOrgDomain("mail.replit.app")); + assertEquals("example.co.uk", publicSuffixList.getOrgDomain("example.co.uk")); + assertEquals("replit.app", publicSuffixList.getOrgDomain("mail.replit.app")); } /* @@ -56,10 +57,10 @@ public void getOrgDomain_shouldReturnPublicSuffixIfMatched() { */ @Test public void getOrgDomain_wildCardMatched() { - assertEquals("sapporo.jp", PublicSuffixList.getOrgDomain("sapporo.jp")); - assertEquals("abc.sapporo.jp", PublicSuffixList.getOrgDomain("abc.sapporo.jp")); - assertEquals("foo.abc.sapporo.jp", PublicSuffixList.getOrgDomain("foo.abc.sapporo.jp")); - assertEquals("foo.abc.sapporo.jp", PublicSuffixList.getOrgDomain("bar.foo.abc.sapporo.jp")); + assertEquals("sapporo.jp", publicSuffixList.getOrgDomain("sapporo.jp")); + assertEquals("abc.sapporo.jp", publicSuffixList.getOrgDomain("abc.sapporo.jp")); + assertEquals("foo.abc.sapporo.jp", publicSuffixList.getOrgDomain("foo.abc.sapporo.jp")); + assertEquals("foo.abc.sapporo.jp", publicSuffixList.getOrgDomain("bar.foo.abc.sapporo.jp")); } /* @@ -67,9 +68,9 @@ public void getOrgDomain_wildCardMatched() { */ @Test public void getOrgDomain_exceptionsMatched() { - assertEquals("city.sapporo.jp", PublicSuffixList.getOrgDomain("city.sapporo.jp")); - assertEquals("city.sapporo.jp", PublicSuffixList.getOrgDomain("abc.city.sapporo.jp")); - assertEquals("city.sapporo.jp", PublicSuffixList.getOrgDomain("x.y.city.sapporo.jp")); + assertEquals("city.sapporo.jp", publicSuffixList.getOrgDomain("city.sapporo.jp")); + assertEquals("city.sapporo.jp", publicSuffixList.getOrgDomain("abc.city.sapporo.jp")); + assertEquals("city.sapporo.jp", publicSuffixList.getOrgDomain("x.y.city.sapporo.jp")); } /* @@ -79,11 +80,11 @@ public void getOrgDomain_exceptionsMatched() { */ @Test public void getOrgDomain_wildCardAndExceptionCombo() { - assertEquals("www.ck", PublicSuffixList.getOrgDomain("www.ck")); // exception - assertEquals("www.ck", PublicSuffixList.getOrgDomain("a.www.ck")); // exception overrides wildcard - assertEquals("abc.ck", PublicSuffixList.getOrgDomain("abc.ck")); // wildcard + one left - assertEquals("foo.abc.ck", PublicSuffixList.getOrgDomain("foo.abc.ck")); // wildcard + two left - assertEquals("foo.abc.ck", PublicSuffixList.getOrgDomain("bar.foo.abc.ck")); // wildcard + two. we stop at two left labels + assertEquals("www.ck", publicSuffixList.getOrgDomain("www.ck")); // exception + assertEquals("www.ck", publicSuffixList.getOrgDomain("a.www.ck")); // exception overrides wildcard + assertEquals("abc.ck", publicSuffixList.getOrgDomain("abc.ck")); // wildcard + one left + assertEquals("foo.abc.ck", publicSuffixList.getOrgDomain("foo.abc.ck")); // wildcard + two left + assertEquals("foo.abc.ck", publicSuffixList.getOrgDomain("bar.foo.abc.ck")); // wildcard + two. we stop at two left labels } /* @@ -91,9 +92,9 @@ public void getOrgDomain_wildCardAndExceptionCombo() { */ @Test public void getOrgDomain_singleLabel() { - assertEquals("localhost", PublicSuffixList.getOrgDomain("localhost")); - assertEquals("com", PublicSuffixList.getOrgDomain("com")); - assertEquals("example", PublicSuffixList.getOrgDomain("example")); + assertEquals("localhost", publicSuffixList.getOrgDomain("localhost")); + assertEquals("com", publicSuffixList.getOrgDomain("com")); + assertEquals("example", publicSuffixList.getOrgDomain("example")); } /* @@ -101,8 +102,8 @@ PSL match with internationalized domain names (IDN) */ @Test public void getOrgDomain_openAiWildcard() { - assertEquals("三重.jp", PublicSuffixList.getOrgDomain("三重.jp")); //Bare PSL match - assertEquals("北海道.三重.jp", PublicSuffixList.getOrgDomain("北海道.三重.jp")); //PSL + one left - assertEquals("北海道.三重.jp", PublicSuffixList.getOrgDomain("大分.北海道.三重.jp")); //PSL + two left + assertEquals("三重.jp", publicSuffixList.getOrgDomain("三重.jp")); //Bare PSL match + assertEquals("北海道.三重.jp", publicSuffixList.getOrgDomain("北海道.三重.jp")); //PSL + one left + assertEquals("北海道.三重.jp", publicSuffixList.getOrgDomain("大分.北海道.三重.jp")); //PSL + two left } }