UpdateDownloader.java
/*
* Copyright (C) 2020-2024 by Savoir-faire Linux
*
* 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.server.update;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import lombok.extern.slf4j.Slf4j;
import net.jami.jams.common.serialization.adapters.GsonFactory;
import net.jami.jams.common.updater.FileDescription;
import net.jami.jams.common.utils.X509Utils;
import net.jami.jams.server.licensing.LicenseService;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import javax.net.ssl.SSLContext;
@Slf4j
public class UpdateDownloader {
private SSLContext sslContext;
private static final String KEYSTORE_TYPE = "JKS";
private KeyStore trustStore;
private static volatile String UPDATE_SERVER_URL;
private final HashMap<String, FileDescription> remoteChecksums = new HashMap<>();
private final Gson gson = GsonFactory.createGson();
public UpdateDownloader() {
try {
InputStream is =
UpdateCheckTask.class.getClassLoader().getResourceAsStream("oem/ca.crt");
X509Certificate certificate =
X509Utils.getCertificateFromPEMString(new String(is.readAllBytes()));
trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null, null);
trustStore.setCertificateEntry("ca", certificate);
is = UpdateDownloader.class.getClassLoader().getResourceAsStream("oem/update.crt");
certificate = X509Utils.getCertificateFromPEMString(new String(is.readAllBytes()));
trustStore.setCertificateEntry("update", certificate);
} catch (Exception e) {
log.info(
"Could not load SFL's CA - this should not happen! detailed error: {}",
e.getMessage());
}
InputStream input = this.getClass().getClassLoader().getResourceAsStream("oem/config.json");
if (input == null) {
log.warn("Missing OEM configuration! Please contact software developer");
System.exit(-1);
}
Reader reader = new InputStreamReader(input);
JsonObject jsonObject = gson.fromJson(reader, JsonObject.class);
UPDATE_SERVER_URL = jsonObject.get("UPDATE_URL").getAsString();
}
public boolean downloadFiles(HashMap<String, FileDescription> files) {
// I know this contradicts my dogma, but this really would have been an overkill for this
// project,
// I just claim that everything which is not core gets dumped to the lib directory.
// We can perpetually reload this,it doesn't really harm anything.
// Build the SSL context here, (this is fairly simple)
KeyStore ks = null;
try {
ks = KeyStore.getInstance(KEYSTORE_TYPE);
ks.load(null);
LicenseService licenseService = new LicenseService();
licenseService.loadLicense();
ks.setKeyEntry(
"licenses",
JAMSUpdater.privateKey,
"".toCharArray(),
new Certificate[] {JAMSUpdater.certificate});
sslContext =
SSLContexts.custom()
.loadKeyMaterial(ks, "".toCharArray())
.loadTrustMaterial(trustStore, null)
.build();
} catch (Exception e) {
log.warn("Could not download an update with error " + e);
}
// temp folder for safe download and integrity check
File tmpFolder = new File(System.getProperty("user.dir") + "/tmp/");
if (!tmpFolder.exists()) {
try {
tmpFolder.mkdirs();
} catch (Exception e) {
log.error("Error creating folder:" + e);
return false;
}
}
files.forEach(
(k, v) -> {
try {
File tmpFile =
new File(
System.getProperty("user.dir") + "/tmp/" + v.getFileName());
if (tmpFile.exists()) {
try {
tmpFile.delete();
} catch (Exception e) {
log.warn(
"Error deleting file: "
+ v.getFileName()
+ " with error "
+ e);
}
}
HttpClient httpClient =
HttpClients.custom().setSSLContext(sslContext).build();
HttpResponse httpResponse =
httpClient.execute(
new HttpGet(
UPDATE_SERVER_URL + "/updates/" + v.getFileName()));
if (httpResponse.getStatusLine().getStatusCode() != 200) return;
FileOutputStream fos =
new FileOutputStream(tmpFolder.getPath() + "/" + v.getFileName());
httpResponse.getEntity().writeTo(fos);
fos.close();
} catch (Exception e1) {
log.warn("Could not download an update with error " + e1);
}
});
return true;
}
}