001: /**
002: * Copyright (C) 2006, 2007 David Bulmore, Software Sensation Inc.
003: * All Rights Reserved.
004: *
005: * This file is part of jWebTk.
006: *
007: * jWebTk is free software; you can redistribute it and/or modify it under
008: * the terms of the GNU General Public License (Version 2) as published by
009: * the Free Software Foundation.
010: *
011: * jWebTk is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * for more details.
015: *
016: * You should have received a copy of the GNU General Public License
017: * along with jWebTk; if not, write to the Free Software Foundation,
018: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
019: */package jwebtk.licensing;
020:
021: import java.io.BufferedReader;
022: import java.io.InputStreamReader;
023: import java.util.Date;
024: import java.util.HashMap;
025: import java.util.Map;
026: import java.util.StringTokenizer;
027: import javax.crypto.Cipher;
028: import javax.crypto.SecretKeyFactory;
029: import javax.crypto.spec.PBEKeySpec;
030: import javax.crypto.spec.PBEParameterSpec;
031:
032: /**
033: * <p>This class is used to decrypt and verify license keys generated by LicenseGenerator.
034: *
035: * <p>By default, LicenseCheck will simply decrypt the license key and return an instance
036: * of LicenseResult. LicenseResult should be verified by the application for validity.
037: *
038: * <p>When using LicenseCheck, you can concat ":match1" and/or ":match2"
039: * to the license key. If match1 or match2 are included then they must match what
040: * is parsed from the license key.
041: *
042: * <p>This is not a fool proof license, only a deterence.
043: */
044:
045: @SuppressWarnings("unchecked")
046: // working to complete a Java 1.5 version
047: public final class LicenseCheck {
048: private static byte[] i_o = { '`', '~', '!', '@', '#', '$', '%',
049: '^', '&', '*', '(', ')', '-', '_', '=', '+', '[', '{', ']',
050: '}', '\\', '|', ';', ':', ' ', '\'', '"', ',', '<', '.',
051: '>', '/', '?', '0', '1', '2', '3', '4', '5', '6', '7', '8',
052: '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k',
053: 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
054: 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
055: 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
056: 'V', 'W', 'X', 'Y', 'Z' };
057: private static byte[] o_i = { '#', '$', '%', '^', '&', '`', '~',
058: 'A', 'B', 'H', 'I', 'J', ' ', 'K', 'L', 'M', 'C', 'D', 'E',
059: 'F', 'G', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'N', 'O',
060: 'P', 'Q', 'R', '!', '@', '+', '[', '{', ']', '}', '\\',
061: '*', 'i', 's', 't', 'u', 'v', 'w', 'x', 'y', '(', ')', '-',
062: '_', '=', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', '>',
063: '/', '|', ';', ':', '\'', '"', ',', '<', '.', '?', '0',
064: '6', '7', '1', '2', '3', '4', '5', 'd', 'e', 'f', '8', '9',
065: 'a', 'b', 'c', 'g', 'h', 'z' };
066:
067: private static Map encryptMap, decryptMap;
068: private static boolean licenseOk = false;
069:
070: static {
071: setEncryptDecryptReplacement(i_o, o_i);
072: }
073:
074: /**
075: * By default, part of the encryption uses a simple replacement algorithm. The arrays
076: * must be of equal lengths. The replacements array must contain the same values as
077: * the values array, but should be mixed up.
078: *
079: * @param values byte array of values
080: * @param replacements byte array of replacement values
081: */
082:
083: public final static void setEncryptDecryptReplacement(
084: byte[] values, byte[] replacements) {
085: encryptMap = new HashMap();
086: decryptMap = new HashMap();
087:
088: for (int i = 0; i < values.length; i++) {
089: encryptMap.put(new Byte(values[i]), new Byte(
090: replacements[i]));
091: decryptMap.put(new Byte(replacements[i]), new Byte(
092: values[i]));
093: }
094: }
095:
096: /**
097: * If checkLicense() has returned a valid parsed license, then isLicenseOk() will return true.
098: *
099: * @return true if the license is valid
100: */
101:
102: public final static boolean isLicenseOk() {
103: return licenseOk;
104: }
105:
106: /**
107: * Decrypts and parses a license generated by LicenseGenerator. If successful, checkLicense()
108: * will return a Result object containing the license parts.
109: *
110: * @param license a license generated by LicenseGenerator
111: * @param match1 match string provided to LicenseGenerator
112: *
113: * @return a LicenseResult that contains the license parts
114: */
115:
116: public final static LicenseResult checkLicense(String license,
117: String match1) throws LicenseException {
118: return checkLicense(license + ":" + match1);
119: }
120:
121: /**
122: * Decrypts and parses a license generated by LicenseGenerator. If successful, checkLicense()
123: * will return a Result object containing the license parts.
124: *
125: * @param license a license generated by LicenseGenerator
126: * @param match1 match string provided to LicenseGenerator
127: * @param match2 match string provided to LicenseGenerator
128: *
129: * @return a LicenseResult that contains the license parts
130: */
131:
132: public final static LicenseResult checkLicense(String license,
133: String match1, String match2) throws LicenseException {
134: return checkLicense(license + ":" + match1 + ":" + match2);
135: }
136:
137: /**
138: * Decrypts and parses a license generated by LicenseGenerator. If successful, checkLicense()
139: * will return a Result object containing the license parts.
140: *
141: * @param license a license generated by LicenseGenerator
142: *
143: * @return a LicenseResult that contains the license parts
144: */
145:
146: public final static LicenseResult checkLicense(String license)
147: throws LicenseException {
148: String password = null, match1 = null, match2 = null;
149:
150: try {
151: if (license.indexOf(':') != -1) {
152: StringTokenizer strtok = new StringTokenizer(license,
153: ":");
154:
155: license = strtok.nextToken();
156: match1 = strtok.nextToken();
157:
158: if (strtok.hasMoreTokens())
159: match2 = strtok.nextToken();
160: }
161:
162: password = license.substring(license.length() - 8);
163: license = license.substring(0, license.length() - 8);
164:
165: if (license != null && password != null) {
166: if (license.startsWith("T_"))
167: return testTimeLicense(license, password, match1,
168: match2);
169: else if (license.startsWith("P_"))
170: return testPermanentLicense(license, password,
171: match1, match2);
172: }
173: } catch (Exception e) {
174: } // the license failed the test and we don't care what the error is
175:
176: throw new LicenseException(
177: "Sorry, your license is not valid. Please obtain a new version and/or license key. Thank you.");
178: }
179:
180: public final static class LicenseResult {
181: boolean licenseValid;
182: String message, match1, match2;
183: Date expiration;
184:
185: public String getMatch1String() {
186: return match1;
187: }
188:
189: public String getMatch2String() {
190: return match2;
191: }
192:
193: public boolean isLicenseValid() {
194: return licenseValid;
195: }
196:
197: public String getMessage() {
198: return message;
199: }
200:
201: public String toString() {
202: return isLicenseValid() + "|" + getMatch1String() + "|"
203: + getMatch2String() + "|" + getExpiration() + "|"
204: + getMessage();
205: }
206:
207: public Date getExpiration() {
208: if (expiration != null)
209: return new Date(expiration.getTime());
210:
211: return null;
212: }
213: }
214:
215: private final static LicenseResult testTimeLicense(String license,
216: String password, String match1, String match2)
217: throws LicenseException {
218: LicenseResult result = new LicenseResult();
219: String decrypt_str = new String(encryptDecrypt(
220: hexToByte(license.substring(2)), password, false));
221: StringTokenizer strtok = new StringTokenizer(decrypt_str, "|");
222:
223: result.match1 = strtok.nextToken();
224: result.match2 = strtok.nextToken();
225:
226: if (match1 != null && !match1.equals(result.match1))
227: throw new LicenseException(
228: "Sorry, your license is not valid. Please obtain a new version and/or license key. Thank you.");
229:
230: if (match2 != null && !match2.equals(result.match2))
231: throw new LicenseException(
232: "Sorry, your license is not valid. Please obtain a new version and/or license key. Thank you.");
233:
234: result.expiration = new Date(new Long(strtok.nextToken())
235: .longValue());
236:
237: if (!new Date().before(result.getExpiration()))
238: throw new LicenseException(
239: "Sorry, your temporary license expired on "
240: + result.getExpiration()
241: + ", please obtain a new license key. Thank you.");
242:
243: result.licenseValid = licenseOk = true;
244: result.message = "License is valid. However it expires on "
245: + result.getExpiration() + ". Thank you!";
246:
247: return result;
248: }
249:
250: private final static LicenseResult testPermanentLicense(
251: String license, String password, String match1,
252: String match2) throws LicenseException {
253: LicenseResult result = new LicenseResult();
254: String decrypt_str = new String(encryptDecrypt(
255: hexToByte(license.substring(2)), password, false));
256: StringTokenizer strtok = new StringTokenizer(decrypt_str, "|");
257:
258: result.match1 = strtok.nextToken();
259: result.match2 = strtok.nextToken();
260:
261: if (match1 != null && !match1.equals(result.match1))
262: throw new LicenseException(
263: "Sorry, your license is not valid. Please obtain a new version and/or license key. Thank you.");
264:
265: if (match2 != null && !match2.equals(result.match2))
266: throw new LicenseException(
267: "Sorry, your license is not valid. Please obtain a new version and/or license key. Thank you.");
268:
269: result.licenseValid = licenseOk = true;
270: result.message = "License is valid";
271:
272: return result;
273: }
274:
275: private final static byte[] hexToByte(String hexString) {
276: char c[] = hexString.toCharArray();
277: int cnt = 0;
278:
279: for (int i = 0; i < c.length; i++)
280: if (c[i] == '-')
281: cnt++;
282:
283: byte val[] = new byte[(hexString.length() - cnt) / 2];
284: int i = 0;
285:
286: while (hexString.length() > 0) {
287: boolean minus = hexString.charAt(0) == '-';
288: if (minus)
289: hexString = hexString.substring(1);
290:
291: String hex_val = hexString.substring(0, 2);
292:
293: int int_val = Integer.decode("0x" + hex_val).intValue();
294:
295: if (int_val > 127 || minus)
296: int_val = -(int_val ^ 0x80);
297:
298: val[i] = (byte) int_val;
299:
300: hexString = hexString.substring(2);
301: i++;
302: }
303:
304: return val;
305: }
306:
307: private static byte[] simpleEncryptDecrypt(byte[] in,
308: boolean encrypt) {
309: byte out[] = new byte[in.length];
310:
311: for (int i = 0; i < in.length; i++) {
312: in[i] = (byte) (in[i] ^ i);
313:
314: Byte byteVal = (Byte) (encrypt ? encryptMap.get(new Byte(
315: in[i])) : decryptMap.get(new Byte(in[i])));
316:
317: if (byteVal != null)
318: out[i] = byteVal.byteValue();
319: else
320: out[i] = in[i];
321:
322: out[i] = (byte) (out[i] ^ i);
323: }
324:
325: return out;
326: }
327:
328: final static byte[] encryptDecrypt(byte[] in, String password,
329: boolean encrypt) throws LicenseException {
330: if (password.length() < 8)
331: throw new LicenseException(
332: "Password must be at least 8 characters in length.");
333:
334: try {
335: Cipher c = Cipher.getInstance("PBEWithMD5AndDES");
336:
337: c.init(encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE,
338: SecretKeyFactory.getInstance("PBEWithMD5AndDES")
339: .generateSecret(
340: new PBEKeySpec(password
341: .toCharArray())),
342: new PBEParameterSpec(password.substring(0, 8)
343: .getBytes(), 20));
344:
345: return encrypt ? c.doFinal(simpleEncryptDecrypt(in, true))
346: : simpleEncryptDecrypt(c.doFinal(in), false);
347: } catch (Exception e) {
348: throw new LicenseException(e);
349: }
350: }
351:
352: /*
353: Permanent Key = P_27D89A908A688D10527B0DB4C3C8712BBA940A8B0B58DDCCXR8GDNS9
354:
355: Permanent License - true|sdfasdfasd|asdfasdfas|null|License is valid
356: */
357: public static void main(String[] args) {
358: BufferedReader b = new BufferedReader(new InputStreamReader(
359: System.in));
360: String key = null;
361:
362: try {
363: if (args.length == 0) {
364: System.out.println("Enter License Key: ");
365: key = b.readLine();
366: } else {
367: key = args[0];
368: }
369:
370: System.out.println(checkLicense(key));
371: } catch (Exception e) {
372: e.printStackTrace();
373: }
374: }
375: }
|