View Javadoc
1   /*
2    * The contents of this file are subject to the terms of the Common Development and
3    * Distribution License (the License). You may not use this file except in compliance with the
4    * License.
5    *
6    * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
7    * specific language governing permission and limitations under the License.
8    *
9    * When distributing Covered Software, include this CDDL Header Notice in each file and include
10   * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
11   * Header, with the fields enclosed by brackets [] replaced by your own identifying
12   * information: "Portions copyright [year] [name of copyright owner]".
13   *
14   * Copyright 2016-2017 ForgeRock AS.
15   */
16  
17  package org.forgerock.json.jose.utils;
18  
19  import java.nio.BufferOverflowException;
20  import java.nio.ByteBuffer;
21  
22  import org.forgerock.util.Reject;
23  
24  /**
25   * Utility methods for reading and writing DER-encoded values. This is just the absolute minimum needed to decode and
26   * encode ECDSA signatures to ES256 format.
27   */
28  public final class DerUtils {
29      /**
30       * DER tag for integer values.
31       */
32      public static final byte INTEGER_TAG = 0x02;
33      /**
34       * DER tag for sequence values.
35       */
36      public static final byte SEQUENCE_TAG = 0x30;
37  
38      private DerUtils() {
39          // Utility class
40      }
41  
42      /**
43       * Reads an unsigned integer value into the given byte array. The output will be in big-endian format and aligned
44       * to take up exactly {@code length} bytes (leaving untouched any unused leading bytes).
45       *
46       * @param input the input DER-encoded byte buffer.
47       * @param output the output byte array.
48       * @param offset the offset into the byte array to start writing the integer value.
49       * @param length the maximum length of the byte value (excluding any leading sign byte).
50       * @throws BufferOverflowException if the integer does not fit in the given output buffer slice.
51       */
52      public static void readUnsignedInteger(ByteBuffer input, byte[] output, int offset, int length) {
53          Reject.ifFalse(input.get() == INTEGER_TAG, "Not an integer");
54          int len = readLength(input);
55          if (len > length) {
56              // See if it fits if we assume the first byte is a redundant zero sign byte
57              if (--len > length || input.get() != 0) {
58                  throw new BufferOverflowException();
59              }
60          }
61          input.get(output, offset + (length - len), len);
62      }
63  
64      /**
65       * Writes an integer value in DER format to the given buffer.
66       *
67       * @param buffer the buffer to write the value to
68       * @param data the integer value (in big-endian format) to write
69       */
70      public static void writeInteger(final ByteBuffer buffer, final byte[] data) {
71          buffer.put(INTEGER_TAG);
72          // according to DER specification, the data cannot start with a leading 0
73          int startIndex = data.length - 1;
74          for (int index = 0; index < data.length; index++) {
75              if (data[index] != 0) {
76                  startIndex = index;
77                  break;
78              }
79          }
80          final int length = data.length - startIndex;
81          writeLength(buffer, length);
82          buffer.put(data, startIndex, length);
83      }
84  
85      /**
86       * Reads a DER-encoded length field from the given byte buffer.
87       *
88       * @param buffer the buffer to read a length field from.
89       * @return the length field.
90       */
91      public static int readLength(final ByteBuffer buffer) {
92          int b = buffer.get();
93          // See http://luca.ntop.org/Teaching/Appunti/asn1.html. If the high-bit of the first byte is 0 then this byte
94          // is the length. Otherwise the first byte (masking off the high bit) gives the number of following bytes
95          // that encode the length (in big-endian unsigned integer format).
96          if ((b & 0x80) == 0) {
97              return b & 0xFF;
98          } else {
99              int numBytes = b & 0x7F;
100             Reject.ifFalse(numBytes > 0 && numBytes < 4, "Unsupported DER length field");
101 
102             int len = 0;
103             for (int i = 0; i < numBytes; ++i) {
104                 len = (len << 8) + (buffer.get() & 0xFF);
105             }
106             return len;
107         }
108     }
109 
110     /**
111      * Writes a length field to the output.
112      * If the length is 127 or less, the byte is the length.
113      * If the length is 128 or greater, the first byte is a combination of 0x80 to indicate the length is defined and
114      * the number of bytes to specify that length. See DER specification for more information.
115      *
116      * @param output the output buffer.
117      * @param length the length to write.
118      */
119     public static void writeLength(final ByteBuffer output, final int length) {
120         if ((length & 0x0000007F) == length) {
121             output.put((byte) length);
122         } else if ((length & 0x000000FF) == length) {
123             output.put((byte) 0x81);
124             output.put((byte) length);
125         } else if ((length & 0x0000FFFF) == length) {
126             output.put((byte) 0x82);
127             output.put((byte) (length >> 8));
128             output.put((byte) length);
129         } else if ((length & 0x00FFFFFF) == length) {
130             output.put((byte) 0x83);
131             output.put((byte) (length >> 16));
132             output.put((byte) (length >> 8));
133             output.put((byte) length);
134         } else {
135             output.put((byte) 0x84);
136             output.put((byte) (length >> 24));
137             output.put((byte) (length >> 16));
138             output.put((byte) (length >> 8));
139             output.put((byte) length);
140         }
141     }
142 }