001/* 002 * Copyright 2019 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2019 Ping Identity Corporation 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU General Public License (GPLv2 only) 010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 011 * as published by the Free Software Foundation. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Public License for more details. 017 * 018 * You should have received a copy of the GNU General Public License 019 * along with this program; if not, see <http://www.gnu.org/licenses>. 020 */ 021package com.unboundid.ldap.listener; 022 023 024 025import java.io.ByteArrayOutputStream; 026import java.io.File; 027import java.net.InetAddress; 028import java.security.SecureRandom; 029import java.text.SimpleDateFormat; 030import java.util.ArrayList; 031import java.util.Date; 032import java.util.Set; 033 034import com.unboundid.ldap.sdk.DN; 035import com.unboundid.ldap.sdk.LDAPConnectionOptions; 036import com.unboundid.ldap.sdk.NameResolver; 037import com.unboundid.ldap.sdk.RDN; 038import com.unboundid.ldap.sdk.ResultCode; 039import com.unboundid.util.Base64; 040import com.unboundid.util.Debug; 041import com.unboundid.util.ObjectPair; 042import com.unboundid.util.StaticUtils; 043import com.unboundid.util.ThreadSafety; 044import com.unboundid.util.ThreadSafetyLevel; 045import com.unboundid.util.ssl.cert.CertException; 046import com.unboundid.util.ssl.cert.ManageCertificates; 047 048import static com.unboundid.ldap.listener.ListenerMessages.*; 049 050 051 052/** 053 * This class provides a mechanism for generating a self-signed certificate for 054 * use by a listener that supports SSL or StartTLS. 055 */ 056@ThreadSafety(level= ThreadSafetyLevel.NOT_THREADSAFE) 057public final class SelfSignedCertificateGenerator 058{ 059 /** 060 * Prevent this utility class from being instantiated. 061 */ 062 private SelfSignedCertificateGenerator() 063 { 064 // No implementation is required. 065 } 066 067 068 069 /** 070 * Generates a temporary keystore containing a self-signed certificate for 071 * use by a listener that supports SSL or StartTLS. 072 * 073 * @param toolName The name of the tool for which the certificate is to 074 * be generated. 075 * @param keyStoreType The key store type for the keystore to be created. 076 * It must not be {@code null}. 077 * 078 * @return An {@code ObjectPair} containing the path and PIN for the keystore 079 * that was generated. 080 * 081 * @throws CertException If a problem occurs while trying to generate the 082 * temporary keystore containing the self-signed 083 * certificate. 084 */ 085 public static ObjectPair<File,char[]> generateTemporarySelfSignedCertificate( 086 final String toolName, 087 final String keyStoreType) 088 throws CertException 089 { 090 final File keyStoreFile; 091 try 092 { 093 keyStoreFile = File.createTempFile("temp-keystore-", ".jks"); 094 } 095 catch (final Exception e) 096 { 097 Debug.debugException(e); 098 throw new CertException( 099 ERR_SELF_SIGNED_CERT_GENERATOR_CANNOT_CREATE_FILE.get( 100 StaticUtils.getExceptionMessage(e)), 101 e); 102 } 103 104 keyStoreFile.delete(); 105 106 final SecureRandom random = new SecureRandom(); 107 final byte[] randomBytes = new byte[50]; 108 random.nextBytes(randomBytes); 109 final String keyStorePIN = Base64.encode(randomBytes); 110 111 generateSelfSignedCertificate(toolName, keyStoreFile, keyStorePIN, 112 keyStoreType, "server-cert"); 113 return new ObjectPair<>(keyStoreFile, keyStorePIN.toCharArray()); 114 } 115 116 117 118 /** 119 * Generates a self-signed certificate in the specified keystore. 120 * 121 * @param toolName The name of the tool for which the certificate is to 122 * be generated. 123 * @param keyStoreFile The path to the keystore file in which the 124 * certificate is to be generated. This must not be 125 * {@code null}, and if the target file exists, then it 126 * must be a JKS or PKCS #12 keystore. If it does not 127 * exist, then at least the parent directory must exist. 128 * @param keyStorePIN The PIN needed to access the keystore. It must not 129 * be {@code null}. 130 * @param keyStoreType The key store type for the keystore to be created, if 131 * it does not already exist. It must not be 132 * {@code null}. 133 * @param alias The alias to use for the certificate in the keystore. 134 * It must not be {@code null}. 135 * 136 * @throws CertException If a problem occurs while trying to generate 137 * self-signed certificate. 138 */ 139 public static void generateSelfSignedCertificate(final String toolName, 140 final File keyStoreFile, 141 final String keyStorePIN, 142 final String keyStoreType, 143 final String alias) 144 throws CertException 145 { 146 // Try to get a set of all addresses associated with the local system and 147 // their corresponding canonical hostnames. 148 final NameResolver nameResolver = 149 LDAPConnectionOptions.DEFAULT_NAME_RESOLVER; 150 final Set<InetAddress> localAddresses = 151 StaticUtils.getAllLocalAddresses(nameResolver); 152 final Set<String> canonicalHostNames = 153 StaticUtils.getAvailableCanonicalHostNames(nameResolver, 154 localAddresses); 155 156 157 // Construct a subject DN for the certificate. 158 final DN subjectDN; 159 if (localAddresses.isEmpty()) 160 { 161 subjectDN = new DN(new RDN("CN", toolName)); 162 } 163 else 164 { 165 subjectDN = new DN( 166 new RDN("CN", 167 nameResolver.getCanonicalHostName( 168 localAddresses.iterator().next())), 169 new RDN("OU", toolName)); 170 } 171 172 173 // Generate a timestamp that corresponds to one day ago. 174 final long oneDayAgoTime = System.currentTimeMillis() - 86_400_000L; 175 final Date oneDayAgoDate = new Date(oneDayAgoTime); 176 final SimpleDateFormat dateFormatter = 177 new SimpleDateFormat("yyyyMMddHHmmss"); 178 final String yesterdayTimeStamp = dateFormatter.format(oneDayAgoDate); 179 180 181 // Build the list of arguments to provide to the manage-certificates tool. 182 final ArrayList<String> argList = new ArrayList<>(30); 183 argList.add("generate-self-signed-certificate"); 184 185 argList.add("--keystore"); 186 argList.add(keyStoreFile.getAbsolutePath()); 187 188 argList.add("--keystore-password"); 189 argList.add(keyStorePIN); 190 191 argList.add("--keystore-type"); 192 argList.add(keyStoreType); 193 194 argList.add("--alias"); 195 argList.add(alias); 196 197 argList.add("--subject-dn"); 198 argList.add(subjectDN.toString()); 199 200 argList.add("--days-valid"); 201 argList.add("3650"); 202 203 argList.add("--validityStartTime"); 204 argList.add(yesterdayTimeStamp); 205 206 argList.add("--key-algorithm"); 207 argList.add("RSA"); 208 209 argList.add("--key-size-bits"); 210 argList.add("2048"); 211 212 argList.add("--signature-algorithm"); 213 argList.add("SHA256withRSA"); 214 215 for (final String hostName : canonicalHostNames) 216 { 217 argList.add("--subject-alternative-name-dns"); 218 argList.add(hostName); 219 } 220 221 for (final InetAddress address : localAddresses) 222 { 223 argList.add("--subject-alternative-name-ip-address"); 224 argList.add(StaticUtils.trimInterfaceNameFromHostAddress( 225 address.getHostAddress())); 226 } 227 228 argList.add("--key-usage"); 229 argList.add("digitalSignature"); 230 argList.add("--key-usage"); 231 argList.add("keyEncipherment"); 232 233 argList.add("--extended-key-usage"); 234 argList.add("server-auth"); 235 argList.add("--extended-key-usage"); 236 argList.add("client-auth"); 237 238 final ByteArrayOutputStream output = new ByteArrayOutputStream(); 239 final ResultCode resultCode = ManageCertificates.main(null, output, output, 240 argList.toArray(StaticUtils.NO_STRINGS)); 241 if (resultCode != ResultCode.SUCCESS) 242 { 243 throw new CertException( 244 ERR_SELF_SIGNED_CERT_GENERATOR_ERROR_GENERATING_CERT.get( 245 StaticUtils.toUTF8String(output.toByteArray()))); 246 } 247 } 248}