src/java.base/share/classes/sun/security/util/ManifestDigester.java
changeset 47216 71c04702a3d5
parent 31538 0981099a3e54
child 47273 f60a42d4b8cd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/sun/security/util/ManifestDigester.java	Tue Sep 12 19:03:39 2017 +0200
@@ -0,0 +1,270 @@
+/*
+ * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.security.util;
+
+import java.security.*;
+import java.util.HashMap;
+import java.io.ByteArrayOutputStream;
+
+/**
+ * This class is used to compute digests on sections of the Manifest.
+ */
+public class ManifestDigester {
+
+    public static final String MF_MAIN_ATTRS = "Manifest-Main-Attributes";
+
+    /** the raw bytes of the manifest */
+    private byte[] rawBytes;
+
+    /** the offset/length pair for a section */
+    private HashMap<String, Entry> entries; // key is a UTF-8 string
+
+    /** state returned by findSection */
+    static class Position {
+        int endOfFirstLine; // not including newline character
+
+        int endOfSection; // end of section, not including the blank line
+                          // between sections
+        int startOfNext;  // the start of the next section
+    }
+
+    /**
+     * find a section in the manifest.
+     *
+     * @param offset should point to the starting offset with in the
+     * raw bytes of the next section.
+     *
+     * @pos set by
+     *
+     * @return false if end of bytes has been reached, otherwise returns
+     *          true
+     */
+    @SuppressWarnings("fallthrough")
+    private boolean findSection(int offset, Position pos)
+    {
+        int i = offset, len = rawBytes.length;
+        int last = offset;
+        int next;
+        boolean allBlank = true;
+
+        pos.endOfFirstLine = -1;
+
+        while (i < len) {
+            byte b = rawBytes[i];
+            switch(b) {
+            case '\r':
+                if (pos.endOfFirstLine == -1)
+                    pos.endOfFirstLine = i-1;
+                if ((i < len) &&  (rawBytes[i+1] == '\n'))
+                    i++;
+                /* fall through */
+            case '\n':
+                if (pos.endOfFirstLine == -1)
+                    pos.endOfFirstLine = i-1;
+                if (allBlank || (i == len-1)) {
+                    if (i == len-1)
+                        pos.endOfSection = i;
+                    else
+                        pos.endOfSection = last;
+                    pos.startOfNext = i+1;
+                    return true;
+                }
+                else {
+                    // start of a new line
+                    last = i;
+                    allBlank = true;
+                }
+                break;
+            default:
+                allBlank = false;
+                break;
+            }
+            i++;
+        }
+        return false;
+    }
+
+    public ManifestDigester(byte[] bytes)
+    {
+        rawBytes = bytes;
+        entries = new HashMap<>();
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+        Position pos = new Position();
+
+        if (!findSection(0, pos))
+            return; // XXX: exception?
+
+        // create an entry for main attributes
+        entries.put(MF_MAIN_ATTRS,
+                new Entry(0, pos.endOfSection + 1, pos.startOfNext, rawBytes));
+
+        int start = pos.startOfNext;
+        while(findSection(start, pos)) {
+            int len = pos.endOfFirstLine-start+1;
+            int sectionLen = pos.endOfSection-start+1;
+            int sectionLenWithBlank = pos.startOfNext-start;
+
+            if (len > 6) {
+                if (isNameAttr(bytes, start)) {
+                    StringBuilder nameBuf = new StringBuilder(sectionLen);
+
+                    try {
+                        nameBuf.append(
+                            new String(bytes, start+6, len-6, "UTF8"));
+
+                        int i = start + len;
+                        if ((i-start) < sectionLen) {
+                            if (bytes[i] == '\r') {
+                                i += 2;
+                            } else {
+                                i += 1;
+                            }
+                        }
+
+                        while ((i-start) < sectionLen) {
+                            if (bytes[i++] == ' ') {
+                                // name is wrapped
+                                int wrapStart = i;
+                                while (((i-start) < sectionLen)
+                                        && (bytes[i++] != '\n'));
+                                    if (bytes[i-1] != '\n')
+                                        return; // XXX: exception?
+                                    int wrapLen;
+                                    if (bytes[i-2] == '\r')
+                                        wrapLen = i-wrapStart-2;
+                                    else
+                                        wrapLen = i-wrapStart-1;
+
+                            nameBuf.append(new String(bytes, wrapStart,
+                                                      wrapLen, "UTF8"));
+                            } else {
+                                break;
+                            }
+                        }
+
+                        entries.put(nameBuf.toString(),
+                            new Entry(start, sectionLen, sectionLenWithBlank,
+                                rawBytes));
+
+                    } catch (java.io.UnsupportedEncodingException uee) {
+                        throw new IllegalStateException(
+                            "UTF8 not available on platform");
+                    }
+                }
+            }
+            start = pos.startOfNext;
+        }
+    }
+
+    private boolean isNameAttr(byte[] bytes, int start)
+    {
+        return ((bytes[start] == 'N') || (bytes[start] == 'n')) &&
+               ((bytes[start+1] == 'a') || (bytes[start+1] == 'A')) &&
+               ((bytes[start+2] == 'm') || (bytes[start+2] == 'M')) &&
+               ((bytes[start+3] == 'e') || (bytes[start+3] == 'E')) &&
+               (bytes[start+4] == ':') &&
+               (bytes[start+5] == ' ');
+    }
+
+    public static class Entry {
+        int offset;
+        int length;
+        int lengthWithBlankLine;
+        byte[] rawBytes;
+        boolean oldStyle;
+
+        public Entry(int offset, int length,
+                     int lengthWithBlankLine, byte[] rawBytes)
+        {
+            this.offset = offset;
+            this.length = length;
+            this.lengthWithBlankLine = lengthWithBlankLine;
+            this.rawBytes = rawBytes;
+        }
+
+        public byte[] digest(MessageDigest md)
+        {
+            md.reset();
+            if (oldStyle) {
+                doOldStyle(md,rawBytes, offset, lengthWithBlankLine);
+            } else {
+                md.update(rawBytes, offset, lengthWithBlankLine);
+            }
+            return md.digest();
+        }
+
+        private void doOldStyle(MessageDigest md,
+                                byte[] bytes,
+                                int offset,
+                                int length)
+        {
+            // this is too gross to even document, but here goes
+            // the 1.1 jar verification code ignored spaces at the
+            // end of lines when calculating digests, so that is
+            // what this code does. It only gets called if we
+            // are parsing a 1.1 signed signature file
+            int i = offset;
+            int start = offset;
+            int max = offset + length;
+            int prev = -1;
+            while(i <max) {
+                if ((bytes[i] == '\r') && (prev == ' ')) {
+                    md.update(bytes, start, i-start-1);
+                    start = i;
+                }
+                prev = bytes[i];
+                i++;
+            }
+            md.update(bytes, start, i-start);
+        }
+
+
+        /** Netscape doesn't include the new line. Intel and JavaSoft do */
+
+        public byte[] digestWorkaround(MessageDigest md)
+        {
+            md.reset();
+            md.update(rawBytes, offset, length);
+            return md.digest();
+        }
+    }
+
+    public Entry get(String name, boolean oldStyle) {
+        Entry e = entries.get(name);
+        if (e != null)
+            e.oldStyle = oldStyle;
+        return e;
+    }
+
+    public byte[] manifestDigest(MessageDigest md) {
+        md.reset();
+        md.update(rawBytes, 0, rawBytes.length);
+        return md.digest();
+    }
+
+}