|
43 | 43 | import java.nio.file.attribute.PosixFileAttributeView;
|
44 | 44 | import java.nio.file.attribute.PosixFilePermissions;
|
45 | 45 | import java.security.GeneralSecurityException;
|
46 |
| -import java.security.KeyStore; |
47 | 46 | import java.security.SecureRandom;
|
48 | 47 | import java.util.Arrays;
|
49 |
| -import java.util.Base64; |
50 |
| -import java.util.Enumeration; |
51 | 48 | import java.util.HashMap;
|
52 |
| -import java.util.HashSet; |
53 | 49 | import java.util.Locale;
|
54 | 50 | import java.util.Map;
|
55 | 51 | import java.util.Set;
|
@@ -109,7 +105,7 @@ private static class Entry {
|
109 | 105 | public static final String KEYSTORE_FILENAME = "elasticsearch.keystore";
|
110 | 106 |
|
111 | 107 | /** The oldest metadata format version that can be read. */
|
112 |
| - private static final int MIN_FORMAT_VERSION = 1; |
| 108 | + private static final int MIN_FORMAT_VERSION = 3; |
113 | 109 | /** Legacy versions of the metadata written before the keystore data. */
|
114 | 110 | public static final int V2_VERSION = 2;
|
115 | 111 | public static final int V3_VERSION = 3;
|
@@ -268,59 +264,15 @@ public static KeyStoreWrapper load(Path configDir) throws IOException {
|
268 | 264 | throw new IllegalStateException("hasPassword boolean is corrupt: " + String.format(Locale.ROOT, "%02x", hasPasswordByte));
|
269 | 265 | }
|
270 | 266 |
|
271 |
| - if (formatVersion <= V2_VERSION) { |
272 |
| - String type = input.readString(); |
273 |
| - if (type.equals("PKCS12") == false) { |
274 |
| - throw new IllegalStateException("Corrupted legacy keystore string encryption algorithm"); |
275 |
| - } |
276 |
| - |
277 |
| - final String stringKeyAlgo = input.readString(); |
278 |
| - if (stringKeyAlgo.equals("PBE") == false) { |
279 |
| - throw new IllegalStateException("Corrupted legacy keystore string encryption algorithm"); |
280 |
| - } |
281 |
| - if (formatVersion == V2_VERSION) { |
282 |
| - final String fileKeyAlgo = input.readString(); |
283 |
| - if (fileKeyAlgo.equals("PBE") == false) { |
284 |
| - throw new IllegalStateException("Corrupted legacy keystore file encryption algorithm"); |
285 |
| - } |
286 |
| - } |
287 |
| - } |
288 |
| - |
289 | 267 | final byte[] dataBytes;
|
290 |
| - if (formatVersion == V2_VERSION) { |
291 |
| - // For v2 we had a map of strings containing the types for each setting. In v3 this map is now |
292 |
| - // part of the encrypted bytes. Unfortunately we cannot seek backwards with checksum input, so |
293 |
| - // we cannot just read the map and find out how long it is. So instead we read the map and |
294 |
| - // store it back using java's builtin DataOutput in a byte array, along with the actual keystore bytes |
295 |
| - Map<String, String> settingTypes = input.readMapOfStrings(); |
296 |
| - ByteArrayOutputStream bytes = new ByteArrayOutputStream(); |
297 |
| - try (DataOutputStream output = new DataOutputStream(bytes)) { |
298 |
| - output.writeInt(settingTypes.size()); |
299 |
| - for (Map.Entry<String, String> entry : settingTypes.entrySet()) { |
300 |
| - output.writeUTF(entry.getKey()); |
301 |
| - output.writeUTF(entry.getValue()); |
302 |
| - } |
303 |
| - final int keystoreLen; |
304 |
| - if (formatVersion < LE_VERSION) { |
305 |
| - keystoreLen = Integer.reverseBytes(input.readInt()); |
306 |
| - } else { |
307 |
| - keystoreLen = input.readInt(); |
308 |
| - } |
309 |
| - byte[] keystoreBytes = new byte[keystoreLen]; |
310 |
| - input.readBytes(keystoreBytes, 0, keystoreLen); |
311 |
| - output.write(keystoreBytes); |
312 |
| - } |
313 |
| - dataBytes = bytes.toByteArray(); |
| 268 | + int dataBytesLen; |
| 269 | + if (formatVersion < LE_VERSION) { |
| 270 | + dataBytesLen = Integer.reverseBytes(input.readInt()); |
314 | 271 | } else {
|
315 |
| - int dataBytesLen; |
316 |
| - if (formatVersion < LE_VERSION) { |
317 |
| - dataBytesLen = Integer.reverseBytes(input.readInt()); |
318 |
| - } else { |
319 |
| - dataBytesLen = input.readInt(); |
320 |
| - } |
321 |
| - dataBytes = new byte[dataBytesLen]; |
322 |
| - input.readBytes(dataBytes, 0, dataBytesLen); |
| 272 | + dataBytesLen = input.readInt(); |
323 | 273 | }
|
| 274 | + dataBytes = new byte[dataBytesLen]; |
| 275 | + input.readBytes(dataBytes, 0, dataBytesLen); |
324 | 276 |
|
325 | 277 | CodecUtil.checkFooter(input);
|
326 | 278 | return new KeyStoreWrapper(formatVersion, hasPassword, dataBytes);
|
@@ -378,13 +330,6 @@ public void decrypt(char[] password) throws GeneralSecurityException, IOExceptio
|
378 | 330 | if (entries.get() != null) {
|
379 | 331 | throw new IllegalStateException("Keystore has already been decrypted");
|
380 | 332 | }
|
381 |
| - if (formatVersion <= V2_VERSION) { |
382 |
| - decryptLegacyEntries(); |
383 |
| - if (password.length != 0) { |
384 |
| - throw new IllegalArgumentException("Keystore format does not accept non-empty passwords"); |
385 |
| - } |
386 |
| - return; |
387 |
| - } |
388 | 333 |
|
389 | 334 | final byte[] salt;
|
390 | 335 | final byte[] iv;
|
@@ -462,79 +407,6 @@ private byte[] encrypt(char[] password, byte[] salt, byte[] iv) throws GeneralSe
|
462 | 407 | return bytes.toByteArray();
|
463 | 408 | }
|
464 | 409 |
|
465 |
| - private void decryptLegacyEntries() throws GeneralSecurityException, IOException { |
466 |
| - // v1 and v2 keystores never had passwords actually used, so we always use an empty password |
467 |
| - KeyStore keystore = KeyStore.getInstance("PKCS12"); |
468 |
| - Map<String, EntryType> settingTypes = new HashMap<>(); |
469 |
| - ByteArrayInputStream inputBytes = new ByteArrayInputStream(dataBytes); |
470 |
| - try (DataInputStream input = new DataInputStream(inputBytes)) { |
471 |
| - // first read the setting types map |
472 |
| - if (formatVersion == V2_VERSION) { |
473 |
| - int numSettings = input.readInt(); |
474 |
| - for (int i = 0; i < numSettings; ++i) { |
475 |
| - String key = input.readUTF(); |
476 |
| - String value = input.readUTF(); |
477 |
| - settingTypes.put(key, EntryType.valueOf(value)); |
478 |
| - } |
479 |
| - } |
480 |
| - // then read the actual keystore |
481 |
| - keystore.load(input, "".toCharArray()); |
482 |
| - } |
483 |
| - |
484 |
| - // verify the settings metadata matches the keystore entries |
485 |
| - Enumeration<String> aliases = keystore.aliases(); |
486 |
| - if (formatVersion == MIN_FORMAT_VERSION) { |
487 |
| - while (aliases.hasMoreElements()) { |
488 |
| - settingTypes.put(aliases.nextElement(), EntryType.STRING); |
489 |
| - } |
490 |
| - } else { |
491 |
| - // verify integrity: keys in keystore match what the metadata thinks exist |
492 |
| - Set<String> expectedSettings = new HashSet<>(settingTypes.keySet()); |
493 |
| - while (aliases.hasMoreElements()) { |
494 |
| - String settingName = aliases.nextElement(); |
495 |
| - if (expectedSettings.remove(settingName) == false) { |
496 |
| - throw new SecurityException("Keystore has been corrupted or tampered with"); |
497 |
| - } |
498 |
| - } |
499 |
| - if (expectedSettings.isEmpty() == false) { |
500 |
| - throw new SecurityException("Keystore has been corrupted or tampered with"); |
501 |
| - } |
502 |
| - } |
503 |
| - |
504 |
| - // fill in the entries now that we know all the types to expect |
505 |
| - this.entries.set(new HashMap<>()); |
506 |
| - SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBE"); |
507 |
| - KeyStore.PasswordProtection password = new KeyStore.PasswordProtection("".toCharArray()); |
508 |
| - |
509 |
| - for (Map.Entry<String, EntryType> settingEntry : settingTypes.entrySet()) { |
510 |
| - String setting = settingEntry.getKey(); |
511 |
| - EntryType settingType = settingEntry.getValue(); |
512 |
| - KeyStore.SecretKeyEntry keystoreEntry = (KeyStore.SecretKeyEntry) keystore.getEntry(setting, password); |
513 |
| - PBEKeySpec keySpec = (PBEKeySpec) keyFactory.getKeySpec(keystoreEntry.getSecretKey(), PBEKeySpec.class); |
514 |
| - char[] chars = keySpec.getPassword(); |
515 |
| - keySpec.clearPassword(); |
516 |
| - |
517 |
| - final byte[] bytes; |
518 |
| - if (settingType == EntryType.STRING) { |
519 |
| - ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode(CharBuffer.wrap(chars)); |
520 |
| - bytes = Arrays.copyOfRange(byteBuffer.array(), byteBuffer.position(), byteBuffer.limit()); |
521 |
| - Arrays.fill(byteBuffer.array(), (byte) 0); |
522 |
| - } else { |
523 |
| - assert settingType == EntryType.FILE; |
524 |
| - // The PBE keyspec gives us chars, we convert to bytes |
525 |
| - byte[] tmpBytes = new byte[chars.length]; |
526 |
| - for (int i = 0; i < tmpBytes.length; ++i) { |
527 |
| - tmpBytes[i] = (byte) chars[i]; // PBE only stores the lower 8 bits, so this narrowing is ok |
528 |
| - } |
529 |
| - bytes = Base64.getDecoder().decode(tmpBytes); |
530 |
| - Arrays.fill(tmpBytes, (byte) 0); |
531 |
| - } |
532 |
| - Arrays.fill(chars, '\0'); |
533 |
| - |
534 |
| - entries.get().put(setting, new Entry(bytes)); |
535 |
| - } |
536 |
| - } |
537 |
| - |
538 | 410 | /** Write the keystore to the given config directory. */
|
539 | 411 | public synchronized void save(Path configDir, char[] password) throws Exception {
|
540 | 412 | save(configDir, password, true);
|
|
0 commit comments