Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in / Register
Toggle navigation
M
Magisk
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Administrator
Magisk
Commits
60181c4f
Commit
60181c4f
authored
Oct 07, 2017
by
topjohnwu
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
MagiskManager -> java
parent
c2154474
Show whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
49 additions
and
910 deletions
+49
-910
.gitignore
.gitignore
+2
-1
.gitmodules
.gitmodules
+1
-1
MagiskManager
MagiskManager
+0
-1
README.MD
README.MD
+1
-1
build.py
build.py
+44
-30
java
java
+1
-0
.gitignore
ziptools/zipsigner/.gitignore
+0
-7
build.gradle
ziptools/zipsigner/build.gradle
+0
-36
gradle-wrapper.jar
ziptools/zipsigner/gradle/wrapper/gradle-wrapper.jar
+0
-0
gradle-wrapper.properties
ziptools/zipsigner/gradle/wrapper/gradle-wrapper.properties
+0
-6
gradlew
ziptools/zipsigner/gradlew
+0
-172
gradlew.bat
ziptools/zipsigner/gradlew.bat
+0
-84
settings.gradle
ziptools/zipsigner/settings.gradle
+0
-4
ZipSigner.java
...ools/zipsigner/src/main/java/com/topjohnwu/ZipSigner.java
+0
-567
No files found.
.gitignore
View file @
60181c4f
...
...
@@ -2,6 +2,7 @@ obj/
libs/
*.zip
*.jks
*.apk
#
Copied
binaries
#
Built
binaries
ziptools/zipadjust
.gitmodules
View file @
60181c4f
...
...
@@ -11,7 +11,7 @@
path = jni/magiskpolicy
url = https://github.com/topjohnwu/magiskpolicy.git
[submodule "MagiskManager"]
path =
MagiskManager
path =
java
url = https://github.com/topjohnwu/MagiskManager.git
[submodule "jni/busybox"]
path = jni/external/busybox
...
...
MagiskManager
@
773c24b7
Subproject commit 773c24b7fc7e6931c0932cd53943813eb4ebd027
README.MD
View file @
60181c4f
...
...
@@ -45,7 +45,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
## Credits
**MagiskManager**
(
`
MagiskManager
`
)
**MagiskManager**
(
`
java
`
)
*
Copyright 2016-2017, John Wu (@topjohnwu)
*
All contributors and translators
...
...
build.py
View file @
60181c4f
...
...
@@ -51,6 +51,10 @@ def zip_with_msg(zipfile, source, target):
print
(
'zip: {} -> {}'
.
format
(
source
,
target
))
zipfile
.
write
(
source
,
target
)
def
cp
(
source
,
target
):
print
(
'cp: {} -> {}'
.
format
(
source
,
target
))
shutil
.
copyfile
(
source
,
target
)
def
build_all
(
args
):
build_binary
(
args
)
build_apk
(
args
)
...
...
@@ -75,26 +79,23 @@ def build_apk(args):
for
key
in
[
'public.certificate.x509.pem'
,
'private.key.pk8'
]:
source
=
os
.
path
.
join
(
'ziptools'
,
key
)
target
=
os
.
path
.
join
(
'MagiskManager'
,
'app'
,
'src'
,
'main'
,
'assets'
,
key
)
print
(
'cp: {} -> {}'
.
format
(
source
,
target
))
shutil
.
copyfile
(
source
,
target
)
target
=
os
.
path
.
join
(
'java'
,
'app'
,
'src'
,
'main'
,
'assets'
,
key
)
cp
(
source
,
target
)
for
script
in
[
'magisk_uninstaller.sh'
,
'util_functions.sh'
]:
source
=
os
.
path
.
join
(
'scripts'
,
script
)
target
=
os
.
path
.
join
(
'MagiskManager'
,
'app'
,
'src'
,
'main'
,
'assets'
,
script
)
print
(
'cp: {} -> {}'
.
format
(
source
,
target
))
shutil
.
copyfile
(
source
,
target
)
target
=
os
.
path
.
join
(
'java'
,
'app'
,
'src'
,
'main'
,
'assets'
,
script
)
cp
(
source
,
target
)
os
.
chdir
(
'
MagiskManager
'
)
os
.
chdir
(
'
java
'
)
# Build unhide app and place in assets
proc
=
subprocess
.
run
(
'{} unhide:
:
assembleRelease'
.
format
(
os
.
path
.
join
(
'.'
,
'gradlew'
)),
shell
=
True
)
proc
=
subprocess
.
run
(
'{} unhide:assembleRelease'
.
format
(
os
.
path
.
join
(
'.'
,
'gradlew'
)),
shell
=
True
)
if
proc
.
returncode
!=
0
:
error
(
'Build Magisk Manager failed!'
)
source
=
os
.
path
.
join
(
'unhide'
,
'build'
,
'outputs'
,
'apk'
,
'release'
,
'unhide-release-unsigned.apk'
)
target
=
os
.
path
.
join
(
'app'
,
'src'
,
'main'
,
'assets'
,
'unhide.apk'
)
print
(
'cp: {} -> {}'
.
format
(
source
,
target
))
shutil
.
copyfile
(
source
,
target
)
cp
(
source
,
target
)
print
(
''
)
...
...
@@ -102,7 +103,7 @@ def build_apk(args):
if
not
os
.
path
.
exists
(
os
.
path
.
join
(
'..'
,
'release_signature.jks'
)):
error
(
'Please generate a java keystore and place it in
\'
release_signature.jks
\'
'
)
proc
=
subprocess
.
run
(
'{} app:
:
assembleRelease'
.
format
(
os
.
path
.
join
(
'.'
,
'gradlew'
)),
shell
=
True
)
proc
=
subprocess
.
run
(
'{} app:assembleRelease'
.
format
(
os
.
path
.
join
(
'.'
,
'gradlew'
)),
shell
=
True
)
if
proc
.
returncode
!=
0
:
error
(
'Build Magisk Manager failed!'
)
...
...
@@ -141,16 +142,26 @@ def build_apk(args):
silentremove
(
unsigned
)
silentremove
(
aligned
)
else
:
proc
=
subprocess
.
run
(
'{} app:
:
assembleDebug'
.
format
(
os
.
path
.
join
(
'.'
,
'gradlew'
)),
shell
=
True
)
proc
=
subprocess
.
run
(
'{} app:assembleDebug'
.
format
(
os
.
path
.
join
(
'.'
,
'gradlew'
)),
shell
=
True
)
if
proc
.
returncode
!=
0
:
error
(
'Build Magisk Manager failed!'
)
# Return to upper directory
os
.
chdir
(
'..'
)
def
sign_adjust_zip
(
unsigned
,
output
):
def
build_snet
(
args
):
os
.
chdir
(
'java'
)
proc
=
subprocess
.
run
(
'{} snet:assembleRelease'
.
format
(
os
.
path
.
join
(
'.'
,
'gradlew'
)),
shell
=
True
)
if
proc
.
returncode
!=
0
:
error
(
'Build snet extention failed!'
)
source
=
os
.
path
.
join
(
'snet'
,
'build'
,
'outputs'
,
'apk'
,
'release'
,
'snet-release-unsigned.apk'
)
target
=
os
.
path
.
join
(
'..'
,
'snet.apk'
)
print
(
''
)
cp
(
source
,
target
)
os
.
chdir
(
'..'
)
zipsigner
=
os
.
path
.
join
(
'ziptools'
,
'zipsigner'
,
'build'
,
'libs'
,
'zipsigner.jar'
)
def
sign_adjust_zip
(
unsigned
,
output
):
jarsigner
=
os
.
path
.
join
(
'java'
,
'jarsigner'
,
'build'
,
'libs'
,
'jarsigner-fat.jar'
)
if
os
.
name
!=
'nt'
and
not
os
.
path
.
exists
(
os
.
path
.
join
(
'ziptools'
,
'zipadjust'
)):
header
(
'* Building zipadjust'
)
...
...
@@ -158,13 +169,13 @@ def sign_adjust_zip(unsigned, output):
proc
=
subprocess
.
run
(
'gcc -o ziptools/zipadjust ziptools/zipadjust_src/*.c -lz'
,
shell
=
True
)
if
proc
.
returncode
!=
0
:
error
(
'Build zipadjust failed!'
)
if
not
os
.
path
.
exists
(
zip
signer
):
header
(
'* Building
zipsigner
.jar'
)
os
.
chdir
(
os
.
path
.
join
(
'ziptools'
,
'zipsigner'
)
)
proc
=
subprocess
.
run
(
'{} shadowJar'
.
format
(
os
.
path
.
join
(
'.'
,
'gradlew'
)),
shell
=
True
)
if
not
os
.
path
.
exists
(
jar
signer
):
header
(
'* Building
jarsigner-fat
.jar'
)
os
.
chdir
(
'java'
)
proc
=
subprocess
.
run
(
'{}
jarsigner:
shadowJar'
.
format
(
os
.
path
.
join
(
'.'
,
'gradlew'
)),
shell
=
True
)
if
proc
.
returncode
!=
0
:
error
(
'Build
zipsigner
.jar failed!'
)
os
.
chdir
(
os
.
path
.
join
(
'..'
,
'..'
)
)
error
(
'Build
jarsigner-fat
.jar failed!'
)
os
.
chdir
(
'..'
)
header
(
'* Signing / Adjusting Zip'
)
...
...
@@ -172,7 +183,7 @@ def sign_adjust_zip(unsigned, output):
privateKey
=
os
.
path
.
join
(
'ziptools'
,
'private.key.pk8'
)
# Unsigned->signed
proc
=
subprocess
.
run
([
'java'
,
'-jar'
,
zip
signer
,
proc
=
subprocess
.
run
([
'java'
,
'-jar'
,
jar
signer
,
publicKey
,
privateKey
,
unsigned
,
'tmp_signed.zip'
])
if
proc
.
returncode
!=
0
:
error
(
'First sign flashable zip failed!'
)
...
...
@@ -183,7 +194,7 @@ def sign_adjust_zip(unsigned, output):
error
(
'Adjust flashable zip failed!'
)
# Adjusted -> output
proc
=
subprocess
.
run
([
'java'
,
'-jar'
,
zip
signer
,
proc
=
subprocess
.
run
([
'java'
,
'-jar'
,
jar
signer
,
"-m"
,
publicKey
,
privateKey
,
'tmp_adjusted.zip'
,
output
])
if
proc
.
returncode
!=
0
:
error
(
'Second sign flashable zip failed!'
)
...
...
@@ -243,7 +254,7 @@ def zip_main(args):
zip_with_msg
(
zipf
,
source
,
target
)
# APK
source
=
os
.
path
.
join
(
'
MagiskManager
'
,
'app'
,
'build'
,
'outputs'
,
'apk'
,
source
=
os
.
path
.
join
(
'
java
'
,
'app'
,
'build'
,
'outputs'
,
'apk'
,
'release'
if
args
.
release
else
'debug'
,
'app-release.apk'
if
args
.
release
else
'app-debug.apk'
)
target
=
os
.
path
.
join
(
'common'
,
'magisk.apk'
)
zip_with_msg
(
zipf
,
source
,
target
)
...
...
@@ -328,20 +339,20 @@ def zip_uninstaller(args):
def
cleanup
(
args
):
if
len
(
args
.
target
)
==
0
:
args
.
target
=
[
'binary'
,
'
apk
'
,
'zip'
]
args
.
target
=
[
'binary'
,
'
java
'
,
'zip'
]
if
'binary'
in
args
.
target
:
header
(
'* Cleaning
Magisk
binaries'
)
header
(
'* Cleaning binaries'
)
subprocess
.
run
(
os
.
path
.
join
(
os
.
environ
[
'ANDROID_HOME'
],
'ndk-bundle'
,
'ndk-build'
)
+
' clean'
,
shell
=
True
)
if
'
apk
'
in
args
.
target
:
header
(
'* Cleaning
Magisk Manager
'
)
os
.
chdir
(
'
MagiskManager
'
)
if
'
java
'
in
args
.
target
:
header
(
'* Cleaning
java
'
)
os
.
chdir
(
'
java
'
)
subprocess
.
run
(
'{} clean'
.
format
(
os
.
path
.
join
(
'.'
,
'gradlew'
)),
shell
=
True
)
os
.
chdir
(
'..'
)
if
'zip'
in
args
.
target
:
header
(
'* Cleaning
created
zip files'
)
header
(
'* Cleaning zip files'
)
for
f
in
os
.
listdir
(
'.'
):
if
'.zip'
in
f
:
print
(
'rm {}'
.
format
(
f
))
...
...
@@ -364,6 +375,9 @@ binary_parser.set_defaults(func=build_binary)
apk_parser
=
subparsers
.
add_parser
(
'apk'
,
help
=
'build Magisk Manager APK'
)
apk_parser
.
set_defaults
(
func
=
build_apk
)
snet_parser
=
subparsers
.
add_parser
(
'snet'
,
help
=
'build snet extention for Magisk Manager'
)
snet_parser
.
set_defaults
(
func
=
build_snet
)
zip_parser
=
subparsers
.
add_parser
(
'zip'
,
help
=
'zip and sign Magisk into a flashable zip'
)
zip_parser
.
add_argument
(
'versionString'
)
zip_parser
.
add_argument
(
'versionCode'
,
type
=
int
)
...
...
@@ -372,7 +386,7 @@ zip_parser.set_defaults(func=zip_main)
uninstaller_parser
=
subparsers
.
add_parser
(
'uninstaller'
,
help
=
'create flashable uninstaller'
)
uninstaller_parser
.
set_defaults
(
func
=
zip_uninstaller
)
clean_parser
=
subparsers
.
add_parser
(
'clean'
,
help
=
'clean [target...] targets: binary
apk
zip'
)
clean_parser
=
subparsers
.
add_parser
(
'clean'
,
help
=
'clean [target...] targets: binary
java
zip'
)
clean_parser
.
add_argument
(
'target'
,
nargs
=
'*'
)
clean_parser
.
set_defaults
(
func
=
cleanup
)
...
...
java
@
39b6df27
Subproject commit 39b6df27b3d72c706010e6773d4bb50a202a1543
ziptools/zipsigner/.gitignore
deleted
100644 → 0
View file @
c2154474
*.iml
.gradle
/local.properties
.idea/
/build
*.hprof
app/.externalNativeBuild/
ziptools/zipsigner/build.gradle
deleted
100644 → 0
View file @
c2154474
group
'com.topjohnwu'
version
'1.0.0'
apply
plugin:
'java'
apply
plugin:
'com.github.johnrengelman.shadow'
sourceCompatibility
=
1.8
jar
{
manifest
{
attributes
'Main-Class'
:
'com.topjohnwu.ZipSigner'
}
}
shadowJar
{
classifier
=
null
version
=
null
}
buildscript
{
repositories
{
jcenter
()
}
dependencies
{
classpath
'com.github.jengelman.gradle.plugins:shadow:2.0.1'
}
}
repositories
{
mavenCentral
()
}
dependencies
{
compile
'org.bouncycastle:bcprov-jdk15on:1.57'
compile
'org.bouncycastle:bcpkix-jdk15on:1.57'
}
ziptools/zipsigner/gradle/wrapper/gradle-wrapper.jar
deleted
100644 → 0
View file @
c2154474
This diff was suppressed by a .gitattributes entry.
ziptools/zipsigner/gradle/wrapper/gradle-wrapper.properties
deleted
100644 → 0
View file @
c2154474
#Thu Aug 24 10:35:40 CST 2017
distributionBase
=
GRADLE_USER_HOME
distributionPath
=
wrapper/dists
zipStoreBase
=
GRADLE_USER_HOME
zipStorePath
=
wrapper/dists
distributionUrl
=
https
\:
//services.gradle.org/distributions/gradle-3.5-rc-2-bin.zip
ziptools/zipsigner/gradlew
deleted
100755 → 0
View file @
c2154474
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG
=
"
$0
"
# Need this for relative symlinks.
while
[
-h
"
$PRG
"
]
;
do
ls
=
`
ls
-ld
"
$PRG
"
`
link
=
`
expr
"
$ls
"
:
'.*-> \(.*\)$'
`
if
expr
"
$link
"
:
'/.*'
>
/dev/null
;
then
PRG
=
"
$link
"
else
PRG
=
`
dirname
"
$PRG
"
`
"/
$link
"
fi
done
SAVED
=
"
`
pwd
`
"
cd
"
`
dirname
\"
$PRG
\"
`
/"
>
/dev/null
APP_HOME
=
"
`
pwd
-P
`
"
cd
"
$SAVED
"
>
/dev/null
APP_NAME
=
"Gradle"
APP_BASE_NAME
=
`
basename
"
$0
"
`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS
=
""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD
=
"maximum"
warn
(
)
{
echo
"
$*
"
}
die
(
)
{
echo
echo
"
$*
"
echo
exit
1
}
# OS specific support (must be 'true' or 'false').
cygwin
=
false
msys
=
false
darwin
=
false
nonstop
=
false
case
"
`
uname
`
"
in
CYGWIN
*
)
cygwin
=
true
;;
Darwin
*
)
darwin
=
true
;;
MINGW
*
)
msys
=
true
;;
NONSTOP
*
)
nonstop
=
true
;;
esac
CLASSPATH
=
$APP_HOME
/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if
[
-n
"
$JAVA_HOME
"
]
;
then
if
[
-x
"
$JAVA_HOME
/jre/sh/java"
]
;
then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD
=
"
$JAVA_HOME
/jre/sh/java"
else
JAVACMD
=
"
$JAVA_HOME
/bin/java"
fi
if
[
!
-x
"
$JAVACMD
"
]
;
then
die
"ERROR: JAVA_HOME is set to an invalid directory:
$JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD
=
"java"
which java
>
/dev/null 2>&1
||
die
"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if
[
"
$cygwin
"
=
"false"
-a
"
$darwin
"
=
"false"
-a
"
$nonstop
"
=
"false"
]
;
then
MAX_FD_LIMIT
=
`
ulimit
-H
-n
`
if
[
$?
-eq
0
]
;
then
if
[
"
$MAX_FD
"
=
"maximum"
-o
"
$MAX_FD
"
=
"max"
]
;
then
MAX_FD
=
"
$MAX_FD_LIMIT
"
fi
ulimit
-n
$MAX_FD
if
[
$?
-ne
0
]
;
then
warn
"Could not set maximum file descriptor limit:
$MAX_FD
"
fi
else
warn
"Could not query maximum file descriptor limit:
$MAX_FD_LIMIT
"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if
$darwin
;
then
GRADLE_OPTS
=
"
$GRADLE_OPTS
\"
-Xdock:name=
$APP_NAME
\"
\"
-Xdock:icon=
$APP_HOME
/media/gradle.icns
\"
"
fi
# For Cygwin, switch paths to Windows format before running java
if
$cygwin
;
then
APP_HOME
=
`
cygpath
--path
--mixed
"
$APP_HOME
"
`
CLASSPATH
=
`
cygpath
--path
--mixed
"
$CLASSPATH
"
`
JAVACMD
=
`
cygpath
--unix
"
$JAVACMD
"
`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW
=
`
find
-L
/
-maxdepth
1
-mindepth
1
-type
d 2>/dev/null
`
SEP
=
""
for
dir
in
$ROOTDIRSRAW
;
do
ROOTDIRS
=
"
$ROOTDIRS$SEP$dir
"
SEP
=
"|"
done
OURCYGPATTERN
=
"(^(
$ROOTDIRS
))"
# Add a user-defined pattern to the cygpath arguments
if
[
"
$GRADLE_CYGPATTERN
"
!=
""
]
;
then
OURCYGPATTERN
=
"
$OURCYGPATTERN
|(
$GRADLE_CYGPATTERN
)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i
=
0
for
arg
in
"
$@
"
;
do
CHECK
=
`
echo
"
$arg
"
|egrep
-c
"
$OURCYGPATTERN
"
-
`
CHECK2
=
`
echo
"
$arg
"
|egrep
-c
"^-"
`
### Determine if an option
if
[
$CHECK
-ne
0
]
&&
[
$CHECK2
-eq
0
]
;
then
### Added a condition
eval
`
echo
args
$i
`
=
`
cygpath
--path
--ignore
--mixed
"
$arg
"
`
else
eval
`
echo
args
$i
`
=
"
\"
$arg
\"
"
fi
i
=
$((
i+1
))
done
case
$i
in
(
0
)
set
--
;;
(
1
)
set
--
"
$args0
"
;;
(
2
)
set
--
"
$args0
"
"
$args1
"
;;
(
3
)
set
--
"
$args0
"
"
$args1
"
"
$args2
"
;;
(
4
)
set
--
"
$args0
"
"
$args1
"
"
$args2
"
"
$args3
"
;;
(
5
)
set
--
"
$args0
"
"
$args1
"
"
$args2
"
"
$args3
"
"
$args4
"
;;
(
6
)
set
--
"
$args0
"
"
$args1
"
"
$args2
"
"
$args3
"
"
$args4
"
"
$args5
"
;;
(
7
)
set
--
"
$args0
"
"
$args1
"
"
$args2
"
"
$args3
"
"
$args4
"
"
$args5
"
"
$args6
"
;;
(
8
)
set
--
"
$args0
"
"
$args1
"
"
$args2
"
"
$args3
"
"
$args4
"
"
$args5
"
"
$args6
"
"
$args7
"
;;
(
9
)
set
--
"
$args0
"
"
$args1
"
"
$args2
"
"
$args3
"
"
$args4
"
"
$args5
"
"
$args6
"
"
$args7
"
"
$args8
"
;;
esac
fi
# Escape application args
save
(
)
{
for
i
do
printf
%s
\\
n
"
$i
"
|
sed
"s/'/'
\\\\
''/g;1s/^/'/;
\$
s/
\$
/'
\\\\
/"
;
done
echo
" "
}
APP_ARGS
=
$(
save
"
$@
"
)
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set
--
$DEFAULT_JVM_OPTS
$JAVA_OPTS
$GRADLE_OPTS
"
\"
-Dorg.gradle.appname=
$APP_BASE_NAME
\"
"
-classpath
"
\"
$CLASSPATH
\"
"
org.gradle.wrapper.GradleWrapperMain
"
$APP_ARGS
"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if
[
"
$(
uname
)
"
=
"Darwin"
]
&&
[
"
$HOME
"
=
"
$PWD
"
]
;
then
cd
"
$(
dirname
"
$0
"
)
"
fi
exec
"
$JAVACMD
"
"
$@
"
ziptools/zipsigner/gradlew.bat
deleted
100644 → 0
View file @
c2154474
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
ziptools/zipsigner/settings.gradle
deleted
100644 → 0
View file @
c2154474
rootProject
.
name
=
'zipsigner'
include
'apksigner'
rootProject
.
name
=
'zipsigner'
ziptools/zipsigner/src/main/java/com/topjohnwu/ZipSigner.java
deleted
100644 → 0
View file @
c2154474
package
com
.
topjohnwu
;
import
org.bouncycastle.asn1.ASN1InputStream
;
import
org.bouncycastle.asn1.ASN1ObjectIdentifier
;
import
org.bouncycastle.asn1.DEROutputStream
;
import
org.bouncycastle.asn1.cms.CMSObjectIdentifiers
;
import
org.bouncycastle.asn1.pkcs.PrivateKeyInfo
;
import
org.bouncycastle.cert.jcajce.JcaCertStore
;
import
org.bouncycastle.cms.CMSException
;
import
org.bouncycastle.cms.CMSProcessableByteArray
;
import
org.bouncycastle.cms.CMSSignedData
;
import
org.bouncycastle.cms.CMSSignedDataGenerator
;
import
org.bouncycastle.cms.CMSTypedData
;
import
org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder
;
import
org.bouncycastle.jce.provider.BouncyCastleProvider
;
import
org.bouncycastle.operator.ContentSigner
;
import
org.bouncycastle.operator.OperatorCreationException
;
import
org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
;
import
org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder
;
import
org.bouncycastle.util.encoders.Base64
;
import
java.io.ByteArrayInputStream
;
import
java.io.ByteArrayOutputStream
;
import
java.io.File
;
import
java.io.FileInputStream
;
import
java.io.FileOutputStream
;
import
java.io.FilterOutputStream
;
import
java.io.IOException
;
import
java.io.InputStream
;
import
java.io.OutputStream
;
import
java.io.PrintStream
;
import
java.security.DigestOutputStream
;
import
java.security.GeneralSecurityException
;
import
java.security.KeyFactory
;
import
java.security.MessageDigest
;
import
java.security.PrivateKey
;
import
java.security.Provider
;
import
java.security.Security
;
import
java.security.cert.CertificateEncodingException
;
import
java.security.cert.CertificateFactory
;
import
java.security.cert.X509Certificate
;
import
java.security.spec.PKCS8EncodedKeySpec
;
import
java.util.ArrayList
;
import
java.util.Arrays
;
import
java.util.Collections
;
import
java.util.Enumeration
;
import
java.util.Locale
;
import
java.util.Map
;
import
java.util.TreeMap
;
import
java.util.jar.Attributes
;
import
java.util.jar.JarEntry
;
import
java.util.jar.JarFile
;
import
java.util.jar.JarOutputStream
;
import
java.util.jar.Manifest
;
import
java.util.regex.Pattern
;
/*
* Modified from from AOSP(Marshmallow) SignAPK.java
* */
public
class
ZipSigner
{
private
static
final
String
CERT_SF_NAME
=
"META-INF/CERT.SF"
;
private
static
final
String
CERT_SIG_NAME
=
"META-INF/CERT.%s"
;
private
static
Provider
sBouncyCastleProvider
;
// bitmasks for which hash algorithms we need the manifest to include.
private
static
final
int
USE_SHA1
=
1
;
private
static
final
int
USE_SHA256
=
2
;
/**
* Return one of USE_SHA1 or USE_SHA256 according to the signature
* algorithm specified in the cert.
*/
private
static
int
getDigestAlgorithm
(
X509Certificate
cert
)
{
String
sigAlg
=
cert
.
getSigAlgName
().
toUpperCase
(
Locale
.
US
);
if
(
"SHA1WITHRSA"
.
equals
(
sigAlg
)
||
"MD5WITHRSA"
.
equals
(
sigAlg
))
{
// see "HISTORICAL NOTE" above.
return
USE_SHA1
;
}
else
if
(
sigAlg
.
startsWith
(
"SHA256WITH"
))
{
return
USE_SHA256
;
}
else
{
throw
new
IllegalArgumentException
(
"unsupported signature algorithm \""
+
sigAlg
+
"\" in cert ["
+
cert
.
getSubjectDN
());
}
}
/** Returns the expected signature algorithm for this key type. */
private
static
String
getSignatureAlgorithm
(
X509Certificate
cert
)
{
String
sigAlg
=
cert
.
getSigAlgName
().
toUpperCase
(
Locale
.
US
);
String
keyType
=
cert
.
getPublicKey
().
getAlgorithm
().
toUpperCase
(
Locale
.
US
);
if
(
"RSA"
.
equalsIgnoreCase
(
keyType
))
{
if
(
getDigestAlgorithm
(
cert
)
==
USE_SHA256
)
{
return
"SHA256withRSA"
;
}
else
{
return
"SHA1withRSA"
;
}
}
else
if
(
"EC"
.
equalsIgnoreCase
(
keyType
))
{
return
"SHA256withECDSA"
;
}
else
{
throw
new
IllegalArgumentException
(
"unsupported key type: "
+
keyType
);
}
}
// Files matching this pattern are not copied to the output.
private
static
Pattern
stripPattern
=
Pattern
.
compile
(
"^(META-INF/((.*)[.](SF|RSA|DSA|EC)|com/android/otacert))|("
+
Pattern
.
quote
(
JarFile
.
MANIFEST_NAME
)
+
")$"
);
private
static
X509Certificate
readPublicKey
(
InputStream
input
)
throws
IOException
,
GeneralSecurityException
{
try
{
CertificateFactory
cf
=
CertificateFactory
.
getInstance
(
"X.509"
);
return
(
X509Certificate
)
cf
.
generateCertificate
(
input
);
}
finally
{
input
.
close
();
}
}
/** Read a PKCS#8 format private key. */
private
static
PrivateKey
readPrivateKey
(
InputStream
input
)
throws
IOException
,
GeneralSecurityException
{
try
{
byte
[]
buffer
=
new
byte
[
4096
];
int
size
=
input
.
read
(
buffer
);
byte
[]
bytes
=
Arrays
.
copyOf
(
buffer
,
size
);
/* Check to see if this is in an EncryptedPrivateKeyInfo structure. */
PKCS8EncodedKeySpec
spec
=
new
PKCS8EncodedKeySpec
(
bytes
);
/*
* Now it's in a PKCS#8 PrivateKeyInfo structure. Read its Algorithm
* OID and use that to construct a KeyFactory.
*/
ASN1InputStream
bIn
=
new
ASN1InputStream
(
new
ByteArrayInputStream
(
spec
.
getEncoded
()));
PrivateKeyInfo
pki
=
PrivateKeyInfo
.
getInstance
(
bIn
.
readObject
());
String
algOid
=
pki
.
getPrivateKeyAlgorithm
().
getAlgorithm
().
getId
();
return
KeyFactory
.
getInstance
(
algOid
).
generatePrivate
(
spec
);
}
finally
{
input
.
close
();
}
}
/**
* Add the hash(es) of every file to the manifest, creating it if
* necessary.
*/
private
static
Manifest
addDigestsToManifest
(
JarFile
jar
,
int
hashes
)
throws
IOException
,
GeneralSecurityException
{
Manifest
input
=
jar
.
getManifest
();
Manifest
output
=
new
Manifest
();
Attributes
main
=
output
.
getMainAttributes
();
if
(
input
!=
null
)
{
main
.
putAll
(
input
.
getMainAttributes
());
}
else
{
main
.
putValue
(
"Manifest-Version"
,
"1.0"
);
main
.
putValue
(
"Created-By"
,
"1.0 (Android SignApk)"
);
}
MessageDigest
md_sha1
=
null
;
MessageDigest
md_sha256
=
null
;
if
((
hashes
&
USE_SHA1
)
!=
0
)
{
md_sha1
=
MessageDigest
.
getInstance
(
"SHA1"
);
}
if
((
hashes
&
USE_SHA256
)
!=
0
)
{
md_sha256
=
MessageDigest
.
getInstance
(
"SHA256"
);
}
byte
[]
buffer
=
new
byte
[
4096
];
int
num
;
// We sort the input entries by name, and add them to the
// output manifest in sorted order. We expect that the output
// map will be deterministic.
TreeMap
<
String
,
JarEntry
>
byName
=
new
TreeMap
<
String
,
JarEntry
>();
for
(
Enumeration
<
JarEntry
>
e
=
jar
.
entries
();
e
.
hasMoreElements
();
)
{
JarEntry
entry
=
e
.
nextElement
();
byName
.
put
(
entry
.
getName
(),
entry
);
}
for
(
JarEntry
entry:
byName
.
values
())
{
String
name
=
entry
.
getName
();
if
(!
entry
.
isDirectory
()
&&
(
stripPattern
==
null
||
!
stripPattern
.
matcher
(
name
).
matches
()))
{
InputStream
data
=
jar
.
getInputStream
(
entry
);
while
((
num
=
data
.
read
(
buffer
))
>
0
)
{
if
(
md_sha1
!=
null
)
md_sha1
.
update
(
buffer
,
0
,
num
);
if
(
md_sha256
!=
null
)
md_sha256
.
update
(
buffer
,
0
,
num
);
}
Attributes
attr
=
null
;
if
(
input
!=
null
)
attr
=
input
.
getAttributes
(
name
);
attr
=
attr
!=
null
?
new
Attributes
(
attr
)
:
new
Attributes
();
if
(
md_sha1
!=
null
)
{
attr
.
putValue
(
"SHA1-Digest"
,
new
String
(
Base64
.
encode
(
md_sha1
.
digest
()),
"ASCII"
));
}
if
(
md_sha256
!=
null
)
{
attr
.
putValue
(
"SHA-256-Digest"
,
new
String
(
Base64
.
encode
(
md_sha256
.
digest
()),
"ASCII"
));
}
output
.
getEntries
().
put
(
name
,
attr
);
}
}
return
output
;
}
/** Write to another stream and track how many bytes have been
* written.
*/
private
static
class
CountOutputStream
extends
FilterOutputStream
{
private
int
mCount
;
public
CountOutputStream
(
OutputStream
out
)
{
super
(
out
);
mCount
=
0
;
}
@Override
public
void
write
(
int
b
)
throws
IOException
{
super
.
write
(
b
);
mCount
++;
}
@Override
public
void
write
(
byte
[]
b
,
int
off
,
int
len
)
throws
IOException
{
super
.
write
(
b
,
off
,
len
);
mCount
+=
len
;
}
public
int
size
()
{
return
mCount
;
}
}
/** Write a .SF file with a digest of the specified manifest. */
private
static
void
writeSignatureFile
(
Manifest
manifest
,
OutputStream
out
,
int
hash
)
throws
IOException
,
GeneralSecurityException
{
Manifest
sf
=
new
Manifest
();
Attributes
main
=
sf
.
getMainAttributes
();
main
.
putValue
(
"Signature-Version"
,
"1.0"
);
main
.
putValue
(
"Created-By"
,
"1.0 (Android SignApk)"
);
MessageDigest
md
=
MessageDigest
.
getInstance
(
hash
==
USE_SHA256
?
"SHA256"
:
"SHA1"
);
PrintStream
print
=
new
PrintStream
(
new
DigestOutputStream
(
new
ByteArrayOutputStream
(),
md
),
true
,
"UTF-8"
);
// Digest of the entire manifest
manifest
.
write
(
print
);
print
.
flush
();
main
.
putValue
(
hash
==
USE_SHA256
?
"SHA-256-Digest-Manifest"
:
"SHA1-Digest-Manifest"
,
new
String
(
Base64
.
encode
(
md
.
digest
()),
"ASCII"
));
Map
<
String
,
Attributes
>
entries
=
manifest
.
getEntries
();
for
(
Map
.
Entry
<
String
,
Attributes
>
entry
:
entries
.
entrySet
())
{
// Digest of the manifest stanza for this entry.
print
.
print
(
"Name: "
+
entry
.
getKey
()
+
"\r\n"
);
for
(
Map
.
Entry
<
Object
,
Object
>
att
:
entry
.
getValue
().
entrySet
())
{
print
.
print
(
att
.
getKey
()
+
": "
+
att
.
getValue
()
+
"\r\n"
);
}
print
.
print
(
"\r\n"
);
print
.
flush
();
Attributes
sfAttr
=
new
Attributes
();
sfAttr
.
putValue
(
hash
==
USE_SHA256
?
"SHA-256-Digest"
:
"SHA1-Digest-Manifest"
,
new
String
(
Base64
.
encode
(
md
.
digest
()),
"ASCII"
));
sf
.
getEntries
().
put
(
entry
.
getKey
(),
sfAttr
);
}
CountOutputStream
cout
=
new
CountOutputStream
(
out
);
sf
.
write
(
cout
);
// A bug in the java.util.jar implementation of Android platforms
// up to version 1.6 will cause a spurious IOException to be thrown
// if the length of the signature file is a multiple of 1024 bytes.
// As a workaround, add an extra CRLF in this case.
if
((
cout
.
size
()
%
1024
)
==
0
)
{
cout
.
write
(
'\r'
);
cout
.
write
(
'\n'
);
}
}
/** Sign data and write the digital signature to 'out'. */
private
static
void
writeSignatureBlock
(
CMSTypedData
data
,
X509Certificate
publicKey
,
PrivateKey
privateKey
,
OutputStream
out
)
throws
IOException
,
CertificateEncodingException
,
OperatorCreationException
,
CMSException
{
ArrayList
<
X509Certificate
>
certList
=
new
ArrayList
<>(
1
);
certList
.
add
(
publicKey
);
JcaCertStore
certs
=
new
JcaCertStore
(
certList
);
CMSSignedDataGenerator
gen
=
new
CMSSignedDataGenerator
();
ContentSigner
signer
=
new
JcaContentSignerBuilder
(
getSignatureAlgorithm
(
publicKey
))
.
setProvider
(
sBouncyCastleProvider
)
.
build
(
privateKey
);
gen
.
addSignerInfoGenerator
(
new
JcaSignerInfoGeneratorBuilder
(
new
JcaDigestCalculatorProviderBuilder
()
.
setProvider
(
sBouncyCastleProvider
)
.
build
())
.
setDirectSignature
(
true
)
.
build
(
signer
,
publicKey
));
gen
.
addCertificates
(
certs
);
CMSSignedData
sigData
=
gen
.
generate
(
data
,
false
);
ASN1InputStream
asn1
=
new
ASN1InputStream
(
sigData
.
getEncoded
());
DEROutputStream
dos
=
new
DEROutputStream
(
out
);
dos
.
writeObject
(
asn1
.
readObject
());
}
/**
* Copy all the files in a manifest from input to output. We set
* the modification times in the output to a fixed time, so as to
* reduce variation in the output file and make incremental OTAs
* more efficient.
*/
private
static
void
copyFiles
(
Manifest
manifest
,
JarFile
in
,
JarOutputStream
out
,
long
timestamp
,
int
alignment
)
throws
IOException
{
byte
[]
buffer
=
new
byte
[
4096
];
int
num
;
Map
<
String
,
Attributes
>
entries
=
manifest
.
getEntries
();
ArrayList
<
String
>
names
=
new
ArrayList
<
String
>(
entries
.
keySet
());
Collections
.
sort
(
names
);
boolean
firstEntry
=
true
;
long
offset
=
0L
;
// We do the copy in two passes -- first copying all the
// entries that are STORED, then copying all the entries that
// have any other compression flag (which in practice means
// DEFLATED). This groups all the stored entries together at
// the start of the file and makes it easier to do alignment
// on them (since only stored entries are aligned).
for
(
String
name
:
names
)
{
JarEntry
inEntry
=
in
.
getJarEntry
(
name
);
JarEntry
outEntry
=
null
;
if
(
inEntry
.
getMethod
()
!=
JarEntry
.
STORED
)
continue
;
// Preserve the STORED method of the input entry.
outEntry
=
new
JarEntry
(
inEntry
);
outEntry
.
setTime
(
timestamp
);
// 'offset' is the offset into the file at which we expect
// the file data to begin. This is the value we need to
// make a multiple of 'alignement'.
offset
+=
JarFile
.
LOCHDR
+
outEntry
.
getName
().
length
();
if
(
firstEntry
)
{
// The first entry in a jar file has an extra field of
// four bytes that you can't get rid of; any extra
// data you specify in the JarEntry is appended to
// these forced four bytes. This is JAR_MAGIC in
// JarOutputStream; the bytes are 0xfeca0000.
offset
+=
4
;
firstEntry
=
false
;
}
if
(
alignment
>
0
&&
(
offset
%
alignment
!=
0
))
{
// Set the "extra data" of the entry to between 1 and
// alignment-1 bytes, to make the file data begin at
// an aligned offset.
int
needed
=
alignment
-
(
int
)(
offset
%
alignment
);
outEntry
.
setExtra
(
new
byte
[
needed
]);
offset
+=
needed
;
}
out
.
putNextEntry
(
outEntry
);
InputStream
data
=
in
.
getInputStream
(
inEntry
);
while
((
num
=
data
.
read
(
buffer
))
>
0
)
{
out
.
write
(
buffer
,
0
,
num
);
offset
+=
num
;
}
out
.
flush
();
}
// Copy all the non-STORED entries. We don't attempt to
// maintain the 'offset' variable past this point; we don't do
// alignment on these entries.
for
(
String
name
:
names
)
{
JarEntry
inEntry
=
in
.
getJarEntry
(
name
);
JarEntry
outEntry
=
null
;
if
(
inEntry
.
getMethod
()
==
JarEntry
.
STORED
)
continue
;
// Create a new entry so that the compressed len is recomputed.
outEntry
=
new
JarEntry
(
name
);
outEntry
.
setTime
(
timestamp
);
out
.
putNextEntry
(
outEntry
);
InputStream
data
=
in
.
getInputStream
(
inEntry
);
while
((
num
=
data
.
read
(
buffer
))
>
0
)
{
out
.
write
(
buffer
,
0
,
num
);
}
out
.
flush
();
}
}
// This class is to provide a file's content, but trimming out the last two bytes
// Used for signWholeFile
private
static
class
CMSProcessableFile
implements
CMSTypedData
{
private
File
file
;
private
ASN1ObjectIdentifier
type
;
private
byte
[]
buffer
;
int
bufferSize
=
0
;
CMSProcessableFile
(
File
file
)
{
this
.
file
=
file
;
type
=
new
ASN1ObjectIdentifier
(
CMSObjectIdentifiers
.
data
.
getId
());
buffer
=
new
byte
[
4096
];
}
@Override
public
ASN1ObjectIdentifier
getContentType
()
{
return
type
;
}
@Override
public
void
write
(
OutputStream
out
)
throws
IOException
,
CMSException
{
FileInputStream
input
=
new
FileInputStream
(
file
);
long
len
=
file
.
length
()
-
2
;
while
((
bufferSize
=
input
.
read
(
buffer
))
>
0
)
{
if
(
len
<=
bufferSize
)
{
out
.
write
(
buffer
,
0
,
(
int
)
len
);
break
;
}
else
{
out
.
write
(
buffer
,
0
,
bufferSize
);
}
len
-=
bufferSize
;
}
}
@Override
public
Object
getContent
()
{
return
file
;
}
byte
[]
getTail
()
{
return
Arrays
.
copyOfRange
(
buffer
,
0
,
bufferSize
);
}
}
private
static
void
signWholeFile
(
File
input
,
X509Certificate
publicKey
,
PrivateKey
privateKey
,
OutputStream
outputStream
)
throws
Exception
{
ByteArrayOutputStream
temp
=
new
ByteArrayOutputStream
();
// put a readable message and a null char at the start of the
// archive comment, so that tools that display the comment
// (hopefully) show something sensible.
// TODO: anything more useful we can put in this message?
byte
[]
message
=
"signed by SignApk"
.
getBytes
(
"UTF-8"
);
temp
.
write
(
message
);
temp
.
write
(
0
);
CMSProcessableFile
cmsFile
=
new
CMSProcessableFile
(
input
);
writeSignatureBlock
(
cmsFile
,
publicKey
,
privateKey
,
temp
);
// For a zip with no archive comment, the
// end-of-central-directory record will be 22 bytes long, so
// we expect to find the EOCD marker 22 bytes from the end.
byte
[]
zipData
=
cmsFile
.
getTail
();
if
(
zipData
[
zipData
.
length
-
22
]
!=
0x50
||
zipData
[
zipData
.
length
-
21
]
!=
0x4b
||
zipData
[
zipData
.
length
-
20
]
!=
0x05
||
zipData
[
zipData
.
length
-
19
]
!=
0x06
)
{
throw
new
IllegalArgumentException
(
"zip data already has an archive comment"
);
}
int
total_size
=
temp
.
size
()
+
6
;
if
(
total_size
>
0xffff
)
{
throw
new
IllegalArgumentException
(
"signature is too big for ZIP file comment"
);
}
// signature starts this many bytes from the end of the file
int
signature_start
=
total_size
-
message
.
length
-
1
;
temp
.
write
(
signature_start
&
0xff
);
temp
.
write
((
signature_start
>>
8
)
&
0xff
);
// Why the 0xff bytes? In a zip file with no archive comment,
// bytes [-6:-2] of the file are the little-endian offset from
// the start of the file to the central directory. So for the
// two high bytes to be 0xff 0xff, the archive would have to
// be nearly 4GB in size. So it's unlikely that a real
// commentless archive would have 0xffs here, and lets us tell
// an old signed archive from a new one.
temp
.
write
(
0xff
);
temp
.
write
(
0xff
);
temp
.
write
(
total_size
&
0xff
);
temp
.
write
((
total_size
>>
8
)
&
0xff
);
temp
.
flush
();
// Signature verification checks that the EOCD header is the
// last such sequence in the file (to avoid minzip finding a
// fake EOCD appended after the signature in its scan). The
// odds of producing this sequence by chance are very low, but
// let's catch it here if it does.
byte
[]
b
=
temp
.
toByteArray
();
for
(
int
i
=
0
;
i
<
b
.
length
-
3
;
++
i
)
{
if
(
b
[
i
]
==
0x50
&&
b
[
i
+
1
]
==
0x4b
&&
b
[
i
+
2
]
==
0x05
&&
b
[
i
+
3
]
==
0x06
)
{
throw
new
IllegalArgumentException
(
"found spurious EOCD header at "
+
i
);
}
}
cmsFile
.
write
(
outputStream
);
outputStream
.
write
(
total_size
&
0xff
);
outputStream
.
write
((
total_size
>>
8
)
&
0xff
);
temp
.
writeTo
(
outputStream
);
}
private
static
void
signFile
(
Manifest
manifest
,
JarFile
inputJar
,
X509Certificate
publicKey
,
PrivateKey
privateKey
,
JarOutputStream
outputJar
)
throws
Exception
{
// Assume the certificate is valid for at least an hour.
long
timestamp
=
publicKey
.
getNotBefore
().
getTime
()
+
3600L
*
1000
;
// MANIFEST.MF
JarEntry
je
=
new
JarEntry
(
JarFile
.
MANIFEST_NAME
);
je
.
setTime
(
timestamp
);
outputJar
.
putNextEntry
(
je
);
manifest
.
write
(
outputJar
);
je
=
new
JarEntry
(
CERT_SF_NAME
);
je
.
setTime
(
timestamp
);
outputJar
.
putNextEntry
(
je
);
ByteArrayOutputStream
baos
=
new
ByteArrayOutputStream
();
writeSignatureFile
(
manifest
,
baos
,
getDigestAlgorithm
(
publicKey
));
byte
[]
signedData
=
baos
.
toByteArray
();
outputJar
.
write
(
signedData
);
// CERT.{EC,RSA} / CERT#.{EC,RSA}
final
String
keyType
=
publicKey
.
getPublicKey
().
getAlgorithm
();
je
=
new
JarEntry
(
String
.
format
(
CERT_SIG_NAME
,
keyType
));
je
.
setTime
(
timestamp
);
outputJar
.
putNextEntry
(
je
);
writeSignatureBlock
(
new
CMSProcessableByteArray
(
signedData
),
publicKey
,
privateKey
,
outputJar
);
}
public
static
void
main
(
String
[]
args
)
{
boolean
minSign
=
false
;
int
argStart
=
0
;
if
(
args
.
length
<
4
)
{
System
.
err
.
println
(
"Usage: zipsigner [-m] publickey.x509[.pem] privatekey.pk8 input.jar output.jar"
);
System
.
exit
(
2
);
}
if
(
args
[
0
].
equals
(
"-m"
))
{
minSign
=
true
;
argStart
=
1
;
}
sBouncyCastleProvider
=
new
BouncyCastleProvider
();
Security
.
insertProviderAt
(
sBouncyCastleProvider
,
1
);
File
pubKey
=
new
File
(
args
[
argStart
]);
File
privKey
=
new
File
(
args
[
argStart
+
1
]);
File
input
=
new
File
(
args
[
argStart
+
2
]);
File
output
=
new
File
(
args
[
argStart
+
3
]);
int
alignment
=
4
;
JarFile
inputJar
=
null
;
FileOutputStream
outputFile
=
null
;
int
hashes
=
0
;
try
{
X509Certificate
publicKey
=
readPublicKey
(
new
FileInputStream
(
pubKey
));
hashes
|=
getDigestAlgorithm
(
publicKey
);
// Set the ZIP file timestamp to the starting valid time
// of the 0th certificate plus one hour (to match what
// we've historically done).
long
timestamp
=
publicKey
.
getNotBefore
().
getTime
()
+
3600L
*
1000
;
PrivateKey
privateKey
=
readPrivateKey
(
new
FileInputStream
(
privKey
));
outputFile
=
new
FileOutputStream
(
output
);
if
(
minSign
)
{
ZipSigner
.
signWholeFile
(
input
,
publicKey
,
privateKey
,
outputFile
);
}
else
{
inputJar
=
new
JarFile
(
input
,
false
);
// Don't verify.
JarOutputStream
outputJar
=
new
JarOutputStream
(
outputFile
);
// For signing .apks, use the maximum compression to make
// them as small as possible (since they live forever on
// the system partition). For OTA packages, use the
// default compression level, which is much much faster
// and produces output that is only a tiny bit larger
// (~0.1% on full OTA packages I tested).
outputJar
.
setLevel
(
9
);
Manifest
manifest
=
addDigestsToManifest
(
inputJar
,
hashes
);
copyFiles
(
manifest
,
inputJar
,
outputJar
,
timestamp
,
alignment
);
signFile
(
manifest
,
inputJar
,
publicKey
,
privateKey
,
outputJar
);
outputJar
.
close
();
}
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
System
.
exit
(
1
);
}
finally
{
try
{
if
(
inputJar
!=
null
)
inputJar
.
close
();
if
(
outputFile
!=
null
)
outputFile
.
close
();
}
catch
(
IOException
e
)
{
e
.
printStackTrace
();
System
.
exit
(
1
);
}
}
}
}
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment