diff --git a/app/build.gradle b/app/build.gradle index 47c462ded..962c6db73 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -177,7 +177,7 @@ dependencies { implementation "com.madgag.spongycastle:bctls-jdk15on:1.58.0.0" //implementation 'org.bouncycastle:bcprov-jdk15on:1.64' - implementation 'com.github.UnifiedPush:android-connector:1.0.0' + implementation 'com.github.UnifiedPush:android-connector:dev-SNAPSHOT' //Flavors //Playstore diff --git a/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java b/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java index 525f7783e..313ec321c 100644 --- a/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java @@ -423,8 +423,7 @@ public abstract class BaseMainActivity extends BaseActivity if (social == UpdateAccountInfoAsyncTask.SOCIAL.MASTODON || social == UpdateAccountInfoAsyncTask.SOCIAL.PLEROMA) { - - new Registration().registerAppWithDialog(BaseMainActivity.this); + new Registration().registerAppWithDialog(BaseMainActivity.this, account.getUsername() + "@" + account.getInstance()); } if (social == UpdateAccountInfoAsyncTask.SOCIAL.MASTODON || social == UpdateAccountInfoAsyncTask.SOCIAL.PLEROMA || social == UpdateAccountInfoAsyncTask.SOCIAL.GNU || social == UpdateAccountInfoAsyncTask.SOCIAL.FRIENDICA) { new SyncTimelinesAsyncTask(BaseMainActivity.this, 0, Helper.canFetchList(BaseMainActivity.this, account), BaseMainActivity.this); diff --git a/app/src/main/java/app/fedilab/android/asynctasks/PostSubscriptionAsyncTask.java b/app/src/main/java/app/fedilab/android/asynctasks/PostSubscriptionAsyncTask.java index 1dbd6c446..b9acfea3e 100644 --- a/app/src/main/java/app/fedilab/android/asynctasks/PostSubscriptionAsyncTask.java +++ b/app/src/main/java/app/fedilab/android/asynctasks/PostSubscriptionAsyncTask.java @@ -15,30 +15,23 @@ package app.fedilab.android.asynctasks; import android.content.Context; -import android.os.Handler; -import android.os.Looper; import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.List; import app.fedilab.android.client.API; import app.fedilab.android.client.APIResponse; import app.fedilab.android.client.Entities.Account; -import app.fedilab.android.interfaces.OnPostSubscription; public class PostSubscriptionAsyncTask { - private final OnPostSubscription listener; private final WeakReference contextReference; private APIResponse apiResponse; private final String endpoint; private final Account account; - public PostSubscriptionAsyncTask(Context context, Account account, String endpoint, OnPostSubscription onPostSubscription) { + public PostSubscriptionAsyncTask(Context context, Account account, String endpoint) { this.contextReference = new WeakReference<>(context); - this.listener = onPostSubscription; this.endpoint = endpoint; this.account = account; doInBackground(); @@ -51,14 +44,6 @@ public class PostSubscriptionAsyncTask { if (apiResponse == null || apiResponse.getPushSubscription() == null || endpoint.compareTo(apiResponse.getPushSubscription().getEndpoint()) != 0) { apiResponse = new API(contextReference.get(), account.getInstance(), account.getToken()).pushSubscription(endpoint, account); } - //TODO: remove this line - apiResponse = new API(contextReference.get(), account.getInstance(), account.getToken()).pushSubscription(endpoint, account); - List accountList = new ArrayList<>(); - accountList.add(account); - apiResponse.setAccounts(accountList); - Handler mainHandler = new Handler(Looper.getMainLooper()); - Runnable myRunnable = () -> listener.onSubscription(apiResponse); - mainHandler.post(myRunnable); }).start(); } diff --git a/app/src/main/java/app/fedilab/android/helper/ECDH.java b/app/src/main/java/app/fedilab/android/helper/ECDH.java index 3e4cda9f0..e60173d51 100644 --- a/app/src/main/java/app/fedilab/android/helper/ECDH.java +++ b/app/src/main/java/app/fedilab/android/helper/ECDH.java @@ -1,5 +1,20 @@ package app.fedilab.android.helper; +/* Copyright 2021 Thomas Schneider + * + * This file is a part of Fedilab + * + * 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. + * + * Fedilab 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 Fedilab; if not, + * see . */ +import android.annotation.SuppressLint; import android.content.Context; import android.content.SharedPreferences; import android.util.Base64; @@ -12,7 +27,9 @@ import org.spongycastle.asn1.ASN1ObjectIdentifier; import org.spongycastle.asn1.x9.ECNamedCurveTable; import org.spongycastle.asn1.x9.X9ECParameters; import org.spongycastle.crypto.params.ECNamedDomainParameters; +import org.spongycastle.crypto.params.ECPrivateKeyParameters; import org.spongycastle.crypto.params.ECPublicKeyParameters; +import org.spongycastle.jce.spec.ECNamedCurveSpec; import org.spongycastle.math.ec.ECCurve; import org.spongycastle.math.ec.ECPoint; @@ -27,7 +44,6 @@ import java.security.PublicKey; import java.security.Security; import java.security.interfaces.ECPublicKey; import java.security.spec.ECGenParameterSpec; -import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import javax.crypto.KeyAgreement; @@ -40,25 +56,25 @@ public class ECDH { private static final String kp_public = "kp_public"; + public static final String peer_public = "peer_public"; + public static final String PROVIDER = org.spongycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME; + private static final String kp_private = "kp_private"; - private static final String peer_public = "peer_public"; + public static final String KEGEN_ALG = "ECDH"; private static final String TAG = ECDH.class.getSimpleName(); - - private static final String PROVIDER = org.spongycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME; - - private static final String KEGEN_ALG = "ECDH"; + private static final String kp_public_affine_x = "kp_public_affine_x"; + private static final String kp_public_affine_y = "kp_public_affine_y"; private static ECDH instance; static { - // Security.removeProvider("BC"); Security.addProvider(new org.spongycastle.jce.provider.BouncyCastleProvider()); } - private final KeyFactory kf; - private final KeyPairGenerator kpg; + public static KeyFactory kf = null; + private static KeyPairGenerator kpg = null; public ECDH() { @@ -89,7 +105,7 @@ public class ECDH { return Base64.decode(str, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP); } - synchronized KeyPair generateKeyPair() + static synchronized KeyPair generateKeyPair() throws Exception { ECGenParameterSpec ecParamSpec = new ECGenParameterSpec("prime256v1"); kpg.initialize(ecParamSpec); @@ -97,7 +113,7 @@ public class ECDH { return kpg.generateKeyPair(); } - byte[] generateSecret(PrivateKey myPrivKey, PublicKey otherPubKey) throws Exception { + private static byte[] generateSecret(PrivateKey myPrivKey, PublicKey otherPubKey) throws Exception { ECPublicKey ecPubKey = (ECPublicKey) otherPubKey; Log.d(TAG, "public key Wx: " + ecPubKey.getW().getAffineX().toString(16)); @@ -111,25 +127,14 @@ public class ECDH { return keyAgreement.generateSecret(); } - synchronized PublicKey readPublicKey(String keyStr) throws Exception { - X509EncodedKeySpec x509ks = new X509EncodedKeySpec( - base64Decode(keyStr)); - return kf.generatePublic(x509ks); - } - synchronized PrivateKey readPrivateKey(String keyStr) throws Exception { - PKCS8EncodedKeySpec p8ks = new PKCS8EncodedKeySpec( - base64Decode(keyStr)); - - return kf.generatePrivate(p8ks); - } - - synchronized KeyPair readKeyPair(String pubKeyStr, String privKeyStr) + static synchronized KeyPair readKeyPair(Context context) throws Exception { - return new KeyPair(readPublicKey(pubKeyStr), readPrivateKey(privKeyStr)); + return new KeyPair(readMyPublicKey(context), readMyPrivateKey(context)); } - KeyPair newPair(Context context) { + @SuppressLint("ApplySharedPref") + static KeyPair newPair(Context context) { SharedPreferences.Editor prefsEditor = PreferenceManager .getDefaultSharedPreferences(context).edit(); KeyPair kp; @@ -139,6 +144,7 @@ public class ECDH { e.printStackTrace(); return null; } + ECPublicKey key = (ECPublicKey) kp.getPublic(); byte[] x = key.getW().getAffineX().toByteArray(); byte[] y = key.getW().getAffineY().toByteArray(); @@ -146,62 +152,95 @@ public class ECDH { BigInteger ybi = new BigInteger(1, y); X9ECParameters x9 = ECNamedCurveTable.getByName("prime256v1"); ASN1ObjectIdentifier oid = ECNamedCurveTable.getOID("prime256v1"); + ECCurve curve = x9.getCurve(); ECPoint point = curve.createPoint(xbi, ybi); ECNamedDomainParameters dParams = new ECNamedDomainParameters(oid, x9.getCurve(), x9.getG(), x9.getN(), x9.getH(), x9.getSeed()); + ECPublicKeyParameters pubKey = new ECPublicKeyParameters(point, dParams); - String keyString = base64Encode(pubKey.getQ().getEncoded(false)); + ECPrivateKeyParameters privateKey = new ECPrivateKeyParameters(new BigInteger(kp.getPrivate().getEncoded()), pubKey.getParameters()); + byte[] privateKeyBytes = privateKey.getD().toByteArray(); + + String keyString = base64Encode(pubKey.getQ().getEncoded(false)); + String keypString = base64Encode(privateKeyBytes); prefsEditor.putString(kp_public, keyString); - prefsEditor.putString(kp_private, base64Encode(kp.getPrivate().getEncoded())); + prefsEditor.putString(kp_public_affine_x, key.getW().getAffineX().toString()); + prefsEditor.putString(kp_public_affine_y, key.getW().getAffineY().toString()); + prefsEditor.putString(kp_private, keypString); prefsEditor.commit(); return kp; } - synchronized KeyPair getPair(Context context) { + + static synchronized PublicKey readMyPublicKey(Context context) throws Exception { + + X9ECParameters x9 = ECNamedCurveTable.getByName("prime256v1"); + ASN1ObjectIdentifier oid = ECNamedCurveTable.getOID("prime256v1"); + SharedPreferences prefs = PreferenceManager .getDefaultSharedPreferences(context); + BigInteger xbi = new BigInteger(prefs.getString(kp_public_affine_x, "0")); + BigInteger ybi = new BigInteger(prefs.getString(kp_public_affine_y, "0")); + ECNamedDomainParameters dParams = new ECNamedDomainParameters(oid, + x9.getCurve(), x9.getG(), x9.getN(), x9.getH(), x9.getSeed()); + + + ECNamedCurveSpec ecNamedCurveSpec = new ECNamedCurveSpec("prime256v1", dParams.getCurve(), dParams.getG(), dParams.getN()); + java.security.spec.ECPoint w = new java.security.spec.ECPoint(xbi, ybi); + return kf.generatePublic(new java.security.spec.ECPublicKeySpec(w, ecNamedCurveSpec)); + } + + + static synchronized PublicKey readPublicKey(String keyStr) throws Exception { + X509EncodedKeySpec x509ks = new X509EncodedKeySpec( + base64Decode(keyStr)); + return kf.generatePublic(x509ks); + } + + + static synchronized PrivateKey readMyPrivateKey(Context context) throws Exception { + X9ECParameters x9 = ECNamedCurveTable.getByName("prime256v1"); + ASN1ObjectIdentifier oid = ECNamedCurveTable.getOID("prime256v1"); + + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(context); + BigInteger ybi = new BigInteger(prefs.getString(kp_public_affine_y, "0")); + ECNamedDomainParameters dParams = new ECNamedDomainParameters(oid, + x9.getCurve(), x9.getG(), x9.getN(), x9.getH(), x9.getSeed()); + ECNamedCurveSpec ecNamedCurveSpec = new ECNamedCurveSpec("prime256v1", dParams.getCurve(), dParams.getG(), dParams.getN()); + return kf.generatePrivate(new java.security.spec.ECPrivateKeySpec(ybi, ecNamedCurveSpec)); + } + + + private static synchronized KeyPair getPair(Context context) { + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(context); String strPub = prefs.getString(kp_public, ""); String strPriv = prefs.getString(kp_private, ""); - if (strPub.isEmpty() || strPriv.isEmpty()) { return newPair(context); } try { - return readKeyPair(strPub, strPriv); + return readKeyPair(context); } catch (Exception e) { e.printStackTrace(); } return null; } - public String getPublicKey(Context context) { + static PublicKey getServerKey(Context context, Account account) throws Exception { SharedPreferences prefs = PreferenceManager .getDefaultSharedPreferences(context); - - String strPub = prefs.getString(kp_public, ""); - return strPub; + String serverKey = prefs.getString(peer_public + account.getId() + account.getInstance(), ""); + return readPublicKey(serverKey); } - public void saveServerKey(Context context, Account account, String strPeerPublic) { - SharedPreferences.Editor prefsEditor = PreferenceManager - .getDefaultSharedPreferences(context).edit(); - - prefsEditor.putString(peer_public + account.getId() + account.getInstance(), strPeerPublic); - prefsEditor.commit(); - } - - PublicKey getServerKey(Context context, Account account) throws Exception { - return readPublicKey( - PreferenceManager.getDefaultSharedPreferences(context) - .getString(peer_public + account.getId() + account.getInstance(), "") - ); - } - - byte[] getSecret(Context context, Account account) { + @SuppressWarnings({"unused", "RedundantSuppression"}) + public static byte[] getSharedSecret(Context context, Account account) { try { return generateSecret(getPair(context).getPrivate(), getServerKey(context, account)); } catch (Exception e) { @@ -209,4 +248,20 @@ public class ECDH { return null; } } + + public String getPublicKey(Context context) { + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(context); + + return prefs.getString(kp_public, ""); + } + + @SuppressLint("ApplySharedPref") + public void saveServerKey(Context context, Account account, String strPeerPublic) { + SharedPreferences.Editor prefsEditor = PreferenceManager + .getDefaultSharedPreferences(context).edit(); + + prefsEditor.putString(peer_public + account.getId() + account.getInstance(), strPeerPublic); + prefsEditor.commit(); + } } diff --git a/app/src/main/java/app/fedilab/android/helper/PushNotifications.java b/app/src/main/java/app/fedilab/android/helper/PushNotifications.java index 451787fd7..146b68aab 100644 --- a/app/src/main/java/app/fedilab/android/helper/PushNotifications.java +++ b/app/src/main/java/app/fedilab/android/helper/PushNotifications.java @@ -1,67 +1,44 @@ package app.fedilab.android.helper; - +/* Copyright 2021 Thomas Schneider + * + * This file is a part of Fedilab + * + * 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. + * + * Fedilab 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 Fedilab; if not, + * see . */ import android.content.Context; -import android.content.SharedPreferences; import android.database.sqlite.SQLiteDatabase; -import android.util.Base64; -import android.util.Log; -import org.unifiedpush.android.connector.Registration; -import java.util.ArrayList; import java.util.List; -import java.util.Random; - -import app.fedilab.android.activities.BaseMainActivity; import app.fedilab.android.asynctasks.PostSubscriptionAsyncTask; -import app.fedilab.android.client.APIResponse; + import app.fedilab.android.client.Entities.Account; -import app.fedilab.android.interfaces.OnPostSubscription; import app.fedilab.android.sqlite.AccountDAO; import app.fedilab.android.sqlite.Sqlite; -import static android.content.Context.MODE_PRIVATE; -public class PushNotifications implements OnPostSubscription { +public class PushNotifications { - private Context context; public void registerPushNotifications(Context context, String endpoint) { - this.context = context; SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); List accountPush = new AccountDAO(context, db).getPushNotificationAccounts(); for (Account account : accountPush) { - new PostSubscriptionAsyncTask(context, account, endpoint, this); + new PostSubscriptionAsyncTask(context, account, endpoint); } } - public static void getDistributors(Context context) { - List distributors = new Registration().getDistributors(context); - if (distributors.isEmpty()) { - - } else { - - } - } public void displayNotification(Context context, String ciphered) { - ECDH ecdh = new ECDH(); - SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); - List accountPush = new AccountDAO(context, db).getPushNotificationAccounts(); - List trousseau = new ArrayList<>(); - for (Account account : accountPush) { - byte[] secret = ecdh.getSecret(context, account); - } - - - //process with the event - // https://openacs.org/webpush-demo/report.html - // decrypt using AES 128 GCM } - @Override - public void onSubscription(APIResponse apiResponse) { - //TODO je ne sais pas si c'est toujours utile - } } diff --git a/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java b/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java index 686c7afff..8595731da 100644 --- a/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java +++ b/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java @@ -1,37 +1,40 @@ package app.fedilab.android.services; - +/* Copyright 2021 Thomas Schneider + * + * This file is a part of Fedilab + * + * 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. + * + * Fedilab 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 Fedilab; if not, + * see . */ import android.content.Context; import android.content.SharedPreferences; -import android.database.sqlite.SQLiteDatabase; -import android.util.Log; - import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.unifiedpush.android.connector.MessagingReceiver; import org.unifiedpush.android.connector.MessagingReceiverHandler; - -import java.util.List; - -import app.fedilab.android.activities.BaseMainActivity; -import app.fedilab.android.activities.LiveNotificationSettingsAccountsActivity; -import app.fedilab.android.asynctasks.PostSubscriptionAsyncTask; -import app.fedilab.android.client.APIResponse; -import app.fedilab.android.client.Entities.Account; import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.PushNotifications; -import app.fedilab.android.interfaces.OnPostSubscription; -import app.fedilab.android.sqlite.AccountDAO; -import app.fedilab.android.sqlite.Sqlite; import static android.content.Context.MODE_PRIVATE; class handler implements MessagingReceiverHandler { - private Context context; - private String endpoint; @Override - public void onNewEndpoint(@Nullable Context context, @NotNull String endpoint) { + public void onMessage(@Nullable Context context, @NotNull String s, @NotNull String s1) { + new PushNotifications() + .displayNotification(context, s); + } + + @Override + public void onNewEndpoint(@Nullable Context context, @NotNull String endpoint, @NotNull String s1) { final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); SharedPreferences.Editor editor = sharedpreferences.edit(); editor.putString(Helper.SERVER_ENDPOINT, endpoint); @@ -42,24 +45,18 @@ class handler implements MessagingReceiverHandler { } @Override - public void onRegistrationFailed(@Nullable Context context) { - // Toast ? + public void onRegistrationFailed(@Nullable Context context, @NotNull String s) { + } @Override - public void onRegistrationRefused(@Nullable Context context) { - // Toast ? + public void onRegistrationRefused(@Nullable Context context, @NotNull String s) { + } @Override - public void onUnregistered(@Nullable Context context) { - // Remove endpoint & ServerKey - } + public void onUnregistered(@Nullable Context context, @NotNull String s) { - @Override - public void onMessage(@Nullable Context context, @NotNull String message) { - new PushNotifications() - .displayNotification(context, message); } }