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}