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