]> WPIA git - gigi.git/blob - src/org/cacert/gigi/util/DomainAssessment.java
5070e5cb3e669d73d5011670eaf1a3ccfd1d7e72
[gigi.git] / src / org / cacert / gigi / util / DomainAssessment.java
1 package org.cacert.gigi.util;
2
3 import java.io.BufferedReader;
4 import java.io.File;
5 import java.io.FileInputStream;
6 import java.io.IOException;
7 import java.io.InputStreamReader;
8 import java.io.Reader;
9 import java.net.IDN;
10 import java.net.URL;
11 import java.util.Collections;
12 import java.util.HashSet;
13 import java.util.Properties;
14 import java.util.Set;
15
16 import org.cacert.gigi.GigiApiException;
17
18 public class DomainAssessment {
19
20     private static class DomainSet {
21
22         private final Set<String> data;
23
24         public DomainSet(URL u) {
25             this(openStream(u));
26         }
27
28         private static Reader openStream(URL u) {
29             try {
30                 return new InputStreamReader(u.openStream(), "UTF-8");
31             } catch (IOException e) {
32                 throw new Error(e);
33             }
34         }
35
36         public DomainSet(Reader r) {
37             HashSet<String> contents = new HashSet<>();
38             try {
39                 BufferedReader br = new BufferedReader(r);
40                 String line;
41                 while ((line = br.readLine()) != null) {
42                     if (line.startsWith("#")) {
43                         continue;
44                     }
45                     if (line.isEmpty()) {
46                         continue;
47                     }
48                     contents.add(line);
49                 }
50             } catch (IOException e) {
51                 throw new Error(e);
52             }
53             data = Collections.unmodifiableSet(contents);
54         }
55
56         public boolean contains(String suffix) {
57             while (suffix.contains(".")) {
58                 if (data.contains(suffix)) {
59                     return true;
60                 }
61                 suffix = suffix.substring(suffix.indexOf('.') + 1);
62             }
63             return data.contains(suffix);
64         }
65     }
66
67     private static DomainSet financial;
68
69     private static final DomainSet idn_enabled = new DomainSet(DomainAssessment.class.getResource("idn_enabled.dat"));
70
71     public static boolean isHighFinancialValue(String suffix) {
72         return financial.contains(suffix);
73     }
74
75     public static void checkCertifiableDomain(String domain, boolean hasPunycodeRight) throws GigiApiException {
76         if (isHighFinancialValue(domain)) {
77             throw new GigiApiException("Domain blocked for automatic adding.");
78         }
79         String[] parts = domain.split("\\.", -1);
80         if (parts.length < 2) {
81             throw new GigiApiException("Domain does not contain '.'.");
82         }
83         boolean neededPunycode = false;
84         for (int i = parts.length - 1; i >= 0; i--) {
85             if ( !isValidDomainPart(parts[i])) {
86                 throw new GigiApiException("Syntax error in Domain.");
87             }
88             boolean canBePunycode = parts[i].length() >= 4 && parts[i].charAt(2) == '-' && parts[i].charAt(3) == '-';
89             if (canBePunycode) {
90                 if ( !hasPunycodeRight) {
91                     throw new GigiApiException("Punycode domain without specific right.");
92                 }
93                 punycodeDecode(parts[i]);
94                 neededPunycode = true;
95             }
96
97         }
98         if (neededPunycode && !idn_enabled.contains(domain)) {
99             throw new GigiApiException("Punycode not allowed under this TLD.");
100         }
101
102         String publicSuffix = PublicSuffixes.getInstance().getRegistrablePart(domain);
103         if ( !domain.equals(publicSuffix)) {
104             throw new GigiApiException("You may only register a domain with exactly one label before the public suffix.");
105         }
106
107         if (("." + domain).matches("(\\.[0-9]*)*")) {
108             // This is not reached because we currently have no TLD that is
109             // numbers only. But who knows..
110             // Better safe than sorry.
111             throw new GigiApiException("IP Addresses are not allowed.");
112         }
113     }
114
115     private static String punycodeDecode(String label) throws GigiApiException {
116         if (label.charAt(2) != '-' || label.charAt(3) != '-') {
117             return label; // is no punycode
118         }
119         if ( !label.startsWith("xn--")) {
120             throw new GigiApiException("Unknown ACE prefix.");
121         }
122         try {
123             String unicode = IDN.toUnicode(label);
124             if (unicode.startsWith("xn--")) {
125                 throw new GigiApiException("Punycode label could not be positively verified.");
126             }
127             return unicode;
128         } catch (IllegalArgumentException e) {
129             throw new GigiApiException("Punycode label could not be positively verified.");
130         }
131     }
132
133     public static boolean isValidDomainPart(String s) {
134         if ( !s.matches("[a-z0-9-]+")) {
135             return false;
136         }
137         if (s.charAt(0) == '-' || s.charAt(s.length() - 1) == '-') {
138             return false;
139         }
140         if (s.length() > 63) {
141             return false;
142         }
143         return true;
144     }
145
146     public static void init(Properties conf) {
147         String financialName = conf.getProperty("highFinancialValue");
148         if (financialName == null) {
149             throw new Error("No property highFinancialValue was configured");
150         }
151         try {
152             financial = new DomainSet(new InputStreamReader(new FileInputStream(new File(financialName)), "UTF-8"));
153         } catch (IOException e) {
154             throw new Error(e);
155         }
156     }
157 }