├── .gitignore ├── .idea ├── .gitignore ├── compiler.xml ├── gradle.xml ├── kotlinc.xml ├── misc.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── mcal │ │ └── apksigner │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── mcal │ │ │ └── apksigner │ │ │ └── app │ │ │ ├── MainActivity.kt │ │ │ └── filepicker │ │ │ ├── FilePickHelper.java │ │ │ ├── MimeTypeUtil.java │ │ │ └── RequestFile.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ └── dialog_spinner.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── values-night │ │ └── themes.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ │ └── xml │ │ ├── backup_rules.xml │ │ └── data_extraction_rules.xml │ └── test │ └── java │ └── com │ └── mcal │ └── apksigner │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── library ├── .gitignore ├── build.gradle └── src │ └── main │ ├── java │ └── com │ │ ├── android │ │ ├── apksig │ │ │ ├── ApkSigner.java │ │ │ ├── ApkSignerEngine.java │ │ │ ├── ApkVerifier.java │ │ │ ├── DefaultApkSignerEngine.java │ │ │ ├── Hints.java │ │ │ ├── SigningCertificateLineage.java │ │ │ ├── apk │ │ │ │ ├── ApkFormatException.java │ │ │ │ ├── ApkSigningBlockNotFoundException.java │ │ │ │ ├── ApkUtils.java │ │ │ │ ├── CodenameMinSdkVersionException.java │ │ │ │ └── MinSdkVersionException.java │ │ │ ├── internal │ │ │ │ ├── apk │ │ │ │ │ ├── AndroidBinXmlParser.java │ │ │ │ │ ├── ApkSigningBlockUtils.java │ │ │ │ │ ├── ContentDigestAlgorithm.java │ │ │ │ │ ├── SignatureAlgorithm.java │ │ │ │ │ ├── SignatureInfo.java │ │ │ │ │ ├── stamp │ │ │ │ │ │ ├── SourceStampVerifier.java │ │ │ │ │ │ ├── V1SourceStampSigner.java │ │ │ │ │ │ ├── V1SourceStampVerifier.java │ │ │ │ │ │ ├── V2SourceStampSigner.java │ │ │ │ │ │ └── V2SourceStampVerifier.java │ │ │ │ │ ├── v1 │ │ │ │ │ │ ├── DigestAlgorithm.java │ │ │ │ │ │ ├── V1SchemeSigner.java │ │ │ │ │ │ └── V1SchemeVerifier.java │ │ │ │ │ ├── v2 │ │ │ │ │ │ ├── V2SchemeSigner.java │ │ │ │ │ │ └── V2SchemeVerifier.java │ │ │ │ │ ├── v3 │ │ │ │ │ │ ├── V3SchemeSigner.java │ │ │ │ │ │ ├── V3SchemeVerifier.java │ │ │ │ │ │ └── V3SigningCertificateLineage.java │ │ │ │ │ └── v4 │ │ │ │ │ │ ├── V4SchemeSigner.java │ │ │ │ │ │ ├── V4SchemeVerifier.java │ │ │ │ │ │ └── V4Signature.java │ │ │ │ ├── asn1 │ │ │ │ │ ├── Asn1BerParser.java │ │ │ │ │ ├── Asn1Class.java │ │ │ │ │ ├── Asn1DecodingException.java │ │ │ │ │ ├── Asn1DerEncoder.java │ │ │ │ │ ├── Asn1EncodingException.java │ │ │ │ │ ├── Asn1Field.java │ │ │ │ │ ├── Asn1OpaqueObject.java │ │ │ │ │ ├── Asn1TagClass.java │ │ │ │ │ ├── Asn1Tagging.java │ │ │ │ │ ├── Asn1Type.java │ │ │ │ │ └── ber │ │ │ │ │ │ ├── BerDataValue.java │ │ │ │ │ │ ├── BerDataValueFormatException.java │ │ │ │ │ │ ├── BerDataValueReader.java │ │ │ │ │ │ ├── BerEncoding.java │ │ │ │ │ │ ├── ByteBufferBerDataValueReader.java │ │ │ │ │ │ └── InputStreamBerDataValueReader.java │ │ │ │ ├── jar │ │ │ │ │ ├── ManifestParser.java │ │ │ │ │ ├── ManifestWriter.java │ │ │ │ │ └── SignatureFileWriter.java │ │ │ │ ├── oid │ │ │ │ │ └── OidConstants.java │ │ │ │ ├── pkcs7 │ │ │ │ │ ├── AlgorithmIdentifier.java │ │ │ │ │ ├── Attribute.java │ │ │ │ │ ├── ContentInfo.java │ │ │ │ │ ├── EncapsulatedContentInfo.java │ │ │ │ │ ├── IssuerAndSerialNumber.java │ │ │ │ │ ├── Pkcs7Constants.java │ │ │ │ │ ├── Pkcs7DecodingException.java │ │ │ │ │ ├── SignedData.java │ │ │ │ │ ├── SignerIdentifier.java │ │ │ │ │ └── SignerInfo.java │ │ │ │ ├── util │ │ │ │ │ ├── AndroidSdkVersion.java │ │ │ │ │ ├── ByteArrayDataSink.java │ │ │ │ │ ├── ByteBufferDataSource.java │ │ │ │ │ ├── ByteBufferSink.java │ │ │ │ │ ├── ByteBufferUtils.java │ │ │ │ │ ├── ByteStreams.java │ │ │ │ │ ├── ChainedDataSource.java │ │ │ │ │ ├── ClassCompat.java │ │ │ │ │ ├── DelegatingX509Certificate.java │ │ │ │ │ ├── FileChannelDataSource.java │ │ │ │ │ ├── GuaranteedEncodedFormX509Certificate.java │ │ │ │ │ ├── InclusiveIntRange.java │ │ │ │ │ ├── MathCompat.java │ │ │ │ │ ├── MessageDigestSink.java │ │ │ │ │ ├── OutputStreamDataSink.java │ │ │ │ │ ├── Pair.java │ │ │ │ │ ├── RandomAccessFileDataSink.java │ │ │ │ │ ├── SupplierCompat.java │ │ │ │ │ ├── TeeDataSink.java │ │ │ │ │ ├── VerityTreeBuilder.java │ │ │ │ │ └── X509CertificateUtils.java │ │ │ │ ├── x509 │ │ │ │ │ ├── AttributeTypeAndValue.java │ │ │ │ │ ├── Certificate.java │ │ │ │ │ ├── Extension.java │ │ │ │ │ ├── Name.java │ │ │ │ │ ├── RSAPublicKey.java │ │ │ │ │ ├── RelativeDistinguishedName.java │ │ │ │ │ ├── SubjectPublicKeyInfo.java │ │ │ │ │ ├── TBSCertificate.java │ │ │ │ │ ├── Time.java │ │ │ │ │ └── Validity.java │ │ │ │ └── zip │ │ │ │ │ ├── CentralDirectoryRecord.java │ │ │ │ │ ├── EocdRecord.java │ │ │ │ │ ├── LocalFileRecord.java │ │ │ │ │ └── ZipUtils.java │ │ │ ├── util │ │ │ │ ├── DataSink.java │ │ │ │ ├── DataSinks.java │ │ │ │ ├── DataSource.java │ │ │ │ ├── DataSources.java │ │ │ │ ├── ReadableDataSink.java │ │ │ │ ├── RunnablesExecutor.java │ │ │ │ └── RunnablesProvider.java │ │ │ └── zip │ │ │ │ └── ZipFormatException.java │ │ └── apksigner │ │ │ ├── ApkSignerTool.java │ │ │ ├── HexEncoding.java │ │ │ ├── OptionsParser.java │ │ │ ├── ParameterException.java │ │ │ ├── PasswordRetriever.java │ │ │ ├── SignerParams.java │ │ │ ├── help.txt │ │ │ ├── help_lineage.txt │ │ │ ├── help_rotate.txt │ │ │ ├── help_sign.txt │ │ │ ├── help_verify.txt │ │ │ └── utils │ │ │ └── FileUtils.java │ │ └── mcal │ │ └── apksigner │ │ ├── ApkSigner.kt │ │ ├── CertConverter.kt │ │ ├── CertCreator.kt │ │ └── utils │ │ ├── Base64.kt │ │ ├── DistinguishedNameValues.kt │ │ ├── JKS.java │ │ ├── JksKeyStore.kt │ │ ├── KeySet.java │ │ ├── KeyStoreHelper.kt │ │ └── LoadKeystoreException.kt │ └── resources │ └── keystore │ ├── testkey.pk8 │ └── testkey.x509.pem └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 20 | 21 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![](https://jitpack.io/v/TimScriptov/apksigner.svg)](https://jitpack.io/#TimScriptov/apksigner) 2 | 3 | # ApkSigner library Multiplatform 4 | 1. Sign with jks/bks, pk8 + x509.pem 5 | 2. Convert jks to bks, bks to jks, jks/bks to pk8 + x509.pem 6 | 3. Validate password 7 | 4. Create jks/bks 8 | 9 | 10 | ## Add it in your root build.gradle at the end of repositories: 11 | ```groovy 12 | allprojects { 13 | repositories { 14 | //... 15 | maven { url 'https://jitpack.io' } 16 | } 17 | } 18 | ``` 19 | 20 | ## Add the dependency 21 | ```groovy 22 | dependencies { 23 | implementation 'com.github.TimScriptov:apksigner:Tag' 24 | } 25 | ``` 26 | 27 | ## Sign apk with pk8 and x509.pem 28 | ```kotlin 29 | ApkSigner(File("path/unsigned_apk.apk"), File("path/signed_apk.apk")).apply { 30 | useDefaultSignatureVersion = false 31 | v1SigningEnabled = true 32 | v2SigningEnabled = true 33 | v3SigningEnabled = true 34 | v4SigningEnabled = false 35 | }.signRelease( 36 | File("path/key.pk8"), 37 | File("path/key.x509.pem"), 38 | ) 39 | ``` 40 | 41 | ```java 42 | final ApkSigner signer = new ApkSigner(new File("path/unsigned_apk.apk"), new File("path/signed_apk.apk")); 43 | signer.setUseDefaultSignatureVersion(false); 44 | signer.setV1SigningEnabled(true); 45 | signer.setV2SigningEnabled(true); 46 | signer.setV3SigningEnabled(true); 47 | signer.setV4SigningEnabled(false); 48 | signer.signRelease(new File("path/key.pk8"), new File("path/key.x509.pem")); 49 | ``` 50 | 51 | ## Sign apk with jks/bks 52 | ```kotlin 53 | ApkSigner(File("path/unsigned_apk.apk"), File("path/signed_apk.apk")).apply { 54 | useDefaultSignatureVersion = false 55 | v1SigningEnabled = true 56 | v2SigningEnabled = true 57 | v3SigningEnabled = true 58 | v4SigningEnabled = false 59 | }.signRelease( 60 | File("path/key.jks or key.bks"), 61 | "cert_pass", 62 | "cert_alias", 63 | "key_pass", 64 | ) 65 | ``` 66 | 67 | ```java 68 | final ApkSigner signer = new ApkSigner(new File("path/unsigned_apk.apk"), new File("path/signed_apk.apk")); 69 | signer.setUseDefaultSignatureVersion(false); 70 | signer.setV1SigningEnabled(true); 71 | signer.setV2SigningEnabled(true); 72 | signer.setV3SigningEnabled(true); 73 | signer.setV4SigningEnabled(false); 74 | signer.signRelease(new File("path/key.jks or key.bks"), "cert_pass", "cert_alias", "key_pass"); 75 | ``` 76 | 77 | ## Sign apk with testkey 78 | ```kotlin 79 | ApkSigner(File("path/unsigned_apk.apk"), File("path/signed_apk.apk")).apply { 80 | useDefaultSignatureVersion = false 81 | v1SigningEnabled = true 82 | v2SigningEnabled = true 83 | v3SigningEnabled = true 84 | v4SigningEnabled = false 85 | }.signDebug() 86 | ``` 87 | 88 | ```java 89 | final ApkSigner signer = new ApkSigner(new File("path/unsigned_apk.apk"), new File("path/signed_apk.apk")); 90 | signer.setUseDefaultSignatureVersion(false); 91 | signer.setV1SigningEnabled(true); 92 | signer.setV2SigningEnabled(true); 93 | signer.setV3SigningEnabled(true); 94 | signer.setV4SigningEnabled(false); 95 | signer.signDebug(); 96 | ``` 97 | 98 | ## Convert jks to bks 99 | ```kotlin 100 | CertConverter.convert(File("path/key.jks"), File("path/key.bks"), "password", "alias_password") 101 | CertConverter.convert(File("path/key.jks"), File("path/key.bks"), "password", "alias", "alias_password") 102 | ``` 103 | 104 | ```java 105 | CertConverter.convert(new File("path/key.jks"), new File("path/key.bks"), "password", "alias_password"); 106 | CertConverter.convert(new File("path/key.jks"), new File("path/key.bks"), "password", "alias", "alias_password"); 107 | ``` 108 | 109 | ## Convert bks to jks 110 | ```kotlin 111 | CertConverter.convert(File("path/key.bks"), File("path/key.jks"), "password", "alias_password") 112 | CertConverter.convert(File("path/key.bks"), File("path/key.jks"), "password", "alias", "alias_password") 113 | ``` 114 | 115 | ```java 116 | CertConverter.convert(new File("path/key.jks"), new File("path/key.bks"), "password", "alias_password"); 117 | CertConverter.convert(new File("path/key.jks"), new File("path/key.bks"), "password", "alias", "alias_password"); 118 | ``` 119 | 120 | ## Convert jks/bks to pk8 and x509.pem 121 | ```kotlin 122 | CertConverter.convert(File("path/key.jks"), "password", "alias_password", File("path/key.pk8"), File("path/key.x509.pem")) 123 | CertConverter.convert(File("path/key.jks"), "password", "alias", "alias_password", File("path/key.pk8"), File("path/key.x509.pem")) 124 | 125 | CertConverter.convert(File("path/key.bks"), "password", "alias_password", File("path/key.pk8"), File("path/key.x509.pem")) 126 | CertConverter.convert(File("path/key.bks"), "password", "alias", "alias_password", File("path/key.pk8"), File("path/key.x509.pem")) 127 | ``` 128 | 129 | ```java 130 | CertConverter.convert(new File("path/key.jks"), "password", "alias_password", new File("path/key.pk8"), new File("path/key.x509.pem")); 131 | CertConverter.convert(new File("path/key.jks"), "password", "alias", "alias_password", new File("path/key.pk8"), new File("path/key.x509.pem")); 132 | 133 | CertConverter.convert(new File("path/key.bks"), "password", "alias_password", new File("path/key.pk8"), new File("path/key.x509.pem")); 134 | CertConverter.convert(new File("path/key.bks"), "password", "alias", "alias_password", new File("path/key.pk8"), new File("path/key.x509.pem")); 135 | ``` 136 | 137 | ## Create jks/bks 138 | ```kotlin 139 | CertCreator.createKeystoreAndKey("path/key.jks", "password", "alias", DistinguishedNameValues()) 140 | 141 | CertCreator.createKeystoreAndKey("path/key.bks", "password", "alias", DistinguishedNameValues()) 142 | ``` 143 | 144 | ```java 145 | CertCreator.createKeystoreAndKey("path/key.jks", "password", "alias", new DistinguishedNameValues()); 146 | 147 | CertCreator.createKeystoreAndKey("path/key.bks", "password", "alias", new DistinguishedNameValues()); 148 | ``` 149 | 150 | ## Validate password jks/bks 151 | ```kotlin 152 | KeyStoreHelper.validateKeystorePassword("path/key.jks", "password") 153 | 154 | KeyStoreHelper.validateKeystorePassword("path/key.bks", "password") 155 | ``` 156 | 157 | ```java 158 | KeyStoreHelper.validateKeystorePassword("path/key.jks", "password", "alias"); 159 | 160 | KeyStoreHelper.validateKeystorePassword("path/key.bks", "password", "alias"); 161 | ``` 162 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'org.jetbrains.kotlin.android' 4 | } 5 | 6 | android { 7 | namespace 'com.mcal.apksigner.app' 8 | compileSdk 33 9 | 10 | defaultConfig { 11 | applicationId "com.mcal.apksigner.app" 12 | minSdk 21 13 | targetSdk 33 14 | versionCode 11 15 | versionName "1.1-template" 16 | 17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 18 | } 19 | 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | buildFeatures { 27 | viewBinding true 28 | } 29 | kotlinOptions { 30 | jvmTarget = 17 31 | } 32 | compileOptions { 33 | sourceCompatibility JavaVersion.VERSION_17 34 | targetCompatibility JavaVersion.VERSION_17 35 | } 36 | } 37 | 38 | dependencies { 39 | implementation project(":library") 40 | implementation 'androidx.appcompat:appcompat:1.6.1' 41 | implementation 'com.google.android.material:material:1.9.0' 42 | implementation 'androidx.core:core-ktx:1.10.1' 43 | testImplementation 'junit:junit:4.13.2' 44 | androidTestImplementation 'androidx.test.ext:junit:1.1.5' 45 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' 46 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/com/mcal/apksigner/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.mcal.apksigner; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | assertEquals("com.mcal.zip", appContext.getPackageName()); 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 18 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/mcal/apksigner/app/filepicker/FilePickHelper.java: -------------------------------------------------------------------------------- 1 | package com.mcal.apksigner.app.filepicker; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.database.Cursor; 6 | import android.net.Uri; 7 | import android.provider.OpenableColumns; 8 | import android.webkit.MimeTypeMap; 9 | 10 | import androidx.annotation.NonNull; 11 | import androidx.annotation.Nullable; 12 | 13 | import java.io.FileInputStream; 14 | import java.io.InputStream; 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | 18 | /** 19 | * Created by radiationx on 13.01.17. 20 | */ 21 | 22 | public class FilePickHelper { 23 | // private final static String LOG_TAG = FilePickHelper.class.getSimpleName(); 24 | 25 | @NonNull 26 | public static Intent pickFile(boolean apk) { 27 | Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); 28 | if (apk) { 29 | intent.setType("application/vnd.android.package-archive"); 30 | } else { 31 | intent.setType("*/*"); 32 | } 33 | intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false); 34 | intent.setAction(Intent.ACTION_GET_CONTENT); 35 | intent.addCategory(Intent.CATEGORY_OPENABLE); 36 | return Intent.createChooser(intent, "Select file"); 37 | } 38 | 39 | @NonNull 40 | public static List onActivityResult(@NonNull Context context, @NonNull Intent data) { 41 | List files = new ArrayList<>(); 42 | RequestFile tempFile; 43 | // Log.d(LOG_TAG, "onActivityResult " + data); 44 | if (data.getData() == null) { 45 | if (data.getClipData() != null) { 46 | for (int i = 0; i < data.getClipData().getItemCount(); i++) { 47 | tempFile = createFile(context, data.getClipData().getItemAt(i).getUri()); 48 | if (tempFile != null) files.add(tempFile); 49 | } 50 | } 51 | } else { 52 | tempFile = createFile(context, data.getData()); 53 | if (tempFile != null) files.add(tempFile); 54 | } 55 | return files; 56 | } 57 | 58 | @Nullable 59 | private static RequestFile createFile(@NonNull Context context, @NonNull Uri uri) { 60 | RequestFile requestFile = null; 61 | // Log.d(LOG_TAG, "createFile " + uri); 62 | try { 63 | InputStream inputStream = null; 64 | String name = getFileName(context, uri); 65 | String extension = MimeTypeUtil.getExtension(name); 66 | String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); 67 | if (mimeType == null) { 68 | mimeType = context.getContentResolver().getType(uri); 69 | } 70 | if (mimeType == null) { 71 | mimeType = MimeTypeUtil.getType(extension); 72 | } 73 | if (uri.getScheme().equals("content")) { 74 | inputStream = context.getContentResolver().openInputStream(uri); 75 | } else if (uri.getScheme().equals("file")) { 76 | inputStream = new FileInputStream(uri.getPath()); 77 | } 78 | requestFile = new RequestFile(name, mimeType, inputStream); 79 | } catch (Exception e) { 80 | e.printStackTrace(); 81 | } 82 | return requestFile; 83 | } 84 | 85 | @NonNull 86 | public static String getFileName(@NonNull Context context, @NonNull Uri uri) { 87 | // Log.d(LOG_TAG, "getFileName " + uri.getScheme() + " : " + context.getContentResolver().getType(uri)); 88 | String result = null; 89 | if (uri.getScheme().equals("content")) { 90 | try (Cursor cursor = context.getContentResolver().query(uri, null, null, null, null)) { 91 | if (cursor != null && cursor.moveToFirst()) { 92 | int index = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); 93 | if (index >= 0) { 94 | result = cursor.getString(index); 95 | } 96 | } 97 | } 98 | } 99 | if (result == null) { 100 | // Log.d(LOG_TAG, "res " + uri.getPath()); 101 | result = uri.getPath(); 102 | int cut = result.lastIndexOf('/'); 103 | if (cut != -1) { 104 | result = result.substring(cut + 1); 105 | } 106 | } 107 | return result; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /app/src/main/java/com/mcal/apksigner/app/filepicker/RequestFile.java: -------------------------------------------------------------------------------- 1 | package com.mcal.apksigner.app.filepicker; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import java.io.InputStream; 6 | 7 | /** 8 | * Created by radiationx on 12.01.17. 9 | */ 10 | 11 | public class RequestFile { 12 | private final String fileName; 13 | private final String mimeType; 14 | private InputStream fileStream; 15 | private String requestName; 16 | 17 | public RequestFile(String fileName, String mimeType, InputStream fileStream) { 18 | this.fileStream = fileStream; 19 | this.fileName = fileName; 20 | this.mimeType = mimeType; 21 | } 22 | 23 | public RequestFile(String requestName, String fileName, String mimeType, InputStream fileStream) { 24 | this.requestName = requestName; 25 | this.fileStream = fileStream; 26 | this.fileName = fileName; 27 | this.mimeType = mimeType; 28 | } 29 | 30 | public InputStream getFileStream() { 31 | return fileStream; 32 | } 33 | 34 | public void setFileStream(InputStream fileStream) { 35 | this.fileStream = fileStream; 36 | } 37 | 38 | public String getFileName() { 39 | return fileName; 40 | } 41 | 42 | public String getMimeType() { 43 | return mimeType; 44 | } 45 | 46 | public String getRequestName() { 47 | return requestName; 48 | } 49 | 50 | public void setRequestName(String requestName) { 51 | this.requestName = requestName; 52 | } 53 | 54 | @NonNull 55 | @Override 56 | public String toString() { 57 | return "RequestFile{" + fileName + ", " + mimeType + ", " + requestName + ", " + fileStream + "}"; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_spinner.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 12 | 13 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timscriptov/apksigner/33964952d6d166d93c0dceb6e0a720ba009682f4/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timscriptov/apksigner/33964952d6d166d93c0dceb6e0a720ba009682f4/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timscriptov/apksigner/33964952d6d166d93c0dceb6e0a720ba009682f4/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timscriptov/apksigner/33964952d6d166d93c0dceb6e0a720ba009682f4/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timscriptov/apksigner/33964952d6d166d93c0dceb6e0a720ba009682f4/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timscriptov/apksigner/33964952d6d166d93c0dceb6e0a720ba009682f4/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timscriptov/apksigner/33964952d6d166d93c0dceb6e0a720ba009682f4/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timscriptov/apksigner/33964952d6d166d93c0dceb6e0a720ba009682f4/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timscriptov/apksigner/33964952d6d166d93c0dceb6e0a720ba009682f4/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timscriptov/apksigner/33964952d6d166d93c0dceb6e0a720ba009682f4/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ApkSigner 3 | Signing… 4 | Select Apk 5 | Signature scheme v1 6 | Signature scheme v2 7 | Signature scheme v3 8 | Signature scheme v4 9 | Select key.jks/bks 10 | Password 11 | Alias 12 | Alias Password 13 | Convert to BKS/JKS 14 | Convert to PK8+X509.PEM 15 | Sign Apk 16 | Select key.pk8 17 | Select key.x509.pem 18 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /app/src/test/java/com/mcal/apksigner/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.mcal.apksigner; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | plugins { 3 | id 'com.android.application' version '8.0.1' apply false 4 | id 'com.android.library' version '8.0.1' apply false 5 | id 'org.jetbrains.kotlin.android' version '1.9.0' apply false 6 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Enables namespacing of each library's R class so that its R class includes only the 19 | # resources declared in the library itself and none from the library's dependencies, 20 | # thereby reducing the size of the R class for that library 21 | android.nonTransitiveRClass=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timscriptov/apksigner/33964952d6d166d93c0dceb6e0a720ba009682f4/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Jul 22 16:10:24 YEKT 2023 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java-library' 2 | apply plugin: 'maven-publish' 3 | apply plugin: 'kotlin' 4 | 5 | dependencies { 6 | implementation("com.madgag.spongycastle:core:1.58.0.0") 7 | implementation("com.madgag.spongycastle:prov:1.58.0.0") 8 | } 9 | 10 | afterEvaluate { 11 | publishing { 12 | publications { 13 | release(MavenPublication) { 14 | from components.java 15 | groupId = 'com.mcal' 16 | artifactId = 'apksigner' 17 | version = "1.2.0" 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/Hints.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.android.apksig; 17 | 18 | import java.io.ByteArrayOutputStream; 19 | import java.io.DataOutputStream; 20 | import java.io.IOException; 21 | import java.io.UnsupportedEncodingException; 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | import java.util.regex.Matcher; 25 | import java.util.regex.Pattern; 26 | 27 | public final class Hints { 28 | /** 29 | * Name of hint pattern asset file in APK. 30 | */ 31 | public static final String PIN_HINT_ASSET_ZIP_ENTRY_NAME = "assets/com.android.hints.pins.txt"; 32 | 33 | /** 34 | * Name of hint byte range data file in APK. Keep in sync with PinnerService.java. 35 | */ 36 | public static final String PIN_BYTE_RANGE_ZIP_ENTRY_NAME = "pinlist.meta"; 37 | 38 | private static int clampToInt(long value) { 39 | return (int) Math.max(0, Math.min(value, Integer.MAX_VALUE)); 40 | } 41 | 42 | /** 43 | * Create a blob of bytes that PinnerService understands as a 44 | * sequence of byte ranges to pin. 45 | */ 46 | public static byte[] encodeByteRangeList(List pinByteRanges) { 47 | ByteArrayOutputStream bos = new ByteArrayOutputStream(pinByteRanges.size() * 8); 48 | DataOutputStream out = new DataOutputStream(bos); 49 | try { 50 | for (ByteRange pinByteRange : pinByteRanges) { 51 | out.writeInt(clampToInt(pinByteRange.start)); 52 | out.writeInt(clampToInt(pinByteRange.end - pinByteRange.start)); 53 | } 54 | } catch (IOException ex) { 55 | throw new AssertionError("impossible", ex); 56 | } 57 | return bos.toByteArray(); 58 | } 59 | 60 | public static ArrayList parsePinPatterns(byte[] patternBlob) { 61 | ArrayList pinPatterns = new ArrayList<>(); 62 | try { 63 | for (String rawLine : new String(patternBlob, "UTF-8").split("\n")) { 64 | String line = rawLine.replaceFirst("#.*", ""); // # starts a comment 65 | String[] fields = line.split(" "); 66 | if (fields.length == 1) { 67 | pinPatterns.add(new PatternWithRange(fields[0])); 68 | } else if (fields.length == 3) { 69 | long start = Long.parseLong(fields[1]); 70 | long end = Long.parseLong(fields[2]); 71 | pinPatterns.add(new PatternWithRange(fields[0], start, end - start)); 72 | } else { 73 | throw new AssertionError("bad pin pattern line " + line); 74 | } 75 | } 76 | } catch (UnsupportedEncodingException ex) { 77 | throw new RuntimeException("UTF-8 must be supported", ex); 78 | } 79 | return pinPatterns; 80 | } 81 | 82 | public static final class ByteRange { 83 | final long start; 84 | final long end; 85 | 86 | public ByteRange(long start, long end) { 87 | this.start = start; 88 | this.end = end; 89 | } 90 | } 91 | 92 | public static final class PatternWithRange { 93 | final Pattern pattern; 94 | final long offset; 95 | final long size; 96 | 97 | public PatternWithRange(String pattern) { 98 | this.pattern = Pattern.compile(pattern); 99 | this.offset = 0; 100 | this.size = Long.MAX_VALUE; 101 | } 102 | 103 | public PatternWithRange(String pattern, long offset, long size) { 104 | this.pattern = Pattern.compile(pattern); 105 | this.offset = offset; 106 | this.size = size; 107 | } 108 | 109 | public Matcher matcher(CharSequence input) { 110 | return this.pattern.matcher(input); 111 | } 112 | 113 | public ByteRange ClampToAbsoluteByteRange(ByteRange rangeIn) { 114 | if (rangeIn.end - rangeIn.start < this.offset) { 115 | return null; 116 | } 117 | long rangeOutStart = rangeIn.start + this.offset; 118 | long rangeOutSize = Math.min(rangeIn.end - rangeOutStart, 119 | this.size); 120 | return new ByteRange(rangeOutStart, 121 | rangeOutStart + rangeOutSize); 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/apk/ApkFormatException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.apk; 18 | 19 | /** 20 | * Indicates that an APK is not well-formed. For example, this may indicate that the APK is not a 21 | * well-formed ZIP archive, in which case {@link #getCause()} will return a 22 | * {@link com.android.apksig.zip.ZipFormatException ZipFormatException}, or that the APK contains 23 | * multiple ZIP entries with the same name. 24 | */ 25 | public class ApkFormatException extends Exception { 26 | private static final long serialVersionUID = 1L; 27 | 28 | public ApkFormatException(String message) { 29 | super(message); 30 | } 31 | 32 | public ApkFormatException(String message, Throwable cause) { 33 | super(message, cause); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/apk/ApkSigningBlockNotFoundException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.apk; 18 | 19 | /** 20 | * Indicates that no APK Signing Block was found in an APK. 21 | */ 22 | public class ApkSigningBlockNotFoundException extends Exception { 23 | private static final long serialVersionUID = 1L; 24 | 25 | public ApkSigningBlockNotFoundException(String message) { 26 | super(message); 27 | } 28 | 29 | public ApkSigningBlockNotFoundException(String message, Throwable cause) { 30 | super(message, cause); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/apk/CodenameMinSdkVersionException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.apk; 18 | 19 | /** 20 | * Indicates that there was an issue determining the minimum Android platform version supported by 21 | * an APK because the version is specified as a codename, rather than as API Level number, and the 22 | * codename is in an unexpected format. 23 | */ 24 | public class CodenameMinSdkVersionException extends MinSdkVersionException { 25 | 26 | private static final long serialVersionUID = 1L; 27 | 28 | /** 29 | * Encountered codename. 30 | */ 31 | private final String mCodename; 32 | 33 | /** 34 | * Constructs a new {@code MinSdkVersionCodenameException} with the provided message and 35 | * codename. 36 | */ 37 | public CodenameMinSdkVersionException(String message, String codename) { 38 | super(message); 39 | mCodename = codename; 40 | } 41 | 42 | /** 43 | * Returns the codename. 44 | */ 45 | public String getCodename() { 46 | return mCodename; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/apk/MinSdkVersionException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.apk; 18 | 19 | /** 20 | * Indicates that there was an issue determining the minimum Android platform version supported by 21 | * an APK. 22 | */ 23 | public class MinSdkVersionException extends ApkFormatException { 24 | 25 | private static final long serialVersionUID = 1L; 26 | 27 | /** 28 | * Constructs a new {@code MinSdkVersionException} with the provided message. 29 | */ 30 | public MinSdkVersionException(String message) { 31 | super(message); 32 | } 33 | 34 | /** 35 | * Constructs a new {@code MinSdkVersionException} with the provided message and cause. 36 | */ 37 | public MinSdkVersionException(String message, Throwable cause) { 38 | super(message, cause); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/apk/ContentDigestAlgorithm.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.apk; 18 | 19 | /** 20 | * APK Signature Scheme v2 content digest algorithm. 21 | */ 22 | public enum ContentDigestAlgorithm { 23 | /** 24 | * SHA2-256 over 1 MB chunks. 25 | */ 26 | CHUNKED_SHA256(1, "SHA-256", 256 / 8), 27 | 28 | /** 29 | * SHA2-512 over 1 MB chunks. 30 | */ 31 | CHUNKED_SHA512(2, "SHA-512", 512 / 8), 32 | 33 | /** 34 | * SHA2-256 over 4 KB chunks for APK verity. 35 | */ 36 | VERITY_CHUNKED_SHA256(3, "SHA-256", 256 / 8), 37 | 38 | /** 39 | * Non-chunk SHA2-256. 40 | */ 41 | SHA256(4, "SHA-256", 256 / 8); 42 | 43 | private final int mId; 44 | private final String mJcaMessageDigestAlgorithm; 45 | private final int mChunkDigestOutputSizeBytes; 46 | 47 | private ContentDigestAlgorithm( 48 | int id, String jcaMessageDigestAlgorithm, int chunkDigestOutputSizeBytes) { 49 | mId = id; 50 | mJcaMessageDigestAlgorithm = jcaMessageDigestAlgorithm; 51 | mChunkDigestOutputSizeBytes = chunkDigestOutputSizeBytes; 52 | } 53 | 54 | /** 55 | * Returns the ID of the digest algorithm used on the APK. 56 | */ 57 | public int getId() { 58 | return mId; 59 | } 60 | 61 | /** 62 | * Returns the {@link java.security.MessageDigest} algorithm used for computing digests of 63 | * chunks by this content digest algorithm. 64 | */ 65 | String getJcaMessageDigestAlgorithm() { 66 | return mJcaMessageDigestAlgorithm; 67 | } 68 | 69 | /** 70 | * Returns the size (in bytes) of the digest of a chunk of content. 71 | */ 72 | int getChunkDigestOutputSizeBytes() { 73 | return mChunkDigestOutputSizeBytes; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/apk/SignatureInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.apk; 18 | 19 | import java.nio.ByteBuffer; 20 | 21 | /** 22 | * APK Signature Scheme block and additional information relevant to verifying the signatures 23 | * contained in the block against the file. 24 | */ 25 | public class SignatureInfo { 26 | /** 27 | * Contents of APK Signature Scheme block. 28 | */ 29 | public final ByteBuffer signatureBlock; 30 | 31 | /** 32 | * Position of the APK Signing Block in the file. 33 | */ 34 | public final long apkSigningBlockOffset; 35 | 36 | /** 37 | * Position of the ZIP Central Directory in the file. 38 | */ 39 | public final long centralDirOffset; 40 | 41 | /** 42 | * Position of the ZIP End of Central Directory (EoCD) in the file. 43 | */ 44 | public final long eocdOffset; 45 | 46 | /** 47 | * Contents of ZIP End of Central Directory (EoCD) of the file. 48 | */ 49 | public final ByteBuffer eocd; 50 | 51 | public SignatureInfo( 52 | ByteBuffer signatureBlock, 53 | long apkSigningBlockOffset, 54 | long centralDirOffset, 55 | long eocdOffset, 56 | ByteBuffer eocd) { 57 | this.signatureBlock = signatureBlock; 58 | this.apkSigningBlockOffset = apkSigningBlockOffset; 59 | this.centralDirOffset = centralDirOffset; 60 | this.eocdOffset = eocdOffset; 61 | this.eocd = eocd; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/apk/stamp/V1SourceStampSigner.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 Muntashir Al-Islam 3 | * Copyright (C) 2020 The Android Open Source Project 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.android.apksig.internal.apk.stamp; 19 | 20 | import static com.android.apksig.internal.apk.ApkSigningBlockUtils.encodeAsLengthPrefixedElement; 21 | import static com.android.apksig.internal.apk.ApkSigningBlockUtils.encodeAsSequenceOfLengthPrefixedElements; 22 | import static com.android.apksig.internal.apk.ApkSigningBlockUtils.encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes; 23 | 24 | import com.android.apksig.internal.apk.ApkSigningBlockUtils; 25 | import com.android.apksig.internal.apk.ApkSigningBlockUtils.SignerConfig; 26 | import com.android.apksig.internal.apk.ContentDigestAlgorithm; 27 | import com.android.apksig.internal.util.Pair; 28 | 29 | import java.security.InvalidKeyException; 30 | import java.security.NoSuchAlgorithmException; 31 | import java.security.SignatureException; 32 | import java.security.cert.CertificateEncodingException; 33 | import java.util.ArrayList; 34 | import java.util.Collections; 35 | import java.util.List; 36 | import java.util.Map; 37 | 38 | /** 39 | * SourceStamp signer. 40 | * 41 | *

SourceStamp improves traceability of apps with respect to unauthorized distribution. 42 | * 43 | *

The stamp is part of the APK that is protected by the signing block. 44 | * 45 | *

The APK contents hash is signed using the stamp key, and is saved as part of the signing 46 | * block. 47 | * 48 | *

V1 of the source stamp allows signing the digest of at most one signature scheme only. 49 | */ 50 | public abstract class V1SourceStampSigner { 51 | 52 | public static final int V1_SOURCE_STAMP_BLOCK_ID = 0x2b09189e; 53 | 54 | /** 55 | * Hidden constructor to prevent instantiation. 56 | */ 57 | private V1SourceStampSigner() { 58 | } 59 | 60 | public static Pair generateSourceStampBlock( 61 | SignerConfig sourceStampSignerConfig, Map digestInfo) 62 | throws SignatureException, NoSuchAlgorithmException, InvalidKeyException { 63 | if (sourceStampSignerConfig.certificates.isEmpty()) { 64 | throw new SignatureException("No certificates configured for signer"); 65 | } 66 | 67 | List> digests = new ArrayList<>(); 68 | for (Map.Entry digest : digestInfo.entrySet()) { 69 | digests.add(Pair.of(digest.getKey().getId(), digest.getValue())); 70 | } 71 | Collections.sort(digests, (o1, o2) -> o1.getFirst().compareTo(o2.getFirst())); 72 | 73 | SourceStampBlock sourceStampBlock = new SourceStampBlock(); 74 | 75 | try { 76 | sourceStampBlock.stampCertificate = 77 | sourceStampSignerConfig.certificates.get(0).getEncoded(); 78 | } catch (CertificateEncodingException e) { 79 | throw new SignatureException( 80 | "Retrieving the encoded form of the stamp certificate failed", e); 81 | } 82 | 83 | byte[] digestBytes = 84 | encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes(digests); 85 | sourceStampBlock.signedDigests = 86 | ApkSigningBlockUtils.generateSignaturesOverData( 87 | sourceStampSignerConfig, digestBytes); 88 | 89 | // FORMAT: 90 | // * length-prefixed bytes: X.509 certificate (ASN.1 DER encoded) 91 | // * length-prefixed sequence of length-prefixed signatures: 92 | // * uint32: signature algorithm ID 93 | // * length-prefixed bytes: signature of signed data 94 | byte[] sourceStampSignerBlock = 95 | encodeAsSequenceOfLengthPrefixedElements( 96 | new byte[][]{ 97 | sourceStampBlock.stampCertificate, 98 | encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes( 99 | sourceStampBlock.signedDigests), 100 | }); 101 | 102 | // FORMAT: 103 | // * length-prefixed stamp block. 104 | return Pair.of( 105 | encodeAsLengthPrefixedElement(sourceStampSignerBlock), V1_SOURCE_STAMP_BLOCK_ID); 106 | } 107 | 108 | private static final class SourceStampBlock { 109 | public byte[] stampCertificate; 110 | public List> signedDigests; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/apk/v1/DigestAlgorithm.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.apk.v1; 18 | 19 | import java.util.Comparator; 20 | 21 | /** 22 | * Digest algorithm used with JAR signing (aka v1 signing scheme). 23 | */ 24 | public enum DigestAlgorithm { 25 | /** 26 | * SHA-1 27 | */ 28 | SHA1("SHA-1"), 29 | 30 | /** 31 | * SHA2-256 32 | */ 33 | SHA256("SHA-256"); 34 | 35 | public static Comparator BY_STRENGTH_COMPARATOR = new StrengthComparator(); 36 | private final String mJcaMessageDigestAlgorithm; 37 | 38 | private DigestAlgorithm(String jcaMessageDigestAlgoritm) { 39 | mJcaMessageDigestAlgorithm = jcaMessageDigestAlgoritm; 40 | } 41 | 42 | /** 43 | * Returns the {@link java.security.MessageDigest} algorithm represented by this digest 44 | * algorithm. 45 | */ 46 | String getJcaMessageDigestAlgorithm() { 47 | return mJcaMessageDigestAlgorithm; 48 | } 49 | 50 | private static class StrengthComparator implements Comparator { 51 | @Override 52 | public int compare(DigestAlgorithm a1, DigestAlgorithm a2) { 53 | switch (a1) { 54 | case SHA1: 55 | switch (a2) { 56 | case SHA1: 57 | return 0; 58 | case SHA256: 59 | return -1; 60 | } 61 | throw new RuntimeException("Unsupported algorithm: " + a2); 62 | 63 | case SHA256: 64 | switch (a2) { 65 | case SHA1: 66 | return 1; 67 | case SHA256: 68 | return 0; 69 | } 70 | throw new RuntimeException("Unsupported algorithm: " + a2); 71 | 72 | default: 73 | throw new RuntimeException("Unsupported algorithm: " + a1); 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/asn1/Asn1Class.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.asn1; 18 | 19 | import java.lang.annotation.ElementType; 20 | import java.lang.annotation.Retention; 21 | import java.lang.annotation.RetentionPolicy; 22 | import java.lang.annotation.Target; 23 | 24 | @Target({ElementType.TYPE}) 25 | @Retention(RetentionPolicy.RUNTIME) 26 | public @interface Asn1Class { 27 | public Asn1Type type(); 28 | } 29 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/asn1/Asn1DecodingException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.asn1; 18 | 19 | /** 20 | * Indicates that input could not be decoded into intended ASN.1 structure. 21 | */ 22 | public class Asn1DecodingException extends Exception { 23 | private static final long serialVersionUID = 1L; 24 | 25 | public Asn1DecodingException(String message) { 26 | super(message); 27 | } 28 | 29 | public Asn1DecodingException(String message, Throwable cause) { 30 | super(message, cause); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/asn1/Asn1EncodingException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.asn1; 18 | 19 | /** 20 | * Indicates that an ASN.1 structure could not be encoded. 21 | */ 22 | public class Asn1EncodingException extends Exception { 23 | private static final long serialVersionUID = 1L; 24 | 25 | public Asn1EncodingException(String message) { 26 | super(message); 27 | } 28 | 29 | public Asn1EncodingException(String message, Throwable cause) { 30 | super(message, cause); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/asn1/Asn1Field.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.asn1; 18 | 19 | import java.lang.annotation.ElementType; 20 | import java.lang.annotation.Retention; 21 | import java.lang.annotation.RetentionPolicy; 22 | import java.lang.annotation.Target; 23 | 24 | @Target({ElementType.FIELD}) 25 | @Retention(RetentionPolicy.RUNTIME) 26 | public @interface Asn1Field { 27 | /** 28 | * Index used to order fields in a container. Required for fields of SEQUENCE containers. 29 | */ 30 | public int index() default 0; 31 | 32 | public Asn1TagClass cls() default Asn1TagClass.AUTOMATIC; 33 | 34 | public Asn1Type type(); 35 | 36 | /** 37 | * Tagging mode. Default: NORMAL. 38 | */ 39 | public Asn1Tagging tagging() default Asn1Tagging.NORMAL; 40 | 41 | /** 42 | * Tag number. Required when IMPLICIT and EXPLICIT tagging mode is used. 43 | */ 44 | public int tagNumber() default -1; 45 | 46 | /** 47 | * {@code true} if this field is optional. Ignored for fields of CHOICE containers. 48 | */ 49 | public boolean optional() default false; 50 | 51 | /** 52 | * Type of elements. Used only for SET_OF or SEQUENCE_OF. 53 | */ 54 | public Asn1Type elementType() default Asn1Type.ANY; 55 | } 56 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/asn1/Asn1OpaqueObject.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.asn1; 18 | 19 | import java.nio.ByteBuffer; 20 | 21 | /** 22 | * Opaque holder of encoded ASN.1 stuff. 23 | */ 24 | public class Asn1OpaqueObject { 25 | private final ByteBuffer mEncoded; 26 | 27 | public Asn1OpaqueObject(ByteBuffer encoded) { 28 | mEncoded = encoded.slice(); 29 | } 30 | 31 | public Asn1OpaqueObject(byte[] encoded) { 32 | mEncoded = ByteBuffer.wrap(encoded); 33 | } 34 | 35 | public ByteBuffer getEncoded() { 36 | return mEncoded.slice(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/asn1/Asn1TagClass.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.asn1; 18 | 19 | public enum Asn1TagClass { 20 | UNIVERSAL, 21 | APPLICATION, 22 | CONTEXT_SPECIFIC, 23 | PRIVATE, 24 | 25 | /** 26 | * Not really an actual tag class: decoder/encoder will attempt to deduce the correct tag class 27 | * automatically. 28 | */ 29 | AUTOMATIC, 30 | } 31 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/asn1/Asn1Tagging.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.asn1; 18 | 19 | public enum Asn1Tagging { 20 | NORMAL, 21 | EXPLICIT, 22 | IMPLICIT, 23 | } 24 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/asn1/Asn1Type.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.asn1; 18 | 19 | public enum Asn1Type { 20 | ANY, 21 | CHOICE, 22 | INTEGER, 23 | OBJECT_IDENTIFIER, 24 | OCTET_STRING, 25 | SEQUENCE, 26 | SEQUENCE_OF, 27 | SET_OF, 28 | BIT_STRING, 29 | UTC_TIME, 30 | GENERALIZED_TIME, 31 | BOOLEAN, 32 | // This type can be used to annotate classes that encapsulate ASN.1 structures that are not 33 | // classified as a SEQUENCE or SET. 34 | UNENCODED_CONTAINER 35 | } 36 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/asn1/ber/BerDataValue.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.asn1.ber; 18 | 19 | import java.nio.ByteBuffer; 20 | 21 | /** 22 | * ASN.1 Basic Encoding Rules (BER) data value -- see {@code X.690}. 23 | */ 24 | public class BerDataValue { 25 | private final ByteBuffer mEncoded; 26 | private final ByteBuffer mEncodedContents; 27 | private final int mTagClass; 28 | private final boolean mConstructed; 29 | private final int mTagNumber; 30 | 31 | BerDataValue( 32 | ByteBuffer encoded, 33 | ByteBuffer encodedContents, 34 | int tagClass, 35 | boolean constructed, 36 | int tagNumber) { 37 | mEncoded = encoded; 38 | mEncodedContents = encodedContents; 39 | mTagClass = tagClass; 40 | mConstructed = constructed; 41 | mTagNumber = tagNumber; 42 | } 43 | 44 | /** 45 | * Returns the tag class of this data value. See {@link BerEncoding} {@code TAG_CLASS} 46 | * constants. 47 | */ 48 | public int getTagClass() { 49 | return mTagClass; 50 | } 51 | 52 | /** 53 | * Returns {@code true} if the content octets of this data value are the complete BER encoding 54 | * of one or more data values, {@code false} if the content octets of this data value directly 55 | * represent the value. 56 | */ 57 | public boolean isConstructed() { 58 | return mConstructed; 59 | } 60 | 61 | /** 62 | * Returns the tag number of this data value. See {@link BerEncoding} {@code TAG_NUMBER} 63 | * constants. 64 | */ 65 | public int getTagNumber() { 66 | return mTagNumber; 67 | } 68 | 69 | /** 70 | * Returns the encoded form of this data value. 71 | */ 72 | public ByteBuffer getEncoded() { 73 | return mEncoded.slice(); 74 | } 75 | 76 | /** 77 | * Returns the encoded contents of this data value. 78 | */ 79 | public ByteBuffer getEncodedContents() { 80 | return mEncodedContents.slice(); 81 | } 82 | 83 | /** 84 | * Returns a new reader of the contents of this data value. 85 | */ 86 | public BerDataValueReader contentsReader() { 87 | return new ByteBufferBerDataValueReader(getEncodedContents()); 88 | } 89 | 90 | /** 91 | * Returns a new reader which returns just this data value. This may be useful for re-reading 92 | * this value in different contexts. 93 | */ 94 | public BerDataValueReader dataValueReader() { 95 | return new ParsedValueReader(this); 96 | } 97 | 98 | private static final class ParsedValueReader implements BerDataValueReader { 99 | private final BerDataValue mValue; 100 | private boolean mValueOutput; 101 | 102 | public ParsedValueReader(BerDataValue value) { 103 | mValue = value; 104 | } 105 | 106 | @Override 107 | public BerDataValue readDataValue() throws BerDataValueFormatException { 108 | if (mValueOutput) { 109 | return null; 110 | } 111 | mValueOutput = true; 112 | return mValue; 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/asn1/ber/BerDataValueFormatException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.asn1.ber; 18 | 19 | /** 20 | * Indicates that an ASN.1 data value being read could not be decoded using 21 | * Basic Encoding Rules (BER). 22 | */ 23 | public class BerDataValueFormatException extends Exception { 24 | 25 | private static final long serialVersionUID = 1L; 26 | 27 | public BerDataValueFormatException(String message) { 28 | super(message); 29 | } 30 | 31 | public BerDataValueFormatException(String message, Throwable cause) { 32 | super(message, cause); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/asn1/ber/BerDataValueReader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.asn1.ber; 18 | 19 | /** 20 | * Reader of ASN.1 Basic Encoding Rules (BER) data values. 21 | * 22 | *

BER data value reader returns data values, one by one, from a source. The interpretation of 23 | * data values (e.g., how to obtain a numeric value from an INTEGER data value, or how to extract 24 | * the elements of a SEQUENCE value) is left to clients of the reader. 25 | */ 26 | public interface BerDataValueReader { 27 | 28 | /** 29 | * Returns the next data value or {@code null} if end of input has been reached. 30 | * 31 | * @throws BerDataValueFormatException if the value being read is malformed. 32 | */ 33 | BerDataValue readDataValue() throws BerDataValueFormatException; 34 | } 35 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/jar/ManifestWriter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.jar; 18 | 19 | import java.io.IOException; 20 | import java.io.OutputStream; 21 | import java.nio.charset.StandardCharsets; 22 | import java.util.Map; 23 | import java.util.Set; 24 | import java.util.SortedMap; 25 | import java.util.TreeMap; 26 | import java.util.jar.Attributes; 27 | 28 | /** 29 | * Producer of {@code META-INF/MANIFEST.MF} file. 30 | * 31 | * @see JAR Manifest format 32 | */ 33 | public abstract class ManifestWriter { 34 | 35 | private static final byte[] CRLF = new byte[]{'\r', '\n'}; 36 | private static final int MAX_LINE_LENGTH = 70; 37 | 38 | private ManifestWriter() { 39 | } 40 | 41 | public static void writeMainSection(OutputStream out, Attributes attributes) 42 | throws IOException { 43 | 44 | // Main section must start with the Manifest-Version attribute. 45 | // See https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Signed_JAR_File. 46 | String manifestVersion = attributes.getValue(Attributes.Name.MANIFEST_VERSION); 47 | if (manifestVersion == null) { 48 | throw new IllegalArgumentException( 49 | "Mandatory " + Attributes.Name.MANIFEST_VERSION + " attribute missing"); 50 | } 51 | writeAttribute(out, Attributes.Name.MANIFEST_VERSION, manifestVersion); 52 | 53 | if (attributes.size() > 1) { 54 | SortedMap namedAttributes = getAttributesSortedByName(attributes); 55 | namedAttributes.remove(Attributes.Name.MANIFEST_VERSION.toString()); 56 | writeAttributes(out, namedAttributes); 57 | } 58 | writeSectionDelimiter(out); 59 | } 60 | 61 | public static void writeIndividualSection(OutputStream out, String name, Attributes attributes) 62 | throws IOException { 63 | writeAttribute(out, "Name", name); 64 | 65 | if (!attributes.isEmpty()) { 66 | writeAttributes(out, getAttributesSortedByName(attributes)); 67 | } 68 | writeSectionDelimiter(out); 69 | } 70 | 71 | static void writeSectionDelimiter(OutputStream out) throws IOException { 72 | out.write(CRLF); 73 | } 74 | 75 | static void writeAttribute(OutputStream out, Attributes.Name name, String value) 76 | throws IOException { 77 | writeAttribute(out, name.toString(), value); 78 | } 79 | 80 | private static void writeAttribute(OutputStream out, String name, String value) 81 | throws IOException { 82 | writeLine(out, name + ": " + value); 83 | } 84 | 85 | private static void writeLine(OutputStream out, String line) throws IOException { 86 | byte[] lineBytes = line.getBytes(StandardCharsets.UTF_8); 87 | int offset = 0; 88 | int remaining = lineBytes.length; 89 | boolean firstLine = true; 90 | while (remaining > 0) { 91 | int chunkLength; 92 | if (firstLine) { 93 | // First line 94 | chunkLength = Math.min(remaining, MAX_LINE_LENGTH); 95 | } else { 96 | // Continuation line 97 | out.write(CRLF); 98 | out.write(' '); 99 | chunkLength = Math.min(remaining, MAX_LINE_LENGTH - 1); 100 | } 101 | out.write(lineBytes, offset, chunkLength); 102 | offset += chunkLength; 103 | remaining -= chunkLength; 104 | firstLine = false; 105 | } 106 | out.write(CRLF); 107 | } 108 | 109 | static SortedMap getAttributesSortedByName(Attributes attributes) { 110 | Set> attributesEntries = attributes.entrySet(); 111 | SortedMap namedAttributes = new TreeMap(); 112 | for (Map.Entry attribute : attributesEntries) { 113 | String attrName = attribute.getKey().toString(); 114 | String attrValue = attribute.getValue().toString(); 115 | namedAttributes.put(attrName, attrValue); 116 | } 117 | return namedAttributes; 118 | } 119 | 120 | static void writeAttributes( 121 | OutputStream out, SortedMap attributesSortedByName) throws IOException { 122 | for (Map.Entry attribute : attributesSortedByName.entrySet()) { 123 | String attrName = attribute.getKey(); 124 | String attrValue = attribute.getValue(); 125 | writeAttribute(out, attrName, attrValue); 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/jar/SignatureFileWriter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.jar; 18 | 19 | import java.io.IOException; 20 | import java.io.OutputStream; 21 | import java.util.SortedMap; 22 | import java.util.jar.Attributes; 23 | 24 | /** 25 | * Producer of JAR signature file ({@code *.SF}). 26 | * 27 | * @see JAR Manifest format 28 | */ 29 | public abstract class SignatureFileWriter { 30 | private SignatureFileWriter() { 31 | } 32 | 33 | public static void writeMainSection(OutputStream out, Attributes attributes) 34 | throws IOException { 35 | 36 | // Main section must start with the Signature-Version attribute. 37 | // See https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Signed_JAR_File. 38 | String signatureVersion = attributes.getValue(Attributes.Name.SIGNATURE_VERSION); 39 | if (signatureVersion == null) { 40 | throw new IllegalArgumentException( 41 | "Mandatory " + Attributes.Name.SIGNATURE_VERSION + " attribute missing"); 42 | } 43 | ManifestWriter.writeAttribute(out, Attributes.Name.SIGNATURE_VERSION, signatureVersion); 44 | 45 | if (attributes.size() > 1) { 46 | SortedMap namedAttributes = 47 | ManifestWriter.getAttributesSortedByName(attributes); 48 | namedAttributes.remove(Attributes.Name.SIGNATURE_VERSION.toString()); 49 | ManifestWriter.writeAttributes(out, namedAttributes); 50 | } 51 | writeSectionDelimiter(out); 52 | } 53 | 54 | public static void writeIndividualSection(OutputStream out, String name, Attributes attributes) 55 | throws IOException { 56 | ManifestWriter.writeIndividualSection(out, name, attributes); 57 | } 58 | 59 | public static void writeSectionDelimiter(OutputStream out) throws IOException { 60 | ManifestWriter.writeSectionDelimiter(out); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/pkcs7/Attribute.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.pkcs7; 18 | 19 | import com.android.apksig.internal.asn1.Asn1Class; 20 | import com.android.apksig.internal.asn1.Asn1Field; 21 | import com.android.apksig.internal.asn1.Asn1OpaqueObject; 22 | import com.android.apksig.internal.asn1.Asn1Type; 23 | 24 | import java.util.List; 25 | 26 | /** 27 | * PKCS #7 {@code Attribute} as specified in RFC 5652. 28 | */ 29 | @Asn1Class(type = Asn1Type.SEQUENCE) 30 | public class Attribute { 31 | 32 | @Asn1Field(index = 0, type = Asn1Type.OBJECT_IDENTIFIER) 33 | public String attrType; 34 | 35 | @Asn1Field(index = 1, type = Asn1Type.SET_OF) 36 | public List attrValues; 37 | } 38 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/pkcs7/ContentInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.pkcs7; 18 | 19 | import com.android.apksig.internal.asn1.Asn1Class; 20 | import com.android.apksig.internal.asn1.Asn1Field; 21 | import com.android.apksig.internal.asn1.Asn1OpaqueObject; 22 | import com.android.apksig.internal.asn1.Asn1Tagging; 23 | import com.android.apksig.internal.asn1.Asn1Type; 24 | 25 | /** 26 | * PKCS #7 {@code ContentInfo} as specified in RFC 5652. 27 | */ 28 | @Asn1Class(type = Asn1Type.SEQUENCE) 29 | public class ContentInfo { 30 | 31 | @Asn1Field(index = 1, type = Asn1Type.OBJECT_IDENTIFIER) 32 | public String contentType; 33 | 34 | @Asn1Field(index = 2, type = Asn1Type.ANY, tagging = Asn1Tagging.EXPLICIT, tagNumber = 0) 35 | public Asn1OpaqueObject content; 36 | } 37 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/pkcs7/EncapsulatedContentInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.pkcs7; 18 | 19 | import com.android.apksig.internal.asn1.Asn1Class; 20 | import com.android.apksig.internal.asn1.Asn1Field; 21 | import com.android.apksig.internal.asn1.Asn1Tagging; 22 | import com.android.apksig.internal.asn1.Asn1Type; 23 | 24 | import java.nio.ByteBuffer; 25 | 26 | /** 27 | * PKCS #7 {@code EncapsulatedContentInfo} as specified in RFC 5652. 28 | */ 29 | @Asn1Class(type = Asn1Type.SEQUENCE) 30 | public class EncapsulatedContentInfo { 31 | 32 | @Asn1Field(index = 0, type = Asn1Type.OBJECT_IDENTIFIER) 33 | public String contentType; 34 | 35 | @Asn1Field( 36 | index = 1, 37 | type = Asn1Type.OCTET_STRING, 38 | tagging = Asn1Tagging.EXPLICIT, tagNumber = 0, 39 | optional = true) 40 | public ByteBuffer content; 41 | 42 | public EncapsulatedContentInfo() { 43 | } 44 | 45 | public EncapsulatedContentInfo(String contentTypeOid) { 46 | contentType = contentTypeOid; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/pkcs7/IssuerAndSerialNumber.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.pkcs7; 18 | 19 | import com.android.apksig.internal.asn1.Asn1Class; 20 | import com.android.apksig.internal.asn1.Asn1Field; 21 | import com.android.apksig.internal.asn1.Asn1OpaqueObject; 22 | import com.android.apksig.internal.asn1.Asn1Type; 23 | 24 | import java.math.BigInteger; 25 | 26 | /** 27 | * PKCS #7 {@code IssuerAndSerialNumber} as specified in RFC 5652. 28 | */ 29 | @Asn1Class(type = Asn1Type.SEQUENCE) 30 | public class IssuerAndSerialNumber { 31 | 32 | @Asn1Field(index = 0, type = Asn1Type.ANY) 33 | public Asn1OpaqueObject issuer; 34 | 35 | @Asn1Field(index = 1, type = Asn1Type.INTEGER) 36 | public BigInteger certificateSerialNumber; 37 | 38 | public IssuerAndSerialNumber() { 39 | } 40 | 41 | public IssuerAndSerialNumber(Asn1OpaqueObject issuer, BigInteger certificateSerialNumber) { 42 | this.issuer = issuer; 43 | this.certificateSerialNumber = certificateSerialNumber; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/pkcs7/Pkcs7Constants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.pkcs7; 18 | 19 | /** 20 | * Assorted PKCS #7 constants from RFC 5652. 21 | */ 22 | public abstract class Pkcs7Constants { 23 | public static final String OID_DATA = "1.2.840.113549.1.7.1"; 24 | public static final String OID_SIGNED_DATA = "1.2.840.113549.1.7.2"; 25 | public static final String OID_CONTENT_TYPE = "1.2.840.113549.1.9.3"; 26 | public static final String OID_MESSAGE_DIGEST = "1.2.840.113549.1.9.4"; 27 | 28 | private Pkcs7Constants() { 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/pkcs7/Pkcs7DecodingException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.pkcs7; 18 | 19 | /** 20 | * Indicates that an error was encountered while decoding a PKCS #7 structure. 21 | */ 22 | public class Pkcs7DecodingException extends Exception { 23 | private static final long serialVersionUID = 1L; 24 | 25 | public Pkcs7DecodingException(String message) { 26 | super(message); 27 | } 28 | 29 | public Pkcs7DecodingException(String message, Throwable cause) { 30 | super(message, cause); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/pkcs7/SignedData.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.pkcs7; 18 | 19 | import com.android.apksig.internal.asn1.Asn1Class; 20 | import com.android.apksig.internal.asn1.Asn1Field; 21 | import com.android.apksig.internal.asn1.Asn1OpaqueObject; 22 | import com.android.apksig.internal.asn1.Asn1Tagging; 23 | import com.android.apksig.internal.asn1.Asn1Type; 24 | 25 | import java.nio.ByteBuffer; 26 | import java.util.List; 27 | 28 | /** 29 | * PKCS #7 {@code SignedData} as specified in RFC 5652. 30 | */ 31 | @Asn1Class(type = Asn1Type.SEQUENCE) 32 | public class SignedData { 33 | 34 | @Asn1Field(index = 0, type = Asn1Type.INTEGER) 35 | public int version; 36 | 37 | @Asn1Field(index = 1, type = Asn1Type.SET_OF) 38 | public List digestAlgorithms; 39 | 40 | @Asn1Field(index = 2, type = Asn1Type.SEQUENCE) 41 | public EncapsulatedContentInfo encapContentInfo; 42 | 43 | @Asn1Field( 44 | index = 3, 45 | type = Asn1Type.SET_OF, 46 | tagging = Asn1Tagging.IMPLICIT, tagNumber = 0, 47 | optional = true) 48 | public List certificates; 49 | 50 | @Asn1Field( 51 | index = 4, 52 | type = Asn1Type.SET_OF, 53 | tagging = Asn1Tagging.IMPLICIT, tagNumber = 1, 54 | optional = true) 55 | public List crls; 56 | 57 | @Asn1Field(index = 5, type = Asn1Type.SET_OF) 58 | public List signerInfos; 59 | } 60 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/pkcs7/SignerIdentifier.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.pkcs7; 18 | 19 | import com.android.apksig.internal.asn1.Asn1Class; 20 | import com.android.apksig.internal.asn1.Asn1Field; 21 | import com.android.apksig.internal.asn1.Asn1Tagging; 22 | import com.android.apksig.internal.asn1.Asn1Type; 23 | 24 | import java.nio.ByteBuffer; 25 | 26 | /** 27 | * PKCS #7 {@code SignerIdentifier} as specified in RFC 5652. 28 | */ 29 | @Asn1Class(type = Asn1Type.CHOICE) 30 | public class SignerIdentifier { 31 | 32 | @Asn1Field(type = Asn1Type.SEQUENCE) 33 | public IssuerAndSerialNumber issuerAndSerialNumber; 34 | 35 | @Asn1Field(type = Asn1Type.OCTET_STRING, tagging = Asn1Tagging.IMPLICIT, tagNumber = 0) 36 | public ByteBuffer subjectKeyIdentifier; 37 | 38 | public SignerIdentifier() { 39 | } 40 | 41 | public SignerIdentifier(IssuerAndSerialNumber issuerAndSerialNumber) { 42 | this.issuerAndSerialNumber = issuerAndSerialNumber; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/pkcs7/SignerInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.pkcs7; 18 | 19 | import com.android.apksig.internal.asn1.Asn1Class; 20 | import com.android.apksig.internal.asn1.Asn1Field; 21 | import com.android.apksig.internal.asn1.Asn1OpaqueObject; 22 | import com.android.apksig.internal.asn1.Asn1Tagging; 23 | import com.android.apksig.internal.asn1.Asn1Type; 24 | 25 | import java.nio.ByteBuffer; 26 | import java.util.List; 27 | 28 | /** 29 | * PKCS #7 {@code SignerInfo} as specified in RFC 5652. 30 | */ 31 | @Asn1Class(type = Asn1Type.SEQUENCE) 32 | public class SignerInfo { 33 | 34 | @Asn1Field(index = 0, type = Asn1Type.INTEGER) 35 | public int version; 36 | 37 | @Asn1Field(index = 1, type = Asn1Type.CHOICE) 38 | public SignerIdentifier sid; 39 | 40 | @Asn1Field(index = 2, type = Asn1Type.SEQUENCE) 41 | public AlgorithmIdentifier digestAlgorithm; 42 | 43 | @Asn1Field( 44 | index = 3, 45 | type = Asn1Type.SET_OF, 46 | tagging = Asn1Tagging.IMPLICIT, tagNumber = 0, 47 | optional = true) 48 | public Asn1OpaqueObject signedAttrs; 49 | 50 | @Asn1Field(index = 4, type = Asn1Type.SEQUENCE) 51 | public AlgorithmIdentifier signatureAlgorithm; 52 | 53 | @Asn1Field(index = 5, type = Asn1Type.OCTET_STRING) 54 | public ByteBuffer signature; 55 | 56 | @Asn1Field( 57 | index = 6, 58 | type = Asn1Type.SET_OF, 59 | tagging = Asn1Tagging.IMPLICIT, tagNumber = 1, 60 | optional = true) 61 | public List unsignedAttrs; 62 | } 63 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/util/AndroidSdkVersion.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.util; 18 | 19 | /** 20 | * Android SDK version / API Level constants. 21 | */ 22 | public abstract class AndroidSdkVersion { 23 | 24 | /** 25 | * Android 2.3. 26 | */ 27 | public static final int GINGERBREAD = 9; 28 | /** 29 | * Android 4.3. The revenge of the beans. 30 | */ 31 | public static final int JELLY_BEAN_MR2 = 18; 32 | /** 33 | * Android 4.4. KitKat, another tasty treat. 34 | */ 35 | public static final int KITKAT = 19; 36 | /** 37 | * Android 5.0. A flat one with beautiful shadows. But still tasty. 38 | */ 39 | public static final int LOLLIPOP = 21; 40 | /** 41 | * Android 6.0. M is for Marshmallow! 42 | */ 43 | public static final int M = 23; 44 | /** 45 | * Android 7.0. N is for Nougat. 46 | */ 47 | public static final int N = 24; 48 | /** 49 | * Android O. 50 | */ 51 | public static final int O = 26; 52 | /** 53 | * Android P. 54 | */ 55 | public static final int P = 28; 56 | /** 57 | * Android R. 58 | */ 59 | public static final int R = 30; 60 | 61 | /** 62 | * Hidden constructor to prevent instantiation. 63 | */ 64 | private AndroidSdkVersion() { 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/util/ByteBufferDataSource.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.util; 18 | 19 | import com.android.apksig.util.DataSink; 20 | import com.android.apksig.util.DataSource; 21 | 22 | import java.io.IOException; 23 | import java.nio.ByteBuffer; 24 | 25 | /** 26 | * {@link DataSource} backed by a {@link ByteBuffer}. 27 | */ 28 | public class ByteBufferDataSource implements DataSource { 29 | 30 | private final ByteBuffer mBuffer; 31 | private final int mSize; 32 | 33 | /** 34 | * Constructs a new {@code ByteBufferDigestSource} based on the data contained in the provided 35 | * buffer between the buffer's position and limit. 36 | */ 37 | public ByteBufferDataSource(ByteBuffer buffer) { 38 | this(buffer, true); 39 | } 40 | 41 | /** 42 | * Constructs a new {@code ByteBufferDigestSource} based on the data contained in the provided 43 | * buffer between the buffer's position and limit. 44 | */ 45 | private ByteBufferDataSource(ByteBuffer buffer, boolean sliceRequired) { 46 | mBuffer = (sliceRequired) ? buffer.slice() : buffer; 47 | mSize = buffer.remaining(); 48 | } 49 | 50 | @Override 51 | public long size() { 52 | return mSize; 53 | } 54 | 55 | @Override 56 | public ByteBuffer getByteBuffer(long offset, int size) { 57 | checkChunkValid(offset, size); 58 | 59 | // checkChunkValid ensures that it's OK to cast offset to int. 60 | int chunkPosition = (int) offset; 61 | int chunkLimit = chunkPosition + size; 62 | // Creating a slice of ByteBuffer modifies the state of the source ByteBuffer (position 63 | // and limit fields, to be more specific). We thus use synchronization around these 64 | // state-changing operations to make instances of this class thread-safe. 65 | synchronized (mBuffer) { 66 | // ByteBuffer.limit(int) and .position(int) check that that the position >= limit 67 | // invariant is not broken. Thus, the only way to safely change position and limit 68 | // without caring about their current values is to first set position to 0 or set the 69 | // limit to capacity. 70 | mBuffer.position(0); 71 | 72 | mBuffer.limit(chunkLimit); 73 | mBuffer.position(chunkPosition); 74 | return mBuffer.slice(); 75 | } 76 | } 77 | 78 | @Override 79 | public void copyTo(long offset, int size, ByteBuffer dest) { 80 | dest.put(getByteBuffer(offset, size)); 81 | } 82 | 83 | @Override 84 | public void feed(long offset, long size, DataSink sink) throws IOException { 85 | if ((size < 0) || (size > mSize)) { 86 | throw new IndexOutOfBoundsException("size: " + size + ", source size: " + mSize); 87 | } 88 | sink.consume(getByteBuffer(offset, (int) size)); 89 | } 90 | 91 | @Override 92 | public ByteBufferDataSource slice(long offset, long size) { 93 | if ((offset == 0) && (size == mSize)) { 94 | return this; 95 | } 96 | if ((size < 0) || (size > mSize)) { 97 | throw new IndexOutOfBoundsException("size: " + size + ", source size: " + mSize); 98 | } 99 | return new ByteBufferDataSource( 100 | getByteBuffer(offset, (int) size), 101 | false // no need to slice -- it's already a slice 102 | ); 103 | } 104 | 105 | private void checkChunkValid(long offset, long size) { 106 | if (offset < 0) { 107 | throw new IndexOutOfBoundsException("offset: " + offset); 108 | } 109 | if (size < 0) { 110 | throw new IndexOutOfBoundsException("size: " + size); 111 | } 112 | if (offset > mSize) { 113 | throw new IndexOutOfBoundsException( 114 | "offset (" + offset + ") > source size (" + mSize + ")"); 115 | } 116 | long endOffset = offset + size; 117 | if (endOffset < offset) { 118 | throw new IndexOutOfBoundsException( 119 | "offset (" + offset + ") + size (" + size + ") overflow"); 120 | } 121 | if (endOffset > mSize) { 122 | throw new IndexOutOfBoundsException( 123 | "offset (" + offset + ") + size (" + size + ") > source size (" + mSize + ")"); 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/util/ByteBufferSink.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.util; 18 | 19 | import com.android.apksig.util.DataSink; 20 | 21 | import java.io.IOException; 22 | import java.nio.BufferOverflowException; 23 | import java.nio.ByteBuffer; 24 | 25 | /** 26 | * Data sink which stores all received data into the associated {@link ByteBuffer}. 27 | */ 28 | public class ByteBufferSink implements DataSink { 29 | 30 | private final ByteBuffer mBuffer; 31 | 32 | public ByteBufferSink(ByteBuffer buffer) { 33 | mBuffer = buffer; 34 | } 35 | 36 | public ByteBuffer getBuffer() { 37 | return mBuffer; 38 | } 39 | 40 | @Override 41 | public void consume(byte[] buf, int offset, int length) throws IOException { 42 | try { 43 | mBuffer.put(buf, offset, length); 44 | } catch (BufferOverflowException e) { 45 | throw new IOException( 46 | "Insufficient space in output buffer for " + length + " bytes", e); 47 | } 48 | } 49 | 50 | @Override 51 | public void consume(ByteBuffer buf) throws IOException { 52 | int length = buf.remaining(); 53 | try { 54 | mBuffer.put(buf); 55 | } catch (BufferOverflowException e) { 56 | throw new IOException( 57 | "Insufficient space in output buffer for " + length + " bytes", e); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/util/ByteBufferUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.util; 18 | 19 | import java.nio.ByteBuffer; 20 | 21 | public final class ByteBufferUtils { 22 | private ByteBufferUtils() { 23 | } 24 | 25 | /** 26 | * Returns the remaining data of the provided buffer as a new byte array and advances the 27 | * position of the buffer to the buffer's limit. 28 | */ 29 | public static byte[] toByteArray(ByteBuffer buf) { 30 | byte[] result = new byte[buf.remaining()]; 31 | buf.get(result); 32 | return result; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/util/ByteStreams.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.util; 18 | 19 | import java.io.ByteArrayOutputStream; 20 | import java.io.IOException; 21 | import java.io.InputStream; 22 | 23 | /** 24 | * Utilities for byte arrays and I/O streams. 25 | */ 26 | public final class ByteStreams { 27 | private ByteStreams() { 28 | } 29 | 30 | /** 31 | * Returns the data remaining in the provided input stream as a byte array 32 | */ 33 | public static byte[] toByteArray(InputStream in) throws IOException { 34 | ByteArrayOutputStream result = new ByteArrayOutputStream(); 35 | byte[] buf = new byte[16384]; 36 | int chunkSize; 37 | while ((chunkSize = in.read(buf)) != -1) { 38 | result.write(buf, 0, chunkSize); 39 | } 40 | return result.toByteArray(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/util/ChainedDataSource.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 Muntashir Al-Islam 3 | * Copyright (C) 2017 The Android Open Source Project 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.android.apksig.internal.util; 19 | 20 | import com.android.apksig.util.DataSink; 21 | import com.android.apksig.util.DataSource; 22 | 23 | import java.io.IOException; 24 | import java.nio.ByteBuffer; 25 | import java.util.ArrayList; 26 | 27 | /** 28 | * Pseudo {@link DataSource} that chains the given {@link DataSource} as a continuous one. 29 | */ 30 | public class ChainedDataSource implements DataSource { 31 | 32 | private final DataSource[] mSources; 33 | private final long mTotalSize; 34 | 35 | public ChainedDataSource(DataSource... sources) { 36 | mSources = sources; 37 | long totalSize = 0; 38 | if (sources != null) for (DataSource source : sources) totalSize += source.size(); 39 | mTotalSize = totalSize; 40 | } 41 | 42 | @Override 43 | public long size() { 44 | return mTotalSize; 45 | } 46 | 47 | @Override 48 | public void feed(long offset, long size, DataSink sink) throws IOException { 49 | if (offset + size > mTotalSize) { 50 | throw new IndexOutOfBoundsException("Requested more than available"); 51 | } 52 | 53 | for (DataSource src : mSources) { 54 | // Offset is beyond the current source. Skip. 55 | if (offset >= src.size()) { 56 | offset -= src.size(); 57 | continue; 58 | } 59 | 60 | // If the remaining is enough, finish it. 61 | long remaining = src.size() - offset; 62 | if (remaining >= size) { 63 | src.feed(offset, size, sink); 64 | break; 65 | } 66 | 67 | // If the remaining is not enough, consume all. 68 | src.feed(offset, remaining, sink); 69 | size -= remaining; 70 | offset = 0; 71 | } 72 | } 73 | 74 | @Override 75 | public ByteBuffer getByteBuffer(long offset, int size) throws IOException { 76 | if (offset + size > mTotalSize) { 77 | throw new IndexOutOfBoundsException("Requested more than available"); 78 | } 79 | 80 | // Skip to the first DataSource we need. 81 | Pair firstSource = locateDataSource(offset); 82 | int i = firstSource.getFirst(); 83 | offset = firstSource.getSecond(); 84 | 85 | // Return the current source's ByteBuffer if it fits. 86 | if (offset + size <= mSources[i].size()) { 87 | return mSources[i].getByteBuffer(offset, size); 88 | } 89 | 90 | // Otherwise, read into a new buffer. 91 | ByteBuffer buffer = ByteBuffer.allocate(size); 92 | for (; i < mSources.length && buffer.hasRemaining(); i++) { 93 | long sizeToCopy = Math.min(mSources[i].size() - offset, buffer.remaining()); 94 | mSources[i].copyTo(offset, MathCompat.toIntExact(sizeToCopy), buffer); 95 | offset = 0; // may not be zero for the first source, but reset after that. 96 | } 97 | buffer.rewind(); 98 | return buffer; 99 | } 100 | 101 | @Override 102 | public void copyTo(long offset, int size, ByteBuffer dest) throws IOException { 103 | feed(offset, size, new ByteBufferSink(dest)); 104 | } 105 | 106 | @Override 107 | public DataSource slice(long offset, long size) { 108 | // Find the first slice. 109 | Pair firstSource = locateDataSource(offset); 110 | int beginIndex = firstSource.getFirst(); 111 | long beginLocalOffset = firstSource.getSecond(); 112 | DataSource beginSource = mSources[beginIndex]; 113 | 114 | if (beginLocalOffset + size <= beginSource.size()) { 115 | return beginSource.slice(beginLocalOffset, size); 116 | } 117 | 118 | // Add the first slice to chaining, followed by the middle full slices, then the last. 119 | ArrayList sources = new ArrayList<>(); 120 | sources.add(beginSource.slice( 121 | beginLocalOffset, beginSource.size() - beginLocalOffset)); 122 | 123 | Pair lastSource = locateDataSource(offset + size - 1); 124 | int endIndex = lastSource.getFirst(); 125 | long endLocalOffset = lastSource.getSecond(); 126 | 127 | for (int i = beginIndex + 1; i < endIndex; i++) { 128 | sources.add(mSources[i]); 129 | } 130 | 131 | sources.add(mSources[endIndex].slice(0, endLocalOffset + 1)); 132 | return new ChainedDataSource(sources.toArray(new DataSource[0])); 133 | } 134 | 135 | /** 136 | * Find the index of DataSource that offset is at. 137 | * 138 | * @return Pair of DataSource index and the local offset in the DataSource. 139 | */ 140 | private Pair locateDataSource(long offset) { 141 | long localOffset = offset; 142 | for (int i = 0; i < mSources.length; i++) { 143 | if (localOffset < mSources[i].size()) { 144 | return Pair.of(i, localOffset); 145 | } 146 | localOffset -= mSources[i].size(); 147 | } 148 | throw new IndexOutOfBoundsException("Access is out of bound, offset: " + offset + 149 | ", totalSize: " + mTotalSize); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/util/ClassCompat.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 Muntashir Al-Islam 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.util; 18 | 19 | import java.lang.annotation.Annotation; 20 | import java.util.Objects; 21 | 22 | public class ClassCompat { 23 | public static A getDeclaredAnnotation(Class containerClass, 24 | Class annotationClass) { 25 | Objects.requireNonNull(annotationClass); 26 | Objects.requireNonNull(containerClass); 27 | // Loop over all directly-present annotations looking for a matching one 28 | for (Annotation annotation : containerClass.getDeclaredAnnotations()) { 29 | if (annotationClass.equals(annotation.annotationType())) { 30 | // More robust to do a dynamic cast at runtime instead 31 | // of compile-time only. 32 | return annotationClass.cast(annotation); 33 | } 34 | } 35 | return null; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/util/GuaranteedEncodedFormX509Certificate.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.util; 18 | 19 | import java.security.cert.CertificateEncodingException; 20 | import java.security.cert.X509Certificate; 21 | import java.util.Arrays; 22 | 23 | /** 24 | * {@link X509Certificate} whose {@link #getEncoded()} returns the data provided at construction 25 | * time. 26 | */ 27 | public class GuaranteedEncodedFormX509Certificate extends DelegatingX509Certificate { 28 | private static final long serialVersionUID = 1L; 29 | 30 | private final byte[] mEncodedForm; 31 | private int mHash = -1; 32 | 33 | public GuaranteedEncodedFormX509Certificate(X509Certificate wrapped, byte[] encodedForm) { 34 | super(wrapped); 35 | this.mEncodedForm = (encodedForm != null) ? encodedForm.clone() : null; 36 | } 37 | 38 | @Override 39 | public byte[] getEncoded() throws CertificateEncodingException { 40 | return (mEncodedForm != null) ? mEncodedForm.clone() : null; 41 | } 42 | 43 | @Override 44 | public boolean equals(Object o) { 45 | if (this == o) return true; 46 | if (!(o instanceof X509Certificate)) return false; 47 | 48 | try { 49 | byte[] a = this.getEncoded(); 50 | byte[] b = ((X509Certificate) o).getEncoded(); 51 | return Arrays.equals(a, b); 52 | } catch (CertificateEncodingException e) { 53 | return false; 54 | } 55 | } 56 | 57 | @Override 58 | public int hashCode() { 59 | if (mHash == -1) { 60 | try { 61 | mHash = Arrays.hashCode(this.getEncoded()); 62 | } catch (CertificateEncodingException e) { 63 | mHash = 0; 64 | } 65 | } 66 | return mHash; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/util/InclusiveIntRange.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.util; 18 | 19 | import java.util.ArrayList; 20 | import java.util.Collections; 21 | import java.util.List; 22 | 23 | /** 24 | * Inclusive interval of integers. 25 | */ 26 | public class InclusiveIntRange { 27 | private final int min; 28 | private final int max; 29 | 30 | private InclusiveIntRange(int min, int max) { 31 | this.min = min; 32 | this.max = max; 33 | } 34 | 35 | public static InclusiveIntRange fromTo(int min, int max) { 36 | return new InclusiveIntRange(min, max); 37 | } 38 | 39 | public static InclusiveIntRange from(int min) { 40 | return new InclusiveIntRange(min, Integer.MAX_VALUE); 41 | } 42 | 43 | public int getMin() { 44 | return min; 45 | } 46 | 47 | public int getMax() { 48 | return max; 49 | } 50 | 51 | public List getValuesNotIn( 52 | List sortedNonOverlappingRanges) { 53 | if (sortedNonOverlappingRanges.isEmpty()) { 54 | return Collections.singletonList(this); 55 | } 56 | 57 | int testValue = min; 58 | List result = null; 59 | for (InclusiveIntRange range : sortedNonOverlappingRanges) { 60 | int rangeMax = range.max; 61 | if (testValue > rangeMax) { 62 | continue; 63 | } 64 | int rangeMin = range.min; 65 | if (testValue < range.min) { 66 | if (result == null) { 67 | result = new ArrayList<>(); 68 | } 69 | result.add(fromTo(testValue, rangeMin - 1)); 70 | } 71 | if (rangeMax >= max) { 72 | return (result != null) ? result : Collections.emptyList(); 73 | } 74 | testValue = rangeMax + 1; 75 | } 76 | if (testValue <= max) { 77 | if (result == null) { 78 | result = new ArrayList<>(1); 79 | } 80 | result.add(fromTo(testValue, max)); 81 | } 82 | return (result != null) ? result : Collections.emptyList(); 83 | } 84 | 85 | @Override 86 | public String toString() { 87 | return "[" + min + ", " + ((max < Integer.MAX_VALUE) ? (max + "]") : "\u221e)"); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/util/MathCompat.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 Muntashir Al-Islam 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.util; 18 | 19 | public class MathCompat { 20 | /** 21 | * Returns the value of the {@code long} argument; 22 | * throwing an exception if the value overflows an {@code int}. 23 | * 24 | * @param value the long value 25 | * @return the argument as an int 26 | * @throws ArithmeticException if the {@code argument} overflows an int 27 | */ 28 | public static int toIntExact(long value) { 29 | if ((int) value != value) { 30 | throw new ArithmeticException("integer overflow"); 31 | } 32 | return (int) value; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/util/MessageDigestSink.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.android.apksig.internal.util; 17 | 18 | import com.android.apksig.util.DataSink; 19 | 20 | import java.nio.ByteBuffer; 21 | import java.security.MessageDigest; 22 | 23 | /** 24 | * Data sink which feeds all received data into the associated {@link MessageDigest} instances. Each 25 | * {@code MessageDigest} instance receives the same data. 26 | */ 27 | public class MessageDigestSink implements DataSink { 28 | 29 | private final MessageDigest[] mMessageDigests; 30 | 31 | public MessageDigestSink(MessageDigest[] digests) { 32 | mMessageDigests = digests; 33 | } 34 | 35 | @Override 36 | public void consume(byte[] buf, int offset, int length) { 37 | for (MessageDigest md : mMessageDigests) { 38 | md.update(buf, offset, length); 39 | } 40 | } 41 | 42 | @Override 43 | public void consume(ByteBuffer buf) { 44 | int originalPosition = buf.position(); 45 | for (MessageDigest md : mMessageDigests) { 46 | // Reset the position back to the original because the previous iteration's 47 | // MessageDigest.update set the buffer's position to the buffer's limit. 48 | buf.position(originalPosition); 49 | md.update(buf); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/util/OutputStreamDataSink.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.util; 18 | 19 | import com.android.apksig.util.DataSink; 20 | 21 | import java.io.IOException; 22 | import java.io.OutputStream; 23 | import java.nio.ByteBuffer; 24 | 25 | /** 26 | * {@link DataSink} which outputs received data into the associated {@link OutputStream}. 27 | */ 28 | public class OutputStreamDataSink implements DataSink { 29 | 30 | private static final int MAX_READ_CHUNK_SIZE = 65536; 31 | 32 | private final OutputStream mOut; 33 | 34 | /** 35 | * Constructs a new {@code OutputStreamDataSink} which outputs received data into the provided 36 | * {@link OutputStream}. 37 | */ 38 | public OutputStreamDataSink(OutputStream out) { 39 | if (out == null) { 40 | throw new NullPointerException("out == null"); 41 | } 42 | mOut = out; 43 | } 44 | 45 | /** 46 | * Returns {@link OutputStream} into which this data sink outputs received data. 47 | */ 48 | public OutputStream getOutputStream() { 49 | return mOut; 50 | } 51 | 52 | @Override 53 | public void consume(byte[] buf, int offset, int length) throws IOException { 54 | mOut.write(buf, offset, length); 55 | } 56 | 57 | @Override 58 | public void consume(ByteBuffer buf) throws IOException { 59 | if (!buf.hasRemaining()) { 60 | return; 61 | } 62 | 63 | if (buf.hasArray()) { 64 | mOut.write( 65 | buf.array(), 66 | buf.arrayOffset() + buf.position(), 67 | buf.remaining()); 68 | buf.position(buf.limit()); 69 | } else { 70 | byte[] tmp = new byte[Math.min(buf.remaining(), MAX_READ_CHUNK_SIZE)]; 71 | while (buf.hasRemaining()) { 72 | int chunkSize = Math.min(buf.remaining(), tmp.length); 73 | buf.get(tmp, 0, chunkSize); 74 | mOut.write(tmp, 0, chunkSize); 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/util/Pair.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.util; 18 | 19 | /** 20 | * Pair of two elements. 21 | */ 22 | public final class Pair { 23 | private final A mFirst; 24 | private final B mSecond; 25 | 26 | private Pair(A first, B second) { 27 | mFirst = first; 28 | mSecond = second; 29 | } 30 | 31 | public static Pair of(A first, B second) { 32 | return new Pair(first, second); 33 | } 34 | 35 | public A getFirst() { 36 | return mFirst; 37 | } 38 | 39 | public B getSecond() { 40 | return mSecond; 41 | } 42 | 43 | @Override 44 | public int hashCode() { 45 | final int prime = 31; 46 | int result = 1; 47 | result = prime * result + ((mFirst == null) ? 0 : mFirst.hashCode()); 48 | result = prime * result + ((mSecond == null) ? 0 : mSecond.hashCode()); 49 | return result; 50 | } 51 | 52 | @Override 53 | public boolean equals(Object obj) { 54 | if (this == obj) { 55 | return true; 56 | } 57 | if (obj == null) { 58 | return false; 59 | } 60 | if (getClass() != obj.getClass()) { 61 | return false; 62 | } 63 | @SuppressWarnings("rawtypes") 64 | Pair other = (Pair) obj; 65 | if (mFirst == null) { 66 | if (other.mFirst != null) { 67 | return false; 68 | } 69 | } else if (!mFirst.equals(other.mFirst)) { 70 | return false; 71 | } 72 | if (mSecond == null) { 73 | if (other.mSecond != null) { 74 | return false; 75 | } 76 | } else if (!mSecond.equals(other.mSecond)) { 77 | return false; 78 | } 79 | return true; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/util/RandomAccessFileDataSink.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.util; 18 | 19 | import com.android.apksig.util.DataSink; 20 | 21 | import java.io.IOException; 22 | import java.io.RandomAccessFile; 23 | import java.nio.ByteBuffer; 24 | import java.nio.channels.FileChannel; 25 | 26 | /** 27 | * {@link DataSink} which outputs received data into the associated file, sequentially. 28 | */ 29 | public class RandomAccessFileDataSink implements DataSink { 30 | 31 | private final RandomAccessFile mFile; 32 | private final FileChannel mFileChannel; 33 | private long mPosition; 34 | 35 | /** 36 | * Constructs a new {@code RandomAccessFileDataSink} which stores output starting from the 37 | * beginning of the provided file. 38 | */ 39 | public RandomAccessFileDataSink(RandomAccessFile file) { 40 | this(file, 0); 41 | } 42 | 43 | /** 44 | * Constructs a new {@code RandomAccessFileDataSink} which stores output starting from the 45 | * specified position of the provided file. 46 | */ 47 | public RandomAccessFileDataSink(RandomAccessFile file, long startPosition) { 48 | if (file == null) { 49 | throw new NullPointerException("file == null"); 50 | } 51 | if (startPosition < 0) { 52 | throw new IllegalArgumentException("startPosition: " + startPosition); 53 | } 54 | mFile = file; 55 | mFileChannel = file.getChannel(); 56 | mPosition = startPosition; 57 | } 58 | 59 | /** 60 | * Returns the underlying {@link RandomAccessFile}. 61 | */ 62 | public RandomAccessFile getFile() { 63 | return mFile; 64 | } 65 | 66 | @Override 67 | public void consume(byte[] buf, int offset, int length) throws IOException { 68 | if (offset < 0) { 69 | // Must perform this check here because RandomAccessFile.write doesn't throw when offset 70 | // is negative but length is 0 71 | throw new IndexOutOfBoundsException("offset: " + offset); 72 | } 73 | if (offset > buf.length) { 74 | // Must perform this check here because RandomAccessFile.write doesn't throw when offset 75 | // is too large but length is 0 76 | throw new IndexOutOfBoundsException( 77 | "offset: " + offset + ", buf.length: " + buf.length); 78 | } 79 | if (length == 0) { 80 | return; 81 | } 82 | 83 | synchronized (mFile) { 84 | mFile.seek(mPosition); 85 | mFile.write(buf, offset, length); 86 | mPosition += length; 87 | } 88 | } 89 | 90 | @Override 91 | public void consume(ByteBuffer buf) throws IOException { 92 | int length = buf.remaining(); 93 | if (length == 0) { 94 | return; 95 | } 96 | 97 | synchronized (mFile) { 98 | mFile.seek(mPosition); 99 | while (buf.hasRemaining()) { 100 | mFileChannel.write(buf); 101 | } 102 | mPosition += length; 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/util/SupplierCompat.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 Muntashir Al-Islam 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.util; 18 | 19 | @FunctionalInterface 20 | public interface SupplierCompat { 21 | T get(); 22 | } 23 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/util/TeeDataSink.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.util; 18 | 19 | import com.android.apksig.util.DataSink; 20 | 21 | import java.io.IOException; 22 | import java.nio.ByteBuffer; 23 | 24 | /** 25 | * {@link DataSink} which copies provided input into each of the sinks provided to it. 26 | */ 27 | public class TeeDataSink implements DataSink { 28 | 29 | private final DataSink[] mSinks; 30 | 31 | public TeeDataSink(DataSink[] sinks) { 32 | mSinks = sinks; 33 | } 34 | 35 | @Override 36 | public void consume(byte[] buf, int offset, int length) throws IOException { 37 | for (DataSink sink : mSinks) { 38 | sink.consume(buf, offset, length); 39 | } 40 | } 41 | 42 | @Override 43 | public void consume(ByteBuffer buf) throws IOException { 44 | int originalPosition = buf.position(); 45 | for (int i = 0; i < mSinks.length; i++) { 46 | if (i > 0) { 47 | buf.position(originalPosition); 48 | } 49 | mSinks[i].consume(buf); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/x509/AttributeTypeAndValue.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.x509; 18 | 19 | import com.android.apksig.internal.asn1.Asn1Class; 20 | import com.android.apksig.internal.asn1.Asn1Field; 21 | import com.android.apksig.internal.asn1.Asn1OpaqueObject; 22 | import com.android.apksig.internal.asn1.Asn1Type; 23 | 24 | /** 25 | * {@code AttributeTypeAndValue} as specified in RFC 5280. 26 | */ 27 | @Asn1Class(type = Asn1Type.SEQUENCE) 28 | public class AttributeTypeAndValue { 29 | 30 | @Asn1Field(index = 0, type = Asn1Type.OBJECT_IDENTIFIER) 31 | public String attrType; 32 | 33 | @Asn1Field(index = 1, type = Asn1Type.ANY) 34 | public Asn1OpaqueObject attrValue; 35 | } -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/x509/Certificate.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.x509; 18 | 19 | import com.android.apksig.internal.asn1.Asn1Class; 20 | import com.android.apksig.internal.asn1.Asn1Field; 21 | import com.android.apksig.internal.asn1.Asn1OpaqueObject; 22 | import com.android.apksig.internal.asn1.Asn1Type; 23 | import com.android.apksig.internal.pkcs7.AlgorithmIdentifier; 24 | import com.android.apksig.internal.pkcs7.IssuerAndSerialNumber; 25 | import com.android.apksig.internal.pkcs7.SignerIdentifier; 26 | import com.android.apksig.internal.util.ByteBufferUtils; 27 | import com.android.apksig.internal.util.GuaranteedEncodedFormX509Certificate; 28 | import com.android.apksig.internal.util.X509CertificateUtils; 29 | 30 | import java.math.BigInteger; 31 | import java.nio.ByteBuffer; 32 | import java.security.cert.CertificateException; 33 | import java.security.cert.X509Certificate; 34 | import java.util.ArrayList; 35 | import java.util.Collection; 36 | import java.util.Collections; 37 | import java.util.List; 38 | 39 | import javax.security.auth.x500.X500Principal; 40 | 41 | /** 42 | * X509 {@code Certificate} as specified in RFC 5280. 43 | */ 44 | @Asn1Class(type = Asn1Type.SEQUENCE) 45 | public class Certificate { 46 | @Asn1Field(index = 0, type = Asn1Type.SEQUENCE) 47 | public TBSCertificate certificate; 48 | 49 | @Asn1Field(index = 1, type = Asn1Type.SEQUENCE) 50 | public AlgorithmIdentifier signatureAlgorithm; 51 | 52 | @Asn1Field(index = 2, type = Asn1Type.BIT_STRING) 53 | public ByteBuffer signature; 54 | 55 | public static X509Certificate findCertificate( 56 | Collection certs, SignerIdentifier id) { 57 | for (X509Certificate cert : certs) { 58 | if (isMatchingCerticicate(cert, id)) { 59 | return cert; 60 | } 61 | } 62 | return null; 63 | } 64 | 65 | private static boolean isMatchingCerticicate(X509Certificate cert, SignerIdentifier id) { 66 | if (id.issuerAndSerialNumber == null) { 67 | // Android doesn't support any other means of identifying the signing certificate 68 | return false; 69 | } 70 | IssuerAndSerialNumber issuerAndSerialNumber = id.issuerAndSerialNumber; 71 | byte[] encodedIssuer = 72 | ByteBufferUtils.toByteArray(issuerAndSerialNumber.issuer.getEncoded()); 73 | X500Principal idIssuer = new X500Principal(encodedIssuer); 74 | BigInteger idSerialNumber = issuerAndSerialNumber.certificateSerialNumber; 75 | return idSerialNumber.equals(cert.getSerialNumber()) 76 | && idIssuer.equals(cert.getIssuerX500Principal()); 77 | } 78 | 79 | public static List parseCertificates( 80 | List encodedCertificates) throws CertificateException { 81 | if (encodedCertificates.isEmpty()) { 82 | return Collections.emptyList(); 83 | } 84 | 85 | List result = new ArrayList<>(encodedCertificates.size()); 86 | for (int i = 0; i < encodedCertificates.size(); i++) { 87 | Asn1OpaqueObject encodedCertificate = encodedCertificates.get(i); 88 | X509Certificate certificate; 89 | byte[] encodedForm = ByteBufferUtils.toByteArray(encodedCertificate.getEncoded()); 90 | try { 91 | certificate = X509CertificateUtils.generateCertificate(encodedForm); 92 | } catch (CertificateException e) { 93 | throw new CertificateException("Failed to parse certificate #" + (i + 1), e); 94 | } 95 | // Wrap the cert so that the result's getEncoded returns exactly the original 96 | // encoded form. Without this, getEncoded may return a different form from what was 97 | // stored in the signature. This is because some X509Certificate(Factory) 98 | // implementations re-encode certificates and/or some implementations of 99 | // X509Certificate.getEncoded() re-encode certificates. 100 | certificate = new GuaranteedEncodedFormX509Certificate(certificate, encodedForm); 101 | result.add(certificate); 102 | } 103 | return result; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/x509/Extension.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.x509; 18 | 19 | import com.android.apksig.internal.asn1.Asn1Class; 20 | import com.android.apksig.internal.asn1.Asn1Field; 21 | import com.android.apksig.internal.asn1.Asn1Type; 22 | 23 | import java.nio.ByteBuffer; 24 | 25 | /** 26 | * X509 {@code Extension} as specified in RFC 5280. 27 | */ 28 | @Asn1Class(type = Asn1Type.SEQUENCE) 29 | public class Extension { 30 | @Asn1Field(index = 0, type = Asn1Type.OBJECT_IDENTIFIER) 31 | public String extensionID; 32 | 33 | @Asn1Field(index = 1, type = Asn1Type.BOOLEAN, optional = true) 34 | public boolean isCritial = false; 35 | 36 | @Asn1Field(index = 2, type = Asn1Type.OCTET_STRING) 37 | public ByteBuffer extensionValue; 38 | } 39 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/x509/Name.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.x509; 18 | 19 | import com.android.apksig.internal.asn1.Asn1Class; 20 | import com.android.apksig.internal.asn1.Asn1Field; 21 | import com.android.apksig.internal.asn1.Asn1Type; 22 | 23 | import java.util.List; 24 | 25 | /** 26 | * X501 {@code Name} as specified in RFC 5280. 27 | */ 28 | @Asn1Class(type = Asn1Type.CHOICE) 29 | public class Name { 30 | 31 | // This field is the RDNSequence specified in RFC 5280. 32 | @Asn1Field(index = 0, type = Asn1Type.SEQUENCE_OF) 33 | public List relativeDistinguishedNames; 34 | } 35 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/x509/RSAPublicKey.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.x509; 18 | 19 | import com.android.apksig.internal.asn1.Asn1Class; 20 | import com.android.apksig.internal.asn1.Asn1Field; 21 | import com.android.apksig.internal.asn1.Asn1Type; 22 | 23 | import java.math.BigInteger; 24 | 25 | /** 26 | * {@code RSAPublicKey} as specified in RFC 3279. 27 | */ 28 | @Asn1Class(type = Asn1Type.SEQUENCE) 29 | public class RSAPublicKey { 30 | @Asn1Field(index = 0, type = Asn1Type.INTEGER) 31 | public BigInteger modulus; 32 | 33 | @Asn1Field(index = 1, type = Asn1Type.INTEGER) 34 | public BigInteger publicExponent; 35 | } 36 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/x509/RelativeDistinguishedName.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.x509; 18 | 19 | import com.android.apksig.internal.asn1.Asn1Class; 20 | import com.android.apksig.internal.asn1.Asn1Field; 21 | import com.android.apksig.internal.asn1.Asn1Type; 22 | 23 | import java.util.List; 24 | 25 | /** 26 | * {@code RelativeDistinguishedName} as specified in RFC 5280. 27 | */ 28 | @Asn1Class(type = Asn1Type.UNENCODED_CONTAINER) 29 | public class RelativeDistinguishedName { 30 | 31 | @Asn1Field(index = 0, type = Asn1Type.SET_OF) 32 | public List attributes; 33 | } 34 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/x509/SubjectPublicKeyInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.x509; 18 | 19 | import com.android.apksig.internal.asn1.Asn1Class; 20 | import com.android.apksig.internal.asn1.Asn1Field; 21 | import com.android.apksig.internal.asn1.Asn1Type; 22 | import com.android.apksig.internal.pkcs7.AlgorithmIdentifier; 23 | 24 | import java.nio.ByteBuffer; 25 | 26 | /** 27 | * {@code SubjectPublicKeyInfo} as specified in RFC 5280. 28 | */ 29 | @Asn1Class(type = Asn1Type.SEQUENCE) 30 | public class SubjectPublicKeyInfo { 31 | @Asn1Field(index = 0, type = Asn1Type.SEQUENCE) 32 | public AlgorithmIdentifier algorithmIdentifier; 33 | 34 | @Asn1Field(index = 1, type = Asn1Type.BIT_STRING) 35 | public ByteBuffer subjectPublicKey; 36 | } 37 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/x509/TBSCertificate.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.x509; 18 | 19 | import com.android.apksig.internal.asn1.Asn1Class; 20 | import com.android.apksig.internal.asn1.Asn1Field; 21 | import com.android.apksig.internal.asn1.Asn1Tagging; 22 | import com.android.apksig.internal.asn1.Asn1Type; 23 | import com.android.apksig.internal.pkcs7.AlgorithmIdentifier; 24 | 25 | import java.math.BigInteger; 26 | import java.nio.ByteBuffer; 27 | import java.util.List; 28 | 29 | /** 30 | * To Be Signed Certificate as specified in RFC 5280. 31 | */ 32 | @Asn1Class(type = Asn1Type.SEQUENCE) 33 | public class TBSCertificate { 34 | 35 | @Asn1Field( 36 | index = 0, 37 | type = Asn1Type.INTEGER, 38 | tagging = Asn1Tagging.EXPLICIT, tagNumber = 0) 39 | public int version; 40 | 41 | @Asn1Field(index = 1, type = Asn1Type.INTEGER) 42 | public BigInteger serialNumber; 43 | 44 | @Asn1Field(index = 2, type = Asn1Type.SEQUENCE) 45 | public AlgorithmIdentifier signatureAlgorithm; 46 | 47 | @Asn1Field(index = 3, type = Asn1Type.CHOICE) 48 | public Name issuer; 49 | 50 | @Asn1Field(index = 4, type = Asn1Type.SEQUENCE) 51 | public Validity validity; 52 | 53 | @Asn1Field(index = 5, type = Asn1Type.CHOICE) 54 | public Name subject; 55 | 56 | @Asn1Field(index = 6, type = Asn1Type.SEQUENCE) 57 | public SubjectPublicKeyInfo subjectPublicKeyInfo; 58 | 59 | @Asn1Field(index = 7, 60 | type = Asn1Type.BIT_STRING, 61 | tagging = Asn1Tagging.IMPLICIT, 62 | optional = true, 63 | tagNumber = 1) 64 | public ByteBuffer issuerUniqueID; 65 | 66 | @Asn1Field(index = 8, 67 | type = Asn1Type.BIT_STRING, 68 | tagging = Asn1Tagging.IMPLICIT, 69 | optional = true, 70 | tagNumber = 2) 71 | public ByteBuffer subjectUniqueID; 72 | 73 | @Asn1Field(index = 9, 74 | type = Asn1Type.SEQUENCE_OF, 75 | tagging = Asn1Tagging.EXPLICIT, 76 | optional = true, 77 | tagNumber = 3) 78 | public List extensions; 79 | } 80 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/x509/Time.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.x509; 18 | 19 | import com.android.apksig.internal.asn1.Asn1Class; 20 | import com.android.apksig.internal.asn1.Asn1Field; 21 | import com.android.apksig.internal.asn1.Asn1Type; 22 | 23 | /** 24 | * {@code Time} as specified in RFC 5280. 25 | */ 26 | @Asn1Class(type = Asn1Type.CHOICE) 27 | public class Time { 28 | 29 | @Asn1Field(type = Asn1Type.UTC_TIME) 30 | public String utcTime; 31 | 32 | @Asn1Field(type = Asn1Type.GENERALIZED_TIME) 33 | public String generalizedTime; 34 | } 35 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/x509/Validity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.x509; 18 | 19 | import com.android.apksig.internal.asn1.Asn1Class; 20 | import com.android.apksig.internal.asn1.Asn1Field; 21 | import com.android.apksig.internal.asn1.Asn1Type; 22 | 23 | /** 24 | * {@code Validity} as specified in RFC 5280. 25 | */ 26 | @Asn1Class(type = Asn1Type.SEQUENCE) 27 | public class Validity { 28 | 29 | @Asn1Field(index = 0, type = Asn1Type.CHOICE) 30 | public Time notBefore; 31 | 32 | @Asn1Field(index = 1, type = Asn1Type.CHOICE) 33 | public Time notAfter; 34 | } 35 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/internal/zip/EocdRecord.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.internal.zip; 18 | 19 | import java.nio.ByteBuffer; 20 | import java.nio.ByteOrder; 21 | 22 | /** 23 | * ZIP End of Central Directory record. 24 | */ 25 | public class EocdRecord { 26 | private static final int CD_RECORD_COUNT_ON_DISK_OFFSET = 8; 27 | private static final int CD_RECORD_COUNT_TOTAL_OFFSET = 10; 28 | private static final int CD_SIZE_OFFSET = 12; 29 | private static final int CD_OFFSET_OFFSET = 16; 30 | 31 | public static ByteBuffer createWithModifiedCentralDirectoryInfo( 32 | ByteBuffer original, 33 | int centralDirectoryRecordCount, 34 | long centralDirectorySizeBytes, 35 | long centralDirectoryOffset) { 36 | ByteBuffer result = ByteBuffer.allocate(original.remaining()); 37 | result.order(ByteOrder.LITTLE_ENDIAN); 38 | result.put(original.slice()); 39 | result.flip(); 40 | ZipUtils.setUnsignedInt16( 41 | result, CD_RECORD_COUNT_ON_DISK_OFFSET, centralDirectoryRecordCount); 42 | ZipUtils.setUnsignedInt16( 43 | result, CD_RECORD_COUNT_TOTAL_OFFSET, centralDirectoryRecordCount); 44 | ZipUtils.setUnsignedInt32(result, CD_SIZE_OFFSET, centralDirectorySizeBytes); 45 | ZipUtils.setUnsignedInt32(result, CD_OFFSET_OFFSET, centralDirectoryOffset); 46 | return result; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/util/DataSink.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.util; 18 | 19 | import java.io.IOException; 20 | import java.nio.ByteBuffer; 21 | 22 | /** 23 | * Consumer of input data which may be provided in one go or in chunks. 24 | */ 25 | public interface DataSink { 26 | 27 | /** 28 | * Consumes the provided chunk of data. 29 | * 30 | *

This data sink guarantees to not hold references to the provided buffer after this method 31 | * terminates. 32 | * 33 | * @throws IndexOutOfBoundsException if {@code offset} or {@code length} are negative, or if 34 | * {@code offset + length} is greater than {@code buf.length}. 35 | */ 36 | void consume(byte[] buf, int offset, int length) throws IOException; 37 | 38 | /** 39 | * Consumes all remaining data in the provided buffer and advances the buffer's position 40 | * to the buffer's limit. 41 | * 42 | *

This data sink guarantees to not hold references to the provided buffer after this method 43 | * terminates. 44 | */ 45 | void consume(ByteBuffer buf) throws IOException; 46 | } 47 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/util/DataSinks.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.util; 18 | 19 | import com.android.apksig.internal.util.ByteArrayDataSink; 20 | import com.android.apksig.internal.util.MessageDigestSink; 21 | import com.android.apksig.internal.util.OutputStreamDataSink; 22 | import com.android.apksig.internal.util.RandomAccessFileDataSink; 23 | 24 | import java.io.OutputStream; 25 | import java.io.RandomAccessFile; 26 | import java.security.MessageDigest; 27 | 28 | /** 29 | * Utility methods for working with {@link DataSink} abstraction. 30 | */ 31 | public abstract class DataSinks { 32 | private DataSinks() { 33 | } 34 | 35 | /** 36 | * Returns a {@link DataSink} which outputs received data into the provided 37 | * {@link OutputStream}. 38 | */ 39 | public static DataSink asDataSink(OutputStream out) { 40 | return new OutputStreamDataSink(out); 41 | } 42 | 43 | /** 44 | * Returns a {@link DataSink} which outputs received data into the provided file, sequentially, 45 | * starting at the beginning of the file. 46 | */ 47 | public static DataSink asDataSink(RandomAccessFile file) { 48 | return new RandomAccessFileDataSink(file); 49 | } 50 | 51 | /** 52 | * Returns a {@link DataSink} which forwards data into the provided {@link MessageDigest} 53 | * instances via their {@code update} method. Each {@code MessageDigest} instance receives the 54 | * same data. 55 | */ 56 | public static DataSink asDataSink(MessageDigest... digests) { 57 | return new MessageDigestSink(digests); 58 | } 59 | 60 | /** 61 | * Returns a new in-memory {@link DataSink} which exposes all data consumed so far via the 62 | * {@link DataSource} interface. 63 | */ 64 | public static ReadableDataSink newInMemoryDataSink() { 65 | return new ByteArrayDataSink(); 66 | } 67 | 68 | /** 69 | * Returns a new in-memory {@link DataSink} which exposes all data consumed so far via the 70 | * {@link DataSource} interface. 71 | * 72 | * @param initialCapacity initial capacity in bytes 73 | */ 74 | public static ReadableDataSink newInMemoryDataSink(int initialCapacity) { 75 | return new ByteArrayDataSink(initialCapacity); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/util/DataSource.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.util; 18 | 19 | import java.io.IOException; 20 | import java.nio.ByteBuffer; 21 | 22 | /** 23 | * Abstract representation of a source of data. 24 | * 25 | *

This abstraction serves three purposes: 26 | *

33 | * 34 | *

There are following ways to obtain a chunk of data from the data source: 35 | *

    36 | *
  • Stream the chunk's data into a {@link DataSink} using 37 | * {@link #feed(long, long, DataSink) feed}. This is best suited for scenarios where there is no 38 | * need to have the chunk's data accessible at the same time, for example, when computing the 39 | * digest of the chunk. If you need to keep the chunk's data around after {@code feed} 40 | * completes, you must create a copy during {@code feed}. However, in that case the following 41 | * methods of obtaining the chunk's data may be more appropriate.
  • 42 | *
  • Obtain a {@link ByteBuffer} containing the chunk's data using 43 | * {@link #getByteBuffer(long, int) getByteBuffer}. Depending on the data source, the chunk's 44 | * data may or may not be copied by this operation. This is best suited for scenarios where 45 | * you need to access the chunk's data in arbitrary order, but don't need to modify the data and 46 | * thus don't require a copy of the data.
  • 47 | *
  • Copy the chunk's data to a {@link ByteBuffer} using 48 | * {@link #copyTo(long, int, ByteBuffer) copyTo}. This is best suited for scenarios where 49 | * you require a copy of the chunk's data, such as to when you need to modify the data. 50 | *
  • 51 | *
52 | */ 53 | public interface DataSource { 54 | 55 | /** 56 | * Returns the amount of data (in bytes) contained in this data source. 57 | */ 58 | long size(); 59 | 60 | /** 61 | * Feeds the specified chunk from this data source into the provided sink. 62 | * 63 | * @param offset index (in bytes) at which the chunk starts inside data source 64 | * @param size size (in bytes) of the chunk 65 | * @throws IndexOutOfBoundsException if {@code offset} or {@code size} is negative, or if 66 | * {@code offset + size} is greater than {@link #size()}. 67 | */ 68 | void feed(long offset, long size, DataSink sink) throws IOException; 69 | 70 | /** 71 | * Returns a buffer holding the contents of the specified chunk of data from this data source. 72 | * Changes to the data source are not guaranteed to be reflected in the returned buffer. 73 | * Similarly, changes in the buffer are not guaranteed to be reflected in the data source. 74 | * 75 | *

The returned buffer's position is {@code 0}, and the buffer's limit and capacity is 76 | * {@code size}. 77 | * 78 | * @param offset index (in bytes) at which the chunk starts inside data source 79 | * @param size size (in bytes) of the chunk 80 | * @throws IndexOutOfBoundsException if {@code offset} or {@code size} is negative, or if 81 | * {@code offset + size} is greater than {@link #size()}. 82 | */ 83 | ByteBuffer getByteBuffer(long offset, int size) throws IOException; 84 | 85 | /** 86 | * Copies the specified chunk from this data source into the provided destination buffer, 87 | * advancing the destination buffer's position by {@code size}. 88 | * 89 | * @param offset index (in bytes) at which the chunk starts inside data source 90 | * @param size size (in bytes) of the chunk 91 | * @throws IndexOutOfBoundsException if {@code offset} or {@code size} is negative, or if 92 | * {@code offset + size} is greater than {@link #size()}. 93 | */ 94 | void copyTo(long offset, int size, ByteBuffer dest) throws IOException; 95 | 96 | /** 97 | * Returns a data source representing the specified region of data of this data source. Changes 98 | * to data represented by this data source will also be visible in the returned data source. 99 | * 100 | * @param offset index (in bytes) at which the region starts inside data source 101 | * @param size size (in bytes) of the region 102 | * @throws IndexOutOfBoundsException if {@code offset} or {@code size} is negative, or if 103 | * {@code offset + size} is greater than {@link #size()}. 104 | */ 105 | DataSource slice(long offset, long size); 106 | } 107 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/util/DataSources.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.util; 18 | 19 | import com.android.apksig.internal.util.ByteBufferDataSource; 20 | import com.android.apksig.internal.util.FileChannelDataSource; 21 | 22 | import java.io.RandomAccessFile; 23 | import java.nio.ByteBuffer; 24 | import java.nio.channels.FileChannel; 25 | 26 | /** 27 | * Utility methods for working with {@link DataSource} abstraction. 28 | */ 29 | public abstract class DataSources { 30 | private DataSources() { 31 | } 32 | 33 | /** 34 | * Returns a {@link DataSource} backed by the provided {@link ByteBuffer}. The data source 35 | * represents the data contained between the position and limit of the buffer. Changes to the 36 | * buffer's contents will be visible in the data source. 37 | */ 38 | public static DataSource asDataSource(ByteBuffer buffer) { 39 | if (buffer == null) { 40 | throw new NullPointerException(); 41 | } 42 | return new ByteBufferDataSource(buffer); 43 | } 44 | 45 | /** 46 | * Returns a {@link DataSource} backed by the provided {@link RandomAccessFile}. Changes to the 47 | * file, including changes to size of file, will be visible in the data source. 48 | */ 49 | public static DataSource asDataSource(RandomAccessFile file) { 50 | return asDataSource(file.getChannel()); 51 | } 52 | 53 | /** 54 | * Returns a {@link DataSource} backed by the provided region of the {@link RandomAccessFile}. 55 | * Changes to the file will be visible in the data source. 56 | */ 57 | public static DataSource asDataSource(RandomAccessFile file, long offset, long size) { 58 | return asDataSource(file.getChannel(), offset, size); 59 | } 60 | 61 | /** 62 | * Returns a {@link DataSource} backed by the provided {@link FileChannel}. Changes to the 63 | * file, including changes to size of file, will be visible in the data source. 64 | */ 65 | public static DataSource asDataSource(FileChannel channel) { 66 | if (channel == null) { 67 | throw new NullPointerException(); 68 | } 69 | return new FileChannelDataSource(channel); 70 | } 71 | 72 | /** 73 | * Returns a {@link DataSource} backed by the provided region of the {@link FileChannel}. 74 | * Changes to the file will be visible in the data source. 75 | */ 76 | public static DataSource asDataSource(FileChannel channel, long offset, long size) { 77 | if (channel == null) { 78 | throw new NullPointerException(); 79 | } 80 | return new FileChannelDataSource(channel, offset, size); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/util/ReadableDataSink.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.util; 18 | 19 | /** 20 | * {@link DataSink} which exposes all data consumed so far as a {@link DataSource}. This abstraction 21 | * offers append-only write access and random read access. 22 | */ 23 | public interface ReadableDataSink extends DataSink, DataSource { 24 | } 25 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/util/RunnablesExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.util; 18 | 19 | import static java.util.concurrent.TimeUnit.MILLISECONDS; 20 | 21 | import java.util.concurrent.ArrayBlockingQueue; 22 | import java.util.concurrent.ExecutorService; 23 | import java.util.concurrent.Phaser; 24 | import java.util.concurrent.ThreadPoolExecutor; 25 | 26 | public interface RunnablesExecutor { 27 | static final RunnablesExecutor SINGLE_THREADED = p -> p.createRunnable().run(); 28 | 29 | static final RunnablesExecutor MULTI_THREADED = new RunnablesExecutor() { 30 | private final int PARALLELISM = Math.min(32, Runtime.getRuntime().availableProcessors()); 31 | private final int QUEUE_SIZE = 4; 32 | 33 | @Override 34 | public void execute(RunnablesProvider provider) { 35 | final ExecutorService mExecutor = 36 | new ThreadPoolExecutor(PARALLELISM, PARALLELISM, 37 | 0L, MILLISECONDS, 38 | new ArrayBlockingQueue<>(QUEUE_SIZE), 39 | new ThreadPoolExecutor.CallerRunsPolicy()); 40 | 41 | Phaser tasks = new Phaser(1); 42 | 43 | for (int i = 0; i < PARALLELISM; ++i) { 44 | Runnable task = () -> { 45 | Runnable r = provider.createRunnable(); 46 | r.run(); 47 | tasks.arriveAndDeregister(); 48 | }; 49 | tasks.register(); 50 | mExecutor.execute(task); 51 | } 52 | 53 | // Waiting for the tasks to complete. 54 | tasks.arriveAndAwaitAdvance(); 55 | 56 | mExecutor.shutdownNow(); 57 | } 58 | }; 59 | 60 | void execute(RunnablesProvider provider); 61 | } 62 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/util/RunnablesProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.util; 18 | 19 | public interface RunnablesProvider { 20 | Runnable createRunnable(); 21 | } 22 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksig/zip/ZipFormatException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksig.zip; 18 | 19 | /** 20 | * Indicates that a ZIP archive is not well-formed. 21 | */ 22 | public class ZipFormatException extends Exception { 23 | private static final long serialVersionUID = 1L; 24 | 25 | public ZipFormatException(String message) { 26 | super(message); 27 | } 28 | 29 | public ZipFormatException(String message, Throwable cause) { 30 | super(message, cause); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksigner/HexEncoding.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksigner; 18 | 19 | import java.nio.ByteBuffer; 20 | 21 | /** 22 | * Hexadecimal encoding where each byte is represented by two hexadecimal digits. 23 | */ 24 | class HexEncoding { 25 | 26 | private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray(); 27 | 28 | /** 29 | * Hidden constructor to prevent instantiation. 30 | */ 31 | private HexEncoding() { 32 | } 33 | 34 | /** 35 | * Encodes the provided data as a hexadecimal string. 36 | */ 37 | public static String encode(byte[] data, int offset, int length) { 38 | StringBuilder result = new StringBuilder(length * 2); 39 | for (int i = 0; i < length; i++) { 40 | byte b = data[offset + i]; 41 | result.append(HEX_DIGITS[(b >>> 4) & 0x0f]); 42 | result.append(HEX_DIGITS[b & 0x0f]); 43 | } 44 | return result.toString(); 45 | } 46 | 47 | /** 48 | * Encodes the provided data as a hexadecimal string. 49 | */ 50 | public static String encode(byte[] data) { 51 | return encode(data, 0, data.length); 52 | } 53 | 54 | /** 55 | * Encodes the remaining bytes of the provided {@link ByteBuffer} as a hexadecimal string. 56 | */ 57 | public static String encodeRemaining(ByteBuffer data) { 58 | return encode(data.array(), data.arrayOffset() + data.position(), data.remaining()); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksigner/ParameterException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.apksigner; 18 | 19 | /** 20 | * Indicates that there is an issue with command-line parameters provided to {@link ApkSignerTool}. 21 | */ 22 | public class ParameterException extends Exception { 23 | private static final long serialVersionUID = 1L; 24 | 25 | ParameterException(String message) { 26 | super(message); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksigner/help.txt: -------------------------------------------------------------------------------- 1 | USAGE: apksigner [options] 2 | apksigner --version 3 | apksigner --help 4 | 5 | EXAMPLE: 6 | apksigner sign --ks release.jks app.apk 7 | apksigner verify --verbose app.apk 8 | 9 | apksigner is a tool for signing Android APK files and for checking whether 10 | signatures of APK files will verify on Android devices. 11 | 12 | 13 | COMMANDS 14 | rotate Add a new signing certificate to the SigningCertificateLineage 15 | 16 | sign Sign the provided APK 17 | 18 | verify Check whether the provided APK is expected to verify on 19 | Android 20 | 21 | lineage Modify the capabilities of one or more signers in an existing 22 | SigningCertificateLineage 23 | 24 | version Show this tool's version number and exit 25 | 26 | help Show this usage page and exit 27 | 28 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksigner/help_verify.txt: -------------------------------------------------------------------------------- 1 | USAGE: apksigner verify [options] apk 2 | 3 | This checks whether the provided APK will verify on Android. By default, this 4 | checks whether the APK will verify on all Android platform versions supported 5 | by the APK (as declared using minSdkVersion in AndroidManifest.xml). Use 6 | --min-sdk-version and/or --max-sdk-version to verify the APK against a custom 7 | range of API Levels. 8 | 9 | 10 | OPTIONS 11 | 12 | --print-certs Show information about the APK's signing certificates 13 | 14 | -v, --verbose Verbose output mode 15 | 16 | --min-sdk-version Lowest API Level on which this APK's signatures will be 17 | verified. By default, the value from AndroidManifest.xml 18 | is used. 19 | 20 | --max-sdk-version Highest API Level on which this APK's signatures will be 21 | verified. By default, the highest possible value is used. 22 | 23 | -Werr Treat warnings as errors 24 | 25 | --in APK file to verify. This is an alternative to specifying 26 | the APK as the very last parameter, after all options. 27 | 28 | -h, --help Show help about this command and exit 29 | 30 | 31 | EXAMPLES 32 | 33 | 1. Check whether the APK's signatures are expected to verify on all Android 34 | platforms declared as supported by this APK: 35 | $ apksigner verify app.apk 36 | 37 | 2. Check whether the APK's signatures are expected to verify on Android 38 | platforms with API Level 15 and higher: 39 | $ apksigner verify --min-sdk-version 15 app.apk 40 | -------------------------------------------------------------------------------- /library/src/main/java/com/android/apksigner/utils/FileUtils.java: -------------------------------------------------------------------------------- 1 | package com.android.apksigner.utils; 2 | 3 | import java.io.File; 4 | 5 | public class FileUtils { 6 | public static boolean moveFile(File sourcePath, File targetPath) { 7 | return sourcePath.renameTo(targetPath); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /library/src/main/java/com/mcal/apksigner/ApkSigner.kt: -------------------------------------------------------------------------------- 1 | package com.mcal.apksigner 2 | 3 | import com.android.apksig.ApkSigner 4 | import com.android.apksigner.ApkSignerTool 5 | import com.mcal.apksigner.utils.KeyStoreHelper 6 | import java.io.File 7 | import java.io.InputStream 8 | import java.security.PrivateKey 9 | import java.security.cert.X509Certificate 10 | 11 | class ApkSigner( 12 | private val unsignedApkFile: File, 13 | private val signedApkFile: File, 14 | ) { 15 | var useDefaultSignatureVersion = true 16 | var v1SigningEnabled = true 17 | var v2SigningEnabled = true 18 | var v3SigningEnabled = true 19 | var v4SigningEnabled = false 20 | 21 | fun signRelease( 22 | pk8File: File, 23 | x509File: File 24 | ): Boolean { 25 | val args = mutableListOf( 26 | "sign", 27 | "--in", 28 | unsignedApkFile.path, 29 | "--out", 30 | signedApkFile.path, 31 | "--key", 32 | pk8File.path, 33 | "--cert", 34 | x509File.path, 35 | ) 36 | if (!useDefaultSignatureVersion) { 37 | args.add("--v1-signing-enabled") 38 | args.add(v1SigningEnabled.toString()) 39 | args.add("--v2-signing-enabled") 40 | args.add(v2SigningEnabled.toString()) 41 | args.add("--v3-signing-enabled") 42 | args.add(v3SigningEnabled.toString()) 43 | args.add("--v4-signing-enabled") 44 | args.add(v4SigningEnabled.toString()) 45 | } 46 | return try { 47 | ApkSignerTool.main(args.toTypedArray()) 48 | true 49 | } catch (e: Exception) { 50 | e.printStackTrace() 51 | false 52 | } 53 | } 54 | 55 | fun signRelease( 56 | keyFile: File, 57 | password: String, 58 | alias: String, 59 | aliasPassword: String, 60 | ): Boolean { 61 | return try { 62 | val keystore = KeyStoreHelper.loadKeyStore(keyFile, password.toCharArray()) 63 | ApkSigner.Builder( 64 | listOf( 65 | ApkSigner.SignerConfig.Builder( 66 | "CERT", 67 | keystore.getKey(alias, aliasPassword.toCharArray()) as PrivateKey, 68 | listOf(keystore.getCertificate(alias) as X509Certificate) 69 | ).build() 70 | ) 71 | ).apply { 72 | setInputApk(unsignedApkFile) 73 | setOutputApk(signedApkFile) 74 | if (!useDefaultSignatureVersion) { 75 | setV1SigningEnabled(v1SigningEnabled) 76 | setV2SigningEnabled(v2SigningEnabled) 77 | setV3SigningEnabled(v3SigningEnabled) 78 | setV4SigningEnabled(v4SigningEnabled) 79 | } 80 | }.build().sign() 81 | true 82 | } catch (e: Exception) { 83 | e.printStackTrace() 84 | false 85 | } 86 | } 87 | 88 | fun signDebug(): Boolean { 89 | val pk8File = File.createTempFile("testkey", "pk8").also { 90 | it.writeBytes((com.mcal.apksigner.ApkSigner::class.java.getResourceAsStream("/keystore/testkey.pk8") as InputStream).readBytes()) 91 | } 92 | 93 | val x509File = File.createTempFile("testkey", "x509.pem").also { 94 | it.writeBytes((com.mcal.apksigner.ApkSigner::class.java.getResourceAsStream("/keystore/testkey.x509.pem") as InputStream).readBytes()) 95 | } 96 | 97 | val args = mutableListOf( 98 | "sign", 99 | "--in", 100 | unsignedApkFile.path, 101 | "--out", 102 | signedApkFile.path, 103 | "--key", 104 | pk8File.path, 105 | "--cert", 106 | x509File.path, 107 | ) 108 | if (!useDefaultSignatureVersion) { 109 | args.add("--v1-signing-enabled") 110 | args.add(v1SigningEnabled.toString()) 111 | args.add("--v2-signing-enabled") 112 | args.add(v2SigningEnabled.toString()) 113 | args.add("--v3-signing-enabled") 114 | args.add(v3SigningEnabled.toString()) 115 | args.add("--v4-signing-enabled") 116 | args.add(v4SigningEnabled.toString()) 117 | } 118 | return try { 119 | ApkSignerTool.main(args.toTypedArray()) 120 | pk8File.delete() 121 | x509File.delete() 122 | true 123 | } catch (e: Exception) { 124 | e.printStackTrace() 125 | pk8File.delete() 126 | x509File.delete() 127 | false 128 | } 129 | } 130 | 131 | fun validateKeystorePassword(keyFile: File, password: String): Boolean { 132 | return KeyStoreHelper.validateKeystorePassword(keyFile, password) 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /library/src/main/java/com/mcal/apksigner/CertCreator.kt: -------------------------------------------------------------------------------- 1 | package com.mcal.apksigner 2 | 3 | import com.mcal.apksigner.utils.DistinguishedNameValues 4 | import com.mcal.apksigner.utils.KeySet 5 | import com.mcal.apksigner.utils.KeyStoreHelper 6 | import org.spongycastle.x509.X509V3CertificateGenerator 7 | import java.io.File 8 | import java.io.IOException 9 | import java.math.BigInteger 10 | import java.security.KeyPairGenerator 11 | import java.security.SecureRandom 12 | import java.security.cert.Certificate 13 | import java.util.Date 14 | 15 | object CertCreator { 16 | /** 17 | * Creates a new keystore and self-signed key. The key will have the same password as the key, and will be 18 | * RSA 2048, with the cert signed using SHA1withRSA. The certificate will have a validity of 19 | * 30 years). 20 | * 21 | * @param keyFile - new keystore file 22 | * @param password - keystore and key password 23 | * @param keyName - the new key will have this as its alias within the keystore 24 | * @param distinguishedNameValues - contains Country, State, Locality,...,Common Name, etc. 25 | */ 26 | @JvmStatic 27 | fun createKeystoreAndKey( 28 | keyFile: File, 29 | password: CharArray, 30 | keyName: String, 31 | distinguishedNameValues: DistinguishedNameValues 32 | ) { 33 | createKeystoreAndKey( 34 | keyFile, password, "RSA", 2048, keyName, password, 35 | "SHA1withRSA", 30, distinguishedNameValues 36 | ) 37 | } 38 | 39 | @JvmStatic 40 | fun createKeystoreAndKey( 41 | keyFile: File, 42 | storePass: CharArray, 43 | keyAlgorithm: String, 44 | keySize: Int, 45 | keyName: String, 46 | keyPass: CharArray, 47 | certSignatureAlgorithm: String, 48 | certValidityYears: Int, 49 | distinguishedNameValues: DistinguishedNameValues 50 | ): KeySet { 51 | return try { 52 | val keySet = createKey( 53 | keyAlgorithm, keySize, keyName, certSignatureAlgorithm, certValidityYears, 54 | distinguishedNameValues 55 | ) 56 | val privateKS = KeyStoreHelper.createKeyStore(keyFile, storePass) 57 | privateKS.setKeyEntry( 58 | keyName, keySet.privateKey, 59 | keyPass, arrayOf(keySet.publicKey) 60 | ) 61 | if (keyFile.exists()) { 62 | throw IOException("File already exists: $keyFile") 63 | } 64 | KeyStoreHelper.writeKeyStore(privateKS, keyFile, storePass) 65 | keySet 66 | } catch (x: RuntimeException) { 67 | throw x 68 | } catch (x: Exception) { 69 | throw RuntimeException(x.message, x) 70 | } 71 | } 72 | 73 | /** 74 | * Create a new key and store it in an existing keystore. 75 | */ 76 | @JvmStatic 77 | fun createKey( 78 | keyFile: File, 79 | storePass: CharArray, 80 | keyAlgorithm: String, 81 | keySize: Int, 82 | keyName: String, 83 | keyPass: CharArray, 84 | certSignatureAlgorithm: String, 85 | certValidityYears: Int, 86 | distinguishedNameValues: DistinguishedNameValues 87 | ): KeySet { 88 | return try { 89 | val keySet = createKey( 90 | keyAlgorithm, keySize, keyName, certSignatureAlgorithm, certValidityYears, 91 | distinguishedNameValues 92 | ) 93 | val privateKS = KeyStoreHelper.createKeyStore(keyFile, storePass) 94 | privateKS.setKeyEntry( 95 | keyName, keySet.privateKey, 96 | keyPass, arrayOf(keySet.publicKey) 97 | ) 98 | KeyStoreHelper.writeKeyStore(privateKS, keyFile, storePass) 99 | keySet 100 | } catch (x: RuntimeException) { 101 | throw x 102 | } catch (x: Exception) { 103 | throw RuntimeException(x.message, x) 104 | } 105 | } 106 | 107 | @JvmStatic 108 | fun createKey( 109 | keyAlgorithm: String, 110 | keySize: Int, 111 | keyName: String, 112 | certSignatureAlgorithm: String, 113 | certValidityYears: Int, 114 | distinguishedNameValues: DistinguishedNameValues 115 | ): KeySet { 116 | return try { 117 | val keyPairGenerator = KeyPairGenerator.getInstance(keyAlgorithm) 118 | keyPairGenerator.initialize(keySize) 119 | val keyPair = keyPairGenerator.generateKeyPair() 120 | val v3CertGen = X509V3CertificateGenerator() 121 | val principal = distinguishedNameValues.principal 122 | 123 | // generate a postitive serial number 124 | var serialNumber = BigInteger.valueOf(SecureRandom().nextInt().toLong()) 125 | while (serialNumber < BigInteger.ZERO) { 126 | serialNumber = BigInteger.valueOf(SecureRandom().nextInt().toLong()) 127 | } 128 | v3CertGen.setSerialNumber(serialNumber) 129 | v3CertGen.setIssuerDN(principal) 130 | v3CertGen.setNotBefore(Date(System.currentTimeMillis() - 1000L * 60L * 60L * 24L * 30L)) 131 | v3CertGen.setNotAfter(Date(System.currentTimeMillis() + 1000L * 60L * 60L * 24L * 366L * certValidityYears.toLong())) 132 | v3CertGen.setSubjectDN(principal) 133 | v3CertGen.setPublicKey(keyPair.public) 134 | v3CertGen.setSignatureAlgorithm(certSignatureAlgorithm) 135 | val certificate = 136 | v3CertGen.generate(keyPair.private/*, "BC" */) 137 | KeySet().apply { 138 | name = keyName 139 | privateKey = keyPair.private 140 | publicKey = certificate 141 | } 142 | } catch (x: Exception) { 143 | throw RuntimeException(x.message, x) 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /library/src/main/java/com/mcal/apksigner/utils/Base64.kt: -------------------------------------------------------------------------------- 1 | package com.mcal.apksigner.utils 2 | 3 | object Base64 { 4 | @JvmStatic 5 | fun encode(raw: ByteArray): String { 6 | val encoded = StringBuffer() 7 | var i = 0 8 | while (i < raw.size) { 9 | encoded.append(encodeBlock(raw, i)) 10 | i += 3 11 | } 12 | return encoded.toString() 13 | } 14 | 15 | @JvmStatic 16 | fun decode(base64: String): ByteArray { 17 | var pad = 0 18 | var i = base64.length - 1 19 | while (base64[i] == '=') { 20 | pad++ 21 | i-- 22 | } 23 | val length = base64.length * 6 / 8 - pad 24 | val raw = ByteArray(length) 25 | var rawIndex = 0 26 | i = 0 27 | while (i < base64.length) { 28 | val block = ((getValue(base64[i]) shl 18) 29 | + (getValue(base64[i + 1]) shl 12) 30 | + (getValue(base64[i + 2]) shl 6) 31 | + getValue(base64[i + 3])) 32 | var j = 0 33 | while (j < 3 && rawIndex + j < raw.size) { 34 | raw[rawIndex + j] = (block shr 8 * (2 - j) and 0xff).toByte() 35 | j++ 36 | } 37 | rawIndex += 3 38 | i += 4 39 | } 40 | return raw 41 | } 42 | 43 | private fun encodeBlock(raw: ByteArray, offset: Int): CharArray { 44 | var block = 0 45 | val slack = raw.size - offset - 1 46 | val end = if (slack >= 2) { 47 | 2 48 | } else { 49 | slack 50 | } 51 | for (i in 0..end) { 52 | val b = raw[offset + i] 53 | val neuter = if (b < 0) { 54 | b + 256 55 | } else { 56 | b.toInt() 57 | } 58 | block += neuter shl 8 * (2 - i) 59 | } 60 | val base64 = CharArray(4) 61 | for (i in 0..3) { 62 | val sixBit = block ushr 6 * (3 - i) and 0x3f 63 | base64[i] = getChar(sixBit) 64 | } 65 | if (slack < 1) { 66 | base64[2] = '=' 67 | } 68 | if (slack < 2) { 69 | base64[3] = '=' 70 | } 71 | return base64 72 | } 73 | 74 | private fun getChar(sixBit: Int): Char { 75 | if (sixBit in 0..25) { 76 | return ('A'.code + sixBit).toChar() 77 | } 78 | if (sixBit in 26..51) { 79 | return ('a'.code + (sixBit - 26)).toChar() 80 | } 81 | if (sixBit in 52..61) { 82 | return ('0'.code + (sixBit - 52)).toChar() 83 | } 84 | if (sixBit == 62) { 85 | return '+' 86 | } 87 | return if (sixBit == 63) { 88 | '/' 89 | } else { 90 | '?' 91 | } 92 | } 93 | 94 | private fun getValue(c: Char): Int { 95 | if (c in 'A'..'Z') { 96 | return c.code - 'A'.code 97 | } 98 | if (c in 'a'..'z') { 99 | return c.code - 'a'.code + 26 100 | } 101 | if (c in '0'..'9') { 102 | return c.code - '0'.code + 52 103 | } 104 | if (c == '+') { 105 | return 62 106 | } 107 | if (c == '/') { 108 | return 63 109 | } 110 | return if (c == '=') { 111 | 0 112 | } else { 113 | -1 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /library/src/main/java/com/mcal/apksigner/utils/DistinguishedNameValues.kt: -------------------------------------------------------------------------------- 1 | package com.mcal.apksigner.utils 2 | 3 | import org.spongycastle.asn1.ASN1ObjectIdentifier 4 | import org.spongycastle.asn1.x500.style.BCStyle 5 | import org.spongycastle.jce.X509Principal 6 | import java.util.Vector 7 | 8 | /** 9 | * Helper class for dealing with the distinguished name RDNs. 10 | */ 11 | class DistinguishedNameValues : LinkedHashMap() { 12 | init { 13 | put(BCStyle.C, "") 14 | put(BCStyle.ST, "") 15 | put(BCStyle.L, "") 16 | put(BCStyle.STREET, "") 17 | put(BCStyle.O, "") 18 | put(BCStyle.OU, "") 19 | put(BCStyle.CN, "") 20 | } 21 | 22 | override fun put(key: ASN1ObjectIdentifier, value: String): String { 23 | if (containsKey(key)) { 24 | super.put(key, value) // preserve original ordering 25 | } else { 26 | super.put(key, value) 27 | } 28 | return value 29 | } 30 | 31 | fun setCountry(country: String) { 32 | put(BCStyle.C, country) 33 | } 34 | 35 | fun setState(state: String) { 36 | put(BCStyle.ST, state) 37 | } 38 | 39 | fun setLocality(locality: String) { 40 | put(BCStyle.L, locality) 41 | } 42 | 43 | fun setStreet(street: String) { 44 | put(BCStyle.STREET, street) 45 | } 46 | 47 | fun setOrganization(organization: String) { 48 | put(BCStyle.O, organization) 49 | } 50 | 51 | fun setOrganizationalUnit(organizationalUnit: String) { 52 | put(BCStyle.OU, organizationalUnit) 53 | } 54 | 55 | fun setCommonName(commonName: String) { 56 | put(BCStyle.CN, commonName) 57 | } 58 | 59 | val principal: X509Principal 60 | get() { 61 | val identifiers = Vector() 62 | val values = Vector() 63 | for ((key, value) in entries) { 64 | identifiers.add(key) 65 | values.add(value) 66 | } 67 | return X509Principal(identifiers, values) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /library/src/main/java/com/mcal/apksigner/utils/JksKeyStore.kt: -------------------------------------------------------------------------------- 1 | package com.mcal.apksigner.utils 2 | 3 | import java.security.KeyStore 4 | 5 | class JksKeyStore : KeyStore(JKS(), KeyStoreHelper.provider, "JKS") 6 | -------------------------------------------------------------------------------- /library/src/main/java/com/mcal/apksigner/utils/KeySet.java: -------------------------------------------------------------------------------- 1 | package com.mcal.apksigner.utils; 2 | 3 | import java.security.PrivateKey; 4 | import java.security.cert.X509Certificate; 5 | 6 | public class KeySet { 7 | String name; 8 | X509Certificate publicKey = null; 9 | PrivateKey privateKey = null; 10 | byte[] sigBlockTemplate = null; 11 | 12 | String signatureAlgorithm = "SHA1withRSA"; 13 | 14 | public KeySet() { 15 | } 16 | 17 | public KeySet(String name, X509Certificate publicKey, PrivateKey privateKey, byte[] sigBlockTemplate) { 18 | this.name = name; 19 | this.publicKey = publicKey; 20 | this.privateKey = privateKey; 21 | this.sigBlockTemplate = sigBlockTemplate; 22 | } 23 | 24 | public KeySet(String name, X509Certificate publicKey, PrivateKey privateKey, String signatureAlgorithm, byte[] sigBlockTemplate) { 25 | this.name = name; 26 | this.publicKey = publicKey; 27 | this.privateKey = privateKey; 28 | if (signatureAlgorithm != null) { 29 | this.signatureAlgorithm = signatureAlgorithm; 30 | } 31 | this.sigBlockTemplate = sigBlockTemplate; 32 | } 33 | 34 | public String getName() { 35 | return name; 36 | } 37 | 38 | public void setName(String name) { 39 | this.name = name; 40 | } 41 | 42 | public X509Certificate getPublicKey() { 43 | return publicKey; 44 | } 45 | 46 | public void setPublicKey(X509Certificate publicKey) { 47 | this.publicKey = publicKey; 48 | } 49 | 50 | public PrivateKey getPrivateKey() { 51 | return privateKey; 52 | } 53 | 54 | public void setPrivateKey(PrivateKey privateKey) { 55 | this.privateKey = privateKey; 56 | } 57 | 58 | public byte[] getSigBlockTemplate() { 59 | return sigBlockTemplate; 60 | } 61 | 62 | public void setSigBlockTemplate(byte[] sigBlockTemplate) { 63 | this.sigBlockTemplate = sigBlockTemplate; 64 | } 65 | 66 | public String getSignatureAlgorithm() { 67 | return signatureAlgorithm; 68 | } 69 | 70 | public void setSignatureAlgorithm(String signatureAlgorithm) { 71 | if (signatureAlgorithm == null) { 72 | this.signatureAlgorithm = "SHA1withRSA"; 73 | } else { 74 | this.signatureAlgorithm = signatureAlgorithm; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /library/src/main/java/com/mcal/apksigner/utils/KeyStoreHelper.kt: -------------------------------------------------------------------------------- 1 | package com.mcal.apksigner.utils 2 | 3 | import org.spongycastle.jce.provider.BouncyCastleProvider 4 | import java.io.File 5 | import java.io.FileInputStream 6 | import java.io.FileOutputStream 7 | import java.security.KeyStore 8 | import java.security.Security 9 | import java.util.Locale 10 | 11 | object KeyStoreHelper { 12 | val provider by lazy(LazyThreadSafetyMode.NONE) { 13 | BouncyCastleProvider().also { 14 | Security.addProvider(it) 15 | } 16 | } 17 | 18 | @JvmStatic 19 | @Throws(Exception::class) 20 | fun loadJks(jksFile: File?, password: CharArray): KeyStore { 21 | val keyStore: KeyStore 22 | try { 23 | keyStore = JksKeyStore() 24 | keyStore.load(jksFile?.let { FileInputStream(it) }, password) 25 | } catch (e: Exception) { 26 | throw RuntimeException("Failed to load keystore: " + e.message) 27 | } 28 | return keyStore 29 | } 30 | 31 | @JvmStatic 32 | @Throws(Exception::class) 33 | fun loadBks(bksFile: File?, password: CharArray): KeyStore { 34 | val keyStore: KeyStore 35 | try { 36 | keyStore = KeyStore.getInstance("BKS", "BC") 37 | keyStore.load(bksFile?.let { FileInputStream(it) }, password) 38 | } catch (e: Exception) { 39 | throw RuntimeException("Failed to load keystore: " + e.message) 40 | } 41 | return keyStore 42 | } 43 | 44 | @JvmStatic 45 | @Throws(java.lang.Exception::class) 46 | fun loadKeyStore(keystoreFile: File, password: CharArray): KeyStore { 47 | return if (keystoreFile.path.lowercase(Locale.getDefault()).endsWith(".bks")) { 48 | loadBks(keystoreFile, password) 49 | } else { 50 | loadJks(keystoreFile, password) 51 | } 52 | } 53 | 54 | @JvmStatic 55 | @Throws(java.lang.Exception::class) 56 | fun createKeyStore(keystoreFile: File, password: CharArray): KeyStore { 57 | return if (keystoreFile.path.lowercase(Locale.getDefault()).endsWith(".bks")) { 58 | loadBks(null, password) 59 | } else { 60 | loadJks(null, password) 61 | } 62 | } 63 | 64 | @JvmStatic 65 | @Throws(Exception::class) 66 | fun writeKeyStore(ks: KeyStore, keystorePath: File, password: CharArray) { 67 | FileOutputStream(keystorePath).use { fos -> 68 | ks.store(fos, password) 69 | } 70 | } 71 | 72 | @JvmStatic 73 | @Throws(Exception::class) 74 | fun validateKeystorePassword(keystoreFile: File, password: String): Boolean { 75 | return try { 76 | loadKeyStore(keystoreFile, password.toCharArray()) 77 | true 78 | } catch (e: Exception) { 79 | false 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /library/src/main/java/com/mcal/apksigner/utils/LoadKeystoreException.kt: -------------------------------------------------------------------------------- 1 | package com.mcal.apksigner.utils 2 | 3 | import java.io.IOException 4 | 5 | /** 6 | * Thrown by JKS.engineLoad() for errors that occur after determining the keystore is actually a JKS keystore. 7 | */ 8 | class LoadKeystoreException : IOException { 9 | constructor() 10 | constructor(message: String?) : super(message) 11 | constructor(message: String?, cause: Throwable?) : super(message, cause) 12 | constructor(cause: Throwable?) : super(cause) 13 | } 14 | -------------------------------------------------------------------------------- /library/src/main/resources/keystore/testkey.pk8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timscriptov/apksigner/33964952d6d166d93c0dceb6e0a720ba009682f4/library/src/main/resources/keystore/testkey.pk8 -------------------------------------------------------------------------------- /library/src/main/resources/keystore/testkey.x509.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEqDCCA5CgAwIBAgIJAJNurL4H8gHfMA0GCSqGSIb3DQEBBQUAMIGUMQswCQYD 3 | VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4g 4 | VmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UE 5 | AxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe 6 | Fw0wODAyMjkwMTMzNDZaFw0zNTA3MTcwMTMzNDZaMIGUMQswCQYDVQQGEwJVUzET 7 | MBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4G 8 | A1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9p 9 | ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZI 10 | hvcNAQEBBQADggENADCCAQgCggEBANaTGQTexgskse3HYuDZ2CU+Ps1s6x3i/waM 11 | qOi8qM1r03hupwqnbOYOuw+ZNVn/2T53qUPn6D1LZLjk/qLT5lbx4meoG7+yMLV4 12 | wgRDvkxyGLhG9SEVhvA4oU6Jwr44f46+z4/Kw9oe4zDJ6pPQp8PcSvNQIg1QCAcy 13 | 4ICXF+5qBTNZ5qaU7Cyz8oSgpGbIepTYOzEJOmc3Li9kEsBubULxWBjf/gOBzAzU 14 | RNps3cO4JFgZSAGzJWQTT7/emMkod0jb9WdqVA2BVMi7yge54kdVMxHEa5r3b97s 15 | zI5p58ii0I54JiCUP5lyfTwE/nKZHZnfm644oLIXf6MdW2r+6R8CAQOjgfwwgfkw 16 | HQYDVR0OBBYEFEhZAFY9JyxGrhGGBaR0GawJyowRMIHJBgNVHSMEgcEwgb6AFEhZ 17 | AFY9JyxGrhGGBaR0GawJyowRoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UE 18 | CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMH 19 | QW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAG 20 | CSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJAJNurL4H8gHfMAwGA1Ud 21 | EwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAHqvlozrUMRBBVEY0NqrrwFbinZa 22 | J6cVosK0TyIUFf/azgMJWr+kLfcHCHJsIGnlw27drgQAvilFLAhLwn62oX6snb4Y 23 | LCBOsVMR9FXYJLZW2+TcIkCRLXWG/oiVHQGo/rWuWkJgU134NDEFJCJGjDbiLCpe 24 | +ZTWHdcwauTJ9pUbo8EvHRkU3cYfGmLaLfgn9gP+pWA7LFQNvXwBnDa6sppCccEX 25 | 31I828XzgXpJ4O+mDL1/dBd+ek8ZPUP0IgdyZm5MTYPhvVqGCHzzTy3sIeJFymwr 26 | sBbmg2OAUNLEMO6nwmocSdN2ClirfxqCzJOLSDE4QyS9BAH6EhY6UFcOaE0= 27 | -----END CERTIFICATE----- 28 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | gradlePluginPortal() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 10 | repositories { 11 | google() 12 | mavenCentral() 13 | } 14 | } 15 | rootProject.name = "apksigner" 16 | include ':app' 17 | include ':library' --------------------------------------------------------------------------------