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")
findSection(int offset, Position pos)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 
ManifestDigester(byte bytes[])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 
isNameAttr(byte bytes[], int start)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 
Entry(int offset, int length, int lengthWithBlankLine, byte[] rawBytes)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 
digest(MessageDigest md)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 
doOldStyle(MessageDigest md, byte[] bytes, int offset, int length)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 
digestWorkaround(MessageDigest md)249         public byte[] digestWorkaround(MessageDigest md)
250         {
251             md.reset();
252             md.update(rawBytes, offset, length);
253             return md.digest();
254         }
255     }
256 
get(String name, boolean oldStyle)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 
manifestDigest(MessageDigest md)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