Make licensing FIPS-140 compliant (#30251)
Necessary changes so that the licensing functionality can be used in a JVM in FIPS 140 approved mode. * 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.
This commit is contained in:
parent
3e9fe3c9cd
commit
cca1a2a7cf
|
@ -8,6 +8,7 @@ package org.elasticsearch.license.licensor;
|
||||||
import org.apache.lucene.util.BytesRef;
|
import org.apache.lucene.util.BytesRef;
|
||||||
import org.apache.lucene.util.BytesRefIterator;
|
import org.apache.lucene.util.BytesRefIterator;
|
||||||
import org.elasticsearch.common.bytes.BytesReference;
|
import org.elasticsearch.common.bytes.BytesReference;
|
||||||
|
import org.elasticsearch.common.hash.MessageDigests;
|
||||||
import org.elasticsearch.common.xcontent.ToXContent;
|
import org.elasticsearch.common.xcontent.ToXContent;
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||||
|
@ -20,7 +21,10 @@ import java.nio.ByteBuffer;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.PublicKey;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.security.Signature;
|
import java.security.Signature;
|
||||||
import java.security.SignatureException;
|
import java.security.SignatureException;
|
||||||
|
@ -35,9 +39,7 @@ import java.util.Map;
|
||||||
public class LicenseSigner {
|
public class LicenseSigner {
|
||||||
|
|
||||||
private static final int MAGIC_LENGTH = 13;
|
private static final int MAGIC_LENGTH = 13;
|
||||||
|
|
||||||
private final Path publicKeyPath;
|
private final Path publicKeyPath;
|
||||||
|
|
||||||
private final Path privateKeyPath;
|
private final Path privateKeyPath;
|
||||||
|
|
||||||
public LicenseSigner(final Path privateKeyPath, final Path publicKeyPath) {
|
public LicenseSigner(final Path privateKeyPath, final Path publicKeyPath) {
|
||||||
|
@ -59,9 +61,11 @@ public class LicenseSigner {
|
||||||
Collections.singletonMap(License.LICENSE_SPEC_VIEW_MODE, "true");
|
Collections.singletonMap(License.LICENSE_SPEC_VIEW_MODE, "true");
|
||||||
licenseSpec.toXContent(contentBuilder, new ToXContent.MapParams(licenseSpecViewMode));
|
licenseSpec.toXContent(contentBuilder, new ToXContent.MapParams(licenseSpecViewMode));
|
||||||
final byte[] signedContent;
|
final byte[] signedContent;
|
||||||
|
final boolean preV4 = licenseSpec.version() < License.VERSION_CRYPTO_ALGORITHMS;
|
||||||
try {
|
try {
|
||||||
final Signature rsa = Signature.getInstance("SHA512withRSA");
|
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();
|
final BytesRefIterator iterator = BytesReference.bytes(contentBuilder).iterator();
|
||||||
BytesRef ref;
|
BytesRef ref;
|
||||||
while((ref = iterator.next()) != null) {
|
while((ref = iterator.next()) != null) {
|
||||||
|
@ -77,15 +81,17 @@ public class LicenseSigner {
|
||||||
final byte[] magic = new byte[MAGIC_LENGTH];
|
final byte[] magic = new byte[MAGIC_LENGTH];
|
||||||
SecureRandom random = new SecureRandom();
|
SecureRandom random = new SecureRandom();
|
||||||
random.nextBytes(magic);
|
random.nextBytes(magic);
|
||||||
final byte[] hash = Base64.getEncoder().encode(Files.readAllBytes(publicKeyPath));
|
final byte[] publicKeyBytes = Files.readAllBytes(publicKeyPath);
|
||||||
assert hash != null;
|
PublicKey publicKey = CryptUtils.readPublicKey(publicKeyBytes);
|
||||||
byte[] bytes = new byte[4 + 4 + MAGIC_LENGTH + 4 + hash.length + 4 + signedContent.length];
|
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);
|
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
|
||||||
byteBuffer.putInt(licenseSpec.version())
|
byteBuffer.putInt(licenseSpec.version())
|
||||||
.putInt(magic.length)
|
.putInt(magic.length)
|
||||||
.put(magic)
|
.put(magic)
|
||||||
.putInt(hash.length)
|
.putInt(pubKeyFingerprint.length)
|
||||||
.put(hash)
|
.put(pubKeyFingerprint)
|
||||||
.putInt(signedContent.length)
|
.putInt(signedContent.length)
|
||||||
.put(signedContent);
|
.put(signedContent);
|
||||||
|
|
||||||
|
@ -93,4 +99,10 @@ public class LicenseSigner {
|
||||||
.fromLicenseSpec(licenseSpec, Base64.getEncoder().encodeToString(bytes))
|
.fromLicenseSpec(licenseSpec, Base64.getEncoder().encodeToString(bytes))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private byte[] getPublicKeyFingerprint(byte[] keyBytes) {
|
||||||
|
MessageDigest sha256 = MessageDigests.sha256();
|
||||||
|
sha256.update(keyBytes);
|
||||||
|
return sha256.digest();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -15,95 +15,71 @@ import javax.crypto.SecretKeyFactory;
|
||||||
import javax.crypto.spec.PBEKeySpec;
|
import javax.crypto.spec.PBEKeySpec;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.KeyFactory;
|
import java.security.KeyFactory;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
import java.security.spec.InvalidKeySpecException;
|
import java.security.spec.InvalidKeySpecException;
|
||||||
import java.security.spec.PKCS8EncodedKeySpec;
|
import java.security.spec.PKCS8EncodedKeySpec;
|
||||||
import java.security.spec.X509EncodedKeySpec;
|
import java.security.spec.X509EncodedKeySpec;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
|
|
||||||
public class CryptUtils {
|
public class CryptUtils {
|
||||||
private static final int minimumPadding = 20;
|
// SALT must be at least 128bits for FIPS 140-2 compliance
|
||||||
private static final byte[] salt = {
|
private static final byte[] SALT = {
|
||||||
(byte) 0xA9, (byte) 0xA2, (byte) 0xB5, (byte) 0xDE,
|
(byte) 0x74, (byte) 0x68, (byte) 0x69, (byte) 0x73,
|
||||||
(byte) 0x2A, (byte) 0x8A, (byte) 0x9A, (byte) 0xE6
|
(byte) 0x69, (byte) 0x73, (byte) 0x74, (byte) 0x68,
|
||||||
|
(byte) 0x65, (byte) 0x73, (byte) 0x61, (byte) 0x6C,
|
||||||
|
(byte) 0x74, (byte) 0x77, (byte) 0x65, (byte) 0x75
|
||||||
};
|
};
|
||||||
private static final int iterationCount = 1024;
|
private static final String KEY_ALGORITHM = "RSA";
|
||||||
private static final int aesKeyLength = 128;
|
private static final char[] DEFAULT_PASS_PHRASE = "elasticsearch-license".toCharArray();
|
||||||
private static final String keyAlgorithm = "RSA";
|
private static final String KDF_ALGORITHM = "PBKDF2WithHmacSHA512";
|
||||||
private static final String passHashAlgorithm = "SHA-512";
|
private static final int KDF_ITERATION_COUNT = 10000;
|
||||||
private static final String DEFAULT_PASS_PHRASE = "elasticsearch-license";
|
private static final String CIPHER_ALGORITHM = "AES";
|
||||||
|
// This can be changed to 256 once Java 9 is the minimum version
|
||||||
private static final SecureRandom random = new SecureRandom();
|
// http://www.oracle.com/technetwork/java/javase/terms/readme/jdk9-readme-3852447.html#jce
|
||||||
|
private static final int ENCRYPTION_KEY_LENGTH = 128;
|
||||||
|
private static final SecureRandom RANDOM = new SecureRandom();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read encrypted private key file content with default pass phrase
|
* Read encrypted private key file content with default pass phrase
|
||||||
*/
|
*/
|
||||||
public static PrivateKey readEncryptedPrivateKey(byte[] fileContents) {
|
public static PrivateKey readEncryptedPrivateKey(byte[] fileContents) {
|
||||||
try {
|
return readEncryptedPrivateKey(fileContents, DEFAULT_PASS_PHRASE, false);
|
||||||
return readEncryptedPrivateKey(fileContents, hashPassPhrase(DEFAULT_PASS_PHRASE));
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new IllegalStateException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read encrypted public key file content with default pass phrase
|
|
||||||
*/
|
|
||||||
public static PublicKey readEncryptedPublicKey(byte[] fileContents) {
|
|
||||||
try {
|
|
||||||
return readEncryptedPublicKey(fileContents, hashPassPhrase(DEFAULT_PASS_PHRASE));
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new IllegalStateException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns encrypted public key file content with default pass phrase
|
|
||||||
*/
|
|
||||||
public static byte[] writeEncryptedPublicKey(PublicKey publicKey) {
|
|
||||||
try {
|
|
||||||
return writeEncryptedPublicKey(publicKey, hashPassPhrase(DEFAULT_PASS_PHRASE));
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new IllegalStateException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns encrypted private key file content with default pass phrase
|
* Returns encrypted private key file content with default pass phrase
|
||||||
*/
|
*/
|
||||||
public static byte[] writeEncryptedPrivateKey(PrivateKey privateKey) {
|
public static byte[] writeEncryptedPrivateKey(PrivateKey privateKey) {
|
||||||
try {
|
return writeEncryptedPrivateKey(privateKey, DEFAULT_PASS_PHRASE);
|
||||||
return writeEncryptedPrivateKey(privateKey, hashPassPhrase(DEFAULT_PASS_PHRASE));
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new IllegalStateException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read encrypted private key file content with provided <code>passPhrase</code>
|
* Read encrypted private key file content with provided <code>passPhrase</code>
|
||||||
*/
|
*/
|
||||||
public static PrivateKey readEncryptedPrivateKey(byte[] fileContents, char[] passPhrase) {
|
public static PrivateKey readEncryptedPrivateKey(byte[] fileContents, char[] passPhrase, boolean preV4) {
|
||||||
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(decrypt(fileContents, passPhrase));
|
byte[] keyBytes = preV4 ? decryptV3Format(fileContents) : decrypt(fileContents, passPhrase);
|
||||||
|
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(keyBytes);
|
||||||
try {
|
try {
|
||||||
return KeyFactory.getInstance(keyAlgorithm).generatePrivate(privateKeySpec);
|
return KeyFactory.getInstance(KEY_ALGORITHM).generatePrivate(privateKeySpec);
|
||||||
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
|
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
|
||||||
throw new IllegalStateException(e);
|
throw new IllegalStateException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read encrypted public key file content with provided <code>passPhrase</code>
|
* Read public key file content
|
||||||
*/
|
*/
|
||||||
public static PublicKey readEncryptedPublicKey(byte[] fileContents, char[] passPhrase) {
|
public static PublicKey readPublicKey(byte[] fileContents) {
|
||||||
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(decrypt(fileContents, passPhrase));
|
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(fileContents);
|
||||||
try {
|
try {
|
||||||
return KeyFactory.getInstance(CryptUtils.keyAlgorithm).generatePublic(publicKeySpec);
|
return KeyFactory.getInstance(CryptUtils.KEY_ALGORITHM).generatePublic(publicKeySpec);
|
||||||
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
|
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
|
||||||
throw new IllegalStateException(e);
|
throw new IllegalStateException(e);
|
||||||
}
|
}
|
||||||
|
@ -112,9 +88,9 @@ public class CryptUtils {
|
||||||
/**
|
/**
|
||||||
* Returns encrypted public key file content with provided <code>passPhrase</code>
|
* Returns encrypted public key file content with provided <code>passPhrase</code>
|
||||||
*/
|
*/
|
||||||
public static byte[] writeEncryptedPublicKey(PublicKey publicKey, char[] passPhrase) {
|
public static byte[] writeEncryptedPublicKey(PublicKey publicKey) {
|
||||||
X509EncodedKeySpec encodedKeySpec = new X509EncodedKeySpec(publicKey.getEncoded());
|
X509EncodedKeySpec encodedKeySpec = new X509EncodedKeySpec(publicKey.getEncoded());
|
||||||
return encrypt(encodedKeySpec.getEncoded(), passPhrase);
|
return encrypt(encodedKeySpec.getEncoded(), DEFAULT_PASS_PHRASE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -128,33 +104,25 @@ public class CryptUtils {
|
||||||
/**
|
/**
|
||||||
* Encrypts provided <code>data</code> with <code>DEFAULT_PASS_PHRASE</code>
|
* Encrypts provided <code>data</code> with <code>DEFAULT_PASS_PHRASE</code>
|
||||||
*/
|
*/
|
||||||
public static byte[] encrypt(byte[] data) {
|
static byte[] encrypt(byte[] data) {
|
||||||
try {
|
return encrypt(data, DEFAULT_PASS_PHRASE);
|
||||||
return encrypt(data, hashPassPhrase(DEFAULT_PASS_PHRASE));
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new IllegalStateException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypts provided <code>encryptedData</code> with <code>DEFAULT_PASS_PHRASE</code>
|
* Decrypts provided <code>encryptedData</code> with <code>DEFAULT_PASS_PHRASE</code>
|
||||||
*/
|
*/
|
||||||
public static byte[] decrypt(byte[] encryptedData) {
|
static byte[] decrypt(byte[] encryptedData) {
|
||||||
try {
|
return decrypt(encryptedData, DEFAULT_PASS_PHRASE);
|
||||||
return decrypt(encryptedData, hashPassPhrase(DEFAULT_PASS_PHRASE));
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new IllegalStateException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypts provided <code>data</code> with <code>passPhrase</code>
|
* Encrypts provided <code>data</code> with <code>passPhrase</code>
|
||||||
*/
|
*/
|
||||||
public static byte[] encrypt(byte[] data, char[] passPhrase) {
|
private static byte[] encrypt(byte[] data, char[] passPhrase) {
|
||||||
try {
|
try {
|
||||||
final Cipher encryptionCipher = getEncryptionCipher(getSecretKey(passPhrase));
|
final Cipher encryptionCipher = getEncryptionCipher(deriveSecretKey(passPhrase));
|
||||||
return encryptionCipher.doFinal(pad(data, minimumPadding));
|
return encryptionCipher.doFinal(data);
|
||||||
} catch (InvalidKeySpecException | IllegalBlockSizeException | BadPaddingException e) {
|
} catch (IllegalBlockSizeException | BadPaddingException e) {
|
||||||
throw new IllegalStateException(e);
|
throw new IllegalStateException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -164,29 +132,60 @@ public class CryptUtils {
|
||||||
*/
|
*/
|
||||||
private static byte[] decrypt(byte[] encryptedData, char[] passPhrase) {
|
private static byte[] decrypt(byte[] encryptedData, char[] passPhrase) {
|
||||||
try {
|
try {
|
||||||
final Cipher cipher = getDecryptionCipher(getSecretKey(passPhrase));
|
final Cipher cipher = getDecryptionCipher(deriveSecretKey(passPhrase));
|
||||||
return unPad(cipher.doFinal(encryptedData));
|
return cipher.doFinal(encryptedData);
|
||||||
} catch (IllegalBlockSizeException | BadPaddingException | InvalidKeySpecException e) {
|
} catch (IllegalBlockSizeException | BadPaddingException e) {
|
||||||
throw new IllegalStateException(e);
|
throw new IllegalStateException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SecretKey getSecretKey(char[] passPhrase) throws InvalidKeySpecException {
|
static byte[] encryptV3Format(byte[] data) {
|
||||||
try {
|
try {
|
||||||
PBEKeySpec keySpec = new PBEKeySpec(passPhrase, salt, iterationCount, aesKeyLength);
|
SecretKey encryptionKey = getV3Key();
|
||||||
|
final Cipher encryptionCipher = getEncryptionCipher(encryptionKey);
|
||||||
|
return encryptionCipher.doFinal(pad(data, 20));
|
||||||
|
} catch (GeneralSecurityException e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
byte[] shortKey = SecretKeyFactory.getInstance("PBEWithSHA1AndDESede").
|
static byte[] decryptV3Format(byte[] data) {
|
||||||
generateSecret(keySpec).getEncoded();
|
try {
|
||||||
|
SecretKey decryptionKey = getV3Key();
|
||||||
|
final Cipher decryptionCipher = getDecryptionCipher(decryptionKey);
|
||||||
|
return unPad(decryptionCipher.doFinal(data));
|
||||||
|
} catch (GeneralSecurityException e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
byte[] intermediaryKey = new byte[aesKeyLength / 8];
|
private static SecretKey getV3Key() throws NoSuchAlgorithmException, InvalidKeySpecException {
|
||||||
for (int i = 0, j = 0; i < aesKeyLength / 8; i++) {
|
final byte[] salt = {
|
||||||
intermediaryKey[i] = shortKey[j];
|
(byte) 0xA9, (byte) 0xA2, (byte) 0xB5, (byte) 0xDE,
|
||||||
if (++j == shortKey.length)
|
(byte) 0x2A, (byte) 0x8A, (byte) 0x9A, (byte) 0xE6
|
||||||
j = 0;
|
};
|
||||||
}
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
return new SecretKeySpec(intermediaryKey, "AES");
|
private static SecretKey deriveSecretKey(char[] passPhrase) {
|
||||||
|
try {
|
||||||
|
PBEKeySpec keySpec = new PBEKeySpec(passPhrase, SALT, KDF_ITERATION_COUNT, ENCRYPTION_KEY_LENGTH);
|
||||||
|
|
||||||
|
SecretKey secretKey = SecretKeyFactory.getInstance(KDF_ALGORITHM).
|
||||||
|
generateSecret(keySpec);
|
||||||
|
return new SecretKeySpec(secretKey.getEncoded(), CIPHER_ALGORITHM);
|
||||||
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
|
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
|
||||||
throw new IllegalStateException(e);
|
throw new IllegalStateException(e);
|
||||||
}
|
}
|
||||||
|
@ -202,8 +201,8 @@ public class CryptUtils {
|
||||||
|
|
||||||
private static Cipher getCipher(int mode, SecretKey secretKey) {
|
private static Cipher getCipher(int mode, SecretKey secretKey) {
|
||||||
try {
|
try {
|
||||||
Cipher cipher = Cipher.getInstance(secretKey.getAlgorithm());
|
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
|
||||||
cipher.init(mode, secretKey, random);
|
cipher.init(mode, secretKey, RANDOM);
|
||||||
return cipher;
|
return cipher;
|
||||||
} catch (NoSuchAlgorithmException | InvalidKeyException | NoSuchPaddingException e) {
|
} catch (NoSuchAlgorithmException | InvalidKeyException | NoSuchPaddingException e) {
|
||||||
throw new IllegalStateException(e);
|
throw new IllegalStateException(e);
|
||||||
|
@ -228,7 +227,7 @@ public class CryptUtils {
|
||||||
|
|
||||||
// fill the rest with random bytes
|
// fill the rest with random bytes
|
||||||
byte[] fill = new byte[padded - 1];
|
byte[] fill = new byte[padded - 1];
|
||||||
random.nextBytes(fill);
|
RANDOM.nextBytes(fill);
|
||||||
System.arraycopy(fill, 0, out, i, padded - 1);
|
System.arraycopy(fill, 0, out, i, padded - 1);
|
||||||
|
|
||||||
out[length] = (byte) (padded + 1);
|
out[length] = (byte) (padded + 1);
|
||||||
|
@ -246,10 +245,4 @@ public class CryptUtils {
|
||||||
|
|
||||||
return out;
|
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,8 @@ public class License implements ToXContentObject {
|
||||||
public static final int VERSION_START = 1;
|
public static final int VERSION_START = 1;
|
||||||
public static final int VERSION_NO_FEATURE_TYPE = 2;
|
public static final int VERSION_NO_FEATURE_TYPE = 2;
|
||||||
public static final int VERSION_START_DATE = 3;
|
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
|
* XContent param name to deserialize license(s) with
|
||||||
|
|
|
@ -402,9 +402,9 @@ public class LicenseService extends AbstractLifecycleComponent implements Cluste
|
||||||
|
|
||||||
boolean noLicense = noLicenseInPrevMetadata && noLicenseInCurrentMetadata;
|
boolean noLicense = noLicenseInPrevMetadata && noLicenseInCurrentMetadata;
|
||||||
// auto-generate license if no licenses ever existed or if the current license is basic and
|
// 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
|
// needs extended or if the license signature needs to be updated. this will trigger a subsequent cluster changed event
|
||||||
if (currentClusterState.getNodes().isLocalNodeElectedMaster()
|
if (currentClusterState.getNodes().isLocalNodeElectedMaster() &&
|
||||||
&& (noLicense || LicenseUtils.licenseNeedsExtended(currentLicense))) {
|
(noLicense || LicenseUtils.licenseNeedsExtended(currentLicense) || LicenseUtils.signatureNeedsUpdate(currentLicense))) {
|
||||||
registerOrUpdateSelfGeneratedLicense();
|
registerOrUpdateSelfGeneratedLicense();
|
||||||
}
|
}
|
||||||
} else if (logger.isDebugEnabled()) {
|
} else if (logger.isDebugEnabled()) {
|
||||||
|
|
|
@ -37,4 +37,13 @@ public class LicenseUtils {
|
||||||
public static boolean licenseNeedsExtended(License license) {
|
public static boolean licenseNeedsExtended(License license) {
|
||||||
return "basic".equals(license.type()) && license.expiryDate() != LicenseService.BASIC_SELF_GENERATED_LICENSE_EXPIRATION_MILLIS;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,9 +37,9 @@ public class LicenseVerifier {
|
||||||
* @param license to verify
|
* @param license to verify
|
||||||
* @return true if valid, false otherwise
|
* @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[] signedContent = null;
|
||||||
byte[] signatureHash = null;
|
byte[] publicKeyFingerprint = null;
|
||||||
try {
|
try {
|
||||||
byte[] signatureBytes = Base64.getDecoder().decode(license.signature());
|
byte[] signatureBytes = Base64.getDecoder().decode(license.signature());
|
||||||
ByteBuffer byteBuffer = ByteBuffer.wrap(signatureBytes);
|
ByteBuffer byteBuffer = ByteBuffer.wrap(signatureBytes);
|
||||||
|
@ -48,32 +48,27 @@ public class LicenseVerifier {
|
||||||
byte[] magic = new byte[magicLen];
|
byte[] magic = new byte[magicLen];
|
||||||
byteBuffer.get(magic);
|
byteBuffer.get(magic);
|
||||||
int hashLen = byteBuffer.getInt();
|
int hashLen = byteBuffer.getInt();
|
||||||
signatureHash = new byte[hashLen];
|
publicKeyFingerprint = new byte[hashLen];
|
||||||
byteBuffer.get(signatureHash);
|
byteBuffer.get(publicKeyFingerprint);
|
||||||
int signedContentLen = byteBuffer.getInt();
|
int signedContentLen = byteBuffer.getInt();
|
||||||
signedContent = new byte[signedContentLen];
|
signedContent = new byte[signedContentLen];
|
||||||
byteBuffer.get(signedContent);
|
byteBuffer.get(signedContent);
|
||||||
XContentBuilder contentBuilder = XContentFactory.contentBuilder(XContentType.JSON);
|
XContentBuilder contentBuilder = XContentFactory.contentBuilder(XContentType.JSON);
|
||||||
license.toXContent(contentBuilder, new ToXContent.MapParams(Collections.singletonMap(License.LICENSE_SPEC_VIEW_MODE, "true")));
|
license.toXContent(contentBuilder, new ToXContent.MapParams(Collections.singletonMap(License.LICENSE_SPEC_VIEW_MODE, "true")));
|
||||||
Signature rsa = Signature.getInstance("SHA512withRSA");
|
Signature rsa = Signature.getInstance("SHA512withRSA");
|
||||||
rsa.initVerify(CryptUtils.readEncryptedPublicKey(encryptedPublicKeyData));
|
rsa.initVerify(CryptUtils.readPublicKey(publicKeyData));
|
||||||
BytesRefIterator iterator = BytesReference.bytes(contentBuilder).iterator();
|
BytesRefIterator iterator = BytesReference.bytes(contentBuilder).iterator();
|
||||||
BytesRef ref;
|
BytesRef ref;
|
||||||
while((ref = iterator.next()) != null) {
|
while((ref = iterator.next()) != null) {
|
||||||
rsa.update(ref.bytes, ref.offset, ref.length);
|
rsa.update(ref.bytes, ref.offset, ref.length);
|
||||||
}
|
}
|
||||||
return rsa.verify(signedContent)
|
return rsa.verify(signedContent);
|
||||||
&& Arrays.equals(Base64.getEncoder().encode(encryptedPublicKeyData), signatureHash);
|
|
||||||
} catch (IOException | NoSuchAlgorithmException | SignatureException | InvalidKeyException e) {
|
} catch (IOException | NoSuchAlgorithmException | SignatureException | InvalidKeyException e) {
|
||||||
throw new IllegalStateException(e);
|
throw new IllegalStateException(e);
|
||||||
} finally {
|
} finally {
|
||||||
Arrays.fill(encryptedPublicKeyData, (byte) 0);
|
|
||||||
if (signedContent != null) {
|
if (signedContent != null) {
|
||||||
Arrays.fill(signedContent, (byte) 0);
|
Arrays.fill(signedContent, (byte) 0);
|
||||||
}
|
}
|
||||||
if (signatureHash != null) {
|
|
||||||
Arrays.fill(signatureHash, (byte) 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,25 +19,36 @@ import java.nio.ByteBuffer;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.Collections;
|
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.encrypt;
|
||||||
|
import static org.elasticsearch.license.CryptUtils.decryptV3Format;
|
||||||
|
import static org.elasticsearch.license.CryptUtils.decrypt;
|
||||||
|
|
||||||
class SelfGeneratedLicense {
|
class SelfGeneratedLicense {
|
||||||
|
|
||||||
public static License create(License.Builder specBuilder) {
|
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
|
License spec = specBuilder
|
||||||
.issuer("elasticsearch")
|
.issuer("elasticsearch")
|
||||||
.version(License.VERSION_CURRENT)
|
.version(version)
|
||||||
.build();
|
.build();
|
||||||
final String signature;
|
final String signature;
|
||||||
try {
|
try {
|
||||||
XContentBuilder contentBuilder = XContentFactory.contentBuilder(XContentType.JSON);
|
XContentBuilder contentBuilder = XContentFactory.contentBuilder(XContentType.JSON);
|
||||||
spec.toXContent(contentBuilder, new ToXContent.MapParams(Collections.singletonMap(License.LICENSE_SPEC_VIEW_MODE, "true")));
|
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];
|
byte[] bytes = new byte[4 + 4 + encrypt.length];
|
||||||
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
|
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
|
||||||
// always generate license version -VERSION_CURRENT
|
// Set -version in signature
|
||||||
byteBuffer.putInt(-License.VERSION_CURRENT)
|
byteBuffer.putInt(-version)
|
||||||
.putInt(encrypt.length)
|
.putInt(encrypt.length)
|
||||||
.put(encrypt);
|
.put(encrypt);
|
||||||
signature = Base64.getEncoder().encodeToString(bytes);
|
signature = Base64.getEncoder().encodeToString(bytes);
|
||||||
|
@ -56,9 +67,11 @@ class SelfGeneratedLicense {
|
||||||
byte[] content = new byte[contentLen];
|
byte[] content = new byte[contentLen];
|
||||||
byteBuffer.get(content);
|
byteBuffer.get(content);
|
||||||
final License expectedLicense;
|
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
|
// EMPTY is safe here because we don't call namedObject
|
||||||
try (XContentParser parser = XContentFactory.xContent(XContentType.JSON)
|
try (XContentParser parser = XContentFactory.xContent(XContentType.JSON)
|
||||||
.createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, decrypt(content))) {
|
.createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, decryptedContent)) {
|
||||||
parser.nextToken();
|
parser.nextToken();
|
||||||
expectedLicense = License.builder().fromLicenseSpec(License.fromXContent(parser),
|
expectedLicense = License.builder().fromLicenseSpec(License.fromXContent(parser),
|
||||||
license.signature()).version(-version).build();
|
license.signature()).version(-version).build();
|
||||||
|
|
|
@ -58,15 +58,41 @@ public class StartupSelfGeneratedLicenseTask extends ClusterStateUpdateTask {
|
||||||
throw new IllegalArgumentException("Illegal self generated license type [" + type +
|
throw new IllegalArgumentException("Illegal self generated license type [" + type +
|
||||||
"]. Must be trial or basic.");
|
"]. Must be trial or basic.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return updateWithLicense(currentState, type);
|
return updateWithLicense(currentState, type);
|
||||||
} else if (LicenseUtils.licenseNeedsExtended(currentLicensesMetaData.getLicense())) {
|
} else if (LicenseUtils.licenseNeedsExtended(currentLicensesMetaData.getLicense())) {
|
||||||
return extendBasic(currentState, currentLicensesMetaData);
|
return extendBasic(currentState, currentLicensesMetaData);
|
||||||
|
} else if (LicenseUtils.signatureNeedsUpdate(currentLicensesMetaData.getLicense())) {
|
||||||
|
return updateLicenseSignature(currentState, currentLicensesMetaData);
|
||||||
} else {
|
} else {
|
||||||
return currentState;
|
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
|
@Override
|
||||||
public void onFailure(String source, @Nullable Exception e) {
|
public void onFailure(String source, @Nullable Exception e) {
|
||||||
logger.error((Supplier<?>) () -> new ParameterizedMessage("unexpected failure during [{}]", source), e);
|
logger.error((Supplier<?>) () -> new ParameterizedMessage("unexpected failure during [{}]", source), e);
|
||||||
|
|
|
@ -153,6 +153,27 @@ public class LicenseServiceClusterTests extends AbstractLicensesIntegrationTestC
|
||||||
assertLicenseActive(false);
|
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 {
|
private void assertOperationMode(License.OperationMode operationMode) throws InterruptedException {
|
||||||
boolean success = awaitBusy(() -> {
|
boolean success = awaitBusy(() -> {
|
||||||
for (XPackLicenseState licenseState : internalCluster().getDataNodeInstances(XPackLicenseState.class)) {
|
for (XPackLicenseState licenseState : internalCluster().getDataNodeInstances(XPackLicenseState.class)) {
|
||||||
|
|
|
@ -19,7 +19,7 @@ import java.util.Base64;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import static org.elasticsearch.license.CryptUtils.encrypt;
|
import static org.elasticsearch.license.CryptUtils.encryptV3Format;
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
|
|
||||||
|
|
||||||
|
@ -98,7 +98,7 @@ public class SelfGeneratedLicenseTests extends ESTestCase {
|
||||||
try {
|
try {
|
||||||
XContentBuilder contentBuilder = XContentFactory.contentBuilder(XContentType.JSON);
|
XContentBuilder contentBuilder = XContentFactory.contentBuilder(XContentType.JSON);
|
||||||
spec.toXContent(contentBuilder, new ToXContent.MapParams(Collections.singletonMap(License.LICENSE_SPEC_VIEW_MODE, "true")));
|
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];
|
byte[] bytes = new byte[4 + 4 + encrypt.length];
|
||||||
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
|
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
|
||||||
byteBuffer.putInt(-spec.version())
|
byteBuffer.putInt(-spec.version())
|
||||||
|
|
|
@ -209,12 +209,11 @@ public class TestUtils {
|
||||||
this.maxNodes = maxNodes;
|
this.maxNodes = maxNodes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private static Path getTestPriKeyPath() throws Exception {
|
||||||
public static Path getTestPriKeyPath() throws Exception {
|
|
||||||
return getResourcePath("/private.key");
|
return getResourcePath("/private.key");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Path getTestPubKeyPath() throws Exception {
|
private static Path getTestPubKeyPath() throws Exception {
|
||||||
return getResourcePath("/public.key");
|
return getResourcePath("/public.key");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -244,6 +243,19 @@ public class TestUtils {
|
||||||
return generateSignedLicense(type, randomIntBetween(License.VERSION_START, License.VERSION_CURRENT), issueDate, expiryDuration);
|
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
|
* 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.
|
* choose trial or basic types as those types can only be self-generated.
|
||||||
|
@ -269,7 +281,7 @@ public class TestUtils {
|
||||||
builder.subscriptionType((type != null) ? type : randomFrom("dev", "gold", "platinum", "silver"));
|
builder.subscriptionType((type != null) ? type : randomFrom("dev", "gold", "platinum", "silver"));
|
||||||
builder.feature(randomAlphaOfLength(10));
|
builder.feature(randomAlphaOfLength(10));
|
||||||
}
|
}
|
||||||
LicenseSigner signer = new LicenseSigner(getTestPriKeyPath(), getTestPubKeyPath());
|
final LicenseSigner signer = new LicenseSigner(getTestPriKeyPath(), getTestPubKeyPath());
|
||||||
return signer.sign(builder.build());
|
return signer.sign(builder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -4,7 +4,7 @@ teardown:
|
||||||
xpack.license.post:
|
xpack.license.post:
|
||||||
acknowledge: true
|
acknowledge: true
|
||||||
body: |
|
body: |
|
||||||
{"licenses":[{"uid":"894371dc-9t49-4997-93cb-8o2e3r7fa6a8","type":"trial","issue_date_in_millis":1411948800000,"expiry_date_in_millis":1916956799999,"max_nodes":1,"issued_to":"issuedTo","issuer":"issuer","signature":"AAAAAgAAAA0FWh0T9njItjQ2qammAAABmC9ZN0hjZDBGYnVyRXpCOW5Bb3FjZDAxOWpSbTVoMVZwUzRxVk1PSmkxakxZdW5IMlhlTHNoN1N2MXMvRFk4d3JTZEx3R3RRZ0pzU3lobWJKZnQvSEFva0ppTHBkWkprZWZSQi9iNmRQNkw1SlpLN0lDalZCS095MXRGN1lIZlpYcVVTTnFrcTE2dzhJZmZrdFQrN3JQeGwxb0U0MXZ0dDJHSERiZTVLOHNzSDByWnpoZEphZHBEZjUrTVBxRENNSXNsWWJjZllaODdzVmEzUjNiWktNWGM5TUhQV2plaUo4Q1JOUml4MXNuL0pSOEhQaVB2azhmUk9QVzhFeTFoM1Q0RnJXSG53MWk2K055c28zSmRnVkF1b2JSQkFLV2VXUmVHNDZ2R3o2VE1qbVNQS2lxOHN5bUErZlNIWkZSVmZIWEtaSU9wTTJENDVvT1NCYklacUYyK2FwRW9xa0t6dldMbmMzSGtQc3FWOTgzZ3ZUcXMvQkt2RUZwMFJnZzlvL2d2bDRWUzh6UG5pdENGWFRreXNKNkE9PQAAAQBZhvozA0trrxhUZ1QbaTsKTna9C5KVQ6pv8yg1pnsBpZXCl8kX1SrgoFn1bXq61IvJwfw5qnmYNiH3hRhTO9EyaCBqaLk8NXZQ6TrRkQSpEnnBwAYUkZeKXsIuBoOk4B4mzwC/r8aMAkzrTiEBtBbog+57cSaU9y37Gkdd+1jXCQrxP+jOEUf7gnXWZvE6oeRroLvCt1fYn09k0CF8kKTbrPTSjC6igZR3uvTHyee74XQ9PRavvHax73T4UOEdQZX/P1ibSQIWKbBRD5YQ1POYVjTayoltTnWLMxfEcAkkATJZLhpBEHST7kZWjrTS6J1dCReJc7a8Vsj/78HXvOIy"}]}
|
{"licenses":[{"uid":"3aa62ffe-36e1-4fad-bfdc-9dff8301eb22","type":"trial","issue_date_in_millis":1523456691721,"expiry_date_in_millis":1838816691721,"max_nodes":5,"issued_to":"customer","issuer":"elasticsearch","signature":"AAAABAAAAA2kWNcuc+DT0lrlmYZKAAAAIAo5/x6hrsGh1GqqrJmy4qgmEC7gK0U4zQ6q5ZEMhm4jAAABAEn6fG9y2VxKBu2T3D5hffh56kzOQODCOdhr0y2d17ZSIJMZRqO7ZywPCWNS1aR33GhfIHkTER0ysML0xMH/gXavhyRvMBndJj0UBKzuwpTawSlnxYtcqN8mSBIvJC7Ki+uJ1SpAILC2ZP9fnkRlqwXqBlTwfYn7xnZgu9DKrOWru/ipTPObo7jcePl8VTK6nWFen7/hCFDQTUFZ0jQvd+nq7A1PAcHGNxGfdbMVmAXCXgGWkRfT3clo9/vadgo+isNyh1sPq9mN7gwsvBAKtA1FrpH2EXYYbfOsSpBvUmhYMgErLg1k3/CbS0pCWLKOaX1xTMayosdZOjagU3auZXY=","start_date_in_millis":-1}]}
|
||||||
---
|
---
|
||||||
"Installing and getting license works":
|
"Installing and getting license works":
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue