From d6fcaf7f2bd84faaec29a1aa68cbbf55647256e5 Mon Sep 17 00:00:00 2001 From: Ioannis Kakavas Date: Mon, 30 Apr 2018 10:43:56 +0300 Subject: [PATCH 1/2] Makes licensing FIPS 140 compliant Necessary changes so that the licensing functionality can be used in a FIPS 140 approved JVM. * Uses adequate salt length in encryption * Changes key derivation to PBKDF2WithHmacSHA512 from a custom approach with SHA512 and manual key stretching * Removes redundant manual padding Other relevant changes: * Uses the SAH512 hash instead of the encrypted key bytes as the key fingerprint to be included in the license specification * Removes the explicit verification check of the encryption key as this is implicitly checked in signature verification. --- .../license/licensor/LicenseSigner.java | 28 ++- .../src/test/resources/private.key | Bin 1232 -> 1232 bytes .../src/test/resources/public.key | Bin 304 -> 294 bytes x-pack/plugin/core/snapshot.key | Bin 304 -> 294 bytes .../org/elasticsearch/license/CryptUtils.java | 179 +++++++++--------- .../org/elasticsearch/license/License.java | 3 +- .../elasticsearch/license/LicenseService.java | 6 +- .../elasticsearch/license/LicenseUtils.java | 9 + .../license/LicenseVerifier.java | 18 +- .../license/SelfGeneratedLicense.java | 25 ++- .../StartupSelfGeneratedLicenseTask.java | 28 ++- .../license/LicenseServiceClusterTests.java | 21 ++ .../license/SelfGeneratedLicenseTests.java | 4 +- .../org/elasticsearch/license/TestUtils.java | 20 +- .../core/src/test/resources/private.key | Bin 1232 -> 1232 bytes .../plugin/core/src/test/resources/public.key | Bin 304 -> 294 bytes .../test/license/20_put_license.yml | 2 +- 17 files changed, 213 insertions(+), 130 deletions(-) diff --git a/x-pack/license-tools/src/main/java/org/elasticsearch/license/licensor/LicenseSigner.java b/x-pack/license-tools/src/main/java/org/elasticsearch/license/licensor/LicenseSigner.java index 1b28878e88875..3a02d83b44229 100644 --- a/x-pack/license-tools/src/main/java/org/elasticsearch/license/licensor/LicenseSigner.java +++ b/x-pack/license-tools/src/main/java/org/elasticsearch/license/licensor/LicenseSigner.java @@ -8,6 +8,7 @@ import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.BytesRefIterator; import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.hash.MessageDigests; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; @@ -20,7 +21,10 @@ import java.nio.file.Files; import java.nio.file.Path; import java.security.InvalidKeyException; +import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; import java.security.SecureRandom; import java.security.Signature; import java.security.SignatureException; @@ -35,9 +39,7 @@ public class LicenseSigner { private static final int MAGIC_LENGTH = 13; - private final Path publicKeyPath; - private final Path privateKeyPath; public LicenseSigner(final Path privateKeyPath, final Path publicKeyPath) { @@ -59,9 +61,11 @@ public License sign(License licenseSpec) throws IOException { Collections.singletonMap(License.LICENSE_SPEC_VIEW_MODE, "true"); licenseSpec.toXContent(contentBuilder, new ToXContent.MapParams(licenseSpecViewMode)); final byte[] signedContent; + final boolean legacy = licenseSpec.version() < License.VERSION_CRYPTO_ALGORITHMS; try { final Signature rsa = Signature.getInstance("SHA512withRSA"); - rsa.initSign(CryptUtils.readEncryptedPrivateKey(Files.readAllBytes(privateKeyPath))); + PrivateKey decryptedPrivateKey = CryptUtils.readEncryptedPrivateKey(Files.readAllBytes(privateKeyPath)); + rsa.initSign(decryptedPrivateKey); final BytesRefIterator iterator = BytesReference.bytes(contentBuilder).iterator(); BytesRef ref; while((ref = iterator.next()) != null) { @@ -77,15 +81,17 @@ public License sign(License licenseSpec) throws IOException { final byte[] magic = new byte[MAGIC_LENGTH]; SecureRandom random = new SecureRandom(); random.nextBytes(magic); - final byte[] hash = Base64.getEncoder().encode(Files.readAllBytes(publicKeyPath)); - assert hash != null; - byte[] bytes = new byte[4 + 4 + MAGIC_LENGTH + 4 + hash.length + 4 + signedContent.length]; + final byte[] publicKeyBytes = Files.readAllBytes(publicKeyPath); + PublicKey publicKey = CryptUtils.readPublicKey(publicKeyBytes); + final byte[] pubKeyFingerprint = legacy ? Base64.getEncoder().encode(CryptUtils.writeEncryptedPublicKey(publicKey)) : + getPublicKeyFingerprint(publicKeyBytes); + byte[] bytes = new byte[4 + 4 + MAGIC_LENGTH + 4 + pubKeyFingerprint.length + 4 + signedContent.length]; ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); byteBuffer.putInt(licenseSpec.version()) .putInt(magic.length) .put(magic) - .putInt(hash.length) - .put(hash) + .putInt(pubKeyFingerprint.length) + .put(pubKeyFingerprint) .putInt(signedContent.length) .put(signedContent); @@ -93,4 +99,10 @@ public License sign(License licenseSpec) throws IOException { .fromLicenseSpec(licenseSpec, Base64.getEncoder().encodeToString(bytes)) .build(); } + + private byte[] getPublicKeyFingerprint(byte[] keyBytes) { + MessageDigest sha256 = MessageDigests.sha256(); + sha256.update(keyBytes); + return sha256.digest(); + } } diff --git a/x-pack/license-tools/src/test/resources/private.key b/x-pack/license-tools/src/test/resources/private.key index 1f545803d875598d976b206250d5e95667c794ef..d0a658c3f07bf66a440c2085264e7df1cfb24d5a 100644 GIT binary patch literal 1232 zcmV;>1TXt!CXCA;xn6gI33Sx;gr<}{3~`2Di&z>_^uze-UP~880u>SHBYftf_|OGd zKL~W)R^OoSkiUKFxd zg;AUXjQUkMIRj*|Q4Y#n$Et3#`WO1|%~boac<@X=J}R1h?D-o}O|U}uH)fWVXi4n3 zMLC0^^);i>eW~B5tyaF*}3}T?qe%fEgUVNz(*0csSvD+DWT5QnV=GP{RnmL?IQvhV^sfXx= z4ug@Ucv9Kv|J#`x$%Y-b(l}U7RYQo%mX9$M+!`fbQo=ibfwM!rX;zT@Nsvwip?*)s zeX#ngV-Y39T*we7XtjK>9K;+Z8;Vs}4BI=pYJ27IUnB83!p<)by=RCjnSZw7;QUX-^fq7 z2Y|=wC%c>W5n6v~gF*@7{XCZtP|tPD3d!LcPv>h$e>7@hat*Xs0(R#{TzA{qC(|Qf z=_!83Rt%zq)=K4}rD2%uW{e3VIf8^$X3B!;xosvD3MV(Q7Q>v!h}s%m2vR315?~ z#_r25G{7;)$V;z3iW<$Vhw{9_5%iV0hM7Mz<7DPCx8;CqotW5XVk9>Q(Vc$EQJ?d; zYHYjhm~pmFxLw`X1#0#z;F--#7IMS^EwwpT+HBf}GwRC8G@JY7bx!c-W&arLDgdaKrzD#nKkfzD%T8P>4PHt^t?n%TP4!8P6u8n2SK{uW;txI51X#~UG@vq3kfh- u=U`2%yZJhw|6{o@4>6LZmM)1TXv1%Bc_wbLO>E3}&#D<@-rDJ$(p047kR zkI3mLE>J{X5~_|GE5>Bxd81`fV@~0j8z81fwu78=uPV1?-b_fL$snD`NE}Zsv(l}S z+Dr52K=W~W#&Y(zE)IK9$=-71EtPe#X0XhttX&@1ia@?lvh&>PD=A#>rfdl2Ru)*H zKaKRTp&qI~gmO-)fq;gzrbngk1r$cUKd~25@6;rjPJf6eLtCHTaDU8Rbt#KGKlZ%g zPd`h6iZ4kWgcXo9P=@b~YoN9WQmtytp^dd%)MXVsW%n~=T9#TzSWvr*YCR25X&mo6 zJoJz>&ju@jBz@-J;se7XMiU5&x@g8>FTI*r^O6Xv2(VF_R@Vk3wQBmIE>&XqH^t=M z5_c~f_`fm)R~9ZqEv|Ma3$nQ}LvHLD`h~L2VKXBZwZnP`c6%Qwnwq8Z0!hd2+^*Gk z6R7QdYCwK?kavUU0Vd*Mq5K{M!Vk zzSLrHwA_oZsoml1+m@@Wyh8$ZA^E2_FpxTTRz7~A$x?V+XFW0!aa4^L&A*^Evlz{gN8ae8h9=J=b zC8F19FN`xNpxA!48b`dGRK3GbQMOho1>byaB*psVh{^^yqVL4J0~$j7viqxma>pYOhkH~PwpJT1j6^yHDMWGO{vw8PyI zIrYD&$F3e@0!346r3BY7JkLDD+7E5EE6oiV(gQ62K@eQRE&#pRq81Tut!V+1TCM~srn6-g`!g_`uozhhJEnN6u9$5|& zLC>$@xw*^CD*fb$ngUWU)yZ%TqX*wM!HU2(O2RRFnw&?^e*xOZWZ?n`No9lb?BLpV=@K}hkB uJ-}qCO)pTE(AWQ24*9x_mu179!iEY^J(*;5;=+=*kZC+fDbTdAaQBA0ondAf&n5h4F(A+hDe6@4FLfG1potr0S^E$f&mHwf&l>lq`lcIsk6j2LTWnY z&Vy*<@g!8mW^?2RawcJR8$@Rx7zP}_aA{rE_%h*d&Qd(+L(pjjdeVA&wh2Wj&Cd*Z zsAyWOeYm&fsNS~>RGo+$v4hB&zx<^`yrvNn4$>3*Y5|jIkB_^*jI^bNe!(9hh|dR5 zd^En1l{tj+Ib)Y`HPe`4vLOsv-FsH1v7%VOr9CWGt&)IbARFa7rR>qO^Jl6#Xhfy$ zV5yh@0~!y9>f2a$=(U>gIOVJb!)|=HX}*mq$FiCrGx9WH4_WAD0CT{Z09Qh{Yjsa= sK(Y<>67jV1{NHszF%CC1e=iYPvTcIF(gc-Bv!3;}#x#Bf0s{d60pXE@5dZ)H literal 304 zcmV-00nh&Zj>mD`1#arZ%pYe0ikx>%eT^BphgE4yij^?pV%9R)x^ebh;_QdCuh!4Q zW6Uc_((oG8AST4JhGxlr-^T)p35dE~WSJg)5Pxr{Pw2i$WUj~xH9;uswX|N?9rjnL zM2)ISwd)TEefcdmYt+G`S`=?3?m4#RvU5l z8S}dpYjk&Ql8jx?OdR#2dLn!Y6H$vDY@hN`@N?om`8<6Sk6Xbq)*aK}TGk$XHH)u} z%E~t-*cAfm8$=KaS>=&0ondAf&n5h4F(A+hDe6@4FLfG1potr0S^E$f&mHwf&l>lq`lcIsk6j2LTWnY z&Vy*<@g!8mW^?2RawcJR8$@Rx7zP}_aA{rE_%h*d&Qd(+L(pjjdeVA&wh2Wj&Cd*Z zsAyWOeYm&fsNS~>RGo+$v4hB&zx<^`yrvNn4$>3*Y5|jIkB_^*jI^bNe!(9hh|dR5 zd^En1l{tj+Ib)Y`HPe`4vLOsv-FsH1v7%VOr9CWGt&)IbARFa7rR>qO^Jl6#Xhfy$ zV5yh@0~!y9>f2a$=(U>gIOVJb!)|=HX}*mq$FiCrGx9WH4_WAD0CT{Z09Qh{Yjsa= sK(Y<>67jV1{NHszF%CC1e=iYPvTcIF(gc-Bv!3;}#x#Bf0s{d60pXE@5dZ)H literal 304 zcmV-00nh&Zj>mD`1#arZ%pYe0ikx>%eT^BphgE4yij^?pV%9R)x^ebh;_QdCuh!4Q zW6Uc_((oG8AST4JhGxlr-^T)p35dE~WSJg)5Pxr{Pw2i$WUj~xH9;uswX|N?9rjnL zM2)ISwd)TEefcdmYt+G`S`=?3?m4#RvU5l z8S}dpYjk&Ql8jx?OdR#2dLn!Y6H$vDY@hN`@N?om`8<6Sk6Xbq)*aK}TGk$XHH)u} z%E~t-*cAfm8$=KaS>=&passPhrase */ - public static PrivateKey readEncryptedPrivateKey(byte[] fileContents, char[] passPhrase) { - PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(decrypt(fileContents, passPhrase)); + public static PrivateKey readEncryptedPrivateKey(byte[] fileContents, char[] passPhrase, boolean preV4) { + byte[] keyBytes = preV4 ? decryptV3Format(fileContents) : decrypt(fileContents, passPhrase); + PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(keyBytes); try { - return KeyFactory.getInstance(keyAlgorithm).generatePrivate(privateKeySpec); + return KeyFactory.getInstance(KEY_ALGORITHM).generatePrivate(privateKeySpec); } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { throw new IllegalStateException(e); } } /** - * Read encrypted public key file content with provided passPhrase + * Read public key file content */ - public static PublicKey readEncryptedPublicKey(byte[] fileContents, char[] passPhrase) { - X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(decrypt(fileContents, passPhrase)); + public static PublicKey readPublicKey(byte[] fileContents) { + X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(fileContents); try { - return KeyFactory.getInstance(CryptUtils.keyAlgorithm).generatePublic(publicKeySpec); + return KeyFactory.getInstance(CryptUtils.KEY_ALGORITHM).generatePublic(publicKeySpec); } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { throw new IllegalStateException(e); } @@ -112,9 +88,9 @@ public static PublicKey readEncryptedPublicKey(byte[] fileContents, char[] passP /** * Returns encrypted public key file content with provided passPhrase */ - public static byte[] writeEncryptedPublicKey(PublicKey publicKey, char[] passPhrase) { + public static byte[] writeEncryptedPublicKey(PublicKey publicKey) { X509EncodedKeySpec encodedKeySpec = new X509EncodedKeySpec(publicKey.getEncoded()); - return encrypt(encodedKeySpec.getEncoded(), passPhrase); + return encrypt(encodedKeySpec.getEncoded(), DEFAULT_PASS_PHRASE); } /** @@ -128,33 +104,25 @@ public static byte[] writeEncryptedPrivateKey(PrivateKey privateKey, char[] pass /** * Encrypts provided data with DEFAULT_PASS_PHRASE */ - public static byte[] encrypt(byte[] data) { - try { - return encrypt(data, hashPassPhrase(DEFAULT_PASS_PHRASE)); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException(e); - } + static byte[] encrypt(byte[] data) { + return encrypt(data, DEFAULT_PASS_PHRASE); } /** * Decrypts provided encryptedData with DEFAULT_PASS_PHRASE */ - public static byte[] decrypt(byte[] encryptedData) { - try { - return decrypt(encryptedData, hashPassPhrase(DEFAULT_PASS_PHRASE)); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException(e); - } + static byte[] decrypt(byte[] encryptedData) { + return decrypt(encryptedData, DEFAULT_PASS_PHRASE); } /** * Encrypts provided data with passPhrase */ - public static byte[] encrypt(byte[] data, char[] passPhrase) { + private static byte[] encrypt(byte[] data, char[] passPhrase) { try { - final Cipher encryptionCipher = getEncryptionCipher(getSecretKey(passPhrase)); - return encryptionCipher.doFinal(pad(data, minimumPadding)); - } catch (InvalidKeySpecException | IllegalBlockSizeException | BadPaddingException e) { + final Cipher encryptionCipher = getEncryptionCipher(deriveSecretKey(passPhrase)); + return encryptionCipher.doFinal(data); + } catch (IllegalBlockSizeException | BadPaddingException e) { throw new IllegalStateException(e); } } @@ -164,29 +132,60 @@ public static byte[] encrypt(byte[] data, char[] passPhrase) { */ private static byte[] decrypt(byte[] encryptedData, char[] passPhrase) { try { - final Cipher cipher = getDecryptionCipher(getSecretKey(passPhrase)); - return unPad(cipher.doFinal(encryptedData)); - } catch (IllegalBlockSizeException | BadPaddingException | InvalidKeySpecException e) { + final Cipher cipher = getDecryptionCipher(deriveSecretKey(passPhrase)); + return cipher.doFinal(encryptedData); + } catch (IllegalBlockSizeException | BadPaddingException e) { throw new IllegalStateException(e); } + } + static byte[] encryptV3Format(byte[] data) { + try { + SecretKey encryptionKey = getv3Key(); + final Cipher encryptionCipher = getEncryptionCipher(encryptionKey); + return encryptionCipher.doFinal(pad(data, 20)); + } catch (GeneralSecurityException e) { + throw new IllegalStateException(e); + } } - private static SecretKey getSecretKey(char[] passPhrase) throws InvalidKeySpecException { + static byte[] decryptV3Format(byte[] data) { try { - PBEKeySpec keySpec = new PBEKeySpec(passPhrase, salt, iterationCount, aesKeyLength); + SecretKey decryptionKey = getv3Key(); + final Cipher decryptionCipher = getDecryptionCipher(decryptionKey); + return unPad(decryptionCipher.doFinal(data)); + } catch (GeneralSecurityException e) { + throw new IllegalStateException(e); + } + } - byte[] shortKey = SecretKeyFactory.getInstance("PBEWithSHA1AndDESede"). - generateSecret(keySpec).getEncoded(); + private static SecretKey getv3Key() throws NoSuchAlgorithmException, InvalidKeySpecException { + final byte[] salt = { + (byte) 0xA9, (byte) 0xA2, (byte) 0xB5, (byte) 0xDE, + (byte) 0x2A, (byte) 0x8A, (byte) 0x9A, (byte) 0xE6 + }; + final byte[] passBytes = "elasticsearch-license".getBytes(StandardCharsets.UTF_8); + final byte[] digest = MessageDigest.getInstance("SHA-512").digest(passBytes); + final char[] hashedPassphrase = Base64.getEncoder().encodeToString(digest).toCharArray(); + PBEKeySpec keySpec = new PBEKeySpec(hashedPassphrase, salt, 1024, 128); + byte[] shortKey = SecretKeyFactory.getInstance("PBEWithSHA1AndDESede"). + generateSecret(keySpec).getEncoded(); + byte[] intermediaryKey = new byte[16]; + for (int i = 0, j = 0; i < 16; i++) { + intermediaryKey[i] = shortKey[j]; + if (++j == shortKey.length) + j = 0; + } + return new SecretKeySpec(intermediaryKey, "AES"); + } - byte[] intermediaryKey = new byte[aesKeyLength / 8]; - for (int i = 0, j = 0; i < aesKeyLength / 8; i++) { - intermediaryKey[i] = shortKey[j]; - if (++j == shortKey.length) - j = 0; - } + private static SecretKey deriveSecretKey(char[] passPhrase) { + try { + PBEKeySpec keySpec = new PBEKeySpec(passPhrase, SALT, KDF_ITERATION_COUNT, ENCRYPTION_KEY_LENGTH); - return new SecretKeySpec(intermediaryKey, "AES"); + SecretKey secretKey = SecretKeyFactory.getInstance(KDF_ALGORITHM). + generateSecret(keySpec); + return new SecretKeySpec(secretKey.getEncoded(), CIPHER_ALGORITHM); } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { throw new IllegalStateException(e); } @@ -202,8 +201,8 @@ private static Cipher getDecryptionCipher(SecretKey secretKey) { private static Cipher getCipher(int mode, SecretKey secretKey) { try { - Cipher cipher = Cipher.getInstance(secretKey.getAlgorithm()); - cipher.init(mode, secretKey, random); + Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); + cipher.init(mode, secretKey, RANDOM); return cipher; } catch (NoSuchAlgorithmException | InvalidKeyException | NoSuchPaddingException e) { throw new IllegalStateException(e); @@ -228,7 +227,7 @@ private static byte[] pad(byte[] bytes, int length) { // fill the rest with random bytes byte[] fill = new byte[padded - 1]; - random.nextBytes(fill); + RANDOM.nextBytes(fill); System.arraycopy(fill, 0, out, i, padded - 1); out[length] = (byte) (padded + 1); @@ -246,10 +245,4 @@ private static byte[] unPad(byte[] bytes) { return out; } - - private static char[] hashPassPhrase(String passPhrase) throws NoSuchAlgorithmException { - final byte[] passBytes = passPhrase.getBytes(StandardCharsets.UTF_8); - final byte[] digest = MessageDigest.getInstance(passHashAlgorithm).digest(passBytes); - return Base64.getEncoder().encodeToString(digest).toCharArray(); - } } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/License.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/License.java index df94a9132a059..144eec96858c6 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/License.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/License.java @@ -38,7 +38,8 @@ public class License implements ToXContentObject { public static final int VERSION_START = 1; public static final int VERSION_NO_FEATURE_TYPE = 2; public static final int VERSION_START_DATE = 3; - public static final int VERSION_CURRENT = VERSION_START_DATE; + public static final int VERSION_CRYPTO_ALGORITHMS = 4; + public static final int VERSION_CURRENT = VERSION_CRYPTO_ALGORITHMS; /** * XContent param name to deserialize license(s) with diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseService.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseService.java index 14e72142715b1..fa0c239aab17d 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseService.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseService.java @@ -402,9 +402,9 @@ public void clusterChanged(ClusterChangedEvent event) { boolean noLicense = noLicenseInPrevMetadata && noLicenseInCurrentMetadata; // auto-generate license if no licenses ever existed or if the current license is basic and - // needs extended. this will trigger a subsequent cluster changed event - if (currentClusterState.getNodes().isLocalNodeElectedMaster() - && (noLicense || LicenseUtils.licenseNeedsExtended(currentLicense))) { + // needs extended or if the license signature needs to be updated. this will trigger a subsequent cluster changed event + if (currentClusterState.getNodes().isLocalNodeElectedMaster() && + (noLicense || LicenseUtils.licenseNeedsExtended(currentLicense) || LicenseUtils.signatureNeedsUpdate(currentLicense))) { registerOrUpdateSelfGeneratedLicense(); } } else if (logger.isDebugEnabled()) { diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseUtils.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseUtils.java index c5ab35f862ccb..8fcdc05bcf986 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseUtils.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseUtils.java @@ -37,4 +37,13 @@ public static boolean isLicenseExpiredException(ElasticsearchSecurityException e public static boolean licenseNeedsExtended(License license) { return "basic".equals(license.type()) && license.expiryDate() != LicenseService.BASIC_SELF_GENERATED_LICENSE_EXPIRATION_MILLIS; } + + /** + * Checks if the signature of a self generated license with older version needs to be + * recreated with the new key + */ + public static boolean signatureNeedsUpdate(License license) { + return ("basic".equals(license.type()) || "trial".equals(license.type())) && + (license.version() < License.VERSION_CRYPTO_ALGORITHMS); + } } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseVerifier.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseVerifier.java index c670f070ad7c5..7d68a2b88fe99 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseVerifier.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseVerifier.java @@ -37,9 +37,9 @@ public class LicenseVerifier { * @param license to verify * @return true if valid, false otherwise */ - public static boolean verifyLicense(final License license, byte[] encryptedPublicKeyData) { + public static boolean verifyLicense(final License license, byte[] publicKeyData) { byte[] signedContent = null; - byte[] signatureHash = null; + byte[] publicKeyFingerprint = null; try { byte[] signatureBytes = Base64.getDecoder().decode(license.signature()); ByteBuffer byteBuffer = ByteBuffer.wrap(signatureBytes); @@ -48,32 +48,27 @@ public static boolean verifyLicense(final License license, byte[] encryptedPubli byte[] magic = new byte[magicLen]; byteBuffer.get(magic); int hashLen = byteBuffer.getInt(); - signatureHash = new byte[hashLen]; - byteBuffer.get(signatureHash); + publicKeyFingerprint = new byte[hashLen]; + byteBuffer.get(publicKeyFingerprint); int signedContentLen = byteBuffer.getInt(); signedContent = new byte[signedContentLen]; byteBuffer.get(signedContent); XContentBuilder contentBuilder = XContentFactory.contentBuilder(XContentType.JSON); license.toXContent(contentBuilder, new ToXContent.MapParams(Collections.singletonMap(License.LICENSE_SPEC_VIEW_MODE, "true"))); Signature rsa = Signature.getInstance("SHA512withRSA"); - rsa.initVerify(CryptUtils.readEncryptedPublicKey(encryptedPublicKeyData)); + rsa.initVerify(CryptUtils.readPublicKey(publicKeyData)); BytesRefIterator iterator = BytesReference.bytes(contentBuilder).iterator(); BytesRef ref; while((ref = iterator.next()) != null) { rsa.update(ref.bytes, ref.offset, ref.length); } - return rsa.verify(signedContent) - && Arrays.equals(Base64.getEncoder().encode(encryptedPublicKeyData), signatureHash); + return rsa.verify(signedContent); } catch (IOException | NoSuchAlgorithmException | SignatureException | InvalidKeyException e) { throw new IllegalStateException(e); } finally { - Arrays.fill(encryptedPublicKeyData, (byte) 0); if (signedContent != null) { Arrays.fill(signedContent, (byte) 0); } - if (signatureHash != null) { - Arrays.fill(signatureHash, (byte) 0); - } } } @@ -88,4 +83,5 @@ public static boolean verifyLicense(final License license) { } return verifyLicense(license, publicKeyBytes); } + } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/SelfGeneratedLicense.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/SelfGeneratedLicense.java index 7ec6b0b95eb74..0bc49d517cd92 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/SelfGeneratedLicense.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/SelfGeneratedLicense.java @@ -19,25 +19,36 @@ import java.util.Base64; import java.util.Collections; -import static org.elasticsearch.license.CryptUtils.decrypt; +import static org.elasticsearch.license.CryptUtils.encryptV3Format; import static org.elasticsearch.license.CryptUtils.encrypt; +import static org.elasticsearch.license.CryptUtils.decryptV3Format; +import static org.elasticsearch.license.CryptUtils.decrypt; class SelfGeneratedLicense { public static License create(License.Builder specBuilder) { + return create(specBuilder, License.VERSION_CURRENT); + } + + public static License create(License.Builder specBuilder, int version) { License spec = specBuilder .issuer("elasticsearch") - .version(License.VERSION_CURRENT) + .version(version) .build(); final String signature; try { XContentBuilder contentBuilder = XContentFactory.contentBuilder(XContentType.JSON); spec.toXContent(contentBuilder, new ToXContent.MapParams(Collections.singletonMap(License.LICENSE_SPEC_VIEW_MODE, "true"))); - byte[] encrypt = encrypt(BytesReference.toBytes(BytesReference.bytes(contentBuilder))); + byte[] encrypt; + if (version < License.VERSION_CRYPTO_ALGORITHMS) { + encrypt = encryptV3Format(BytesReference.toBytes(BytesReference.bytes(contentBuilder))); + } else { + encrypt = encrypt(BytesReference.toBytes(BytesReference.bytes(contentBuilder))); + } byte[] bytes = new byte[4 + 4 + encrypt.length]; ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); - // always generate license version -VERSION_CURRENT - byteBuffer.putInt(-License.VERSION_CURRENT) + // Set -version in signature + byteBuffer.putInt(-version) .putInt(encrypt.length) .put(encrypt); signature = Base64.getEncoder().encodeToString(bytes); @@ -56,9 +67,11 @@ public static boolean verify(final License license) { byte[] content = new byte[contentLen]; byteBuffer.get(content); final License expectedLicense; + // Version in signature is -version, so check for -(-version) < 4 + byte[] decryptedContent = (-version < License.VERSION_CRYPTO_ALGORITHMS) ? decryptV3Format(content) : decrypt(content); // EMPTY is safe here because we don't call namedObject try (XContentParser parser = XContentFactory.xContent(XContentType.JSON) - .createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, decrypt(content))) { + .createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, decryptedContent)) { parser.nextToken(); expectedLicense = License.builder().fromLicenseSpec(License.fromXContent(parser), license.signature()).version(-version).build(); diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/StartupSelfGeneratedLicenseTask.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/StartupSelfGeneratedLicenseTask.java index ef654513c80ca..77695f64538bc 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/StartupSelfGeneratedLicenseTask.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/StartupSelfGeneratedLicenseTask.java @@ -58,15 +58,41 @@ public ClusterState execute(ClusterState currentState) throws Exception { throw new IllegalArgumentException("Illegal self generated license type [" + type + "]. Must be trial or basic."); } - return updateWithLicense(currentState, type); } else if (LicenseUtils.licenseNeedsExtended(currentLicensesMetaData.getLicense())) { return extendBasic(currentState, currentLicensesMetaData); + } else if (LicenseUtils.signatureNeedsUpdate(currentLicensesMetaData.getLicense())) { + return updateLicenseSignature(currentState, currentLicensesMetaData); } else { return currentState; } } + private ClusterState updateLicenseSignature(ClusterState currentState, LicensesMetaData currentLicenseMetaData) { + License license = currentLicenseMetaData.getLicense(); + MetaData.Builder mdBuilder = MetaData.builder(currentState.metaData()); + String type = license.type(); + long issueDate = license.issueDate(); + long expiryDate; + if ("basic".equals(type)) { + expiryDate = LicenseService.BASIC_SELF_GENERATED_LICENSE_EXPIRATION_MILLIS; + } else { + expiryDate = issueDate + LicenseService.NON_BASIC_SELF_GENERATED_LICENSE_DURATION.getMillis(); + } + License.Builder specBuilder = License.builder() + .uid(license.uid()) + .issuedTo(license.issuedTo()) + .maxNodes(selfGeneratedLicenseMaxNodes) + .issueDate(issueDate) + .type(type) + .expiryDate(expiryDate); + License selfGeneratedLicense = SelfGeneratedLicense.create(specBuilder); + Version trialVersion = currentLicenseMetaData.getMostRecentTrialVersion(); + LicensesMetaData newLicenseMetadata = new LicensesMetaData(selfGeneratedLicense, trialVersion); + mdBuilder.putCustom(LicensesMetaData.TYPE, newLicenseMetadata); + return ClusterState.builder(currentState).metaData(mdBuilder).build(); + } + @Override public void onFailure(String source, @Nullable Exception e) { logger.error((Supplier) () -> new ParameterizedMessage("unexpected failure during [{}]", source), e); diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseServiceClusterTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseServiceClusterTests.java index ad508ddb7bc77..808228dd980ff 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseServiceClusterTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseServiceClusterTests.java @@ -149,6 +149,27 @@ public void testClusterRestartWhileExpired() throws Exception { assertLicenseActive(false); } + public void testClusterRestartWithOldSignature() throws Exception { + wipeAllLicenses(); + internalCluster().startNode(); + ensureGreen(); + assertLicenseActive(true); + putLicense(TestUtils.generateSignedLicenseOldSignature()); + LicensingClient licensingClient = new LicensingClient(client()); + assertThat(licensingClient.prepareGetLicense().get().license().version(), equalTo(License.VERSION_START_DATE)); + logger.info("--> restart node"); + internalCluster().fullRestart(); // restart so that license is updated + ensureYellow(); + logger.info("--> await node for enabled"); + assertLicenseActive(true); + licensingClient = new LicensingClient(client()); + assertThat(licensingClient.prepareGetLicense().get().license().version(), equalTo(License.VERSION_CURRENT)); //license updated + internalCluster().fullRestart(); // restart once more and verify updated license is active + ensureYellow(); + logger.info("--> await node for enabled"); + assertLicenseActive(true); + } + private void assertOperationMode(License.OperationMode operationMode) throws InterruptedException { boolean success = awaitBusy(() -> { for (XPackLicenseState licenseState : internalCluster().getDataNodeInstances(XPackLicenseState.class)) { diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/license/SelfGeneratedLicenseTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/license/SelfGeneratedLicenseTests.java index bf48079781410..aa27dbdcb4964 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/license/SelfGeneratedLicenseTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/license/SelfGeneratedLicenseTests.java @@ -19,7 +19,7 @@ import java.util.Collections; import java.util.UUID; -import static org.elasticsearch.license.CryptUtils.encrypt; +import static org.elasticsearch.license.CryptUtils.encryptV3Format; import static org.hamcrest.Matchers.equalTo; @@ -98,7 +98,7 @@ private static License createTrialLicense(License.Builder specBuilder) { try { XContentBuilder contentBuilder = XContentFactory.contentBuilder(XContentType.JSON); spec.toXContent(contentBuilder, new ToXContent.MapParams(Collections.singletonMap(License.LICENSE_SPEC_VIEW_MODE, "true"))); - byte[] encrypt = encrypt(BytesReference.toBytes(BytesReference.bytes(contentBuilder))); + byte[] encrypt = encryptV3Format(BytesReference.toBytes(BytesReference.bytes(contentBuilder))); byte[] bytes = new byte[4 + 4 + encrypt.length]; ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); byteBuffer.putInt(-spec.version()) diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/license/TestUtils.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/license/TestUtils.java index e8fa6f32a9bb5..d236dacaa4d99 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/license/TestUtils.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/license/TestUtils.java @@ -209,12 +209,11 @@ public LicenseSpec(int version, String uid, String feature, String issueDate, St this.maxNodes = maxNodes; } } - - public static Path getTestPriKeyPath() throws Exception { + private static Path getTestPriKeyPath() throws Exception { return getResourcePath("/private.key"); } - public static Path getTestPubKeyPath() throws Exception { + private static Path getTestPubKeyPath() throws Exception { return getResourcePath("/public.key"); } @@ -244,6 +243,19 @@ public static License generateSignedLicense(String type, long issueDate, TimeVal return generateSignedLicense(type, randomIntBetween(License.VERSION_START, License.VERSION_CURRENT), issueDate, expiryDuration); } + public static License generateSignedLicenseOldSignature() { + long issueDate = System.currentTimeMillis(); + License.Builder specBuilder = License.builder() + .uid(UUID.randomUUID().toString()) + .version(License.VERSION_START_DATE) + .issuedTo("customer") + .maxNodes(5) + .type("trial") + .issueDate(issueDate) + .expiryDate(issueDate + TimeValue.timeValueHours(24).getMillis()); + return SelfGeneratedLicense.create(specBuilder, License.VERSION_START_DATE); + } + /** * This method which chooses the license type randomly if the type is null. However, it will not randomly * choose trial or basic types as those types can only be self-generated. @@ -269,7 +281,7 @@ public static License generateSignedLicense(String type, int version, long issue builder.subscriptionType((type != null) ? type : randomFrom("dev", "gold", "platinum", "silver")); builder.feature(randomAlphaOfLength(10)); } - LicenseSigner signer = new LicenseSigner(getTestPriKeyPath(), getTestPubKeyPath()); + final LicenseSigner signer = new LicenseSigner(getTestPriKeyPath(), getTestPubKeyPath()); return signer.sign(builder.build()); } diff --git a/x-pack/plugin/core/src/test/resources/private.key b/x-pack/plugin/core/src/test/resources/private.key index 1f545803d875598d976b206250d5e95667c794ef..d0a658c3f07bf66a440c2085264e7df1cfb24d5a 100644 GIT binary patch literal 1232 zcmV;>1TXt!CXCA;xn6gI33Sx;gr<}{3~`2Di&z>_^uze-UP~880u>SHBYftf_|OGd zKL~W)R^OoSkiUKFxd zg;AUXjQUkMIRj*|Q4Y#n$Et3#`WO1|%~boac<@X=J}R1h?D-o}O|U}uH)fWVXi4n3 zMLC0^^);i>eW~B5tyaF*}3}T?qe%fEgUVNz(*0csSvD+DWT5QnV=GP{RnmL?IQvhV^sfXx= z4ug@Ucv9Kv|J#`x$%Y-b(l}U7RYQo%mX9$M+!`fbQo=ibfwM!rX;zT@Nsvwip?*)s zeX#ngV-Y39T*we7XtjK>9K;+Z8;Vs}4BI=pYJ27IUnB83!p<)by=RCjnSZw7;QUX-^fq7 z2Y|=wC%c>W5n6v~gF*@7{XCZtP|tPD3d!LcPv>h$e>7@hat*Xs0(R#{TzA{qC(|Qf z=_!83Rt%zq)=K4}rD2%uW{e3VIf8^$X3B!;xosvD3MV(Q7Q>v!h}s%m2vR315?~ z#_r25G{7;)$V;z3iW<$Vhw{9_5%iV0hM7Mz<7DPCx8;CqotW5XVk9>Q(Vc$EQJ?d; zYHYjhm~pmFxLw`X1#0#z;F--#7IMS^EwwpT+HBf}GwRC8G@JY7bx!c-W&arLDgdaKrzD#nKkfzD%T8P>4PHt^t?n%TP4!8P6u8n2SK{uW;txI51X#~UG@vq3kfh- u=U`2%yZJhw|6{o@4>6LZmM)1TXv1%Bc_wbLO>E3}&#D<@-rDJ$(p047kR zkI3mLE>J{X5~_|GE5>Bxd81`fV@~0j8z81fwu78=uPV1?-b_fL$snD`NE}Zsv(l}S z+Dr52K=W~W#&Y(zE)IK9$=-71EtPe#X0XhttX&@1ia@?lvh&>PD=A#>rfdl2Ru)*H zKaKRTp&qI~gmO-)fq;gzrbngk1r$cUKd~25@6;rjPJf6eLtCHTaDU8Rbt#KGKlZ%g zPd`h6iZ4kWgcXo9P=@b~YoN9WQmtytp^dd%)MXVsW%n~=T9#TzSWvr*YCR25X&mo6 zJoJz>&ju@jBz@-J;se7XMiU5&x@g8>FTI*r^O6Xv2(VF_R@Vk3wQBmIE>&XqH^t=M z5_c~f_`fm)R~9ZqEv|Ma3$nQ}LvHLD`h~L2VKXBZwZnP`c6%Qwnwq8Z0!hd2+^*Gk z6R7QdYCwK?kavUU0Vd*Mq5K{M!Vk zzSLrHwA_oZsoml1+m@@Wyh8$ZA^E2_FpxTTRz7~A$x?V+XFW0!aa4^L&A*^Evlz{gN8ae8h9=J=b zC8F19FN`xNpxA!48b`dGRK3GbQMOho1>byaB*psVh{^^yqVL4J0~$j7viqxma>pYOhkH~PwpJT1j6^yHDMWGO{vw8PyI zIrYD&$F3e@0!346r3BY7JkLDD+7E5EE6oiV(gQ62K@eQRE&#pRq81Tut!V+1TCM~srn6-g`!g_`uozhhJEnN6u9$5|& zLC>$@xw*^CD*fb$ngUWU)yZ%TqX*wM!HU2(O2RRFnw&?^e*xOZWZ?n`No9lb?BLpV=@K}hkB uJ-}qCO)pTE(AWQ24*9x_mu179!iEY^J(*;5;=+=*kZC+fDbTdAaQBA0ondAf&n5h4F(A+hDe6@4FLfG1potr0S^E$f&mHwf&l>lq`lcIsk6j2LTWnY z&Vy*<@g!8mW^?2RawcJR8$@Rx7zP}_aA{rE_%h*d&Qd(+L(pjjdeVA&wh2Wj&Cd*Z zsAyWOeYm&fsNS~>RGo+$v4hB&zx<^`yrvNn4$>3*Y5|jIkB_^*jI^bNe!(9hh|dR5 zd^En1l{tj+Ib)Y`HPe`4vLOsv-FsH1v7%VOr9CWGt&)IbARFa7rR>qO^Jl6#Xhfy$ zV5yh@0~!y9>f2a$=(U>gIOVJb!)|=HX}*mq$FiCrGx9WH4_WAD0CT{Z09Qh{Yjsa= sK(Y<>67jV1{NHszF%CC1e=iYPvTcIF(gc-Bv!3;}#x#Bf0s{d60pXE@5dZ)H literal 304 zcmV-00nh&Zj>mD`1#arZ%pYe0ikx>%eT^BphgE4yij^?pV%9R)x^ebh;_QdCuh!4Q zW6Uc_((oG8AST4JhGxlr-^T)p35dE~WSJg)5Pxr{Pw2i$WUj~xH9;uswX|N?9rjnL zM2)ISwd)TEefcdmYt+G`S`=?3?m4#RvU5l z8S}dpYjk&Ql8jx?OdR#2dLn!Y6H$vDY@hN`@N?om`8<6Sk6Xbq)*aK}TGk$XHH)u} z%E~t-*cAfm8$=KaS>=& Date: Wed, 2 May 2018 14:02:10 +0300 Subject: [PATCH 2/2] Address feedback --- .../org/elasticsearch/license/licensor/LicenseSigner.java | 4 ++-- .../main/java/org/elasticsearch/license/CryptUtils.java | 8 ++++---- .../java/org/elasticsearch/license/LicenseVerifier.java | 1 - 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/x-pack/license-tools/src/main/java/org/elasticsearch/license/licensor/LicenseSigner.java b/x-pack/license-tools/src/main/java/org/elasticsearch/license/licensor/LicenseSigner.java index 3a02d83b44229..a2b8dc9908fa9 100644 --- a/x-pack/license-tools/src/main/java/org/elasticsearch/license/licensor/LicenseSigner.java +++ b/x-pack/license-tools/src/main/java/org/elasticsearch/license/licensor/LicenseSigner.java @@ -61,7 +61,7 @@ public License sign(License licenseSpec) throws IOException { Collections.singletonMap(License.LICENSE_SPEC_VIEW_MODE, "true"); licenseSpec.toXContent(contentBuilder, new ToXContent.MapParams(licenseSpecViewMode)); final byte[] signedContent; - final boolean legacy = licenseSpec.version() < License.VERSION_CRYPTO_ALGORITHMS; + final boolean preV4 = licenseSpec.version() < License.VERSION_CRYPTO_ALGORITHMS; try { final Signature rsa = Signature.getInstance("SHA512withRSA"); PrivateKey decryptedPrivateKey = CryptUtils.readEncryptedPrivateKey(Files.readAllBytes(privateKeyPath)); @@ -83,7 +83,7 @@ public License sign(License licenseSpec) throws IOException { random.nextBytes(magic); final byte[] publicKeyBytes = Files.readAllBytes(publicKeyPath); PublicKey publicKey = CryptUtils.readPublicKey(publicKeyBytes); - final byte[] pubKeyFingerprint = legacy ? Base64.getEncoder().encode(CryptUtils.writeEncryptedPublicKey(publicKey)) : + final byte[] pubKeyFingerprint = preV4 ? Base64.getEncoder().encode(CryptUtils.writeEncryptedPublicKey(publicKey)) : getPublicKeyFingerprint(publicKeyBytes); byte[] bytes = new byte[4 + 4 + MAGIC_LENGTH + 4 + pubKeyFingerprint.length + 4 + signedContent.length]; ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/CryptUtils.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/CryptUtils.java index 0c2e986021318..8f55cca1a8457 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/CryptUtils.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/CryptUtils.java @@ -29,7 +29,7 @@ import java.util.Base64; public class CryptUtils { - // SALT must be at least 128bits + // SALT must be at least 128bits for FIPS 140-2 compliance private static final byte[] SALT = { (byte) 0x74, (byte) 0x68, (byte) 0x69, (byte) 0x73, (byte) 0x69, (byte) 0x73, (byte) 0x74, (byte) 0x68, @@ -141,7 +141,7 @@ private static byte[] decrypt(byte[] encryptedData, char[] passPhrase) { static byte[] encryptV3Format(byte[] data) { try { - SecretKey encryptionKey = getv3Key(); + SecretKey encryptionKey = getV3Key(); final Cipher encryptionCipher = getEncryptionCipher(encryptionKey); return encryptionCipher.doFinal(pad(data, 20)); } catch (GeneralSecurityException e) { @@ -151,7 +151,7 @@ static byte[] encryptV3Format(byte[] data) { static byte[] decryptV3Format(byte[] data) { try { - SecretKey decryptionKey = getv3Key(); + SecretKey decryptionKey = getV3Key(); final Cipher decryptionCipher = getDecryptionCipher(decryptionKey); return unPad(decryptionCipher.doFinal(data)); } catch (GeneralSecurityException e) { @@ -159,7 +159,7 @@ static byte[] decryptV3Format(byte[] data) { } } - private static SecretKey getv3Key() throws NoSuchAlgorithmException, InvalidKeySpecException { + private static SecretKey getV3Key() throws NoSuchAlgorithmException, InvalidKeySpecException { final byte[] salt = { (byte) 0xA9, (byte) 0xA2, (byte) 0xB5, (byte) 0xDE, (byte) 0x2A, (byte) 0x8A, (byte) 0x9A, (byte) 0xE6 diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseVerifier.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseVerifier.java index 7d68a2b88fe99..a879dc9ed1807 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseVerifier.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseVerifier.java @@ -83,5 +83,4 @@ public static boolean verifyLicense(final License license) { } return verifyLicense(license, publicKeyBytes); } - }