From a39129f555723eda2515d0872c8591f27f301e21 Mon Sep 17 00:00:00 2001 From: AbdurazaaqMohammed Date: Sun, 11 Aug 2024 21:11:27 -0400 Subject: [PATCH] 1.6.3.7 Try to fix sign failing on some/older Android versions --- app/build.gradle.kts | 8 +- app/src/main/assets/debug23.keystore | Bin 0 -> 2153 bytes app/src/main/assets/testkey.past | Bin 0 -> 1458 bytes app/src/main/assets/testkey.pk8 | Bin 0 -> 1217 bytes .../AntiSplit/main/MainActivity.java | 53 ++++++-- .../AntiSplit/main/SignUtil.java | 93 +++++++++++++ .../java/com/android/apksig/ApkSigner.java | 6 +- .../apksig/internal/jar/ManifestParser.java | 16 ++- .../internal/util/ByteArrayDataSink.java | 15 ++- .../internal/util/VerityTreeBuilder.java | 126 ++++++++++++------ .../internal/util/X509CertificateUtils.java | 2 +- .../internal/zip/CentralDirectoryRecord.java | 9 +- .../apksig/util/RunnablesExecutor.java | 1 + .../angads25/filepicker/utils/Utility.java | 3 +- .../github/paul035}/LocaleHelper.java | 3 +- .../com/reandroid/apkeditor/merge/Merger.java | 50 +------ app/src/main/java/com/starry/FileUtils.java | 23 +--- 17 files changed, 278 insertions(+), 130 deletions(-) create mode 100644 app/src/main/assets/debug23.keystore create mode 100644 app/src/main/assets/testkey.past create mode 100644 app/src/main/assets/testkey.pk8 create mode 100644 app/src/main/java/com/abdurazaaqmohammed/AntiSplit/main/SignUtil.java rename app/src/main/java/{ankur035 => com/github/paul035}/LocaleHelper.java (95%) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5164e0a4..7127f058 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -10,8 +10,8 @@ android { applicationId = "com.abdurazaaqmohammed.AntiSplit" minSdk = 4 targetSdk = 35 - versionCode = 21 - versionName = "1.6.3.6" + versionCode = 22 + versionName = "1.6.3.7" multiDexEnabled = true } @@ -35,7 +35,9 @@ android { } dependencies { implementation("com.android.support:multidex:1.0.3") + //implementation("org.bouncycastle:bcprov-jdk15to18:1.78.1") + // implementation("org.bouncycastle:bcpkix-jdk15to18:1.78.1") + // implementation("com.madgag.spongycastle:core:1.58.0.0") coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4") - } } \ No newline at end of file diff --git a/app/src/main/assets/debug23.keystore b/app/src/main/assets/debug23.keystore new file mode 100644 index 0000000000000000000000000000000000000000..c852a343f6e5f06ea88302260e774ea758e826bc GIT binary patch literal 2153 zcmbVLc{tP!7yZpD+sqgY28lE!OJm<&K06^x$cVwz$T|$g#K=}!q!6-{Eo+5Fh!*?4 zlzq!CB#LirV@bVwzW0y!@9)2R&-0vn?mg$Z004jh0N~Rp8?l7C4{5IeqW5FfrUfgg zXKDceW)B6BBwuoXpC_5@b}7)^+b#HzkYIo=c0FW)02o0|8Ltii5MF!~1c{FVv9cjx z5Eu;Q?hL+&=Z3SNzHM@w#SH>+!T`LgJSUzV4zYv6dBJuz@+dswPs72>ru#nyc?_QK z4+7!k{s%c`@JAGXOn_TK39lxvfX6Ez78eDP3V3CCc?AXae}MmgbQDP9-)n+k03-^8 z1P(9Z07rp90E4N(>i)GKmh0SD6*N#v>|3dBDj87@djg@8gVtp^-_4n{ZyRvulQF7%j>A0~(TxbQR$kkO{~IS20AYD5Hi> zbFlwj!RD*?`T+hX#^Q7buv4(6ZLC1hlx6Xkg1WRjVz(9J<^z=uc49F ztuhK20s=s};&?H<=wURX2q*^1CuhkSKglOF)l(N}H?}71{X9)?;NK=5W_@TP2r!~* z(TT3AV%-cM@MA~Ya`hB0N5WhH&zU9u=z>-((Qi zWgR9S88{0JlMMM{m6(^$EPdi$8?I(MW%U-a7&x1 zUSxgL$ked=a~I<~`lQA_iMI=NngzN7ukSIWCygCn;U2W!DM;{WR_?=CE^Osivh9Uz zkSjGD2VOYgk}8lNe9lWv9?jqU^o|lPoPUo9={s#`%OmaQ@7iYN!y75`(S_*wdfD$G zau`;4U@WZ9h`Fyl@6%^%-K#$t5;;F!wAEnd5lbB1Bo-*rS>t)Rk*^yXvH$?O`RBLN zizG@H#N5xHztdz@5=mcDyERmN_*+<$kw#V;)pRv&rTlB>F^o)2sWZZtJ`Tq^YGnm00x(0#fZDgm#LREsu)uhWcRnWf@H>I~tm_b2`dezOF z&2KIYN?H-d5}UN>wsMHC_omuWoEwzdotfn3u%21JW|eoGR>NIMt8dnxHTGRSSAR8^ ziMknilS2LWtln!8EN!1CqeM&!N1&OabbO7MS90acyV}hZ_Vom|0zXm;VTF1euITRi z#=${jYdpZnw7uJLa}%ag-aACF=x?e%D~3`WJHq8X?#>l#*^zlPZdf|}{n_`Z@KU9h zr4(UTT*q5p>{EGP}A2g!u&76zw>x zl!Vh~AFP`NbF5!4r4?2pPVJo5ep>N)F0S$ij!p0!;9{>+W1zpc@xRE_x_ zT)8S&UKuKO*I_$F91pz%@u?p5f5IZhpkMnM)xy=$a$&h_G%(CC^&?4@HE|_P(W}3p zd&gZ0XE>Xd@l7hh0?na#p)Po084i~hbqTG1xejf0u)H7o0ic+;BQT)N&KvMO|zM0T(BQ0x)eWALaBW#>xM&f zmuaqBtV))P)jp5l-)Bp0Efk^eL$PJ*O_-ez&a#1D9-tZK=Fq5l7Vdl2*97ZU=Xp$Y zl9y1Xnz_P3$@SZswfiO^w`JdK#qhojUnxL?hlIbg`mXEl#Cb@1d9Ngj!FmQ5Y+9P& z*)bk)PU%2E!Vl4AT4le6u#RjU){`B|pm{n4mdjf#hqc|#d|ZzS9eD3&MU(0^a7b_U zho^g|IjuCQX)ZQ}KQCw1_B|BLE+S~j(!_r!p6HU`dSt88rp%k^z?7g2OM%#D1G!s! zB@gb(z4+b6lax2PI=8QPBO|pUAGB4uhIq1My$X-0RK{w&>eecro}S7KE>A?X<3%6E zNcx#9P1MY78!)7PX+7De9O-XC|Ll+>qJcu1d@s_A8!=p=n?4P4u0;H8qkVV|5W%4Q>&U7W5 zTf?(n)hF*zff|p^MGI6DR`A1Lj$=TrIaPR<*^+TH1#D`o%lpvpi%TRyY2MgqKd5tp zu(+9^n=`*565UxF{rTj`tbk>NZnf$vRmH7mAmcx1-hiNpo$?!vsr`gurz7tfE$PYT kx=|#|ihZ|Wa#SYp!(nLI3~& literal 0 HcmV?d00001 diff --git a/app/src/main/assets/testkey.past b/app/src/main/assets/testkey.past new file mode 100644 index 0000000000000000000000000000000000000000..37b7151ed29bc18112b92800e8a671a4f766e95c GIT binary patch literal 1458 zcmXqLVq3??snzDu_MMlJooPW6+kAs2w%JUKjE39>oNTPxe9TNztPBR+2t|wwnpoBt zG_kBOXkwnQfSHMriHVb8a^9MK?4KC#8}PyvGO_~oHBJGW!yL-OCd?EXY$$9X$Ohza z2y;0n=47Vj7v*Ip8j2Z+fCSlvd42Os^GXsk^Ay4|Q_Bqn4ER9eT*B;*c_~HtnJF+4 zZVVA-gou)X0^ALpjAFux5WNmiS}!?2*Fa93*TBNS$jH*b(Ae0-EJ~c$*wnz>&>YGg ztj=y?R6q`3Mpg#qCPrQcgC<4}rY1&4hHH~0S?(RE6!$LuE<-?wLIq;-){byrjh?|+Lf<#e*VJNJ7vd{e~T_Z3;XyeeTDS?O$J*l z4zW1z^C^7_>R}-043f44tApl z4b#QnWw9DZK3g{Bjn3vzEen>Uov4~}!`hJ3D&1U9KSk(3Uar%}2#Ndum>bXVTyeRT zbNBELl?X`>#?7iJ!v6d3%{Zx1?s5BTdR7Q;W5|i!r`UHsat}2YJd!=@d;YyQXZkXq zpICIEuR={>iv7%@S{s&sMKfh*-k-hBV! z9LEFliZBbS0W%}xf8^u=%zwb-!N^dxep=7#fFq8qff5&PtzOR<-BlK)zFc(Cp)LMO zBBK9qonz*V+P_5iJ3B{FjzZ?s!+Cetu`uk@bk*VTK2*1DVcnX!`y_M}{5A#)ehIyy zvMucPlRHWd6Lm}5{&h^1WnA%Z>$)hXgy7g8CWfpkN^U)7k94%+eonb2d)*-G$;oe1 zr57L6mz5N`drV#`>6Y#f^>57omL^#1gz)aIVVq;OYtt;J!h_=XgKTae{oGjP`QZIB zp1t)Y;&oO2lD5uYl-P^X@_c-oAMTB6E*&v=xBO6+ zjOXPtu87t3QcY(jcY7FGIIHYsVf-Z|W)%?5m*H#J#CQsrMUDXrHG{@QgEU5w%TT0p O7pXV}=604QMg{<8&)ls5 literal 0 HcmV?d00001 diff --git a/app/src/main/assets/testkey.pk8 b/app/src/main/assets/testkey.pk8 new file mode 100644 index 0000000000000000000000000000000000000000..586c1bd5cf96f9358f36b37ea98fef93f4d0a8e3 GIT binary patch literal 1217 zcmV;y1U~yPf&{$+0RS)!1_>&LNQUrr!ay9qXGc{0)hbn0M?Tk1m4CA zB(d$sV&K`>B|bjQZ0jB3{|1bx=)9=SYtwjcrwXTR<_^0LnKfDe**XXo? z!`w>qP$CUb2nRCYfR`8UY6UY{=BAYFEVJ^2prmHVdX(5ZF$p?nH!d$^62NY4Lh)D_ z-~I!E%nZ~-+HBp!xFlE^NCC4YWD`%n-k8ZKcSzgyXKGXpfmF!5$_KgPM^!Ts#A}-O zZ{F<8j%nw}qR@_bCLokQnR0zR1pacF9hu*ot~j8w7k{H2TWbF49|8db0)hbn0FMm2 zSd@eZZOil8LF+r^ZYh5=o$?dxssvrd@O{PJN8(UP#IeNlLarW*N_QWU=dJbtwUa9MNB6mRRfiD@u-F4570O|hs88oP`H<{-WtCyW#0(F+HjcFBTNtPVs zqOAxo7-A$eO)H3r6sgRe?MH7gF5FJ$Tk3bck@E0NKQy`twmC#7`?TOyTgJ~$><(c= zegFe}?aLXyS&y+x7cyW?K{hgS`ZOu%e`OsJB7tT%e$vUP84FySkKHZd#R8E)%>sde z0Gxnk4`^q&#Rvr|X}T^DSmg_(X~iRQqj#>*`b>&xVy7{s?@%B>l*r?UT zWofQjN z+qr0%Y;#)55J7EtGjdx74aSIRNZLvuA3cfg=kcW}3-?Ohe|UTbd3%Dz{Ubm)=h@$h z^0YwLsa02~o`+_B=3mQ)WXwP_fX>WJrp<9hszYeD7Xa?hso~qjW(SiJU+Bp}E&;YH zGXjBtV=ArL8r`H~*_EbpVp|2xsYW66=~;8`kD4v(aiPJi8`ByM)}o1ZhZ|~p>uyzH zw4p3uqoir?dUseWHOKf>xwmY}&%k#`MgFNpFR|mPj0O9er&4Gc#l%UD4Z+(RrHVV$ f`+ { int curr = -1; - /* - for(int i=0; i { - updateLang(LocaleHelper.setLocale(MainActivity.this, lang = rss.getStringArray(R.array.langs)[which]).getResources()); + updateLang(LocaleHelper.setLocale(MainActivity.this, lang = langs[which]).getResources()); dialog.dismiss(); }).create(), display); }); @@ -538,6 +546,29 @@ private void processOneSplitApkUri(Uri uri) { else selectDirToSaveAPKOrSaveNow(); } + public static void signApk(InputStream key, String password, File inputApk, File output) throws KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException, ApkFormatException, SignatureException, InvalidKeyException, UnrecoverableEntryException { + signApk(key, password, inputApk, output, true, true, true); + } + + public static void signApk(InputStream key, String password, File inputApk, File output, boolean v1, boolean v2, boolean v3) throws KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException, ApkFormatException, SignatureException, InvalidKeyException, UnrecoverableEntryException { + char[] pw = password.toCharArray(); + + KeyStore keystore = KeyStore.getInstance("PKCS12"); + keystore.load(key, pw); + + String alias = keystore.aliases().nextElement(); + + new ApkSigner.Builder(Collections.singletonList(new ApkSigner.SignerConfig.Builder("CERT", + ((KeyStore.PrivateKeyEntry) keystore.getEntry(alias, new KeyStore.PasswordProtection(pw))).getPrivateKey(), + Collections.singletonList((X509Certificate) keystore.getCertificate(alias))).build())) + .setInputApk(inputApk) + .setOutputApk(output) + .setCreatedBy("Android Gradle 8.0.2") + .setV1SigningEnabled(v1) + .setV2SigningEnabled(v2) + .setV3SigningEnabled(v3).build().sign(); + } + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { @@ -698,8 +729,8 @@ private void showSuccess() { } private void showError(Exception e) { - errorOccurred = true; final String mainErr = e.toString(); + errorOccurred = !mainErr.equals(rss.getString(R.string.sign_failed)); StringBuilder stackTrace = new StringBuilder().append(mainErr).append('\n'); for(StackTraceElement line : e.getStackTrace()) stackTrace.append(line).append('\n'); runOnUiThread(() -> { @@ -762,7 +793,7 @@ private void selectDirToSaveAPKOrSaveNow() { getAntisplitMFolder() + File.separator + getOriginalFileName(this, splitAPKUri) // If originalFilePath is null urisAreSplitApks must be true because getNameFromNonSplitApks will always return something : originalFilePath.replaceFirst("\\.(?:xapk|aspk|apk[sm])", "_antisplit.apk"); if(TextUtils.isEmpty(newFilePath) || - newFilePath.startsWith("/data/") // when a file is shared it in /data/ + newFilePath.startsWith("/data/") // || !(f = new File(newFilePath)).createNewFile() || f.canWrite() ) { f = new File(getAntisplitMFolder(), newFilePath.substring(newFilePath.lastIndexOf(File.separator) + 1)); diff --git a/app/src/main/java/com/abdurazaaqmohammed/AntiSplit/main/SignUtil.java b/app/src/main/java/com/abdurazaaqmohammed/AntiSplit/main/SignUtil.java new file mode 100644 index 00000000..aaabb27b --- /dev/null +++ b/app/src/main/java/com/abdurazaaqmohammed/AntiSplit/main/SignUtil.java @@ -0,0 +1,93 @@ +package com.abdurazaaqmohammed.AntiSplit.main; + +import android.content.Context; +import android.net.Uri; +import android.os.Build; + +import com.abdurazaaqmohammed.AntiSplit.R; +import com.aefyr.pseudoapksigner.IOUtils; +import com.aefyr.pseudoapksigner.PseudoApkSigner; +import com.android.apksig.ApkSigner; +import com.android.apksig.apk.ApkFormatException; +import com.reandroid.apkeditor.merge.LogUtil; +import com.starry.FileUtils; + + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.security.InvalidKeyException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.SignatureException; +import java.security.UnrecoverableEntryException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Collections; + +public class SignUtil { + public static void signApk(InputStream key, String password, File inputApk, File output) throws KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException, ApkFormatException, SignatureException, InvalidKeyException, UnrecoverableEntryException { + signApk(key, password, inputApk, output, true, true, true); + } + + public static void signApk(InputStream key, String password, File inputApk, File output, boolean v1, boolean v2, boolean v3) throws KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException, ApkFormatException, SignatureException, InvalidKeyException, UnrecoverableEntryException { + char[] pw = password.toCharArray(); + + KeyStore keystore = KeyStore.getInstance("BKS"); + keystore.load(key, pw); + + String alias = keystore.aliases().nextElement(); + + new ApkSigner.Builder(Collections.singletonList(new ApkSigner.SignerConfig.Builder("CERT", + ((KeyStore.PrivateKeyEntry) keystore.getEntry(alias, new KeyStore.PasswordProtection(pw))).getPrivateKey(), + Collections.singletonList((X509Certificate) keystore.getCertificate(alias))).build())) + .setInputApk(inputApk) + .setOutputApk(output) + .setCreatedBy("Android Gradle 8.0.2") + .setV1SigningEnabled(v1) + .setV2SigningEnabled(v2) + .setV3SigningEnabled(v3).build().sign(); + } + + public static void signDebugKey(Context c, File inputApk, File output, boolean v1, boolean v2, boolean v3) throws IOException, ApkFormatException, UnrecoverableEntryException, CertificateException, KeyStoreException, NoSuchAlgorithmException, SignatureException, InvalidKeyException { + signApk(c.getAssets().open("debug23.keystore"), "android", inputApk, output, v1, v2, v3); + } + + public static void signDebugKey(Context c, File inputApk, File output) throws IOException, ApkFormatException, UnrecoverableEntryException, CertificateException, KeyStoreException, NoSuchAlgorithmException, SignatureException, InvalidKeyException { + signApk(c.getAssets().open("debug23.keystore"), "android", inputApk, output); + } + + public static void signPseudoApkSigner(File temp, Context context, Uri out, Exception e) throws IOException { + String msg = com.abdurazaaqmohammed.AntiSplit.main.MainActivity.rss.getString(R.string.sign_failed); + if(Build.VERSION.SDK_INT < 30) { + // When I tried signing with apksig in AVD with sdk 10 java.security is throwing some error saying something not found + // Apparently 11 is the last version that supports v1 signing alone. + try (InputStream fis = FileUtils.getInputStream(temp)) { + final String FILE_NAME_PAST = "testkey.past"; + final String FILE_NAME_PRIVATE_KEY = "testkey.pk8"; + File signingEnvironment = new File(context.getFilesDir(), "signing"); + File pastFile = new File(signingEnvironment, FILE_NAME_PAST); + File privateKeyFile = new File(signingEnvironment, FILE_NAME_PRIVATE_KEY); + + if (!pastFile.exists() || !privateKeyFile.exists()) { + signingEnvironment.mkdir(); + IOUtils.copyFileFromAssets(context, FILE_NAME_PAST, pastFile); + IOUtils.copyFileFromAssets(context, FILE_NAME_PRIVATE_KEY, privateKeyFile); + } + + PseudoApkSigner.sign(fis, FileUtils.getOutputStream(out, context), pastFile, privateKeyFile); + } catch (Exception e2) { + LogUtil.logMessage(msg); + FileUtils.copyFile(temp, FileUtils.getOutputStream(out, context)); + throw(new RuntimeException(msg, e)); // for showError + } + } else { + LogUtil.logMessage(msg); + FileUtils.copyFile(temp, FileUtils.getOutputStream(out, context)); + throw(new RuntimeException(msg, e)); // for showError + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/android/apksig/ApkSigner.java b/app/src/main/java/com/android/apksig/ApkSigner.java index 37226175..f94209a8 100644 --- a/app/src/main/java/com/android/apksig/ApkSigner.java +++ b/app/src/main/java/com/android/apksig/ApkSigner.java @@ -20,6 +20,8 @@ import static com.android.apksig.internal.apk.v3.V3SchemeConstants.MIN_SDK_WITH_V31_SUPPORT; import static com.android.apksig.internal.apk.v3.V3SchemeConstants.MIN_SDK_WITH_V3_SUPPORT; +import android.text.TextUtils; + import com.android.apksig.apk.ApkFormatException; import com.android.apksig.apk.ApkSigningBlockNotFoundException; import com.android.apksig.apk.ApkUtils; @@ -1091,9 +1093,7 @@ public Builder( PrivateKey privateKey, List certificates, boolean deterministicDsaSigning) { - if (name.isEmpty()) { - throw new IllegalArgumentException("Empty name"); - } + if (TextUtils.isEmpty(name)) throw new IllegalArgumentException("Empty name"); mName = name; mPrivateKey = privateKey; mCertificates = new ArrayList<>(certificates); diff --git a/app/src/main/java/com/android/apksig/internal/jar/ManifestParser.java b/app/src/main/java/com/android/apksig/internal/jar/ManifestParser.java index ab0a5dad..8febf35e 100644 --- a/app/src/main/java/com/android/apksig/internal/jar/ManifestParser.java +++ b/app/src/main/java/com/android/apksig/internal/jar/ManifestParser.java @@ -16,6 +16,9 @@ package com.android.apksig.internal.jar; +import android.os.Build; +import android.text.TextUtils; + import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; @@ -80,14 +83,14 @@ public Section readSection() { if (attr == null) { return null; } - } while (attr.length() == 0); + } while (TextUtils.isEmpty(attr)); List attrs = new ArrayList<>(); attrs.add(parseAttr(attr)); // Read attributes until end of section reached while (true) { attr = readAttribute(); - if ((attr == null) || (attr.length() == 0)) { + if (TextUtils.isEmpty(attr)) { // End of section break; } @@ -248,7 +251,14 @@ private byte[] readLine() { if (newlineStartOffset == startOffset) { return EMPTY_BYTE_ARRAY; } - return Arrays.copyOfRange(mManifest, startOffset, newlineStartOffset); + if(Build.VERSION.SDK_INT > 8) return Arrays.copyOfRange(mManifest, startOffset, newlineStartOffset); + else { + int newLength = startOffset - newlineStartOffset; + byte[] copy = new byte[newLength]; + System.arraycopy(mManifest, startOffset, copy, 0, + Math.min(mManifest.length - startOffset, newLength)); + return copy; + } } diff --git a/app/src/main/java/com/android/apksig/internal/util/ByteArrayDataSink.java b/app/src/main/java/com/android/apksig/internal/util/ByteArrayDataSink.java index 95cca859..deca039c 100644 --- a/app/src/main/java/com/android/apksig/internal/util/ByteArrayDataSink.java +++ b/app/src/main/java/com/android/apksig/internal/util/ByteArrayDataSink.java @@ -16,6 +16,7 @@ package com.android.apksig.internal.util; +import com.abdurazaaqmohammed.AntiSplit.main.LegacyUtils; import com.android.apksig.util.DataSink; import com.android.apksig.util.DataSource; import com.android.apksig.util.ReadableDataSink; @@ -89,7 +90,7 @@ public void consume(ByteBuffer buf) throws IOException { } } - private void ensureAvailable(int minAvailable) throws IOException { + private void ensureAvailable(int minAvailable) { if (minAvailable <= 0) { return; } @@ -104,7 +105,13 @@ private void ensureAvailable(int minAvailable) throws IOException { } int doubleCurrentSize = (int) Math.min(mArray.length * 2L, Integer.MAX_VALUE); int newSize = (int) Math.max(minCapacity, doubleCurrentSize); - mArray = Arrays.copyOf(mArray, newSize); + if(LegacyUtils.supportsArraysCopyOf) mArray = Arrays.copyOf(mArray, newSize); + else { + byte[] copy = new byte[newSize]; + System.arraycopy(mArray, 0, copy, 0, + Math.min(mArray.length, newSize)); + mArray = copy; + } } @Override @@ -191,7 +198,7 @@ public void feed(long offset, long size, DataSink sink) throws IOException { } @Override - public ByteBuffer getByteBuffer(long offset, int size) throws IOException { + public ByteBuffer getByteBuffer(long offset, int size) { checkChunkValid(offset, size); // checkChunkValid combined with the way instances of this class are constructed ensures // that mSliceOffset + offset does not overflow. @@ -199,7 +206,7 @@ public ByteBuffer getByteBuffer(long offset, int size) throws IOException { } @Override - public void copyTo(long offset, int size, ByteBuffer dest) throws IOException { + public void copyTo(long offset, int size, ByteBuffer dest) { checkChunkValid(offset, size); // checkChunkValid combined with the way instances of this class are constructed ensures // that mSliceOffset + offset does not overflow. diff --git a/app/src/main/java/com/android/apksig/internal/util/VerityTreeBuilder.java b/app/src/main/java/com/android/apksig/internal/util/VerityTreeBuilder.java index 81026ba5..712ba0c0 100644 --- a/app/src/main/java/com/android/apksig/internal/util/VerityTreeBuilder.java +++ b/app/src/main/java/com/android/apksig/internal/util/VerityTreeBuilder.java @@ -18,11 +18,14 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; +import android.os.Build; + import com.android.apksig.internal.zip.ZipUtils; import com.android.apksig.util.DataSink; import com.android.apksig.util.DataSource; import com.android.apksig.util.DataSources; +import java.io.Closeable; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -31,17 +34,17 @@ import java.util.ArrayList; import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Phaser; import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; /** * VerityTreeBuilder is used to generate the root hash of verity tree built from the input file. * The root hash can be used on device for on-access verification. The tree itself is reproducible * on device, and is not shipped with the APK. */ -public class VerityTreeBuilder implements AutoCloseable { +public class VerityTreeBuilder implements Closeable { /** * Maximum size (in bytes) of each node of the tree. @@ -179,7 +182,7 @@ public ByteBuffer generateVerityTree(DataSource fileSource) throws IOException { /** * Returns the digested root hash from the top level (only page) of a verity tree. */ - public byte[] getRootHashFromTree(ByteBuffer verityBuffer) throws IOException { + public byte[] getRootHashFromTree(ByteBuffer verityBuffer) { ByteBuffer firstPage = slice(verityBuffer.asReadOnlyBuffer(), 0, CHUNK_SIZE); return saltedDigest(firstPage); } @@ -228,44 +231,89 @@ private void digestDataByChunks(DataSource dataSource, DataSink dataSink) throws final byte[][] hashes = new byte[chunks][]; - Phaser tasks = new Phaser(1); - - // Reading the input file as fast as we can. - final long maxReadSize = ioSizeChunks * CHUNK_SIZE; - - long readOffset = 0; - int startChunkIndex = 0; - while (readOffset < size) { - final long readLimit = Math.min(readOffset + maxReadSize, size); - final int readSize = (int) (readLimit - readOffset); - final int bufferSizeChunks = (int) divideRoundup(readSize, CHUNK_SIZE); - - // Overllocating to zero-pad last chunk. - // With 4MiB block size, 32 threads and 4 queue size we might allocate up to 144MiB. - final ByteBuffer buffer = ByteBuffer.allocate(bufferSizeChunks * CHUNK_SIZE); - dataSource.copyTo(readOffset, readSize, buffer); - buffer.rewind(); - - final int readChunkIndex = startChunkIndex; - Runnable task = () -> { - final MessageDigest md = cloneMessageDigest(); - for (int offset = 0, finish = buffer.capacity(), chunkIndex = readChunkIndex; - offset < finish; offset += CHUNK_SIZE, ++chunkIndex) { - ByteBuffer chunk = slice(buffer, offset, offset + CHUNK_SIZE); - hashes[chunkIndex] = saltedDigest(md, chunk); - } - tasks.arriveAndDeregister(); - }; - tasks.register(); - mExecutor.execute(task); - - startChunkIndex += bufferSizeChunks; - readOffset += readSize; - } + if (Build.VERSION.SDK_INT > 20) { + Phaser tasks = new Phaser(1); + // Reading the input file as fast as we can. + final long maxReadSize = ioSizeChunks * CHUNK_SIZE; + + long readOffset = 0; + int startChunkIndex = 0; + while (readOffset < size) { + final long readLimit = Math.min(readOffset + maxReadSize, size); + final int readSize = (int) (readLimit - readOffset); + final int bufferSizeChunks = (int) divideRoundup(readSize, CHUNK_SIZE); + + // Overllocating to zero-pad last chunk. + // With 4MiB block size, 32 threads and 4 queue size we might allocate up to 144MiB. + final ByteBuffer buffer = ByteBuffer.allocate(bufferSizeChunks * CHUNK_SIZE); + dataSource.copyTo(readOffset, readSize, buffer); + buffer.rewind(); + + final int readChunkIndex = startChunkIndex; + Runnable task = () -> { + final MessageDigest md = cloneMessageDigest(); + for (int offset = 0, finish = buffer.capacity(), chunkIndex = readChunkIndex; + offset < finish; offset += CHUNK_SIZE, ++chunkIndex) { + ByteBuffer chunk = slice(buffer, offset, offset + CHUNK_SIZE); + hashes[chunkIndex] = saltedDigest(md, chunk); + } + tasks.arriveAndDeregister(); + }; + tasks.register(); + mExecutor.execute(task); + + startChunkIndex += bufferSizeChunks; + readOffset += readSize; + } - // Waiting for the tasks to complete. - tasks.arriveAndAwaitAdvance(); + // Waiting for the tasks to complete. + tasks.arriveAndAwaitAdvance(); + } else { + final CountDownLatch latch = new CountDownLatch(chunks); + + // Reading the input file as fast as we can. + final long maxReadSize = ioSizeChunks * CHUNK_SIZE; + + long readOffset = 0; + int startChunkIndex = 0; + while (readOffset < size) { + final long readLimit = Math.min(readOffset + maxReadSize, size); + final int readSize = (int) (readLimit - readOffset); + final int bufferSizeChunks = (int) divideRoundup(readSize, CHUNK_SIZE); + + // Overallocating to zero-pad last chunk. + // With 4MiB block size, 32 threads and 4 queue size we might allocate up to 144MiB. + final ByteBuffer buffer = ByteBuffer.allocate(bufferSizeChunks * CHUNK_SIZE); + dataSource.copyTo(readOffset, readSize, buffer); + buffer.rewind(); + + final int readChunkIndex = startChunkIndex; + Runnable task = () -> { + try { + final MessageDigest md = cloneMessageDigest(); + for (int offset = 0, finish = buffer.capacity(), chunkIndex = readChunkIndex; + offset < finish; offset += CHUNK_SIZE, ++chunkIndex) { + ByteBuffer chunk = slice(buffer, offset, offset + CHUNK_SIZE); + hashes[chunkIndex] = saltedDigest(md, chunk); + } + } finally { + latch.countDown(); + } + }; + mExecutor.execute(task); + + startChunkIndex += bufferSizeChunks; + readOffset += readSize; + } + // Waiting for the tasks to complete. + try { + latch.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); // Restore interrupted status + throw new RuntimeException("Operation interrupted", e); + } + } // Streaming hashes back. for (byte[] hash : hashes) { dataSink.consume(hash, 0, hash.length); diff --git a/app/src/main/java/com/android/apksig/internal/util/X509CertificateUtils.java b/app/src/main/java/com/android/apksig/internal/util/X509CertificateUtils.java index 1b6bafc9..17458ea5 100644 --- a/app/src/main/java/com/android/apksig/internal/util/X509CertificateUtils.java +++ b/app/src/main/java/com/android/apksig/internal/util/X509CertificateUtils.java @@ -17,8 +17,8 @@ package com.android.apksig.internal.util; -import android.util.Base64; +import com.aefyr.pseudoapksigner.Base64; import com.android.apksig.internal.asn1.Asn1BerParser; import com.android.apksig.internal.asn1.Asn1DecodingException; import com.android.apksig.internal.asn1.Asn1DerEncoder; diff --git a/app/src/main/java/com/android/apksig/internal/zip/CentralDirectoryRecord.java b/app/src/main/java/com/android/apksig/internal/zip/CentralDirectoryRecord.java index d2f444dd..18ff1760 100644 --- a/app/src/main/java/com/android/apksig/internal/zip/CentralDirectoryRecord.java +++ b/app/src/main/java/com/android/apksig/internal/zip/CentralDirectoryRecord.java @@ -16,7 +16,10 @@ package com.android.apksig.internal.zip; +import com.aefyr.pseudoapksigner.Constants; import com.android.apksig.zip.ZipFormatException; + +import java.io.UnsupportedEncodingException; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -223,7 +226,11 @@ public static CentralDirectoryRecord createWithDeflateCompressedData( long compressedSize, long uncompressedSize, long localFileHeaderOffset) { - byte[] nameBytes = name.getBytes(StandardCharsets.UTF_8); + byte[] nameBytes = null; + try { + nameBytes = name.getBytes(Constants.UTF8); + } catch (UnsupportedEncodingException ignored) { + } short gpFlags = ZipUtils.GP_FLAG_EFS; // UTF-8 character encoding used for entry name short compressionMethod = ZipUtils.COMPRESSION_METHOD_DEFLATED; int recordSize = HEADER_SIZE_BYTES + nameBytes.length; diff --git a/app/src/main/java/com/android/apksig/util/RunnablesExecutor.java b/app/src/main/java/com/android/apksig/util/RunnablesExecutor.java index 74017f8d..6ac61f14 100644 --- a/app/src/main/java/com/android/apksig/util/RunnablesExecutor.java +++ b/app/src/main/java/com/android/apksig/util/RunnablesExecutor.java @@ -38,6 +38,7 @@ public void execute(RunnablesProvider provider) { new ArrayBlockingQueue<>(QUEUE_SIZE), new ThreadPoolExecutor.CallerRunsPolicy()); + //not used Phaser tasks = new Phaser(1); for (int i = 0; i < PARALLELISM; ++i) { diff --git a/app/src/main/java/com/github/angads25/filepicker/utils/Utility.java b/app/src/main/java/com/github/angads25/filepicker/utils/Utility.java index 4c5457bd..f473161d 100644 --- a/app/src/main/java/com/github/angads25/filepicker/utils/Utility.java +++ b/app/src/main/java/com/github/angads25/filepicker/utils/Utility.java @@ -87,8 +87,7 @@ public static ArrayList prepareFileListEntries(ArrayList(); } return internalList; diff --git a/app/src/main/java/ankur035/LocaleHelper.java b/app/src/main/java/com/github/paul035/LocaleHelper.java similarity index 95% rename from app/src/main/java/ankur035/LocaleHelper.java rename to app/src/main/java/com/github/paul035/LocaleHelper.java index 89335c01..f3f4c373 100644 --- a/app/src/main/java/ankur035/LocaleHelper.java +++ b/app/src/main/java/com/github/paul035/LocaleHelper.java @@ -1,4 +1,4 @@ -package ankur035; +package com.github.paul035; import android.annotation.TargetApi; import android.content.Context; import android.content.SharedPreferences; @@ -9,6 +9,7 @@ import java.util.Locale; +// https://www.geeksforgeeks.org/how-to-change-the-whole-app-language-in-android-programmatically/ public class LocaleHelper { private static final String SELECTED_LANGUAGE = "Locale.Helper.Selected.Language"; diff --git a/app/src/main/java/com/reandroid/apkeditor/merge/Merger.java b/app/src/main/java/com/reandroid/apkeditor/merge/Merger.java index 47d560bf..47e2aee2 100644 --- a/app/src/main/java/com/reandroid/apkeditor/merge/Merger.java +++ b/app/src/main/java/com/reandroid/apkeditor/merge/Merger.java @@ -21,7 +21,7 @@ import com.abdurazaaqmohammed.AntiSplit.R; import com.abdurazaaqmohammed.AntiSplit.main.DeviceSpecsUtil; import com.abdurazaaqmohammed.AntiSplit.main.MainActivity; -import com.android.apksig.ApkSigner; +import com.abdurazaaqmohammed.AntiSplit.main.SignUtil; import com.reandroid.apk.ApkBundle; import com.reandroid.apk.ApkModule; import com.reandroid.apkeditor.common.AndroidManifestHelper; @@ -42,10 +42,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.security.KeyStore; -import java.security.PrivateKey; -import java.security.cert.X509Certificate; -import java.util.Collections; import java.util.Enumeration; import java.util.List; import java.util.zip.ZipEntry; @@ -219,51 +215,15 @@ public static void run(InputStream ins, File cacheDir, Uri out, Uri xapkUri, Con final File temp = new File(cacheDir, "temp.apk"); mergedModule.writeApk(temp); LogUtil.logMessage(com.abdurazaaqmohammed.AntiSplit.main.MainActivity.rss.getString(R.string.signing)); + boolean noPerm = MainActivity.doesNotHaveStoragePerm(context); + File stupid = new File(noPerm ? (context.getCacheDir() + File.separator + "stupid.apk") : FileUtils.getPath(out, context)); try { - /*final String FILE_NAME_PAST = "testkey.past"; - final String FILE_NAME_PRIVATE_KEY = "testkey.pk8"; - File signingEnvironment = new File(context.getFilesDir(), "signing"); - File pastFile = new File(signingEnvironment, FILE_NAME_PAST); - File privateKeyFile = new File(signingEnvironment, FILE_NAME_PRIVATE_KEY); - - if (!pastFile.exists() || !privateKeyFile.exists()) { - signingEnvironment.mkdir(); - IOUtils.copyFileFromAssets(context, FILE_NAME_PAST, pastFile); - IOUtils.copyFileFromAssets(context, FILE_NAME_PRIVATE_KEY, privateKeyFile); - } - - PseudoApkSigner.sign(fis, out, pastFile, privateKeyFile);*/ - char[] password = "android".toCharArray(); - - KeyStore keystore = KeyStore.getInstance("PKCS12"); - keystore.load(context.getAssets().open("debug.keystore"), password); - - String alias = keystore.aliases().nextElement(); - KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) keystore.getEntry(alias, new KeyStore.PasswordProtection(password)); - PrivateKey privateKey = privateKeyEntry.getPrivateKey(); - - ApkSigner.SignerConfig signerConfig = new ApkSigner.SignerConfig.Builder("CERT", - privateKey, - Collections.singletonList((X509Certificate) keystore.getCertificate(alias))).build(); - ApkSigner.Builder builder = new ApkSigner.Builder(Collections.singletonList(signerConfig)); - builder.setInputApk(temp); - boolean noPerm = MainActivity.doesNotHaveStoragePerm(context); - File stupid = new File(noPerm ? (context.getCacheDir() + File.separator + "stupid.apk") : FileUtils.getPath(out, context)); - builder.setOutputApk(stupid); - builder.setCreatedBy("Android Gradle 8.0.2"); - builder.setV2SigningEnabled(true); - builder.setV3SigningEnabled(true); - ApkSigner signer = builder.build(); - signer.sign(); + SignUtil.signDebugKey(context, temp, stupid); if(noPerm) { FileUtils.copyFile(stupid, FileUtils.getOutputStream(out, context)); stupid.delete(); } - } catch (Exception e) { - LogUtil.logMessage(com.abdurazaaqmohammed.AntiSplit.main.MainActivity.rss.getString(R.string.sign_failed)); - FileUtils.copyFile(temp, FileUtils.getOutputStream(out, context)); - throw(e); // for showError - } + } catch (Exception e) { SignUtil.signPseudoApkSigner(temp, context, out, e); } } else mergedModule.writeApk(FileUtils.getOutputStream(out, context)); mergedModule.close(); bundle.close(); diff --git a/app/src/main/java/com/starry/FileUtils.java b/app/src/main/java/com/starry/FileUtils.java index d51e0bc2..0950057d 100644 --- a/app/src/main/java/com/starry/FileUtils.java +++ b/app/src/main/java/com/starry/FileUtils.java @@ -64,14 +64,9 @@ public static OutputStream getOutputStream(File file) throws IOException { } public static void copyFile(File sourceFile, File destinationFile) throws IOException { - try (InputStream fis = getInputStream(sourceFile); - OutputStream fos = getOutputStream(destinationFile)) { - - byte[] buffer = new byte[1024]; - int length; - while ((length = fis.read(buffer)) > 0) { - fos.write(buffer, 0, length); - } + try (InputStream is = getInputStream(sourceFile); + OutputStream os = getOutputStream(destinationFile)) { + copyFile(is, os); } } @@ -82,12 +77,8 @@ public static void copyFile(File in, OutputStream os) throws IOException { } public static void copyFile(InputStream is, File destinationFile) throws IOException { - try (OutputStream fos = getOutputStream(destinationFile)) { - byte[] buffer = new byte[1024]; - int length; - while ((length = is.read(buffer)) > 0) { - fos.write(buffer, 0, length); - } + try (OutputStream os = getOutputStream(destinationFile)) { + copyFile(is, os); } } @@ -95,9 +86,7 @@ public static void copyFile(InputStream is, OutputStream os) throws IOException if(LegacyUtils.supportsWriteExternalStorage) { byte[] buffer = new byte[1024]; int length; - while ((length = is.read(buffer)) > 0) { - os.write(buffer, 0, length); - } + while ((length = is.read(buffer)) > 0) os.write(buffer, 0, length); } else android.os.FileUtils.copy(is, os); }