+ * This class is used to encode the username-password into base64 as request by the HTTP header definition.
+ *
+ * Modify by : Patrik Dufresne
+ */
+public final class Base64 {
+
+ private static final int BASELENGTH = 255;
+
+ private static final int EIGHTBIT = 8;
+
+ private static final int FOURBYTE = 4;
+
+ private static final int LOOKUPLENGTH = 64;
+
+ private static final byte PAD = (byte) '=';
+
+ private static final int SIGN = -128;
+
+ private static final int SIXTEENBIT = 16;
+
+ private static final int TWENTYFOURBITGROUP = 24;
+
+ private static byte[] base64Alphabet = new byte[BASELENGTH];
+
+ private static byte[] lookUpBase64Alphabet = new byte[LOOKUPLENGTH];
+ static {
+ for (int i = 0; i < BASELENGTH; i++) {
+ base64Alphabet[i] = -1;
+ }
+ for (int i = 'Z'; i >= 'A'; i--) {
+ base64Alphabet[i] = (byte) (i - 'A');
+ }
+ for (int i = 'z'; i >= 'a'; i--) {
+ base64Alphabet[i] = (byte) (i - 'a' + 26);
+ }
+ for (int i = '9'; i >= '0'; i--) {
+ base64Alphabet[i] = (byte) (i - '0' + 52);
+ }
+ base64Alphabet['+'] = 62;
+ base64Alphabet['/'] = 63;
+ for (int i = 0; i <= 25; i++) {
+ lookUpBase64Alphabet[i] = (byte) ('A' + i);
+ }
+ for (int i = 26, j = 0; i <= 51; i++, j++) {
+ lookUpBase64Alphabet[i] = (byte) ('a' + j);
+ }
+ for (int i = 52, j = 0; i <= 61; i++, j++) {
+ lookUpBase64Alphabet[i] = (byte) ('0' + j);
+ }
+ lookUpBase64Alphabet[62] = (byte) '+';
+ lookUpBase64Alphabet[63] = (byte) '/';
+ }
+
+ /**
+ * Decodes Base64 data into octects
+ *
+ * @param base64Data
+ * byte array containing Base64 data
+ * @return Array containing decoded data.
+ */
+ public static byte[] decode(byte[] base64Data) {
+ // Should we throw away anything not in base64Data ?
+ // handle the edge case, so we don't have to worry about it later
+ if (base64Data.length == 0) {
+ return new byte[0];
+ }
+ int numberQuadruple = base64Data.length / FOURBYTE;
+ byte decodedData[] = null;
+ byte b1 = 0, b2 = 0, b3 = 0, b4 = 0, marker0 = 0, marker1 = 0;
+ int encodedIndex = 0;
+ int dataIndex = 0;
+ {
+ // this block sizes the output array properly - rlw
+ int lastData = base64Data.length;
+ // ignore the '=' padding
+ while (base64Data[lastData - 1] == PAD) {
+ if (--lastData == 0) {
+ return new byte[0];
+ }
+ }
+ decodedData = new byte[lastData - numberQuadruple];
+ }
+ for (int i = 0; i < numberQuadruple; i++) {
+ dataIndex = i * 4;
+ marker0 = base64Data[dataIndex + 2];
+ marker1 = base64Data[dataIndex + 3];
+ b1 = base64Alphabet[base64Data[dataIndex]];
+ b2 = base64Alphabet[base64Data[dataIndex + 1]];
+ if (marker0 != PAD && marker1 != PAD) { // No PAD e.g 3cQl
+ b3 = base64Alphabet[marker0];
+ b4 = base64Alphabet[marker1];
+ decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
+ decodedData[encodedIndex + 1] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
+ decodedData[encodedIndex + 2] = (byte) (b3 << 6 | b4);
+ } else if (marker0 == PAD) { // Two PAD e.g. 3c[Pad][Pad]
+ decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
+ } else if (marker1 == PAD) { // One PAD e.g. 3cQ[Pad]
+ b3 = base64Alphabet[marker0];
+ decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
+ decodedData[encodedIndex + 1] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
+ }
+ encodedIndex += 3;
+ }
+ return decodedData;
+ }
+
+ /**
+ * Encodes hex octects into Base64
+ *
+ * @param binaryData
+ * Array containing binaryData
+ * @return Base64-encoded array
+ */
+ public static String encode(byte[] binaryData) {
+ int lengthDataBits = binaryData.length * EIGHTBIT;
+ int fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP;
+ int numberTriplets = lengthDataBits / TWENTYFOURBITGROUP;
+ byte encodedData[] = null;
+ if (fewerThan24bits != 0) {
+ // data not divisible by 24 bit
+ encodedData = new byte[(numberTriplets + 1) * 4];
+ } else {
+ // 16 or 8 bit
+ encodedData = new byte[numberTriplets * 4];
+ }
+ byte k = 0;
+ byte l = 0;
+ byte b1 = 0;
+ byte b2 = 0;
+ byte b3 = 0;
+ int encodedIndex = 0;
+ int dataIndex = 0;
+ int i = 0;
+ for (i = 0; i < numberTriplets; i++) {
+ dataIndex = i * 3;
+ b1 = binaryData[dataIndex];
+ b2 = binaryData[dataIndex + 1];
+ b3 = binaryData[dataIndex + 2];
+ l = (byte) (b2 & 0x0f);
+ k = (byte) (b1 & 0x03);
+ encodedIndex = i * 4;
+ byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
+ byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);
+ byte val3 = ((b3 & SIGN) == 0) ? (byte) (b3 >> 6) : (byte) ((b3) >> 6 ^ 0xfc);
+ encodedData[encodedIndex] = lookUpBase64Alphabet[val1];
+ encodedData[encodedIndex + 1] = lookUpBase64Alphabet[val2 | (k << 4)];
+ encodedData[encodedIndex + 2] = lookUpBase64Alphabet[(l << 2) | val3];
+ encodedData[encodedIndex + 3] = lookUpBase64Alphabet[b3 & 0x3f];
+ }
+ // form integral number of 6-bit groups
+ dataIndex = i * 3;
+ encodedIndex = i * 4;
+ if (fewerThan24bits == EIGHTBIT) {
+ b1 = binaryData[dataIndex];
+ k = (byte) (b1 & 0x03);
+ byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
+ encodedData[encodedIndex] = lookUpBase64Alphabet[val1];
+ encodedData[encodedIndex + 1] = lookUpBase64Alphabet[k << 4];
+ encodedData[encodedIndex + 2] = PAD;
+ encodedData[encodedIndex + 3] = PAD;
+ } else if (fewerThan24bits == SIXTEENBIT) {
+ b1 = binaryData[dataIndex];
+ b2 = binaryData[dataIndex + 1];
+ l = (byte) (b2 & 0x0f);
+ k = (byte) (b1 & 0x03);
+ byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
+ byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);
+ encodedData[encodedIndex] = lookUpBase64Alphabet[val1];
+ encodedData[encodedIndex + 1] = lookUpBase64Alphabet[val2 | (k << 4)];
+ encodedData[encodedIndex + 2] = lookUpBase64Alphabet[l << 2];
+ encodedData[encodedIndex + 3] = PAD;
+ }
+ return new String(encodedData);
+ }
+
+ public static boolean isArrayByteBase64(byte[] arrayOctect) {
+ int length = arrayOctect.length;
+ if (length == 0) {
+ return true;
+ }
+ for (int i = 0; i < length; i++) {
+ if (!isBase64(arrayOctect[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ static boolean isBase64(byte octect) {
+ // Should we ignore white space?
+ return (octect == PAD || base64Alphabet[octect] != -1);
+ }
+
+ public static boolean isBase64(String isValidString) {
+ return isArrayByteBase64(isValidString.getBytes());
+ }
+}
diff --git a/src/main/java/com/patrikdufresne/license/EncryptionManager.java b/src/main/java/com/patrikdufresne/license/EncryptionManager.java
index fb180631f32e951b6f2aa56bc46b0e46d4335ca5..3e10c86da5103d4e4eacb816f559762704d999f1 100644
--- a/src/main/java/com/patrikdufresne/license/EncryptionManager.java
+++ b/src/main/java/com/patrikdufresne/license/EncryptionManager.java
@@ -33,25 +33,15 @@ import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
/**
- * This class is used to manage the encryption of the license. It's used to
- * encrypt, sign and validate using a public or private key.
+ * This class is used to manage the encryption of the license. It's used to encrypt, sign and validate using a public or
+ * private key.
*
* @author Patrik Dufresne
*
*/
public class EncryptionManager {
- private static final int SIZE = 2048;
-
- /**
- * Single instance of the utility class.
- */
- // private static EncryptionManager instance;
-
- // private static final String PUBLIC_KEY_FILE = "/License/public_key.der";
-
- // private static final String PRIVATE_KEY_FILE =
- // "/path/to/your/private_key.der";
+ private static final int BUF_SIZE = 4096;
private PublicKey publicKey;
@@ -70,8 +60,7 @@ public class EncryptionManager {
* @throws NoSuchAlgorithmException
* if no Provider supports RSA
* @throws InvalidKeySpecException
- * if the given key specification is inappropriate for this key
- * factory to produce a public key.
+ * if the given key specification is inappropriate for this key factory to produce a public key.
*/
public EncryptionManager(byte[] publicKey, byte[] privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
if (publicKey == null) {
@@ -100,8 +89,8 @@ public class EncryptionManager {
*/
public static byte[] readAll(InputStream input) throws IOException {
// Read the content of the file and store it in a byte array.
- ByteArrayOutputStream out = new ByteArrayOutputStream(SIZE);
- byte[] buf = new byte[SIZE];
+ ByteArrayOutputStream out = new ByteArrayOutputStream(BUF_SIZE);
+ byte[] buf = new byte[BUF_SIZE];
int size;
while ((size = input.read(buf)) != -1) {
out.write(buf, 0, size);
@@ -110,16 +99,14 @@ public class EncryptionManager {
}
/**
- * This function maybe used to read the public and/or private key from a
- * file.
+ * This function maybe used to read the public and/or private key from a file.
*
* @param file
* the file to read
* @return the file data
*
* @throws IOException
- * if the file does not exist, or if the first byte cannot be
- * read for any reason
+ * if the file does not exist, or if the first byte cannot be read for any reason
*/
public static byte[] readAll(File file) throws IOException {
InputStream input = new FileInputStream(file);
@@ -144,8 +131,7 @@ public class EncryptionManager {
* @throws InvalidKeyException
* if the key is invalid.
* @throws SignatureException
- * if this signature algorithm is unable to process the input
- * data
+ * if this signature algorithm is unable to process the input data
*/
public boolean verify(byte[] data, byte[] sig) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
@@ -162,20 +148,17 @@ public class EncryptionManager {
}
/**
- * Sign the given input stream data. The signature is append to the output
- * stream.
+ * Sign the given input stream data. The signature is append to the output stream.
*
* @param data
* the the data to be signed.
* @return the signature for the given data.
* @throws NoSuchAlgorithmException
- * if no Provider supports a Signature implementation for
- * SHA1withRSA.
+ * if no Provider supports a Signature implementation for SHA1withRSA.
* @throws InvalidKeyException
* if the private key is invalid.
* @throws SignatureException
- * if this signature algorithm is unable to process the input
- * data provided.
+ * if this signature algorithm is unable to process the input data provided.
* @throws UnsupportedOperationException
* if the private key was not providedin the constructor.
*/
diff --git a/src/main/java/com/patrikdufresne/license/AbstractLicense.java b/src/main/java/com/patrikdufresne/license/License.java
similarity index 52%
rename from src/main/java/com/patrikdufresne/license/AbstractLicense.java
rename to src/main/java/com/patrikdufresne/license/License.java
index ca1fa1398a7f899c72d65ed681bff3836a8fc8cf..1d5d3296797f2b16d75b3f84aed67fd8d509d6b3 100644
--- a/src/main/java/com/patrikdufresne/license/AbstractLicense.java
+++ b/src/main/java/com/patrikdufresne/license/License.java
@@ -16,168 +16,132 @@
package com.patrikdufresne.license;
import java.io.Serializable;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Collections;
import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
- * Abstract implementation of the license interface use by the license manager.
+ * This class represent the license information. The {@link License} class should be used to store related information
+ * about the license. This information may then be saved into an encrypted file.
*
* @author Patrik Dufresne
*
*/
-public abstract class AbstractLicense implements Serializable, ILicense {
+public final class License {
- private static final long serialVersionUID = -144058457108187374L;
+ public static final String EMAIL = "email";
+ public static final String EXPIRATION = "expiration";
+ public static final String ID = "id";
+ public static final String LICENSE_NUMBER = "licenseNumber";
+ public static final String LICENSE_TYPE = "licenseType";
+ public static final String NAME = "name";
/**
* License type for lifetime version. Always valid.
*/
public static final String TYPE_LIFETIME = "lifetime";
/**
- * License type for single version. This type is valid for the given
- * version.
+ * License type for single version. This type is valid for the given version.
*/
public static final String TYPE_SINGLE_VERSION = "single-version";
/**
- * License type for trial version. This type is valid until the expiration
- * date.
+ * License type for trial version. This type is valid until the expiration date.
*/
public static final String TYPE_TRIAL = "trial";
-
- private String email;
-
- private Date expiration;
-
- private String licenseNumber;
-
- private String licenseType;
- private String name;
- private String version;
-
- /**
- * Create a new license with default property value.
- */
- public AbstractLicense() {
- name = "";
- email = "";
- licenseNumber = "";
- expiration = new Date();
- version = "";
- licenseType = TYPE_TRIAL;
- }
-
- /**
- * Return the associated email value.
- *
- * @return the email or null
- */
- public String getEmail() {
- return email;
- }
+ public static final String VERSION = "version";
/**
- * @return the expiration
+ * Map to store properties.
*/
- public Date getExpiration() {
- return expiration;
- }
+ private Map
- * Generally, an application will not required more then one instance of license
- * manager.
+ * Generally, an application will not required more then one instance of license manager.
*
* @author Patrik Dufresne
*
*/
public class LicenseManager {
- private static final int SIZE = 2048;
+ /**
+ * Property to store the signature.
+ */
+ private static final String SIGNATURE = "signature";
/**
* The encryption manager used by this class.F
*/
private EncryptionManager encryptionManager;
/**
- * Create a new license manager.
+ * Create a new license manager. Generally, an application will not required more then one instance of license
+ * manager.
*
* @param publicKey
- * the public key filename.
+ * the public key (can't be null).
* @param privateKey
- * the private key filename (null if not available).
+ * the private key (null if not available).
* @throws GeneralSecurityException
* if the provided key are invalid.
- * @throws IOException
- * if the file doesn't exists
*/
- public LicenseManager(String publicKey, String privateKey) throws GeneralSecurityException, IOException {
- byte[] pubdata = EncryptionManager.readAll(new File(publicKey));
- byte[] privdata = null;
- if (privateKey != null) {
- privdata = EncryptionManager.readAll(new File(privateKey));
- }
- this.encryptionManager = new EncryptionManager(pubdata, privdata);
+ public LicenseManager(byte[] publicKey, byte[] privateKey) throws GeneralSecurityException {
+ this.encryptionManager = new EncryptionManager(publicKey, privateKey);
}
/**
@@ -108,19 +111,24 @@ public class LicenseManager {
}
/**
- * Create a new license manager. Generally, an application will not required
- * more then one instance of license manager.
+ * Create a new license manager.
*
* @param publicKey
- * the public key (can't be null).
- *
+ * the public key filename.
* @param privateKey
- * the private key (null if not available).
+ * the private key filename (null if not available).
* @throws GeneralSecurityException
* if the provided key are invalid.
+ * @throws IOException
+ * if the file doesn't exists
*/
- public LicenseManager(byte[] publicKey, byte[] privateKey) throws GeneralSecurityException {
- this.encryptionManager = new EncryptionManager(publicKey, privateKey);
+ public LicenseManager(String publicKey, String privateKey) throws GeneralSecurityException, IOException {
+ byte[] pubdata = EncryptionManager.readAll(new File(publicKey));
+ byte[] privdata = null;
+ if (privateKey != null) {
+ privdata = EncryptionManager.readAll(new File(privateKey));
+ }
+ this.encryptionManager = new EncryptionManager(pubdata, privdata);
}
/**
@@ -132,53 +140,44 @@ public class LicenseManager {
* @throws IOException
* if file not found or read error.
* @throws SignatureException
- * if this signature algorithm is unable to process the content
- * of the file
+ * if this signature algorithm is unable to process the content of the file
* @throws NoSuchAlgorithmException
* if the SHA algorithm doesn't exists
* @throws InvalidKeyException
* if the public key is invalid
* @throws ClassNotFoundException
- * if the implementation of {@link ILicense} stored in the file
- * can't be found
+ * if the implementation of {@link License} stored in the file can't be found
*/
- public ILicense readLicenseFile(File file) throws IOException, InvalidKeyException, NoSuchAlgorithmException, SignatureException, ClassNotFoundException {
-
- // Read the content of the file
- byte[] sig;
- byte[] data;
- ObjectInputStream fileIn = new ObjectInputStream(new BufferedInputStream(new FileInputStream(file)));
- try {
- int sigLength = fileIn.readInt();
- sig = new byte[sigLength];
- fileIn.read(sig);
-
- ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
- byte[] buf = new byte[SIZE];
- int len;
- while ((len = fileIn.read(buf)) != -1) {
- dataStream.write(buf, 0, len);
+ public License readLicenseFile(File file) throws IOException, InvalidKeyException, NoSuchAlgorithmException, SignatureException, ClassNotFoundException {
+
+ String base64Signature = null;
+ // Read the license file as a property file.
+ Properties prop = new Properties();
+ prop.load(new FileReader(file));
+ License lic = new License();
+ for (Object key : prop.keySet()) {
+ String value = (String) prop.get(key);
+ if (SIGNATURE.equals(key)) {
+ base64Signature = value;
+ } else {
+ lic.setProperty((String) key, value);
}
- dataStream.flush();
- data = dataStream.toByteArray();
- dataStream.close();
- } finally {
- fileIn.close();
}
+ // Check if the signature is available.
+ if (base64Signature == null) {
+ throw new SignatureException("No signature was found");
+ }
+ byte[] sig = Base64.decode(base64Signature.getBytes());
+
+ // Check if the signature matches.
+ byte[] data = writeLicenseToByteArray(lic);
// Validate the signature
if (!encryptionManager.verify(data, sig)) {
return null;
}
- // Read the license object from the data.
- ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(data));
- try {
- ILicense license = (ILicense) in.readObject();
- return license;
- } finally {
- in.close();
- }
+ return lic;
}
@@ -188,40 +187,56 @@ public class LicenseManager {
* @param license
* the license object.
* @param file
- * the location where to save the new license file. If file
- * exists, it's overwrite.
+ * the location where to save the new license file. If file exists, it's overwrite.
* @throws IOException
* if the file doesn't exists or can't be written to
* @throws SignatureException
- * if this signature algorithm is unable to process the license
- * data
+ * if this signature algorithm is unable to process the license data
* @throws NoSuchAlgorithmException
* if the algorithm SHA is not supported
* @throws InvalidKeyException
* if the private key is invalid.
*/
- public void writeLicense(ILicense license, File file) throws IOException, InvalidKeyException, NoSuchAlgorithmException, SignatureException {
- // Write the license information into a byte array.
- ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
- ObjectOutputStream out = new ObjectOutputStream(dataStream);
- out.writeObject(license);
- byte[] data = dataStream.toByteArray();
- out.close();
+ public void writeLicense(License lic, File file) throws IOException, InvalidKeyException, NoSuchAlgorithmException, SignatureException {
+
+ byte[] data = writeLicenseToByteArray(lic);
// Then sign the byte array
byte[] signature = this.encryptionManager.sign(data);
+ String base64signature = Base64.encode(signature);
- // Write all the data into one single file.
- ObjectOutputStream fileOut = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(file)));
- try {
- fileOut.writeInt(signature.length);
- fileOut.write(signature);
- fileOut.write(data);
- fileOut.flush();
- } finally {
- fileOut.close();
+ // Create property file
+ Properties prop = new Properties();
+ for (Entry