diff --git a/src/main/java/org/elasticsearch/license/core/DateUtils.java b/src/main/java/org/elasticsearch/license/core/DateUtils.java index fc9e2bbc3dc..68557aab1ea 100644 --- a/src/main/java/org/elasticsearch/license/core/DateUtils.java +++ b/src/main/java/org/elasticsearch/license/core/DateUtils.java @@ -15,6 +15,7 @@ import java.util.TimeZone; public class DateUtils { public static final TimeZone TIME_ZONE = TimeZone.getTimeZone("UTC"); + //MutableDateTime. private static DateFormat getDateFormat() { DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); dateFormat.setTimeZone(TIME_ZONE); diff --git a/src/main/java/org/elasticsearch/license/licensor/ESLicenseSigner.java b/src/main/java/org/elasticsearch/license/licensor/ESLicenseSigner.java index 9c83fe31ee9..8bd6d388d36 100644 --- a/src/main/java/org/elasticsearch/license/licensor/ESLicenseSigner.java +++ b/src/main/java/org/elasticsearch/license/licensor/ESLicenseSigner.java @@ -14,6 +14,9 @@ import net.nicholaswilliams.java.licensing.licensor.LicenseCreator; import net.nicholaswilliams.java.licensing.licensor.LicenseCreatorProperties; import org.apache.commons.codec.binary.Base64; import org.elasticsearch.common.collect.ImmutableSet; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentFactory; +import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.license.core.ESLicense; import org.elasticsearch.license.core.LicensesCharset; @@ -90,11 +93,11 @@ public class ESLicenseSigner { .withIssueDate(licenseSpec.issueDate()) .withProductKey(licenseSpec.uid()) .withHolder(licenseSpec.issuedTo()) - .withIssuer(licenseSpec.issuer()) - .addFeature("feature:" + licenseSpec.feature(), licenseSpec.expiryDate()) - .addFeature("maxNodes:" + String.valueOf(licenseSpec.maxNodes())) - .addFeature("type:" + licenseSpec.type().string()) - .addFeature("subscription_type:" + licenseSpec.subscriptionType().string()); + .withIssuer(licenseSpec.issuer()); + + XContentBuilder contentBuilder = XContentFactory.contentBuilder(XContentType.JSON); + featureToXContent(licenseSpec, contentBuilder); + licenseBuilder.addFeature(contentBuilder.string()); final License license = licenseBuilder.build(); @@ -121,4 +124,13 @@ public class ESLicenseSigner { .verifyAndBuild(); } + private void featureToXContent(ESLicense license, XContentBuilder builder) throws IOException { + builder.startObject(); + builder.field("feature", license.feature()); + builder.field("type", license.type().string()); + builder.field("subscription_type", license.subscriptionType().string()); + builder.field("max_nodes", license.maxNodes()); + builder.endObject(); + } + } diff --git a/src/main/java/org/elasticsearch/license/manager/ESLicenseManager.java b/src/main/java/org/elasticsearch/license/manager/ESLicenseManager.java index 9d8f86c9d21..34d7ab306f8 100644 --- a/src/main/java/org/elasticsearch/license/manager/ESLicenseManager.java +++ b/src/main/java/org/elasticsearch/license/manager/ESLicenseManager.java @@ -10,19 +10,20 @@ import net.nicholaswilliams.java.licensing.encryption.Hasher; import net.nicholaswilliams.java.licensing.encryption.PasswordProvider; import net.nicholaswilliams.java.licensing.exception.ExpiredLicenseException; import net.nicholaswilliams.java.licensing.exception.InvalidLicenseException; +import org.apache.commons.codec.binary.Base64; import org.elasticsearch.common.collect.ImmutableSet; import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.xcontent.XContentFactory; +import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.license.core.ESLicense; import org.elasticsearch.license.core.ResourcePublicKeyDataProvider; -import java.util.Collection; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.*; import static org.elasticsearch.license.core.ESLicense.SubscriptionType; import static org.elasticsearch.license.core.ESLicense.Type; -import static org.elasticsearch.license.manager.Utils.extractSignedLicence; /** * Class responsible for reading signed licenses, maintaining an effective esLicenses instance, verification of licenses @@ -33,11 +34,11 @@ public class ESLicenseManager { private final LicenseManager licenseManager; - private static class Prefix { - static final String MAX_NODES = "maxNodes:"; - static final String TYPE = "type:"; - static final String SUBSCRIPTION_TYPE = "subscription_type:"; - static final String FEATURE = "feature:"; + private static class FeatureFields { + static final String MAX_NODES = "max_nodes"; + static final String TYPE = "type"; + static final String SUBSCRIPTION_TYPE = "subscription_type"; + static final String FEATURE = "feature"; } // Initialize LicenseManager @@ -89,27 +90,32 @@ public class ESLicenseManager { } } catch (ExpiredLicenseException e) { throw new InvalidLicenseException("Expired License"); - } catch (InvalidLicenseException e) { + } catch (InvalidLicenseException | IOException e) { throw new InvalidLicenseException("Invalid License"); } } private ESLicense fromSignature(String signature) { - final SignedLicense signedLicense = Utils.extractSignedLicence(signature); + final SignedLicense signedLicense = extractSignedLicence(signature); License license = licenseManager.decryptAndVerifyLicense(signedLicense); ESLicense.Builder builder = ESLicense.builder(); for (License.Feature feature : license.getFeatures()) { String featureName = feature.getName(); - if (featureName.startsWith(Prefix.MAX_NODES)) { - builder.maxNodes(Integer.parseInt(featureName.substring(Prefix.MAX_NODES.length()))); - } else if (featureName.startsWith(Prefix.TYPE)) { - builder.type(Type.fromString(featureName.substring(Prefix.TYPE.length()))); - } else if (featureName.startsWith(Prefix.SUBSCRIPTION_TYPE)) { - builder.subscriptionType(SubscriptionType.fromString(featureName.substring(Prefix.SUBSCRIPTION_TYPE.length()))); - } else if (featureName.startsWith(Prefix.FEATURE)) { - builder.feature(featureName.substring(Prefix.FEATURE.length())); - } + LicenseFeatures licenseFeatures; + try { + licenseFeatures = licenseFeaturesFromSource(featureName); + if (licenseFeatures.maxNodes != -1 + && licenseFeatures.feature != null + && licenseFeatures.type != null + && licenseFeatures.subscriptionType != null) { + builder.maxNodes(licenseFeatures.maxNodes) + .feature(licenseFeatures.feature) + .type(Type.fromString(licenseFeatures.type)) + .subscriptionType(SubscriptionType.fromString(licenseFeatures.subscriptionType)); + break; + } + } catch (IOException ignored) {} } return builder @@ -122,12 +128,11 @@ public class ESLicenseManager { .build(); } - private static void verifyLicenseFields(License license, ESLicense eslicense) { + private static void verifyLicenseFields(License license, ESLicense eslicense) throws IOException { boolean licenseValid = license.getProductKey().equals(eslicense.uid()) && license.getHolder().equals(eslicense.issuedTo()) && license.getIssueDate() == eslicense.issueDate() && license.getGoodBeforeDate() == eslicense.expiryDate(); - assert license.getFeatures().size() == 4 : "one license should have only four features"; boolean maxNodesValid = false; boolean featureValid = false; boolean typeValid = false; @@ -135,16 +140,14 @@ public class ESLicenseManager { for (License.Feature feature : license.getFeatures()) { String featureName = feature.getName(); - if (featureName.startsWith(Prefix.MAX_NODES)) { - maxNodesValid = eslicense.maxNodes() == Integer.parseInt(featureName.substring(Prefix.MAX_NODES.length())); - } else if (featureName.startsWith(Prefix.TYPE)) { - typeValid = eslicense.type() == Type.fromString(featureName.substring(Prefix.TYPE.length())); - } else if (featureName.startsWith(Prefix.SUBSCRIPTION_TYPE)) { - subscriptionTypeValid = eslicense.subscriptionType() == SubscriptionType.fromString(featureName.substring(Prefix.SUBSCRIPTION_TYPE.length())); - } else if (featureName.startsWith(Prefix.FEATURE)) { - String featureValue = featureName.substring(Prefix.FEATURE.length()); - featureValid = featureValue.equals(eslicense.feature()) - && feature.getGoodBeforeDate() == eslicense.expiryDate(); + LicenseFeatures licenseFeatures = licenseFeaturesFromSource(featureName); + maxNodesValid = eslicense.maxNodes() == licenseFeatures.maxNodes; + typeValid = eslicense.type().string().equals(licenseFeatures.type); + subscriptionTypeValid = eslicense.subscriptionType().string().equals(licenseFeatures.subscriptionType); + featureValid = eslicense.feature().equals(licenseFeatures.feature); + + if (maxNodesValid && typeValid && subscriptionTypeValid && featureValid) { + break; } } if (!licenseValid || !featureValid || !maxNodesValid || !typeValid || !subscriptionTypeValid) { @@ -152,6 +155,73 @@ public class ESLicenseManager { } } + /** + * Extract a signedLicense (SIGNED_LICENSE_CONTENT) from the signature. + * Validates the public key used to decrypt the license by comparing their hashes + *
+ * Signature structure: + * | MAGIC | HEADER_LENGTH | VERSION | PUB_KEY_DIGEST | SIGNED_LICENSE_CONTENT | + * + * @param signature of a single license + * @return signed license content for the license + */ + private static SignedLicense extractSignedLicence(String signature) { + byte[] signatureBytes = Base64.decodeBase64(signature); + ByteBuffer byteBuffer = ByteBuffer.wrap(signatureBytes); + byteBuffer = (ByteBuffer) byteBuffer.position(13); + int start = byteBuffer.getInt(); + int version = byteBuffer.getInt(); + return new ObjectSerializer().readObject(SignedLicense.class, Arrays.copyOfRange(signatureBytes, start, signatureBytes.length)); + } + + private static LicenseFeatures licenseFeaturesFromSource(String source) throws IOException { + XContentParser parser = XContentFactory.xContent(source).createParser(source); + XContentParser.Token token; + + String feature = null; + String type = null; + String subscriptionType = null; + int maxNodes = -1; + + String currentName = null; + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + if (token == XContentParser.Token.FIELD_NAME) { + currentName = parser.currentName(); + } else if (token == XContentParser.Token.VALUE_STRING) { + switch (currentName) { + case FeatureFields.FEATURE: + feature = parser.text(); + break; + case FeatureFields.TYPE: + type = parser.text(); + break; + case FeatureFields.SUBSCRIPTION_TYPE: + subscriptionType = parser.text(); + break; + } + } else if (token == XContentParser.Token.VALUE_NUMBER) { + if (FeatureFields.MAX_NODES.equals(currentName)) { + maxNodes = parser.intValue(); + } + } + } + return new LicenseFeatures(feature, type, subscriptionType, maxNodes); + } + + private static class LicenseFeatures { + private final String feature; + private final String type; + private final String subscriptionType; + private final int maxNodes; + + private LicenseFeatures(String feature, String type, String subscriptionType, int maxNodes) { + this.feature = feature; + this.type = type; + this.subscriptionType = subscriptionType; + this.maxNodes = maxNodes; + } + } + // TODO: Need a better password management private static class ESPublicKeyPasswordProvider implements PasswordProvider { private final String DEFAULT_PASS_PHRASE = "elasticsearch-license"; diff --git a/src/main/java/org/elasticsearch/license/manager/Utils.java b/src/main/java/org/elasticsearch/license/manager/Utils.java deleted file mode 100644 index f78a397bbfb..00000000000 --- a/src/main/java/org/elasticsearch/license/manager/Utils.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.license.manager; - -import net.nicholaswilliams.java.licensing.ObjectSerializer; -import net.nicholaswilliams.java.licensing.SignedLicense; -import org.apache.commons.codec.binary.Base64; -import org.elasticsearch.common.collect.ImmutableMap; -import org.elasticsearch.license.core.ESLicense; - -import java.nio.ByteBuffer; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -public final class Utils { - /** - * Extract a signedLicense (SIGNED_LICENSE_CONTENT) from the signature. - * Validates the public key used to decrypt the license by comparing their hashes - * - * Signature structure: - * | MAGIC | HEADER_LENGTH | VERSION | PUB_KEY_DIGEST | SIGNED_LICENSE_CONTENT | - * - * @param signature of a single license - * @return signed license content for the license - */ - public static SignedLicense extractSignedLicence(String signature) { - byte[] signatureBytes = Base64.decodeBase64(signature); - ByteBuffer byteBuffer = ByteBuffer.wrap(signatureBytes); - byteBuffer = (ByteBuffer) byteBuffer.position(13); - int start = byteBuffer.getInt(); - int version = byteBuffer.getInt(); - return new ObjectSerializer().readObject(SignedLicense.class, Arrays.copyOfRange(signatureBytes, start, signatureBytes.length)); - } -} diff --git a/src/test/java/org/elasticsearch/license/manager/LicenseVerificationTests.java b/src/test/java/org/elasticsearch/license/manager/LicenseVerificationTests.java index 6028e6832ce..bbb78c08c24 100644 --- a/src/test/java/org/elasticsearch/license/manager/LicenseVerificationTests.java +++ b/src/test/java/org/elasticsearch/license/manager/LicenseVerificationTests.java @@ -152,6 +152,7 @@ public class LicenseVerificationTests extends AbstractLicensingTestBase { } } + // TODO: move to TestUtils public static void verifyLicenses(FileBasedESLicenseProvider licenseProvider, Map