Giter VIP home page Giter VIP logo

license3j's Introduction

License3j Free License management for Java

alt text

License3j is a free and open source Java library to manage license files in Java programs that need technical license management enforcement support. A license file is a special configuration file, which is electronically signed. The library can create, sign such license files and can also check the signature and parameters of the license file when embedded into the licensed application.

Introduction

License3j is a Java library that can be used to create and assert license files. This way, Java programs can enforce the users to compensate for their use of the software in the form of payment. This is the usual way when closed source programs are distributed.

License management alone does not guarantee that the program will not be stolen, pirated or used in any illegal way. However, license management may increase the difficulty to use the program illegally and therefore may drive users to become customers. There is another effect of license management which is a legal aspect. If there is sufficient license management, illegal users have less probability to successfully claim their use was based on the lack of, or on false knowledge of license conditions.

License3j is an open source license manager that you can use free of charge for non-profit purposes…

as well as for profit purposes as well under the license terms covered by Apache 2.0 license as defined on the web page http://www.apache.org/licenses/LICENSE-2.0

In short

Add the dependency to your project:

<dependency>
    <groupId>com.javax0.license3j</groupId>
    <artifactId>license3j</artifactId>
    <version>3.2.0</version>
</dependency>

The following code fragment shows the structure you have to program to check the license:

// load the license using a license reader
try (var reader = new LicenseReader('license.bin')) {
    License license = reader.read();
} catch (IOException e) {
    error("Error reading license file " + e);
}

// encode the public key into your application
// (you can copy paste this from License3jRepl after key generation, see later)
byte [] key = new byte[] {
    (byte)0x30,
    (byte)0x81, (byte)0x9F, (byte)0x30, (byte)0x0D, (byte)0x06, (byte)0x09, (byte)0x2A, (byte)0x86,
    (byte)0x48, (byte)0x86, (byte)0xF7, (byte)0x0D, (byte)0x01, (byte)0x01, (byte)0x01, (byte)0x05,
    (byte)0x00, (byte)0x03, (byte)0x81, (byte)0x8D, (byte)0x00, (byte)0x30, (byte)0x81, (byte)0x89,

    ... some lines are deleted as actual values are irrelevant ...

    (byte)0xE3, (byte)0xBB, (byte)0xE3, (byte)0xB1, (byte)0x67, (byte)0xAC, (byte)0x2A, (byte)0x9D,
    (byte)0x9D, (byte)0x67, (byte)0xB0, (byte)0x9D, (byte)0x3A, (byte)0xDE, (byte)0x48, (byte)0xA5,
    (byte)0x2A, (byte)0xE8, (byte)0xBB, (byte)0xC6, (byte)0xE2, (byte)0x39, (byte)0x0D, (byte)0x41,
    (byte)0xDF, (byte)0x76, (byte)0xD0, (byte)0xA7, (byte)0x02, (byte)0x03, (byte)0x01, (byte)0x00,
    (byte)0x01,
    };
// check that the license is signed properly
if( !license.isOK(key) ){
    // if not signed, stop the application
    return;
}
// get a feature and from the feature type specific data, like date, int, long, String...
Date birthday = license.get("bd").getDate();

Create keys, license and sign license using a text editor, and/or the REPL application (see below).

What is a license in License3j

A license in License3j is a collection of features. Each feature has

  • a name,

  • a type and

  • a value.

The name can be any string you like, but there are some predefined names that have special meaning for the license management library. A feature’s type can be

  • BINARY can contain an arbitrary binary value that is retrieved by the Java code as a byte[] array

  • STRING can contain any string, will be retrieved as java.lang.String

  • BYTE contains a single byte value.

  • SHORT contains a single short value

  • INT contains an integer (int) value

  • LONG contains a long value

  • FLOAT contains a float value

  • DOUBLE contains a double value

  • BIGINTEGER contains a big integer value

  • BIGDECIMAL contains a big decimal value

  • DATE contains a date value

  • UUID contains a UUID value

The value of the different features can be retrieved as the corresponding Java object or a primitive value. There is no automatic conversion between the different types of the features.

When the license is saved to a file, it can be saved binary, base64 or text.

  • BINARY, the format is suitable to store in a file. This is also the shortest, most compact format of the license. It may not be suitable to be sent over the internet inside and eMail and is not directly editable.

  • BASE64, the format is the same as the binary format, but it is encoded using the base64 encoding

  • TEXT, the format is a human-readable format, suitable for editing in a text editor, looking at the actual content of the license without any special tool. The text format is always encoded UTF-8 a character set.

All three formats are suitable to store the license information, and when a program is protected using License3j it can be programmed to read only one, two and all three formats. The license object created in the JVM memory as a result of reading the license file is the same independent of the source format.

When a program is protected using License3j the application has a small code fragment that checks the existence of a license file, the validity of the license, and it can also use the parameters encoded in the license as license features.

Load a license from a file

To read a license from a file you need a javax0.license3j.io.LicenseReader object

try (var reader = new LicenseReader('license.bin')) {
    License license = reader.read();
} catch (IOException e) {
    error("Error reading license file " + e);
}

This will read the license from the file license.bin assuming the license is there binary formatted. In case the license file is not readable or has a different format either IOException or IllegalArgumentException will be thrown. If the license is not binary, the code should use the read method with the format parameter either reader.read(IOFormat.STRING) or reader.read(IOFormat.BASE64).

Check Signature on the license

The license is read from the file even if it is not signed. A license can be signed, unsigned, or it may have a compromised signature. Reading the license does not check either the existence of the signature nor the validity of that. To check the existence, and the validity of the signature, the application needs the public key. Licenses are signed using public key cryptography, where a private key is used to sign the license. The corresponding public key is used to check the authenticity of the signature. The public key can be read from a file, or it can be hard-coded in the application. The latter is recommended.

