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}