Added the licensing package and implemented the license manager.
authorMarco Zanon <info@marcozanon.com>
Thu, 29 Feb 2024 17:51:41 +0000 (17:51 +0000)
committerMarco Zanon <info@marcozanon.com>
Thu, 29 Feb 2024 17:51:41 +0000 (17:51 +0000)
9.x/CHANGELOG
9.x/src/main/java/com/marcozanon/macaco/licensing/MLicenseManager.java [new file with mode: 0644]
9.x/src/main/java/com/marcozanon/macaco/licensing/MLicensingException.java [new file with mode: 0644]

index 01b6c911d848434c8c097f07939c88278bc52c34..9ff8ba1a30cc293cc85f051e8e90d0548df3537f 100644 (file)
@@ -2,6 +2,11 @@ Macaco
 Copyright (c) 2009-2024 Marco Zanon <info@marcozanon.com>.
 See LICENSE for details.
 
+------------------
+9.2.0 (2024-02-29)
+------------------
+* Added the licensing package and implemented the license manager.
+
 ------------------
 9.1.0 (2024-02-29)
 ------------------
diff --git a/9.x/src/main/java/com/marcozanon/macaco/licensing/MLicenseManager.java b/9.x/src/main/java/com/marcozanon/macaco/licensing/MLicenseManager.java
new file mode 100644 (file)
index 0000000..f90fe07
--- /dev/null
@@ -0,0 +1,135 @@
+/**
+ * Macaco
+ * Copyright (c) 2009-2024 Marco Zanon <info@marcozanon.com>.
+ * See LICENSE for details.
+ */
+
+package com.marcozanon.macaco.licensing;
+
+import com.marcozanon.macaco.MConstants;
+import com.marcozanon.macaco.json.MInvalidJsonValueException;
+import com.marcozanon.macaco.json.MJsonObject;
+import com.marcozanon.macaco.json.MJsonString;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Base64;
+
+public class MLicenseManager {
+
+    /* License generation. */
+
+    public static void generateFullLicense(Path privateKeyFile, Path licenseSkeletonFile, Path fullLicenseFile) throws MLicensingException {
+        try {
+            String privateKeyPemString = new String(Files.readAllBytes(privateKeyFile), MConstants.DEFAULT_CHARSET);
+            String licenseSkeletonJsonString = new String(Files.readAllBytes(licenseSkeletonFile), MConstants.DEFAULT_CHARSET);
+            //
+            MJsonObject fullLicense = MLicenseManager.generateFullLicense(privateKeyPemString, licenseSkeletonJsonString);
+            //
+            Files.write(fullLicenseFile, fullLicense.getJsonValue(true).getBytes(MConstants.DEFAULT_CHARSET), StandardOpenOption.CREATE);
+        }
+        catch (IOException exception) {
+            throw new MLicensingException("Could not generate full license.", exception);
+        }
+    }
+
+    public static MJsonObject generateFullLicense(String privateKeyPemString, String licenseSkeletonJsonString) throws MLicensingException {
+        try {
+            byte[] privateKeyContent = Base64.getDecoder().decode(privateKeyPemString.replace("-----BEGIN PRIVATE KEY-----", "").replaceAll("\\R", "").replace("-----END PRIVATE KEY-----", ""));
+            RSAPrivateKey privateKey = (RSAPrivateKey)KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(privateKeyContent));
+            //
+            MJsonObject licenseSkeleton = new MJsonObject(licenseSkeletonJsonString);
+            //
+            MJsonObject licenseData = (MJsonObject)licenseSkeleton.getValue("licenseData");
+            byte[] licenseDataHashContent = MessageDigest.getInstance("SHA-256").digest(licenseData.getJsonValue().getBytes(MConstants.DEFAULT_CHARSET));
+            //
+            Signature signature = Signature.getInstance("SHA256withRSA");
+            signature.initSign​(privateKey);
+            signature.update(licenseDataHashContent);
+            byte[] licenseDataHashSignatureContent = signature.sign();
+            //
+            licenseSkeleton.setValue("licenseDataSignature", new MJsonString("\"" + Base64.getEncoder().encodeToString(licenseDataHashSignatureContent) + "\""));
+            //
+            return licenseSkeleton;
+        }
+        catch (InvalidKeyException exception) {
+            throw new MLicensingException("Could not generate full license.", exception);
+        }
+        catch (InvalidKeySpecException exception) {
+            throw new MLicensingException("Could not generate full license.", exception);
+        }
+        catch (MInvalidJsonValueException exception) {
+            throw new MLicensingException("Could not generate full license.", exception);
+        }
+        catch (NoSuchAlgorithmException exception) {
+            throw new MLicensingException("Could not generate full license.", exception);
+        }
+        catch (SignatureException exception) {
+            throw new MLicensingException("Could not generate full license.", exception);
+        }
+    }
+
+    /* License verification. */
+
+    public static MJsonObject verifyFullLicense(Path publicKeyFile, Path fullLicenseFile) throws MLicensingException {
+        try {
+            String publicKeyPemString = new String(Files.readAllBytes(publicKeyFile), MConstants.DEFAULT_CHARSET);
+            String fullLicenseJsonString = new String(Files.readAllBytes(fullLicenseFile), MConstants.DEFAULT_CHARSET);
+            //
+            return MLicenseManager.verifyFullLicense(publicKeyPemString, fullLicenseJsonString);
+        }
+        catch (IOException exception) {
+            throw new MLicensingException("Could not verify full license.", exception);
+        }
+    }
+
+    public static MJsonObject verifyFullLicense(String publicKeyPemString, String fullLicenseJsonString) throws MLicensingException {
+        try {
+            byte[] publicKeyContent = Base64.getDecoder().decode(publicKeyPemString.replace("-----BEGIN PUBLIC KEY-----", "").replaceAll("\\R", "").replace("-----END PUBLIC KEY-----", ""));
+            RSAPublicKey publicKey = (RSAPublicKey)KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(publicKeyContent));
+            //
+            MJsonObject fullLicense = new MJsonObject(fullLicenseJsonString);
+            //
+            MJsonObject licenseData = (MJsonObject)fullLicense.getValue("licenseData");
+            byte[] licenseDataHashContent = MessageDigest.getInstance("SHA-256").digest(licenseData.getJsonValue().getBytes(MConstants.DEFAULT_CHARSET));
+            //
+            MJsonString licenseDataSignature = (MJsonString)fullLicense.getValue("licenseDataSignature");
+            byte[] licenseDataSignatureContent = Base64.getDecoder().decode(licenseDataSignature.getValue());
+            //
+            Signature signature = Signature.getInstance("SHA256withRSA");
+            signature.initVerify​(publicKey);
+            signature.update(licenseDataHashContent);
+            signature.verify(licenseDataSignatureContent);
+            //
+            return fullLicense;
+        }
+        catch (InvalidKeyException exception) {
+            throw new MLicensingException("Could not verify full license.", exception);
+        }
+        catch (InvalidKeySpecException exception) {
+            throw new MLicensingException("Could not verify full license.", exception);
+        }
+        catch (MInvalidJsonValueException exception) {
+            throw new MLicensingException("Could not verify full license.", exception);
+        }
+        catch (NoSuchAlgorithmException exception) {
+            throw new MLicensingException("Could not verify full license.", exception);
+        }
+        catch (SignatureException exception) {
+            throw new MLicensingException("Could not verify full license.", exception);
+        }
+    }
+
+}
diff --git a/9.x/src/main/java/com/marcozanon/macaco/licensing/MLicensingException.java b/9.x/src/main/java/com/marcozanon/macaco/licensing/MLicensingException.java
new file mode 100644 (file)
index 0000000..c5ac150
--- /dev/null
@@ -0,0 +1,32 @@
+/**
+ * Macaco
+ * Copyright (c) 2009-2024 Marco Zanon <info@marcozanon.com>.
+ * See LICENSE for details.
+ */
+
+package com.marcozanon.macaco.licensing;
+
+import com.marcozanon.macaco.MException;
+
+@SuppressWarnings("serial")
+public class MLicensingException extends MException {
+
+    /* */
+
+    public MLicensingException() {
+        super();
+    }
+
+    public MLicensingException(String message) {
+        super(message);
+    }
+
+    public MLicensingException(Throwable error) {
+        super(error);
+    }
+
+    public MLicensingException(String message, Throwable error) {
+        super(message, error);
+    }
+
+}