1 package com.verisign.cl;
3 import java.io.BufferedReader;
4 import java.io.FileReader;
5 import java.io.IOException;
6 import java.net.SocketTimeoutException;
9 import org.xbill.DNS.*;
11 import com.verisign.tat.dnssec.CaptiveValidator;
12 import com.verisign.tat.dnssec.SecurityStatus;
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 Set<Name> zoneNames;
29 public String queryFile;
30 public String dnskeyFile;
31 public List<String> dnskeyNames;
34 validator = new CaptiveValidator();
38 * Convert a query line of the form: <qname> <qtype> <flags> to a request
42 * @return A query message
43 * @throws TextParseException
44 * @throws NameTooLongException
46 private Message queryFromString(String query_line)
47 throws TextParseException, NameTooLongException {
49 String[] tokens = query_line.split("[ \t]+");
55 if (tokens.length < 1)
57 qname = Name.fromString(tokens[0]);
58 if (!qname.isAbsolute()) {
59 qname = Name.concatenate(qname, Name.root);
62 for (int i = 1; i < tokens.length; i++) {
63 if (tokens[i].startsWith("+")) {
64 // For now, we ignore flags as uninteresting
68 int type = Type.value(tokens[i]);
73 int cls = DClass.value(tokens[i]);
86 Message query = Message
87 .newQuery(Record.newRecord(qname, qtype, qclass));
93 * Fetch the next query from either the command line or the query file
95 * @return a query Message, or null if the query list is exhausted
98 private Message nextQuery() throws IOException {
100 Message res = queryFromString(query);
105 if (queryStream == null && queryFile != null) {
106 queryStream = new BufferedReader(new FileReader(queryFile));
109 if (queryStream != null) {
110 String line = queryStream.readLine();
115 return queryFromString(line);
123 * Figure out the correct zone from the query by comparing the qname to the
124 * list of trusted DNSKEY owner names.
127 * @return a zone name
128 * @throws IOException
130 private Name zoneFromQuery(Message query) throws IOException {
132 if (zoneNames == null) {
133 zoneNames = new HashSet<Name>();
134 for (String key : validator.listTrustedKeys()) {
135 String[] components = key.split("/");
136 Name keyname = Name.fromString(components[0]);
137 if (! keyname.isAbsolute()) {
138 keyname = Name.concatenate(keyname, Name.root);
140 zoneNames.add(keyname);
144 Name qname = query.getQuestion().getName();
145 for (Name n : zoneNames) {
146 if (qname.subdomain(n)) {
154 private Message resolve(Message query) {
157 return resolver.send(query);
158 } catch (SocketTimeoutException e) {
159 System.err.println("Error: timed out querying " + server + " for " + queryToString(query));
160 } catch (IOException e) {
161 System.err.println("Error: error querying " + server + " for " + queryToString(query) + ":" + e.getMessage());
166 private String queryToString(Message query) {
167 if (query == null) return null;
168 Record question = query.getQuestion();
169 return question.getName() + "/" + Type.string(question.getType()) + "/" + DClass.string(question.getDClass());
172 public void execute() throws IOException {
173 // Configure our resolver
174 resolver = new SimpleResolver(server);
175 resolver.setEDNS(0, 4096, Flags.DO, null);
177 // Prime the validator
178 if (dnskeyFile != null) {
179 validator.addTrustedKeysFromFile(dnskeyFile);
181 for (String name : dnskeyNames) {
182 Message query = queryFromString(name + " DNSKEY");
183 Message response = resolve(query);
184 validator.addTrustedKeysFromResponse(response);
188 // Log our set of trusted keys
189 for (String key : validator.listTrustedKeys()) {
190 System.out.println("Trusted Key: " + key);
193 // Iterate over all queries
194 Message query = nextQuery();
197 while (query != null) {
199 Name zone = zoneFromQuery(query);
200 // Skip queries in zones that we don't have keys for
205 Message response = resolve(query);
206 if (response == null) {
209 byte result = validator.validateMessage(response, zone.toString());
212 case SecurityStatus.BOGUS:
213 case SecurityStatus.INVALID:
214 System.out.println("BOGUS Answer:");
215 System.out.println("Query: " + queryToString(query));
216 System.out.println("Response:\n" + response);
217 for (String err : validator.getErrorList()) {
218 System.out.println("Error: " + err);
220 System.out.println("");
222 case SecurityStatus.INSECURE:
223 case SecurityStatus.INDETERMINATE:
224 case SecurityStatus.UNCHECKED:
225 System.out.println("Insecure Answer:");
226 System.out.println("Query: " + queryToString(query));
227 System.out.println("Response:\n" + response);
228 for (String err : validator.getErrorList()) {
229 System.out.println("Error: " + err);
232 case SecurityStatus.SECURE:
236 if (++count % 1000 == 0) {
237 System.out.println("Completed " + count + " queries.");
243 System.out.println("Completed " + count + (count > 1 ? " queries" : " query"));
246 private static void usage() {
248 .println("usage: java -jar dnssecreconiler.jar [..options..]");
249 System.err.println(" server: the DNS server to query.");
250 System.err.println(" query: a name [type [flags]] string.");
252 .println(" query_file: a list of queries, one query per line.");
254 .println(" dnskey_file: a file containing DNSKEY RRs to trust.");
256 .println(" dnskey_query: query 'server' for DNSKEY at given name to trust, may repeat");
259 public static void main(String[] argv) {
261 DNSSECReconciler dr = new DNSSECReconciler();
264 // Parse the command line options
265 for (String arg : argv) {
267 if (arg.indexOf('=') < 0) {
268 System.err.println("Unrecognized option: " + arg);
273 String[] split_arg = arg.split("=", 2);
274 String opt = split_arg[0];
275 String optarg = split_arg[1];
277 if (opt.equals("server")) {
279 } else if (opt.equals("query")) {
281 } else if (opt.equals("query_file")) {
282 dr.queryFile = optarg;
283 } else if (opt.equals("dnskey_file")) {
284 dr.dnskeyFile = optarg;
285 } else if (opt.equals("dnskey_query")) {
286 if (dr.dnskeyNames == null) {
287 dr.dnskeyNames = new ArrayList<String>();
289 dr.dnskeyNames.add(optarg);
291 System.err.println("Unrecognized option: " + opt);
297 // Check for minimum usage
298 if (dr.server == null) {
299 System.err.println("'server' must be specified");
303 if (dr.query == null && dr.queryFile == null) {
305 .println("Either 'query' or 'query_file' must be specified");
309 if (dr.dnskeyFile == null && dr.dnskeyNames == null) {
311 .println("Either 'dnskey_file' or 'dnskey_query' must be specified");
319 } catch (Exception e) {