Path: blob/master/src/java.base/share/classes/sun/security/util/ManifestDigester.java
41159 views
/*1* Copyright (c) 1997, 2019, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.security.util;2627import java.security.MessageDigest;28import java.util.ArrayList;29import java.util.HashMap;30import java.io.ByteArrayOutputStream;31import java.io.OutputStream;32import java.io.IOException;33import java.util.List;3435import static java.nio.charset.StandardCharsets.UTF_8;3637/**38* This class is used to compute digests on sections of the Manifest.39* Please note that multiple sections might have the same name, and they40* all belong to a single Entry.41*/42public class ManifestDigester {4344/**45* The part "{@code Manifest-Main-Attributes}" of the main attributes46* digest header name in a signature file as described in the jar47* specification:48* <blockquote>{@code x-Digest-Manifest-Main-Attributes}49* (where x is the standard name of a {@link MessageDigest} algorithm):50* The value of this attribute is the digest value of the main attributes51* of the manifest.</blockquote>52* @see <a href="{@docRoot}/../specs/jar/jar.html#signature-file">53* JAR File Specification, section Signature File</a>54* @see #getMainAttsEntry55*/56public static final String MF_MAIN_ATTRS = "Manifest-Main-Attributes";5758/** the raw bytes of the manifest */59private final byte[] rawBytes;6061private final Entry mainAttsEntry;6263/** individual sections by their names */64private final HashMap<String, Entry> entries = new HashMap<>();6566/** state returned by findSection */67static class Position {68int endOfFirstLine; // not including newline character6970int endOfSection; // end of section, not including the blank line71// between sections72int startOfNext; // the start of the next section73}7475/**76* find a section in the manifest.77*78* @param offset should point to the starting offset with in the79* raw bytes of the next section.80*81* @pos set by82*83* @return false if end of bytes has been reached, otherwise returns84* true85*/86@SuppressWarnings("fallthrough")87private boolean findSection(int offset, Position pos)88{89int i = offset, len = rawBytes.length;90int last = offset - 1;91int next;92boolean allBlank = true;9394/* denotes that a position is not yet assigned.95* As a primitive type int it cannot be null96* and -1 would be confused with (i - 1) when i == 0 */97final int UNASSIGNED = Integer.MIN_VALUE;9899pos.endOfFirstLine = UNASSIGNED;100101while (i < len) {102byte b = rawBytes[i];103switch(b) {104case '\r':105if (pos.endOfFirstLine == UNASSIGNED)106pos.endOfFirstLine = i-1;107if (i < len - 1 && rawBytes[i + 1] == '\n')108i++;109/* fall through */110case '\n':111if (pos.endOfFirstLine == UNASSIGNED)112pos.endOfFirstLine = i-1;113if (allBlank || (i == len-1)) {114pos.endOfSection = allBlank ? last : i;115pos.startOfNext = i+1;116return true;117}118else {119// start of a new line120last = i;121allBlank = true;122}123break;124default:125allBlank = false;126break;127}128i++;129}130return false;131}132133public ManifestDigester(byte[] bytes)134{135rawBytes = bytes;136137Position pos = new Position();138139if (!findSection(0, pos)) {140mainAttsEntry = null;141return; // XXX: exception?142}143144// create an entry for main attributes145mainAttsEntry = new Entry().addSection(new Section(1460, pos.endOfSection + 1, pos.startOfNext, rawBytes));147148int start = pos.startOfNext;149while(findSection(start, pos)) {150int len = pos.endOfFirstLine-start+1;151int sectionLen = pos.endOfSection-start+1;152int sectionLenWithBlank = pos.startOfNext-start;153154if (len >= 6) { // 6 == "Name: ".length()155if (isNameAttr(bytes, start)) {156ByteArrayOutputStream nameBuf = new ByteArrayOutputStream();157nameBuf.write(bytes, start+6, len-6);158159int i = start + len;160if ((i-start) < sectionLen) {161if (bytes[i] == '\r'162&& i + 1 - start < sectionLen163&& bytes[i + 1] == '\n') {164i += 2;165} else {166i += 1;167}168}169170while ((i-start) < sectionLen) {171if (bytes[i++] == ' ') {172// name is wrapped173int wrapStart = i;174while (((i-start) < sectionLen)175&& (bytes[i] != '\r')176&& (bytes[i] != '\n')) i++;177int wrapLen = i - wrapStart;178if (i - start < sectionLen) {179i++;180if (bytes[i - 1] == '\r'181&& i - start < sectionLen182&& bytes[i] == '\n')183i++;184}185186nameBuf.write(bytes, wrapStart, wrapLen);187} else {188break;189}190}191192entries.computeIfAbsent(nameBuf.toString(UTF_8),193dummy -> new Entry())194.addSection(new Section(start, sectionLen,195sectionLenWithBlank, rawBytes));196}197}198start = pos.startOfNext;199}200}201202private boolean isNameAttr(byte[] bytes, int start)203{204return ((bytes[start] == 'N') || (bytes[start] == 'n')) &&205((bytes[start+1] == 'a') || (bytes[start+1] == 'A')) &&206((bytes[start+2] == 'm') || (bytes[start+2] == 'M')) &&207((bytes[start+3] == 'e') || (bytes[start+3] == 'E')) &&208(bytes[start+4] == ':') &&209(bytes[start+5] == ' ');210}211212public static class Entry {213214// One Entry for one name, and one name can have multiple sections.215// According to the JAR File Specification: "If there are multiple216// individual sections for the same file entry, the attributes in217// these sections are merged."218private List<Section> sections = new ArrayList<>();219boolean oldStyle;220221private Entry addSection(Section sec)222{223sections.add(sec);224return this;225}226227/**228* Check if the sections (particularly the last one of usually only one)229* are properly delimited with a trailing blank line so that another230* section can be correctly appended and return {@code true} or return231* {@code false} to indicate that reproduction is not advised and should232* be carried out with a clean "normalized" newly-written manifest.233*234* @see #reproduceRaw235*/236public boolean isProperlyDelimited() {237return sections.stream().allMatch(238Section::isProperlySectionDelimited);239}240241public void reproduceRaw(OutputStream out) throws IOException {242for (Section sec : sections) {243out.write(sec.rawBytes, sec.offset, sec.lengthWithBlankLine);244}245}246247public byte[] digest(MessageDigest md)248{249md.reset();250for (Section sec : sections) {251if (oldStyle) {252Section.doOldStyle(md, sec.rawBytes, sec.offset, sec.lengthWithBlankLine);253} else {254md.update(sec.rawBytes, sec.offset, sec.lengthWithBlankLine);255}256}257return md.digest();258}259260/** Netscape doesn't include the new line. Intel and JavaSoft do */261262public byte[] digestWorkaround(MessageDigest md)263{264md.reset();265for (Section sec : sections) {266md.update(sec.rawBytes, sec.offset, sec.length);267}268return md.digest();269}270}271272private static class Section {273int offset;274int length;275int lengthWithBlankLine;276byte[] rawBytes;277278public Section(int offset, int length,279int lengthWithBlankLine, byte[] rawBytes)280{281this.offset = offset;282this.length = length;283this.lengthWithBlankLine = lengthWithBlankLine;284this.rawBytes = rawBytes;285}286287/**288* Returns {@code true} if the raw section is terminated with a blank289* line so that another section can possibly be appended resulting in a290* valid manifest and {@code false} otherwise.291*/292private boolean isProperlySectionDelimited() {293return lengthWithBlankLine > length;294}295296private static void doOldStyle(MessageDigest md,297byte[] bytes,298int offset,299int length)300{301// this is too gross to even document, but here goes302// the 1.1 jar verification code ignored spaces at the303// end of lines when calculating digests, so that is304// what this code does. It only gets called if we305// are parsing a 1.1 signed signature file306int i = offset;307int start = offset;308int max = offset + length;309int prev = -1;310while(i <max) {311if ((bytes[i] == '\r') && (prev == ' ')) {312md.update(bytes, start, i-start-1);313start = i;314}315prev = bytes[i];316i++;317}318md.update(bytes, start, i-start);319}320}321322/**323* @see #MF_MAIN_ATTRS324*/325public Entry getMainAttsEntry() {326return mainAttsEntry;327}328329/**330* @see #MF_MAIN_ATTRS331*/332public Entry getMainAttsEntry(boolean oldStyle) {333mainAttsEntry.oldStyle = oldStyle;334return mainAttsEntry;335}336337public Entry get(String name) {338return entries.get(name);339}340341public Entry get(String name, boolean oldStyle) {342Entry e = get(name);343if (e == null && MF_MAIN_ATTRS.equals(name)) {344e = getMainAttsEntry();345}346if (e != null) {347e.oldStyle = oldStyle;348}349return e;350}351352public byte[] manifestDigest(MessageDigest md) {353md.reset();354md.update(rawBytes, 0, rawBytes.length);355return md.digest();356}357358}359360361