diff --git a/pom.xml b/pom.xml index 59102bbb8e5d67af5f051f621eb7485407efa120..fbfbb2bcefa166f5524f51895bcd2d3549905762 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.patrikdufresne.license com.patrikdufresne.license - 0.3-SNAPSHOT + 0.4-SNAPSHOT diff --git a/src/main/java/com/patrikdufresne/license/Base64.java b/src/main/java/com/patrikdufresne/license/Base64.java new file mode 100644 index 0000000000000000000000000000000000000000..8e991d5fee46cd49452b769be490bb6db0def0d9 --- /dev/null +++ b/src/main/java/com/patrikdufresne/license/Base64.java @@ -0,0 +1,219 @@ +/** + * Copyright(C) 2013 Patrik Dufresne Service Logiciel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.patrikdufresne.license; + +/** + * This class is an adaptation of the BASE64Encoder provided in CA Spectrum RESTful Examples: + * /usr/Spectrum/RestfulExamples/src/test/utils/BASE64Encoder.java + * + *

+ * 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 properties; /** - * @return the licenseNumber - */ - public String getLicenseNumber() { - return licenseNumber; - } - - /** - * Return the license type. - * - * @return the licenseType + * Create a new license with default property value. */ - public String getLicenseType() { - return licenseType; + public License() { + this.properties = new HashMap(); + // id = ""; + // name = ""; + // email = ""; + // licenseNumber = ""; + // expiration = new Date(); + // version = ""; + // licenseType = TYPE_TRIAL; } /** - * Return the name associated with the license. + * Return an unmodifiable map of properties. * - * @return the name or null + * @return */ - public String getName() { - return name; + public Map getProperties() { + return Collections.unmodifiableMap(this.properties); } /** - * Return the license version + * Return the expiration date. * - * @return the version or null + * @return the expiration */ - public String getVersion() { - return version; + public Date getExpiration() { + String value = getProperty(EXPIRATION); + if (value == null || value.trim().isEmpty()) { + return null; + } + DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); + try { + return df.parse(value); + } catch (ParseException e) { + return null; + } } /** - * Sets the associated email + * Get the property value. * - * @param email - * the email or null. + * @param key + * the property key */ - public void setEmail(String email) { - this.email = email; + public String getProperty(String key) { + return this.properties.get(key); } /** * Set the license expiration date. Required with TYPE_TRIAL * * @param expiration - * the expiration date or null. + * the expiration date. */ public void setExpiration(Date expiration) { - this.expiration = expiration; - } - - /** - * Sets the license number. - * - * @param licenseNumber - * the licenseNumber - */ - public void setLicenseNumber(String licenseNumber) { - this.licenseNumber = licenseNumber; - } - - /** - * Sets the license type. - * - * @param licenseType - * the licenseType, one of the TYPE_* constants (can't be null). - */ - public void setLicenseType(String licenseType) { - if (licenseType == null) { - throw new NullPointerException(); + if (expiration == null) { + setProperty(EXPIRATION, null); + } else { + DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); + setProperty(EXPIRATION, df.format(expiration)); } - this.licenseType = licenseType; } /** - * Sets the name associated with the license + * Sets the property value. * - * @param name - * the name or null - */ - public void setName(String name) { - this.name = name; - } - - /** - * Sets the license version. Required with SINGLE_VERSION. - * - * @param version - * the version or null - */ - public void setVersion(String version) { - this.version = version; + * @param key + * the property key + * @param value + * the property value. + */ + public void setProperty(String key, String value) { + if (value == null) { + this.properties.remove(key); + } else { + this.properties.put(key, value); + } } /** @@ -202,7 +166,7 @@ public abstract class AbstractLicense implements Serializable, ILicense { * @throws LicenseExpiredException */ protected void validateExpiration(Date currentDate) throws LicenseExpiredException { - if (getLicenseType().equals(TYPE_TRIAL)) { + if (TYPE_TRIAL.equals(getProperty(LICENSE_TYPE))) { if (getExpiration() == null || currentDate.after(getExpiration())) { throw new LicenseExpiredException(); } @@ -219,11 +183,11 @@ public abstract class AbstractLicense implements Serializable, ILicense { */ protected void validateVersion(String currentVersion) throws LicenseVersionExpiredException { - if (getLicenseType().equals(TYPE_SINGLE_VERSION)) { - if (getVersion() == null) { + if (TYPE_SINGLE_VERSION.equals(getProperty(LICENSE_TYPE))) { + if (getProperty(VERSION) == null) { throw new LicenseVersionExpiredException(); } - Pattern pattern = Pattern.compile(getVersion()); + Pattern pattern = Pattern.compile(getProperty(VERSION)); Matcher matcher = pattern.matcher(currentVersion); if (!matcher.matches()) { throw new LicenseVersionExpiredException(); diff --git a/src/main/java/com/patrikdufresne/license/LicenseManager.java b/src/main/java/com/patrikdufresne/license/LicenseManager.java index 5a65305b6b6fbd6c6084a11ceb632a32385faca0..31783a5190252b6c5dc75cde77fbaa063490c01f 100644 --- a/src/main/java/com/patrikdufresne/license/LicenseManager.java +++ b/src/main/java/com/patrikdufresne/license/LicenseManager.java @@ -22,6 +22,8 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; @@ -30,43 +32,44 @@ import java.security.GeneralSecurityException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SignatureException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Map.Entry; +import java.util.Properties; /** - * This the main entry point of the licensing module. This class should be used - * to create and check license files. + * This the main entry point of the licensing module. This class should be used to create and check license files. *

- * 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 e : lic.getProperties().entrySet()) { + prop.setProperty(e.getKey(), e.getValue()); } + prop.put(SIGNATURE, base64signature); + // Write the property file + prop.store(new FileWriter(file), "License file"); } + /** + * Write the license information into a byte array ready to be signed. + * + * @param lic + * the license + * @return the byte array + * @throws IOException + */ + protected byte[] writeLicenseToByteArray(License lic) throws IOException { + ByteArrayOutputStream dataStream = new ByteArrayOutputStream(); + ObjectOutputStream out = new ObjectOutputStream(dataStream); + // Sort the key to have a predictable results. + List keys = new ArrayList(lic.getProperties().keySet()); + Collections.sort(keys); + for (String key : keys) { + String value = lic.getProperty(key); + out.writeChars(key); + out.writeChars(value); + } + byte[] data = dataStream.toByteArray(); + out.close(); + return data; + } } diff --git a/src/main/java/com/patrikdufresne/license/ILicense.java b/src/test/java/com/patrikdufresne/license/AllTests.java similarity index 76% rename from src/main/java/com/patrikdufresne/license/ILicense.java rename to src/test/java/com/patrikdufresne/license/AllTests.java index 2f3cff3ad0ec63de3586d838fb73b671f39c5444..779f753e22a7094fc83bc908f2d850209c5d2a45 100644 --- a/src/main/java/com/patrikdufresne/license/ILicense.java +++ b/src/test/java/com/patrikdufresne/license/AllTests.java @@ -15,14 +15,11 @@ */ package com.patrikdufresne.license; -import java.io.Serializable; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; -/** - * This interface is used to represent a license data. - * - * @author ikus060 - * - */ -public interface ILicense extends Serializable { +@RunWith(Suite.class) +@Suite.SuiteClasses( { EncryptionManagerTest.class, KeyManagerTest.class, LicenseManagerTest.class }) +public class AllTests { } diff --git a/src/test/java/com/patrikdufresne/license/LicenseManagerTest.java b/src/test/java/com/patrikdufresne/license/LicenseManagerTest.java index 96f6100ca58fb0f41c8024ad29d6b7cc9c1c72b9..e33eff2f23a8468374d98ccb32b6c7c0a557397b 100644 --- a/src/test/java/com/patrikdufresne/license/LicenseManagerTest.java +++ b/src/test/java/com/patrikdufresne/license/LicenseManagerTest.java @@ -28,20 +28,13 @@ import org.junit.Before; import org.junit.Test; /** - * This class test all the functionnality provided by the {@link LicenseManager} - * . + * This class test all the functionnality provided by the {@link LicenseManager} . * * @author ikus060 * */ public class LicenseManagerTest { - static class MockLicense extends AbstractLicense { - - private static final long serialVersionUID = -4612807836761969030L; - - } - private LicenseManager manager; @Before @@ -57,9 +50,18 @@ public class LicenseManagerTest { * @throws NoSuchAlgorithmException * @throws InvalidKeyException */ + @Test + public void writeLicense_WithEmptyLicense_CreateTheFile() throws InvalidKeyException, NoSuchAlgorithmException, SignatureException, IOException { + License license = new License(); + File file = new File("unittest.lic"); + manager.writeLicense(license, file); + assertTrue(file.exists()); + } + @Test public void writeLicense_WithLicense_CreateTheFile() throws InvalidKeyException, NoSuchAlgorithmException, SignatureException, IOException { - MockLicense license = new MockLicense(); + License license = new License(); + license.setProperty(License.NAME, "A test name"); File file = new File("unittest.lic"); manager.writeLicense(license, file); assertTrue(file.exists()); @@ -81,13 +83,13 @@ public class LicenseManagerTest { SignatureException, IOException, ClassNotFoundException { - MockLicense license = new MockLicense(); + License license = new License(); File file = new File("unittest2.lic"); manager.writeLicense(license, file); assertTrue(file.exists()); // Read the file - MockLicense license2 = (MockLicense) manager.readLicenseFile(file); + License license2 = (License) manager.readLicenseFile(file); assertNotNull(license2); }