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}