diff --git a/otgc/build.gradle b/otgc/build.gradle index 91bc28abf878c092e3edc57e37dd550169b49a8c..10a3102b8e536c2a9d109dab30cc77bc1f9fd2b3 100644 --- a/otgc/build.gradle +++ b/otgc/build.gradle @@ -30,7 +30,7 @@ android { minSdkVersion 21 targetSdkVersion 28 versionCode 13 - versionName "2.10.0" + versionName "2.11.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" compileOptions { diff --git a/otgc/src/main/AndroidManifest.xml b/otgc/src/main/AndroidManifest.xml index 56e31385dfdcac33916196a4cb68577d6185da8e..2c7adcdc73957c5d1a8cedfbfa08341f4a13673b 100644 --- a/otgc/src/main/AndroidManifest.xml +++ b/otgc/src/main/AndroidManifest.xml @@ -101,6 +101,11 @@ android:configChanges="orientation|screenSize" android:label="@string/trust_anchor_title" android:parentActivityName=".view.devicelist.DeviceListActivity" /> + { + 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); diff --git a/otgc/src/main/java/org/openconnectivity/otgc/data/repository/IORepository.java b/otgc/src/main/java/org/openconnectivity/otgc/data/repository/IORepository.java index 17f2c81162f09ae71a4e22e00047b68a839ad756..dbdb6cd33835e7dfebfd395b9f08b653599a7ca7 100644 --- a/otgc/src/main/java/org/openconnectivity/otgc/data/repository/IORepository.java +++ b/otgc/src/main/java/org/openconnectivity/otgc/data/repository/IORepository.java @@ -178,6 +178,17 @@ public class IORepository { }); } + public Single getBytesFromFile(InputStream is) { + return Single.fromCallable(() -> { + int numBytes = is.available() + 1; + byte[] fileBytes = new byte[numBytes]; + is.read(fileBytes); + fileBytes[numBytes - 1] = '\0'; + + return fileBytes; + }); + } + public Single getAssetSvrAsCbor(String resource, long device) { return Single.create(emitter -> { try (FileInputStream stream = new FileInputStream(mContext.getFilesDir() + diff --git a/otgc/src/main/java/org/openconnectivity/otgc/domain/usecase/InitializeIotivityUseCase.java b/otgc/src/main/java/org/openconnectivity/otgc/domain/usecase/InitializeIotivityUseCase.java index 34422716bf646e86a945dddfb364d3da13987753..65e8ce46abbd961f824ccc953c417a51231e378c 100644 --- a/otgc/src/main/java/org/openconnectivity/otgc/domain/usecase/InitializeIotivityUseCase.java +++ b/otgc/src/main/java/org/openconnectivity/otgc/domain/usecase/InitializeIotivityUseCase.java @@ -22,6 +22,8 @@ package org.openconnectivity.otgc.domain.usecase; +import android.content.Context; + import org.iotivity.OCFactoryPresetsHandler; import org.iotivity.OCObt; import org.iotivity.OCPki; @@ -30,6 +32,11 @@ import org.openconnectivity.otgc.data.repository.IotivityRepository; import org.openconnectivity.otgc.data.repository.PreferencesRepository; import org.openconnectivity.otgc.utils.constant.OtgcConstant; import org.openconnectivity.otgc.utils.constant.OtgcMode; +import org.openconnectivity.otgc.utils.handler.DisplayNotValidCertificateHandler; + +import java.io.InputStream; +import java.security.cert.X509Certificate; +import java.util.Date; import javax.inject.Inject; @@ -40,6 +47,9 @@ public class InitializeIotivityUseCase { private final IORepository ioRepository; private final PreferencesRepository settingRepository; + private Context context; + private DisplayNotValidCertificateHandler displayNotValidCertificateHandler; + @Inject public InitializeIotivityUseCase(IotivityRepository iotivityRepository, IORepository ioRepository, @@ -49,7 +59,10 @@ public class InitializeIotivityUseCase { this.settingRepository = settingRepository; } - public Completable execute() { + public Completable execute(Context context, DisplayNotValidCertificateHandler displayNotValidCertificateHandler) { + this.context = context; + this.displayNotValidCertificateHandler = displayNotValidCertificateHandler; + Completable initOic = iotivityRepository.initOICStack(); Completable completable = iotivityRepository.setFactoryResetHandler(factoryReset); @@ -76,31 +89,58 @@ public class InitializeIotivityUseCase { } }); private void factoryResetHandler(long device) throws Exception { + /* Current date */ + Date date = new Date(); + /* 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"); - } + InputStream inputStream = context.getAssets().open(OtgcConstant.KYRIO_EE_CERTIFICATE); + X509Certificate eeCert = ioRepository.getFileAsX509Certificate(inputStream).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 */ - byte[] kyrioSubcaCertificate = ioRepository.getBytesFromFile(OtgcConstant.KYRIO_SUBCA_CERTIFICATE).blockingGet(); - if (OCPki.addMfgIntermediateCert(device, credid, kyrioSubcaCertificate) == -1) { - throw new Exception("Add intermediate certificate error"); + /* Kyrio intermediate cert */ + inputStream = context.getAssets().open(OtgcConstant.KYRIO_SUBCA_CERTIFICATE); + X509Certificate subCaCert = ioRepository.getFileAsX509Certificate(inputStream).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 { + this.displayNotValidCertificateHandler.handler("Kyrio intermediate certificate is not valid"); + } + } else { + this.displayNotValidCertificateHandler.handler("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"); + inputStream = context.getAssets().open(OtgcConstant.KYRIO_ROOT_CERTIFICATE); + X509Certificate caCert = ioRepository.getFileAsX509Certificate(inputStream).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 { + this.displayNotValidCertificateHandler.handler("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"); + inputStream = context.getAssets().open(OtgcConstant.EONTI_ROOT_CERTIFICATE); + caCert = ioRepository.getFileAsX509Certificate(inputStream).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 { + this.displayNotValidCertificateHandler.handler("EonTi root certificate is not valid"); } OCObt.shutdown(); diff --git a/otgc/src/main/java/org/openconnectivity/otgc/domain/usecase/trustanchor/GetTrustAnchorUseCase.java b/otgc/src/main/java/org/openconnectivity/otgc/domain/usecase/trustanchor/GetTrustAnchorUseCase.java index 4c25eb54b7547e9b967cff1444248170a74c9a16..3dbf39a21080d98d0eb2cb1e8522006cd920363a 100644 --- a/otgc/src/main/java/org/openconnectivity/otgc/domain/usecase/trustanchor/GetTrustAnchorUseCase.java +++ b/otgc/src/main/java/org/openconnectivity/otgc/domain/usecase/trustanchor/GetTrustAnchorUseCase.java @@ -45,7 +45,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); } } diff --git a/otgc/src/main/java/org/openconnectivity/otgc/domain/usecase/trustanchor/SaveEndEntityCertificateUseCase.java b/otgc/src/main/java/org/openconnectivity/otgc/domain/usecase/trustanchor/SaveEndEntityCertificateUseCase.java new file mode 100644 index 0000000000000000000000000000000000000000..4851da47d26fae7890b772a0da21536281ad4aa1 --- /dev/null +++ b/otgc/src/main/java/org/openconnectivity/otgc/domain/usecase/trustanchor/SaveEndEntityCertificateUseCase.java @@ -0,0 +1,54 @@ +/* + * 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 org.openconnectivity.otgc.data.repository.CmsRepository; +import org.openconnectivity.otgc.data.repository.IORepository; + +import java.io.InputStream; + +import javax.inject.Inject; + +import io.reactivex.Completable; +import io.reactivex.Single; + +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(InputStream certIs, InputStream keyIs) { + Single pemCertObservable = ioRepository.getBytesFromFile(certIs); + + Single pemKeyCertObservable = ioRepository.getBytesFromFile(keyIs); + + return pemCertObservable.flatMapCompletable( + cert -> pemKeyCertObservable.flatMapCompletable( + keyCert -> cmsRepository.addEndEntityCertificate(cert, keyCert) + )); + } + +} \ No newline at end of file diff --git a/otgc/src/main/java/org/openconnectivity/otgc/domain/usecase/trustanchor/SaveIntermediateCertificateUseCase.java b/otgc/src/main/java/org/openconnectivity/otgc/domain/usecase/trustanchor/SaveIntermediateCertificateUseCase.java new file mode 100644 index 0000000000000000000000000000000000000000..675dc2bf4ff90bf788a415615c0bc87f2bee942c --- /dev/null +++ b/otgc/src/main/java/org/openconnectivity/otgc/domain/usecase/trustanchor/SaveIntermediateCertificateUseCase.java @@ -0,0 +1,47 @@ +/* + * 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 org.openconnectivity.otgc.data.repository.CmsRepository; +import org.openconnectivity.otgc.data.repository.IORepository; + +import java.io.InputStream; + +import javax.inject.Inject; + +import io.reactivex.Completable; + +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, InputStream is) { + return ioRepository.getBytesFromFile(is) + .flatMapCompletable(cert -> cmsRepository.addIntermediateCertificate(credid, cert)); + } + +} \ No newline at end of file diff --git a/otgc/src/main/java/org/openconnectivity/otgc/utils/di/BuildersModule.java b/otgc/src/main/java/org/openconnectivity/otgc/utils/di/BuildersModule.java index a130f3727edd479bfc14825ff5f55382957e960f..3ed73e94c2e2305afd1fdd80127852cb223c6625 100644 --- a/otgc/src/main/java/org/openconnectivity/otgc/utils/di/BuildersModule.java +++ b/otgc/src/main/java/org/openconnectivity/otgc/utils/di/BuildersModule.java @@ -32,6 +32,7 @@ import org.openconnectivity.otgc.view.devicelist.DeviceListActivity; import org.openconnectivity.otgc.view.link.LinkedRolesActivity; import org.openconnectivity.otgc.view.login.LoginActivity; import org.openconnectivity.otgc.view.splash.SplashActivity; +import org.openconnectivity.otgc.view.trustanchor.CertificateActivity; import org.openconnectivity.otgc.view.trustanchor.TrustAnchorActivity; import org.openconnectivity.otgc.view.wlanscan.WlanScanActivity; @@ -73,4 +74,7 @@ public interface BuildersModule { @ContributesAndroidInjector abstract TrustAnchorActivity bindTrustAnchorActivity(); + + @ContributesAndroidInjector + abstract CertificateActivity bindCertificateActivity(); } diff --git a/otgc/src/main/java/org/openconnectivity/otgc/utils/di/ViewModelModule.java b/otgc/src/main/java/org/openconnectivity/otgc/utils/di/ViewModelModule.java index cf828aa552ec526b3aa6d7540e0ebe47d7f49f9c..23722e7cd22c775de7ecce926dda39d58c18d7cb 100644 --- a/otgc/src/main/java/org/openconnectivity/otgc/utils/di/ViewModelModule.java +++ b/otgc/src/main/java/org/openconnectivity/otgc/utils/di/ViewModelModule.java @@ -27,6 +27,7 @@ import androidx.lifecycle.ViewModelProvider; import org.openconnectivity.otgc.viewmodel.AccessControlViewModel; import org.openconnectivity.otgc.viewmodel.AceViewModel; +import org.openconnectivity.otgc.viewmodel.CertificateViewModel; import org.openconnectivity.otgc.viewmodel.ResourceViewModel; import org.openconnectivity.otgc.utils.viewmodel.ViewModelFactory; import org.openconnectivity.otgc.viewmodel.GenericClientViewModel; @@ -119,4 +120,9 @@ abstract class ViewModelModule { @IntoMap @ViewModelKey(TrustAnchorViewModel.class) abstract ViewModel bindTrustAnchorsViewModel(TrustAnchorViewModel trustAnchorViewModel); + + @Binds + @IntoMap + @ViewModelKey(CertificateViewModel.class) + abstract ViewModel bindCertificateViewModel(CertificateViewModel certificateViewModel); } diff --git a/otgc/src/main/java/org/openconnectivity/otgc/utils/handler/DisplayNotValidCertificateHandler.java b/otgc/src/main/java/org/openconnectivity/otgc/utils/handler/DisplayNotValidCertificateHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..b8decf00413208721878826c828ff81c928e8c98 --- /dev/null +++ b/otgc/src/main/java/org/openconnectivity/otgc/utils/handler/DisplayNotValidCertificateHandler.java @@ -0,0 +1,27 @@ +/* + * ***************************************************************** + * + * 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.utils.handler; + +public interface DisplayNotValidCertificateHandler { + public void handler(String content); +} diff --git a/otgc/src/main/java/org/openconnectivity/otgc/view/devicelist/DeviceListActivity.java b/otgc/src/main/java/org/openconnectivity/otgc/view/devicelist/DeviceListActivity.java index 9efe12f47435f39e8b28e4c1b56e2351732281c4..216b77dd14b847e14c5d21797ecd20cb3ebf77bc 100644 --- a/otgc/src/main/java/org/openconnectivity/otgc/view/devicelist/DeviceListActivity.java +++ b/otgc/src/main/java/org/openconnectivity/otgc/view/devicelist/DeviceListActivity.java @@ -39,6 +39,7 @@ import android.widget.Toast; import org.iotivity.OCRandomPinHandler; import org.openconnectivity.otgc.utils.constant.OtgcMode; +import org.openconnectivity.otgc.utils.handler.DisplayNotValidCertificateHandler; import org.openconnectivity.otgc.utils.handler.OCSetRandomPinHandler; import org.openconnectivity.otgc.utils.view.RecyclerWithSwipeFragment; import org.openconnectivity.otgc.utils.viewmodel.CommonError; @@ -131,6 +132,18 @@ public class DeviceListActivity extends AppCompatActivity implements HasSupportF }); }; + DisplayNotValidCertificateHandler displayNotValidCertificateHandler = content -> { + runOnUiThread(() -> { + AlertDialog.Builder alertDialog = new AlertDialog.Builder(new ContextThemeWrapper(DeviceListActivity.this, R.style.AppTheme)); + alertDialog.setTitle(DeviceListActivity.this.getString(R.string.devices_dialog_show_not_valid_certificate_title)); + alertDialog.setMessage(content); + alertDialog.setCancelable(false); + alertDialog.setPositiveButton( + DeviceListActivity.this.getString(R.string.devices_dialog_show_not_valid_certificate_ok_option), + (dialog, which) -> dialog.dismiss()).show(); + }); + }; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -169,7 +182,7 @@ public class DeviceListActivity extends AppCompatActivity implements HasSupportF } } - mViewModel.initializeIotivityStack(); + mViewModel.initializeIotivityStack(getApplicationContext(), displayNotValidCertificateHandler); return true; } diff --git a/otgc/src/main/java/org/openconnectivity/otgc/view/trustanchor/CertificateActivity.java b/otgc/src/main/java/org/openconnectivity/otgc/view/trustanchor/CertificateActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..201d525a64ef0fa18b7e9c6a2cebf52daedf648c --- /dev/null +++ b/otgc/src/main/java/org/openconnectivity/otgc/view/trustanchor/CertificateActivity.java @@ -0,0 +1,241 @@ +package org.openconnectivity.otgc.view.trustanchor; + +import android.app.Activity; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.view.MenuItem; +import android.view.View; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.RadioButton; +import android.widget.Spinner; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.lifecycle.ViewModelProvider; +import androidx.lifecycle.ViewModelProviders; + +import org.openconnectivity.otgc.R; +import org.openconnectivity.otgc.domain.model.resource.secure.cred.OcCredential; +import org.openconnectivity.otgc.utils.di.Injectable; +import org.openconnectivity.otgc.utils.viewmodel.ViewModelError; +import org.openconnectivity.otgc.viewmodel.CertificateViewModel; + +import java.io.InputStream; +import java.util.ArrayList; + +import javax.inject.Inject; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; + +public class CertificateActivity extends AppCompatActivity implements Injectable { + @Inject + ViewModelProvider.Factory mViewModelFactory; + + @BindView(R.id.toolbar) Toolbar mToolbar; + @BindView(R.id.trust_anchor_select_end_entity_layout) LinearLayout selectEndEntityLayout; + @BindView(R.id.spinner_select_end_entity_certificate) Spinner selectEndEntityCert; + @BindView(R.id.button_select_certificate) Button selectCertificate; + @BindView(R.id.trust_anchor_selected_certificate_text) TextView selectedCertificateText; + @BindView(R.id.trust_anchor_select_key_layout) LinearLayout selectKeyLayout; + @BindView(R.id.button_select_key) Button selectKey; + @BindView(R.id.trust_anchor_selected_key_text) TextView selectKeyText; + @BindView(R.id.radio_root_certificate) RadioButton rootRadioButton; + @BindView(R.id.radio_intermediate_certificate) RadioButton intermediateRadioButton; + @BindView(R.id.radio_end_entity_certificate) RadioButton endEntityRadioButton; + + @OnClick({ R.id.radio_root_certificate, R.id.radio_intermediate_certificate, R.id.radio_end_entity_certificate}) + public void onRadioButtonClicked(RadioButton rb) { + // Is the button now checked? + boolean checked = rb.isChecked(); + + // Check which radio button was clicked + switch (rb.getId()) { + case R.id.radio_root_certificate: + if (checked) { + selectEndEntityLayout.setVisibility(View.GONE); + selectKeyLayout.setVisibility(View.GONE); + } + break; + case R.id.radio_intermediate_certificate: + if (checked) { + selectEndEntityLayout.setVisibility(View.VISIBLE); + selectKeyLayout.setVisibility(View.GONE); + } + break; + case R.id.radio_end_entity_certificate: + if (checked) { + selectEndEntityLayout.setVisibility(View.GONE); + selectKeyLayout.setVisibility(View.VISIBLE); + } + break; + } + } + + private static final int READ_REQUEST_CODE = 42; + + @OnClick({ R.id.button_select_certificate, R.id.button_select_key }) + public void onSelectFileClicked(Button button) { + if (button.getId() == R.id.button_select_certificate) { + isForCert = true; + isForKey = false; + } else if (button.getId() == R.id.button_select_key) { + isForCert = false; + isForKey = true; + } + + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("*/*"); + startActivityForResult(intent, READ_REQUEST_CODE); + } + + private boolean isForCert = false; + private boolean isForKey = false; + private InputStream fileIs; + private InputStream keyIs; + + @Override + public void onActivityResult(int requestCode, int resultCode, + Intent resultData) { + + // The ACTION_OPEN_DOCUMENT intent was sent with the request code + // READ_REQUEST_CODE. If the request code seen here doesn't match, it's the + // response to some other intent, and the code below shouldn't run at all. + + if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) { + // The document selected by the user won't be returned in the intent. + // Instead, a URI to that document will be contained in the return intent + // provided to this method as a parameter. + // Pull that URI using resultData.getData(). + Uri uri = null; + if (resultData != null) { + uri = resultData.getData(); + try { + InputStream is = getContentResolver().openInputStream(uri); + if (isForCert) { + fileIs = is; + isForCert = false; + selectedCertificateText.setText(uri.getPath()); + } else if (isForKey) { + keyIs = is; + isForKey = false; + selectKeyText.setText(uri.getPath()); + } + } catch (Exception e) { + int errorId = R.string.trust_anchor_create_error; + Toast.makeText(this, errorId, Toast.LENGTH_SHORT).show(); + } + + } + } + } + + @OnClick(R.id.floating_button_cert_save) + protected void onSavePressed() { + if (rootRadioButton.isChecked()) { + mViewModel.saveTrustAnchor(fileIs); + } else if (intermediateRadioButton.isChecked()) { + if (selectEndEntityCert.getSelectedItem() != null) { + int index = selectEndEntityCert.getSelectedItemPosition(); + Integer credid = (Integer)selectEndEntityCert.getAdapter().getItem(index); + mViewModel.saveIntermediateCertificate(credid, fileIs); + } + } else if (endEntityRadioButton.isChecked()) { + mViewModel.saveEndEntityCertificate(fileIs, keyIs); + } + } + + private CertificateViewModel mViewModel; + + private ArrayList mCertList; + + private ArrayAdapter mAdapter; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_certificate); + + Intent intent = getIntent(); + mCertList = (ArrayList) intent.getSerializableExtra("certs"); + + ButterKnife.bind(this); + initViews(); + initViewModel(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + if (id == android.R.id.home) { + onBackPressed(); + return true; + } + return super.onOptionsItemSelected(item); + } + + private void initViews() { + setSupportActionBar(mToolbar); + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(true); + actionBar.setDisplayShowHomeEnabled(true); + } + + selectedCertificateText.setText(R.string.trust_anchor_no_selected_certificate_text); + selectKeyText.setText(R.string.trust_anchor_no_selected_key_text); + + mAdapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_dropdown_item); + if (mCertList != null) { + mAdapter.addAll(mCertList); + } + selectEndEntityCert.setAdapter(mAdapter); + } + + private void initViewModel() { + mViewModel = ViewModelProviders.of(this, mViewModelFactory).get(CertificateViewModel.class); + + mViewModel.isProcessing().observe(this, this::handleProcessing); + mViewModel.getError().observe(this, this::handleError); + + mViewModel.getSuccess().observe(this, this::processSuccess); + } + + private void handleProcessing(@NonNull Boolean isProcessing) { + // TODO + } + + private void handleError(@NonNull ViewModelError error) { + int errorId = 0; + switch ((CertificateViewModel.Error)error.getType()) { + case ROOT_CERTIFICATE: + errorId = R.string.trust_anchor_error_create_root_certificate; + break; + case INTERMEDIATE_CERTIFICATE: + errorId = R.string.trust_anchor_error_create_intermediate_certificate; + break; + case END_ENTITY_CERTIFICATE: + errorId = R.string.trust_anchor_error_create_end_entity_certificate; + break; + } + + Toast.makeText(this, errorId, Toast.LENGTH_SHORT).show(); + } + + private void processSuccess(@NonNull Boolean operationSucceeded) { + if (operationSucceeded) { + finish(); + } else { + // TODO + } + } +} diff --git a/otgc/src/main/java/org/openconnectivity/otgc/view/trustanchor/TrustAnchorActivity.java b/otgc/src/main/java/org/openconnectivity/otgc/view/trustanchor/TrustAnchorActivity.java index 2b301866c6e8ebdad7c6e46425643c4ee1f9ce07..8246956220ca756c7f199fad6cda14ac0ebc982c 100644 --- a/otgc/src/main/java/org/openconnectivity/otgc/view/trustanchor/TrustAnchorActivity.java +++ b/otgc/src/main/java/org/openconnectivity/otgc/view/trustanchor/TrustAnchorActivity.java @@ -18,9 +18,7 @@ package org.openconnectivity.otgc.view.trustanchor; -import android.app.Activity; import android.content.Intent; -import android.net.Uri; import android.os.Bundle; import android.view.MenuItem; import android.view.View; @@ -49,16 +47,14 @@ import org.openconnectivity.otgc.viewmodel.TrustAnchorViewModel; import org.spongycastle.jce.provider.BouncyCastleProvider; import org.spongycastle.util.encoders.Base64; -import java.io.BufferedReader; import java.io.ByteArrayInputStream; -import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; import java.security.Security; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.text.SimpleDateFormat; -import java.util.Objects; +import java.util.ArrayList; +import java.util.List; import javax.inject.Inject; @@ -75,41 +71,16 @@ public class TrustAnchorActivity extends AppCompatActivity implements Injectable @BindView(R.id.recycler_ocf_trustanchors) EmptyRecyclerView mRecyclerView; @BindView(R.id.floating_button_trustanchor_add) FloatingActionButton mFloatingActionButton; - private static final int READ_REQUEST_CODE = 42; - @OnClick(R.id.floating_button_trustanchor_add) protected void onAddPressed() { - Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); - intent.addCategory(Intent.CATEGORY_OPENABLE); - intent.setType("*/*"); - startActivityForResult(intent, READ_REQUEST_CODE); - } - - @Override - public void onActivityResult(int requestCode, int resultCode, - Intent resultData) { - - // The ACTION_OPEN_DOCUMENT intent was sent with the request code - // READ_REQUEST_CODE. If the request code seen here doesn't match, it's the - // response to some other intent, and the code below shouldn't run at all. - - if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) { - // The document selected by the user won't be returned in the intent. - // Instead, a URI to that document will be contained in the return intent - // provided to this method as a parameter. - // Pull that URI using resultData.getData(). - Uri uri = null; - if (resultData != null) { - uri = resultData.getData(); - try { - mViewModel.addTrustAnchor(getContentResolver().openInputStream(uri)); - } catch (Exception e) { - int errorId = R.string.trust_anchor_create_error; - Toast.makeText(this, errorId, Toast.LENGTH_SHORT).show(); - } - - } + Intent intent = new Intent(this, CertificateActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + ArrayList certList = new ArrayList<>(); + for (int i=0; i mProcessing = new MutableLiveData<>(); + private final MutableLiveData mError = new MutableLiveData<>(); + + private final MutableLiveData mSuccess = new MutableLiveData<>(); + + @Inject + CertificateViewModel(SchedulersFacade schedulersFacade, + StoreTrustAnchorUseCase storeTrustAnchorUseCase, + SaveIntermediateCertificateUseCase saveIntermediateCertificateUseCase, + SaveEndEntityCertificateUseCase saveEndEntityCertificateUseCase) { + this.mSchedulersFacade = schedulersFacade; + this.storeTrustAnchorUseCase = storeTrustAnchorUseCase; + this.saveIntermediateCertificateUseCase = saveIntermediateCertificateUseCase; + this.saveEndEntityCertificateUseCase = saveEndEntityCertificateUseCase; + } + + @Override + protected void onCleared() { + mDisposables.clear(); + } + + public LiveData isProcessing() { + return mProcessing; + } + + public LiveData getError() { + return mError; + } + + public LiveData getSuccess() { + return mSuccess; + } + + public void saveTrustAnchor(InputStream is) { + mDisposables.add(storeTrustAnchorUseCase.execute(is) + .subscribeOn(mSchedulersFacade.io()) + .observeOn(mSchedulersFacade.ui()) + .subscribe( + () -> mSuccess.setValue(true), + throwable -> { + mSuccess.setValue(false); + mError.setValue(new ViewModelError(Error.ROOT_CERTIFICATE, null)); + } + )); + } + + public void saveIntermediateCertificate(int credid, InputStream is) { + mDisposables.add(saveIntermediateCertificateUseCase.execute(credid, is) + .subscribeOn(mSchedulersFacade.io()) + .observeOn(mSchedulersFacade.ui()) + .subscribe( + () -> mSuccess.setValue(true), + throwable -> { + mSuccess.setValue(false); + mError.setValue(new ViewModelError(Error.INTERMEDIATE_CERTIFICATE, null)); + } + )); + } + + public void saveEndEntityCertificate(InputStream fileIs, InputStream keyIs) { + mDisposables.add(saveEndEntityCertificateUseCase.execute(fileIs, keyIs) + .subscribeOn(mSchedulersFacade.io()) + .observeOn(mSchedulersFacade.ui()) + .subscribe( + () -> mSuccess.setValue(true), + throwable -> { + mSuccess.setValue(false); + mError.setValue(new ViewModelError(Error.END_ENTITY_CERTIFICATE, null)); + } + )); + } + + public enum Error implements ViewModelErrorType { + ROOT_CERTIFICATE, + INTERMEDIATE_CERTIFICATE, + END_ENTITY_CERTIFICATE + } + +} diff --git a/otgc/src/main/java/org/openconnectivity/otgc/viewmodel/DeviceListViewModel.java b/otgc/src/main/java/org/openconnectivity/otgc/viewmodel/DeviceListViewModel.java index c9dd2dcec00f5d55e490ddfa829c5f129a787f0e..b8385b3ac3580fb2dca5fc0da6ad361d2ce96d9f 100644 --- a/otgc/src/main/java/org/openconnectivity/otgc/viewmodel/DeviceListViewModel.java +++ b/otgc/src/main/java/org/openconnectivity/otgc/viewmodel/DeviceListViewModel.java @@ -21,6 +21,8 @@ */ package org.openconnectivity.otgc.viewmodel; +import android.content.Context; + import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.ViewModel; @@ -36,6 +38,7 @@ import org.openconnectivity.otgc.domain.usecase.SetObtModeUseCase; import org.openconnectivity.otgc.domain.usecase.wifi.CheckConnectionUseCase; import org.openconnectivity.otgc.domain.usecase.InitializeIotivityUseCase; import org.openconnectivity.otgc.domain.usecase.login.LogoutUseCase; +import org.openconnectivity.otgc.utils.handler.DisplayNotValidCertificateHandler; import org.openconnectivity.otgc.utils.handler.OCSetRandomPinHandler; import org.openconnectivity.otgc.utils.viewmodel.CommonError; import org.openconnectivity.otgc.utils.viewmodel.Response; @@ -147,8 +150,8 @@ public class DeviceListViewModel extends ViewModel { return mDeviceId; } - public void initializeIotivityStack() { - disposables.add(mInitializeIotivityUseCase.execute() + public void initializeIotivityStack(Context context, DisplayNotValidCertificateHandler displayNotValidCertificateHandler) { + disposables.add(mInitializeIotivityUseCase.execute(context, displayNotValidCertificateHandler) .subscribeOn(schedulersFacade.io()) .observeOn(schedulersFacade.ui()) .subscribe( diff --git a/otgc/src/main/java/org/openconnectivity/otgc/viewmodel/TrustAnchorViewModel.java b/otgc/src/main/java/org/openconnectivity/otgc/viewmodel/TrustAnchorViewModel.java index 7849c2ce9af96f64213196da4ddf5ece69117eac..2b6b936abbe4c3d75b91f301f4d57228a169cc23 100644 --- a/otgc/src/main/java/org/openconnectivity/otgc/viewmodel/TrustAnchorViewModel.java +++ b/otgc/src/main/java/org/openconnectivity/otgc/viewmodel/TrustAnchorViewModel.java @@ -9,6 +9,8 @@ import androidx.lifecycle.ViewModel; 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.SaveEndEntityCertificateUseCase; +import org.openconnectivity.otgc.domain.usecase.trustanchor.SaveIntermediateCertificateUseCase; import org.openconnectivity.otgc.domain.usecase.trustanchor.StoreTrustAnchorUseCase; import org.openconnectivity.otgc.utils.rx.SchedulersFacade; import org.openconnectivity.otgc.utils.viewmodel.ViewModelError; @@ -31,6 +33,8 @@ public class TrustAnchorViewModel extends ViewModel { // Use cases private final StoreTrustAnchorUseCase storeTrustAnchorUseCase; + private final SaveIntermediateCertificateUseCase saveIntermediateCertificateUseCase; + private final SaveEndEntityCertificateUseCase saveEndEntityCertificateUseCase; private final GetTrustAnchorUseCase getTrustAnchorUseCase; private final RemoveTrustAnchorByCredidUseCase removeTrustAnchorByCredidUseCase; @@ -41,10 +45,14 @@ public class TrustAnchorViewModel extends ViewModel { @Inject public TrustAnchorViewModel(SchedulersFacade schedulersFacade, StoreTrustAnchorUseCase storeTrustAnchorUseCase, + SaveIntermediateCertificateUseCase saveIntermediateCertificateUseCase, + SaveEndEntityCertificateUseCase saveEndEntityCertificateUseCase, GetTrustAnchorUseCase getTrustAnchorUseCase, RemoveTrustAnchorByCredidUseCase removeTrustAnchorByCredidUseCase) { this.schedulersFacade = schedulersFacade; this.storeTrustAnchorUseCase = storeTrustAnchorUseCase; + this.saveIntermediateCertificateUseCase = saveIntermediateCertificateUseCase; + this.saveEndEntityCertificateUseCase = saveEndEntityCertificateUseCase; this.getTrustAnchorUseCase = getTrustAnchorUseCase; this.removeTrustAnchorByCredidUseCase = removeTrustAnchorByCredidUseCase; } @@ -70,7 +78,7 @@ public class TrustAnchorViewModel extends ViewModel { return deleteCredid; } - public void retrieveTrustAnchors() { + public void retrieveCertificates() { disposable.add(getTrustAnchorUseCase.execute() .subscribeOn(schedulersFacade.io()) .observeOn(schedulersFacade.ui()) @@ -90,12 +98,32 @@ public class TrustAnchorViewModel extends ViewModel { .subscribeOn(schedulersFacade.io()) .observeOn(schedulersFacade.ui()) .subscribe( - () -> retrieveTrustAnchors(), + () -> retrieveCertificates(), throwable -> mError.setValue(new ViewModelError(Error.ADD_ROOT_CERT, throwable.getMessage())) )); } - public void removeTrustAnchorByCredid(long credid) { + public void saveIntermediateCertificate(Integer credid, InputStream is) { + disposable.add(saveIntermediateCertificateUseCase.execute(credid, is) + .subscribeOn(schedulersFacade.io()) + .observeOn(schedulersFacade.ui()) + .subscribe( + () -> retrieveCertificates(), + throwable -> mError.setValue(new ViewModelError(Error.ADD_ROOT_CERT, throwable.getMessage())) + )); + } + + public void saveEndEntityCertificate(InputStream fileIs, InputStream keyIs) { + disposable.add(saveEndEntityCertificateUseCase.execute(fileIs, keyIs) + .subscribeOn(schedulersFacade.io()) + .observeOn(schedulersFacade.ui()) + .subscribe( + () -> retrieveCertificates(), + throwable -> mError.setValue(new ViewModelError(Error.ADD_ROOT_CERT, throwable.getMessage())) + )); + } + + public void removeCertificateByCredid(long credid) { disposable.add(removeTrustAnchorByCredidUseCase.execute(credid) .subscribeOn(schedulersFacade.io()) .observeOn(schedulersFacade.ui()) diff --git a/otgc/src/main/res/drawable-v23/ic_ocf_logo_white.png b/otgc/src/main/res/drawable-v23/ic_ocf_logo_white.png new file mode 100644 index 0000000000000000000000000000000000000000..8d6315ad5b10c2b87d8ac4970d730826e5bc9e6d Binary files /dev/null and b/otgc/src/main/res/drawable-v23/ic_ocf_logo_white.png differ diff --git a/otgc/src/main/res/drawable-v23/ic_ocf_logo_white.xml b/otgc/src/main/res/drawable-v23/ic_ocf_logo_white_old.xml similarity index 100% rename from otgc/src/main/res/drawable-v23/ic_ocf_logo_white.xml rename to otgc/src/main/res/drawable-v23/ic_ocf_logo_white_old.xml diff --git a/otgc/src/main/res/drawable-v23/splash.xml b/otgc/src/main/res/drawable-v23/splash.xml index 93fa1f498b3be517affdd86f5acbe5c4216080d4..4f2e0696f27cc3d3ecf5f6d95f9f2be7716442ea 100644 --- a/otgc/src/main/res/drawable-v23/splash.xml +++ b/otgc/src/main/res/drawable-v23/splash.xml @@ -23,8 +23,10 @@ - + android:drawable="@color/colorPrimary" /> + + + \ No newline at end of file diff --git a/otgc/src/main/res/drawable/ocf_logo_horizontal.png b/otgc/src/main/res/drawable/ocf_logo_horizontal.png index e588d337bd0af4b541374d85f013b8ed7d6c0146..3ee083e7622aa22bef04b96f092d9757030462e5 100644 Binary files a/otgc/src/main/res/drawable/ocf_logo_horizontal.png and b/otgc/src/main/res/drawable/ocf_logo_horizontal.png differ diff --git a/otgc/src/main/res/layout/activity_certificate.xml b/otgc/src/main/res/layout/activity_certificate.xml new file mode 100644 index 0000000000000000000000000000000000000000..808762b49514f1e383d22f8184e22796422ddd88 --- /dev/null +++ b/otgc/src/main/res/layout/activity_certificate.xml @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + +