Skip to content

Commit

Permalink
Merge branch 'release/4.0.3'
Browse files Browse the repository at this point in the history
  • Loading branch information
christian-schlichtherle committed Feb 9, 2020
2 parents 7a7919c + d2cdb31 commit 36a0593
Show file tree
Hide file tree
Showing 25 changed files with 185 additions and 47 deletions.
20 changes: 11 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# TrueLicense

TrueLicense is an open source engine for license management on the Java Virtual Machine.
Due to its functional and modular design TrueLicense scales from simple to complex licensing schemas.
Due to its functional and modular design, it scales from simple to complex licensing schemas.
TrueLicense features various interfaces for managing free trial periods, subscriptions, multiple editions,
internationalization, privacy protection and more.

Expand All @@ -18,27 +18,29 @@ In the mean time, please still use it as your reference.
For a quick start, here's how you can generate a sample project using the [TrueLicense Maven Archetype](https://github.com/christian-schlichtherle/truelicense-maven-archetype) with the new V4 license key format:

```bash
$ mvn org.apache.maven.plugins:maven-archetype-plugin:3.1.0:generate \
mvn org.apache.maven.plugins:maven-archetype-plugin:generate \
-B \
-DarchetypeGroupId=global.namespace.truelicense-maven-archetype \
-DarchetypeArtifactId=truelicense-maven-archetype \
-DarchetypeVersion=4.0.0 \
-DarchetypeVersion=4.0.3 \
-DartifactId=basic \
-Dcompany='Company Inc.' \
-DgroupId=com.company.product \
-Dpassword=test1234 \
-Dsubject='Product 1' \
-Dsubject='StarGazer 2020' \
-Dversion=1.0-SNAPSHOT
$ cd basic
$ chmod +x mvnw
$ ./mvnw clean verify
cd basic
chmod +x mvnw
./mvnw clean verify
```

Next, you can generate and install a license key like this:

```bash
$ java -jar keygen/target/*-keygen-*-standalone.jar generate license.lic -output -
{"consumerAmount":1,"consumerType":"User","holder":"CN=Unknown","issued":1565085418292,"issuer":"CN=Company Inc.","subject":"Product 1"}
{"consumerAmount":1,"consumerType":"User","holder":"CN=Unknown","issued":1565085418292,"issuer":"CN=Company Inc.","subject":"StarGazer 2020"}
$ java -jar keymgr/target/*-keymgr-*-guarded.jar wizard
```

Follow the instructions of the licensing wizard to install, view and uninstall the license key previously saved to the `license.lic` file.
Follow the instructions of the licensing wizard to install, view and uninstall the license key previously saved to the
`license.lic` file.
2 changes: 1 addition & 1 deletion api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<parent>
<groupId>global.namespace.truelicense</groupId>
<artifactId>truelicense</artifactId>
<version>4.0.1</version>
<version>4.0.3</version>
</parent>

<artifactId>truelicense-api</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* <p>
* A (checked) consumer license manager generally throws a {@link LicenseManagementException} if an operation fails.
*
* <h3>How to Preview License Keys</h3>
* <h2>How to Preview License Keys</h2>
* <p>
* This interface intentionally lacks a method to preview the license bean which is encoded in a license key.
* You can work around this constraint by using a {@linkplain LicenseManagementContext license management context} to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
* An unchecked consumer license manager generally throws an {@link UncheckedLicenseManagementException} with a
* (checked) {@link LicenseManagementException} as its cause if an operation fails.
*
* <h3>How to Preview License Keys</h3>
* <h2>How to Preview License Keys</h2>
* <p>
* This interface intentionally lacks a method to preview the license bean which is encoded in a license key.
* You can work around this constraint by using a {@linkplain LicenseManagementContext license management context} to
Expand Down
2 changes: 1 addition & 1 deletion build-tasks/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<parent>
<groupId>global.namespace.truelicense</groupId>
<artifactId>truelicense</artifactId>
<version>4.0.1</version>
<version>4.0.3</version>
</parent>

<artifactId>truelicense-build-tasks</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<parent>
<groupId>global.namespace.truelicense</groupId>
<artifactId>truelicense</artifactId>
<version>4.0.1</version>
<version>4.0.3</version>
</parent>

