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}