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