001/*
002 * acme4j - Java ACME client
003 *
004 * Copyright (C) 2018 Richard "Shred" Körber
005 *   http://acme4j.shredzone.org
006 *
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
013 */
014package org.shredzone.acme4j;
015
016import static java.util.Objects.requireNonNull;
017import static org.shredzone.acme4j.toolbox.AcmeUtils.toAce;
018
019import java.io.Serializable;
020import java.net.InetAddress;
021import java.net.UnknownHostException;
022import java.util.Map;
023
024import org.shredzone.acme4j.exception.AcmeProtocolException;
025import org.shredzone.acme4j.toolbox.JSON;
026import org.shredzone.acme4j.toolbox.JSONBuilder;
027
028/**
029 * Represents an identifier.
030 * <p>
031 * The ACME protocol only defines the DNS identifier, which identifies a domain name.
032 * acme4j also supports IP identifiers.
033 * <p>
034 * CAs may define further, proprietary identifier types.
035 *
036 * @since 2.3
037 */
038public class Identifier implements Serializable {
039    private static final long serialVersionUID = -7777851842076362412L;
040
041    /**
042     * Type constant for DNS identifiers.
043     */
044    public static final String TYPE_DNS = "dns";
045
046    /**
047     * Type constant for IP identifiers.
048     *
049     * @see <a href="https://tools.ietf.org/html/rfc8738">RFC 8738</a>
050     */
051    public static final String TYPE_IP = "ip";
052
053    private static final String KEY_TYPE = "type";
054    private static final String KEY_VALUE = "value";
055
056    private final String type;
057    private final String value;
058
059    /**
060     * Creates a new {@link Identifier}.
061     * <p>
062     * This is a generic constructor for identifiers. Refer to the documentation of your
063     * CA to find out about the accepted identifier types and values.
064     * <p>
065     * Note that for DNS identifiers, no ASCII encoding of unicode domain takes place
066     * here. Use {@link #dns(String)} instead.
067     *
068     * @param type
069     *            Identifier type
070     * @param value
071     *            Identifier value
072     */
073    public Identifier(String type, String value) {
074        this.type = requireNonNull(type, KEY_TYPE);
075        this.value = requireNonNull(value, KEY_VALUE);
076    }
077
078    /**
079     * Creates a new {@link Identifier} from the given {@link JSON} structure.
080     *
081     * @param json
082     *            {@link JSON} containing the identifier data
083     */
084    public Identifier(JSON json) {
085        this(json.get(KEY_TYPE).asString(), json.get(KEY_VALUE).asString());
086    }
087
088    /**
089     * Creates a new DNS identifier for the given domain name.
090     *
091     * @param domain
092     *            Domain name. Unicode domains are automatically ASCII encoded.
093     * @return New {@link Identifier}
094     */
095    public static Identifier dns(String domain) {
096        return new Identifier(TYPE_DNS, toAce(domain));
097    }
098
099    /**
100     * Creates a new IP identifier for the given {@link InetAddress}.
101     *
102     * @param ip
103     *            {@link InetAddress}
104     * @return New {@link Identifier}
105     */
106    public static Identifier ip(InetAddress ip) {
107        return new Identifier(TYPE_IP, ip.getHostAddress());
108    }
109
110    /**
111     * Creates a new IP identifier for the given {@link InetAddress}.
112     *
113     * @param ip
114     *            IP address as {@link String}
115     * @return New {@link Identifier}
116     * @since 2.7
117     */
118    public static Identifier ip(String ip) {
119        try {
120            return ip(InetAddress.getByName(ip));
121        } catch (UnknownHostException ex) {
122            throw new IllegalArgumentException("Bad IP: " + ip, ex);
123        }
124    }
125
126    /**
127     * Returns the identifier type.
128     */
129    public String getType() {
130        return type;
131    }
132
133    /**
134     * Returns the identifier value.
135     */
136    public String getValue() {
137        return value;
138    }
139
140    /**
141     * Returns the domain name if this is a DNS identifier.
142     *
143     * @return Domain name. Unicode domains are ASCII encoded.
144     * @throws AcmeProtocolException
145     *             if this is not a DNS identifier.
146     */
147    public String getDomain() {
148        if (!TYPE_DNS.equals(type)) {
149            throw new AcmeProtocolException("expected 'dns' identifier, but found '" + type + "'");
150        }
151        return value;
152    }
153
154    /**
155     * Returns the IP address if this is an IP identifier.
156     *
157     * @return {@link InetAddress}
158     * @throws AcmeProtocolException
159     *             if this is not a DNS identifier.
160     */
161    public InetAddress getIP() {
162        if (!TYPE_IP.equals(type)) {
163            throw new AcmeProtocolException("expected 'ip' identifier, but found '" + type + "'");
164        }
165        try {
166            return InetAddress.getByName(value);
167        } catch (UnknownHostException ex) {
168            throw new AcmeProtocolException("bad ip identifier value", ex);
169        }
170    }
171
172    /**
173     * Returns the identifier as JSON map.
174     */
175    public Map<String, Object> toMap() {
176        return new JSONBuilder().put(KEY_TYPE, type).put(KEY_VALUE, value).toMap();
177    }
178
179    @Override
180    public String toString() {
181        return type + "=" + value;
182    }
183
184    @Override
185    public boolean equals(Object obj) {
186        if (!(obj instanceof Identifier)) {
187            return false;
188        }
189
190        Identifier i = (Identifier) obj;
191        return type.equals(i.type) && value.equals(i.value);
192    }
193
194    @Override
195    public int hashCode() {
196        return type.hashCode() ^ value.hashCode();
197    }
198
199}