Home | History | Annotate | Download | only in util
      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 }
    272