|  | @@ -19,6 +19,18 @@
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  package org.elasticsearch.common.settings;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +import org.apache.lucene.codecs.CodecUtil;
 | 
	
		
			
				|  |  | +import org.apache.lucene.store.BufferedChecksumIndexInput;
 | 
	
		
			
				|  |  | +import org.apache.lucene.store.ChecksumIndexInput;
 | 
	
		
			
				|  |  | +import org.apache.lucene.store.IOContext;
 | 
	
		
			
				|  |  | +import org.apache.lucene.store.IndexInput;
 | 
	
		
			
				|  |  | +import org.apache.lucene.store.IndexOutput;
 | 
	
		
			
				|  |  | +import org.apache.lucene.store.SimpleFSDirectory;
 | 
	
		
			
				|  |  | +import org.apache.lucene.util.SetOnce;
 | 
	
		
			
				|  |  | +import org.elasticsearch.cli.ExitCodes;
 | 
	
		
			
				|  |  | +import org.elasticsearch.cli.UserException;
 | 
	
		
			
				|  |  | +import org.elasticsearch.common.Randomness;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  import javax.crypto.Cipher;
 | 
	
		
			
				|  |  |  import javax.crypto.CipherInputStream;
 | 
	
		
			
				|  |  |  import javax.crypto.CipherOutputStream;
 | 
	
	
		
			
				|  | @@ -27,6 +39,7 @@ import javax.crypto.SecretKeyFactory;
 | 
	
		
			
				|  |  |  import javax.crypto.spec.GCMParameterSpec;
 | 
	
		
			
				|  |  |  import javax.crypto.spec.PBEKeySpec;
 | 
	
		
			
				|  |  |  import javax.crypto.spec.SecretKeySpec;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  import java.io.ByteArrayInputStream;
 | 
	
		
			
				|  |  |  import java.io.ByteArrayOutputStream;
 | 
	
		
			
				|  |  |  import java.io.DataInputStream;
 | 
	
	
		
			
				|  | @@ -56,18 +69,6 @@ import java.util.Map;
 | 
	
		
			
				|  |  |  import java.util.Set;
 | 
	
		
			
				|  |  |  import java.util.regex.Pattern;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -import org.apache.lucene.codecs.CodecUtil;
 | 
	
		
			
				|  |  | -import org.apache.lucene.store.BufferedChecksumIndexInput;
 | 
	
		
			
				|  |  | -import org.apache.lucene.store.ChecksumIndexInput;
 | 
	
		
			
				|  |  | -import org.apache.lucene.store.IOContext;
 | 
	
		
			
				|  |  | -import org.apache.lucene.store.IndexInput;
 | 
	
		
			
				|  |  | -import org.apache.lucene.store.IndexOutput;
 | 
	
		
			
				|  |  | -import org.apache.lucene.store.SimpleFSDirectory;
 | 
	
		
			
				|  |  | -import org.apache.lucene.util.SetOnce;
 | 
	
		
			
				|  |  | -import org.elasticsearch.cli.ExitCodes;
 | 
	
		
			
				|  |  | -import org.elasticsearch.cli.UserException;
 | 
	
		
			
				|  |  | -import org.elasticsearch.common.Randomness;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |  /**
 | 
	
		
			
				|  |  |   * A disk based container for sensitive settings in Elasticsearch.
 | 
	
		
			
				|  |  |   *
 | 
	
	
		
			
				|  | @@ -84,17 +85,6 @@ public class KeyStoreWrapper implements SecureSettings {
 | 
	
		
			
				|  |  |          FILE
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    /** An entry in the keystore. The bytes are opaque and interpreted based on the entry type. */
 | 
	
		
			
				|  |  | -    private static class Entry {
 | 
	
		
			
				|  |  | -        final EntryType type;
 | 
	
		
			
				|  |  | -        final byte[] bytes;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        Entry(EntryType type, byte[] bytes) {
 | 
	
		
			
				|  |  | -            this.type = type;
 | 
	
		
			
				|  |  | -            this.bytes = bytes;
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |      /**
 | 
	
		
			
				|  |  |       * A regex for the valid characters that a setting name in the keystore may use.
 | 
	
		
			
				|  |  |       */
 | 
	
	
		
			
				|  | @@ -110,7 +100,7 @@ public class KeyStoreWrapper implements SecureSettings {
 | 
	
		
			
				|  |  |      private static final String KEYSTORE_FILENAME = "elasticsearch.keystore";
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      /** The version of the metadata written before the keystore data. */
 | 
	
		
			
				|  |  | -    private static final int FORMAT_VERSION = 3;
 | 
	
		
			
				|  |  | +    private static final int FORMAT_VERSION = 4;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      /** The oldest metadata format version that can be read. */
 | 
	
		
			
				|  |  |      private static final int MIN_FORMAT_VERSION = 1;
 | 
	
	
		
			
				|  | @@ -146,6 +136,7 @@ public class KeyStoreWrapper implements SecureSettings {
 | 
	
		
			
				|  |  |      // 1: initial version, ES 5.3
 | 
	
		
			
				|  |  |      // 2: file setting, ES 5.4
 | 
	
		
			
				|  |  |      // 3: FIPS compliant algos, ES 6.3
 | 
	
		
			
				|  |  | +    // 4: remove distinction between string/files, ES 6.8/7.1
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      /** The metadata format version used to read the current keystore wrapper. */
 | 
	
		
			
				|  |  |      private final int formatVersion;
 | 
	
	
		
			
				|  | @@ -157,7 +148,7 @@ public class KeyStoreWrapper implements SecureSettings {
 | 
	
		
			
				|  |  |      private final byte[] dataBytes;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      /** The decrypted secret data. See {@link #decrypt(char[])}. */
 | 
	
		
			
				|  |  | -    private final SetOnce<Map<String, Entry>> entries = new SetOnce<>();
 | 
	
		
			
				|  |  | +    private final SetOnce<Map<String, byte[]>> entries = new SetOnce<>();
 | 
	
		
			
				|  |  |      private volatile boolean closed;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      private KeyStoreWrapper(int formatVersion, boolean hasPassword, byte[] dataBytes) {
 | 
	
	
		
			
				|  | @@ -273,11 +264,13 @@ public class KeyStoreWrapper implements SecureSettings {
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      /** Upgrades the format of the keystore, if necessary. */
 | 
	
		
			
				|  |  |      public static void upgrade(KeyStoreWrapper wrapper, Path configDir, char[] password) throws Exception {
 | 
	
		
			
				|  |  | -        // ensure keystore.seed exists
 | 
	
		
			
				|  |  | -        if (wrapper.getSettingNames().contains(SEED_SETTING.getKey())) {
 | 
	
		
			
				|  |  | +        if (wrapper.getFormatVersion() == FORMAT_VERSION && wrapper.getSettingNames().contains(SEED_SETTING.getKey())) {
 | 
	
		
			
				|  |  |              return;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | -        addBootstrapSeed(wrapper);
 | 
	
		
			
				|  |  | +        // add keystore.seed if necessary
 | 
	
		
			
				|  |  | +        if (wrapper.getSettingNames().contains(SEED_SETTING.getKey()) == false) {
 | 
	
		
			
				|  |  | +            addBootstrapSeed(wrapper);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |          wrapper.save(configDir, password);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -350,11 +343,14 @@ public class KeyStoreWrapper implements SecureSettings {
 | 
	
		
			
				|  |  |              int numEntries = input.readInt();
 | 
	
		
			
				|  |  |              while (numEntries-- > 0) {
 | 
	
		
			
				|  |  |                  String setting = input.readUTF();
 | 
	
		
			
				|  |  | -                EntryType entryType = EntryType.valueOf(input.readUTF());
 | 
	
		
			
				|  |  | +                if (formatVersion == 3) {
 | 
	
		
			
				|  |  | +                    // legacy, the keystore format would previously store the entry type
 | 
	
		
			
				|  |  | +                    input.readUTF();
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  |                  int entrySize = input.readInt();
 | 
	
		
			
				|  |  |                  byte[] entryBytes = new byte[entrySize];
 | 
	
		
			
				|  |  |                  input.readFully(entryBytes);
 | 
	
		
			
				|  |  | -                entries.get().put(setting, new Entry(entryType, entryBytes));
 | 
	
		
			
				|  |  | +                entries.get().put(setting, entryBytes);
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |              if (input.read() != -1) {
 | 
	
		
			
				|  |  |                  throw new SecurityException("Keystore has been corrupted or tampered with");
 | 
	
	
		
			
				|  | @@ -373,12 +369,11 @@ public class KeyStoreWrapper implements SecureSettings {
 | 
	
		
			
				|  |  |          try (CipherOutputStream cipherStream = new CipherOutputStream(bytes, cipher);
 | 
	
		
			
				|  |  |               DataOutputStream output = new DataOutputStream(cipherStream)) {
 | 
	
		
			
				|  |  |              output.writeInt(entries.get().size());
 | 
	
		
			
				|  |  | -            for (Map.Entry<String, Entry> mapEntry : entries.get().entrySet()) {
 | 
	
		
			
				|  |  | +            for (Map.Entry<String, byte[]> mapEntry : entries.get().entrySet()) {
 | 
	
		
			
				|  |  |                  output.writeUTF(mapEntry.getKey());
 | 
	
		
			
				|  |  | -                Entry entry = mapEntry.getValue();
 | 
	
		
			
				|  |  | -                output.writeUTF(entry.type.name());
 | 
	
		
			
				|  |  | -                output.writeInt(entry.bytes.length);
 | 
	
		
			
				|  |  | -                output.write(entry.bytes);
 | 
	
		
			
				|  |  | +                byte[] entry = mapEntry.getValue();
 | 
	
		
			
				|  |  | +                output.writeInt(entry.length);
 | 
	
		
			
				|  |  | +                output.write(entry);
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |          return bytes.toByteArray();
 | 
	
	
		
			
				|  | @@ -453,7 +448,7 @@ public class KeyStoreWrapper implements SecureSettings {
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |              Arrays.fill(chars, '\0');
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            entries.get().put(setting, new Entry(settingType, bytes));
 | 
	
		
			
				|  |  | +            entries.get().put(setting, bytes);
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -526,11 +521,8 @@ public class KeyStoreWrapper implements SecureSettings {
 | 
	
		
			
				|  |  |      @Override
 | 
	
		
			
				|  |  |      public synchronized SecureString getString(String setting) {
 | 
	
		
			
				|  |  |          ensureOpen();
 | 
	
		
			
				|  |  | -        Entry entry = entries.get().get(setting);
 | 
	
		
			
				|  |  | -        if (entry == null || entry.type != EntryType.STRING) {
 | 
	
		
			
				|  |  | -            throw new IllegalArgumentException("Secret setting " + setting + " is not a string");
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -        ByteBuffer byteBuffer = ByteBuffer.wrap(entry.bytes);
 | 
	
		
			
				|  |  | +        byte[] entry = entries.get().get(setting);
 | 
	
		
			
				|  |  | +        ByteBuffer byteBuffer = ByteBuffer.wrap(entry);
 | 
	
		
			
				|  |  |          CharBuffer charBuffer = StandardCharsets.UTF_8.decode(byteBuffer);
 | 
	
		
			
				|  |  |          return new SecureString(Arrays.copyOfRange(charBuffer.array(), charBuffer.position(), charBuffer.limit()));
 | 
	
		
			
				|  |  |      }
 | 
	
	
		
			
				|  | @@ -538,11 +530,8 @@ public class KeyStoreWrapper implements SecureSettings {
 | 
	
		
			
				|  |  |      @Override
 | 
	
		
			
				|  |  |      public synchronized InputStream getFile(String setting) {
 | 
	
		
			
				|  |  |          ensureOpen();
 | 
	
		
			
				|  |  | -        Entry entry = entries.get().get(setting);
 | 
	
		
			
				|  |  | -        if (entry == null || entry.type != EntryType.FILE) {
 | 
	
		
			
				|  |  | -            throw new IllegalArgumentException("Secret setting " + setting + " is not a file");
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -        return new ByteArrayInputStream(entry.bytes);
 | 
	
		
			
				|  |  | +        byte[] entry = entries.get().get(setting);
 | 
	
		
			
				|  |  | +        return new ByteArrayInputStream(entry);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      /**
 | 
	
	
		
			
				|  | @@ -564,9 +553,9 @@ public class KeyStoreWrapper implements SecureSettings {
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode(CharBuffer.wrap(value));
 | 
	
		
			
				|  |  |          byte[] bytes = Arrays.copyOfRange(byteBuffer.array(), byteBuffer.position(), byteBuffer.limit());
 | 
	
		
			
				|  |  | -        Entry oldEntry = entries.get().put(setting, new Entry(EntryType.STRING, bytes));
 | 
	
		
			
				|  |  | +        byte[] oldEntry = entries.get().put(setting, bytes);
 | 
	
		
			
				|  |  |          if (oldEntry != null) {
 | 
	
		
			
				|  |  | -            Arrays.fill(oldEntry.bytes, (byte)0);
 | 
	
		
			
				|  |  | +            Arrays.fill(oldEntry, (byte)0);
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -575,18 +564,18 @@ public class KeyStoreWrapper implements SecureSettings {
 | 
	
		
			
				|  |  |          ensureOpen();
 | 
	
		
			
				|  |  |          validateSettingName(setting);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        Entry oldEntry = entries.get().put(setting, new Entry(EntryType.FILE, Arrays.copyOf(bytes, bytes.length)));
 | 
	
		
			
				|  |  | +        byte[] oldEntry = entries.get().put(setting, Arrays.copyOf(bytes, bytes.length));
 | 
	
		
			
				|  |  |          if (oldEntry != null) {
 | 
	
		
			
				|  |  | -            Arrays.fill(oldEntry.bytes, (byte)0);
 | 
	
		
			
				|  |  | +            Arrays.fill(oldEntry, (byte)0);
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      /** Remove the given setting from the keystore. */
 | 
	
		
			
				|  |  |      void remove(String setting) {
 | 
	
		
			
				|  |  |          ensureOpen();
 | 
	
		
			
				|  |  | -        Entry oldEntry = entries.get().remove(setting);
 | 
	
		
			
				|  |  | +        byte[] oldEntry = entries.get().remove(setting);
 | 
	
		
			
				|  |  |          if (oldEntry != null) {
 | 
	
		
			
				|  |  | -            Arrays.fill(oldEntry.bytes, (byte)0);
 | 
	
		
			
				|  |  | +            Arrays.fill(oldEntry, (byte)0);
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -601,8 +590,8 @@ public class KeyStoreWrapper implements SecureSettings {
 | 
	
		
			
				|  |  |      public synchronized void close() {
 | 
	
		
			
				|  |  |          this.closed = true;
 | 
	
		
			
				|  |  |          if (null != entries.get() && entries.get().isEmpty() == false) {
 | 
	
		
			
				|  |  | -            for (Entry entry : entries.get().values()) {
 | 
	
		
			
				|  |  | -                Arrays.fill(entry.bytes, (byte) 0);
 | 
	
		
			
				|  |  | +            for (byte[] entry : entries.get().values()) {
 | 
	
		
			
				|  |  | +                Arrays.fill(entry, (byte) 0);
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |      }
 |