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 }