| OLD | NEW |
| (Empty) |
| 1 // Copyright 2015 The Go Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style | |
| 3 // license that can be found in the LICENSE file. | |
| 4 | |
| 5 package main | |
| 6 | |
| 7 // APK is the archival format used for Android apps. It is a ZIP archive with | |
| 8 // three extra files: | |
| 9 // | |
| 10 // META-INF/MANIFEST.MF | |
| 11 // META-INF/CERT.SF | |
| 12 // META-INF/CERT.RSA | |
| 13 // | |
| 14 // The MANIFEST.MF comes from the Java JAR archive format. It is a list of | |
| 15 // files included in the archive along with a SHA1 hash, for example: | |
| 16 // | |
| 17 // Name: lib/armeabi/libbasic.so | |
| 18 // SHA1-Digest: ntLSc1eLCS2Tq1oB4Vw6jvkranw= | |
| 19 // | |
| 20 // For debugging, the equivalent SHA1-Digest can be generated with OpenSSL: | |
| 21 // | |
| 22 // cat lib/armeabi/libbasic.so | openssl sha1 -binary | openssl base64 | |
| 23 // | |
| 24 // CERT.SF is a similar manifest. It begins with a SHA1 digest of the entire | |
| 25 // manifest file: | |
| 26 // | |
| 27 // Signature-Version: 1.0 | |
| 28 // Created-By: 1.0 (Android) | |
| 29 // SHA1-Digest-Manifest: aJw+u+10C3Enbg8XRCN6jepluYA= | |
| 30 // | |
| 31 // Then for each entry in the manifest it has a SHA1 digest of the manfiest's | |
| 32 // hash combined with the file name: | |
| 33 // | |
| 34 // Name: lib/armeabi/libbasic.so | |
| 35 // SHA1-Digest: Q7NAS6uzrJr6WjePXSGT+vvmdiw= | |
| 36 // | |
| 37 // This can also be generated with openssl: | |
| 38 // | |
| 39 // echo -en "Name: lib/armeabi/libbasic.so\r\nSHA1-Digest: ntLSc1eLCS2Tq1oB
4Vw6jvkranw=\r\n\r\n" | openssl sha1 -binary | openssl base64 | |
| 40 // | |
| 41 // Note the \r\n line breaks. | |
| 42 // | |
| 43 // CERT.RSA is an RSA signature block made of CERT.SF. Verify it with: | |
| 44 // | |
| 45 // openssl smime -verify -in CERT.RSA -inform DER -content CERT.SF cert.pem | |
| 46 // | |
| 47 // The APK format imposes two extra restrictions on the ZIP format. First, | |
| 48 // it is uncompressed. Second, each contained file is 4-byte aligned. This | |
| 49 // allows the Android OS to mmap contents without unpacking the archive. | |
| 50 | |
| 51 // Note: to make life a little harder, Android Studio stores the RSA key used | |
| 52 // for signing in an Oracle Java proprietary keystore format, JKS. For example, | |
| 53 // the generated debug key is in ~/.android/debug.keystore, and can be | |
| 54 // extracted using the JDK's keytool utility: | |
| 55 // | |
| 56 // keytool -importkeystore -srckeystore ~/.android/debug.keystore -destkeys
tore ~/.android/debug.p12 -deststoretype PKCS12 | |
| 57 // | |
| 58 // Once in standard PKCS12, the key can be converted to PEM for use in the | |
| 59 // Go crypto packages: | |
| 60 // | |
| 61 // openssl pkcs12 -in ~/.android/debug.p12 -nocerts -nodes -out ~/.android/
debug.pem | |
| 62 // | |
| 63 // Fortunately for debug builds, all that matters is that the APK is signed. | |
| 64 // The choice of key is unimportant, so we can generate one for normal builds. | |
| 65 // For production builds, we can ask users to provide a PEM file. | |
| 66 | |
| 67 import ( | |
| 68 "archive/zip" | |
| 69 "bytes" | |
| 70 "crypto/rand" | |
| 71 "crypto/rsa" | |
| 72 "crypto/sha1" | |
| 73 "encoding/base64" | |
| 74 "fmt" | |
| 75 "hash" | |
| 76 "io" | |
| 77 ) | |
| 78 | |
| 79 // NewWriter returns a new Writer writing an APK file to w. | |
| 80 // The APK will be signed with key. | |
| 81 func NewWriter(w io.Writer, priv *rsa.PrivateKey) *Writer { | |
| 82 apkw := &Writer{priv: priv} | |
| 83 apkw.w = zip.NewWriter(&countWriter{apkw: apkw, w: w}) | |
| 84 return apkw | |
| 85 } | |
| 86 | |
| 87 // Writer implements an APK file writer. | |
| 88 type Writer struct { | |
| 89 offset int | |
| 90 w *zip.Writer | |
| 91 priv *rsa.PrivateKey | |
| 92 manifest []manifestEntry | |
| 93 cur *fileWriter | |
| 94 } | |
| 95 | |
| 96 // Create adds a file to the APK archive using the provided name. | |
| 97 // | |
| 98 // The name must be a relative path. The file's contents must be written to | |
| 99 // the returned io.Writer before the next call to Create or Close. | |
| 100 func (w *Writer) Create(name string) (io.Writer, error) { | |
| 101 if err := w.clearCur(); err != nil { | |
| 102 return nil, fmt.Errorf("apk: Create(%s): %v", name, err) | |
| 103 } | |
| 104 if name == "AndroidManifest.xml" { | |
| 105 w.cur = &fileWriter{ | |
| 106 name: name, | |
| 107 w: new(bytes.Buffer), | |
| 108 sha1: sha1.New(), | |
| 109 } | |
| 110 return w.cur, nil | |
| 111 } | |
| 112 res, err := w.create(name) | |
| 113 if err != nil { | |
| 114 return nil, fmt.Errorf("apk: Create(%s): %v", name, err) | |
| 115 } | |
| 116 return res, nil | |
| 117 } | |
| 118 | |
| 119 func (w *Writer) create(name string) (io.Writer, error) { | |
| 120 // Align start of file contents by using Extra as padding. | |
| 121 if err := w.w.Flush(); err != nil { // for exact offset | |
| 122 return nil, err | |
| 123 } | |
| 124 const fileHeaderLen = 30 // + filename + extra | |
| 125 start := w.offset + fileHeaderLen + len(name) | |
| 126 extra := start % 4 | |
| 127 | |
| 128 zipfw, err := w.w.CreateHeader(&zip.FileHeader{ | |
| 129 Name: name, | |
| 130 Extra: make([]byte, extra), | |
| 131 }) | |
| 132 if err != nil { | |
| 133 return nil, err | |
| 134 } | |
| 135 w.cur = &fileWriter{ | |
| 136 name: name, | |
| 137 w: zipfw, | |
| 138 sha1: sha1.New(), | |
| 139 } | |
| 140 return w.cur, nil | |
| 141 } | |
| 142 | |
| 143 // Close finishes writing the APK. This includes writing the manifest and | |
| 144 // signing the archive, and writing the ZIP central directory. | |
| 145 // | |
| 146 // It does not close the underlying writer. | |
| 147 func (w *Writer) Close() error { | |
| 148 if err := w.clearCur(); err != nil { | |
| 149 return fmt.Errorf("apk: %v", err) | |
| 150 } | |
| 151 | |
| 152 manifest := new(bytes.Buffer) | |
| 153 fmt.Fprint(manifest, manifestHeader) | |
| 154 certBody := new(bytes.Buffer) | |
| 155 | |
| 156 for _, entry := range w.manifest { | |
| 157 n := entry.name | |
| 158 h := base64.StdEncoding.EncodeToString(entry.sha1.Sum(nil)) | |
| 159 fmt.Fprintf(manifest, "Name: %s\nSHA1-Digest: %s\n\n", n, h) | |
| 160 cHash := sha1.New() | |
| 161 fmt.Fprintf(cHash, "Name: %s\r\nSHA1-Digest: %s\r\n\r\n", n, h) | |
| 162 ch := base64.StdEncoding.EncodeToString(cHash.Sum(nil)) | |
| 163 fmt.Fprintf(certBody, "Name: %s\nSHA1-Digest: %s\n\n", n, ch) | |
| 164 } | |
| 165 | |
| 166 mHash := sha1.New() | |
| 167 mHash.Write(manifest.Bytes()) | |
| 168 cert := new(bytes.Buffer) | |
| 169 fmt.Fprint(cert, certHeader) | |
| 170 fmt.Fprintf(cert, "SHA1-Digest-Manifest: %s\n\n", base64.StdEncoding.Enc
odeToString(mHash.Sum(nil))) | |
| 171 cert.Write(certBody.Bytes()) | |
| 172 | |
| 173 mw, err := w.Create("META-INF/MANIFEST.MF") | |
| 174 if err != nil { | |
| 175 return err | |
| 176 } | |
| 177 if _, err := mw.Write(manifest.Bytes()); err != nil { | |
| 178 return err | |
| 179 } | |
| 180 | |
| 181 cw, err := w.Create("META-INF/CERT.SF") | |
| 182 if err != nil { | |
| 183 return err | |
| 184 } | |
| 185 if _, err := cw.Write(cert.Bytes()); err != nil { | |
| 186 return err | |
| 187 } | |
| 188 | |
| 189 rsa, err := signPKCS7(rand.Reader, w.priv, cert.Bytes()) | |
| 190 if err != nil { | |
| 191 return fmt.Errorf("apk: %v", err) | |
| 192 } | |
| 193 rw, err := w.Create("META-INF/CERT.RSA") | |
| 194 if err != nil { | |
| 195 return err | |
| 196 } | |
| 197 if _, err := rw.Write(rsa); err != nil { | |
| 198 return err | |
| 199 } | |
| 200 | |
| 201 return w.w.Close() | |
| 202 } | |
| 203 | |
| 204 const manifestHeader = `Manifest-Version: 1.0 | |
| 205 Created-By: 1.0 (Go) | |
| 206 | |
| 207 ` | |
| 208 | |
| 209 const certHeader = `Signature-Version: 1.0 | |
| 210 Created-By: 1.0 (Go) | |
| 211 ` | |
| 212 | |
| 213 func (w *Writer) clearCur() error { | |
| 214 if w.cur == nil { | |
| 215 return nil | |
| 216 } | |
| 217 if w.cur.name == "AndroidManifest.xml" { | |
| 218 buf := w.cur.w.(*bytes.Buffer) | |
| 219 b, err := binaryXML(buf) | |
| 220 if err != nil { | |
| 221 return err | |
| 222 } | |
| 223 f, err := w.create("AndroidManifest.xml") | |
| 224 if err != nil { | |
| 225 return err | |
| 226 } | |
| 227 if _, err := f.Write(b); err != nil { | |
| 228 return err | |
| 229 } | |
| 230 } | |
| 231 w.manifest = append(w.manifest, manifestEntry{ | |
| 232 name: w.cur.name, | |
| 233 sha1: w.cur.sha1, | |
| 234 }) | |
| 235 w.cur.closed = true | |
| 236 w.cur = nil | |
| 237 return nil | |
| 238 } | |
| 239 | |
| 240 type manifestEntry struct { | |
| 241 name string | |
| 242 sha1 hash.Hash | |
| 243 } | |
| 244 | |
| 245 type countWriter struct { | |
| 246 apkw *Writer | |
| 247 w io.Writer | |
| 248 } | |
| 249 | |
| 250 func (c *countWriter) Write(p []byte) (n int, err error) { | |
| 251 n, err = c.w.Write(p) | |
| 252 c.apkw.offset += n | |
| 253 return n, err | |
| 254 } | |
| 255 | |
| 256 type fileWriter struct { | |
| 257 name string | |
| 258 w io.Writer | |
| 259 sha1 hash.Hash | |
| 260 closed bool | |
| 261 } | |
| 262 | |
| 263 func (w *fileWriter) Write(p []byte) (n int, err error) { | |
| 264 if w.closed { | |
| 265 return 0, fmt.Errorf("apk: write to closed file %q", w.name) | |
| 266 } | |
| 267 w.sha1.Write(p) | |
| 268 n, err = w.w.Write(p) | |
| 269 if err != nil { | |
| 270 err = fmt.Errorf("apk: %v", err) | |
| 271 } | |
| 272 return n, err | |
| 273 } | |
| OLD | NEW |