From cc328c9d91262d2cf48b1eaa309bcaa1b25641da Mon Sep 17 00:00:00 2001 From: Hal Wine Date: Fri, 29 Dec 2023 15:29:48 -0800 Subject: [PATCH] Preserve changes backed out from main The changes in 7bc7ab2ce78848cd6a69dc4753990a6d89e02eac were backed out in order to ensure the production branch is deployable from the head. (These changes had been landed for testing, and deployed for a while in stage, but never validated. Hence we were tagging the prior revision for production deploys.) The other files in the original commit were improvements to the documentation and development processes. Since these did not impact the production code, they were left in. --- handlers_racing_test.go | 24 +++++++++++++++++++++++- signer/apk2/apk2.go | 30 ++++++++++++++++++++++++++++-- 2 files changed, 51 insertions(+), 3 deletions(-) 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 { }