To embed the public key into the application, you have to have a public key in the first place. To create a key pair, you should start the interactive application available from a separate project at https://github.com/verhas/license3jrepl

$ java -jar license3jrepl.jar

This will start with an interactive prompt where you can enter commands. The prompt you will see is L3j> $.

To generate a key pair, you have to enter the command:

generateKeys algorithm=RSA size=1024 format=BINARY public=public.key private=private.key

This will generate the public and the private keys and save them into the files public.key and private.key. The keys also remain loaded into the REPL application. To embed this key into the application, you can execute the command dumpPublicKey that will dump the Java code to the screen, something like:

--KEY DIGEST START
byte [] digest = new byte[] {
(byte)0xA1,
(byte)0x04, (byte)0x1D, (byte)0x2C, (byte)0xF1, (byte)0x56, (byte)0xFB, (byte)0x06, (byte)0x43,

... some lines are deleted as actual values are irrelevant ...

(byte)0x98, (byte)0xB6, (byte)0xD9, (byte)0x60, (byte)0x51, (byte)0x9E, (byte)0xA2,
};
---KEY DIGEST END
--KEY START
byte [] key = new byte[] {
(byte)0x30,
(byte)0x81, (byte)0x9F, (byte)0x30, (byte)0x0D, (byte)0x06, (byte)0x09, (byte)0x2A, (byte)0x86,
(byte)0x48, (byte)0x86, (byte)0xF7, (byte)0x0D, (byte)0x01, (byte)0x01, (byte)0x01, (byte)0x05,
(byte)0x00, (byte)0x03, (byte)0x81, (byte)0x8D, (byte)0x00, (byte)0x30, (byte)0x81, (byte)0x89,

... some lines are deleted as actual values are irrelevant ...

(byte)0xE3, (byte)0xBB, (byte)0xE3, (byte)0xB1, (byte)0x67, (byte)0xAC, (byte)0x2A, (byte)0x9D,
(byte)0x9D, (byte)0x67, (byte)0xB0, (byte)0x9D, (byte)0x3A, (byte)0xDE, (byte)0x48, (byte)0xA5,
(byte)0x2A, (byte)0xE8, (byte)0xBB, (byte)0xC6, (byte)0xE2, (byte)0x39, (byte)0x0D, (byte)0x41,
(byte)0xDF, (byte)0x76, (byte)0xD0, (byte)0xA7, (byte)0x02, (byte)0x03, (byte)0x01, (byte)0x00,
(byte)0x01,
};
---KEY END

The digest is the SHA-512 digest of the public key. If you want to arrange your code so that it loads the public key from a file or from some external resource you can check the key against the stored digest. This ensures that the key is really the one to use to check the signature. The recommended way, however, is to copy and paste into your application the second array, which is the actual public key.

Having a loaded license and the public key it is fairly straightforward to check the validity of the license. All you have to invoke is

license.isOK(key)

This call will return true if the license is signed, and the license signature can be verified using the key argument. If this call returns false, the license should not be used as a reliable source for usage rights configuration.

When the license is verified, the features can be retrieved using the names of the features. The call to license.get(name) will return the feature object of the name name. To get the actual value of the feature you can call feature.getXxx() where Xxx is the feature type. You can also check the feature’s type calling one of the feature.isXxx() but, honestly, your code has to know it. You create the license, and you check the license is intact using digital signature before calling any of the getXxx() methods, thus it is not likely you try to fetch the wrong type unless you have a bug in your code.

License formats

License Binary and Base64

Binary and base64 formats are essentially the same. The Base64 format is encoded using the base64 encoding to ensure that only printable characters are in the license. Neither of the forms is directly readable by a human. You can, however, read and convert any of the formats using the REPL application (mentioned above).

Magic bytes

The binary representation of the license starts with the bytes 0xCE, 0x21, 0x5E, 0x4E. This is the serialized format of the Java Integer value 0x21CE4E5E that stands for 21LI (leet code), CE itself, 4EN (ASCII), 5ESE (leet code). It reads together as LICENSE. It is a bit lame but gives a bit of joy to the game and prevents accidental loading of non-license files. Since the sizes and the types are stored on four bytes as Integers, very large files could be loaded accidentally.

If the loading of too large files is a concern, there are size limiting constructors for the class LicenseReader. Using the constructor reading of large files will be aborted before it would eat up Java memory.

Feature length 4bytes

The magic bytes are followed by the features in binary format. The length of the feature encoded on 4 bytes precedes the feature.

Feature type 4bytes

The feature starts with the type of the feature also in 4 bytes. Since there are a limited number of types, there is plenty of room for introducing new types.

Name length 4bytes

This is followed by the length of the name also in 4 bytes.

Value length 4bytes (optional)

Some types have fixed length. If the type has fixed length, the value directly follows and the four bytes of the length. If the value for the given type can be variable length, then the value length follows on 4 bytes.

Currently only BINARY, STRING, BIGINTEGER and BIGDECIMAL types have variable length.

Name and value

The next section is the feature’s name and value. Since the length of the name and the value is known, the name and the value can be read directly.

License Text

The textual format of the license encoded using the UTF-8 character set. Each line in the file is a feature, or a feature continuation. Continuation lines are used to represent features on multiple lines.

A line describing a feature starts with the name of the feature. This is followed by the type of the feature separated by a : from the name. The type is written in all capital letters as listed above BINARY, STRING, BYTE etc. The type is followed by a = and then comes the value of the feature. The type, along with the separating : can be missing in case it is STRING. (Note that there was a bug prior the version 3.1.5 that did not allow the use of string values that contained : characters, unless the explicit :STRING followed the name of the string feature.)

