1 package com.verisign.cl;
4 import java.net.SocketTimeoutException;
7 import org.apache.log4j.BasicConfigurator;
8 import org.xbill.DNS.*;
10 import com.verisign.tat.dnssec.CaptiveValidator;
11 import com.verisign.tat.dnssec.SecurityStatus;
12 import com.verisign.tat.dnssec.Util;
14 public class DNSSECReconciler {
17 * Invoke with java -jar dnssecreconciler.jar server=127.0.0.1 \
18 * query_file=queries.txt dnskey_query=net dnskey_query=edu
20 private CaptiveValidator validator;
21 private SimpleResolver resolver;
23 private BufferedReader queryStream;
24 private PrintStream errorStream;
25 private Set<Name> zoneNames;
30 public String queryFile;
31 public String dnskeyFile;
32 public List<String> dnskeyNames;
33 public String errorFile;
34 public long count = 0;
37 validator = new CaptiveValidator();
41 * Convert a query line of the form: <qname> <qtype> <flags> to a request
45 * @return A query message
46 * @throws TextParseException
47 * @throws NameTooLongException
49 private Message queryFromString(String query_line)
50 throws TextParseException, NameTooLongException {
52 String[] tokens = query_line.split("[ \t]+");
58 if (tokens.length < 1)
60 qname = Name.fromString(tokens[0]);
61 if (!qname.isAbsolute()) {
62 qname = Name.concatenate(qname, Name.root);
65 for (int i = 1; i < tokens.length; i++) {
66 if (tokens[i].startsWith("+")) {
67 // For now, we ignore flags as uninteresting
68 // All queries will get the DO bit anyway
72 int type = Type.value(tokens[i]);
77 int cls = DClass.value(tokens[i]);
90 Message query = Message.newQuery(Record.newRecord(qname, qtype, qclass));
96 * Fetch the next query from either the command line or the query file
98 * @return a query Message, or null if the query list is exhausted
101 private Message nextQuery() throws IOException {
103 Message res = queryFromString(query);
108 if (queryStream == null && queryFile != null) {
109 queryStream = new BufferedReader(new FileReader(queryFile));
112 if (queryStream != null) {
113 String line = queryStream.readLine();
118 return queryFromString(line);
126 * Figure out the correct zone from the query by comparing the qname to the
127 * list of trusted DNSKEY owner names.
130 * @return a zone name
131 * @throws IOException
133 private Name zoneFromQuery(Message query) throws IOException {
135 if (zoneNames == null) {
136 zoneNames = new HashSet<Name>();
137 for (String key : validator.listTrustedKeys()) {
138 String[] components = key.split("/");
139 Name keyname = Name.fromString(components[0]);
140 if (!keyname.isAbsolute()) {
141 keyname = Name.concatenate(keyname, Name.root);
143 zoneNames.add(keyname);
147 Name qname = query.getQuestion().getName();
148 for (Name n : zoneNames) {
149 if (qname.subdomain(n)) {
157 private Message resolve(Message query) {
160 return resolver.send(query);
161 } catch (SocketTimeoutException e) {
162 System.err.println("Error: timed out querying " + server + " for "
163 + queryToString(query));
164 } catch (IOException e) {
165 System.err.println("Error: error querying " + server + " for "
166 + queryToString(query) + ":" + e.getMessage());
171 private String queryToString(Message query) {
174 Record question = query.getQuestion();
175 return question.getName() + "/" + Type.string(question.getType()) + "/"
176 + DClass.string(question.getDClass());
179 public void execute() throws IOException {
180 // Configure our resolver
181 resolver = new SimpleResolver(server);
182 resolver.setEDNS(0, 4096, Flags.DO, null);
184 // Create our DNSSEC error stream
185 if (errorFile != null) {
186 errorStream = new PrintStream(new FileOutputStream(errorFile, true));
188 errorStream = System.out;
191 // Prime the validator
192 if (dnskeyFile != null) {
193 validator.addTrustedKeysFromFile(dnskeyFile);
195 for (String name : dnskeyNames) {
196 Message query = queryFromString(name + " DNSKEY");
197 Message response = resolve(query);
198 validator.addTrustedKeysFromResponse(response);
202 // Log our set of trusted keys
203 for (String key : validator.listTrustedKeys()) {
204 System.out.println("Trusted Key: " + key);
207 // Iterate over all queries
208 Message query = nextQuery();
213 while (query != null) {
215 Name zone = zoneFromQuery(query);
216 // Skip queries in zones that we don't have keys for
221 Message response = resolve(query);
222 if (response == null) {
225 byte result = validator.validateMessage(response, zone.toString());
228 case SecurityStatus.BOGUS:
229 case SecurityStatus.INVALID:
230 errorStream.println("BOGUS Answer:");
231 errorStream.println("Query: " + queryToString(query));
232 errorStream.println("Response:\n" + response);
233 for (String err : validator.getErrorList()) {
234 errorStream.println("Error: " + err);
236 errorStream.println("");
239 case SecurityStatus.INSECURE:
240 case SecurityStatus.INDETERMINATE:
241 case SecurityStatus.UNCHECKED:
242 errorStream.println("Insecure Answer:");
243 errorStream.println("Query: " + queryToString(query));
244 errorStream.println("Response:\n" + response);
245 for (String err : validator.getErrorList()) {
246 errorStream.println("Error: " + err);
250 case SecurityStatus.SECURE:
255 if (++total % 1000 == 0) {
256 System.out.println("Completed " + total + " queries: "
257 + validCount + " valid, " + errorCount + " errors.");
260 if (count > 0 && total >= count) {
267 System.out.println("Completed " + total
268 + (total > 1 ? " queries" : " query") +
269 ": " + validCount + " valid, " + errorCount + " errors.");
272 private static void usage() {
273 System.err.println("usage: java -jar dnssecreconiler.jar [..options..]");
274 System.err.println(" server: the DNS server to query.");
275 System.err.println(" query: a name [type [flags]] string.");
276 System.err.println(" query_file: a list of queries, one query per line.");
277 System.err.println(" count: send up to'count' queries, then stop.");
278 System.err.println(" dnskey_file: a file containing DNSKEY RRs to trust.");
279 System.err.println(" dnskey_query: query 'server' for DNSKEY at given name to trust, may repeat.");
280 System.err.println(" error_file: write DNSSEC validation failure details to this file.");
283 public static void main(String[] argv) {
285 // Set up Log4J to just log to console.
286 BasicConfigurator.configure();
288 DNSSECReconciler dr = new DNSSECReconciler();
291 // Parse the command line options
292 for (String arg : argv) {
294 if (arg.indexOf('=') < 0) {
295 System.err.println("Unrecognized option: " + arg);
300 String[] split_arg = arg.split("=", 2);
301 String opt = split_arg[0];
302 String optarg = split_arg[1];
304 if (opt.equals("server")) {
306 } else if (opt.equals("query")) {
308 } else if (opt.equals("query_file")) {
309 dr.queryFile = optarg;
310 } else if (opt.equals("count")) {
311 dr.count = Util.parseInt(optarg, 0);
312 } else if (opt.equals("error_file")) {
313 dr.errorFile = optarg;
314 } else if (opt.equals("dnskey_file")) {
315 dr.dnskeyFile = optarg;
316 } else if (opt.equals("dnskey_query")) {
317 if (dr.dnskeyNames == null) {
318 dr.dnskeyNames = new ArrayList<String>();
320 dr.dnskeyNames.add(optarg);
322 System.err.println("Unrecognized option: " + opt);
328 // Check for minimum usage
329 if (dr.server == null) {
330 System.err.println("'server' must be specified");
334 if (dr.query == null && dr.queryFile == null) {
335 System.err.println("Either 'query' or 'query_file' must be specified");
339 if (dr.dnskeyFile == null && dr.dnskeyNames == null) {
340 System.err.println("Either 'dnskey_file' or 'dnskey_query' must be specified");
348 } catch (Exception e) {