github.com/adwpc/xmobile@v0.0.0-20231212131043-3f9720cf0e99/cmd/gomobile/writer.go (about)

     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: ntLSc1eLCS2Tq1oB4Vw6jvkranw=\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 -destkeystore ~/.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  	res, err := w.create(name)
   105  	if err != nil {
   106  		return nil, fmt.Errorf("apk: Create(%s): %v", name, err)
   107  	}
   108  	return res, nil
   109  }
   110  
   111  func (w *Writer) create(name string) (io.Writer, error) {
   112  	// Align start of file contents by using Extra as padding.
   113  	if err := w.w.Flush(); err != nil { // for exact offset
   114  		return nil, err
   115  	}
   116  	const fileHeaderLen = 30 // + filename + extra
   117  	start := w.offset + fileHeaderLen + len(name)
   118  	extra := (-start) & 3
   119  
   120  	zipfw, err := w.w.CreateHeader(&zip.FileHeader{
   121  		Name:  name,
   122  		Extra: make([]byte, extra),
   123  	})
   124  	if err != nil {
   125  		return nil, err
   126  	}
   127  	w.cur = &fileWriter{
   128  		name: name,
   129  		w:    zipfw,
   130  		sha1: sha1.New(),
   131  	}
   132  	return w.cur, nil
   133  }
   134  
   135  // Close finishes writing the APK. This includes writing the manifest and
   136  // signing the archive, and writing the ZIP central directory.
   137  //
   138  // It does not close the underlying writer.
   139  func (w *Writer) Close() error {
   140  	if err := w.clearCur(); err != nil {
   141  		return fmt.Errorf("apk: %v", err)
   142  	}
   143  
   144  	hasDex := false
   145  	for _, entry := range w.manifest {
   146  		if entry.name == "classes.dex" {
   147  			hasDex = true
   148  			break
   149  		}
   150  	}
   151  
   152  	manifest := new(bytes.Buffer)
   153  	if hasDex {
   154  		fmt.Fprint(manifest, manifestDexHeader)
   155  	} else {
   156  		fmt.Fprint(manifest, manifestHeader)
   157  	}
   158  	certBody := new(bytes.Buffer)
   159  
   160  	for _, entry := range w.manifest {
   161  		n := entry.name
   162  		h := base64.StdEncoding.EncodeToString(entry.sha1.Sum(nil))
   163  		fmt.Fprintf(manifest, "Name: %s\nSHA1-Digest: %s\n\n", n, h)
   164  		cHash := sha1.New()
   165  		fmt.Fprintf(cHash, "Name: %s\r\nSHA1-Digest: %s\r\n\r\n", n, h)
   166  		ch := base64.StdEncoding.EncodeToString(cHash.Sum(nil))
   167  		fmt.Fprintf(certBody, "Name: %s\nSHA1-Digest: %s\n\n", n, ch)
   168  	}
   169  
   170  	mHash := sha1.New()
   171  	mHash.Write(manifest.Bytes())
   172  	cert := new(bytes.Buffer)
   173  	fmt.Fprint(cert, certHeader)
   174  	fmt.Fprintf(cert, "SHA1-Digest-Manifest: %s\n\n", base64.StdEncoding.EncodeToString(mHash.Sum(nil)))
   175  	cert.Write(certBody.Bytes())
   176  
   177  	mw, err := w.Create("META-INF/MANIFEST.MF")
   178  	if err != nil {
   179  		return err
   180  	}
   181  	if _, err := mw.Write(manifest.Bytes()); err != nil {
   182  		return err
   183  	}
   184  
   185  	cw, err := w.Create("META-INF/CERT.SF")
   186  	if err != nil {
   187  		return err
   188  	}
   189  	if _, err := cw.Write(cert.Bytes()); err != nil {
   190  		return err
   191  	}
   192  
   193  	rsa, err := signPKCS7(rand.Reader, w.priv, cert.Bytes())
   194  	if err != nil {
   195  		return fmt.Errorf("apk: %v", err)
   196  	}
   197  	rw, err := w.Create("META-INF/CERT.RSA")
   198  	if err != nil {
   199  		return err
   200  	}
   201  	if _, err := rw.Write(rsa); err != nil {
   202  		return err
   203  	}
   204  
   205  	return w.w.Close()
   206  }
   207  
   208  const manifestHeader = `Manifest-Version: 1.0
   209  Created-By: 1.0 (Go)
   210  
   211  `
   212  
   213  const manifestDexHeader = `Manifest-Version: 1.0
   214  Dex-Location: classes.dex
   215  Created-By: 1.0 (Go)
   216  
   217  `
   218  
   219  const certHeader = `Signature-Version: 1.0
   220  Created-By: 1.0 (Go)
   221  `
   222  
   223  func (w *Writer) clearCur() error {
   224  	if w.cur == nil {
   225  		return nil
   226  	}
   227  	w.manifest = append(w.manifest, manifestEntry{
   228  		name: w.cur.name,
   229  		sha1: w.cur.sha1,
   230  	})
   231  	w.cur.closed = true
   232  	w.cur = nil
   233  	return nil
   234  }
   235  
   236  type manifestEntry struct {
   237  	name string
   238  	sha1 hash.Hash
   239  }
   240  
   241  type countWriter struct {
   242  	apkw *Writer
   243  	w    io.Writer
   244  }
   245  
   246  func (c *countWriter) Write(p []byte) (n int, err error) {
   247  	n, err = c.w.Write(p)
   248  	c.apkw.offset += n
   249  	return n, err
   250  }
   251  
   252  type fileWriter struct {
   253  	name   string
   254  	w      io.Writer
   255  	sha1   hash.Hash
   256  	closed bool
   257  }
   258  
   259  func (w *fileWriter) Write(p []byte) (n int, err error) {
   260  	if w.closed {
   261  		return 0, fmt.Errorf("apk: write to closed file %q", w.name)
   262  	}
   263  	w.sha1.Write(p)
   264  	n, err = w.w.Write(p)
   265  	if err != nil {
   266  		err = fmt.Errorf("apk: %v", err)
   267  	}
   268  	return n, err
   269  }