Skip to content
Snippets Groups Projects
Commit 73ebf114 authored by Felix Sidokhine's avatar Felix Sidokhine
Browse files

cleaned up the whole licensing thing

parent 02b75875
No related branches found
No related tags found
No related merge requests found
Showing
with 193 additions and 156 deletions
......@@ -75,10 +75,12 @@ module jams.common {
exports net.jami.jams.common.objects.responses;
exports net.jami.jams.common.cryptoengineapi.ocsp;
exports net.jami.jams.common.updater;
exports net.jami.jams.common.updater.subscription;
requires jdk.crypto.cryptoki;
requires java.base;
requires java.sql;
requires org.apache.xbean.classloader;
requires jeromq;
requires java.naming;
}
......@@ -27,11 +27,9 @@ import net.jami.jams.common.authentication.AuthenticationSourceType;
import net.jami.jams.common.jami.NameServer;
import net.jami.jams.common.objects.user.User;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPublicKey;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
public interface AuthenticationModule {
......
package net.jami.jams.common.objects.responses;
import lombok.Getter;
import lombok.Setter;
import net.jami.jams.common.updater.subscription.LicenseInformation;
import net.jami.jams.common.updater.subscription.SubscriptionType;
import java.util.HashMap;
import static net.jami.jams.common.utils.LicenseUtils.checkVersion;
@Getter
@Setter
public class SubscriptionStatusResponse {
private LicenseInformation licenseInformation;
private Boolean activated;
private HashMap<String,String> versions = checkVersion();
}
package net.jami.jams.common.updater.subscription;
import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
@Getter
@Setter
public class LicenseInformation {
private SubscriptionType type = SubscriptionType.COMMUNITY;
private List<String> products = new ArrayList<>();
}
package net.jami.jams.common.updater.subscription;
public enum SubscriptionType {
COMMUNITY
COMMUNITY,
PREMIUM
}
package net.jami.jams.common.utils;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
@Slf4j
public class LicenseUtils {
private static final String IMPLEMENTATION_VERSION_FIELD = "Implementation-Version";
public static String checkVersion(String destinationDir, String jarPath) throws IOException {
String resp = "";
File file = new File(jarPath);
JarFile jar = new JarFile(file);
Manifest manifest = jar.getManifest();
resp = manifest.getMainAttributes().getValue("Implementation-Version");
System.out.println("Found version: " + resp);
return resp;
public static HashMap<String,String> checkVersion() {
try {
HashMap<String,String> res = new HashMap<>();
ArrayList<Path> files = new ArrayList<>();
Files.walk(Paths.get(System.getProperty("user.dir"))).filter(Files::isRegularFile).forEach(files::add);
files.forEach( e ->{
if(e.endsWith(".jar")){
try {
String version = new JarFile(e.toFile()).getManifest().getMainAttributes().getValue(IMPLEMENTATION_VERSION_FIELD);
res.put(e.toString(),version);
log.info("Found version {} of {}",e.toString(),version);
}
catch (Exception e1){
log.error("Could detect version for file with error {}",e1.getMessage());
}
}
} );
return res;
}
catch (Exception e){
log.error("An error has occurred while trying to list file versions {}",e.getMessage());
return null;
}
}
}
/*
* Copyright (C) 2020 by Savoir-faire Linux
* Authors: William Enright <william.enright@savoirfairelinux.com>
* Ndeye Anna Ndiaye <anna.ndiaye@savoirfairelinux.com>
* Johnny Flores <johnny.flores@savoirfairelinux.com>
* Mohammed Raza <mohammed.raza@savoirfairelinux.com>
* Felix Sidokhine <felix.sidokhine@savoirfairelinux.com>
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
* Copyright (C) 2020 by Savoir-faire Linux
* Authors: William Enright <william.enright@savoirfairelinux.com>
* Ndeye Anna Ndiaye <anna.ndiaye@savoirfairelinux.com>
* Johnny Flores <johnny.flores@savoirfairelinux.com>
* Mohammed Raza <mohammed.raza@savoirfairelinux.com>
* Felix Sidokhine <felix.sidokhine@savoirfairelinux.com>
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.jami.jams.common.utils;
import com.jsoniter.JsonIterator;
import lombok.extern.slf4j.Slf4j;
import net.jami.jams.common.updater.subscription.LicenseInformation;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.openssl.PEMKeyPair;
......@@ -30,6 +32,8 @@ import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import javax.naming.ldap.LdapName;
import javax.naming.ldap.Rdn;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
......@@ -39,6 +43,7 @@ import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.Vector;
@Slf4j
public class X509Utils {
......@@ -68,7 +73,7 @@ public class X509Utils {
}
}
public static PublicKey getPubKeyFromPEMString(String keyString){
public static PublicKey getPubKeyFromPEMString(String keyString) {
try {
PEMParser parser = new PEMParser(new StringReader(keyString));
Object parsedObject = parser.readObject();
......@@ -80,8 +85,7 @@ public class X509Utils {
JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
return converter.getPublicKey((SubjectPublicKeyInfo) parsedObject);
}
}
catch (Exception e){
} catch (Exception e) {
log.error("And error has occurred reading the public key from string!");
return null;
}
......@@ -125,7 +129,7 @@ public class X509Utils {
}
}
public static String getPEMStringFromPubKey(PublicKey publicKey){
public static String getPEMStringFromPubKey(PublicKey publicKey) {
StringBuilder stringBuilder = new StringBuilder();
try {
stringBuilder.append(PPK_HEADER);
......@@ -152,4 +156,33 @@ public class X509Utils {
}
}
public static Vector<Object> loadLicenseFromDatFile(String fileContents) {
Vector<Object> res = new Vector<>();
String keypair = new String(Base64.getDecoder().decode(fileContents));
int cutPoint = keypair.indexOf("-----BEGIN PRIVATE KEY-----");
String strCertificate = keypair.substring(0, cutPoint);
String strPrivateKey = keypair.substring(cutPoint);
res.add(getCertificateFromPEMString(strCertificate));
res.add(getKeyFromPEMString(strPrivateKey));
return res;
}
public static LicenseInformation extractSubscriptionTypeFromCertificate(X509Certificate certificate) {
try {
LdapName ln = new LdapName(certificate.getSubjectDN().toString());
for (Rdn rdn : ln.getRdns()) {
try {
return JsonIterator.deserialize(Base64.getDecoder().decode(((String) rdn.getValue()).getBytes()),
LicenseInformation.class);
} catch (IllegalArgumentException e) {
//Do nothing here, because this is not critical and thit wi
}
}
return null;
} catch (Exception e) {
return null;
}
}
}
package net.jami.jams.common.utils;
import org.junit.jupiter.api.Test;
import java.util.HashMap;
class LicenseUtilsTest {
@Test
public void testFileScan(){
HashMap<String,String> res = LicenseUtils.checkVersion();
System.out.println(res);
}
}
\ No newline at end of file
......@@ -56,7 +56,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.logging.Logger;
@Getter
@Setter
......
......@@ -52,7 +52,6 @@ module jams.server {
exports net.jami.jams.server.servlets.api.install to org.apache.tomcat.embed.core;
exports net.jami.jams.server.servlets.general to org.apache.tomcat.embed.core;
exports net.jami.jams.server.servlets.api.jaminameserver to org.apache.tomcat.embed.core;
exports net.jami.jams.server.servlets.x509 to org.apache.tomcat.embed.core;
......
......@@ -38,6 +38,7 @@ import net.jami.jams.common.utils.UpdateInterface;
import net.jami.jams.nameserver.LocalNameServer;
import net.jami.jams.nameserver.PublicNameServer;
import net.jami.jams.server.core.TomcatLauncher;
import net.jami.jams.server.licensing.LicenseService;
import net.jami.jams.server.startup.AuthModuleLoader;
import net.jami.jams.server.startup.CryptoEngineLoader;
import net.jami.jams.server.startup.UpdaterLoader;
......@@ -65,6 +66,7 @@ public class Server {
public static NameServer nameServer;
private static TomcatLauncher tomcatLauncher = null;
public static final UpdateInterface updateInterface = new UpdateInterface();
public static final LicenseService licenseService = null;
public static void main(String[] args) {
//Start tomcat.
......@@ -106,6 +108,7 @@ public class Server {
}
else nameServer = new LocalNameServer(dataStore,userAuthenticationModule,serverSettings.getServerPublicURI());
appUpdater = UpdaterLoader.loadUpdater();
licenseService.loadLicense();
log.info("All services are UP and ready for use...");
}
catch (Exception e){
......
......@@ -3,88 +3,62 @@ package net.jami.jams.server.licensing;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import net.jami.jams.common.updater.subscription.LicenseInformation;
import net.jami.jams.common.utils.X509Utils;
import net.jami.jams.server.Server;
import org.json.JSONObject;
import javax.naming.ldap.LdapName;
import javax.naming.ldap.Rdn;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.PrivateKey;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
import java.util.Base64;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.Vector;
@Getter
@Setter
@Slf4j
public class LicenseService {
private AtomicBoolean activationStatus = new AtomicBoolean(false);
private String licenseType = "COMMUNITY";
private X509Certificate certificate;
private PrivateKey privateKey;
private X509Certificate caCertificate;
private LicenseInformation licenseInformation = new LicenseInformation();
//Load the license.
public void loadLicense() {
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
//Assuming the file exists, we just read the file.
String b64License = new String(Files.readAllBytes(Path.of(System.getProperty("user.dir") + File.separator + "license.dat")));
//Since this is base64, we need to decode it.
String strLicense = new String(Base64.getDecoder().decode(b64License));
//Now we need to split it. This is actually easy.
int cutPoint = strLicense.indexOf("-----BEGIN PRIVATE KEY-----");
String publicKey = strLicense.substring(0,cutPoint);
InputStream inputStream = new ByteArrayInputStream(publicKey.getBytes(StandardCharsets.UTF_8));
Certificate c = cf.generateCertificate(inputStream);
String dn = ((X509Certificate)c).getSubjectDN().toString();
LdapName ln = new LdapName(dn);
byte[] array = null;
for(Rdn rdn : ln.getRdns()) {
try {
array = Base64.getDecoder().decode(((String)rdn.getValue()).getBytes());
} catch (IllegalArgumentException e) {
}
Vector<Object> v = X509Utils.loadLicenseFromDatFile(
new String(Files.readAllBytes(Path.of(System.getProperty("user.dir") + File.separator + "license.dat")))
);
certificate = (X509Certificate) v.get(0);
privateKey = (PrivateKey) v.get(1);
caCertificate = X509Utils.getCertificateFromPEMString(
new String(LicenseService.class.getClassLoader().getResourceAsStream("oem/ca.crt").readAllBytes())
);
//Check the license for validity.
try {
certificate.checkValidity();
}
//This is kept inside the resources/oem folder, this is a lot less violent than what we had before.
//TODO: You should re-use the same technique to validate whatever they stick in the textbox.
InputStream input = LicenseService.class.getClassLoader().getResourceAsStream("oem/ca.crt");
if (input == null) {
System.out.println("No CA Found... this is critical!");
System.exit(-1);
catch (CertificateExpiredException | CertificateNotYetValidException c2){
log.error("Your license is not yet valid or has expired!");
Server.activated.set(false);
}
Certificate ca = cf.generateCertificate(input);
try{
((X509Certificate) c).checkValidity();
c.verify(ca.getPublicKey());
Server.setActivated(true);
if (array != null) {
JSONObject jsonObject = new JSONObject(new String(array));
licenseType = (String) jsonObject.get("type");
}
try {
certificate.verify(caCertificate.getPublicKey());
}
catch (Exception e){
Server.setActivated(false);
licenseType = "COMMUNITY";
log.warn("Your license is no longer valid or has been tampered with - " + e.toString());
catch (Exception e) {
log.error("The license file you have provided could not be verified!");
Server.activated.set(false);
}
//If all these checks have passed then:
Server.activated.set(true);
licenseInformation = X509Utils.extractSubscriptionTypeFromCertificate(certificate);
}
catch (Exception e){
log.error("A generic occurred while trying to load your license or your license could not be found");
Server.activated.set(false);
licenseType = "COMMUNITY";
//logger.warning("An exception occurred while checking your license: " + e.toString());
}
}
public String getLicenseType() {
loadLicense();
return licenseType;
}
}
package net.jami.jams.server.servlets.api.update;
import com.jsoniter.output.JsonStream;
import net.jami.jams.ca.JamsCA;
import net.jami.jams.common.cryptoengineapi.CertificateAuthority;
import net.jami.jams.common.utils.LicenseUtils;
import net.jami.jams.server.Server;
import net.jami.jams.server.licensing.LicenseService;
import net.jami.jams.server.startup.CryptoEngineLoader;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
@WebServlet("/api/auth/license")
public class LicenseServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
resp.setHeader("Access-Control-Allow-Origin", JamsCA.serverDomain);
resp.setContentType("application/json");
try {
resp.setStatus(200);
HashMap<String,Object> payload = new HashMap<>();
payload.put("isActive", Server.isActivated());
payload.put("licenseType", new LicenseService().getLicenseType());
payload.put("currentVersion", LicenseUtils.checkVersion(System.getProperty("user.dir") + "/tmpjar/", System.getProperty("user.dir") + "/jams-server.jar"));
resp.getOutputStream().write(JsonStream.serialize(payload).getBytes());
}
catch (Exception e){
resp.setStatus(403);
}
}
}
\ No newline at end of file
package net.jami.jams.server.servlets.api.update;
import lombok.extern.slf4j.Slf4j;
import net.jami.jams.ca.JamsCA;
import net.jami.jams.server.Server;
......@@ -7,12 +8,11 @@ import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.logging.Logger;
@WebServlet("/api/startupdate")
@Slf4j
public class StartUpdateServlet extends HttpServlet {
private final static Logger logger = Logger.getLogger(StartUpdateServlet.class.getName());
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
......@@ -20,7 +20,6 @@ public class StartUpdateServlet extends HttpServlet {
resp.setContentType("application/json");
try {
Server.updateInterface.approveUpdate();
resp.setStatus(200);
}
catch (Exception e){
resp.setStatus(500);
......
package net.jami.jams.server.servlets.api.update;
import com.jsoniter.output.JsonStream;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import net.jami.jams.ca.JamsCA;
import net.jami.jams.common.objects.responses.SubscriptionStatusResponse;
import net.jami.jams.server.Server;
import java.io.IOException;
......@@ -14,16 +18,19 @@ public class SubscriptionServlet extends HttpServlet {
//Get the subscription status (see: SubscriptionStatusResponse.class)
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
resp.setHeader("Access-Control-Allow-Origin", JamsCA.serverDomain);
resp.setContentType("application/json");
SubscriptionStatusResponse subscriptionStatusResponse = new SubscriptionStatusResponse();
subscriptionStatusResponse.setLicenseInformation(Server.licenseService.getLicenseInformation());
subscriptionStatusResponse.setActivated(Server.activated.get());
resp.getOutputStream().write(JsonStream.serialize(subscriptionStatusResponse).getBytes());
}
//Upload the license here, which is really just uploading a base64 representation of the keypair - and store it
//somewhere.
// on disk..
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//Create the keystore based on the uploadaded keypair.
super.doPost(req, resp);
}
}
......@@ -4,14 +4,12 @@ package net.jami.jams.server.servlets.api.user;
import com.jsoniter.output.JsonStream;
import lombok.extern.slf4j.Slf4j;
import net.jami.jams.ca.JamsCA;
import net.jami.jams.common.authentication.local.LocalAuthSettings;
import net.jami.jams.common.authmodule.AuthModuleKey;
import net.jami.jams.common.authentication.AuthenticationSourceType;
import net.jami.jams.common.dao.StatementElement;
import net.jami.jams.common.dao.StatementList;
import net.jami.jams.common.objects.user.User;
import net.jami.jams.server.Server;
import net.jami.jams.server.servlets.api.install.CachedObjects;
import net.jami.jams.common.authentication.AuthenticationSourceType;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
......@@ -19,7 +17,6 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.logging.Logger;
import java.util.stream.Collectors;
@WebServlet("/api/user/needsreset")
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment