1 package com.verisign.cl;
4 import java.net.SocketTimeoutException;
7 import org.apache.log4j.BasicConfigurator;
8 import org.apache.log4j.Level;
9 import org.apache.log4j.Logger;
10 import org.xbill.DNS.*;
12 import com.verisign.tat.dnssec.CaptiveValidator;
13 import com.verisign.tat.dnssec.SecurityStatus;
14 import com.verisign.tat.dnssec.Util;
16 public class DNSSECValTool {
19 * Invoke with java -jar dnssecreconciler.jar server=127.0.0.1 \
20 * query_file=queries.txt dnskey_query=net dnskey_query=edu
22 private CaptiveValidator validator;
23 private SimpleResolver resolver;
25 private BufferedReader queryStream;
26 private PrintStream errorStream;
27 private Set<Name> zoneNames;
32 public String queryFile;
33 public String dnskeyFile;
34 public List<String> dnskeyNames;
35 public String errorFile;
36 public long count = 0;
37 public boolean debug = false;
40 validator = new CaptiveValidator();
44 * Convert a query line of the form: <qname> <qtype> <flags> to a request
48 * @return A query message
49 * @throws TextParseException
50 * @throws NameTooLongException
52 private Message queryFromString(String query_line)
53 throws TextParseException, NameTooLongException {
55 String[] tokens = query_line.split("[ \t]+");
61 if (tokens.length < 1)
63 qname = Name.fromString(tokens[0]);
64 if (!qname.isAbsolute()) {
65 qname = Name.concatenate(qname, Name.root);
68 for (int i = 1; i < tokens.length; i++) {
69 if (tokens[i].startsWith("+")) {
70 // For now, we ignore flags as uninteresting
71 // All queries will get the DO bit anyway
75 int type = Type.value(tokens[i]);
80 int cls = DClass.value(tokens[i]);
93 Message query = Message.newQuery(Record.newRecord(qname, qtype, qclass));
99 * Fetch the next query from either the command line or the query file
101 * @return a query Message, or null if the query list is exhausted
102 * @throws IOException
104 private Message nextQuery() throws IOException {
106 Message res = queryFromString(query);
111 if (queryStream == null && queryFile != null) {
112 queryStream = new BufferedReader(new FileReader(queryFile));
115 if (queryStream != null) {
116 String line = queryStream.readLine();
121 return queryFromString(line);
129 * Figure out the correct zone from the query by comparing the qname to the
130 * list of trusted DNSKEY owner names.
133 * @return a zone name
134 * @throws IOException
136 private Name zoneFromQuery(Message query) throws IOException {
138 if (zoneNames == null) {
139 zoneNames = new HashSet<Name>();
140 for (String key : validator.listTrustedKeys()) {
141 String[] components = key.split("/");
142 Name keyname = Name.fromString(components[0]);
143 if (!keyname.isAbsolute()) {
144 keyname = Name.concatenate(keyname, Name.root);
146 zoneNames.add(keyname);
150 Name qname = query.getQuestion().getName();
151 for (Name n : zoneNames) {
152 if (qname.subdomain(n)) {
160 private Message resolve(Message query) {
163 return resolver.send(query);
164 } catch (SocketTimeoutException e) {
165 System.err.println("Error: timed out querying " + server + " for "
166 + queryToString(query));
167 } catch (IOException e) {
168 System.err.println("Error: error querying " + server + " for "
169 + queryToString(query) + ":" + e.getMessage());
174 private String queryToString(Message query) {
177 Record question = query.getQuestion();
178 return question.getName() + "/" + Type.string(question.getType()) + "/"
179 + DClass.string(question.getDClass());
182 public void execute() throws IOException {
183 // Configure our resolver
184 resolver = new SimpleResolver(server);
185 resolver.setEDNS(0, 4096, Flags.DO, null);
187 // Create our DNSSEC error stream
188 if (errorFile != null) {
189 errorStream = new PrintStream(new FileOutputStream(errorFile, true));
191 errorStream = System.out;
194 // Prime the validator
195 if (dnskeyFile != null) {
196 validator.addTrustedKeysFromFile(dnskeyFile);
198 for (String name : dnskeyNames) {
199 Message query = queryFromString(name + " DNSKEY");
200 Message response = resolve(query);
201 validator.addTrustedKeysFromResponse(response);
205 // Log our set of trusted keys
206 List<String> trustedKeys = validator.listTrustedKeys();
207 if (trustedKeys.size() == 0) {
208 System.err.println("ERROR: no trusted keys found/provided.");
212 for (String key : validator.listTrustedKeys()) {
213 System.out.println("Trusted Key: " + key);
216 // Iterate over all queries
217 Message query = nextQuery();
222 while (query != null) {
224 Name zone = zoneFromQuery(query);
225 // Skip queries in zones that we don't have keys for
228 System.out.println("DEBUG: skipping query " + queryToString(query));
235 System.out.println("DEBUG: querying for: " + queryToString(query));
238 Message response = resolve(query);
239 if (response == null) {
240 System.out.println("ERROR: No response for query: " + queryToString(query));
243 byte result = validator.validateMessage(response, zone.toString());
246 case SecurityStatus.BOGUS:
247 case SecurityStatus.INVALID:
248 errorStream.println("BOGUS Answer:");
249 errorStream.println("Query: " + queryToString(query));
250 errorStream.println("Response:\n" + response);
251 for (String err : validator.getErrorList()) {
252 errorStream.println("Error: " + err);
254 errorStream.println("");
257 case SecurityStatus.INSECURE:
258 case SecurityStatus.INDETERMINATE:
259 case SecurityStatus.UNCHECKED:
260 errorStream.println("Insecure Answer:");
261 errorStream.println("Query: " + queryToString(query));
262 errorStream.println("Response:\n" + response);
263 for (String err : validator.getErrorList()) {
264 errorStream.println("Error: " + err);
268 case SecurityStatus.SECURE:
269 if (debug) System.out.println("DEBUG: response for " + queryToString(query) + " was valid.");
274 if (++total % 1000 == 0) {
275 System.out.println("Completed " + total + " queries: "
276 + validCount + " valid, " + errorCount + " errors.");
279 if (count > 0 && total >= count) {
280 if (debug) System.out.println("DEBUG: reached maximum number of queries, exiting");
287 System.out.println("Completed " + total
288 + (total > 1 ? " queries" : " query") +
289 ": " + validCount + " valid, " + errorCount + " errors.");
292 private static void usage() {
293 System.err.println("usage: java -jar dnssecvaltool.jar [..options..]");
294 System.err.println(" server: the DNS server to query.");
295 System.err.println(" query: a name [type [flags]] string.");
296 System.err.println(" query_file: a list of queries, one query per line.");
297 System.err.println(" count: send up to'count' queries, then stop.");
298 System.err.println(" dnskey_file: a file containing DNSKEY RRs to trust.");
299 System.err.println(" dnskey_query: query 'server' for DNSKEY at given name to trust, may repeat.");
300 System.err.println(" error_file: write DNSSEC validation failure details to this file.");
303 public static void main(String[] argv) {
305 // Set up Log4J to just log to console.
306 BasicConfigurator.configure();
307 // And raise the log level quite high
308 Logger rootLogger = Logger.getRootLogger();
309 rootLogger.setLevel(Level.FATAL);
311 DNSSECValTool dr = new DNSSECValTool();
314 // Parse the command line options
315 for (String arg : argv) {
317 if (arg.indexOf('=') < 0) {
318 System.err.println("Unrecognized option: " + arg);
323 String[] split_arg = arg.split("=", 2);
324 String opt = split_arg[0];
325 String optarg = split_arg[1];
327 if (opt.equals("server")) {
329 } else if (opt.equals("query")) {
331 } else if (opt.equals("query_file")) {
332 dr.queryFile = optarg;
333 } else if (opt.equals("count")) {
334 dr.count = Util.parseInt(optarg, 0);
335 } else if (opt.equals("error_file")) {
336 dr.errorFile = optarg;
337 } else if (opt.equals("dnskey_file")) {
338 dr.dnskeyFile = optarg;
339 } else if (opt.equals("dnskey_query")) {
340 if (dr.dnskeyNames == null) {
341 dr.dnskeyNames = new ArrayList<String>();
343 dr.dnskeyNames.add(optarg);
344 } else if (opt.equals("debug")) {
345 dr.debug = Boolean.parseBoolean(optarg);
346 rootLogger.setLevel(Level.TRACE);
348 System.err.println("Unrecognized option: " + opt);
354 // Check for minimum usage
355 if (dr.server == null) {
356 System.err.println("'server' must be specified");
360 if (dr.query == null && dr.queryFile == null) {
361 System.err.println("Either 'query' or 'query_file' must be specified");
365 if (dr.dnskeyFile == null && dr.dnskeyNames == null) {
366 System.err.println("Either 'dnskey_file' or 'dnskey_query' must be specified");
374 } catch (Exception e) {