code: /*
   * Copyright (c) 2019 the Eclipse Milo Authors
   *
   * This program and the accompanying materials are made
   * available under the terms of the Eclipse Public License 2.0
   * which is available at https://www.eclipse.org/legal/epl-2.0/
   *
   * SPDX-License-Identifier: EPL-2.0
   */
  
  package org.eclipse.milo.opcua.stack.core.util;
  
  import java.math.BigInteger;
  import java.security.KeyPair;
  import java.security.KeyPairGenerator;
  import java.security.NoSuchAlgorithmException;
  import java.security.Provider;
  import java.security.SecureRandom;
  import java.security.cert.X509Certificate;
  import java.util.ArrayList;
  import java.util.Date;
  import java.util.List;
  import java.util.Locale;
  
  import org.bouncycastle.asn1.x500.X500Name;
  import org.bouncycastle.asn1.x500.X500NameBuilder;
  import org.bouncycastle.asn1.x500.style.BCStyle;
  import org.bouncycastle.asn1.x509.BasicConstraints;
  import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
  import org.bouncycastle.asn1.x509.Extension;
  import org.bouncycastle.asn1.x509.GeneralName;
  import org.bouncycastle.asn1.x509.GeneralNames;
  import org.bouncycastle.asn1.x509.KeyPurposeId;
  import org.bouncycastle.asn1.x509.KeyUsage;
  import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
  import org.bouncycastle.cert.CertIOException;
  import org.bouncycastle.cert.X509CertificateHolder;
  import org.bouncycastle.cert.X509v3CertificateBuilder;
  import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
  import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
  import org.bouncycastle.jce.provider.BouncyCastleProvider;
  import org.bouncycastle.operator.ContentSigner;
  import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
  import org.jetbrains.annotations.Nullable;
  
  public class SelfSignedCertificateGenerator {
  
      /**
       * Generate an RSA {@link KeyPair} of bit length {@code length}.
       *
       * @param length the length, in bits, of the key to generate.
       * @return a {@link KeyPair} of bit length {@code length}.
       * @throws NoSuchAlgorithmException if no {@link Provider} supports RSA KeyPair generation.
       */
      public static KeyPair generateRsaKeyPair(int length) throws NoSuchAlgorithmException {
          KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
          generator.initialize(length, new SecureRandom());
  
          return generator.generateKeyPair();
      }
  
      /**
       * Generate an EC {@link KeyPair} of bit length {@code length}.
       *
       * @param length the length, in bits, of the key to generate.
       * @return a {@link KeyPair} of bit length {@code length}.
       * @throws NoSuchAlgorithmException if no {@link Provider} supports EC KeyPair generation.
       */
      public static KeyPair generateEcKeyPair(int length) throws NoSuchAlgorithmException {
          KeyPairGenerator generator = KeyPairGenerator.getInstance("EC");
          generator.initialize(length, new SecureRandom());
  
          return generator.generateKeyPair();
      }
  
      public X509Certificate generateSelfSigned(
          KeyPair keyPair,
          Date notBefore,
          Date notAfter,
          @Nullable String commonName,
          @Nullable String organization,
          @Nullable String organizationalUnit,
          @Nullable String localityName,
          @Nullable String stateName,
          @Nullable String countryCode,
          @Nullable String applicationUri,
          List<String> dnsNames,
          List<String> ipAddresses,
          String signatureAlgorithm) throws Exception {
  
          X500NameBuilder nameBuilder = new X500NameBuilder();
  
          if (commonName != null) {
              nameBuilder.addRDN(BCStyle.CN, commonName);
          }
          if (organization != null) {
              nameBuilder.addRDN(BCStyle.O, organization);
          }
          if (organizationalUnit != null) {
              nameBuilder.addRDN(BCStyle.OU, organizationalUnit);
          }
          if (localityName != null) {
              nameBuilder.addRDN(BCStyle.L, localityName);
          }
          if (stateName != null) {
              nameBuilder.addRDN(BCStyle.ST, stateName);
          }
          if (countryCode != null) {
              nameBuilder.addRDN(BCStyle.C, countryCode);
          }
  
          X500Name name = nameBuilder.build();
  
          // Using the current timestamp as the certificate serial number
          BigInteger certSerialNumber = new BigInteger(Long.toString(System.currentTimeMillis()));
  
          SubjectPublicKeyInfo subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(
              keyPair.getPublic().getEncoded()
          );
  
          X509v3CertificateBuilder certificateBuilder = new X509v3CertificateBuilder(
              name,
              certSerialNumber,
              notBefore,
              notAfter,
              Locale.ENGLISH,
              name,
              subjectPublicKeyInfo
          );
  
          // Explicitly set path length constraint to 0. This constructor also sets cA=true.
          BasicConstraints basicConstraints = new BasicConstraints(0);
  
          // Authority Key Identifier
          addAuthorityKeyIdentifier(certificateBuilder, keyPair);
  
          // Basic Constraints
          addBasicConstraints(certificateBuilder, basicConstraints);
  
          // Key Usage
          addKeyUsage(certificateBuilder);
  
          // Extended Key Usage
          addExtendedKeyUsage(certificateBuilder);
  
          // Subject Alternative Name
          addSubjectAlternativeNames(certificateBuilder, keyPair, applicationUri, dnsNames, ipAddresses);
  
          ContentSigner contentSigner = new JcaContentSignerBuilder(signatureAlgorithm)
              .setProvider(new BouncyCastleProvider())
              .build(keyPair.getPrivate());
  
          X509CertificateHolder certificateHolder = certificateBuilder.build(contentSigner);
  
          return new JcaX509CertificateConverter().getCertificate(certificateHolder);
      }
  
      protected void addSubjectAlternativeNames(
          X509v3CertificateBuilder certificateBuilder,
          KeyPair keyPair,
          @Nullable String applicationUri,
          List<String> dnsNames,
          List<String> ipAddresses) throws CertIOException, NoSuchAlgorithmException {
  
          List<GeneralName> generalNames = new ArrayList<>();
  
          if (applicationUri != null) {
              generalNames.add(new GeneralName(GeneralName.uniformResourceIdentifier, applicationUri));
          }
  
          dnsNames.stream()
              .distinct()
              .map(s -> new GeneralName(GeneralName.dNSName, s))
              .forEach(generalNames::add);
  
          ipAddresses.stream()
              .distinct()
              .map(s -> new GeneralName(GeneralName.iPAddress, s))
              .forEach(generalNames::add);
  
          certificateBuilder.addExtension(
              Extension.subjectAlternativeName,
              false,
              new GeneralNames(generalNames.toArray(new GeneralName[]{}))
          );
  
          // Subject Key Identifier
          certificateBuilder.addExtension(
              Extension.subjectKeyIdentifier,
              false,
              new JcaX509ExtensionUtils()
                  .createSubjectKeyIdentifier(keyPair.getPublic())
          );
      }
  
      protected void addExtendedKeyUsage(X509v3CertificateBuilder certificateBuilder) throws CertIOException {
          certificateBuilder.addExtension(
              Extension.extendedKeyUsage,
              false,
              new ExtendedKeyUsage(
                  new KeyPurposeId[]{
                      KeyPurposeId.id_kp_clientAuth,
                      KeyPurposeId.id_kp_serverAuth
                  }
              )
          );
      }
  
      protected void addKeyUsage(X509v3CertificateBuilder certificateBuilder) throws CertIOException {
          certificateBuilder.addExtension(
              Extension.keyUsage,
              false,
              new KeyUsage(
                  KeyUsage.dataEncipherment |
                      KeyUsage.digitalSignature |
                      KeyUsage.keyAgreement |
                      KeyUsage.keyCertSign |
                      KeyUsage.keyEncipherment |
                      KeyUsage.nonRepudiation
              )
          );
      }
  
      protected void addBasicConstraints(
          X509v3CertificateBuilder certificateBuilder,
          BasicConstraints basicConstraints) throws CertIOException {
  
          certificateBuilder.addExtension(
              Extension.basicConstraints,
              false,
              basicConstraints
          );
      }
  
      protected void addAuthorityKeyIdentifier(
          X509v3CertificateBuilder certificateBuilder,
          KeyPair keyPair) throws CertIOException, NoSuchAlgorithmException {
  
          certificateBuilder.addExtension(
              Extension.authorityKeyIdentifier,
              false,
              new JcaX509ExtensionUtils()
                  .createAuthorityKeyIdentifier(keyPair.getPublic())
          );
      }
  
  }
  
  
  
  
  