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