1 /*
   2  * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.security.util;
  27 
  28 import java.security.*;
  29 import java.util.HashMap;
  30 import java.io.ByteArrayOutputStream;
  31 
  32 /**
  33  * This class is used to compute digests on sections of the Manifest.
  34  */
  35 public class ManifestDigester {
  36 
  37     public static final String MF_MAIN_ATTRS = "Manifest-Main-Attributes";
  38 
  39     /** the raw bytes of the manifest */
  40     private byte rawBytes[];
  41 
  42     /** the offset/length pair for a section */
  43     private HashMap<String, Entry> entries; // key is a UTF-8 string
  44 
  45     /** state returned by findSection */
  46     static class Position {
  47         int endOfFirstLine; // not including newline character
  48 
  49         int endOfSection; // end of section, not including the blank line
  50                           // between sections
  51         int startOfNext;  // the start of the next section
  52     }
  53 
  54     /**
  55      * find a section in the manifest.
  56      *
  57      * @param offset should point to the starting offset with in the
  58      * raw bytes of the next section.
  59      *
  60      * @pos set by
  61      *
  62      * @returns false if end of bytes has been reached, otherwise returns
  63      *          true
  64      */
  65     @SuppressWarnings("fallthrough")
  66     private boolean findSection(int offset, Position pos)
  67     {
  68         int i = offset, len = rawBytes.length;
  69         int last = offset;
  70         int next;
  71         boolean allBlank = true;
  72 
  73         pos.endOfFirstLine = -1;
  74 
  75         while (i < len) {
  76             byte b = rawBytes[i];
  77             switch(b) {
  78             case '\r':
  79                 if (pos.endOfFirstLine == -1)
  80                     pos.endOfFirstLine = i-1;
  81                 if ((i < len) &&  (rawBytes[i+1] == '\n'))
  82                     i++;
  83                 /* fall through */
  84             case '\n':
  85                 if (pos.endOfFirstLine == -1)
  86                     pos.endOfFirstLine = i-1;
  87                 if (allBlank || (i == len-1)) {
  88                     if (i == len-1)
  89                         pos.endOfSection = i;
  90                     else
  91                         pos.endOfSection = last;
  92                     pos.startOfNext = i+1;
  93                     return true;
  94                 }
  95                 else {
  96                     // start of a new line
  97                     last = i;
  98                     allBlank = true;
  99                 }
 100                 break;
 101             default:
 102                 allBlank = false;
 103                 break;
 104             }
 105             i++;
 106         }
 107         return false;
 108     }
 109 
 110     public ManifestDigester(byte bytes[])
 111     {
 112         rawBytes = bytes;
 113         entries = new HashMap<String, Entry>();
 114 
 115         ByteArrayOutputStream baos = new ByteArrayOutputStream();
 116 
 117         Position pos = new Position();
 118 
 119         if (!findSection(0, pos))
 120             return; // XXX: exception?
 121 
 122         // create an entry for main attributes
 123         entries.put(MF_MAIN_ATTRS,
 124                 new Entry(0, pos.endOfSection + 1, pos.startOfNext, rawBytes));
 125 
 126         int start = pos.startOfNext;
 127         while(findSection(start, pos)) {
 128             int len = pos.endOfFirstLine-start+1;
 129             int sectionLen = pos.endOfSection-start+1;
 130             int sectionLenWithBlank = pos.startOfNext-start;
 131 
 132             if (len > 6) {
 133                 if (isNameAttr(bytes, start)) {
 134                     StringBuilder nameBuf = new StringBuilder(sectionLen);
 135 
 136                     try {
 137                         nameBuf.append(
 138                             new String(bytes, start+6, len-6, "UTF8"));
 139 
 140                         int i = start + len;
 141                         if ((i-start) < sectionLen) {
 142                             if (bytes[i] == '\r') {
 143                                 i += 2;
 144                             } else {
 145                                 i += 1;
 146                             }
 147                         }
 148 
 149                         while ((i-start) < sectionLen) {
 150                             if (bytes[i++] == ' ') {
 151                                 // name is wrapped
 152                                 int wrapStart = i;
 153                                 while (((i-start) < sectionLen)
 154                                         && (bytes[i++] != '\n'));
 155                                     if (bytes[i-1] != '\n')
 156                                         return; // XXX: exception?
 157                                     int wrapLen;
 158                                     if (bytes[i-2] == '\r')
 159                                         wrapLen = i-wrapStart-2;
 160                                     else
 161                                         wrapLen = i-wrapStart-1;
 162 
 163                             nameBuf.append(new String(bytes, wrapStart,
 164                                                       wrapLen, "UTF8"));
 165                             } else {
 166                                 break;
 167                             }
 168                         }
 169 
 170                         entries.put(nameBuf.toString(),
 171                             new Entry(start, sectionLen, sectionLenWithBlank,
 172                                 rawBytes));
 173 
 174                     } catch (java.io.UnsupportedEncodingException uee) {
 175                         throw new IllegalStateException(
 176                             "UTF8 not available on platform");
 177                     }
 178                 }
 179             }
 180             start = pos.startOfNext;
 181         }
 182     }
 183 
 184     private boolean isNameAttr(byte bytes[], int start)
 185     {
 186         return ((bytes[start] == 'N') || (bytes[start] == 'n')) &&
 187                ((bytes[start+1] == 'a') || (bytes[start+1] == 'A')) &&
 188                ((bytes[start+2] == 'm') || (bytes[start+2] == 'M')) &&
 189                ((bytes[start+3] == 'e') || (bytes[start+3] == 'E')) &&
 190                (bytes[start+4] == ':') &&
 191                (bytes[start+5] == ' ');
 192     }
 193 
 194     public static class Entry {
 195         int offset;
 196         int length;
 197         int lengthWithBlankLine;
 198         byte[] rawBytes;
 199         boolean oldStyle;
 200 
 201         public Entry(int offset, int length,
 202                      int lengthWithBlankLine, byte[] rawBytes)
 203         {
 204             this.offset = offset;
 205             this.length = length;
 206             this.lengthWithBlankLine = lengthWithBlankLine;
 207             this.rawBytes = rawBytes;
 208         }
 209 
 210         public byte[] digest(MessageDigest md)
 211         {
 212             md.reset();
 213             if (oldStyle) {
 214                 doOldStyle(md,rawBytes, offset, lengthWithBlankLine);
 215             } else {
 216                 md.update(rawBytes, offset, lengthWithBlankLine);
 217             }
 218             return md.digest();
 219         }
 220 
 221         private void doOldStyle(MessageDigest md,
 222                                 byte[] bytes,
 223                                 int offset,
 224                                 int length)
 225         {
 226             // this is too gross to even document, but here goes
 227             // the 1.1 jar verification code ignored spaces at the
 228             // end of lines when calculating digests, so that is
 229             // what this code does. It only gets called if we
 230             // are parsing a 1.1 signed signature file
 231             int i = offset;
 232             int start = offset;
 233             int max = offset + length;
 234             int prev = -1;
 235             while(i <max) {
 236                 if ((bytes[i] == '\r') && (prev == ' ')) {
 237                     md.update(bytes, start, i-start-1);
 238                     start = i;
 239                 }
 240                 prev = bytes[i];
 241                 i++;
 242             }
 243             md.update(bytes, start, i-start);
 244         }
 245 
 246 
 247         /** Netscape doesn't include the new line. Intel and JavaSoft do */
 248 
 249         public byte[] digestWorkaround(MessageDigest md)
 250         {
 251             md.reset();
 252             md.update(rawBytes, offset, length);
 253             return md.digest();
 254         }
 255     }
 256 
 257     public Entry get(String name, boolean oldStyle) {
 258         Entry e = entries.get(name);
 259         if (e != null)
 260             e.oldStyle = oldStyle;
 261         return e;
 262     }
 263 
 264     public byte[] manifestDigest(MessageDigest md)
 265         {
 266             md.reset();
 267             md.update(rawBytes, 0, rawBytes.length);
 268             return md.digest();
 269         }
 270 
 271 }