REFACTOR: convert internal license feature to json blob

Original commit: elastic/x-pack-elasticsearch@21d99b2219
This commit is contained in:
Areek Zillur 2014-10-27 17:57:52 -04:00
parent 7f11de275f
commit a57164ec67
5 changed files with 122 additions and 77 deletions

View File

@ -15,6 +15,7 @@ import java.util.TimeZone;
public class DateUtils { public class DateUtils {
public static final TimeZone TIME_ZONE = TimeZone.getTimeZone("UTC"); public static final TimeZone TIME_ZONE = TimeZone.getTimeZone("UTC");
//MutableDateTime.
private static DateFormat getDateFormat() { private static DateFormat getDateFormat() {
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
dateFormat.setTimeZone(TIME_ZONE); dateFormat.setTimeZone(TIME_ZONE);

View File

@ -14,6 +14,9 @@ import net.nicholaswilliams.java.licensing.licensor.LicenseCreator;
import net.nicholaswilliams.java.licensing.licensor.LicenseCreatorProperties; import net.nicholaswilliams.java.licensing.licensor.LicenseCreatorProperties;
import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.Base64;
import org.elasticsearch.common.collect.ImmutableSet; 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.ESLicense;
import org.elasticsearch.license.core.LicensesCharset; import org.elasticsearch.license.core.LicensesCharset;
@ -90,11 +93,11 @@ public class ESLicenseSigner {
.withIssueDate(licenseSpec.issueDate()) .withIssueDate(licenseSpec.issueDate())
.withProductKey(licenseSpec.uid()) .withProductKey(licenseSpec.uid())
.withHolder(licenseSpec.issuedTo()) .withHolder(licenseSpec.issuedTo())
.withIssuer(licenseSpec.issuer()) .withIssuer(licenseSpec.issuer());
.addFeature("feature:" + licenseSpec.feature(), licenseSpec.expiryDate())
.addFeature("maxNodes:" + String.valueOf(licenseSpec.maxNodes())) XContentBuilder contentBuilder = XContentFactory.contentBuilder(XContentType.JSON);
.addFeature("type:" + licenseSpec.type().string()) featureToXContent(licenseSpec, contentBuilder);
.addFeature("subscription_type:" + licenseSpec.subscriptionType().string()); licenseBuilder.addFeature(contentBuilder.string());
final License license = licenseBuilder.build(); final License license = licenseBuilder.build();
@ -121,4 +124,13 @@ public class ESLicenseSigner {
.verifyAndBuild(); .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();
}
} }

View File

@ -10,19 +10,20 @@ import net.nicholaswilliams.java.licensing.encryption.Hasher;
import net.nicholaswilliams.java.licensing.encryption.PasswordProvider; import net.nicholaswilliams.java.licensing.encryption.PasswordProvider;
import net.nicholaswilliams.java.licensing.exception.ExpiredLicenseException; import net.nicholaswilliams.java.licensing.exception.ExpiredLicenseException;
import net.nicholaswilliams.java.licensing.exception.InvalidLicenseException; import net.nicholaswilliams.java.licensing.exception.InvalidLicenseException;
import org.apache.commons.codec.binary.Base64;
import org.elasticsearch.common.collect.ImmutableSet; import org.elasticsearch.common.collect.ImmutableSet;
import org.elasticsearch.common.inject.Inject; 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.ESLicense;
import org.elasticsearch.license.core.ResourcePublicKeyDataProvider; import org.elasticsearch.license.core.ResourcePublicKeyDataProvider;
import java.util.Collection; import java.io.IOException;
import java.util.HashSet; import java.nio.ByteBuffer;
import java.util.Map; import java.util.*;
import java.util.Set;
import static org.elasticsearch.license.core.ESLicense.SubscriptionType; import static org.elasticsearch.license.core.ESLicense.SubscriptionType;
import static org.elasticsearch.license.core.ESLicense.Type; 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 * 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 final LicenseManager licenseManager;
private static class Prefix { private static class FeatureFields {
static final String MAX_NODES = "maxNodes:"; static final String MAX_NODES = "max_nodes";
static final String TYPE = "type:"; static final String TYPE = "type";
static final String SUBSCRIPTION_TYPE = "subscription_type:"; static final String SUBSCRIPTION_TYPE = "subscription_type";
static final String FEATURE = "feature:"; static final String FEATURE = "feature";
} }
// Initialize LicenseManager // Initialize LicenseManager
@ -89,27 +90,32 @@ public class ESLicenseManager {
} }
} catch (ExpiredLicenseException e) { } catch (ExpiredLicenseException e) {
throw new InvalidLicenseException("Expired License"); throw new InvalidLicenseException("Expired License");
} catch (InvalidLicenseException e) { } catch (InvalidLicenseException | IOException e) {
throw new InvalidLicenseException("Invalid License"); throw new InvalidLicenseException("Invalid License");
} }
} }
private ESLicense fromSignature(String signature) { private ESLicense fromSignature(String signature) {
final SignedLicense signedLicense = Utils.extractSignedLicence(signature); final SignedLicense signedLicense = extractSignedLicence(signature);
License license = licenseManager.decryptAndVerifyLicense(signedLicense); License license = licenseManager.decryptAndVerifyLicense(signedLicense);
ESLicense.Builder builder = ESLicense.builder(); ESLicense.Builder builder = ESLicense.builder();
for (License.Feature feature : license.getFeatures()) { for (License.Feature feature : license.getFeatures()) {
String featureName = feature.getName(); String featureName = feature.getName();
if (featureName.startsWith(Prefix.MAX_NODES)) { LicenseFeatures licenseFeatures;
builder.maxNodes(Integer.parseInt(featureName.substring(Prefix.MAX_NODES.length()))); try {
} else if (featureName.startsWith(Prefix.TYPE)) { licenseFeatures = licenseFeaturesFromSource(featureName);
builder.type(Type.fromString(featureName.substring(Prefix.TYPE.length()))); if (licenseFeatures.maxNodes != -1
} else if (featureName.startsWith(Prefix.SUBSCRIPTION_TYPE)) { && licenseFeatures.feature != null
builder.subscriptionType(SubscriptionType.fromString(featureName.substring(Prefix.SUBSCRIPTION_TYPE.length()))); && licenseFeatures.type != null
} else if (featureName.startsWith(Prefix.FEATURE)) { && licenseFeatures.subscriptionType != null) {
builder.feature(featureName.substring(Prefix.FEATURE.length())); builder.maxNodes(licenseFeatures.maxNodes)
.feature(licenseFeatures.feature)
.type(Type.fromString(licenseFeatures.type))
.subscriptionType(SubscriptionType.fromString(licenseFeatures.subscriptionType));
break;
} }
} catch (IOException ignored) {}
} }
return builder return builder
@ -122,12 +128,11 @@ public class ESLicenseManager {
.build(); .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()) boolean licenseValid = license.getProductKey().equals(eslicense.uid())
&& license.getHolder().equals(eslicense.issuedTo()) && license.getHolder().equals(eslicense.issuedTo())
&& license.getIssueDate() == eslicense.issueDate() && license.getIssueDate() == eslicense.issueDate()
&& license.getGoodBeforeDate() == eslicense.expiryDate(); && license.getGoodBeforeDate() == eslicense.expiryDate();
assert license.getFeatures().size() == 4 : "one license should have only four features";
boolean maxNodesValid = false; boolean maxNodesValid = false;
boolean featureValid = false; boolean featureValid = false;
boolean typeValid = false; boolean typeValid = false;
@ -135,16 +140,14 @@ public class ESLicenseManager {
for (License.Feature feature : license.getFeatures()) { for (License.Feature feature : license.getFeatures()) {
String featureName = feature.getName(); String featureName = feature.getName();
if (featureName.startsWith(Prefix.MAX_NODES)) { LicenseFeatures licenseFeatures = licenseFeaturesFromSource(featureName);
maxNodesValid = eslicense.maxNodes() == Integer.parseInt(featureName.substring(Prefix.MAX_NODES.length())); maxNodesValid = eslicense.maxNodes() == licenseFeatures.maxNodes;
} else if (featureName.startsWith(Prefix.TYPE)) { typeValid = eslicense.type().string().equals(licenseFeatures.type);
typeValid = eslicense.type() == Type.fromString(featureName.substring(Prefix.TYPE.length())); subscriptionTypeValid = eslicense.subscriptionType().string().equals(licenseFeatures.subscriptionType);
} else if (featureName.startsWith(Prefix.SUBSCRIPTION_TYPE)) { featureValid = eslicense.feature().equals(licenseFeatures.feature);
subscriptionTypeValid = eslicense.subscriptionType() == SubscriptionType.fromString(featureName.substring(Prefix.SUBSCRIPTION_TYPE.length()));
} else if (featureName.startsWith(Prefix.FEATURE)) { if (maxNodesValid && typeValid && subscriptionTypeValid && featureValid) {
String featureValue = featureName.substring(Prefix.FEATURE.length()); break;
featureValid = featureValue.equals(eslicense.feature())
&& feature.getGoodBeforeDate() == eslicense.expiryDate();
} }
} }
if (!licenseValid || !featureValid || !maxNodesValid || !typeValid || !subscriptionTypeValid) { 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
* <p/>
* 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 // TODO: Need a better password management
private static class ESPublicKeyPasswordProvider implements PasswordProvider { private static class ESPublicKeyPasswordProvider implements PasswordProvider {
private final String DEFAULT_PASS_PHRASE = "elasticsearch-license"; private final String DEFAULT_PASS_PHRASE = "elasticsearch-license";

View File

@ -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
* <p/>
* 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));
}
}

View File

@ -152,6 +152,7 @@ public class LicenseVerificationTests extends AbstractLicensingTestBase {
} }
} }
// TODO: move to TestUtils
public static void verifyLicenses(FileBasedESLicenseProvider licenseProvider, Map<String, TestUtils.FeatureAttributes> featureAttributeMap) throws ParseException { public static void verifyLicenses(FileBasedESLicenseProvider licenseProvider, Map<String, TestUtils.FeatureAttributes> featureAttributeMap) throws ParseException {
for (Map.Entry<String, TestUtils.FeatureAttributes> entry : featureAttributeMap.entrySet()) { for (Map.Entry<String, TestUtils.FeatureAttributes> entry : featureAttributeMap.entrySet()) {