<artifactId>truelicense-core</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
*/
public abstract class AbstractLicense extends LicenseStub {

private int consumerAmount;
private int consumerAmount = 1; // default value is required for compatibility with V1 license keys.
private String consumerType;
private Object extra;
private X500Principal holder;
Expand Down
63 changes: 63 additions & 0 deletions core/src/main/java/global/namespace/truelicense/core/Filters.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package global.namespace.truelicense.core;

import global.namespace.fun.io.api.Filter;
import global.namespace.fun.io.api.Socket;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import static global.namespace.fun.io.bios.BIOS.buffer;

final class Filters {

/**
* Returns a filter which, upon writing, first compresses the data and then encrypts it using the given filters.
* Upon reading, the filter first tries to do the inverse, that is to first decrypt the data and then decompress it.
* Failing that, the filter tries to swap the underlying filter, that is to first decompress the data and then
* decrypt it.
* This is a workaround for a bug in TrueLicense 4.0.0 and 4.0.1, where these filters were accidentally swapped,
* thus (a) generating license keys which were bigger than they would need to be (because compressing encrypted data
* only adds overhead and thus makes it a little bigger instead of smaller) and (b) breaking compatibility with
* license keys generated by previous versions of TrueLicense.
* Trying the underlying filters in both orders effectively restores compatibility with license keys generated by
* *all* previous versions, with only a very small performance penalty for license keys generated by TrueLicense
* 4.0.0 and 4.0.1.
* Last but not least, if the filters fail in both attempts, the exception of the first attempt (which first
* tried to decrypt the data and then decompress it) will be thrown, effectively discarding the exception of the
* second attempt.
*/
static Filter compressionAndEncryption(final Filter compression, final Filter encryption) {
assert compression != null;
assert encryption != null;
return new Filter() {

@Override
public Socket<OutputStream> output(Socket<OutputStream> output) {
return compression.output(encryption.output(output));
}

@SuppressWarnings({"deprecation", "ResultOfMethodCallIgnored"})
@Override
public Socket<InputStream> input(Socket<InputStream> input) {
return () -> {
try {
// Use a buffer filter and do a simple one byte read-ahead test:
return buffer().input(compression.input(encryption.input(input))).map(in -> {
in.mark(1);
in.read();
in.reset();
return in;
}).get();
} catch (final IOException e) {
try {
return encryption.input(compression.input(input)).get();
} catch (IOException ignored) {
throw e;
}
}
};
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -448,8 +448,8 @@ public Codec codec() {
return codec;
}

Filter compressionThenEncryption() {
return compression().andThen(encryption());
Filter compressionAndEncryption() {
return Filters.compressionAndEncryption(compression(), encryption());
}

@Override
Expand Down Expand Up @@ -495,6 +495,7 @@ ConsumerLicenseManager parent() {
return parent.get();
}

@SuppressWarnings("rawtypes")
@Override
public RepositoryFactory repositoryFactory() {
return repositoryFactory;
Expand Down Expand Up @@ -696,7 +697,7 @@ public License license() throws LicenseManagementException {
@Override
public LicenseKeyGenerator saveTo(final Sink sink) throws LicenseManagementException {
callChecked(() -> {
codec().encoder(sink.map(compressionThenEncryption())).encode(model());
codec().encoder(sink.map(compressionAndEncryption())).encode(model());
return null;
});
return this;
Expand Down Expand Up @@ -810,7 +811,7 @@ Object repositoryModel(Source source) throws Exception {
}

Source decryptedAndDecompressedSource(Source source) {
return source.map(compressionThenEncryption());
return source.map(compressionAndEncryption());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package global.namespace.truelicense.core

import java.security.SecureRandom

import global.namespace.fun.io.api.Filter
import global.namespace.fun.io.bios.BIOS._
import global.namespace.truelicense.core.Filters.compressionAndEncryption
import global.namespace.truelicense.core.FiltersSpec._
import javax.crypto.Cipher.{DECRYPT_MODE, ENCRYPT_MODE}
import javax.crypto.spec.{PBEKeySpec, PBEParameterSpec}
import javax.crypto.{Cipher, SecretKeyFactory}
import org.scalatest.Matchers._
import org.scalatest.WordSpec
import org.scalatest.prop.TableDrivenPropertyChecks._

class FiltersSpec extends WordSpec {

"compressAndEncrypt()" should {
"round trip compress and encrypt some data" in {
forAll(Tests) { (compression, encryption) =>
val filter = compressionAndEncryption(compression, encryption)
val store = memory map filter
store content Message.getBytes
new String(store.content) shouldBe Message
}
}

"transparently read some data which has been processed in the wrong order when writing (issue #7)" in {
forAll(Tests) { (compression, encryption) =>
val wrong = compressionAndEncryption(encryption, compression)
val right = compressionAndEncryption(compression, encryption)
val store = memory
store map wrong content Message.getBytes
new String((store map right).content) shouldBe Message
}
}
}
}

private object FiltersSpec {

private val Message = "Hello world!"

private[this] val Algorithm = "PBEWithMD5AndDES"

private[this] val Skf = SecretKeyFactory getInstance Algorithm

private[this] val PbeKeySpec = new PBEKeySpec("secret".toCharArray)

private[this] val PbeParameterSpec = {
val salt = new Array[Byte](8)
new SecureRandom nextBytes salt
new PBEParameterSpec(salt, 2017)
}

private[this] val PBE: Filter = {
cipher { outputMode: java.lang.Boolean =>
val secretKey = Skf generateSecret PbeKeySpec
val cipher = Cipher getInstance Algorithm
cipher.init(if (outputMode) ENCRYPT_MODE else DECRYPT_MODE, secretKey, PbeParameterSpec)
cipher
}
}

private val Tests = Table(
("compression", "encryption"),
(deflate, PBE),
(gzip, PBE)
)
}
2 changes: 1 addition & 1 deletion jax-rs/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<parent>
<groupId>global.namespace.truelicense</groupId>
<artifactId>truelicense</artifactId>
<version>4.0.1</version>
<version>4.0.3</version>
</parent>

<artifactId>truelicense-jax-rs</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion jsf/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<parent>
<groupId>global.namespace.truelicense</groupId>
<artifactId>truelicense</artifactId>
<version>4.0.1</version>
<version>4.0.3</version>
</parent>

<artifactId>truelicense-jsf</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion maven-plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<parent>
<groupId>global.namespace.truelicense</groupId>
<artifactId>truelicense</artifactId>
<version>4.0.1</version>
<version>4.0.3</version>
</parent>

<artifactId>truelicense-maven-plugin</artifactId>
Expand Down
4 changes: 2 additions & 2 deletions obfuscate/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<parent>
<groupId>global.namespace.truelicense</groupId>
<artifactId>truelicense</artifactId>
<version>4.0.1</version>
<version>4.0.3</version>
</parent>

<artifactId>truelicense-obfuscate</artifactId>
Expand All @@ -32,7 +32,7 @@
<groupId>global.namespace.truelicense</groupId>
<artifactId>truelicense-maven-plugin</artifactId>
<!-- Cannot use ${project.version} while bootstrapping! -->
<version>4.0.0</version>
<version>4.0.1</version>
<executions>
<execution>
<id>generate-main-sources</id>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import java.util.concurrent.Callable;
/**
* A thread-safe representation of an obfuscated string.
*
* <h3>Motivation</h3>
* <h2>Motivation</h2>
* <p>
* Software developers may wish to apply some licensing scheme to protect their
* Intellectual Property (IP).
Expand All @@ -30,7 +30,7 @@ import java.util.concurrent.Callable;
* Java byte code in order to identify the obfuscated licensing code or replace
* its key strings with self-generated key strings.
*
* <h3>Usage</h3>
* <h2>Usage</h2>
* <p>
* This class specifically addresses the need to obfuscate constant string
* expressions in Java byte code.
Expand All @@ -51,7 +51,7 @@ import java.util.concurrent.Callable;
* However, it needs to call {@link ${hash}array} instead of {@link ${hash}obfuscate} at
* build time.
*
* <h3>Security Considerations</h3>
* <h2>Security Considerations</h2>
* <p>
* Note that obfuscation is <em>not</em> equal to encryption:
* In contrast to the application of the simple and cheap obfuscation scheme in
Expand Down
Loading

0 comments on commit 36a0593

Please sign in to comment.