code: /*
   * Copyright (c) 2021 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.security.SecureRandom;
  import java.util.Random;
  import java.util.concurrent.CompletableFuture;
  import java.util.concurrent.CompletionStage;
  import java.util.concurrent.ExecutionException;
  import java.util.concurrent.ThreadLocalRandom;
  import java.util.concurrent.TimeUnit;
  import java.util.concurrent.TimeoutException;
  import java.util.concurrent.atomic.AtomicReference;
  
  import org.bouncycastle.util.Arrays;
  import org.eclipse.milo.opcua.stack.core.StatusCodes;
  import org.eclipse.milo.opcua.stack.core.UaException;
  import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
  import org.eclipse.milo.opcua.stack.core.types.builtin.ByteString;
  import org.slf4j.LoggerFactory;
  
  public class NonceUtil {
  
      /**
       * The minimum required nonce length for validation by {@link NonceUtil#validateNonce(ByteString)}.
       * <p>
       * This is specified by OPC UA Part 4 to be least 32 bytes in the CreateSession and ActivateSession services.
       */
      public static final int MINIMUM_NONCE_LENGTH = 32;
  
      private static volatile boolean SECURE_RANDOM_ENABLED = true;
  
      private static final CompletableFuture<Void> SEED_FUTURE = new CompletableFuture<>();
  
      private static final AtomicReference<SecureRandom> SECURE_RANDOM = new AtomicReference<>();
  
      static {
          // The first call to a SecureRandom causes it to seed, which is potentially blocking, so don't make the
          // SecureRandom instance available until after a seed has been generated.
          new Thread(() -> {
              long start = System.nanoTime();
  
              SecureRandom sr = new SecureRandom();
              sr.nextBytes(new byte[32]);
              SECURE_RANDOM.set(sr);
  
              long delta = System.nanoTime() - start;
  
              LoggerFactory.getLogger(NonceUtil.class).info(
                  "SecureRandom seeded in {}ms.",
                  TimeUnit.MILLISECONDS.convert(delta, TimeUnit.NANOSECONDS)
              );
  
              SEED_FUTURE.complete(null);
          }, "milo-nonce-util-secure-random").start();
      }
  
      /**
       * Block until the {@link SecureRandom} instance has been seeded.
       *
       * @throws ExecutionException   if seeding completed exceptionally.
       * @throws InterruptedException if the current thread was interrupted.
       */
      public static void blockUntilSecureRandomSeeded() throws ExecutionException, InterruptedException {
          SEED_FUTURE.get();
      }
  
      /**
       * Block for at most {@code timeout} waiting for the {@link SecureRandom} instance to be seeded.
       *
       * @param timeout the maximum time to wait.
       * @param unit    the {@link TimeUnit} of the {@code timeout} argument.
       * @throws ExecutionException   if seeding completed exceptionally.
       * @throws InterruptedException if the current thread was interrupted.
       * @throws TimeoutException     if the wait timed out.
       */
      public static void blockUntilSecureRandomSeeded(
          long timeout,
          TimeUnit unit
      ) throws ExecutionException, InterruptedException, TimeoutException {
  
          SEED_FUTURE.get(timeout, unit);
      }
  
      /**
       * Get a {@link CompletionStage} that is completed when the {@link SecureRandom} instance has been seeded.
       *
       * @return a {@link CompletionStage} that is completed when the {@link SecureRandom} instance has been seeded.
       */
      public static CompletionStage<Void> secureRandomSeeded() {
          return SEED_FUTURE;
      }
  
      /**
       * Enable the use of a {@link SecureRandom} for nonce generation. This is the default.
       */
      public static void enableSecureRandom() {
          SECURE_RANDOM_ENABLED = true;
      }
  
      /**
       * Disable the use of a {@link SecureRandom} for nonce generation. Not recommended.
       */
      public static void disableSecureRandom() {
          SECURE_RANDOM_ENABLED = false;
      }
  
      /**
       * @return {@code true} is nonce generation uses a {@link SecureRandom} instance.
       */
      public static boolean isSecureRandomEnabled() {
          return SECURE_RANDOM_ENABLED;
      }
  
      /**
       * @return {@code true} if the {@link SecureRandom} instance is seeded and available.
       */
      public static boolean isSecureRandomSeeded() {
          return SECURE_RANDOM.get() != null;
      }
  
      /**
       * @param length the length of the nonce to generate.
       * @return a nonce of the given length.
       */
      public static ByteString generateNonce(int length) {
          if (length == 0) return ByteString.NULL_VALUE;
  
          byte[] bs = new byte[length];
  
          Random random = null;
  
          if (SECURE_RANDOM_ENABLED) {
              random = SECURE_RANDOM.get();
          }
  
          if (random == null) {
              random = ThreadLocalRandom.current();
          }
  
          random.nextBytes(bs);
  
          return new ByteString(bs);
      }
  
      /**
       * Generate a nonce for the given {@link SecurityPolicy}.
       * <p>
       * The length is determined by the policy: see {@link #getNonceLength(SecurityPolicy)}.
       *
       * <b>Important</b>: this should only be used to generate a nonce exchanged when establishing a secure channel.
       * The nonce used by sessions has different length requirements.
       *
       * @param securityPolicy the {@link SecurityPolicy} to use when determining the nonce length.
       * @return a nonce of the appropriate length for the given security policy.
       */
      public static ByteString generateNonce(SecurityPolicy securityPolicy) {
          return generateNonce(getNonceLength(securityPolicy));
      }
  
      /**
       * Get the minimum nonce length for use with {@code securityPolicy}.
       * <p>
       * For an RSA-based {@link SecurityPolicy}, the nonce shall be a cryptographic random number with a length equal to
       * the key length specified by policy's SymmetricEncryptionAlgorithm or the length of hash algorithm used by the
       * policy's KeyDerivationAlgorithm, whichever is greater.
       *
       * @param securityPolicy the {@link SecurityPolicy} in use.
       * @return the minimum nonce length for use with {@code securityPolicy}.
       */
      private static int getNonceLength(SecurityPolicy securityPolicy) {
          switch (securityPolicy) {
              case Basic128Rsa15:
                  return 16;
              case Basic256:
              case Basic256Sha256:
              case Aes128_Sha256_RsaOaep:
              case Aes256_Sha256_RsaPss:
                  return 32;
              case None:
              default:
                  return 0;
          }
      }
  
      /**
       * Validate that {@code nonce} meets the minimum length requirement for {@code securityPolicy} and is non-zeroes.
       * <p>
       * <b>Important</b>: this should only be used to validate the nonce exchanged when establishing a secure channel.
       * The nonce used by sessions has different length requirements.
       *
       * @param nonce          the nonce to validate.
       * @param securityPolicy the {@link SecurityPolicy} dictating the minimum nonce length.
       * @throws UaException if nonce validation failed.
       */
      public static void validateNonce(ByteString nonce, SecurityPolicy securityPolicy) throws UaException {
          validateNonce(nonce, getNonceLength(securityPolicy));
      }
  
      /**
       * Validate that {@code nonce} is at least {@link NonceUtil#MINIMUM_NONCE_LENGTH} and is non-zeroes.
       *
       * @param nonce the nonce to validate.
       * @throws UaException if nonce validation failed.
       */
      public static void validateNonce(ByteString nonce) throws UaException {
          validateNonce(nonce, MINIMUM_NONCE_LENGTH);
      }
  
      /**
       * Validate that {@code nonce} is at least {@code minimumLength} and is non-zeroes.
       *
       * @param nonce         the nonce to validate.
       * @param minimumLength the minimum required nonce length
       * @throws UaException if nonce validation failed.
       */
      public static void validateNonce(ByteString nonce, int minimumLength) throws UaException {
          byte[] bs = nonce.bytesOrEmpty();
  
          if (bs.length < minimumLength) {
              throw new UaException(
                  StatusCodes.Bad_NonceInvalid,
                  "nonce must be at least " + minimumLength + " bytes"
              );
          }
  
          if (bs.length > 0 && Arrays.areAllZeroes(bs, 0, bs.length)) {
              throw new UaException(StatusCodes.Bad_NonceInvalid, "nonce must be non-zero");
          }
      }
  
  }
  