When a DATE feature is converted to and from a text then the actual value should be interpreted as time zone independent value. (Note that there was a bug in 3.X.X releases prior the version 3.1.1 that used the local time zone to interpret text representation of the date/time values.)

The values are encoded as text in a human-readable and editable way. When a value cannot fit on a single line, for example, a multi-line string, then the feature value starts with the characters << and it is followed by a string till the end of the line which does not appear in the value. The following lines contain the value of the feature until a line contains the string, which was after the << characters on the start line. This is similar to the "here string" syntax of UNIX shell.

License3j REPL application

The repl application is NOT part of the license3j.jar file. It is available as a separate JAR from https://github.com/verhas/license3jrepl. To start the repl (Read Evaluate Print Loop) using the Java command:

$ java -jar license3jrepl.jar

You do not need any other library or class on the classpath.

The application is interactive, and it reads the commands from the console and writes the output to the standard output. If the console is not available, then it uses the standard input. The prompt it displays is:

License3j REPL
CDW is /Users/verhasp/Dropbox/github/License3j/.
help for help
L3j> $

The simplest command you can type in is help:

L3j> $ help
License is not loaded.
Keys are not loaded
[INFO] Use ! to execute shell commands
[INFO] !cd has no effect, current working directory cannot be changed
[INFO] exit to exit
[INFO] other commands:
[INFO]     help
[INFO]     feature name:TYPE=value
[INFO]     licenseLoad [format=TEXT*|BINARY|BASE64] fileName
[INFO]     saveLicense [format=TEXT*|BINARY|BASE64] fileName
[INFO]     loadPrivateKey [format=BINARY|BASE64] keyFile=xxx
[INFO]     loadPublicKey [format=BINARY|BASE64] keyFile=xxx
[INFO]     sign [digest=SHA-512]
[INFO]     verify >>no argument<<
[INFO]     generateKeys [algorithm=RSA] [size=2048] [format=BINARY|BASE64] public=xxx private=xxx
[INFO]     newLicense >>no argument<<
[INFO]     dump >>no argument<<
[INFO]     digestPublicKey >>no argument<<
[INFO] For more information read the documentation

Note that the actual output of the command help may be different for different versions of the program and from what you actually can see in this documentation.

You can exit the application using the command exit. You can execute external commands using the ! mark. Any string you type on a line that starts with the ! character will be passed to the underlying operating system, and it will be executed. You can, for example , type !ls on Linux to see what files are there in the current working directory, or you can type !dir to do the same under Windows. You cannot change the current working directory this way. You can issue the command cd other_dir and it actually will change the current working directory but only for the new shell, which is executing the command and not for the process that executes the Repl application. It means that as soon as the command has finished and returns to the repl application the current working directory is restored to the original value.

When you execute the Repl you can create a new license, new key pair, you can save them to files, or you can load them from files. The commands work with the keys using the license that is currently in the memory. The information is also printed on the screen about the license and the key. When you start the Repl there is no license or keys loaded.

You can type the commands interactively, or you can type a file name as a command following a . (dot) character. The Repl application will read the file line by line and execute the lines as they were typed into the interactive prompt.

If there is a file .license3j in the current working directory when the Repl is started then it will be read and executed automatically. This can be used to load the default public and private keys that you usually work with.

The commands can be abbreviated. You need to write only so many characters so that the command can uniquely be identified. The same is true for the command parameters that have names. Thus you can type si instead of sign to sign a license.

Older versions of License3j included the Repl application. The current and later versions of License3j will not include the Repl application. The Repl application is moved to a separate application. It uses the javax0.repl library as a framework. This solution provides a leaner license3j library that you include into your application. Your application will not contain the code of the License3j Repl application and of the libraries that it uses. Moving the repl framework to a separate library makes it more viable and can be used by other Java applications as well.

No matter which version you use following 3.0.0 there will be a Repl application available to manage the licenses and the keys.

Download and Installation

The License3j module can be downloaded from the Sonatype central repository. To search the central repo follow the URL http://central.sonatype.org/

If you use maven you can insert the lines

<dependency>
    <groupId>com.javax0.license3j</groupId>
    <artifactId>license3j</artifactId>
    <version>3.1.5</version>
</dependency>

in to your pom.xml file. Check the central repository for the latest version.

Note on release history

License3j versions 1.x.x and 2.0.0 were released for Java 1.5 … 1.8. The release 3.0.0 is a total rewrite of the library. Neither the API nor the binary formats are compatible with previous versions. It is also released only for Java 11 and later and there is no planned backport release for Java 8 or earlier. Note however that the generated byte code is compatible with Java 8 JVM, so you can use the library as a dependency for Java 8 projects. This may change in later releases.

License3j prior to version 3.0.0 has a dependency on the Bouncy Castle encryption library. The version 3.0.0 and later breaks this dependency and this version is standalone. Also, this version can be used to generate the keys, sign licenses and does not need the external gpg tool. (Also note that you cannot use the gpg tool to generate keys for this version as the format of the keys are not compatible with older versions.)

Name of the game

There are many names that contain '2'. In these cases '2' stands for 'to' instead of 'two'. There are names containing '4' that stands for 'for'. For example license4j.

'3' in license3j stands for 'free' instead of 'three'. Because this is a free program.

license3j's People

Contributors

cwash avatar dkcm avatar gschueler avatar klenkes74 avatar robert7k avatar verhas avatar verhasi avatar zej0hn avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

license3j's Issues

