All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.id4me.Id4meResolver Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2016-2020 OX Software GmbH
 * Developed by Peter Höbel [email protected]
 * See the LICENSE file for licensing conditions
 * SPDX-License-Identifier: MIT
*/

package org.id4me;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.IDN;
import java.net.UnknownHostException;
import java.security.MessageDigest;
import java.util.Iterator;

import org.jitsi.dnssec.validator.ValidatingResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xbill.DNS.DClass;
import org.xbill.DNS.Flags;
import org.xbill.DNS.Message;
import org.xbill.DNS.Name;
import org.xbill.DNS.RRset;
import org.xbill.DNS.Rcode;
import org.xbill.DNS.Record;
import org.xbill.DNS.Resolver;
import org.xbill.DNS.Section;
import org.xbill.DNS.SimpleResolver;
import org.xbill.DNS.Type;

/**
 * This class implements methods needed to lookup the dns record for ID4me and
 * verify DANE.
 * 
 * @author phoebel
 *
 */
public class Id4meResolver {

	private static final Logger log = LoggerFactory.getLogger(Id4meResolver.class);

	private final Resolver vr;
	private final boolean dnssecRequired;

	/**
	 * Instanciate the validating resolver object.
	 * 
	 * @param dnsServer
	 *            a dnssec enabled dns server address to use or if null is localhost
	 *            used
	 * @param rootKey
	 *            for the dns resolver to validate dnssec
	 * @param dnssecRequired
	 *            whether DNSSEC is required
	 * @throws UnknownHostException,
	 *             UnsupportedEncodingException, IOException
	 */
	public Id4meResolver(String dnsServer, String rootKey, boolean dnssecRequired)
			throws UnknownHostException, UnsupportedEncodingException, IOException {

		SimpleResolver sr = dnsServer != null ? new SimpleResolver(dnsServer) : new SimpleResolver();

		if (dnssecRequired) {
			vr = new ValidatingResolver(sr);
			((ValidatingResolver) vr).loadTrustAnchors(new ByteArrayInputStream(rootKey.getBytes("ASCII")));
		} else {
			vr = sr;
		}

		this.dnssecRequired = dnssecRequired;
		log.debug("Id4meResolver created for DNS server: {}", dnsServer);
	}

	/**
	 * Lookup the dns for a given id4me and verifies the data. Returns the DNS
	 * response and a login hint, if the id4me and the dns value are valid.
	 * 
	 * @param id4me
	 *            the user to logon
	 * @return the DNS response containing the attributes version, iau and iag; and
	 *         a login hint
	 * @throws Exception
	 *             if something fails
	 */
	public Id4meDnsDataWithLoginHint getDataFromDns(String id4me) throws Exception {
		if (id4me.indexOf(".") > 0) {
			if (!Id4meValidator.isValidUserid(IDN.toASCII(id4me))) {
				log.info("ID4me identifier has wrong format: {}", id4me);
				throw new Exception("ID4me identifier has wrong format: " + id4me);
			}
		} else {
			if (!Id4meValidator.isValidUserid(id4me)) {
				log.info("ID4me identifier has wrong format: {}", id4me);
				throw new Exception("ID4me identifier has wrong format: " + id4me);
			}
		}

		String loginHint;
		int atPos = id4me.indexOf('@');
		if (atPos > 0) {
			loginHint = id4me;
			String localPart = id4me.substring(0, atPos);
			String domain = id4me.substring(atPos + 1, id4me.length());
			id4me = "_openid." + sha256(localPart) + "." + domain;
//			loginHint = localPart + "." + domain;
		} else {
			loginHint = id4me;
			id4me = "_openid." + id4me;
		}

		String domain = id4me.endsWith(".") ? id4me : id4me + ".";
		domain = IDN.toASCII(domain);
		log.info("Get data from DNS: domain: {}", domain);

		LookupResponse response = lookupDnssec(domain);
		String data = null;
		if (response != null)
			data = response.getData();

		if (response == null || data == null || data.trim().equals("")) {
			log.info("No resource record found in DNS for domain: {}", domain);
			String[] fields = domain.split("\\.");
			if (fields.length > 2) {
				int start = 2;
				for (; start < fields.length; start++) {
					domain = fields[start];
					for (int i = start + 1; i < fields.length; i++) {
						domain += "." + fields[i];
					}
					domain = "_openid." + domain + ".";
					response = lookupDnssec(domain);
					if (response != null)
						data = response.getData();
					if (data != null && !data.trim().equals("")) {
						break;
					}
					log.info("No resource record found in DNS for domain: {}", domain);

				}
				if (data == null || data.trim().equals(""))
					throw new Exception("No resource record found in DNS for domain: " + domain);
			}
		}

		log.info("Get data from DNS: data retrieved: {}", data);

		if (dnssecRequired && !response.isDnssec()) {
			log.info("Error getting domain-id data from DNS: DNSSFLAG == false");
			throw new Exception("Error getting domain-id data from DNS: DNSSFLAG == false");
		}

		Id4meDnsData dnsResponse = Id4meDnsResponseParser.parseDnsResponse(data);
		return new Id4meDnsDataWithLoginHint(dnsResponse, loginHint);
	}

