mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Valery Kharseko
yesterday f93487455cc1b5d7062ea6577d99dea25bfd81b3
opendj-server-legacy/src/main/java/org/opends/server/util/Platform.java
@@ -21,8 +21,17 @@
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.GroupPrincipal;
import java.nio.file.attribute.PosixFileAttributeView;
import java.nio.file.attribute.PosixFileAttributes;
import java.security.Key;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
@@ -70,6 +79,45 @@
  private static final PlatformIMPL IMPL;
  /** Puts a key store written next to its file in the place of that file. */
  interface KeyStoreReplacer
  {
    /**
     * Puts the new file in the place of the old one at once.
     *
     * @param temp
     *          the new file, next to the old one
     * @param target
     *          the old file
     * @throws IOException
     *           if the old file cannot be replaced that way
     */
    void replace(Path temp, Path target) throws IOException;
  }
  /** Replaces a key store file; a test makes it fail to reach the write in place. */
  static volatile KeyStoreReplacer keyStoreReplacer = (temp, target) ->
      Files.move(temp, target, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
  /** Gives a key store file written next to the old one the group of that one. */
  interface KeyStoreGroupSetter
  {
    /**
     * Gives the new file the group of the old one.
     *
     * @param view
     *          the attributes of the new file
     * @param group
     *          the group of the old file
     * @throws IOException
     *           if the new file cannot be given that group
     */
    void setGroup(PosixFileAttributeView view, GroupPrincipal group) throws IOException;
  }
  /** Gives the new key store file its group; a test makes it fail to reach the write in place. */
  static volatile KeyStoreGroupSetter keyStoreGroupSetter = PosixFileAttributeView::setGroup;
  /** The minimum java supported version. */
  public static final String JAVA_MINIMUM_VERSION_NUMBER = "11";
@@ -131,6 +179,125 @@
    {
    }
    /**
     * Writes the key store over its file. On a POSIX file system it is written to a file of its
     * own next to it first, which then takes the place of the old one at once: a reader of the
     * key store, which the server reads from its file for each SSL context, never finds it half
     * written, and a process stopped part way through leaves the old file whole (#1188). The new
     * file keeps the mode, the owner and the group of the old one; it does not keep its ACLs.
     * <p>
     * The key store is written in place, as it was before, where it cannot be replaced that way
     * without changing what the file is: a new key store, one this process may not write to
     * (refused, as before), one with other hard links, one whose owner or group the new file
     * cannot take, one whose links, owner or group cannot be told, one in a directory this
     * process cannot write to, and one which cannot be replaced, such as a file mounted on its own
     * into a container. On a file system without POSIX attributes (Windows), where neither the
     * owner, the ACLs nor the hard links of the old file would reach a new one, it is always
     * written in place.
     */
    private static void store(KeyStore ks, String ksPath, char[] pwd) throws Exception
    {
      Path target = Paths.get(ksPath);
      if (!Files.exists(target) || !target.getFileSystem().supportedFileAttributeViews().contains("posix"))
      {
        storeInPlace(ks, target, pwd);
        return;
      }
      // A link goes on pointing where it did: the file it points to is the one replaced.
      target = target.toRealPath();
      if (!Files.isWritable(target) || hasOtherLinks(target))
      {
        storeInPlace(ks, target, pwd);
        return;
      }
      final Path temp;
      try
      {
        temp = Files.createTempFile(target.getParent(), "." + target.getFileName() + ".", ".tmp");
      }
      catch (IOException e)
      {
        storeInPlace(ks, target, pwd);
        return;
      }
      try
      {
        try (OutputStream out = Files.newOutputStream(temp))
        {
          ks.store(out, pwd);
        }
        if (!takeOwnerAndMode(temp, target))
        {
          storeInPlace(ks, target, pwd);
          return;
        }
        try
        {
          keyStoreReplacer.replace(temp, target);
        }
        catch (IOException e)
        {
          storeInPlace(ks, target, pwd);
        }
      }
      finally
      {
        Files.deleteIfExists(temp);
      }
    }
    /**
     * Whether the file has other names, which a new file in its place would not have; a file
     * whose links cannot be told is taken to have some.
     */
    private static boolean hasOtherLinks(Path target)
    {
      try
      {
        return ((Number) Files.getAttribute(target, "unix:nlink")).intValue() > 1;
      }
      catch (IOException | UnsupportedOperationException e)
      {
        return true;
      }
    }
    /**
     * Gives the new file the group and the mode of the old one, and tells whether it has its
     * owner and its group: a process may give a file only the groups it is a member of, and only
     * its own user as owner. The mode comes last, so that the new file is never open to a group
     * the old one was not, and only when the file can take the place of the old one; a file whose
     * attributes cannot be read or set cannot.
     */
    private static boolean takeOwnerAndMode(Path temp, Path target)
    {
      try
      {
        final PosixFileAttributes old = Files.readAttributes(target, PosixFileAttributes.class);
        final PosixFileAttributeView view = Files.getFileAttributeView(temp, PosixFileAttributeView.class);
        keyStoreGroupSetter.setGroup(view, old.group());
        final PosixFileAttributes now = view.readAttributes();
        if (!now.owner().equals(old.owner()) || !now.group().equals(old.group()))
        {
          return false;
        }
        view.setPermissions(old.permissions());
        return true;
      }
      catch (IOException | UnsupportedOperationException e)
      {
        return false;
      }
    }
    private static void storeInPlace(KeyStore ks, Path target, char[] pwd) throws Exception
    {
      try (OutputStream out = new FileOutputStream(target.toFile()))
      {
        ks.store(out, pwd);
      }
    }
    private final void deleteAlias(KeyStore ks, String ksPath, String alias,
                                   char[] pwd) throws KeyStoreException
    {
@@ -142,10 +309,7 @@
          throw new KeyStoreException(msg.toString());
        }
        ks.deleteEntry(alias);
        try (final FileOutputStream fs = new FileOutputStream(ksPath))
        {
          ks.store(fs, pwd);
        }
        store(ks, ksPath, pwd);
      }
      catch (Exception e)
      {
@@ -182,9 +346,7 @@
          LocalizableMessage msg = ERR_CERTMGR_ALIAS_INVALID.get(alias);
          throw new KeyStoreException(msg.toString());
        }
        try (FileOutputStream fileOutStream = new FileOutputStream(ksPath)) {
          ks.store(fileOutStream, pwd);
        }
        store(ks, ksPath, pwd);
      }
      catch (Exception e)
      {
@@ -212,9 +374,7 @@
        // the server are initialised with the store password only, so a key which kept the
        // password of the key store it comes from could not be read back.
        ks.setKeyEntry(alias, privateKey, pwd, chain);
        try (FileOutputStream fileOutStream = new FileOutputStream(ksPath)) {
          ks.store(fileOutStream, pwd);
        }
        store(ks, ksPath, pwd);
      }
      catch (Exception e)
      {
@@ -238,9 +398,7 @@
          throw new KeyStoreException(msg.toString());
        }
        ks.setCertificateEntry(alias, certificate);
        try (FileOutputStream fileOutStream = new FileOutputStream(ksPath)) {
          ks.store(fileOutStream, pwd);
        }
        store(ks, ksPath, pwd);
      }
      catch (Exception e)
      {
@@ -277,9 +435,7 @@
                generateSelfCertificate(keyPair, keyType, subject, validity)
        };
        ks.setKeyEntry(alias, privateKey, pwd, certificateChain);
        try (FileOutputStream fileOutStream = new FileOutputStream(ksPath)) {
          ks.store(fileOutStream, pwd);
        }
        store(ks, ksPath, pwd);
        return ks;
      }
      catch (Exception e)