Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 85b8a014 authored by Alex Klyubin's avatar Alex Klyubin
Browse files

JAR signing logic for the apksigner-core library.

apksigner-code library will offer a high-level primitive (future
commit) for signing APKs. This is meant to be used by
build/tools/signapk and Android Studio's APK builder/signer.

This commit adds a lower-level JAR signing (aka v1 signing) code which
will be used by the future APK signing abstraction exposed by this
library.

All classes added by this commit are internal (i.e., implementation
details of this library). Clients of this library should not be using
these classes.

Bug: 26516150
Change-Id: I5cecd435e63aab206d63868be5e0d0e289e7c423
parent c9778e33
Loading
Loading
Loading
Loading
+19 −0
Original line number Diff line number Diff line
#
# Copyright (C) 2016 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

LOCAL_PATH := $(call my-dir)

include $(call all-makefiles-under,$(LOCAL_PATH))
+26 −0
Original line number Diff line number Diff line
#
# Copyright (C) 2016 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
LOCAL_PATH := $(call my-dir)

# apksigner library, for signing APKs and verification signatures of APKs
# ============================================================
include $(CLEAR_VARS)
LOCAL_MODULE := apksigner-core
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_JAVA_LIBRARIES = \
  bouncycastle-host \
  bouncycastle-bcpkix-host
include $(BUILD_HOST_JAVA_LIBRARY)
+42 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.apksigner.core.internal.apk.v1;

/**
 * Digest algorithm used with JAR signing (aka v1 signing scheme).
 */
public enum DigestAlgorithm {
    /** SHA-1 */
    SHA1("SHA-1"),

    /** SHA2-256 */
    SHA256("SHA-256");

    private final String mJcaMessageDigestAlgorithm;

    private DigestAlgorithm(String jcaMessageDigestAlgoritm) {
        mJcaMessageDigestAlgorithm = jcaMessageDigestAlgoritm;
    }

    /**
     * Returns the {@link java.security.MessageDigest} algorithm represented by this digest
     * algorithm.
     */
    String getJcaMessageDigestAlgorithm() {
        return mJcaMessageDigestAlgorithm;
    }
}
+526 −0

File added.

Preview size limit exceeded, changes collapsed.

+124 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.apksigner.core.internal.jar;

import java.io.IOException;
import java.io.OutputStream;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.jar.Attributes;

/**
 * Producer of {@code META-INF/MANIFEST.MF} file.
 */
public abstract class ManifestWriter {

    private static final byte[] CRLF = new byte[] {'\r', '\n'};
    private static final int MAX_LINE_LENGTH = 70;

    private ManifestWriter() {}

    public static void writeMainSection(OutputStream out, Attributes attributes)
            throws IOException {

        // Main section must start with the Manifest-Version attribute.
        // See https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Signed_JAR_File.
        String manifestVersion = attributes.getValue(Attributes.Name.MANIFEST_VERSION);
        if (manifestVersion == null) {
            throw new IllegalArgumentException(
                    "Mandatory " + Attributes.Name.MANIFEST_VERSION + " attribute missing");
        }
        writeAttribute(out, Attributes.Name.MANIFEST_VERSION, manifestVersion);

        if (attributes.size() > 1) {
            SortedMap<String, String> namedAttributes = getAttributesSortedByName(attributes);
            namedAttributes.remove(Attributes.Name.MANIFEST_VERSION.toString());
            writeAttributes(out, namedAttributes);
        }
        writeSectionDelimiter(out);
    }

    public static void writeIndividualSection(OutputStream out, String name, Attributes attributes)
            throws IOException {
        writeAttribute(out, "Name", name);

        if (!attributes.isEmpty()) {
            writeAttributes(out, getAttributesSortedByName(attributes));
        }
        writeSectionDelimiter(out);
    }

    static void writeSectionDelimiter(OutputStream out) throws IOException {
        out.write(CRLF);
    }

    static void writeAttribute(OutputStream  out, Attributes.Name name, String value)
            throws IOException {
        writeAttribute(out, name.toString(), value);
    }

    private static void writeAttribute(OutputStream  out, String name, String value)
            throws IOException {
        writeLine(out, name + ": " + value);
    }

    private static void writeLine(OutputStream  out, String line) throws IOException {
        byte[] lineBytes = line.getBytes("UTF-8");
        int offset = 0;
        int remaining = lineBytes.length;
        boolean firstLine = true;
        while (remaining > 0) {
            int chunkLength;
            if (firstLine) {
                // First line
                chunkLength = Math.min(remaining, MAX_LINE_LENGTH);
            } else {
                // Continuation line
                out.write(CRLF);
                out.write(' ');
                chunkLength = Math.min(remaining, MAX_LINE_LENGTH - 1);
            }
            out.write(lineBytes, offset, chunkLength);
            offset += chunkLength;
            remaining -= chunkLength;
            firstLine = false;
        }
        out.write(CRLF);
    }

    static SortedMap<String, String> getAttributesSortedByName(Attributes attributes) {
        Set<Map.Entry<Object, Object>> attributesEntries = attributes.entrySet();
        SortedMap<String, String> namedAttributes = new TreeMap<String, String>();
        for (Map.Entry<Object, Object> attribute : attributesEntries) {
            String attrName = attribute.getKey().toString();
            String attrValue = attribute.getValue().toString();
            namedAttributes.put(attrName, attrValue);
        }
        return namedAttributes;
    }

    static void writeAttributes(
            OutputStream out, SortedMap<String, String> attributesSortedByName) throws IOException {
        for (Map.Entry<String, String> attribute : attributesSortedByName.entrySet()) {
            String attrName = attribute.getKey();
            String attrValue = attribute.getValue();
            writeAttribute(out, attrName, attrValue);
        }
    }
}
Loading