Status of the 1.0.8 version

What the status of the 1.0.8 version? Is it still under development? Should I use 1.0.7 version?
Just inserted a few lines from the readme.

        <dependency>
            <groupId>com.verhas</groupId>
            <artifactId>license3j</artifactId>
            <version>1.0.8</version>
        </dependency>

And I got:

Could not find artifact com.verhas:license3j:jar:1.0.8 in central (https://repo.maven.apache.org/maven2)

java.lang.ClassNotFoundException: javax0.license3j.io.LicenseReader

Hi I am using Java 8 and license3j-3.1.5.jar using eclipse get this error :

java.lang.reflect.InvocationTargetException at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:389) at com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:328) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:873) Caused by: java.lang.RuntimeException: Exception in Application start method at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:917) at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$1(LauncherImpl.java:182) at java.lang.Thread.run(Thread.java:748) Caused by: java.lang.NoClassDefFoundError: javax0/license3j/io/LicenseReader at chart.AlnairMainClient.checkLicense(AlnairMainClient.java:615) at chart.AlnairMainClient.start(AlnairMainClient.java:270) at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$8(LauncherImpl.java:863) at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$7(PlatformImpl.java:326) at com.sun.javafx.application.PlatformImpl.lambda$null$5(PlatformImpl.java:295) at java.security.AccessController.doPrivileged(Native Method) at com.sun.javafx.application.PlatformImpl.lambda$runLater$6(PlatformImpl.java:294) at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95) at com.sun.glass.ui.win.WinApplication._runLoop(Native Method) at com.sun.glass.ui.win.WinApplication.lambda$null$3(WinApplication.java:177) ... 1 more Caused by: java.lang.ClassNotFoundException: javax0.license3j.io.LicenseReader at java.net.URLClassLoader.findClass(URLClassLoader.java:382) at java.lang.ClassLoader.loadClass(ClassLoader.java:419) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:352) at java.lang.ClassLoader.loadClass(ClassLoader.java:352)

missing class

In your home you describe the creation of a key pair through:

$ java -cp license3j-3.0.0.jar javax0.license3j.Repl

but in the jar i've downloaded, it does not exist the class.
It si somtheing missing or the docs are outdated?

Best regards

Damiano

License3j package

Hi, thanks for sharing your great project!
I started following your tutorial to generate a license. When you mention the file license3j.bat, you said

This time you have to start the command line processor included in the license3j JAR file.

But the JAR file in maven repository does not include that file, which I think is right. Maybe, you need to update the tutorial and link to this github project, since it is the only place where I found license3j.bat.

On the other hand, and here is the real issue, I included license3j-1.0.7.jar to my project in order to use License3j.main(String[]). However, this class is in the default package, thus it is not possible to import it from a named package. You can read about this here.

I managed to call License3j.main(String[]) using reflection, but I think that class should be inside com.verhas or com.verhas.utils.

can't verify licence after add dumpPublicKey[] to license

RSA decryption error - IllegalBlockSizeException: Data must not be longer than 128 or 256 bytes depend how to generate private and public key
It's correct to be verified one license if is authentic with generated public key
how can I solve this?

Can't decode the license file

Dear all,
It's my first time with License3j. I 'm following the tutorial "http://verhas.github.io/License3j/tuto.html" but I have a problem in the last step.
If i write
license3j.bat decode --license-file=license.out --keyring-file="C:\Users\Utente\AppData\Roaming\gnupg\pubring.gpg"
the following error is reported
log4j:WARN No appenders could be found for logger (com.verhas.licensor.License).

log4j:WARN Please initialize the log4j system properly.
Usage: java -cp license3j.jar License3j decode options
mandatory options are:
--license-file, --keyring-file, [ --output ] [--charset]
Exception in thread "main" java.lang.NullPointerException
at java.io.ByteArrayInputStream.(ByteArrayInputStream.java:106)
at com.verhas.licensor.License.setLicenseEncoded(License.java:543)
at com.verhas.licensor.License.setLicenseEncoded(License.java:508)
at com.verhas.licensor.License.setLicenseEncodedFromFile(License.java:48
9)
at License3j.decode(License3j.java:102)
at License3j.main(License3j.java:173)
"cmd" non è riconosciuto come comando interno o esterno,
un programma eseguibile o un file batch.

Can you help me?

Date serialization is TZ-dependent

Needs to use an Instant type and an ISO format. You're clearly living and testing in UTC+1 or UTC-1, because all your test cases are an hour out. I have a test cluster across multiple timezones.

Changing network interfaces names cause license to expire

Hello,
I would like to report following issue.
Tests conducted by my team with new license3j version have failed due to using “getName()” method which returns mnemonic plus index number as name (“net14”, “net16” etc.). When in our network we turn on VPN connection, new interfaces appear and some old disappear from our list. While they are filtered out thanks to your current changes, they introduce change on accepted interfaces index part (interface that was “net12” becomes “net14”), causing our license to expire.
Thank you in advance for your help.
Best regards,
Jerzy Bogusławski

License verification on Ubuntu vs Windows

I am having a weird issue where license verification. For me same license and same public key works in Windows but it does not work on Ubuntu

Windows

This works as expected but when I try same exact files on Ubuntu machine:

Ubuntu

What could cause this issue?

German Vowels not handled correctly ?

Hi,
i just encoded a license file with a German vowel (ä) as a feature value.
When loading the license file with

License lic = new License();
try {
  lic.loadKeyRingFromResource("license3j/pubring.gpg", digest);
  lic.setLicenseEncodedFromFile(licenseFileName);
} catch(Exception e) { }

