You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

License.java 31KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942
  1. /* *************************************************************************
  2. IT Mill Toolkit
  3. Development of Browser User Intarfaces Made Easy
  4. Copyright (C) 2000-2006 IT Mill Ltd
  5. *************************************************************************
  6. This product is distributed under commercial license that can be found
  7. from the product package on license/license.txt. Use of this product might
  8. require purchasing a commercial license from IT Mill Ltd. For guidelines
  9. on usage, see license/licensing-guidelines.html
  10. *************************************************************************
  11. For more information, contact:
  12. IT Mill Ltd phone: +358 2 4802 7180
  13. Ruukinkatu 2-4 fax: +358 2 4802 7181
  14. 20540, Turku email: info@itmill.com
  15. Finland company www: www.itmill.com
  16. Primary source for information and releases: www.itmill.com
  17. ********************************************************************** */
  18. package com.itmill.toolkit.service;
  19. import java.io.ByteArrayInputStream;
  20. import java.io.CharArrayWriter;
  21. import java.io.IOException;
  22. import java.io.InputStream;
  23. import java.io.UnsupportedEncodingException;
  24. import java.io.Writer;
  25. import java.security.InvalidKeyException;
  26. import java.security.NoSuchAlgorithmException;
  27. import java.security.PublicKey;
  28. import java.security.Signature;
  29. import java.security.SignatureException;
  30. import java.security.cert.CertificateException;
  31. import java.security.cert.CertificateFactory;
  32. import java.security.cert.X509Certificate;
  33. import java.util.Calendar;
  34. import java.util.Iterator;
  35. import java.util.TreeSet;
  36. import javax.xml.parsers.DocumentBuilder;
  37. import javax.xml.parsers.DocumentBuilderFactory;
  38. import javax.xml.parsers.ParserConfigurationException;
  39. import org.w3c.dom.Document;
  40. import org.w3c.dom.Element;
  41. import org.w3c.dom.NamedNodeMap;
  42. import org.w3c.dom.Node;
  43. import org.w3c.dom.NodeList;
  44. import org.xml.sax.SAXException;
  45. public class License {
  46. /** IT Mill License Manager certificate */
  47. private static String certificate = "-----BEGIN CERTIFICATE-----\n"
  48. + "MIIDJjCCAuQCBEVqxNwwCwYHKoZIzjgEAwUAMHkxCzAJBgNVBAYTAkZJMRAwDgYDVQQIEwdVbmtu\n"
  49. + "b3duMQ4wDAYDVQQHEwVUdXJrdTEUMBIGA1UEChMLSVQgTWlsbCBMdGQxEDAOBgNVBAsTB1Vua25v\n"
  50. + "d24xIDAeBgNVBAMTF0lUIE1pbGwgTGljZW5zZSBNYW5hZ2VyMB4XDTA2MTEyNzEwNTgzNloXDTQ3\n"
  51. + "MTIyMjEwNTgzNloweTELMAkGA1UEBhMCRkkxEDAOBgNVBAgTB1Vua25vd24xDjAMBgNVBAcTBVR1\n"
  52. + "cmt1MRQwEgYDVQQKEwtJVCBNaWxsIEx0ZDEQMA4GA1UECxMHVW5rbm93bjEgMB4GA1UEAxMXSVQg\n"
  53. + "TWlsbCBMaWNlbnNlIE1hbmFnZXIwggG3MIIBLAYHKoZIzjgEATCCAR8CgYEA/X9TgR11EilS30qc\n"
  54. + "Luzk5/YRt1I870QAwx4/gLZRJmlFXUAiUftZPY1Y+r/F9bow9subVWzXgTuAHTRv8mZgt2uZUKWk\n"
  55. + "n5/oBHsQIsJPu6nX/rfGG/g7V+fGqKYVDwT7g/bTxR7DAjVUE1oWkTL2dfOuK2HXKu/yIgMZndFI\n"
  56. + "AccCFQCXYFCPFSMLzLKSuYKi64QL8Fgc9QKBgQD34aCF1ps93su8q1w2uFe5eZSvu/o66oL5V0wL\n"
  57. + "PQeCZ1FZV4661FlP5nEHEIGAtEkWcSPoTCgWE7fPCTKMyKbhPBZ6i1R8jSjgo64eK7OmdZFuo38L\n"
  58. + "+iE1YvH7YnoBJDvMpPG+qFGQiaiD3+Fa5Z8GkotmXoB7VSVkAUw7/s9JKgOBhAACgYB2wjpuZXqK\n"
  59. + "Ldgw1uZRlNCON7vo4m420CSna0mhETqzW9UMFHmZfn9edD0B1dDh6NwmRIDjljf8+ODuhwZKkzl8\n"
  60. + "DHUq3HPnipEsr0C3g1Dz7ZbjcvUhzsPDElpKBZhHRaoqfAfWiNxeVF2Kh2IlIMwuJ2xZeSaUH7Pj\n"
  61. + "LwAkKye6dzALBgcqhkjOOAQDBQADLwAwLAIUDgvWt7ItRyZfpWNEeJ0P9yaxOwoCFC21LRtwLi1t\n"
  62. + "c+yomHtX+mpxF7VO\n" + "-----END CERTIFICATE-----\n";
  63. /** License XML Document */
  64. private Document licenseXML = null;
  65. /** The signature has already been checked and is valid */
  66. private boolean signatureIsValid = false;
  67. /**
  68. * Read the license-file from input stream.
  69. *
  70. * License file can only ne read once, after it has been read it stays.
  71. *
  72. * @param is
  73. * Input stream where the license file is read from.
  74. * @throws SAXException
  75. * Error parsing the license file
  76. * @throws IOException
  77. * Error reading the license file
  78. * @throws LicenseFileHasAlreadyBeenRead
  79. * License file has already been read.
  80. */
  81. public void readLicenseFile(InputStream is) throws SAXException,
  82. IOException, LicenseFileHasAlreadyBeenRead {
  83. // Once the license has been read, it stays
  84. if (hasBeenRead())
  85. throw new LicenseFileHasAlreadyBeenRead();
  86. // Parse XML
  87. DocumentBuilder db;
  88. try {
  89. db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
  90. licenseXML = db.parse(is);
  91. } catch (ParserConfigurationException e) {
  92. throw new RuntimeException(e);
  93. }
  94. }
  95. /**
  96. * Is the license file already been read.
  97. *
  98. * @return true if the license-file has already been read.
  99. */
  100. public boolean hasBeenRead() {
  101. return licenseXML != null;
  102. }
  103. /** Should the license description be printed on (first) application init. */
  104. public boolean shouldLimitsBePrintedOnInit()
  105. throws LicenseFileHasNotBeenRead, LicenseSignatureIsInvalid,
  106. InvalidLicenseFile {
  107. checkThatLicenseDOMisValid();
  108. NodeList lL = licenseXML.getElementsByTagName("limits");
  109. if (lL == null || lL.getLength() == 0)
  110. throw new InvalidLicenseFile("limits not found from license-file");
  111. Element e = (Element) lL.item(0);
  112. String print = e.getAttribute("print-limits-on-init");
  113. return "true".equalsIgnoreCase(print);
  114. }
  115. public String getDescription() throws LicenseFileHasNotBeenRead,
  116. InvalidLicenseFile, LicenseSignatureIsInvalid {
  117. checkThatLicenseDOMisValid();
  118. StringBuffer d = new StringBuffer();
  119. d.append("------------------ License Info -----------------------\n");
  120. d.append("License number: " + getLicenseNumber() + "\n");
  121. d.append("Product: " + getProductName());
  122. if (getProductEdition() != null)
  123. d.append(" Edition: " + getProductEdition());
  124. d.append("\n");
  125. // Print version info
  126. String versionDescription = getVersionDescription();
  127. if (versionDescription != null)
  128. d.append("Version: " + versionDescription + "\n");
  129. if (getLicenseeName() != null)
  130. d.append("Licensed to: " + getLicenseeName() + "\n");
  131. if (getPurpose() != null)
  132. d.append("Use is limited to: " + getPurpose() + "\n");
  133. if (getMaxConcurrentUsers() >= 0)
  134. d.append("Maximum number of concurrent (active) users allowed: "
  135. + getMaxConcurrentUsers() + "\n");
  136. if (getMaxJVMs() >= 0)
  137. d.append("Maximum number of JVM:s this license"
  138. + " can be used concurrently: " + getMaxJVMs() + "\n");
  139. // Print valid-until date
  140. NodeList vuL = licenseXML.getElementsByTagName("valid-until");
  141. if (vuL != null && vuL.getLength() > 0) {
  142. Element e = (Element) vuL.item(0);
  143. String year = e.getAttribute("year");
  144. String month = e.getAttribute("month");
  145. String day = e.getAttribute("day");
  146. d.append("License is valid until: " + year + "-" + month + "-"
  147. + day + "\n");
  148. }
  149. // Print application info
  150. NodeList aL = licenseXML.getElementsByTagName("application");
  151. if (aL != null && aL.getLength() > 0) {
  152. Element e = (Element) aL.item(0);
  153. String app = e.getAttribute("name");
  154. String purpose = e.getAttribute("purpose");
  155. String prefix = e.getAttribute("classPrefix");
  156. if (app != null && app.length() > 0)
  157. d.append("For use with this application only: " + app + "\n");
  158. if (app != null && app.length() > 0)
  159. d.append("Application usage purpose is limited to: " + purpose
  160. + "\n");
  161. if (app != null && app.length() > 0)
  162. d.append("Application class name must match prefix: " + prefix
  163. + "\n");
  164. }
  165. d.append("--------------------------------------------------------\n");
  166. return d.toString();
  167. }
  168. private void checkThatLicenseDOMisValid() throws LicenseFileHasNotBeenRead,
  169. InvalidLicenseFile, LicenseSignatureIsInvalid {
  170. // Check that the license file has already been read
  171. if (!hasBeenRead())
  172. throw new LicenseFileHasNotBeenRead();
  173. // Check validity of the signature
  174. if (!isSignatureValid())
  175. throw new LicenseSignatureIsInvalid();
  176. }
  177. /**
  178. * Check if the license valid for given usage?
  179. *
  180. * Checks that the license is valid for specified usage. Throws an exception
  181. * if there is something wrong with the license or use.
  182. *
  183. * @param applicationClass
  184. * Class of the application this license is used for
  185. * @param concurrentUsers
  186. * Number if users concurrently using this application
  187. * @param majorVersion
  188. * Major version number (for example 4 if version is 4.1.7)
  189. * @param minorVersion
  190. * Minor version number (for example 1 if version is 4.1.7)
  191. * @param productName
  192. * The name of the product
  193. * @param productEdition
  194. * The name of the product edition
  195. * @throws LicenseFileHasNotBeenRead
  196. * if the license file has not been read
  197. * @throws LicenseSignatureIsInvalid
  198. * if the license file has been changed or signature is
  199. * otherwise invalid
  200. * @throws InvalidLicenseFile
  201. * License if the license file is not of correct XML format
  202. * @throws LicenseViolation
  203. *
  204. */
  205. public void check(Class applicationClass, int concurrentUsers,
  206. int majorVersion, int minorVersion, String productName,
  207. String productEdition) throws LicenseFileHasNotBeenRead,
  208. LicenseSignatureIsInvalid, InvalidLicenseFile, LicenseViolation {
  209. checkThatLicenseDOMisValid();
  210. // Check usage
  211. checkProductNameAndEdition(productName, productEdition);
  212. checkVersion(majorVersion, minorVersion);
  213. checkConcurrentUsers(concurrentUsers);
  214. checkApplicationClass(applicationClass);
  215. checkDate();
  216. }
  217. private void checkDate() throws LicenseViolation {
  218. NodeList vuL = licenseXML.getElementsByTagName("valid-until");
  219. if (vuL != null && vuL.getLength() > 0) {
  220. Element e = (Element) vuL.item(0);
  221. String year = e.getAttribute("year");
  222. String month = e.getAttribute("month");
  223. String day = e.getAttribute("day");
  224. Calendar cal = Calendar.getInstance();
  225. if ((year != null && year.length() > 0 && Integer.parseInt(year) < cal
  226. .get(Calendar.YEAR))
  227. || (month != null && month.length() > 0 && Integer
  228. .parseInt(month) < (1 + cal.get(Calendar.MONTH)))
  229. || (day != null && day.length() > 0 && Integer
  230. .parseInt(day) < cal.get(Calendar.DAY_OF_MONTH)))
  231. throw new LicenseViolation("The license is valid until " + year
  232. + "-" + month + "-" + day);
  233. }
  234. }
  235. private void checkApplicationClass(Class applicationClass)
  236. throws LicenseViolation {
  237. // check class
  238. NodeList appL = licenseXML.getElementsByTagName("application");
  239. if (appL != null && appL.getLength() > 0) {
  240. String classPrefix = ((Element) appL.item(0))
  241. .getAttribute("classPrefix");
  242. if (classPrefix != null && classPrefix.length() > 0
  243. && !applicationClass.getName().startsWith(classPrefix))
  244. throw new LicenseViolation(
  245. "License limits application class prefix to '"
  246. + classPrefix
  247. + "' but requested application class is '"
  248. + applicationClass.getName() + "'");
  249. }
  250. }
  251. private void checkConcurrentUsers(int concurrentUsers)
  252. throws TooManyConcurrentUsers {
  253. int max = getMaxConcurrentUsers();
  254. if (max >= 0 && concurrentUsers > max)
  255. throw new TooManyConcurrentUsers(
  256. "Currently "
  257. + concurrentUsers
  258. + " concurrent users are connected, while license sets limit to "
  259. + max);
  260. }
  261. private int getMaxConcurrentUsers() {
  262. NodeList cuL = licenseXML
  263. .getElementsByTagName("concurrent-users-per-server");
  264. if (cuL == null && cuL.getLength() == 0)
  265. return -1;
  266. String limit = ((Element) cuL.item(0)).getAttribute("limit");
  267. if (limit != null && limit.length() > 0
  268. && !limit.equalsIgnoreCase("unlimited"))
  269. return Integer.parseInt(limit);
  270. return -1;
  271. }
  272. private int getMaxJVMs() {
  273. NodeList cuL = licenseXML.getElementsByTagName("concurrent-jvms");
  274. if (cuL == null && cuL.getLength() == 0)
  275. return -1;
  276. Element e= (Element) cuL.item(0);
  277. String limit = e == null ? null : e.getAttribute("limit");
  278. if (limit != null && limit.length() > 0
  279. && !limit.equalsIgnoreCase("unlimited"))
  280. return Integer.parseInt(limit);
  281. return -1;
  282. }
  283. private void checkVersion(int majorVersion, int minorVersion)
  284. throws LicenseViolation {
  285. // check version
  286. NodeList verL = licenseXML.getElementsByTagName("version");
  287. if (verL != null && verL.getLength() > 0) {
  288. NodeList checks = verL.item(0).getChildNodes();
  289. for (int i = 0; i < checks.getLength(); i++) {
  290. Node n = checks.item(i);
  291. if (n.getNodeType() == Node.ELEMENT_NODE) {
  292. Element e = (Element) n;
  293. String tag = e.getTagName();
  294. String eq = e.getAttribute("equals-to");
  295. String eqol = e.getAttribute("equals-to-or-is-less-than");
  296. String eqom = e.getAttribute("equals-to-or-is-more-than");
  297. int value = -1;
  298. if ("major".equalsIgnoreCase(tag)) {
  299. value = majorVersion;
  300. } else if ("minor".equalsIgnoreCase(tag)) {
  301. value = minorVersion;
  302. }
  303. if (value >= 0) {
  304. if (eq != null && eq.length() > 0)
  305. if (value != Integer.parseInt(eq))
  306. throw new LicenseViolation("Product " + tag
  307. + " version is " + value
  308. + " but license requires it to be "
  309. + eq);
  310. if (eqol != null && eqol.length() > 0)
  311. if (value > Integer.parseInt(eqol))
  312. throw new LicenseViolation(
  313. "Product "
  314. + tag
  315. + " version is "
  316. + value
  317. + " but license requires it to be equal or less than"
  318. + eqol);
  319. if (eqom != null && eqom.length() > 0)
  320. if (value < Integer.parseInt(eqom))
  321. throw new LicenseViolation(
  322. "Product "
  323. + tag
  324. + " version is "
  325. + value
  326. + " but license requires it to be equal or more than"
  327. + eqom);
  328. }
  329. }
  330. }
  331. }
  332. }
  333. private String getVersionDescription() {
  334. StringBuffer v = new StringBuffer();
  335. NodeList verL = licenseXML.getElementsByTagName("version");
  336. if (verL != null && verL.getLength() > 0) {
  337. NodeList checks = verL.item(0).getChildNodes();
  338. for (int i = 0; i < checks.getLength(); i++) {
  339. Node n = checks.item(i);
  340. if (n.getNodeType() == Node.ELEMENT_NODE) {
  341. Element e = (Element) n;
  342. String tag = e.getTagName();
  343. appendVersionDescription(e.getAttribute("equals-to"),v,tag,"=");
  344. appendVersionDescription(e.getAttribute("equals-to-or-is-less-than"),v,tag,"<=");
  345. appendVersionDescription(e.getAttribute("equals-to-or-is-more-than"),v,tag,">=");
  346. }
  347. }
  348. }
  349. if (v.length() == 0) return null;
  350. return v.toString();
  351. }
  352. private void appendVersionDescription(String num, StringBuffer v, String tag, String relation) {
  353. if (num == null || num.length() == 0) return;
  354. if (v.length() > 0) v.append(" and ");
  355. v.append(tag + " version " + relation + " " + num);
  356. }
  357. private void checkProductNameAndEdition(String productName,
  358. String productEdition) throws InvalidLicenseFile, LicenseViolation {
  359. // Check product name
  360. if (productName == null || productName.length() == 0)
  361. throw new IllegalArgumentException(
  362. "productName must not be empty or null");
  363. if (productEdition != null && productEdition.length() == 0)
  364. throw new IllegalArgumentException(
  365. "productEdition must either be null (not present) or non-empty string");
  366. String name = getProductName();
  367. if (!name.equals(productName))
  368. throw new LicenseViolation("The license file is for product '"
  369. + name + "' but it was requested to be used with '"
  370. + productName + "'");
  371. // Check product edition
  372. String edition = getProductEdition();
  373. if (productEdition != null || edition != null)
  374. if (edition == null || !edition.equals(productEdition))
  375. throw new LicenseViolation("Requested edition '"
  376. + productEdition + "', but license-file is for '"
  377. + edition + "'");
  378. }
  379. private String getProductEdition() throws InvalidLicenseFile {
  380. Element prod = (Element) licenseXML.getElementsByTagName("product")
  381. .item(0);
  382. if (prod == null)
  383. throw new InvalidLicenseFile("product not found in license-file");
  384. NodeList editionE = (NodeList) prod.getElementsByTagName("edition");
  385. if (editionE == null || editionE.getLength() == 0)
  386. return null;
  387. return editionE.item(0).getTextContent();
  388. }
  389. private String getProductName() throws InvalidLicenseFile {
  390. Element prod = (Element) licenseXML.getElementsByTagName("product")
  391. .item(0);
  392. if (prod == null)
  393. throw new InvalidLicenseFile("product not found in license-file");
  394. String name = ((Element) prod.getElementsByTagName("name").item(0))
  395. .getTextContent();
  396. if (name == null || name.length() == 0)
  397. throw new InvalidLicenseFile(
  398. "product name not found in license-file");
  399. return name;
  400. }
  401. private String getLicenseeName() {
  402. NodeList licenseeL = licenseXML.getElementsByTagName("licensee");
  403. if (licenseeL == null || licenseeL.getLength() == 0)
  404. return null;
  405. NodeList nameL = ((Element) licenseeL.item(0))
  406. .getElementsByTagName("name");
  407. if (nameL == null || nameL.getLength() == 0)
  408. return null;
  409. String name = nameL.item(0).getTextContent();
  410. if (name == null || name.length() == 0)
  411. return null;
  412. return name;
  413. }
  414. private String getPurpose() {
  415. NodeList purposeL = licenseXML.getElementsByTagName("purpose");
  416. if (purposeL == null || purposeL.getLength() == 0)
  417. return null;
  418. return purposeL.item(0).getTextContent();
  419. }
  420. private String getLicenseNumber() throws InvalidLicenseFile {
  421. Element lic = (Element) licenseXML.getElementsByTagName("license")
  422. .item(0);
  423. if (lic == null)
  424. throw new InvalidLicenseFile(
  425. "license element not found in license-file");
  426. return lic.getAttribute("number");
  427. }
  428. private String getNormalizedLisenceData() throws InvalidLicenseFile,
  429. LicenseFileHasNotBeenRead {
  430. // License must be read before
  431. if (licenseXML == null)
  432. throw new LicenseFileHasNotBeenRead();
  433. // Initialize result
  434. CharArrayWriter sink = new CharArrayWriter();
  435. // Serialize document to sink
  436. try {
  437. serialize(licenseXML, sink);
  438. } catch (IOException e) {
  439. throw new InvalidLicenseFile("Can not serialize the license file.");
  440. }
  441. return new String(sink.toCharArray());
  442. }
  443. private static void serialize(Node node, Writer sink) throws IOException {
  444. // Do not serialize comments and processing instructions
  445. if (node.getNodeType() == Node.COMMENT_NODE
  446. || node.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE)
  447. return;
  448. // Do not serialize whitespace text-nodes
  449. if (node.getNodeType() == Node.TEXT_NODE) {
  450. String value = node.getNodeValue();
  451. if (value.matches("^\\s*$"))
  452. return;
  453. }
  454. // Do not serialize signature
  455. if (node.getNodeType() == Node.ELEMENT_NODE
  456. && "signature".equals(node.getNodeName()))
  457. return;
  458. // Serialize node name
  459. sink.write(node.getNodeName().toLowerCase());
  460. // Serialize value of the node
  461. String value = node.getNodeValue();
  462. if (value != null)
  463. sink.write("='" + value + "'");
  464. // Serialize attributes if it has any, in sorted order
  465. NamedNodeMap attrs = node.getAttributes();
  466. if (attrs != null) {
  467. TreeSet names = new TreeSet();
  468. for (int i = 0; i < attrs.getLength(); i++)
  469. names.add(attrs.item(i).getNodeName());
  470. for (Iterator i = names.iterator(); i.hasNext();)
  471. serialize(attrs.getNamedItem((String) i.next()), sink);
  472. }
  473. // Serialize child nodes (other than attributes)
  474. Node child = node.getFirstChild();
  475. if (child != null && node.getNodeType() != Node.ATTRIBUTE_NODE) {
  476. sink.write("{");
  477. while (child != null) {
  478. if (child.getNodeType() != Node.ATTRIBUTE_NODE)
  479. serialize(child, sink);
  480. child = child.getNextSibling();
  481. }
  482. sink.write("}");
  483. }
  484. }
  485. private byte[] getSignature() throws InvalidLicenseFile {
  486. if (licenseXML == null)
  487. return null;
  488. // Get the base64 encoded signature from license-file
  489. NodeList nl = licenseXML.getElementsByTagName("signature");
  490. if (nl == null || nl.getLength() != 1)
  491. throw new InvalidLicenseFile("Signature element not found");
  492. Node text = nl.item(0).getFirstChild();
  493. if (text == null || text.getNodeType() != Node.TEXT_NODE)
  494. throw new InvalidLicenseFile("Invalid signature element");
  495. String base64 = text.getNodeValue();
  496. return base64_decode(base64);
  497. }
  498. private boolean isSignatureValid() throws InvalidLicenseFile,
  499. LicenseFileHasNotBeenRead, LicenseSignatureIsInvalid {
  500. if (signatureIsValid)
  501. return true;
  502. try {
  503. // Get X.509 factory implementation
  504. CertificateFactory x509factory = CertificateFactory
  505. .getInstance("X.509");
  506. // Decode statically linked X.509 certificate
  507. X509Certificate cert = (X509Certificate) x509factory
  508. .generateCertificate(new ByteArrayInputStream(certificate
  509. .getBytes()));
  510. PublicKey publicKey = cert.getPublicKey();
  511. // Verify signature with DSA
  512. Signature dsa = Signature.getInstance("SHA1withDSA");
  513. dsa.initVerify(publicKey);
  514. dsa.update(getNormalizedLisenceData().getBytes("UTF-8"));
  515. if (dsa.verify(getSignature())) {
  516. signatureIsValid = true;
  517. return true;
  518. }
  519. } catch (NoSuchAlgorithmException e) {
  520. throw new RuntimeException(e);
  521. } catch (InvalidKeyException e) {
  522. throw new RuntimeException(e);
  523. } catch (SignatureException e) {
  524. throw new LicenseSignatureIsInvalid();
  525. } catch (UnsupportedEncodingException e) {
  526. throw new RuntimeException(e);
  527. } catch (CertificateException e) {
  528. throw new RuntimeException(e);
  529. }
  530. // Verification failed
  531. return false;
  532. }
  533. public class LicenseViolation extends Exception {
  534. public LicenseViolation(String msg) {
  535. super(msg);
  536. }
  537. }
  538. public class LicenseFileHasAlreadyBeenRead extends Exception {
  539. }
  540. public class LicenseFileHasNotBeenRead extends Exception {
  541. }
  542. public class LicenseSignatureIsInvalid extends Exception {
  543. }
  544. public class TooManyConcurrentUsers extends LicenseViolation {
  545. public TooManyConcurrentUsers(String msg) {
  546. super(msg);
  547. }
  548. }
  549. public class LicenseHasExpired extends LicenseViolation {
  550. public LicenseHasExpired(String msg) {
  551. super(msg);
  552. }
  553. }
  554. public class ApplicationClassNameDoesNotMatch extends LicenseViolation {
  555. public ApplicationClassNameDoesNotMatch(String msg) {
  556. super(msg);
  557. }
  558. }
  559. public class InvalidLicenseFile extends Exception {
  560. InvalidLicenseFile(String message) {
  561. super(message);
  562. }
  563. }
  564. public class LicenseFileCanNotBeRead extends Exception {
  565. LicenseFileCanNotBeRead(String message) {
  566. super(message);
  567. }
  568. }
  569. /* ****** BASE64 implementation created by Robert Harder ****** */
  570. /** The equals sign (=) as a byte. */
  571. private final static byte Base64_EQUALS_SIGN = (byte) '=';
  572. /** Preferred encoding. */
  573. private final static String Base64_PREFERRED_ENCODING = "UTF-8";
  574. /**
  575. * Translates a Base64 value to either its 6-bit reconstruction value or a
  576. * negative number indicating some other meaning.
  577. */
  578. private final static byte[] Base64_DECODABET = { -9, -9, -9, -9, -9, -9,
  579. -9, -9, -9, // Decimal 0 - 8
  580. -5, -5, // Whitespace: Tab and Linefeed
  581. -9, -9, // Decimal 11 - 12
  582. -5, // Whitespace: Carriage Return
  583. -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 -
  584. // 26
  585. -9, -9, -9, -9, -9, // Decimal 27 - 31
  586. -5, // Whitespace: Space
  587. -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42
  588. 62, // Plus sign at decimal 43
  589. -9, -9, -9, // Decimal 44 - 46
  590. 63, // Slash at decimal 47
  591. 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine
  592. -9, -9, -9, // Decimal 58 - 60
  593. -1, // Equals sign at decimal 61
  594. -9, -9, -9, // Decimal 62 - 64
  595. 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A'
  596. // through 'N'
  597. 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O'
  598. // through 'Z'
  599. -9, -9, -9, -9, -9, -9, // Decimal 91 - 96
  600. 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a'
  601. // through 'm'
  602. 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n'
  603. // through 'z'
  604. -9, -9, -9, -9 // Decimal 123 - 126
  605. };
  606. // I think I end up not using the BAD_ENCODING indicator.
  607. // private final static byte BAD_ENCODING = -9; // Indicates error in
  608. // encoding
  609. private final static byte Base64_WHITE_SPACE_ENC = -5; // Indicates white
  610. // space in encoding
  611. private final static byte Base64_EQUALS_SIGN_ENC = -1; // Indicates equals
  612. // sign in encoding
  613. /**
  614. * Decodes four bytes from array <var>source</var> and writes the resulting
  615. * bytes (up to three of them) to <var>destination</var>. The source and
  616. * destination arrays can be manipulated anywhere along their length by
  617. * specifying <var>srcOffset</var> and <var>destOffset</var>. This method
  618. * does not check to make sure your arrays are large enough to accomodate
  619. * <var>srcOffset</var> + 4 for the <var>source</var> array or
  620. * <var>destOffset</var> + 3 for the <var>destination</var> array. This
  621. * method returns the actual number of bytes that were converted from the
  622. * Base64 encoding.
  623. *
  624. *
  625. * @param source
  626. * the array to convert
  627. * @param srcOffset
  628. * the index where conversion begins
  629. * @param destination
  630. * the array to hold the conversion
  631. * @param destOffset
  632. * the index where output will be put
  633. * @return the number of decoded bytes converted
  634. * @since 1.3
  635. */
  636. private static int base64_decode4to3(byte[] source, int srcOffset,
  637. byte[] destination, int destOffset) {
  638. // Example: Dk==
  639. if (source[srcOffset + 2] == Base64_EQUALS_SIGN) {
  640. // Two ways to do the same thing. Don't know which way I like best.
  641. // int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6
  642. // )
  643. // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 );
  644. int outBuff = ((Base64_DECODABET[source[srcOffset]] & 0xFF) << 18)
  645. | ((Base64_DECODABET[source[srcOffset + 1]] & 0xFF) << 12);
  646. destination[destOffset] = (byte) (outBuff >>> 16);
  647. return 1;
  648. }
  649. // Example: DkL=
  650. else if (source[srcOffset + 3] == Base64_EQUALS_SIGN) {
  651. // Two ways to do the same thing. Don't know which way I like best.
  652. // int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6
  653. // )
  654. // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
  655. // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 );
  656. int outBuff = ((Base64_DECODABET[source[srcOffset]] & 0xFF) << 18)
  657. | ((Base64_DECODABET[source[srcOffset + 1]] & 0xFF) << 12)
  658. | ((Base64_DECODABET[source[srcOffset + 2]] & 0xFF) << 6);
  659. destination[destOffset] = (byte) (outBuff >>> 16);
  660. destination[destOffset + 1] = (byte) (outBuff >>> 8);
  661. return 2;
  662. }
  663. // Example: DkLE
  664. else {
  665. try {
  666. // Two ways to do the same thing. Don't know which way I like
  667. // best.
  668. // int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 )
  669. // >>> 6 )
  670. // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
  671. // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 )
  672. // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 );
  673. int outBuff = ((Base64_DECODABET[source[srcOffset]] & 0xFF) << 18)
  674. | ((Base64_DECODABET[source[srcOffset + 1]] & 0xFF) << 12)
  675. | ((Base64_DECODABET[source[srcOffset + 2]] & 0xFF) << 6)
  676. | ((Base64_DECODABET[source[srcOffset + 3]] & 0xFF));
  677. destination[destOffset] = (byte) (outBuff >> 16);
  678. destination[destOffset + 1] = (byte) (outBuff >> 8);
  679. destination[destOffset + 2] = (byte) (outBuff);
  680. return 3;
  681. } catch (Exception e) {
  682. System.out.println("" + source[srcOffset] + ": "
  683. + (Base64_DECODABET[source[srcOffset]]));
  684. System.out.println("" + source[srcOffset + 1] + ": "
  685. + (Base64_DECODABET[source[srcOffset + 1]]));
  686. System.out.println("" + source[srcOffset + 2] + ": "
  687. + (Base64_DECODABET[source[srcOffset + 2]]));
  688. System.out.println("" + source[srcOffset + 3] + ": "
  689. + (Base64_DECODABET[source[srcOffset + 3]]));
  690. return -1;
  691. } // e nd catch
  692. }
  693. } // end decodeToBytes
  694. /**
  695. * Very low-level access to decoding ASCII characters in the form of a byte
  696. * array. Does not support automatically gunzipping or any other "fancy"
  697. * features.
  698. *
  699. * @param source
  700. * The Base64 encoded data
  701. * @param off
  702. * The offset of where to begin decoding
  703. * @param len
  704. * The length of characters to decode
  705. * @return decoded data
  706. * @since 1.3
  707. */
  708. private static byte[] base64_decode(byte[] source, int off, int len) {
  709. int len34 = len * 3 / 4;
  710. byte[] outBuff = new byte[len34]; // Upper limit on size of output
  711. int outBuffPosn = 0;
  712. byte[] b4 = new byte[4];
  713. int b4Posn = 0;
  714. int i = 0;
  715. byte sbiCrop = 0;
  716. byte sbiDecode = 0;
  717. for (i = off; i < off + len; i++) {
  718. sbiCrop = (byte) (source[i] & 0x7f); // Only the low seven bits
  719. sbiDecode = Base64_DECODABET[sbiCrop];
  720. if (sbiDecode >= Base64_WHITE_SPACE_ENC) // White space, Equals
  721. // sign or better
  722. {
  723. if (sbiDecode >= Base64_EQUALS_SIGN_ENC) {
  724. b4[b4Posn++] = sbiCrop;
  725. if (b4Posn > 3) {
  726. outBuffPosn += base64_decode4to3(b4, 0, outBuff,
  727. outBuffPosn);
  728. b4Posn = 0;
  729. // If that was the equals sign, break out of 'for' loop
  730. if (sbiCrop == Base64_EQUALS_SIGN)
  731. break;
  732. } // end if: quartet built
  733. } // end if: equals sign or better
  734. } // end if: white space, equals sign or better
  735. else {
  736. System.err.println("Bad Base64 input character at " + i + ": "
  737. + source[i] + "(decimal)");
  738. return null;
  739. } // end else:
  740. } // each input character
  741. byte[] out = new byte[outBuffPosn];
  742. System.arraycopy(outBuff, 0, out, 0, outBuffPosn);
  743. return out;
  744. } // end decode
  745. /**
  746. * Decodes data from Base64 notation, automatically detecting
  747. * gzip-compressed data and decompressing it.
  748. *
  749. * @param s
  750. * the string to decode
  751. * @return the decoded data
  752. * @since 1.4
  753. */
  754. private static byte[] base64_decode(String s) {
  755. byte[] bytes;
  756. try {
  757. bytes = s.getBytes(Base64_PREFERRED_ENCODING);
  758. } // end try
  759. catch (java.io.UnsupportedEncodingException uee) {
  760. bytes = s.getBytes();
  761. } // end catch
  762. // </change>
  763. // Decode
  764. bytes = base64_decode(bytes, 0, bytes.length);
  765. // Check to see if it's gzip-compressed
  766. // GZIP Magic Two-Byte Number: 0x8b1f (35615)
  767. if (bytes != null && bytes.length >= 4) {
  768. int head = ((int) bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00);
  769. if (java.util.zip.GZIPInputStream.GZIP_MAGIC == head) {
  770. java.io.ByteArrayInputStream bais = null;
  771. java.util.zip.GZIPInputStream gzis = null;
  772. java.io.ByteArrayOutputStream baos = null;
  773. byte[] buffer = new byte[2048];
  774. int length = 0;
  775. try {
  776. baos = new java.io.ByteArrayOutputStream();
  777. bais = new java.io.ByteArrayInputStream(bytes);
  778. gzis = new java.util.zip.GZIPInputStream(bais);
  779. while ((length = gzis.read(buffer)) >= 0) {
  780. baos.write(buffer, 0, length);
  781. } // end while: reading input
  782. // No error? Get new bytes.
  783. bytes = baos.toByteArray();
  784. } // end try
  785. catch (java.io.IOException e) {
  786. // Just return originally-decoded bytes
  787. } // end catch
  788. finally {
  789. try {
  790. baos.close();
  791. } catch (Exception e) {
  792. }
  793. try {
  794. gzis.close();
  795. } catch (Exception e) {
  796. }
  797. try {
  798. bais.close();
  799. } catch (Exception e) {
  800. }
  801. } // end finally
  802. } // end if: gzipped
  803. } // end if: bytes.length >= 2
  804. return bytes;
  805. } // end decode
  806. }