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 }