and ask for that value i get back "ä".
The plain license file is UTF-8.
Am i missing here something or could this be a bug of your tool ?

Error when creating new license file

I'm trying to encode a new license file, but i got the following error :

Exception in thread "main" java.lang.IllegalArgumentException: Can't find signing key in key ring.
at com.verhas.licensor.License.loadKey(License.java:392)
at com.verhas.licensor.License.loadKey(License.java:348)
at com.verhas.licensor.License.loadKey(License.java:214)
at License3j.encode(License3j.java:84)
at License3j.main(License3j.java:170)

.bouncycastle errors - could you share the jar file you used ?

Exception in thread "main" java.lang.NoClassDefFoundError: org/bouncycastle/asn1/DEREncodable
at org.bouncycastle.openpgp.PGPKeyRing.readSignaturesAndTrust(Unknown Source)
at org.bouncycastle.openpgp.PGPKeyRing.readUserIDs(Unknown Source)
at org.bouncycastle.openpgp.PGPSecretKeyRing.(Unknown Source)
at org.bouncycastle.openpgp.PGPObjectFactory.nextObject(Unknown Source)
at org.bouncycastle.openpgp.PGPSecretKeyRingCollection.(Unknown Source)
at com.verhas.licensor.License.loadKey(License.java:440)
at com.verhas.licensor.License.loadKey(License.java:404)
at com.verhas.licensor.License.loadKey(License.java:250)
at License3j.encode(License3j.java:34)
at License3j.main(License3j.java:152)
Caused by: java.lang.ClassNotFoundException: org.bouncycastle.asn1.DEREncodable
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)


Exception in thread "main" java.lang.SecurityException: class "org.bouncycastle.asn1.DEREncodable"'s signer information does not match signer information of other classes in theame package
at java.lang.ClassLoader.checkCerts(ClassLoader.java:895)
at java.lang.ClassLoader.preDefineClass(ClassLoader.java:665)
at java.lang.ClassLoader.defineClass(ClassLoader.java:758)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at org.bouncycastle.openpgp.PGPKeyRing.readSignaturesAndTrust(Unknown Source)
at org.bouncycastle.openpgp.PGPKeyRing.readUserIDs(Unknown Source)
at org.bouncycastle.openpgp.PGPSecretKeyRing.(Unknown Source)
at org.bouncycastle.openpgp.PGPObjectFactory.nextObject(Unknown Source)
at org.bouncycastle.openpgp.PGPSecretKeyRingCollection.(Unknown Source)
at com.verhas.licensor.License.loadKey(License.java:440)
at com.verhas.licensor.License.loadKey(License.java:404)
at com.verhas.licensor.License.loadKey(License.java:250)
at License3j.encode(License3j.java:34)
at License3j.main(License3j.java:152)
:lib/bcpg-jdk15+-1.46.jar:lib/bcpg-jdk15on-1.46.jar:lib/bcpg-jdk16-1.46.jar:lib/bcprov-jdk15on-1.50.jar:lib/bcprov-jdk15on-152.jar:lib/bcprov-jdk16-140.jar:lib/bcprov-jdk16-1.44ar:lib/bcprov-jdk16-1.44-sources.jar:lib/license3j-1.0.5.jar:lib/license3j-1.0.8-SNAPSHOT.jar:lib/license3j-1.0.8-SNAPSHOT-javadoc.jar:lib/License3j.class

Exception when use a string with colon

I have a use case where i have to a string with colon in the license file. If i try this i get an Exception.

Here a sample code:

        File f = new File("test.lic");
        License license = new License();
        license.add(Feature.Create.stringFeature("id", "03:12:13"));
        license.add(Feature.Create.dateFeature("feature1", new Date()));
        license.add(Feature.Create.dateFeature("feature2", new Date()));
        LicenseWriter licenseWriter = new LicenseWriter(f.getAbsoluteFile());
        licenseWriter.write(license, IOFormat.STRING);
        
        LicenseReader reader = new LicenseReader(f);
        License license1  = reader.read(IOFormat.STRING);
        System.out.println(license1.get("id").getString());

output

Exception in thread "main" java.lang.IllegalArgumentException: Feature string representation needs '=' after the type
	at javax0.license3j.Feature.splitString(Feature.java:87)
	at javax0.license3j.License$Create.from(License.java:494)
	at javax0.license3j.io.LicenseReader.read(LicenseReader.java:109)
	at com.x.main(x.java:32)

Can you please check if it is a bug?

maven jar 1.0.8 doesn't exist

Hello,

i am trying to bind a macAdress with a licence, but i can't use setUseHwAddress().
i was on licence3j 1.0.5 so i thought it was a versioning problem so i tried to put 1.0.8 but it dosen't exist?

my dependency :

com.verhas
license3j
1.0.8

i can't find any jar in 1.0.8, even after many time looking on the internet ?

tell me if i am doing wrong pls xD
Thanks,
Quentin.

Incorrect license validation process

I noticed this feature.
You can delete the last characters of the encrypted license and the license can still be decrypted.
Please tell me why this is happening.

Wrong command names in REPL application

The commands of the REPL application are all lower case (i.e. generatekeys instead of generateKeys), which is inconsistent with the "help" command output, as well as the readme.

A new version with org.bouncycastle:bcprov-jdk15on v1.60

