| | |
| | | |
| | | 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; |
| | |
| | | |
| | | 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"; |
| | | |
| | |
| | | { |
| | | } |
| | | |
| | | /** |
| | | * 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 |
| | | { |
| | |
| | | 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) |
| | | { |
| | |
| | | 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) |
| | | { |
| | |
| | | // 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) |
| | | { |
| | |
| | | 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) |
| | | { |
| | |
| | | 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) |