extensions;
79 | }
80 |
--------------------------------------------------------------------------------
/library/src/main/java/com/android/apksig/internal/x509/Time.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 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 com.android.apksig.internal.x509;
18 |
19 | import com.android.apksig.internal.asn1.Asn1Class;
20 | import com.android.apksig.internal.asn1.Asn1Field;
21 | import com.android.apksig.internal.asn1.Asn1Type;
22 |
23 | /**
24 | * {@code Time} as specified in RFC 5280.
25 | */
26 | @Asn1Class(type = Asn1Type.CHOICE)
27 | public class Time {
28 |
29 | @Asn1Field(type = Asn1Type.UTC_TIME)
30 | public String utcTime;
31 |
32 | @Asn1Field(type = Asn1Type.GENERALIZED_TIME)
33 | public String generalizedTime;
34 | }
35 |
--------------------------------------------------------------------------------
/library/src/main/java/com/android/apksig/internal/x509/Validity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 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 com.android.apksig.internal.x509;
18 |
19 | import com.android.apksig.internal.asn1.Asn1Class;
20 | import com.android.apksig.internal.asn1.Asn1Field;
21 | import com.android.apksig.internal.asn1.Asn1Type;
22 |
23 | /**
24 | * {@code Validity} as specified in RFC 5280.
25 | */
26 | @Asn1Class(type = Asn1Type.SEQUENCE)
27 | public class Validity {
28 |
29 | @Asn1Field(index = 0, type = Asn1Type.CHOICE)
30 | public Time notBefore;
31 |
32 | @Asn1Field(index = 1, type = Asn1Type.CHOICE)
33 | public Time notAfter;
34 | }
35 |
--------------------------------------------------------------------------------
/library/src/main/java/com/android/apksig/internal/zip/EocdRecord.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 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 com.android.apksig.internal.zip;
18 |
19 | import java.nio.ByteBuffer;
20 | import java.nio.ByteOrder;
21 |
22 | /**
23 | * ZIP End of Central Directory record.
24 | */
25 | public class EocdRecord {
26 | private static final int CD_RECORD_COUNT_ON_DISK_OFFSET = 8;
27 | private static final int CD_RECORD_COUNT_TOTAL_OFFSET = 10;
28 | private static final int CD_SIZE_OFFSET = 12;
29 | private static final int CD_OFFSET_OFFSET = 16;
30 |
31 | public static ByteBuffer createWithModifiedCentralDirectoryInfo(
32 | ByteBuffer original,
33 | int centralDirectoryRecordCount,
34 | long centralDirectorySizeBytes,
35 | long centralDirectoryOffset) {
36 | ByteBuffer result = ByteBuffer.allocate(original.remaining());
37 | result.order(ByteOrder.LITTLE_ENDIAN);
38 | result.put(original.slice());
39 | result.flip();
40 | ZipUtils.setUnsignedInt16(
41 | result, CD_RECORD_COUNT_ON_DISK_OFFSET, centralDirectoryRecordCount);
42 | ZipUtils.setUnsignedInt16(
43 | result, CD_RECORD_COUNT_TOTAL_OFFSET, centralDirectoryRecordCount);
44 | ZipUtils.setUnsignedInt32(result, CD_SIZE_OFFSET, centralDirectorySizeBytes);
45 | ZipUtils.setUnsignedInt32(result, CD_OFFSET_OFFSET, centralDirectoryOffset);
46 | return result;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/library/src/main/java/com/android/apksig/util/DataSink.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 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 com.android.apksig.util;
18 |
19 | import java.io.IOException;
20 | import java.nio.ByteBuffer;
21 |
22 | /**
23 | * Consumer of input data which may be provided in one go or in chunks.
24 | */
25 | public interface DataSink {
26 |
27 | /**
28 | * Consumes the provided chunk of data.
29 | *
30 | * This data sink guarantees to not hold references to the provided buffer after this method
31 | * terminates.
32 | *
33 | * @throws IndexOutOfBoundsException if {@code offset} or {@code length} are negative, or if
34 | * {@code offset + length} is greater than {@code buf.length}.
35 | */
36 | void consume(byte[] buf, int offset, int length) throws IOException;
37 |
38 | /**
39 | * Consumes all remaining data in the provided buffer and advances the buffer's position
40 | * to the buffer's limit.
41 | *
42 | *
This data sink guarantees to not hold references to the provided buffer after this method
43 | * terminates.
44 | */
45 | void consume(ByteBuffer buf) throws IOException;
46 | }
47 |
--------------------------------------------------------------------------------
/library/src/main/java/com/android/apksig/util/DataSinks.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 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 com.android.apksig.util;
18 |
19 | import com.android.apksig.internal.util.ByteArrayDataSink;
20 | import com.android.apksig.internal.util.MessageDigestSink;
21 | import com.android.apksig.internal.util.OutputStreamDataSink;
22 | import com.android.apksig.internal.util.RandomAccessFileDataSink;
23 |
24 | import java.io.OutputStream;
25 | import java.io.RandomAccessFile;
26 | import java.security.MessageDigest;
27 |
28 | /**
29 | * Utility methods for working with {@link DataSink} abstraction.
30 | */
31 | public abstract class DataSinks {
32 | private DataSinks() {
33 | }
34 |
35 | /**
36 | * Returns a {@link DataSink} which outputs received data into the provided
37 | * {@link OutputStream}.
38 | */
39 | public static DataSink asDataSink(OutputStream out) {
40 | return new OutputStreamDataSink(out);
41 | }
42 |
43 | /**
44 | * Returns a {@link DataSink} which outputs received data into the provided file, sequentially,
45 | * starting at the beginning of the file.
46 | */
47 | public static DataSink asDataSink(RandomAccessFile file) {
48 | return new RandomAccessFileDataSink(file);
49 | }
50 |
51 | /**
52 | * Returns a {@link DataSink} which forwards data into the provided {@link MessageDigest}
53 | * instances via their {@code update} method. Each {@code MessageDigest} instance receives the
54 | * same data.
55 | */
56 | public static DataSink asDataSink(MessageDigest... digests) {
57 | return new MessageDigestSink(digests);
58 | }
59 |
60 | /**
61 | * Returns a new in-memory {@link DataSink} which exposes all data consumed so far via the
62 | * {@link DataSource} interface.
63 | */
64 | public static ReadableDataSink newInMemoryDataSink() {
65 | return new ByteArrayDataSink();
66 | }
67 |
68 | /**
69 | * Returns a new in-memory {@link DataSink} which exposes all data consumed so far via the
70 | * {@link DataSource} interface.
71 | *
72 | * @param initialCapacity initial capacity in bytes
73 | */
74 | public static ReadableDataSink newInMemoryDataSink(int initialCapacity) {
75 | return new ByteArrayDataSink(initialCapacity);
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/library/src/main/java/com/android/apksig/util/DataSource.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 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 com.android.apksig.util;
18 |
19 | import java.io.IOException;
20 | import java.nio.ByteBuffer;
21 |
22 | /**
23 | * Abstract representation of a source of data.
24 | *
25 | *
This abstraction serves three purposes:
26 | *
27 | * - Transparent handling of different types of sources, such as {@code byte[]},
28 | * {@link java.nio.ByteBuffer}, {@link java.io.RandomAccessFile}, memory-mapped file.
29 | * - Support sources larger than 2 GB. If all sources were smaller than 2 GB, {@code ByteBuffer}
30 | * may have worked as the unifying abstraction.
31 | * - Support sources which do not fit into logical memory as a contiguous region.
32 | *
33 | *
34 | * There are following ways to obtain a chunk of data from the data source:
35 | *
36 | * - Stream the chunk's data into a {@link DataSink} using
37 | * {@link #feed(long, long, DataSink) feed}. This is best suited for scenarios where there is no
38 | * need to have the chunk's data accessible at the same time, for example, when computing the
39 | * digest of the chunk. If you need to keep the chunk's data around after {@code feed}
40 | * completes, you must create a copy during {@code feed}. However, in that case the following
41 | * methods of obtaining the chunk's data may be more appropriate.
42 | * - Obtain a {@link ByteBuffer} containing the chunk's data using
43 | * {@link #getByteBuffer(long, int) getByteBuffer}. Depending on the data source, the chunk's
44 | * data may or may not be copied by this operation. This is best suited for scenarios where
45 | * you need to access the chunk's data in arbitrary order, but don't need to modify the data and
46 | * thus don't require a copy of the data.
47 | * - Copy the chunk's data to a {@link ByteBuffer} using
48 | * {@link #copyTo(long, int, ByteBuffer) copyTo}. This is best suited for scenarios where
49 | * you require a copy of the chunk's data, such as to when you need to modify the data.
50 | *
51 | *
52 | */
53 | public interface DataSource {
54 |
55 | /**
56 | * Returns the amount of data (in bytes) contained in this data source.
57 | */
58 | long size();
59 |
60 | /**
61 | * Feeds the specified chunk from this data source into the provided sink.
62 | *
63 | * @param offset index (in bytes) at which the chunk starts inside data source
64 | * @param size size (in bytes) of the chunk
65 | * @throws IndexOutOfBoundsException if {@code offset} or {@code size} is negative, or if
66 | * {@code offset + size} is greater than {@link #size()}.
67 | */
68 | void feed(long offset, long size, DataSink sink) throws IOException;
69 |
70 | /**
71 | * Returns a buffer holding the contents of the specified chunk of data from this data source.
72 | * Changes to the data source are not guaranteed to be reflected in the returned buffer.
73 | * Similarly, changes in the buffer are not guaranteed to be reflected in the data source.
74 | *
75 | * The returned buffer's position is {@code 0}, and the buffer's limit and capacity is
76 | * {@code size}.
77 | *
78 | * @param offset index (in bytes) at which the chunk starts inside data source
79 | * @param size size (in bytes) of the chunk
80 | * @throws IndexOutOfBoundsException if {@code offset} or {@code size} is negative, or if
81 | * {@code offset + size} is greater than {@link #size()}.
82 | */
83 | ByteBuffer getByteBuffer(long offset, int size) throws IOException;
84 |
85 | /**
86 | * Copies the specified chunk from this data source into the provided destination buffer,
87 | * advancing the destination buffer's position by {@code size}.
88 | *
89 | * @param offset index (in bytes) at which the chunk starts inside data source
90 | * @param size size (in bytes) of the chunk
91 | * @throws IndexOutOfBoundsException if {@code offset} or {@code size} is negative, or if
92 | * {@code offset + size} is greater than {@link #size()}.
93 | */
94 | void copyTo(long offset, int size, ByteBuffer dest) throws IOException;
95 |
96 | /**
97 | * Returns a data source representing the specified region of data of this data source. Changes
98 | * to data represented by this data source will also be visible in the returned data source.
99 | *
100 | * @param offset index (in bytes) at which the region starts inside data source
101 | * @param size size (in bytes) of the region
102 | * @throws IndexOutOfBoundsException if {@code offset} or {@code size} is negative, or if
103 | * {@code offset + size} is greater than {@link #size()}.
104 | */
105 | DataSource slice(long offset, long size);
106 | }
107 |
--------------------------------------------------------------------------------
/library/src/main/java/com/android/apksig/util/DataSources.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 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 com.android.apksig.util;
18 |
19 | import com.android.apksig.internal.util.ByteBufferDataSource;
20 | import com.android.apksig.internal.util.FileChannelDataSource;
21 |
22 | import java.io.RandomAccessFile;
23 | import java.nio.ByteBuffer;
24 | import java.nio.channels.FileChannel;
25 |
26 | /**
27 | * Utility methods for working with {@link DataSource} abstraction.
28 | */
29 | public abstract class DataSources {
30 | private DataSources() {
31 | }
32 |
33 | /**
34 | * Returns a {@link DataSource} backed by the provided {@link ByteBuffer}. The data source
35 | * represents the data contained between the position and limit of the buffer. Changes to the
36 | * buffer's contents will be visible in the data source.
37 | */
38 | public static DataSource asDataSource(ByteBuffer buffer) {
39 | if (buffer == null) {
40 | throw new NullPointerException();
41 | }
42 | return new ByteBufferDataSource(buffer);
43 | }
44 |
45 | /**
46 | * Returns a {@link DataSource} backed by the provided {@link RandomAccessFile}. Changes to the
47 | * file, including changes to size of file, will be visible in the data source.
48 | */
49 | public static DataSource asDataSource(RandomAccessFile file) {
50 | return asDataSource(file.getChannel());
51 | }
52 |
53 | /**
54 | * Returns a {@link DataSource} backed by the provided region of the {@link RandomAccessFile}.
55 | * Changes to the file will be visible in the data source.
56 | */
57 | public static DataSource asDataSource(RandomAccessFile file, long offset, long size) {
58 | return asDataSource(file.getChannel(), offset, size);
59 | }
60 |
61 | /**
62 | * Returns a {@link DataSource} backed by the provided {@link FileChannel}. Changes to the
63 | * file, including changes to size of file, will be visible in the data source.
64 | */
65 | public static DataSource asDataSource(FileChannel channel) {
66 | if (channel == null) {
67 | throw new NullPointerException();
68 | }
69 | return new FileChannelDataSource(channel);
70 | }
71 |
72 | /**
73 | * Returns a {@link DataSource} backed by the provided region of the {@link FileChannel}.
74 | * Changes to the file will be visible in the data source.
75 | */
76 | public static DataSource asDataSource(FileChannel channel, long offset, long size) {
77 | if (channel == null) {
78 | throw new NullPointerException();
79 | }
80 | return new FileChannelDataSource(channel, offset, size);
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/library/src/main/java/com/android/apksig/util/ReadableDataSink.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 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 com.android.apksig.util;
18 |
19 | /**
20 | * {@link DataSink} which exposes all data consumed so far as a {@link DataSource}. This abstraction
21 | * offers append-only write access and random read access.
22 | */
23 | public interface ReadableDataSink extends DataSink, DataSource {
24 | }
25 |
--------------------------------------------------------------------------------
/library/src/main/java/com/android/apksig/util/RunnablesExecutor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2019 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 com.android.apksig.util;
18 |
19 | import static java.util.concurrent.TimeUnit.MILLISECONDS;
20 |
21 | import java.util.concurrent.ArrayBlockingQueue;
22 | import java.util.concurrent.ExecutorService;
23 | import java.util.concurrent.Phaser;
24 | import java.util.concurrent.ThreadPoolExecutor;
25 |
26 | public interface RunnablesExecutor {
27 | static final RunnablesExecutor SINGLE_THREADED = p -> p.createRunnable().run();
28 |
29 | static final RunnablesExecutor MULTI_THREADED = new RunnablesExecutor() {
30 | private final int PARALLELISM = Math.min(32, Runtime.getRuntime().availableProcessors());
31 | private final int QUEUE_SIZE = 4;
32 |
33 | @Override
34 | public void execute(RunnablesProvider provider) {
35 | final ExecutorService mExecutor =
36 | new ThreadPoolExecutor(PARALLELISM, PARALLELISM,
37 | 0L, MILLISECONDS,
38 | new ArrayBlockingQueue<>(QUEUE_SIZE),
39 | new ThreadPoolExecutor.CallerRunsPolicy());
40 |
41 | Phaser tasks = new Phaser(1);
42 |
43 | for (int i = 0; i < PARALLELISM; ++i) {
44 | Runnable task = () -> {
45 | Runnable r = provider.createRunnable();
46 | r.run();
47 | tasks.arriveAndDeregister();
48 | };
49 | tasks.register();
50 | mExecutor.execute(task);
51 | }
52 |
53 | // Waiting for the tasks to complete.
54 | tasks.arriveAndAwaitAdvance();
55 |
56 | mExecutor.shutdownNow();
57 | }
58 | };
59 |
60 | void execute(RunnablesProvider provider);
61 | }
62 |
--------------------------------------------------------------------------------
/library/src/main/java/com/android/apksig/util/RunnablesProvider.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2019 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 com.android.apksig.util;
18 |
19 | public interface RunnablesProvider {
20 | Runnable createRunnable();
21 | }
22 |
--------------------------------------------------------------------------------
/library/src/main/java/com/android/apksig/zip/ZipFormatException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 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 com.android.apksig.zip;
18 |
19 | /**
20 | * Indicates that a ZIP archive is not well-formed.
21 | */
22 | public class ZipFormatException extends Exception {
23 | private static final long serialVersionUID = 1L;
24 |
25 | public ZipFormatException(String message) {
26 | super(message);
27 | }
28 |
29 | public ZipFormatException(String message, Throwable cause) {
30 | super(message, cause);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/library/src/main/java/com/android/apksigner/HexEncoding.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 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 com.android.apksigner;
18 |
19 | import java.nio.ByteBuffer;
20 |
21 | /**
22 | * Hexadecimal encoding where each byte is represented by two hexadecimal digits.
23 | */
24 | class HexEncoding {
25 |
26 | private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray();
27 |
28 | /**
29 | * Hidden constructor to prevent instantiation.
30 | */
31 | private HexEncoding() {
32 | }
33 |
34 | /**
35 | * Encodes the provided data as a hexadecimal string.
36 | */
37 | public static String encode(byte[] data, int offset, int length) {
38 | StringBuilder result = new StringBuilder(length * 2);
39 | for (int i = 0; i < length; i++) {
40 | byte b = data[offset + i];
41 | result.append(HEX_DIGITS[(b >>> 4) & 0x0f]);
42 | result.append(HEX_DIGITS[b & 0x0f]);
43 | }
44 | return result.toString();
45 | }
46 |
47 | /**
48 | * Encodes the provided data as a hexadecimal string.
49 | */
50 | public static String encode(byte[] data) {
51 | return encode(data, 0, data.length);
52 | }
53 |
54 | /**
55 | * Encodes the remaining bytes of the provided {@link ByteBuffer} as a hexadecimal string.
56 | */
57 | public static String encodeRemaining(ByteBuffer data) {
58 | return encode(data.array(), data.arrayOffset() + data.position(), data.remaining());
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/library/src/main/java/com/android/apksigner/ParameterException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2019 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 com.android.apksigner;
18 |
19 | /**
20 | * Indicates that there is an issue with command-line parameters provided to {@link ApkSignerTool}.
21 | */
22 | public class ParameterException extends Exception {
23 | private static final long serialVersionUID = 1L;
24 |
25 | ParameterException(String message) {
26 | super(message);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/library/src/main/java/com/android/apksigner/help.txt:
--------------------------------------------------------------------------------
1 | USAGE: apksigner [options]
2 | apksigner --version
3 | apksigner --help
4 |
5 | EXAMPLE:
6 | apksigner sign --ks release.jks app.apk
7 | apksigner verify --verbose app.apk
8 |
9 | apksigner is a tool for signing Android APK files and for checking whether
10 | signatures of APK files will verify on Android devices.
11 |
12 |
13 | COMMANDS
14 | rotate Add a new signing certificate to the SigningCertificateLineage
15 |
16 | sign Sign the provided APK
17 |
18 | verify Check whether the provided APK is expected to verify on
19 | Android
20 |
21 | lineage Modify the capabilities of one or more signers in an existing
22 | SigningCertificateLineage
23 |
24 | version Show this tool's version number and exit
25 |
26 | help Show this usage page and exit
27 |
28 |
--------------------------------------------------------------------------------
/library/src/main/java/com/android/apksigner/help_verify.txt:
--------------------------------------------------------------------------------
1 | USAGE: apksigner verify [options] apk
2 |
3 | This checks whether the provided APK will verify on Android. By default, this
4 | checks whether the APK will verify on all Android platform versions supported
5 | by the APK (as declared using minSdkVersion in AndroidManifest.xml). Use
6 | --min-sdk-version and/or --max-sdk-version to verify the APK against a custom
7 | range of API Levels.
8 |
9 |
10 | OPTIONS
11 |
12 | --print-certs Show information about the APK's signing certificates
13 |
14 | -v, --verbose Verbose output mode
15 |
16 | --min-sdk-version Lowest API Level on which this APK's signatures will be
17 | verified. By default, the value from AndroidManifest.xml
18 | is used.
19 |
20 | --max-sdk-version Highest API Level on which this APK's signatures will be
21 | verified. By default, the highest possible value is used.
22 |
23 | -Werr Treat warnings as errors
24 |
25 | --in APK file to verify. This is an alternative to specifying
26 | the APK as the very last parameter, after all options.
27 |
28 | -h, --help Show help about this command and exit
29 |
30 |
31 | EXAMPLES
32 |
33 | 1. Check whether the APK's signatures are expected to verify on all Android
34 | platforms declared as supported by this APK:
35 | $ apksigner verify app.apk
36 |
37 | 2. Check whether the APK's signatures are expected to verify on Android
38 | platforms with API Level 15 and higher:
39 | $ apksigner verify --min-sdk-version 15 app.apk
40 |
--------------------------------------------------------------------------------
/library/src/main/java/com/android/apksigner/utils/FileUtils.java:
--------------------------------------------------------------------------------
1 | package com.android.apksigner.utils;
2 |
3 | import java.io.File;
4 |
5 | public class FileUtils {
6 | public static boolean moveFile(File sourcePath, File targetPath) {
7 | return sourcePath.renameTo(targetPath);
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/library/src/main/java/com/mcal/apksigner/ApkSigner.kt:
--------------------------------------------------------------------------------
1 | package com.mcal.apksigner
2 |
3 | import com.android.apksig.ApkSigner
4 | import com.android.apksigner.ApkSignerTool
5 | import com.mcal.apksigner.utils.KeyStoreHelper
6 | import java.io.File
7 | import java.io.InputStream
8 | import java.security.PrivateKey
9 | import java.security.cert.X509Certificate
10 |
11 | class ApkSigner(
12 | private val unsignedApkFile: File,
13 | private val signedApkFile: File,
14 | ) {
15 | var useDefaultSignatureVersion = true
16 | var v1SigningEnabled = true
17 | var v2SigningEnabled = true
18 | var v3SigningEnabled = true
19 | var v4SigningEnabled = false
20 |
21 | fun signRelease(
22 | pk8File: File,
23 | x509File: File
24 | ): Boolean {
25 | val args = mutableListOf(
26 | "sign",
27 | "--in",
28 | unsignedApkFile.path,
29 | "--out",
30 | signedApkFile.path,
31 | "--key",
32 | pk8File.path,
33 | "--cert",
34 | x509File.path,
35 | )
36 | if (!useDefaultSignatureVersion) {
37 | args.add("--v1-signing-enabled")
38 | args.add(v1SigningEnabled.toString())
39 | args.add("--v2-signing-enabled")
40 | args.add(v2SigningEnabled.toString())
41 | args.add("--v3-signing-enabled")
42 | args.add(v3SigningEnabled.toString())
43 | args.add("--v4-signing-enabled")
44 | args.add(v4SigningEnabled.toString())
45 | }
46 | return try {
47 | ApkSignerTool.main(args.toTypedArray())
48 | true
49 | } catch (e: Exception) {
50 | e.printStackTrace()
51 | false
52 | }
53 | }
54 |
55 | fun signRelease(
56 | keyFile: File,
57 | password: String,
58 | alias: String,
59 | aliasPassword: String,
60 | ): Boolean {
61 | return try {
62 | val keystore = KeyStoreHelper.loadKeyStore(keyFile, password.toCharArray())
63 | ApkSigner.Builder(
64 | listOf(
65 | ApkSigner.SignerConfig.Builder(
66 | "CERT",
67 | keystore.getKey(alias, aliasPassword.toCharArray()) as PrivateKey,
68 | listOf(keystore.getCertificate(alias) as X509Certificate)
69 | ).build()
70 | )
71 | ).apply {
72 | setInputApk(unsignedApkFile)
73 | setOutputApk(signedApkFile)
74 | if (!useDefaultSignatureVersion) {
75 | setV1SigningEnabled(v1SigningEnabled)
76 | setV2SigningEnabled(v2SigningEnabled)
77 | setV3SigningEnabled(v3SigningEnabled)
78 | setV4SigningEnabled(v4SigningEnabled)
79 | }
80 | }.build().sign()
81 | true
82 | } catch (e: Exception) {
83 | e.printStackTrace()
84 | false
85 | }
86 | }
87 |
88 | fun signDebug(): Boolean {
89 | val pk8File = File.createTempFile("testkey", "pk8").also {
90 | it.writeBytes((com.mcal.apksigner.ApkSigner::class.java.getResourceAsStream("/keystore/testkey.pk8") as InputStream).readBytes())
91 | }
92 |
93 | val x509File = File.createTempFile("testkey", "x509.pem").also {
94 | it.writeBytes((com.mcal.apksigner.ApkSigner::class.java.getResourceAsStream("/keystore/testkey.x509.pem") as InputStream).readBytes())
95 | }
96 |
97 | val args = mutableListOf(
98 | "sign",
99 | "--in",
100 | unsignedApkFile.path,
101 | "--out",
102 | signedApkFile.path,
103 | "--key",
104 | pk8File.path,
105 | "--cert",
106 | x509File.path,
107 | )
108 | if (!useDefaultSignatureVersion) {
109 | args.add("--v1-signing-enabled")
110 | args.add(v1SigningEnabled.toString())
111 | args.add("--v2-signing-enabled")
112 | args.add(v2SigningEnabled.toString())
113 | args.add("--v3-signing-enabled")
114 | args.add(v3SigningEnabled.toString())
115 | args.add("--v4-signing-enabled")
116 | args.add(v4SigningEnabled.toString())
117 | }
118 | return try {
119 | ApkSignerTool.main(args.toTypedArray())
120 | pk8File.delete()
121 | x509File.delete()
122 | true
123 | } catch (e: Exception) {
124 | e.printStackTrace()
125 | pk8File.delete()
126 | x509File.delete()
127 | false
128 | }
129 | }
130 |
131 | fun validateKeystorePassword(keyFile: File, password: String): Boolean {
132 | return KeyStoreHelper.validateKeystorePassword(keyFile, password)
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/library/src/main/java/com/mcal/apksigner/CertCreator.kt:
--------------------------------------------------------------------------------
1 | package com.mcal.apksigner
2 |
3 | import com.mcal.apksigner.utils.DistinguishedNameValues
4 | import com.mcal.apksigner.utils.KeySet
5 | import com.mcal.apksigner.utils.KeyStoreHelper
6 | import org.spongycastle.x509.X509V3CertificateGenerator
7 | import java.io.File
8 | import java.io.IOException
9 | import java.math.BigInteger
10 | import java.security.KeyPairGenerator
11 | import java.security.SecureRandom
12 | import java.security.cert.Certificate
13 | import java.util.Date
14 |
15 | object CertCreator {
16 | /**
17 | * Creates a new keystore and self-signed key. The key will have the same password as the key, and will be
18 | * RSA 2048, with the cert signed using SHA1withRSA. The certificate will have a validity of
19 | * 30 years).
20 | *
21 | * @param keyFile - new keystore file
22 | * @param password - keystore and key password
23 | * @param keyName - the new key will have this as its alias within the keystore
24 | * @param distinguishedNameValues - contains Country, State, Locality,...,Common Name, etc.
25 | */
26 | @JvmStatic
27 | fun createKeystoreAndKey(
28 | keyFile: File,
29 | password: CharArray,
30 | keyName: String,
31 | distinguishedNameValues: DistinguishedNameValues
32 | ) {
33 | createKeystoreAndKey(
34 | keyFile, password, "RSA", 2048, keyName, password,
35 | "SHA1withRSA", 30, distinguishedNameValues
36 | )
37 | }
38 |
39 | @JvmStatic
40 | fun createKeystoreAndKey(
41 | keyFile: File,
42 | storePass: CharArray,
43 | keyAlgorithm: String,
44 | keySize: Int,
45 | keyName: String,
46 | keyPass: CharArray,
47 | certSignatureAlgorithm: String,
48 | certValidityYears: Int,
49 | distinguishedNameValues: DistinguishedNameValues
50 | ): KeySet {
51 | return try {
52 | val keySet = createKey(
53 | keyAlgorithm, keySize, keyName, certSignatureAlgorithm, certValidityYears,
54 | distinguishedNameValues
55 | )
56 | val privateKS = KeyStoreHelper.createKeyStore(keyFile, storePass)
57 | privateKS.setKeyEntry(
58 | keyName, keySet.privateKey,
59 | keyPass, arrayOf(keySet.publicKey)
60 | )
61 | if (keyFile.exists()) {
62 | throw IOException("File already exists: $keyFile")
63 | }
64 | KeyStoreHelper.writeKeyStore(privateKS, keyFile, storePass)
65 | keySet
66 | } catch (x: RuntimeException) {
67 | throw x
68 | } catch (x: Exception) {
69 | throw RuntimeException(x.message, x)
70 | }
71 | }
72 |
73 | /**
74 | * Create a new key and store it in an existing keystore.
75 | */
76 | @JvmStatic
77 | fun createKey(
78 | keyFile: File,
79 | storePass: CharArray,
80 | keyAlgorithm: String,
81 | keySize: Int,
82 | keyName: String,
83 | keyPass: CharArray,
84 | certSignatureAlgorithm: String,
85 | certValidityYears: Int,
86 | distinguishedNameValues: DistinguishedNameValues
87 | ): KeySet {
88 | return try {
89 | val keySet = createKey(
90 | keyAlgorithm, keySize, keyName, certSignatureAlgorithm, certValidityYears,
91 | distinguishedNameValues
92 | )
93 | val privateKS = KeyStoreHelper.createKeyStore(keyFile, storePass)
94 | privateKS.setKeyEntry(
95 | keyName, keySet.privateKey,
96 | keyPass, arrayOf(keySet.publicKey)
97 | )
98 | KeyStoreHelper.writeKeyStore(privateKS, keyFile, storePass)
99 | keySet
100 | } catch (x: RuntimeException) {
101 | throw x
102 | } catch (x: Exception) {
103 | throw RuntimeException(x.message, x)
104 | }
105 | }
106 |
107 | @JvmStatic
108 | fun createKey(
109 | keyAlgorithm: String,
110 | keySize: Int,
111 | keyName: String,
112 | certSignatureAlgorithm: String,
113 | certValidityYears: Int,
114 | distinguishedNameValues: DistinguishedNameValues
115 | ): KeySet {
116 | return try {
117 | val keyPairGenerator = KeyPairGenerator.getInstance(keyAlgorithm)
118 | keyPairGenerator.initialize(keySize)
119 | val keyPair = keyPairGenerator.generateKeyPair()
120 | val v3CertGen = X509V3CertificateGenerator()
121 | val principal = distinguishedNameValues.principal
122 |
123 | // generate a postitive serial number
124 | var serialNumber = BigInteger.valueOf(SecureRandom().nextInt().toLong())
125 | while (serialNumber < BigInteger.ZERO) {
126 | serialNumber = BigInteger.valueOf(SecureRandom().nextInt().toLong())
127 | }
128 | v3CertGen.setSerialNumber(serialNumber)
129 | v3CertGen.setIssuerDN(principal)
130 | v3CertGen.setNotBefore(Date(System.currentTimeMillis() - 1000L * 60L * 60L * 24L * 30L))
131 | v3CertGen.setNotAfter(Date(System.currentTimeMillis() + 1000L * 60L * 60L * 24L * 366L * certValidityYears.toLong()))
132 | v3CertGen.setSubjectDN(principal)
133 | v3CertGen.setPublicKey(keyPair.public)
134 | v3CertGen.setSignatureAlgorithm(certSignatureAlgorithm)
135 | val certificate =
136 | v3CertGen.generate(keyPair.private/*, "BC" */)
137 | KeySet().apply {
138 | name = keyName
139 | privateKey = keyPair.private
140 | publicKey = certificate
141 | }
142 | } catch (x: Exception) {
143 | throw RuntimeException(x.message, x)
144 | }
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/library/src/main/java/com/mcal/apksigner/utils/Base64.kt:
--------------------------------------------------------------------------------
1 | package com.mcal.apksigner.utils
2 |
3 | object Base64 {
4 | @JvmStatic
5 | fun encode(raw: ByteArray): String {
6 | val encoded = StringBuffer()
7 | var i = 0
8 | while (i < raw.size) {
9 | encoded.append(encodeBlock(raw, i))
10 | i += 3
11 | }
12 | return encoded.toString()
13 | }
14 |
15 | @JvmStatic
16 | fun decode(base64: String): ByteArray {
17 | var pad = 0
18 | var i = base64.length - 1
19 | while (base64[i] == '=') {
20 | pad++
21 | i--
22 | }
23 | val length = base64.length * 6 / 8 - pad
24 | val raw = ByteArray(length)
25 | var rawIndex = 0
26 | i = 0
27 | while (i < base64.length) {
28 | val block = ((getValue(base64[i]) shl 18)
29 | + (getValue(base64[i + 1]) shl 12)
30 | + (getValue(base64[i + 2]) shl 6)
31 | + getValue(base64[i + 3]))
32 | var j = 0
33 | while (j < 3 && rawIndex + j < raw.size) {
34 | raw[rawIndex + j] = (block shr 8 * (2 - j) and 0xff).toByte()
35 | j++
36 | }
37 | rawIndex += 3
38 | i += 4
39 | }
40 | return raw
41 | }
42 |
43 | private fun encodeBlock(raw: ByteArray, offset: Int): CharArray {
44 | var block = 0
45 | val slack = raw.size - offset - 1
46 | val end = if (slack >= 2) {
47 | 2
48 | } else {
49 | slack
50 | }
51 | for (i in 0..end) {
52 | val b = raw[offset + i]
53 | val neuter = if (b < 0) {
54 | b + 256
55 | } else {
56 | b.toInt()
57 | }
58 | block += neuter shl 8 * (2 - i)
59 | }
60 | val base64 = CharArray(4)
61 | for (i in 0..3) {
62 | val sixBit = block ushr 6 * (3 - i) and 0x3f
63 | base64[i] = getChar(sixBit)
64 | }
65 | if (slack < 1) {
66 | base64[2] = '='
67 | }
68 | if (slack < 2) {
69 | base64[3] = '='
70 | }
71 | return base64
72 | }
73 |
74 | private fun getChar(sixBit: Int): Char {
75 | if (sixBit in 0..25) {
76 | return ('A'.code + sixBit).toChar()
77 | }
78 | if (sixBit in 26..51) {
79 | return ('a'.code + (sixBit - 26)).toChar()
80 | }
81 | if (sixBit in 52..61) {
82 | return ('0'.code + (sixBit - 52)).toChar()
83 | }
84 | if (sixBit == 62) {
85 | return '+'
86 | }
87 | return if (sixBit == 63) {
88 | '/'
89 | } else {
90 | '?'
91 | }
92 | }
93 |
94 | private fun getValue(c: Char): Int {
95 | if (c in 'A'..'Z') {
96 | return c.code - 'A'.code
97 | }
98 | if (c in 'a'..'z') {
99 | return c.code - 'a'.code + 26
100 | }
101 | if (c in '0'..'9') {
102 | return c.code - '0'.code + 52
103 | }
104 | if (c == '+') {
105 | return 62
106 | }
107 | if (c == '/') {
108 | return 63
109 | }
110 | return if (c == '=') {
111 | 0
112 | } else {
113 | -1
114 | }
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/library/src/main/java/com/mcal/apksigner/utils/DistinguishedNameValues.kt:
--------------------------------------------------------------------------------
1 | package com.mcal.apksigner.utils
2 |
3 | import org.spongycastle.asn1.ASN1ObjectIdentifier
4 | import org.spongycastle.asn1.x500.style.BCStyle
5 | import org.spongycastle.jce.X509Principal
6 | import java.util.Vector
7 |
8 | /**
9 | * Helper class for dealing with the distinguished name RDNs.
10 | */
11 | class DistinguishedNameValues : LinkedHashMap() {
12 | init {
13 | put(BCStyle.C, "")
14 | put(BCStyle.ST, "")
15 | put(BCStyle.L, "")
16 | put(BCStyle.STREET, "")
17 | put(BCStyle.O, "")
18 | put(BCStyle.OU, "")
19 | put(BCStyle.CN, "")
20 | }
21 |
22 | override fun put(key: ASN1ObjectIdentifier, value: String): String {
23 | if (containsKey(key)) {
24 | super.put(key, value) // preserve original ordering
25 | } else {
26 | super.put(key, value)
27 | }
28 | return value
29 | }
30 |
31 | fun setCountry(country: String) {
32 | put(BCStyle.C, country)
33 | }
34 |
35 | fun setState(state: String) {
36 | put(BCStyle.ST, state)
37 | }
38 |
39 | fun setLocality(locality: String) {
40 | put(BCStyle.L, locality)
41 | }
42 |
43 | fun setStreet(street: String) {
44 | put(BCStyle.STREET, street)
45 | }
46 |
47 | fun setOrganization(organization: String) {
48 | put(BCStyle.O, organization)
49 | }
50 |
51 | fun setOrganizationalUnit(organizationalUnit: String) {
52 | put(BCStyle.OU, organizationalUnit)
53 | }
54 |
55 | fun setCommonName(commonName: String) {
56 | put(BCStyle.CN, commonName)
57 | }
58 |
59 | val principal: X509Principal
60 | get() {
61 | val identifiers = Vector()
62 | val values = Vector()
63 | for ((key, value) in entries) {
64 | identifiers.add(key)
65 | values.add(value)
66 | }
67 | return X509Principal(identifiers, values)
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/library/src/main/java/com/mcal/apksigner/utils/JksKeyStore.kt:
--------------------------------------------------------------------------------
1 | package com.mcal.apksigner.utils
2 |
3 | import java.security.KeyStore
4 |
5 | class JksKeyStore : KeyStore(JKS(), KeyStoreHelper.provider, "JKS")
6 |
--------------------------------------------------------------------------------
/library/src/main/java/com/mcal/apksigner/utils/KeySet.java:
--------------------------------------------------------------------------------
1 | package com.mcal.apksigner.utils;
2 |
3 | import java.security.PrivateKey;
4 | import java.security.cert.X509Certificate;
5 |
6 | public class KeySet {
7 | String name;
8 | X509Certificate publicKey = null;
9 | PrivateKey privateKey = null;
10 | byte[] sigBlockTemplate = null;
11 |
12 | String signatureAlgorithm = "SHA1withRSA";
13 |
14 | public KeySet() {
15 | }
16 |
17 | public KeySet(String name, X509Certificate publicKey, PrivateKey privateKey, byte[] sigBlockTemplate) {
18 | this.name = name;
19 | this.publicKey = publicKey;
20 | this.privateKey = privateKey;
21 | this.sigBlockTemplate = sigBlockTemplate;
22 | }
23 |
24 | public KeySet(String name, X509Certificate publicKey, PrivateKey privateKey, String signatureAlgorithm, byte[] sigBlockTemplate) {
25 | this.name = name;
26 | this.publicKey = publicKey;
27 | this.privateKey = privateKey;
28 | if (signatureAlgorithm != null) {
29 | this.signatureAlgorithm = signatureAlgorithm;
30 | }
31 | this.sigBlockTemplate = sigBlockTemplate;
32 | }
33 |
34 | public String getName() {
35 | return name;
36 | }
37 |
38 | public void setName(String name) {
39 | this.name = name;
40 | }
41 |
42 | public X509Certificate getPublicKey() {
43 | return publicKey;
44 | }
45 |
46 | public void setPublicKey(X509Certificate publicKey) {
47 | this.publicKey = publicKey;
48 | }
49 |
50 | public PrivateKey getPrivateKey() {
51 | return privateKey;
52 | }
53 |
54 | public void setPrivateKey(PrivateKey privateKey) {
55 | this.privateKey = privateKey;
56 | }
57 |
58 | public byte[] getSigBlockTemplate() {
59 | return sigBlockTemplate;
60 | }
61 |
62 | public void setSigBlockTemplate(byte[] sigBlockTemplate) {
63 | this.sigBlockTemplate = sigBlockTemplate;
64 | }
65 |
66 | public String getSignatureAlgorithm() {
67 | return signatureAlgorithm;
68 | }
69 |
70 | public void setSignatureAlgorithm(String signatureAlgorithm) {
71 | if (signatureAlgorithm == null) {
72 | this.signatureAlgorithm = "SHA1withRSA";
73 | } else {
74 | this.signatureAlgorithm = signatureAlgorithm;
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/library/src/main/java/com/mcal/apksigner/utils/KeyStoreHelper.kt:
--------------------------------------------------------------------------------
1 | package com.mcal.apksigner.utils
2 |
3 | import org.spongycastle.jce.provider.BouncyCastleProvider
4 | import java.io.File
5 | import java.io.FileInputStream
6 | import java.io.FileOutputStream
7 | import java.security.KeyStore
8 | import java.security.Security
9 | import java.util.Locale
10 |
11 | object KeyStoreHelper {
12 | val provider by lazy(LazyThreadSafetyMode.NONE) {
13 | BouncyCastleProvider().also {
14 | Security.addProvider(it)
15 | }
16 | }
17 |
18 | @JvmStatic
19 | @Throws(Exception::class)
20 | fun loadJks(jksFile: File?, password: CharArray): KeyStore {
21 | val keyStore: KeyStore
22 | try {
23 | keyStore = JksKeyStore()
24 | keyStore.load(jksFile?.let { FileInputStream(it) }, password)
25 | } catch (e: Exception) {
26 | throw RuntimeException("Failed to load keystore: " + e.message)
27 | }
28 | return keyStore
29 | }
30 |
31 | @JvmStatic
32 | @Throws(Exception::class)
33 | fun loadBks(bksFile: File?, password: CharArray): KeyStore {
34 | val keyStore: KeyStore
35 | try {
36 | keyStore = KeyStore.getInstance("BKS", "BC")
37 | keyStore.load(bksFile?.let { FileInputStream(it) }, password)
38 | } catch (e: Exception) {
39 | throw RuntimeException("Failed to load keystore: " + e.message)
40 | }
41 | return keyStore
42 | }
43 |
44 | @JvmStatic
45 | @Throws(java.lang.Exception::class)
46 | fun loadKeyStore(keystoreFile: File, password: CharArray): KeyStore {
47 | return if (keystoreFile.path.lowercase(Locale.getDefault()).endsWith(".bks")) {
48 | loadBks(keystoreFile, password)
49 | } else {
50 | loadJks(keystoreFile, password)
51 | }
52 | }
53 |
54 | @JvmStatic
55 | @Throws(java.lang.Exception::class)
56 | fun createKeyStore(keystoreFile: File, password: CharArray): KeyStore {
57 | return if (keystoreFile.path.lowercase(Locale.getDefault()).endsWith(".bks")) {
58 | loadBks(null, password)
59 | } else {
60 | loadJks(null, password)
61 | }
62 | }
63 |
64 | @JvmStatic
65 | @Throws(Exception::class)
66 | fun writeKeyStore(ks: KeyStore, keystorePath: File, password: CharArray) {
67 | FileOutputStream(keystorePath).use { fos ->
68 | ks.store(fos, password)
69 | }
70 | }
71 |
72 | @JvmStatic
73 | @Throws(Exception::class)
74 | fun validateKeystorePassword(keystoreFile: File, password: String): Boolean {
75 | return try {
76 | loadKeyStore(keystoreFile, password.toCharArray())
77 | true
78 | } catch (e: Exception) {
79 | false
80 | }
81 | }
82 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/mcal/apksigner/utils/LoadKeystoreException.kt:
--------------------------------------------------------------------------------
1 | package com.mcal.apksigner.utils
2 |
3 | import java.io.IOException
4 |
5 | /**
6 | * Thrown by JKS.engineLoad() for errors that occur after determining the keystore is actually a JKS keystore.
7 | */
8 | class LoadKeystoreException : IOException {
9 | constructor()
10 | constructor(message: String?) : super(message)
11 | constructor(message: String?, cause: Throwable?) : super(message, cause)
12 | constructor(cause: Throwable?) : super(cause)
13 | }
14 |
--------------------------------------------------------------------------------
/library/src/main/resources/keystore/testkey.pk8:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timscriptov/apksigner/33964952d6d166d93c0dceb6e0a720ba009682f4/library/src/main/resources/keystore/testkey.pk8
--------------------------------------------------------------------------------
/library/src/main/resources/keystore/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 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | gradlePluginPortal()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | }
15 | rootProject.name = "apksigner"
16 | include ':app'
17 | include ':library'
--------------------------------------------------------------------------------