├── testkey.pk8
├── tests
└── assets
│ └── tiny.apk
├── .gitignore
├── sign.bat
├── license
├── io_utils_notice
│ └── NOTICE.txt
└── LICENSE-2.0.txt
├── test.sh
├── testkey.x509.pem
├── .travis.yml
├── README.md
├── src
├── s
│ ├── IOUtils.java
│ └── Sign.java
└── orig
│ └── SignApk.java
└── pom.xml
/testkey.pk8:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/appium-boneyard/sign/HEAD/testkey.pk8
--------------------------------------------------------------------------------
/tests/assets/tiny.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/appium-boneyard/sign/HEAD/tests/assets/tiny.apk
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | bin/
2 | target/
3 | .settings
4 | .project
5 | .classpath
6 | *.log
7 | .idea/
8 | *.iml
9 |
--------------------------------------------------------------------------------
/sign.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | if "%PATH_BASE%" == "" set PATH_BASE=%PATH%
3 | set PATH=%CD%;%PATH_BASE%;
4 | chcp 65001 2>nul >nul
5 | java -jar -Duser.language=en -Dfile.encoding=UTF8 "%~dp0\sign.jar" %*
--------------------------------------------------------------------------------
/license/io_utils_notice/NOTICE.txt:
--------------------------------------------------------------------------------
1 | Apache Commons IO
2 | Copyright 2002-2012 The Apache Software Foundation
3 |
4 | This product includes software developed by
5 | The Apache Software Foundation (http://www.apache.org/).
6 |
7 |
--------------------------------------------------------------------------------
/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -x
4 |
5 | tmpfile=$(mktemp /tmp/out.XXXXXX)
6 | jarsigner -verify -verbose "./tests/assets/tiny.apk" > "$tmpfile"
7 | if grep -q 'jar verified' "$tmpfile"; then
8 | exit 1
9 | fi
10 | java -jar ./target/sign*.jar "./tests/assets/tiny.apk"
11 | jarsigner -verify -verbose "./tests/assets/tiny.s.apk" > "$tmpfile"
12 | if ! grep -q 'jar verified' "$tmpfile"; then
13 | exit 1
14 | fi
15 |
--------------------------------------------------------------------------------
/testkey.x509.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIEqDCCA5CgAwIBAgIJAJNurL4H8gHfMA0GCSqGSIb3DQEBBQUAMIGUMQswCQYD
3 | VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4g
4 | VmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UE
5 | AxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe
6 | Fw0wODAyMjkwMTMzNDZaFw0zNTA3MTcwMTMzNDZaMIGUMQswCQYDVQQGEwJVUzET
7 | MBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4G
8 | A1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9p
9 | ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZI
10 | hvcNAQEBBQADggENADCCAQgCggEBANaTGQTexgskse3HYuDZ2CU+Ps1s6x3i/waM
11 | qOi8qM1r03hupwqnbOYOuw+ZNVn/2T53qUPn6D1LZLjk/qLT5lbx4meoG7+yMLV4
12 | wgRDvkxyGLhG9SEVhvA4oU6Jwr44f46+z4/Kw9oe4zDJ6pPQp8PcSvNQIg1QCAcy
13 | 4ICXF+5qBTNZ5qaU7Cyz8oSgpGbIepTYOzEJOmc3Li9kEsBubULxWBjf/gOBzAzU
14 | RNps3cO4JFgZSAGzJWQTT7/emMkod0jb9WdqVA2BVMi7yge54kdVMxHEa5r3b97s
15 | zI5p58ii0I54JiCUP5lyfTwE/nKZHZnfm644oLIXf6MdW2r+6R8CAQOjgfwwgfkw
16 | HQYDVR0OBBYEFEhZAFY9JyxGrhGGBaR0GawJyowRMIHJBgNVHSMEgcEwgb6AFEhZ
17 | AFY9JyxGrhGGBaR0GawJyowRoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UE
18 | CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMH
19 | QW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAG
20 | CSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJAJNurL4H8gHfMAwGA1Ud
21 | EwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAHqvlozrUMRBBVEY0NqrrwFbinZa
22 | J6cVosK0TyIUFf/azgMJWr+kLfcHCHJsIGnlw27drgQAvilFLAhLwn62oX6snb4Y
23 | LCBOsVMR9FXYJLZW2+TcIkCRLXWG/oiVHQGo/rWuWkJgU134NDEFJCJGjDbiLCpe
24 | +ZTWHdcwauTJ9pUbo8EvHRkU3cYfGmLaLfgn9gP+pWA7LFQNvXwBnDa6sppCccEX
25 | 31I828XzgXpJ4O+mDL1/dBd+ek8ZPUP0IgdyZm5MTYPhvVqGCHzzTy3sIeJFymwr
26 | sBbmg2OAUNLEMO6nwmocSdN2ClirfxqCzJOLSDE4QyS9BAH6EhY6UFcOaE0=
27 | -----END CERTIFICATE-----
28 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 | sudo: false
3 | jdk: openjdk8
4 |
5 | jobs:
6 | include:
7 | - stage:
8 | name: JDK 8
9 | before_script:
10 | - |
11 | if [[ -n "$TRAVIS_TAG" ]]; then
12 | ./build.sh "$TRAVIS_TAG"
13 | else
14 | ./build.sh
15 | fi
16 | script: "./test.sh"
17 | - stage:
18 | name: JDK 9
19 | jdk: openjdk9
20 | before_script: "./build.sh"
21 | script: "./test.sh"
22 | - stage:
23 | name: JDK 10
24 | jdk: openjdk10
25 | before_script: "./build.sh"
26 | script: "./test.sh"
27 | - stage:
28 | name: JDK 11
29 | jdk: openjdk11
30 | before_script: "./build.sh"
31 | script: "./test.sh"
32 | - stage:
33 | name: JDK 12
34 | jdk: openjdk12
35 | before_script: "./build.sh"
36 | script: "./test.sh"
37 | deploy:
38 | provider: releases
39 | api_key:
40 | secure: S389qkxYxHbqlv2/KOLzhwSuz36m35cY7yafHRd2y2bsY4nwJ+jaqzggBLoSY6bN+UOxxaZOgw9GO5TvZUMGcSC8xt1f7DnKeAYsHfvAd8mh7hXQBc9B7XK6OEcWatsLpnuyNoGN8bssDTY1xiJVsGyyz6XozqhnKGzfZFiPEp+eTIKzUT1YpRjOfXFZ152u42GugANX4G9TgVipz0jp33CJrrYgAEHOsx4In3Ib1i+Tmpg5XnCsMsBDI/75eegGFl/d34sV1jN3rsijGVMzHCwyL89uw7Duo1Bt3E/NwehayXBiiK3DWP7u276kDY8+XVUFIcKnH8FyGawrYh6IJfkQUzTARrmXpzHDjFGAQqRnqRR/DYMYpEtfZp6Uuidjmbr4P8XsoGlTEMGiAc+6obqeju0yCtx/WewBTx6pMjrGIX50fpGt+yMc4LB4jsvlMAAl370xYeTiaRakIrK2OzAi4UdbS99NHXQvlDrFXVGJCy4RANrbA5kVwhTBwF25QbE12W3+8tCMOxft+gcWz/M3abhEusiL9J3+W9pYyg1X3PKr7sSrGhwd7hrM7ZPglrysdEs+oOJKfxeQbFLdJ+WwLJdag05CHlPzfFUkdIL3LWFvtdbjbFWB3mi+Y2qIcBg4s2fPdoAX+rOpfCBpXCoNvwCo67oz2vNVZE2GfRU=
41 | file_glob: true
42 | file: target/*.jar
43 | skip_cleanup: true
44 | on:
45 | jdk: openjdk8
46 | tags: true
47 | repo: appium/sign
48 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Apk Sign
2 |
3 | Automatically sign an apk with the Android test certificate. The public and private keys are embedded within the jar.
4 |
5 |
6 | ### Development
7 |
8 | Building:
9 |
10 | ```bash
11 | ./build.sh
12 | ```
13 |
14 | Testing:
15 |
16 | ```bash
17 | ./test.sh
18 | ```
19 |
20 |
21 | ### Usage
22 |
23 | - `sign my.apk`
24 |
25 | `my.s.apk` is created next to `my.apk`
26 |
27 | - `sign my.apk --override`
28 |
29 | `my.apk` is replaced with a signed version
30 |
31 | Verify signature.
32 |
33 | `jarsigner -verify my.s.apk`
34 |
35 |
36 | ### Release
37 |
38 | New releases are published to GitHub automatically by CI agent.
39 | It is only necessary to push a new version tag to `master`:
40 |
41 | ```bash
42 | git tag -a 1.0 -m 1.0
43 | git push --tags origin master
44 | git push --tags remote master
45 | ```
46 |
47 |
48 | ### License
49 |
50 | Released under the Apache 2.0 License (the same as Android's [SignApk.java](https://github.com/android/platform_build/blob/master/tools/signapk/SignApk.java)).
51 |
52 |
53 | ### Based on the following AOSP 4.1.1 files & sources
54 |
55 | ```
56 | https://github.com/android/platform_build/blob/master/tools/signapk/SignApk.java
57 | http://androidxref.com/4.1.1/xref/build/tools/signapk/
58 | http://androidxref.com/4.1.1/xref/build/tools/signapk/test/run
59 | http://androidxref.com/4.1.1/xref/cts/tests/assets/otacerts.zip
60 | http://androidxref.com/4.1.1/xref/external/quake/tools/packagesharedlib#11
61 | http://androidxref.com/4.1.1/raw/build/target/product/security/testkey.pk8
62 | http://androidxref.com/4.1.1/xref/build/target/product/security/
63 | ```
64 |
65 | The following commands are equivalent.
66 |
67 | `sign my.apk`
68 |
69 | `java -classpath sign.jar orig.SignApk testkey.x509.pem testkey.pk8 my.apk my.s.apk`
70 |
71 | `java -jar SignApk.jar testkey.x509.pem testkey.pk8 my.apk my.s.apk`
72 |
73 |
74 | ### Similar Projects
75 |
76 | [ApkSign](http://code.google.com/p/dex2jar/source/browse/dex-tools/src/main/java/com/googlecode/dex2jar/tools/ApkSign.java) by Panxiaobo. dex2jar's ApkSign has many dependencies and does not fit into one source file. While the name `ApkSign` is similar to `apks`, no source from dex2jar is used in this project.
77 |
78 | [Tiny Sign](http://code.google.com/p/tiny-sign/) by Panxiaobo. Simple jar signing that can run on Android.
79 |
--------------------------------------------------------------------------------
/src/s/IOUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package s;
18 |
19 | import java.io.ByteArrayOutputStream;
20 | import java.io.IOException;
21 | import java.io.InputStream;
22 | import java.io.OutputStream;
23 |
24 | public class IOUtils {
25 |
26 | /**
27 | * Get the contents of an InputStream as a byte[].
28 | *
29 | * This method buffers the input internally, so there is no need to use a
30 | * BufferedInputStream.
31 | *
32 | * @param input
33 | * the InputStream to read from
34 | * @return the requested byte array
35 | */
36 | public static byte[] toByteArray(InputStream input) {
37 | try {
38 | ByteArrayOutputStream output = new ByteArrayOutputStream();
39 | copy(input, output);
40 | return output.toByteArray();
41 | } catch (Exception e) {
42 | e.printStackTrace();
43 | return null;
44 | }
45 | }
46 |
47 | private static int copy(InputStream input, OutputStream output)
48 | throws IOException {
49 | long count = copyLarge(input, output);
50 | if (count > Integer.MAX_VALUE) {
51 | return -1;
52 | }
53 | return (int) count;
54 | }
55 |
56 | private static long copyLarge(InputStream input, OutputStream output)
57 | throws IOException {
58 | byte[] buffer = new byte[4096];
59 | long count = 0;
60 | int n = 0;
61 | while (-1 != (n = input.read(buffer))) {
62 | output.write(buffer, 0, n);
63 | count += n;
64 | }
65 | return count;
66 | }
67 |
68 | }
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 | apks
5 | apks
6 | 0.0.1-SNAPSHOT
7 | jar
8 |
9 | UTF-8
10 | s.Sign
11 | sign
12 |
13 |
14 |
15 |
16 | ${basedir}
17 | false
18 |
19 | testkey.x509.pem
20 | testkey.pk8
21 | license/LICENSE-2.0.txt
22 |
23 |
24 |
25 |
26 | src
27 |
28 |
29 | maven-compiler-plugin
30 | 2.5.1
31 |
32 | 1.7
33 | 1.7
34 |
35 |
36 |
37 | org.apache.maven.plugins
38 | maven-resources-plugin
39 | 2.5
40 |
41 | UTF-8
42 |
43 |
44 |
45 | org.apache.maven.plugins
46 | maven-jar-plugin
47 | 2.4
48 |
49 | ${namePrefix}-${project.version}
50 |
51 |
52 | true
53 | ${mainClass}
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/license/LICENSE-2.0.txt:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/src/s/Sign.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2008 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package s;
18 |
19 | import java.io.ByteArrayInputStream;
20 | import java.io.ByteArrayOutputStream;
21 | import java.io.File;
22 | import java.io.FileOutputStream;
23 | import java.io.FilterOutputStream;
24 | import java.io.IOException;
25 | import java.io.InputStream;
26 | import java.io.OutputStream;
27 | import java.io.PrintStream;
28 | import java.security.DigestOutputStream;
29 | import java.security.GeneralSecurityException;
30 | import java.security.KeyFactory;
31 | import java.security.MessageDigest;
32 | import java.security.PrivateKey;
33 | import java.security.Signature;
34 | import java.security.SignatureException;
35 | import java.security.cert.CertificateFactory;
36 | import java.security.cert.X509Certificate;
37 | import java.security.spec.InvalidKeySpecException;
38 | import java.security.spec.KeySpec;
39 | import java.security.spec.PKCS8EncodedKeySpec;
40 | import java.util.ArrayList;
41 | import java.util.Collections;
42 | import java.util.Date;
43 | import java.util.Enumeration;
44 | import java.util.List;
45 | import java.util.Map;
46 | import java.util.TreeMap;
47 | import java.util.jar.Attributes;
48 | import java.util.jar.JarEntry;
49 | import java.util.jar.JarFile;
50 | import java.util.jar.JarOutputStream;
51 | import java.util.jar.Manifest;
52 | import java.util.regex.Pattern;
53 |
54 | import java.util.Base64;
55 | import sun.security.pkcs.ContentInfo;
56 | import sun.security.pkcs.PKCS7;
57 | import sun.security.pkcs.SignerInfo;
58 | import sun.security.x509.AlgorithmId;
59 | import sun.security.x509.X500Name;
60 |
61 | /**
62 | * Command line tool to sign JAR files (including APKs and OTA updates) in a way
63 | * compatible with the mincrypt verifier, using SHA1 and RSA keys.
64 | */
65 | @SuppressWarnings("restriction")
66 | class Sign {
67 | private static final String CERT_SF_NAME = "META-INF/CERT.SF";
68 | private static final String CERT_RSA_NAME = "META-INF/CERT.RSA";
69 |
70 | private static final String OTACERT_NAME = "META-INF/com/android/otacert";
71 |
72 | // Files matching this pattern are not copied to the output.
73 | private static Pattern stripPattern = Pattern
74 | .compile("^META-INF/(.*)[.](SF|RSA|DSA)$");
75 |
76 | private static X509Certificate readPublicKey() throws IOException,
77 | GeneralSecurityException {
78 | final InputStream publicKeyFileIS = new ByteArrayInputStream(publicBytes);
79 | final CertificateFactory cf = CertificateFactory.getInstance("X.509");
80 | return (X509Certificate) cf.generateCertificate(publicKeyFileIS);
81 | }
82 |
83 | /** Read a PKCS 8 format private key. */
84 | private static PrivateKey readPrivateKey() throws IOException,
85 | GeneralSecurityException {
86 | KeySpec spec = new PKCS8EncodedKeySpec(privateBytes);
87 |
88 | try {
89 | return KeyFactory.getInstance("RSA").generatePrivate(spec);
90 | } catch (InvalidKeySpecException ex) {
91 | return KeyFactory.getInstance("DSA").generatePrivate(spec);
92 | }
93 |
94 | }
95 |
96 | /** Add the SHA1 of every file to the manifest, creating it if necessary. */
97 | private static Manifest addDigestsToManifest(JarFile jar) throws IOException,
98 | GeneralSecurityException {
99 | Manifest input = jar.getManifest();
100 | Manifest output = new Manifest();
101 | Attributes main = output.getMainAttributes();
102 | if (input != null) {
103 | main.putAll(input.getMainAttributes());
104 | } else {
105 | main.putValue("Manifest-Version", "1.0");
106 | }
107 |
108 | Base64.Encoder base64 = Base64.getEncoder().withoutPadding();
109 | MessageDigest md = MessageDigest.getInstance("SHA1");
110 | byte[] buffer = new byte[4096];
111 | int num;
112 |
113 | // We sort the input entries by name, and add them to the
114 | // output manifest in sorted order. We expect that the output
115 | // map will be deterministic.
116 |
117 | TreeMap byName = new TreeMap();
118 |
119 | for (Enumeration e = jar.entries(); e.hasMoreElements();) {
120 | JarEntry entry = e.nextElement();
121 | byName.put(entry.getName(), entry);
122 | }
123 |
124 | for (JarEntry entry : byName.values()) {
125 | String name = entry.getName();
126 | if (!entry.isDirectory() && !name.equals(JarFile.MANIFEST_NAME)
127 | && !name.equals(CERT_SF_NAME) && !name.equals(CERT_RSA_NAME)
128 | && !name.equals(OTACERT_NAME)
129 | && (stripPattern == null || !stripPattern.matcher(name).matches())) {
130 | InputStream data = jar.getInputStream(entry);
131 | while ((num = data.read(buffer)) > 0) {
132 | md.update(buffer, 0, num);
133 | }
134 |
135 | Attributes attr = null;
136 | if (input != null)
137 | attr = input.getAttributes(name);
138 | attr = attr != null ? new Attributes(attr) : new Attributes();
139 | attr.putValue("SHA1-Digest", base64.encodeToString(md.digest()));
140 | output.getEntries().put(name, attr);
141 | }
142 | }
143 |
144 | return output;
145 | }
146 |
147 | /**
148 | * Add a copy of the public key to the archive; this should exactly match one
149 | * of the files in /system/etc/security/otacerts.zip on the device. (The same
150 | * cert can be extracted from the CERT.RSA file but this is much easier to get
151 | * at.)
152 | */
153 | private static void addOtacert(JarOutputStream outputJar, long timestamp,
154 | Manifest manifest) throws IOException, GeneralSecurityException {
155 | final InputStream input = new ByteArrayInputStream(publicBytes);
156 |
157 | Base64.Encoder base64 = Base64.getEncoder().withoutPadding();
158 | MessageDigest md = MessageDigest.getInstance("SHA1");
159 |
160 | JarEntry je = new JarEntry(OTACERT_NAME);
161 | je.setTime(timestamp);
162 | outputJar.putNextEntry(je);
163 |
164 | byte[] b = new byte[4096];
165 | int read;
166 | while ((read = input.read(b)) != -1) {
167 | outputJar.write(b, 0, read);
168 | md.update(b, 0, read);
169 | }
170 | input.close();
171 |
172 | Attributes attr = new Attributes();
173 | attr.putValue("SHA1-Digest", base64.encodeToString(md.digest()));
174 | manifest.getEntries().put(OTACERT_NAME, attr);
175 | }
176 |
177 | /** Write to another stream and also feed it to the Signature object. */
178 | private static class SignatureOutputStream extends FilterOutputStream {
179 | private Signature mSignature;
180 | private int mCount;
181 |
182 | public SignatureOutputStream(OutputStream out, Signature sig) {
183 | super(out);
184 | mSignature = sig;
185 | mCount = 0;
186 | }
187 |
188 | @Override
189 | public void write(int b) throws IOException {
190 | try {
191 | mSignature.update((byte) b);
192 | } catch (SignatureException e) {
193 | throw new IOException("SignatureException: " + e);
194 | }
195 | super.write(b);
196 | mCount++;
197 | }
198 |
199 | @Override
200 | public void write(byte[] b, int off, int len) throws IOException {
201 | try {
202 | mSignature.update(b, off, len);
203 | } catch (SignatureException e) {
204 | throw new IOException("SignatureException: " + e);
205 | }
206 | super.write(b, off, len);
207 | mCount += len;
208 | }
209 |
210 | public int size() {
211 | return mCount;
212 | }
213 | }
214 |
215 | /** Write a .SF file with a digest of the specified manifest. */
216 | private static void writeSignatureFile(Manifest manifest,
217 | SignatureOutputStream out) throws IOException, GeneralSecurityException {
218 | Manifest sf = new Manifest();
219 | Attributes main = sf.getMainAttributes();
220 | main.putValue("Signature-Version", "1.0");
221 |
222 | Base64.Encoder base64 = Base64.getEncoder().withoutPadding();
223 | MessageDigest md = MessageDigest.getInstance("SHA1");
224 | PrintStream print = new PrintStream(new DigestOutputStream(
225 | new ByteArrayOutputStream(), md), true, "UTF-8");
226 |
227 | // Digest of the entire manifest
228 | manifest.write(print);
229 | print.flush();
230 | main.putValue("SHA1-Digest-Manifest", base64.encodeToString(md.digest()));
231 |
232 | Map entries = manifest.getEntries();
233 | for (Map.Entry entry : entries.entrySet()) {
234 | // Digest of the manifest stanza for this entry.
235 | print.print("Name: " + entry.getKey() + "\r\n");
236 | for (Map.Entry