diff --git a/handlers_racing_test.go b/handlers_racing_test.go index ecf135818..1e3cceed4 100644 --- a/handlers_racing_test.go +++ b/handlers_racing_test.go @@ -78,7 +78,7 @@ func TestSignaturePass(t *testing.T) { }, }, { - // Sign an APK file + // Sign an APK file that has no min-sdk-version so we have to manually force it "/sign/file", []formats.SignatureRequest{ formats.SignatureRequest{ @@ -87,6 +87,28 @@ func TestSignaturePass(t *testing.T) { }, }, }, + { + // Sign an APK file + /* + How to create a small-enough APK: + 1. Create a new Android project with Android Studio, the official Android IDE + 2. Initialize a git repository, in case you make a mistake with one of the steps above + 3. Delete all files that seem unnecessary + 4. Strip out unnecessary metadata in AndroidManifest.xml + 5. Add to AndroidManifest.xml. 21 matches Android 5.0: https://apilevels.com/ + 6. Remove all dependencies in build.gradle + 7. `./gradlew clean assemble && ls -lh app/build/outputs/apk/release/app-release-unsigned.apk` says the generated APK is 1.2 kB. + 8. `unzip -l app-release-unsigned.apk` to show all files contained in the APK. Only AndroidManifest.xml is needed. Purge all others with `zip -d app-release-unsigned.apk $file` + 9. base64 the APK and paste the content in this file. + */ + "/sign/file", + []formats.SignatureRequest{ + formats.SignatureRequest{ + Input: "UEsDBAAAAAAIACEIIQJ3XNdl0gIAAMwHAAATAAAAQW5kcm9pZE1hbmlmZXN0LnhtbJ2VTU9TQRSGz/QCrZYiFMq3RhMTjQlFkYVxYYJEokllodGFKyoFIbTQ9F5QEmNYu3bpwoV7/QX+AhcuWPhr1GfOndtOr238uM17Z+adc945c+bcaSA5+ZoVMTIvrwdELkjneef1R8AsKIMK2AYn4CP4Ar6DH2DZiNwBa2AbHIG34D04BdMZkSWwBt6Ab+BqwBg8A3XwAZyCQalLVZ7LFq3IgOwzajASYt6SV9KUA2lJRL8GV2BuF5vHjPbkKWxLQpgDOJG8HHUxq7xrqpWeWW+vMop2lZkXjKK+ulWiqzN6KXfpb2JzSGR2JtReEmUoj3jbnYxhdcAaTVTq6PVWnvkLq2QXncwEcoMzuk4vQ++mntxDOZYVjaSO3ya2kfNfYn5Io47a6+ba4yMQ4SuShdtnpRY2u5rt2RRT1txHGkvEyNcsE8GKPCCzIhf/4BdHt0XObdaOYSr4PsF3Ve7LPXIY57zXbmzsaX8baZzHstZMVTNq81mmd9xHyeblyn/52b1WdU8rv2XxmuzQi/C7LYv8Qix2UGzgEWrOujMTr7+oa+3RtrANddx9FoWuHC7wbdqKibRSbE4a6rELb/0jPc+mq9WqVjffpe7G7mRbM9egmg9Vp9an5v7FZ937cg9hbRwLwFY094jJybLYO8iYAIyC4Ywxc2AeNMEJ+BQY0xo0JgSC1bRWuchPnlHaKTuG/+zx9pmhP8Ev6+6yKb1N4vkcn7lx3KDWlo1Rgrzjznh2lxx3Fgw7u2E3b+/GkuNKjptL+dr+vMcVXLwVPZFOvOddvBkv3sDzKzluKKUfuJyktZI18h4/3mONbEevaNupeD9dnNXacP8HidY5pxV4WqJ1Geej4Dib50nHTSbn1UP/MlzR0592+sljbW7Rjns2I87GeDEYvWvj9ca8/KT9Er2Sx0/00Ss6vaKnl/ZL+PQeEj6du4RPn0+QqvGklk2f2v8FUEsBAgAAAAAAAAgAIQghAndc12XSAgAAzAcAABMAAAAAAAAAAAAAAAAAAAAAAEFuZHJvaWRNYW5pZmVzdC54bWxQSwUGAAAAAAEAAQBBAAAAAwMAAAAA", + KeyID: "testapp-android", + }, + }, + }, { // Sign MAR data "/sign/data", diff --git a/signer/apk2/apk2.go b/signer/apk2/apk2.go index 0baa3e538..1768937a9 100644 --- a/signer/apk2/apk2.go +++ b/signer/apk2/apk2.go @@ -158,14 +158,27 @@ func (s *APK2Signer) SignFile(file []byte, options interface{}) (signer.SignedFi args = append(args, "--key", keyPath.Name(), "--cert", certPath.Name(), - "--min-sdk-version", s.minSdkVersion, tmpAPKFile.Name(), ) apkSigCmd := exec.Command("java", args...) out, err := apkSigCmd.CombinedOutput() if err != nil { - return nil, fmt.Errorf("apk2: failed to sign\n%s: %w", out, err) + if !bytes.Contains(out, []byte("com.android.apksig.apk.MinSdkVersionException")) { + return nil, fmt.Errorf("apk2: failed to sign\n%s: %w", out, err) + } else { + log.Printf("apk2: APK does not provide minSdkVersion. Attempting to sign again with: --min-sdk-version %s", s.minSdkVersion) + + args = insertIntoSliceAtIndex(args, "--min-sdk-version", len(args)-1) + args = insertIntoSliceAtIndex(args, s.minSdkVersion, len(args)-1) + + apkSigCmd = exec.Command("java", args...) + out, err = apkSigCmd.CombinedOutput() + + if err != nil { + return nil, fmt.Errorf("apk2: failed to sign even when forcing --min-sdk-version\n%s: %w", out, err) + } + } } log.Debugf("signed as:\n%s\n", string(out)) @@ -176,6 +189,19 @@ func (s *APK2Signer) SignFile(file []byte, options interface{}) (signer.SignedFi return signer.SignedFile(signedApk), nil } +// go 1.16 doesn't support generics. +// TODO: Replace string by T in function signature once we use go 1.18+ +func insertIntoSliceAtIndex(destination []string, element string, index int) []string { + if len(destination) == index { + return append(destination, element) + } + + destination = append(destination[:index+1], destination[index:]...) // index < len(a) + destination[index] = element + + return destination +} + // Options are not implemented for this signer type Options struct { }