Commit a6ddf313 authored by Javier Guerra Melgares's avatar Javier Guerra Melgares

Merge branch 'develop'

parents 9810cd74 1ce7b2d6
......@@ -11,7 +11,7 @@
# Constants
PROJECT_NAME="otgc"
VERSION="2.10.0"
VERSION="2.11.0"
program=$0
......
......@@ -6,7 +6,7 @@
<groupId>otgc</groupId>
<artifactId>otgc</artifactId>
<version>2.10.0</version>
<version>2.11.0</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
......
......@@ -210,6 +210,26 @@ public class CmsRepository {
});
}
public Completable addIntermediateCertificate(Integer credid, byte[] cert) {
return Completable.create(emitter -> {
if (OCPki.addMfgIntermediateCert(0 /* First device */, credid, cert) == -1) {
emitter.onError(new Exception("Add intermediate certificate error"));
}
emitter.onComplete();
});
}
public Completable addEndEntityCertificate(byte[] cert, byte[] key) {
return Completable.create(emitter -> {
if (OCPki.addMfgCert(0 /* First device */, cert, key) == -1) {
emitter.onError(new Exception("Add end entity certificate error"));
}
emitter.onComplete();
});
}
public Completable removeTrustAnchor(long credid) {
return Completable.create(emitter -> {
int ret = OCObt.deleteOwnCredByCredId((int)credid);
......
......@@ -105,6 +105,20 @@ public class IORepository {
});
}
public Single<byte[]> getBytesFromPath(String path) {
return Single.fromCallable(() -> {
byte[] fileBytes;
try (InputStream inputStream = new FileInputStream(path)) {
int numBytes = inputStream.available() + 1;
fileBytes = new byte[numBytes];
inputStream.read(fileBytes);
fileBytes[numBytes - 1] = '\0';
}
return fileBytes;
});
}
public Single<CBORObject> getAssetSvrAsCbor(String resource, long device) {
return Single.create(emitter -> {
try (FileInputStream stream = new FileInputStream(OtgcConstant.OTGC_CREDS_DIR + File.separator + resource + "_" + device)) {
......
......@@ -20,6 +20,10 @@
package org.openconnectivity.otgc.domain.usecase;
import io.reactivex.Completable;
import javafx.application.Platform;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonBar;
import javafx.scene.control.ButtonType;
import org.iotivity.OCFactoryPresetsHandler;
import org.iotivity.OCObt;
import org.iotivity.OCPki;
......@@ -28,6 +32,9 @@ import org.openconnectivity.otgc.data.repository.*;
import org.openconnectivity.otgc.utils.constant.OtgcMode;
import javax.inject.Inject;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.Optional;
public class InitOicStackUseCase {
......@@ -69,34 +76,74 @@ public class InitOicStackUseCase {
}
});
private void factoryResetHandler(long device) throws Exception {
/* Kyrio end-entity cert */
byte[] kyrioEeCertificate = ioRepository.getBytesFromFile(OtgcConstant.KYRIO_EE_CERTIFICATE).blockingGet();
/* private key of Kyrio end-entity cert */
byte[] kyrioEeKey = ioRepository.getBytesFromFile(OtgcConstant.KYRIO_EE_KEY).blockingGet();
int credid = OCPki.addMfgCert(device, kyrioEeCertificate, kyrioEeKey);
if (credid == -1) {
throw new Exception("Add identity certificate error");
}
/* Current date */
Date date = new Date();
/* Kyrio intermediate cert */
byte[] kyrioSubcaCertificate = ioRepository.getBytesFromFile(OtgcConstant.KYRIO_SUBCA_CERTIFICATE).blockingGet();
if (OCPki.addMfgIntermediateCert(device, credid, kyrioSubcaCertificate) == -1) {
throw new Exception("Add intermediate certificate error");
/* Kyrio end-entity cert */
X509Certificate eeCert = ioRepository.getFileAsX509Certificate(OtgcConstant.DATA_PATH + OtgcConstant.KYRIO_EE_CERTIFICATE).blockingGet();
if (date.after(eeCert.getNotBefore()) && date.before(eeCert.getNotAfter())) {
byte[] kyrioEeCertificate = ioRepository.getBytesFromFile(OtgcConstant.KYRIO_EE_CERTIFICATE).blockingGet();
/* private key of Kyrio end-entity cert */
byte[] kyrioEeKey = ioRepository.getBytesFromFile(OtgcConstant.KYRIO_EE_KEY).blockingGet();
int credid = OCPki.addMfgCert(device, kyrioEeCertificate, kyrioEeKey);
if (credid == -1) {
throw new Exception("Add identity certificate error");
}
/* Kyrio intermediate cert */
X509Certificate subCaCert = ioRepository.getFileAsX509Certificate(OtgcConstant.DATA_PATH + OtgcConstant.KYRIO_SUBCA_CERTIFICATE).blockingGet();
if (date.after(subCaCert.getNotBefore()) && date.before(subCaCert.getNotAfter())) {
byte[] kyrioSubcaCertificate = ioRepository.getBytesFromFile(OtgcConstant.KYRIO_SUBCA_CERTIFICATE).blockingGet();
if (OCPki.addMfgIntermediateCert(device, credid, kyrioSubcaCertificate) == -1) {
throw new Exception("Add intermediate certificate error");
}
} else {
showPopupNotValidCertificate("Kyrio intermediate certificate is not valid.");
}
} else {
showPopupNotValidCertificate("Kyrio end entity certificate is not valid.");
}
/* Kyrio root cert */
byte[] kyrioRootcaCertificate = ioRepository.getBytesFromFile(OtgcConstant.KYRIO_ROOT_CERTIFICATE).blockingGet();
if (OCPki.addMfgTrustAnchor(device, kyrioRootcaCertificate) == -1) {
throw new Exception("Add root certificate error");
X509Certificate caCert = ioRepository.getFileAsX509Certificate(OtgcConstant.DATA_PATH + OtgcConstant.KYRIO_ROOT_CERTIFICATE).blockingGet();
if (date.after(caCert.getNotBefore()) && date.before(caCert.getNotAfter())) {
byte[] kyrioRootcaCertificate = ioRepository.getBytesFromFile(OtgcConstant.KYRIO_ROOT_CERTIFICATE).blockingGet();
if (OCPki.addMfgTrustAnchor(device, kyrioRootcaCertificate) == -1) {
throw new Exception("Add root certificate error");
}
} else {
showPopupNotValidCertificate("Kyrio root certificate is not valid.");
}
/* EonTi root cert */
byte[] eontiRootcaCertificate = ioRepository.getBytesFromFile(OtgcConstant.EONTI_ROOT_CERTIFICATE).blockingGet();
if (OCPki.addMfgTrustAnchor(device, eontiRootcaCertificate) == -1) {
throw new Exception("Add root certificate error");
caCert = ioRepository.getFileAsX509Certificate(OtgcConstant.DATA_PATH + OtgcConstant.EONTI_ROOT_CERTIFICATE).blockingGet();
if (date.after(caCert.getNotBefore()) && date.before(caCert.getNotAfter())) {
byte[] eontiRootcaCertificate = ioRepository.getBytesFromFile(OtgcConstant.EONTI_ROOT_CERTIFICATE).blockingGet();
if (OCPki.addMfgTrustAnchor(device, eontiRootcaCertificate) == -1) {
throw new Exception("Add root certificate error");
}
} else {
showPopupNotValidCertificate("EonTi root certificate is not valid.");
}
OCObt.shutdown();
}
private void showPopupNotValidCertificate(String content) {
Platform.runLater(() -> {
ButtonType closeButton = new ButtonType("Close", ButtonBar.ButtonData.CANCEL_CLOSE);
Alert alertDialog = new Alert(Alert.AlertType.WARNING);
alertDialog.setTitle("Not Valid Certificate");
alertDialog.setHeaderText(content);
alertDialog.getButtonTypes().clear();
alertDialog.getButtonTypes().add(closeButton);
Optional<ButtonType> result = alertDialog.showAndWait();
if (result.get() == closeButton) {
alertDialog.close();
}
});
}
}
......@@ -44,7 +44,9 @@ public class GetTrustAnchorUseCase {
for (OcCredential cred : creds.getCredList()) {
if (cred.getCredusage() != null && !cred.getCredusage().isEmpty()
&& (OCCredUtil.parseCredUsage(cred.getCredusage()) == OCCredUsage.OC_CREDUSAGE_MFG_TRUSTCA
|| OCCredUtil.parseCredUsage(cred.getCredusage()) == OCCredUsage.OC_CREDUSAGE_TRUSTCA)) {
|| OCCredUtil.parseCredUsage(cred.getCredusage()) == OCCredUsage.OC_CREDUSAGE_TRUSTCA
|| OCCredUtil.parseCredUsage(cred.getCredusage()) == OCCredUsage.OC_CREDUSAGE_IDENTITY_CERT
|| OCCredUtil.parseCredUsage(cred.getCredusage()) == OCCredUsage.OC_CREDUSAGE_MFG_CERT)) {
trustAnchorList.add(cred);
}
}
......
/*
* Copyright 2018 DEKRA Testing and Certification, S.A.U. All Rights Reserved.
*
* *****************************************************************
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openconnectivity.otgc.domain.usecase.trustanchor;
import io.reactivex.Completable;
import io.reactivex.Single;
import org.openconnectivity.otgc.data.repository.CmsRepository;
import org.openconnectivity.otgc.data.repository.IORepository;
import javax.inject.Inject;
public class SaveEndEntityCertificateUseCase {
private final IORepository ioRepository;
private final CmsRepository cmsRepository;
@Inject
public SaveEndEntityCertificateUseCase(IORepository ioRepository,
CmsRepository cmsRepository) {
this.ioRepository = ioRepository;
this.cmsRepository = cmsRepository;
}
public Completable execute(String pathCert, String pathKeyCert) {
Single<byte[]> pemCertObservable = ioRepository.getBytesFromPath(pathCert);
Single<byte[]> pemKeyCertObservable = ioRepository.getBytesFromPath(pathKeyCert);
return pemCertObservable.flatMapCompletable(
cert -> pemKeyCertObservable.flatMapCompletable(
keyCert -> cmsRepository.addEndEntityCertificate(cert, keyCert)
));
}
}
\ No newline at end of file
/*
* Copyright 2018 DEKRA Testing and Certification, S.A.U. All Rights Reserved.
*
* *****************************************************************
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openconnectivity.otgc.domain.usecase.trustanchor;
import io.reactivex.Completable;
import org.openconnectivity.otgc.data.repository.CmsRepository;
import org.openconnectivity.otgc.data.repository.IORepository;
import javax.inject.Inject;
public class SaveIntermediateCertificateUseCase {
private final IORepository ioRepository;
private final CmsRepository cmsRepository;
@Inject
public SaveIntermediateCertificateUseCase(IORepository ioRepository,
CmsRepository cmsRepository) {
this.ioRepository = ioRepository;
this.cmsRepository = cmsRepository;
}
public Completable execute(Integer credid, String path) {
return ioRepository.getBytesFromPath(path)
.flatMapCompletable(cert -> cmsRepository.addIntermediateCertificate(credid, cert));
}
}
\ No newline at end of file
......@@ -19,16 +19,19 @@
package org.openconnectivity.otgc.view.trustanchor;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXComboBox;
import com.jfoenix.controls.JFXRadioButton;
import de.saxsys.mvvmfx.FxmlView;
import de.saxsys.mvvmfx.InjectViewModel;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Alert;
import javafx.scene.control.ListView;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import javafx.util.Callback;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Base64;
import org.openconnectivity.otgc.domain.model.resource.secure.cred.OcCredential;
......@@ -56,31 +59,66 @@ public class TrustAnchorView implements FxmlView<TrustAnchorViewModel>, Initiali
private ResourceBundle resourceBundle;
@FXML private ListView<OcCredential> listView;
@FXML private JFXButton infoCaButton;
@FXML private JFXButton removeCaButton;
@FXML private JFXRadioButton rootRadioButton;
@FXML private JFXRadioButton intermediateRadioButton;
@FXML private JFXRadioButton endentityRadioButton;
@FXML private VBox selectEndEntityLayout;
@FXML private JFXComboBox<OcCredential> selectEndEntityCertificate;
@FXML private JFXButton selectCertificateButton;
@FXML private Label selectCertificateText;
@FXML private VBox selectKeyLayout;
@FXML private JFXButton selectKeyButton;
@FXML private Label selectKeyText;
@FXML private JFXButton infoCertificateButton;
@FXML private JFXButton saveCertificateButton;
@FXML private JFXButton removeCertificateButton;
@Override
public void initialize(URL location, ResourceBundle resources) {
this.resourceBundle = resourceBundle;
viewModel.retrieveTrustAnchors();
selectCertificateText.setText("No selected certificate");
selectKeyText.setText("No selected key");
viewModel.retrieveCertificates();
listView.itemsProperty().bind(viewModel.trustAnchorListProperty());
listView.setCellFactory(deviceListView -> new TrustAnchorViewCell());
viewModel.storeTrustAnchorResponseProperty().addListener(this::processStoreTrustAnchorResponse);
selectEndEntityCertificate.itemsProperty().bind(viewModel.trustAnchorListProperty());
selectEndEntityCertificate.setCellFactory(new Callback<ListView<OcCredential>, ListCell<OcCredential>>(){
@Override
public ListCell<OcCredential> call(ListView<OcCredential> p) {
final ListCell<OcCredential> cell = new ListCell<OcCredential>() {
@Override
protected void updateItem(OcCredential t, boolean bln) {
super.updateItem(t, bln);
if (t != null){
setText("ID: " + t.getCredid());
} else {
setText(null);
}
}
};
return cell;
}
});
viewModel.saveCertificateResponseProperty().addListener(this::processStoreTrustAnchorResponse);
infoCaButton.disableProperty().bind(Bindings.createBooleanBinding(() ->
infoCertificateButton.disableProperty().bind(Bindings.createBooleanBinding(() ->
listView.getSelectionModel().getSelectedItem() == null,
listView.getSelectionModel().selectedItemProperty()));
removeCaButton.disableProperty().bind(Bindings.createBooleanBinding(() ->
removeCertificateButton.disableProperty().bind(Bindings.createBooleanBinding(() ->
listView.getSelectionModel().getSelectedItem() == null,
listView.getSelectionModel().selectedItemProperty()));
}
@FXML
public void handleInfoCaButton() {
public void handleInfoCertificateButton() {
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setResizable(true);
alert.setTitle("Trust Anchor - Information");
......@@ -91,11 +129,28 @@ public class TrustAnchorView implements FxmlView<TrustAnchorViewModel>, Initiali
} else if (listView.getSelectionModel().getSelectedItem().getPublicData().getPemData() != null) {
String pem = listView.getSelectionModel().getSelectedItem().getPublicData().getPemData();
String base64 = pem.replaceAll("\\s", "")
.replaceAll("\\r\\n", "")
.replace("-----BEGINCERTIFICATE-----", "")
.replace("-----ENDCERTIFICATE-----", "");
byte[] der = Base64.decode(base64.getBytes());
alert.setContentText(showX509CertificateInformation(der));
.replaceAll("\\r\\n", "")
.replace("-----ENDCERTIFICATE-----", "")
.replace("\\u0000", "");
String[] certList = base64.split("-----BEGINCERTIFICATE-----");
String res = "";
for (String cert : certList) {
if (!cert.isEmpty()) {
byte[] byteArr = cert.getBytes();
byte[] der = Base64.decode(byteArr);
res += showX509CertificateInformation(der);
res += "\n";
}
}
if (res.isEmpty()) {
alert.setContentText("No information to show");
}
TextArea area = new TextArea(res);
area.setWrapText(true);
area.setEditable(false);
alert.getDialogPane().setContent(area);
alert.setResizable(true);
}
alert.getDialogPane().setMinWidth(600.0);
......@@ -131,9 +186,26 @@ public class TrustAnchorView implements FxmlView<TrustAnchorViewModel>, Initiali
}
@FXML
public void handleAddCaButton() {
public void handleCertificateGroup() {
if (rootRadioButton.isSelected()) {
selectEndEntityLayout.setDisable(true);
selectKeyLayout.setDisable(true);
} else if (intermediateRadioButton.isSelected()) {
selectEndEntityLayout.setDisable(false);
selectKeyLayout.setDisable(true);
} else if (endentityRadioButton.isSelected()) {
selectEndEntityLayout.setDisable(true);
selectKeyLayout.setDisable(false);
}
}
private File selectedCertificateFile = null;
private File selectedKeyFile = null;
@FXML
public void handleSelectCertificateButton() {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Select a new Trust Anchor");
fileChooser.setTitle("Select a new certificate");
fileChooser.setInitialDirectory(
new File(System.getProperty("user.home"))
);
......@@ -144,7 +216,26 @@ public class TrustAnchorView implements FxmlView<TrustAnchorViewModel>, Initiali
);
File file = fileChooser.showOpenDialog(primaryStage);
if (file != null) {
viewModel.addTrustAnchor(file);
selectedCertificateFile = file;
selectCertificateText.setText(selectedCertificateFile.getName());
}
}
@FXML
public void handleSelectKeyButton() {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Select a new key for the selected certificate");
fileChooser.setInitialDirectory(
new File(System.getProperty("user.home"))
);
fileChooser.getExtensionFilters().addAll(
new FileChooser.ExtensionFilter("All files", "*.*"),
new FileChooser.ExtensionFilter("KEY", "*.key")
);
File file = fileChooser.showOpenDialog(primaryStage);
if (file != null) {
selectedKeyFile = file;
selectKeyText.setText(selectedKeyFile.getName());
}
}
......@@ -159,7 +250,22 @@ public class TrustAnchorView implements FxmlView<TrustAnchorViewModel>, Initiali
}
@FXML
public void handleRemoveCaButton() {
viewModel.removeTrustAnchorByCredid(listView.getSelectionModel().getSelectedItem().getCredid());
public void handleSaveCertificateButton() {
if (selectedCertificateFile != null) {
if (rootRadioButton.isSelected()) {
viewModel.addTrustAnchor(selectedCertificateFile);
} else if (intermediateRadioButton.isSelected()) {
if (selectEndEntityCertificate.getSelectionModel().getSelectedItem() != null) {
viewModel.saveIntermediateCertificate(selectEndEntityCertificate.getSelectionModel().getSelectedItem().getCredid(), selectedCertificateFile);
}
} else if (endentityRadioButton.isSelected()) {
viewModel.saveEndEntityCertificate(selectedCertificateFile, selectedKeyFile);
}
}
}
@FXML
public void handleRemoveCertificateButton() {
viewModel.removeCertificateByCredid(listView.getSelectionModel().getSelectedItem().getCredid());
}
}
\ No newline at end of file
......@@ -26,16 +26,12 @@ import javafx.beans.property.SimpleListProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import org.openconnectivity.otgc.domain.model.resource.secure.cred.OcCredential;
import org.openconnectivity.otgc.domain.usecase.trustanchor.GetTrustAnchorUseCase;
import org.openconnectivity.otgc.domain.usecase.trustanchor.RemoveTrustAnchorByCredidUseCase;
import org.openconnectivity.otgc.domain.usecase.trustanchor.StoreTrustAnchorUseCase;
import org.openconnectivity.otgc.domain.usecase.trustanchor.*;
import org.openconnectivity.otgc.utils.rx.SchedulersFacade;
import org.openconnectivity.otgc.utils.viewmodel.Response;
import javax.inject.Inject;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class TrustAnchorViewModel implements ViewModel {
......@@ -45,6 +41,8 @@ public class TrustAnchorViewModel implements ViewModel {
// Use cases
private final StoreTrustAnchorUseCase storeTrustAnchorUseCase;
private final SaveIntermediateCertificateUseCase saveIntermediateCertificateUseCase;
private final SaveEndEntityCertificateUseCase saveEndEntityCertificateUseCase;
private final GetTrustAnchorUseCase getTrustAnchorUseCase;
private final RemoveTrustAnchorByCredidUseCase remoteRemoveTrustAnchorByCredidUseCase;
......@@ -53,24 +51,28 @@ public class TrustAnchorViewModel implements ViewModel {
public ListProperty<OcCredential> trustAnchorListProperty() {
return trustAnchorList;
}
private final ObjectProperty<Response<Void>> storeTrustAnchorResponse = new SimpleObjectProperty<>();
private final ObjectProperty<Response<Void>> saveCertificateResponse = new SimpleObjectProperty<>();
@Inject
public TrustAnchorViewModel(SchedulersFacade schedulersFacade,
StoreTrustAnchorUseCase storeTrustAnchorUseCase,
SaveIntermediateCertificateUseCase saveIntermediateCertificateUseCase,
SaveEndEntityCertificateUseCase saveEndEntityCertificateUseCase,
GetTrustAnchorUseCase getTrustAnchorUseCase,
RemoveTrustAnchorByCredidUseCase remoteRemoveTrustAnchorByCredidUseCase) {
this.schedulersFacade = schedulersFacade;
this.storeTrustAnchorUseCase = storeTrustAnchorUseCase;
this.saveIntermediateCertificateUseCase = saveIntermediateCertificateUseCase;
this.saveEndEntityCertificateUseCase = saveEndEntityCertificateUseCase;
this.getTrustAnchorUseCase = getTrustAnchorUseCase;
this.remoteRemoveTrustAnchorByCredidUseCase = remoteRemoveTrustAnchorByCredidUseCase;
}
public ObjectProperty<Response<Void>> storeTrustAnchorResponseProperty() {
return storeTrustAnchorResponse;
public ObjectProperty<Response<Void>> saveCertificateResponseProperty() {
return saveCertificateResponse;
}
public void retrieveTrustAnchors() {
public void retrieveCertificates() {
disposable.add(getTrustAnchorUseCase.execute()
.subscribeOn(schedulersFacade.io())
.observeOn(schedulersFacade.ui())
......@@ -86,17 +88,37 @@ public class TrustAnchorViewModel implements ViewModel {
.subscribeOn(schedulersFacade.io())
.observeOn(schedulersFacade.ui())
.subscribe(
() -> retrieveTrustAnchors(),
throwable -> storeTrustAnchorResponse.set(Response.error(throwable))
() -> retrieveCertificates(),
throwable -> saveCertificateResponse.set(Response.error(throwable))
));
}
public void removeTrustAnchorByCredid(long credid) {
public void saveIntermediateCertificate(Integer credid, File file) {
disposable.add(saveIntermediateCertificateUseCase.execute(credid, file.getPath())
.subscribeOn(schedulersFacade.io())
.observeOn(schedulersFacade.ui())
.subscribe(
() -> retrieveCertificates(),
throwable -> saveCertificateResponse.set(Response.error(throwable))
));
}
public void saveEndEntityCertificate(File certFile, File keyFile) {
disposable.add(saveEndEntityCertificateUseCase.execute(certFile.getPath(), keyFile.getPath())
.subscribeOn(schedulersFacade.io())
.observeOn(schedulersFacade.ui())
.subscribe(
() -> retrieveCertificates(),
throwable -> saveCertificateResponse.set(Response.error(throwable))
));