001/* 002 * Copyright 2017-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2017-2024 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2017-2024 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.asn1; 037 038 039 040import java.util.ArrayList; 041import java.util.Iterator; 042import java.util.List; 043 044import com.unboundid.util.ByteStringBuffer; 045import com.unboundid.util.Debug; 046import com.unboundid.util.NotMutable; 047import com.unboundid.util.NotNull; 048import com.unboundid.util.OID; 049import com.unboundid.util.ThreadSafety; 050import com.unboundid.util.ThreadSafetyLevel; 051 052import static com.unboundid.asn1.ASN1Messages.*; 053 054 055 056/** 057 * This class provides an ASN.1 object identifier element, whose value 058 * represents a numeric OID. Note that ASN.1 object identifier elements must 059 * strictly conform to the numeric OID specification, which has the following 060 * requirements: 061 * <UL> 062 * <LI>All valid OIDs must contain at least two components.</LI> 063 * <LI>The value of the first component must be 0, 1, or 2.</LI> 064 * <LI>If the value of the first component is 0 or 1, then the value of the 065 * second component must not be greater than 39.</LI> 066 * </UL> 067 */ 068@NotMutable() 069@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 070public final class ASN1ObjectIdentifier 071 extends ASN1Element 072{ 073 /** 074 * The serial version UID for this serializable class. 075 */ 076 private static final long serialVersionUID = -777778295086222273L; 077 078 079 080 // The OID represented by this object identifier element. 081 @NotNull private final OID oid; 082 083 084 085 /** 086 * Creates a new ASN.1 object identifier element with the default BER type and 087 * the provided OID. 088 * 089 * @param oid The OID to represent with this element. It must not be 090 * {@code null}, and it must represent a valid OID. 091 * 092 * @throws ASN1Exception If the provided OID does not strictly adhere to the 093 * numeric OID format. 094 */ 095 public ASN1ObjectIdentifier(@NotNull final OID oid) 096 throws ASN1Exception 097 { 098 this(ASN1Constants.UNIVERSAL_OBJECT_IDENTIFIER_TYPE, oid); 099 } 100 101 102 103 /** 104 * Creates a new ASN.1 object identifier element with the specified BER type 105 * and the provided OID. 106 * 107 * @param type The BER type for this element. 108 * @param oid The OID to represent with this element. It must not be 109 * {@code null}, and it must represent a valid OID. 110 * 111 * @throws ASN1Exception If the provided OID does not strictly adhere to the 112 * numeric OID format. 113 */ 114 public ASN1ObjectIdentifier(final byte type, @NotNull final OID oid) 115 throws ASN1Exception 116 { 117 this(type, oid, encodeValue(oid)); 118 } 119 120 121 122 /** 123 * Creates a new ASN.1 object identifier element with the default BER type and 124 * the provided OID. 125 * 126 * @param oidString The string representation of the OID to represent with 127 * this element. It must not be {@code null}, and it must 128 * represent a valid OID. 129 * 130 * @throws ASN1Exception If the provided OID does not strictly adhere to the 131 * numeric OID format. 132 */ 133 public ASN1ObjectIdentifier(@NotNull final String oidString) 134 throws ASN1Exception 135 { 136 this(ASN1Constants.UNIVERSAL_OBJECT_IDENTIFIER_TYPE, oidString); 137 } 138 139 140 141 /** 142 * Creates a new ASN.1 object identifier element with the specified BER type 143 * and the provided OID. 144 * 145 * @param type The BER type for this element. 146 * @param oidString The string representation of the OID to represent with 147 * this element. It must not be {@code null}, and it must 148 * represent a valid OID. 149 * 150 * @throws ASN1Exception If the provided OID does not strictly adhere to the 151 * numeric OID format. 152 */ 153 public ASN1ObjectIdentifier(final byte type, 154 @NotNull final String oidString) 155 throws ASN1Exception 156 { 157 this(type, new OID(oidString)); 158 } 159 160 161 162 /** 163 * Creates a new ASN.1 object identifier element with the provided 164 * information. 165 * 166 * @param type The BER type to use for this element. 167 * @param oid The OID to represent with this element. 168 * @param encodedValue The encoded value for this element. 169 */ 170 private ASN1ObjectIdentifier(final byte type, @NotNull final OID oid, 171 @NotNull final byte[] encodedValue) 172 { 173 super(type, encodedValue); 174 175 this.oid = oid; 176 } 177 178 179 180 /** 181 * Generates an encoded value for an object identifier element with the 182 * provided OID. 183 * 184 * @param oid The OID to represent with this element. It must not be 185 * {@code null}, and it must represent a valid OID. 186 * 187 * @return The encoded value. 188 * 189 * @throws ASN1Exception If the provided OID does not strictly conform to 190 * the requirements for ASN.1 OIDs. 191 */ 192 @NotNull() 193 private static byte[] encodeValue(@NotNull final OID oid) 194 throws ASN1Exception 195 { 196 // Make sure that the provided UID conforms to the necessary constraints. 197 if (! oid.isValidNumericOID()) 198 { 199 throw new ASN1Exception(ERR_OID_ENCODE_NOT_NUMERIC.get()); 200 } 201 202 final List<Integer> components = oid.getComponents(); 203 if (components.size() < 2) 204 { 205 throw new ASN1Exception(ERR_OID_ENCODE_NOT_ENOUGH_COMPONENTS.get( 206 oid.toString())); 207 } 208 209 final Iterator<Integer> componentIterator = components.iterator(); 210 211 final int firstComponent = componentIterator.next(); 212 if ((firstComponent < 0) || (firstComponent > 2)) 213 { 214 throw new ASN1Exception(ERR_OID_ENCODE_INVALID_FIRST_COMPONENT.get( 215 oid.toString(), firstComponent)); 216 } 217 218 final int secondComponent = componentIterator.next(); 219 if ((secondComponent < 0) || 220 ((firstComponent != 2) && (secondComponent > 39))) 221 { 222 throw new ASN1Exception(ERR_OID_ENCODE_INVALID_SECOND_COMPONENT.get( 223 oid.toString(), firstComponent, secondComponent)); 224 } 225 226 227 // Construct the encoded representation of the OID. Compute it as follows: 228 // - The first and second components are merged together by multiplying the 229 // value of the first component by 40 and adding the value of the second 230 // component. Every other component is handled individually. 231 // - For components (including the merged first and second components) whose 232 // value is less than or equal to 127, the encoded representation of that 233 // component is simply the single-byte encoded representation of that 234 // number. 235 // - For components (including the merged first and second components) whose 236 // value is greater than 127, that component must be encoded in multiple 237 // bytes. In the encoded representation, only the lower seven bits of 238 // each byte will be used to convey the value. The most significant bit 239 // of each byte will be used to indicate whether there are more bytes in 240 // the component. 241 final ByteStringBuffer buffer = new ByteStringBuffer(); 242 final int mergedFirstComponents = (40 * firstComponent) + secondComponent; 243 encodeComponent(mergedFirstComponents, buffer); 244 while (componentIterator.hasNext()) 245 { 246 encodeComponent(componentIterator.next(), buffer); 247 } 248 249 return buffer.toByteArray(); 250 } 251 252 253 254 /** 255 * Appends an encoded representation of the provided component value to the 256 * given buffer. 257 * 258 * @param c The value of the component to encode. 259 * @param b The buffer to which the encoded representation should be 260 * appended. 261 */ 262 private static void encodeComponent(final int c, 263 @NotNull final ByteStringBuffer b) 264 { 265 final int finalByte = c & 0b1111111; 266 if (finalByte == c) 267 { 268 b.append((byte) finalByte); 269 } 270 else if ((c & 0b1111111_1111111) == c) 271 { 272 b.append((byte) (0b10000000 | ((c >> 7) & 0b1111111))); 273 b.append((byte) finalByte); 274 } 275 else if ((c & 0b1111111_1111111_1111111) == c) 276 { 277 b.append((byte) (0b10000000 | ((c >> 14) & 0b1111111))); 278 b.append((byte) (0b10000000 | ((c >> 7) & 0b1111111))); 279 b.append((byte) finalByte); 280 } 281 else if ((c & 0b1111111_1111111_1111111_1111111) == c) 282 { 283 b.append((byte) (0b10000000 | ((c >> 21) & 0b1111111))); 284 b.append((byte) (0b10000000 | ((c >> 14) & 0b1111111))); 285 b.append((byte) (0b10000000 | ((c >> 7) & 0b1111111))); 286 b.append((byte) finalByte); 287 } 288 else 289 { 290 b.append((byte) (0b10000000 | ((c >> 28) & 0b1111111))); 291 b.append((byte) (0b10000000 | ((c >> 21) & 0b1111111))); 292 b.append((byte) (0b10000000 | ((c >> 14) & 0b1111111))); 293 b.append((byte) (0b10000000 | ((c >> 7) & 0b1111111))); 294 b.append((byte) finalByte); 295 } 296 } 297 298 299 300 /** 301 * Retrieves the OID represented by this object identifier element. 302 * 303 * @return The OID represented by this object identifier element. 304 */ 305 @NotNull() 306 public OID getOID() 307 { 308 return oid; 309 } 310 311 312 313 /** 314 * Decodes the contents of the provided byte array as an object identifier 315 * element. 316 * 317 * @param elementBytes The byte array to decode as an ASN.1 object 318 * identifier element. 319 * 320 * @return The decoded ASN.1 object identifier element. 321 * 322 * @throws ASN1Exception If the provided array cannot be decoded as an 323 * object identifier element. 324 */ 325 @NotNull() 326 public static ASN1ObjectIdentifier decodeAsObjectIdentifier( 327 @NotNull final byte[] elementBytes) 328 throws ASN1Exception 329 { 330 try 331 { 332 int valueStartPos = 2; 333 int length = (elementBytes[1] & 0x7F); 334 if (length != elementBytes[1]) 335 { 336 final int numLengthBytes = length; 337 338 length = 0; 339 for (int i=0; i < numLengthBytes; i++) 340 { 341 length <<= 8; 342 length |= (elementBytes[valueStartPos++] & 0xFF); 343 } 344 } 345 346 if ((elementBytes.length - valueStartPos) != length) 347 { 348 throw new ASN1Exception(ERR_ELEMENT_LENGTH_MISMATCH.get(length, 349 (elementBytes.length - valueStartPos))); 350 } 351 352 final byte[] elementValue = new byte[length]; 353 System.arraycopy(elementBytes, valueStartPos, elementValue, 0, length); 354 final OID oid = decodeValue(elementValue); 355 return new ASN1ObjectIdentifier(elementBytes[0], oid, elementValue); 356 } 357 catch (final ASN1Exception ae) 358 { 359 Debug.debugException(ae); 360 throw ae; 361 } 362 catch (final Exception e) 363 { 364 Debug.debugException(e); 365 throw new ASN1Exception(ERR_ELEMENT_DECODE_EXCEPTION.get(e), e); 366 } 367 } 368 369 370 371 /** 372 * Decodes the provided ASN.1 element as an object identifier element. 373 * 374 * @param element The ASN.1 element to be decoded. 375 * 376 * @return The decoded ASN.1 object identifier element. 377 * 378 * @throws ASN1Exception If the provided element cannot be decoded as an 379 * object identifier element. 380 */ 381 @NotNull() 382 public static ASN1ObjectIdentifier decodeAsObjectIdentifier( 383 @NotNull final ASN1Element element) 384 throws ASN1Exception 385 { 386 final OID oid = decodeValue(element.getValue()); 387 return new ASN1ObjectIdentifier(element.getType(), oid, element.getValue()); 388 } 389 390 391 392 /** 393 * Decodes the provided value as an OID. 394 * 395 * @param elementValue The bytes that comprise the encoded value for an 396 * object identifier element. 397 * 398 * @return The decoded OID. 399 * 400 * @throws ASN1Exception If the provided value cannot be decoded as a valid 401 * OID. 402 */ 403 @NotNull() 404 private static OID decodeValue(@NotNull final byte[] elementValue) 405 throws ASN1Exception 406 { 407 if (elementValue.length == 0) 408 { 409 throw new ASN1Exception(ERR_OID_DECODE_EMPTY_VALUE.get()); 410 } 411 412 final byte lastByte = elementValue[elementValue.length - 1]; 413 if ((lastByte & 0x80) == 0x80) 414 { 415 throw new ASN1Exception(ERR_OID_DECODE_INCOMPLETE_VALUE.get()); 416 } 417 418 int currentComponent = 0x00; 419 final ArrayList<Integer> components = new ArrayList<>(elementValue.length); 420 for (final byte b : elementValue) 421 { 422 currentComponent <<= 7; 423 currentComponent |= (b & 0x7F); 424 if ((b & 0x80) == 0x00) 425 { 426 if (components.isEmpty()) 427 { 428 if (currentComponent < 40) 429 { 430 components.add(0); 431 components.add(currentComponent); 432 } 433 else if (currentComponent < 80) 434 { 435 components.add(1); 436 components.add(currentComponent - 40); 437 } 438 else 439 { 440 components.add(2); 441 components.add(currentComponent - 80); 442 } 443 } 444 else 445 { 446 components.add(currentComponent); 447 } 448 449 currentComponent = 0x00; 450 } 451 } 452 453 return new OID(components); 454 } 455 456 457 458 /** 459 * {@inheritDoc} 460 */ 461 @Override() 462 public void toString(@NotNull final StringBuilder buffer) 463 { 464 buffer.append(oid.toString()); 465 } 466}