Search code examples
androidcertificateself-signedmirrorlink

MirrorLink-aware app validator fails after signing APK with Google developer key


I'm working on MirrorLink-aware Android application and faced issues with Mirrorlink's check of 'application ID' after signing APK with Google developer key.

I have created empty project generated with Mirrorlink plugin for Android studio, which created Gradle task allowing to generate .crt file with Mirrorlink-related configuration info. I just copied this script to my own build.gradle file in the main app module. But Mirrorlink-aware application validator show error for signed APK (standard app signing for publishing to Google Play), while find no problems if APK is unsigned.

How to find out the issue arisen after the app was signed?

What I tried: 1) Align Gradle script with example app provided by Mirrorlink - no luck.

2) Inserting v1SigningEnabled false and v2SigningEnabled false - no luck.

3) Gradle outputs for signed- and unsigned APKs are the same, except of output file name and > Task :app:validateSigningRelease task.

AndroidManifest.xml Mirrorlink-related snipped:

<uses-permission android:name="com.mirrorlink.android.service.ACCESS_PERMISSION"/>

<intent-filter>
                <action android:name="com.mirrorlink.android.app.LAUNCH"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>

            <intent-filter>
                <action android:name="com.mirrorlink.android.app.TERMINATE"/>
                <category android:name="android.intent.category.DEFAULT"/>

App module's build.gradle:

def generateSelfSignedCertificate(version){
    def buildType = version == "release" ? "assembleRelease": "assembleDebug"
    def rerunAssemble = true
    def projectLocation = projectDir.toString()
    def certGeneratorLocation = "certificategenerator-android-studio.jar"
    def certXmlLocation = projectLocation + "/certificate.xml"
    def certificateDestination = projectLocation + "/src/main/assets/self-signed.ccc.crt"
    def certificateFolder = projectLocation + "/src/main/assets/"
    def certificateIssuer = "CN=SELF-SIGNED"
    def developerId = ""
    def apkLocation = ""
    android.applicationVariants.all { variant ->
        if ((variant.name).equals(version)) {
            variant.outputs.each { output ->
                //noinspection GrReassignedInClosureLocalVar
                apkLocation = output.outputFile
            }
        }
    }

    if (project.hasProperty("isLast")) {
        rerunAssemble = !isLast
    }


    if (rerunAssemble) {
        def subdir = new File(certificateFolder)
        if( !subdir.exists() ) {
            subdir.mkdirs()
        }
        exec {
            executable 'java'
            args "-jar", certGeneratorLocation, "generate-certificate", apkLocation,
                    android.defaultConfig.applicationId, android.defaultConfig.versionCode,
                    certXmlLocation, certificateDestination, certificateIssuer, developerId
            println(args)
        }
        if (System.properties['os.name'].toLowerCase().contains("windows")) {
            exec {
                executable "cmd"
                workingDir projectLocation
                args  "/c", "..\\gradlew.bat", buildType, "-PisLast=true"
            }
        } else {
            exec {
                executable "bash"
                workingDir projectLocation
                args "../gradlew", buildType, "-PisLast=true"
            }
        }
    }
}

android {
.....

    signingConfigs {
        storeFile = 'sign.keyStorePath')
            keyAlias = 'sign.keyAlias'
            storePassword = 'sign.storePassword'
            keyPassword = 'sign.keyPassword'
        }
    }

    afterEvaluate {
        if (this.hasProperty("assembleRelease")){
            assembleRelease.finalizedBy generateSelfSignedCertificateForRelase
        }
    }

    task generateSelfSignedCertificateForRelase {
        doLast {
            generateSelfSignedCertificate("release")
        }
    }

    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.release
        }
    }
}

....
}

Running "assesbleRelease" runs with no problems - it produces .crt file with Mirrorlink application ID. But if I put this file in "Mirrorlink-aware application validator" - it shows error report, as follows:

Checking com.mirrorlink.android.app.LAUNCH intent - OK
Checking com.mirrorlink.android.app.TERMINATE intent - OK
Checking com.mirrorlink.android.service.ACCESS_PERMISSION permission - OK
Checking DEVELOPER entity - INCONCLUSIVE. DEVELOPER entity not found
Checking if developer certificate exists for serverID="" - INCONCLUSIVE. DEVELOPER entity not found
Checking application id - FAIL. Calculated application ID doesn't mach id provided in self signed certificate
Checking self signed certificate - OK


The APK is not configured correctly.

BUT if I remove the string signingConfig signingConfigs.release the validator finds no problems:

Checking com.mirrorlink.android.app.LAUNCH intent - OK
Checking com.mirrorlink.android.app.TERMINATE intent - OK
Checking com.mirrorlink.android.service.ACCESS_PERMISSION permission - OK
Checking DEVELOPER entity - INCONCLUSIVE. DEVELOPER entity not found
Checking if developer certificate exists for serverID="" - INCONCLUSIVE. DEVELOPER entity not found
Checking application id - OK
Checking self signed certificate - OK


The APK is correctly configured as a MirrorLink Aware application.

Solution

  • Well, guys, I found the issue :) I have Crashlytics in the project, and it creates file crashlytics-build.properties that is slightly changed on every build.

    enter image description here

    And yeah, this file is counted by Mirrorlink's tool certificategenerator-android-studio.jar that generates appID with certificate. I have done some reverse-engineering and checked what is happening inside of this tool: it only skips assets/self-signed.ccc.crt file and META-INF/ content.

    So, I had slightly different APKs on EVERY assemble... Disabling Crashlytics plugin solves the issue. You can either disable Crashlytics plugin (bad idea), or use ext.alwaysUpdateBuildId = false param to skip creating unique build ID. Read more here.