1 /* 2 * Copyright (c) 2015, 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.ssl; 27 28 import java.io.IOException; 29 import java.nio.ByteBuffer; 30 import java.security.cert.Extension; 31 import java.util.ArrayList; 32 import java.util.List; 33 import java.util.Collections; 34 import javax.net.ssl.SSLException; 35 import sun.security.util.DerValue; 36 import sun.security.util.DerInputStream; 37 import sun.security.util.DerOutputStream; 38 import sun.security.provider.certpath.ResponderId; 39 40 /* 41 * RFC6066 defines the TLS extension,"status_request" (type 0x5), 42 * which allows the client to request that the server perform OCSP 43 * on the client's behalf. 44 * 45 * The RFC defines an OCSPStatusRequest structure: 46 * 47 * struct { 48 * ResponderID responder_id_list<0..2^16-1>; 49 * Extensions request_extensions; 50 * } OCSPStatusRequest; 51 */ 52 final class OCSPStatusRequest implements StatusRequest { 53 54 private final List<ResponderId> responderIds; 55 private final List<Extension> extensions; 56 private int encodedLen; 57 private int ridListLen; 58 private int extListLen; 59 60 /** 61 * Construct a default {@code OCSPStatusRequest} object with empty 62 * responder ID and code extension list fields. 63 */ 64 OCSPStatusRequest() { 65 responderIds = new ArrayList<>(); 66 extensions = new ArrayList<>(); 67 encodedLen = this.length(); 68 } 69 70 /** 71 * Construct an {@code OCSPStatusRequest} object using the provided 72 * {@code ResponderId} and {@code Extension} lists. 73 * 74 * @param respIds the list of {@code ResponderId} objects to be placed 75 * into the {@code OCSPStatusRequest}. If the user wishes to place 76 * no {@code ResponderId} objects in the request, either an empty 77 * {@code List} or {@code null} is acceptable. 78 * @param exts the list of {@code Extension} objects to be placed into 79 * the {@code OCSPStatusRequest} If the user wishes to place 80 * no {@code Extension} objects in the request, either an empty 81 * {@code List} or {@code null} is acceptable. 82 */ 83 OCSPStatusRequest(List<ResponderId> respIds, List<Extension> exts) { 84 responderIds = new ArrayList<>(respIds != null ? respIds : 85 Collections.emptyList()); 86 extensions = new ArrayList<>(exts != null ? exts : 87 Collections.emptyList()); 88 encodedLen = this.length(); 89 } 90 91 /** 92 * Construct an {@code OCSPStatusRequest} object from data read from 93 * a {@code HandshakeInputStream} 94 * 95 * @param s the {@code HandshakeInputStream} providing the encoded data 96 * 97 * @throws IOException if any decoding errors happen during object 98 * construction. 99 */ 100 OCSPStatusRequest(HandshakeInStream in) throws IOException { 101 responderIds = new ArrayList<>(); 102 extensions = new ArrayList<>(); 103 104 int ridListBytesRemaining = in.getInt16(); 105 while (ridListBytesRemaining != 0) { 106 byte[] ridBytes = in.getBytes16(); 107 responderIds.add(new ResponderId(ridBytes)); 108 ridListBytesRemaining -= (ridBytes.length + 2); 109 // Make sure that no individual responder ID's length caused an 110 // overrun relative to the outer responder ID list length 111 if (ridListBytesRemaining < 0) { 112 throw new SSLException("Responder ID length overflow: " + 113 "current rid = " + ridBytes.length + ", remaining = " + 114 ridListBytesRemaining); 115 } 116 } 117 118 int extensionLength = in.getInt16(); 119 if (extensionLength > 0) { 120 byte[] extensionData = new byte[extensionLength]; 121 in.read(extensionData); 122 DerInputStream dis = new DerInputStream(extensionData); 123 DerValue[] extSeqContents = dis.getSequence(extensionData.length); 124 for (DerValue extDerVal : extSeqContents) { 125 extensions.add(new sun.security.x509.Extension(extDerVal)); 126 } 127 } 128 } 129 130 /** 131 * Construct an {@code OCSPStatusRequest} from its encoded form 132 * 133 * @param requestBytes the status request extension bytes 134 * 135 * @throws IOException if any error occurs during decoding 136 */ 137 OCSPStatusRequest(byte[] requestBytes) throws IOException { 138 responderIds = new ArrayList<>(); 139 extensions = new ArrayList<>(); 140 ByteBuffer reqBuf = ByteBuffer.wrap(requestBytes); 141 142 // Get the ResponderId list length 143 encodedLen = requestBytes.length; 144 ridListLen = Short.toUnsignedInt(reqBuf.getShort()); 145 int endOfRidList = reqBuf.position() + ridListLen; 146 147 // The end position of the ResponderId list in the ByteBuffer 148 // should be at least 2 less than the end of the buffer. This 149 // 2 byte defecit is the minimum length required to encode a 150 // zero-length extensions segment. 151 if (reqBuf.limit() - endOfRidList < 2) { 152 throw new SSLException 153 ("ResponderId List length exceeds provided buffer - Len: " 154 + ridListLen + ", Buffer: " + reqBuf.remaining()); 155 } 156 157 while (reqBuf.position() < endOfRidList) { 158 int ridLength = Short.toUnsignedInt(reqBuf.getShort()); 159 // Make sure an individual ResponderId length doesn't 160 // run past the end of the ResponderId list portion of the 161 // provided buffer. 162 if (reqBuf.position() + ridLength > endOfRidList) { 163 throw new SSLException 164 ("ResponderId length exceeds list length - Off: " 165 + reqBuf.position() + ", Length: " + ridLength 166 + ", End offset: " + endOfRidList); 167 } 168 169 // Consume/add the ResponderId 170 if (ridLength > 0) { 171 byte[] ridData = new byte[ridLength]; 172 reqBuf.get(ridData); 173 responderIds.add(new ResponderId(ridData)); 174 } 175 } 176 177 // Get the Extensions length 178 int extensionsLen = Short.toUnsignedInt(reqBuf.getShort()); 179 180 // The end of the extensions should also be the end of the 181 // encoded OCSPStatusRequest 182 if (extensionsLen != reqBuf.remaining()) { 183 throw new SSLException("Incorrect extensions length: Read " 184 + extensionsLen + ", Data length: " + reqBuf.remaining()); 185 } 186 187 // Extensions are a SEQUENCE of Extension 188 if (extensionsLen > 0) { 189 byte[] extensionData = new byte[extensionsLen]; 190 reqBuf.get(extensionData); 191 DerInputStream dis = new DerInputStream(extensionData); 192 DerValue[] extSeqContents = dis.getSequence(extensionData.length); 193 for (DerValue extDerVal : extSeqContents) { 194 extensions.add(new sun.security.x509.Extension(extDerVal)); 195 } 196 } 197 } 198 199 /** 200 * Obtain the length of the {@code OCSPStatusRequest} object in its 201 * encoded form 202 * 203 * @return the length of the {@code OCSPStatusRequest} object in its 204 * encoded form 205 */ 206 @Override 207 public int length() { 208 // If we've previously calculated encodedLen simply return it 209 if (encodedLen != 0) { 210 return encodedLen; 211 } 212 213 ridListLen = 0; 214 for (ResponderId rid : responderIds) { 215 ridListLen += rid.length() + 2; 216 } 217 218 extListLen = 0; 219 if (!extensions.isEmpty()) { 220 try { 221 DerOutputStream extSequence = new DerOutputStream(); 222 DerOutputStream extEncoding = new DerOutputStream(); 223 for (Extension ext : extensions) { 224 ext.encode(extEncoding); 225 } 226 extSequence.write(DerValue.tag_Sequence, extEncoding); 227 extListLen = extSequence.size(); 228 } catch (IOException ioe) { 229 // Not sure what to do here 230 } 231 } 232 233 // Total length is the responder ID list length and extensions length 234 // plus each lists' 2-byte length fields. 235 encodedLen = ridListLen + extListLen + 4; 236 237 return encodedLen; 238 } 239 240 /** 241 * Send the encoded {@code OCSPStatusRequest} out through the provided 242 * {@code HandshakeOutputStream} 243 * 244 * @param s the {@code HandshakeOutputStream} on which to send the encoded 245 * data 246 * 247 * @throws IOException if any encoding errors occur 248 */ 249 @Override 250 public void send(HandshakeOutStream s) throws IOException { 251 s.putInt16(ridListLen); 252 for (ResponderId rid : responderIds) { 253 s.putBytes16(rid.getEncoded()); 254 } 255 256 DerOutputStream seqOut = new DerOutputStream(); 257 DerOutputStream extBytes = new DerOutputStream(); 258 259 if (extensions.size() > 0) { 260 for (Extension ext : extensions) { 261 ext.encode(extBytes); 262 } 263 seqOut.write(DerValue.tag_Sequence, extBytes); 264 } 265 s.putBytes16(seqOut.toByteArray()); 266 } 267 268 /** 269 * Determine if a provided {@code OCSPStatusRequest} objects is equal to 270 * this one. 271 * 272 * @param obj an {@code OCSPStatusRequest} object to be compared against 273 * 274 * @return {@code true} if the objects are equal, {@code false} otherwise. 275 * Equivalence is established if the lists of responder IDs and 276 * extensions between the two objects are also equal. 277 */ 278 @Override 279 public boolean equals(Object obj) { 280 if (obj == null) { 281 return false; 282 } else if (this == obj) { 283 return true; 284 } else if (obj instanceof OCSPStatusRequest) { 285 OCSPStatusRequest respObj = (OCSPStatusRequest)obj; 286 return responderIds.equals(respObj.getResponderIds()) && 287 extensions.equals(respObj.getExtensions()); 288 } 289 290 return false; 291 } 292 293 /** 294 * Returns the hash code value for this {@code OCSPStatusRequest} 295 * 296 * @return the hash code value for this {@code OCSPStatusRequest} 297 */ 298 @Override 299 public int hashCode() { 300 int result = 17; 301 302 result = 31 * result + responderIds.hashCode(); 303 result = 31 * result + extensions.hashCode(); 304 305 return result; 306 } 307 308 /** 309 * Create a string representation of this {@code OCSPStatusRequest} 310 * 311 * @return a string representation of this {@code OCSPStatusRequest} 312 */ 313 @Override 314 public String toString() { 315 StringBuilder sb = new StringBuilder(); 316 sb.append("OCSPStatusRequest\n"); 317 sb.append(" ResponderIds:"); 318 319 if (responderIds.isEmpty()) { 320 sb.append(" <EMPTY>"); 321 } else { 322 for (ResponderId rid : responderIds) { 323 sb.append("\n ").append(rid.toString()); 324 } 325 } 326 327 sb.append("\n").append(" Extensions:"); 328 if (extensions.isEmpty()) { 329 sb.append(" <EMPTY>"); 330 } else { 331 for (Extension ext : extensions) { 332 sb.append("\n ").append(ext.toString()); 333 } 334 } 335 336 return sb.toString(); 337 } 338 339 /** 340 * Get the list of {@code ResponderId} objects for this 341 * {@code OCSPStatusRequest} 342 * 343 * @return an unmodifiable {@code List} of {@code ResponderId} objects 344 */ 345 List<ResponderId> getResponderIds() { 346 return Collections.unmodifiableList(responderIds); 347 } 348 349 /** 350 * Get the list of {@code Extension} objects for this 351 * {@code OCSPStatusRequest} 352 * 353 * @return an unmodifiable {@code List} of {@code Extension} objects 354 */ 355 List<Extension> getExtensions() { 356 return Collections.unmodifiableList(extensions); 357 } 358 }