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.NotNull;
057import com.unboundid.util.ObjectPair;
058import com.unboundid.util.StaticUtils;
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 SecureRandom random = new SecureRandom();
124    final byte[] randomBytes = new byte[50];
125    random.nextBytes(randomBytes);
126    final String keyStorePIN = Base64.encode(randomBytes);
127
128    generateSelfSignedCertificate(toolName, keyStoreFile, keyStorePIN,
129         keyStoreType, "server-cert");
130    return new ObjectPair<>(keyStoreFile, keyStorePIN.toCharArray());
131  }
132
133
134
135  /**
136   * Generates a self-signed certificate in the specified keystore.
137   *
138   * @param  toolName      The name of the tool for which the certificate is to
139   *                       be generated.
140   * @param  keyStoreFile  The path to the keystore file in which the
141   *                       certificate is to be generated.  This must not be
142   *                       {@code null}, and if the target file exists, then it
143   *                       must be a JKS or PKCS #12 keystore.  If it does not
144   *                       exist, then at least the parent directory must exist.
145   * @param  keyStorePIN   The PIN needed to access the keystore.  It must not
146   *                       be {@code null}.
147   * @param  keyStoreType  The key store type for the keystore to be created, if
148   *                       it does not already exist.  It must not be
149   *                       {@code null}.
150   * @param  alias         The alias to use for the certificate in the keystore.
151   *                       It must not be {@code null}.
152   *
153   * @throws  CertException  If a problem occurs while trying to generate
154   *                         self-signed certificate.
155   */
156  public static void generateSelfSignedCertificate(
157                          @NotNull final String toolName,
158                          @NotNull final File keyStoreFile,
159                          @NotNull final String keyStorePIN,
160                          @NotNull final String keyStoreType,
161                          @NotNull final String alias)
162         throws CertException
163  {
164    // Try to get a set of all addresses associated with the local system and
165    // their corresponding canonical hostnames.
166    final NameResolver nameResolver =
167         LDAPConnectionOptions.DEFAULT_NAME_RESOLVER;
168    final Set<InetAddress> localAddresses =
169         StaticUtils.getAllLocalAddresses(nameResolver);
170    final Set<String> canonicalHostNames =
171         StaticUtils.getAvailableCanonicalHostNames(nameResolver,
172              localAddresses);
173
174
175    // Construct a subject DN for the certificate.
176    final DN subjectDN;
177    if (localAddresses.isEmpty())
178    {
179      subjectDN = new DN(new RDN("CN", toolName));
180    }
181    else
182    {
183      subjectDN = new DN(
184           new RDN("CN",
185                nameResolver.getCanonicalHostName(
186                     localAddresses.iterator().next())),
187           new RDN("OU", toolName));
188    }
189
190
191    // Generate a timestamp that corresponds to one day ago.
192    final long oneDayAgoTime = System.currentTimeMillis() - 86_400_000L;
193    final Date oneDayAgoDate = new Date(oneDayAgoTime);
194    final SimpleDateFormat dateFormatter =
195         new SimpleDateFormat("yyyyMMddHHmmss");
196    final String yesterdayTimeStamp = dateFormatter.format(oneDayAgoDate);
197
198
199    // Build the list of arguments to provide to the manage-certificates tool.
200    final ArrayList<String> argList = new ArrayList<>(30);
201    argList.add("generate-self-signed-certificate");
202
203    argList.add("--keystore");
204    argList.add(keyStoreFile.getAbsolutePath());
205
206    argList.add("--keystore-password");
207    argList.add(keyStorePIN);
208
209    argList.add("--keystore-type");
210    argList.add(keyStoreType);
211
212    argList.add("--alias");
213    argList.add(alias);
214
215    argList.add("--subject-dn");
216    argList.add(subjectDN.toString());
217
218    argList.add("--days-valid");
219    argList.add("3650");
220
221    argList.add("--validityStartTime");
222    argList.add(yesterdayTimeStamp);
223
224    argList.add("--key-algorithm");
225    argList.add("RSA");
226
227    argList.add("--key-size-bits");
228    argList.add("2048");
229
230    argList.add("--signature-algorithm");
231    argList.add("SHA256withRSA");
232
233    for (final String hostName : canonicalHostNames)
234    {
235      argList.add("--subject-alternative-name-dns");
236      argList.add(hostName);
237    }
238
239    for (final InetAddress address : localAddresses)
240    {
241      argList.add("--subject-alternative-name-ip-address");
242      argList.add(StaticUtils.trimInterfaceNameFromHostAddress(
243           address.getHostAddress()));
244    }
245
246    argList.add("--key-usage");
247    argList.add("digitalSignature");
248    argList.add("--key-usage");
249    argList.add("keyEncipherment");
250
251    argList.add("--extended-key-usage");
252    argList.add("server-auth");
253    argList.add("--extended-key-usage");
254    argList.add("client-auth");
255
256    final ByteArrayOutputStream output = new ByteArrayOutputStream();
257    final ResultCode resultCode = ManageCertificates.main(null, output, output,
258         argList.toArray(StaticUtils.NO_STRINGS));
259    if (resultCode != ResultCode.SUCCESS)
260    {
261      throw new CertException(
262           ERR_SELF_SIGNED_CERT_GENERATOR_ERROR_GENERATING_CERT.get(
263                StaticUtils.toUTF8String(output.toByteArray())));
264    }
265  }
266}