Hi,
I am currently using version 2.0.0 from maven central repository in my application.
The tool snyk.com we use to check for any vulnerabilities marking License3j with the following two errors

  1. Insecure Encryption [High Severity][https://snyk.io/vuln/SNYK-JAVA-ORGBOUNCYCASTLE-32369] in org.bouncycastle:[email protected]
  2. Deserialization of Untrusted Data [High Severity][https://snyk.io/vuln/SNYK-JAVA-ORGBOUNCYCASTLE-32412] in org.bouncycastle:[email protected]

It indicates that both are introduced by com.verhas:[email protected] > org.bouncycastle:[email protected] > org.bouncycastle:[email protected]

And that this issue was fixed in versions: 1.60 of bcpg-jdk15on and bcprov-jdk15on dependencies of org.bouncycastle

Is there a near future plans to prepare a new version of License3j that includes the version 1.60 of above artefacts of org.bouncycastle ?

Or
Can you tell me how I can prepare one in my own workspace?

Thanks in advance
Tezcan

CLI: Can't find signing key in key ring.

Hi Peter,

i have a problem to generate licenses on Linux (Ubuntu) machines. On Windows the license3j.bat works fine.

I get following exception: Exception in thread "main" java.lang.IllegalArgumentException: Can't find signing key in key ring.

The CLI call is:
./license3j.sh encode --license-file=liz.txt --keyring-file=secring.gpg --key="xxx (gfdsg) [email protected]" --password=gheim--output=liz.lic

Have you an idea what the problem is? I tried Java SDK 6,7 and 8 wih the 1.0.8 release.

Best regards,
Marco

Could you please help to consider to open the setLicense(final InputStream is) method to be a public method?

Dear Sir,

Regarding to the decoding, we are able to pass the inputstream as a parameter as the following: -

InputStream ins = //load file/string/resource/etc as as stream.
license.setLicenseEncode(ins);

Please correct me, if I'm wrong. I though that it would be nice to have the same way for encoding as well. Could you please help to consider to open the setLicense(final InputStream is) method to be a public method?

Regards,

Charlee Ch.

Compilation errors on master branch

First of all, thank you for this project

On the master branch, when I run mvn verify I get:

[INFO] --- maven-compiler-plugin:3.7.0:testCompile (default-testCompile) @ license3j ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 7 source files to /___/License3j/target/test-classes
[INFO] -------------------------------------------------------------
[ERROR] COMPILATION ERROR : 
[INFO] -------------------------------------------------------------
[ERROR] /___/License3j/src/test/java/com/javax0/license3j/filecompare/FilesAre.java:[77,19] cannot find symbol
  symbol:   method isComment()
  location: variable a of type com.javax0.license3j.filecompare.SourceLine
[ERROR] /___/License3j/src/test/java/com/javax0/license3j/filecompare/FilesAre.java:[77,36] cannot find symbol
  symbol:   method isComment()
  location: variable b of type com.javax0.license3j.filecompare.SourceLine
[INFO] 2 errors 
[INFO] -------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------

This is confirmed by travis status:
https://travis-ci.org/verhas/License3j/branches

I have switched to jdk8 branch where I could successfully run this command:

mvn verify -Dgpg.skip=true

What's the LICENSE of this codebase?

hi @verhas ,
This is a great project.
Can this codebase be used in a commercial project?
Can you add a LICENSE.txt file for this codebase, Apache License 2.0, MIT, or Others?
Thanks!

One more method is needed in License

Great library. Thank you. The License class though needs IMHO the following method :

    /**
     * Get an unmodifiable map of feature names to features.
     * 
     * @return the name to feature map
     */
    public Map<String, Feature> getFeatures() {
        return Collections.unmodifiableMap(features);
    }

There are multiple use cases for this. Let me mention just as an example conversion to JSON.

Regards
Nikolay

The Bouncy Castle 1.52 removes the PGPPublicKeyRingCollection(InputStream).

Dear Sir,

The Bouncy Castle 1.52 removes the PGPPublicKeyRingCollection(InputStream).

By overview I've googled and found some example coding at [1]. If I'm not wrong it may be as the following: -

//        final PGPPublicKeyRingCollection pgpRing = new PGPPublicKeyRingCollection(
//                PGPUtil.getDecoderStream(keyIn));
        final BcPGPPublicKeyRingCollection  pgpRing = new BcPGPPublicKeyRingCollection(
                                              PGPUtil.getDecoderStream(keyIn));

Could you please help to consider? Thank you very much for your help in advance. I'm looking forward to hearing from you soon.

Regards,

Charlee Ch.

[1] https://gerrit.googlesource.com/gerrit/+/2ec9cf967754f99a314646d9398fe888b54ea9a0

Invalid license digest with multiple RSA service providers

Today I encountered, that as I loaded a certain library (i.e. icepdf), suddenly my otherwise valid license was no longer recognized as properly signed. As I dug through the code, the issue appears to be originating from the bouncy castle library, which is included and loaded by icepdf.

In particular, the issue encounters, because in the License class on line 140, the following function call
final var cipher = Cipher.getInstance(key.getAlgorithm());
Does no longer return the "SunJCE: Cipher.RSA -> com.sun.crypto.provider.RSACipher" but instead, the "BC: Cipher.RSA -> org.bouncycastle.jcajce.provider.asymmetric.rsa.CipherSpi$NoPadding" Cipher.

The latter decrypts the signature in a substantially different way as the first, native Cipher implementation as it "padds" the decrypted digest to 255 bytes. See the following example:

Decrypted signature with SunJCE: Cipher.RSA (Also the message digest generated by the digester):
[-59, -25, -68, -101, 12, -88, 80, 0, 59, 69, -43, 47, 94, 28, -12, 72, 34, 48, 97, -71, 30, -19, 113, -117, 96, 49, -39, -106, -98, -120, -90, -80, -114, 87, 124, 30, -16, 3, 61, 71, -13, -92, 118, -54, 16, -7, 105, 19, 48, 46, 59, 48, 120, 54, -12, 97, -21, -75, -21, 73, 50, -26, 18, -34]

Decrypted signature with BC: Cipher.RSA:
[1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, -59, -25, -68, -101, 12, -88, 80, 0, 59, 69, -43, 47, 94, 28, -12, 72, 34, 48, 97, -71, 30, -19, 113, -117, 96, 49, -39, -106, -98, -120, -90, -80, -114, 87, 124, 30, -16, 3, 61, 71, -13, -92, 118, -54, 16, -7, 105, 19, 48, 46, 59, 48, 120, 54, -12, 97, -21, -75, -21, 73, 50, -26, 18, -34]

Due to this, the following array comparison (Line 143)
return Arrays.equals(digestValue, sigDigest);
will FAIL and the license will be marked unsigned.

At this point, I'm not quite sure how to fix this issue, but a reasonable approach would be to enforce the selection of Sun's Cipher.RSA, no matter which other crypto libraries are loaded.

Cheers
Maurice

"JCE cannot authenticate the provider BC" when BC is repackaged within another jar

Hi,

We are working on a standalone application where License3j is used for Licensing feature, and License3j in turn uses bouncy castle (bcprov-jdk16-1.46.jar). Our product is a jar and we repackage the class files of License3J and its dependencies (in this case the BCprov jar) into this jar.

When we tried to execute our application through command line, we are getting the following exception, “JCE cannot authenticate the provider BC” (Please find the attached stack trace).

Caused by: org.bouncycastle.openpgp.PGPException: Exception creating cipher
at org.bouncycastle.openpgp.PGPSecretKey.extractKeyData(Unknown Source)
~[LicenseGenerator-2.0.0-467.jar:na]
at org.bouncycastle.openpgp.PGPSecretKey.extractPrivateKey(Unknown Sourc
e) ~[LicenseGenerator-2.0.0-467.jar:na]
at org.bouncycastle.openpgp.PGPSecretKey.extractPrivateKey(Unknown Sourc
e) ~[LicenseGenerator-2.0.0-467.jar:na]
at com.verhas.licensor.License.encodeLicense(License.java:465) ~[License
Generator-2.0.0-467.jar:na]
at com.truvenhealth.analyticsengine.licensegenerator.LicenseGeneratorImp
l.createLicense(LicenseGeneratorImpl.java:105) ~[LicenseGenerator-2.0.0-467.jar:
na]
... 2 common frames omitted
Caused by: java.lang.SecurityException: JCE cannot authenticate the provider BC
at javax.crypto.Cipher.getInstance(DashoA13_..) ~[na:1.6]
... 7 common frames omitted
Caused by: java.util.jar.JarException: Cannot parse file:/C:/Users/dharanirajd/D
esktop/Test/Test3/LicenseGenerator-2.0.0-467.jar
at javax.crypto.SunJCE_c.a(DashoA13_..) ~[na:1.6]
at javax.crypto.SunJCE_b.b(DashoA13_..) ~[na:1.6]
at javax.crypto.SunJCE_b.a(DashoA13_..) ~[na:1.6]
... 8 common frames omitted

While making the BC as an installed/bundled provider (placing the bcprov jar in the jre ext folder worked), we do not want this. We want the bcprov classes to be within our jar and want the BC to be registered dynamically.

We tried with the following scenarios, like,

  1. Set the Bouncy castle jar in the classpath
  2. Set the Provider dynamically with the priority, as, “Security.insertProviderAt(new BouncyCastleProvider(), 2)”
  3. Also edited the java.policy file and set the grant permission for the bouncy castle jar.

Even after all the above 3 steps we are still getting the above exception.

But if we place the bouncy castle jar in the JAVA’s ext folder, then it’s working, but we didn’t want to go with that approach.

Can you please help us with your suggestions on how to get this working.

Thanks and Regards
Raga

Please release to central with Java 8

The central release was compiled with Java 10 and uses class file version 54.

Please can you compile with java 7 or 8 so it can be used more widely?

problem to create license

I have this problem to create the license, "I was not found or loaded the main class com.javax0.license3j.License3j" when I run it from windows cmd.

Support for offline licensing

If we use this then the same license can be validated on multiple machines, is there any other way to overcome this using license3j?
Thank you in advance.

License file is not closed after reading it

Hi,

I am using license3j to read the license file that i have.

It seems, It's not closing the inputstream whenever we are reading the license file.

I am using setLicenseEncodedFromFile(File, CharSet) method.

Below is the piece of code that i seen in License class

public License setLicenseEncoded(InputStream inputStream, String charset)
        throws IOException, PGPException {
        final ByteArrayInputStream keyIn = new ByteArrayInputStream(
            publicKeyRing);
        final InputStream decoderInputStream = PGPUtil
            .getDecoderStream(inputStream);
}    

Here inside getDecoderStream we are only reseting the inputStream, We are not closing after reading it.

Am i doing something wrong?

Please help!

Documentation for Java8?

Hi , I am using "license3j-2.0.0-JVM8.jar" with Java 8 . The commands given in the documentation refer to Java 11 commands. There is no Repl class in this jar.

So can you please share the commands to create the license file with this jar.
Thank you.

Bytes output from digest command do not include algorithm

final var key = keyPair.getPair().getPublic().getEncoded();

Shouldn't this line use keyPair.getPublic() - which seems to include the algorithm - and not just the encoded byte portion - when generating the byte array of the public key to place in your application?

When testing this, I couldn't get it to work with just the encoded byte portion. Upon attempting to load the public key in, the LicenseKeyPair.getAlgorithm() call would always fail in this code block, because it was not finding the right ASCII byte representation of the algorithm at the beginning of buffer.

private static String getAlgorithm(byte[] buffer){ for(int i = 0 ; i < buffer.length ; i ++){ if( buffer[i] == 0x00){ return new String(Arrays.copyOf(buffer,i),StandardCharsets.UTF_8); } } throw new IllegalArgumentException("key does not contain algorithm specification"); }

After changing keyPair.getPair().getPublic().getEncoded() to just keyPair.getPublic() things seemed to work better for me.

HardwareBinder giving different UUID

I am using the HardwareBinder class to generate a UUID and include it in the license file generated by a license creator java app. Then I use this UUID to validate the license in the actual app. The problem is that the HardwareBinder class gives me a different UUID after some time. For example, the UUID is the same for two days then it changes. Also, there was a time when I restarted the computer and the UUID changed. What can this be related to? Is it maybe the OS? Has anybody had this problem before?

License lacking Dependencies and batch error

Dear verhas,

The first issue I found is the lack of version 1.0.8 in the central repository. Which also seems not exist in the project.

The second, which I am having struggles with, is the error to find/load the main class. Either with package "com.javax0.license3j.License3j" or simply "License3j". I am using java 8 and the Windows .bat file.

Moreover, I used the branch jdk8 and the license3j-1.0.7 tag with no success. Hope you can help, thank you.

signed key is not recognized by program

Exception in thread "main" java.lang.IllegalArgumentException: Can't find signing key in key ring.
at com.verhas.licensor.License.loadKey(License.java:438)
at com.verhas.licensor.License.loadKey(License.java:396)
at com.verhas.licensor.License.loadKey(License.java:251)
at License3j.encode(License3j.java:34)
at License3j.main(License3j.java:150)
pub 4096R/ADADBA50 created: 2015-05-22 expires: never usage: SC
trust: ultimate validity: ultimate
sub 4096R/BCEA7B3E created: 2015-05-22 expires: never usage: S
ultimate. XXXXXXXX(Licensor at XXXX.com) [email protected]

UTC in unit tests

In the travis, the unit tests are working properly, I guess it may be running in UTC timezone.

In my case, and probably most of other people laptops, the timezone ins't UTC, and the tests should be work correctly in different timezones.

Basically we can fix it changing:

final var now = new Date(1545047719295L);

to

final var now = Date.from(LocalDateTime.of(2018,12, 17, 12, 55, 19, 295 * 1000000)
                .toInstant(ZoneOffset.UTC));

The Bouncy Castle 1.51 removes the PGPSecretKey.extractPrivateKey(String)

Dear Sir,

The Bouncy Castle 1.51 removes the PGPSecretKey.extractPrivateKey(String), it cause the License3j some exception as

java.lang.NoSuchMethodError: org.bouncycastle.openpgp.PGPSecretKey.extractPrivateKey([CLjava/lang/String;)Lorg/bouncycastle/openpgp/PGPPrivateKey;
at com.verhas.licensor.License.encodeLicense(License.java:482)

By overview I've googled and found some example coding at [1]. If I'm not wrong it may be as the following: -

        PGPSecretKey                key          = null;
        String                      pin          = null;
        PGPDigestCalculatorProvider calcProvider = null;
        PBESecretKeyDecryptor       decryptor    = null;
        PGPPrivateKey               pKey         = null;
        try {
            key          = //Loading key
            pin          = "some-pin";
            calcProvider = new JcaPGPDigestCalculatorProviderBuilder().
                                    setProvider(BouncyCastleProvider.PROVIDER_NAME).
                                    build();
            decryptor    = new JcePBESecretKeyDecryptorBuilder(calcProvider).
                                    setProvider(BouncyCastleProvider.PROVIDER_NAME).
                                    build(pin.toCharArray());
            pKey         = key.extractPrivateKey(decryptor);

        } catch (PGPException e) {
            // TODO Handle the exception
            e.printStackTrace();
        } finally {
            key          = null;
            pin          = null;
            calcProvider = null;
            decryptor    = null;
            pKey         = null;
        }

Could you please help to consider? Thank you very much for your help in advance. I'm looking forward to hearing from you soon.

Regards,

Charlee Ch.

[1] https://github.com/bcgit/bc-java/blob/master/pg/src/test/java/org/bouncycastle/openpgp/test/PGPUnicodeTest.java

Fraud Link Not Accessible

On the page https://github.com/verhas/License3j/wiki/sample the fraud link is not accessible.

If you are debugging, not serious or for some other reason do not want this extra security check to be performed then you can pass null as second argument to method loadKeyRingFromResource(). For more information why you need to include your digest into the code and pass it to the key ring loading have a look at the page {{./fraud.html}}.

Command line tool not runnable (UNIX)

I tried to execute the license3j.sh script to encode a license, but it references a missing folder lib.

I built the project with maven

           mvn clean package

but neither are dependencies (bouncycastle) compiled together with the source code into a runnable jar file nor is this lib folder generated.

Improve performance by reducing / deferring Stream terminal operations

Thanks @verhas, great library. I plan to leverage your library in a project so took a deep dive into the code.

Suggest the following to improve performance:

  1. Prefer NetworkInterface.networkInterfaces(), which returns a Stream

    return Collections.list(NetworkInterface.getNetworkInterfaces()).stream()

  2. Return a Stream instead of collecting into a List

    .map(Network.Interface.Data::new).collect(Collectors.toList());

  3. So here we can call sorted (an intermediate operation) instead of sort

    networkInterfaces.sort(Comparator.comparing(a -> a.name));

  4. Finally, terminal operation here on the Stream interfaces.forEach(ni -> ...

I would be happy to raise a PR for the above this weekend, if you like.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.