	private String sha256(String str) throws Exception {
		MessageDigest md = MessageDigest.getInstance("SHA-256");
		md.update(str.getBytes());
		byte[] digest = md.digest();
		String hash = String.format("%064x", new java.math.BigInteger(1, digest));

		String sha256 = hash.substring(0, 56);
		log.debug("sha256(\"{}\") = \"{}\"", str, sha256);

		return sha256;
	}

	public static class Id4meDnsDataWithLoginHint {
		private final Id4meDnsData dnsResponse;
		private final String loginHint;

		Id4meDnsDataWithLoginHint(Id4meDnsData dnsResponse, String loginHint) {
			this.dnsResponse = dnsResponse;
			this.loginHint = loginHint;
		}

		public Id4meDnsData getDnsResponse() {
			return dnsResponse;
		}

		String getLoginHint() {
			return loginHint;
		}
	}

	/**
	 * Perform a dns TXT record lookup with a dnssec validating resolver
	 * 
	 * @param name
	 *            the domain to lookup
	 * @return dns record data field and DNSSEC flag
	 * @throws IOException
	 */
	private LookupResponse lookupDnssec(String name) throws IOException {
		return lookup(vr, name);
	}

	/**
	 * Perform a dns TLSA record lookup with a dnssec validating resolver. This data
	 * is used to validate the DANE status of the current TLS connection.
	 * 
	 * @param name
	 *            the domain to lookup
	 * @return dns record data field and DNSSEC flag
	 * @throws IOException
	 */
	LookupResponse lookupDane(String name) throws IOException {
		return lookupWithType(vr, name, Type.TLSA);
	}

	@SuppressWarnings("unchecked")
	private LookupResponse lookup(Resolver resolver, String name) throws IOException {
		log.debug("DNS lookup: {}", name);
		Record qr = Record.newRecord(Name.fromConstantString(name), Type.TXT, DClass.IN);
		Message response = resolver.send(Message.newQuery(qr));

		boolean dnssec = response.getHeader().getFlag(Flags.AD);
		String rcode = Rcode.string(response.getRcode());
		if (!"NOERROR".equals(rcode)) {
			log.warn("DNS lookup: response error: {}", rcode);
			return null;
		}

		RRset[] answer = response.getSectionRRsets(Section.ANSWER);
		for (RRset set : answer) {
			Iterator rrIter = set.rrs();
			while (rrIter.hasNext()) {
				final Record rec = rrIter.next();
				if (rec.getType() == Type.TXT) {
					String data = rec.rdataToString();
					log.debug("DNS lookup: response: {}", data);
					return new LookupResponse(data.replace("\"", ""), dnssec);
				}
			}
		}

		String data = qr.rdataToString();
		log.debug("DNS lookup: response: {}", data);
		return new LookupResponse(data, dnssec);
	}

	@SuppressWarnings("unchecked")
	private LookupResponse lookupWithType(Resolver resolver, String name, int type) throws IOException {
		log.debug("DNS lookup: {}; type: {}", name, type);
		Record qr = Record.newRecord(Name.fromConstantString(name), Type.ANY, DClass.IN);
		Message response = resolver.send(Message.newQuery(qr));
		boolean dnssec = response.getHeader().getFlag(Flags.AD);
		String rcode = Rcode.string(response.getRcode());
		if (!"NOERROR".equals(rcode)) {
			log.warn("DNS lookup: response error: {}", rcode);
			return null;
		}

		RRset[] answer = response.getSectionRRsets(Section.ANSWER);
		for (RRset set : answer) {
			Iterator rrIter = set.rrs();
			while (rrIter.hasNext()) {
				final Record rec = rrIter.next();
				if (rec.getType() == type) {
					String data = rec.rdataToString();
					log.debug("DNS lookup: response: {}", data);
					return new LookupResponse(data.replace("\"", ""), dnssec);
				}
				if (rec.getType() == Type.CNAME) {
					String data = rec.rdataToString();
					log.debug("DNS lookup: response (CNAME): {}", data);
					return lookupWithType(resolver, data.replace("\"", ""), type);
				}
			}
		}

		String data = qr.rdataToString();
		log.debug("DNS lookup: response: {}", data);
		return new LookupResponse(data, dnssec);
	}

	class LookupResponse {
		private final String data;
		private final boolean dnssec;

		LookupResponse(String data, boolean dnssec) {
			this.data = data;
			this.dnssec = dnssec;
		}

		String getData() {
			return data;
		}

		boolean isDnssec() {
			return dnssec;
		}
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy