001/* 002 * Copyright 2022-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2022-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) 2022-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.ldap.sdk.unboundidds.logs.v2.syntax; 037 038 039 040import java.security.MessageDigest; 041import java.util.LinkedList; 042 043import com.unboundid.ldap.sdk.LDAPException; 044import com.unboundid.ldap.sdk.LDAPRuntimeException; 045import com.unboundid.ldap.sdk.ResultCode; 046import com.unboundid.util.Base64; 047import com.unboundid.util.ByteStringBuffer; 048import com.unboundid.util.CryptoHelper; 049import com.unboundid.util.Debug; 050import com.unboundid.util.NotExtensible; 051import com.unboundid.util.NotNull; 052import com.unboundid.util.StaticUtils; 053import com.unboundid.util.ThreadSafety; 054import com.unboundid.util.ThreadSafetyLevel; 055import com.unboundid.util.json.JSONBuffer; 056 057import static com.unboundid.ldap.sdk.unboundidds.logs.v2.syntax. 058 LogSyntaxMessages.*; 059 060 061 062/** 063 * This class defines the base class for syntaxes that may be used for field 064 * values in log messages. 065 * <BR> 066 * <BLOCKQUOTE> 067 * <B>NOTE:</B> This class, and other classes within the 068 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 069 * supported for use against Ping Identity, UnboundID, and 070 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 071 * for proprietary functionality or for external specifications that are not 072 * considered stable or mature enough to be guaranteed to work in an 073 * interoperable way with other types of LDAP servers. 074 * </BLOCKQUOTE> 075 * 076 * @param <T> The type of value represented by this syntax. 077 */ 078@NotExtensible() 079@ThreadSafety(level=ThreadSafetyLevel.INTERFACE_THREADSAFE) 080public abstract class LogFieldSyntax<T> 081{ 082 /** 083 * The code point that represents the ASCII carriage return character. 084 */ 085 protected static final int CARRIAGE_RETURN_CODE_POINT = 0x0D; 086 087 088 089 /** 090 * The code point that represents the ASCII double quote character. 091 */ 092 protected static final int DOUBLE_QUOTE_CODE_POINT = 0x22; 093 094 095 096 /** 097 * The code point that represents the ASCII newline character. 098 */ 099 protected static final int NEWLINE_CODE_POINT = 0x0A; 100 101 102 103 /** 104 * The code point that represents the ASCII octothorpe character. 105 */ 106 protected static final int OCTOTHORPE_CODE_POINT = 0x23; 107 108 109 110 /** 111 * The code point that represents the ASCII tab character. 112 */ 113 protected static final int TAB_CODE_POINT = 0x09; 114 115 116 117 /** 118 * A string that will be used to indicate that the value has been redacted. 119 */ 120 @NotNull public static final String REDACTED_STRING = "{REDACTED}"; 121 122 123 124 /** 125 * A prefix that will be used before a token in a tokenized value. 126 */ 127 @NotNull public static final String TOKEN_PREFIX_STRING = "{TOKENIZED:"; 128 129 130 131 /** 132 * A suffix that will be used after a token in a tokenized value. 133 */ 134 @NotNull public static final String TOKEN_SUFFIX_STRING = "}"; 135 136 137 138 /** 139 * The digest algorithm used in the course of generating value tokens. 140 */ 141 @NotNull private static final String TOKEN_DIGEST_ALGORITHM = "SHA-256"; 142 143 144 145 /** 146 * The number of digest bytes to use when generating token values. 147 */ 148 private static final int TOKEN_DIGEST_BYTES_LENGTH = 12; 149 150 151 152 // The maximum length (in characters) to use for strings within values. 153 private final int maxStringLengthCharacters; 154 155 // A set of thread-local buffers that may be used in processing. 156 @NotNull private final ThreadLocal<LinkedList<ByteStringBuffer>> 157 threadLocalBuffers; 158 159 // A set of thread-local message digests that may be used in processing. 160 @NotNull private final ThreadLocal<MessageDigest> threadLocalDigests; 161 162 163 164 165 /** 166 * Creates a new instance of this log field syntax implementation. 167 * 168 * @param maxStringLengthCharacters The maximum length (in characters) to 169 * use for strings within values. Strings 170 * that are longer than this should be 171 * truncated before inclusion in the log. 172 * This value must be greater than or equal 173 * to zero. 174 */ 175 protected LogFieldSyntax(final int maxStringLengthCharacters) 176 { 177 this.maxStringLengthCharacters = maxStringLengthCharacters; 178 179 threadLocalBuffers = new ThreadLocal<>(); 180 threadLocalDigests = new ThreadLocal<>(); 181 } 182 183 184 185 /** 186 * Retrieves the maximum length (in characters) to use for strings within 187 * values. Strings that are longer than this should be truncated before 188 * inclusion in the log. 189 * 190 * @return The maximum length (in characters) to use for strings within 191 * values. 192 */ 193 protected int getMaxStringLengthCharacters() 194 { 195 return maxStringLengthCharacters; 196 } 197 198 199 200 /** 201 * Retrieves the name for this syntax. 202 * 203 * @return The name for this syntax. 204 */ 205 @NotNull() 206 public abstract String getSyntaxName(); 207 208 209 210 /** 211 * Encodes the provided value to a sanitized string representation suitable 212 * for inclusion in a log message. The sanitized string should at least be 213 * cleaned of control characters and other non-printable characters, but 214 * depending on the syntax, it may clean other characters as well. 215 * 216 * @param value The value to be encoded. It must not be {@code null}. 217 * 218 * @return The encoded representation of the value. It must not be 219 * {@code null}, but may be empty. 220 */ 221 @NotNull() 222 public String valueToSanitizedString(@NotNull final T value) 223 { 224 final ByteStringBuffer buffer = getTemporaryBuffer(); 225 try 226 { 227 valueToSanitizedString(value, buffer); 228 return buffer.toString(); 229 } 230 finally 231 { 232 releaseTemporaryBuffer(buffer); 233 } 234 } 235 236 237 238 /** 239 * Encodes the provided value to a sanitized string representation suitable 240 * for inclusion in a log message. The sanitized string should at least be 241 * cleaned of control characters and other non-printable characters, but 242 * depending on the syntax, it may clean other characters as well. 243 * 244 * @param value The value to be encoded. It must not be {@code null}. 245 * @param buffer The buffer to which the string representation should be 246 * appended. It must not be {@code null}. 247 */ 248 public abstract void valueToSanitizedString( 249 @NotNull final T value, 250 @NotNull final ByteStringBuffer buffer); 251 252 253 254 /** 255 * Appends a sanitized representation of the specified field (both field name 256 * and value) for a text-formatted log message to the given buffer. 257 * 258 * @param fieldName The name for the field. It must not be {@code null}. 259 * @param fieldValue The value to use for the field. It must not be 260 * {@code null}. 261 * @param buffer The buffer to which the sanitized log field should be 262 * appended. It must not be {@code null}. 263 */ 264 public abstract void logSanitizedFieldToTextFormattedLog( 265 @NotNull final String fieldName, 266 @NotNull final T fieldValue, 267 @NotNull final ByteStringBuffer buffer); 268 269 270 271 /** 272 * Appends a sanitized representation of the specified field (both field name 273 * and value) for a JSON-formatted log message to the given buffer. 274 * 275 * @param fieldName The name for the field. It must not be {@code null}. 276 * @param fieldValue The value to use for the field. It must not be 277 * {@code null}. 278 * @param buffer The buffer to which the sanitized log field should be 279 * appended. It must not be {@code null}. 280 */ 281 public abstract void logSanitizedFieldToJSONFormattedLog( 282 @NotNull final String fieldName, 283 @NotNull final T fieldValue, 284 @NotNull final JSONBuffer buffer); 285 286 287 288 /** 289 * Appends a sanitized representation of the provided value (without a field 290 * name, as might be suitable for a value included in a JSON array) for a 291 * JSON-formatted log message to the given buffer. 292 * 293 * @param value The value to be appended to the buffer. It must not be 294 * {@code null}. 295 * @param buffer The buffer to which the sanitized value should be appended. 296 * It must not be {@code null}. 297 */ 298 public abstract void logSanitizedValueToJSONFormattedLog( 299 @NotNull final T value, 300 @NotNull final JSONBuffer buffer); 301 302 303 304 /** 305 * Retrieves a sanitized version of the provided string. 306 * 307 * @param string The string to be sanitized. It must not be {@code null}. 308 * 309 * @return The sanitized version of the provided string. 310 */ 311 @NotNull() 312 protected final String sanitize(@NotNull final String string) 313 { 314 final ByteStringBuffer buffer = getTemporaryBuffer(); 315 try 316 { 317 sanitize(string, buffer); 318 return buffer.toString(); 319 } 320 finally 321 { 322 releaseTemporaryBuffer(buffer); 323 } 324 } 325 326 327 328 /** 329 * Appends an appropriately sanitized version of the provided string to the 330 * given buffer. 331 * 332 * @param string The string to be sanitized. It must not be {@code null}. 333 * @param buffer The buffer to which the sanitized representation should be 334 * appended. It must not be {@code null}. 335 */ 336 protected final void sanitize(@NotNull final String string, 337 @NotNull final ByteStringBuffer buffer) 338 { 339 final int numCharsToExamine; 340 final int numCharsToTruncate; 341 final int stringLength = string.length(); 342 if (stringLength > maxStringLengthCharacters) 343 { 344 numCharsToExamine = maxStringLengthCharacters; 345 numCharsToTruncate = stringLength - maxStringLengthCharacters; 346 } 347 else 348 { 349 numCharsToExamine = stringLength; 350 numCharsToTruncate = 0; 351 } 352 353 int pos = 0; 354 while (pos < numCharsToExamine) 355 { 356 final int codePoint = string.codePointAt(pos); 357 switch (codePoint) 358 { 359 case DOUBLE_QUOTE_CODE_POINT: 360 buffer.append((byte) '\''); 361 break; 362 case NEWLINE_CODE_POINT: 363 buffer.append("\\n"); 364 break; 365 case CARRIAGE_RETURN_CODE_POINT: 366 buffer.append("\\r"); 367 break; 368 case TAB_CODE_POINT: 369 buffer.append("\\t"); 370 break; 371 case OCTOTHORPE_CODE_POINT: 372 buffer.append("#23"); 373 break; 374 default: 375 if (StaticUtils.isLikelyDisplayableCharacter(codePoint)) 376 { 377 buffer.appendCodePoint(codePoint); 378 } 379 else 380 { 381 for (final byte b : StaticUtils.getBytesForCodePoint(codePoint)) 382 { 383 buffer.append('#'); 384 StaticUtils.toHex(b, buffer); 385 } 386 } 387 break; 388 } 389 390 pos += Character.charCount(codePoint); 391 } 392 393 if (numCharsToTruncate > 0) 394 { 395 if (numCharsToTruncate == 1) 396 { 397 buffer.append( 398 INFO_LOG_SYNTAX_TRUNCATED_1_CHAR.get()); 399 } 400 else 401 { 402 buffer.append( 403 INFO_LOG_SYNTAX_TRUNCATED_CHARS.get(numCharsToTruncate)); 404 } 405 } 406 } 407 408 409 410 /** 411 * Attempts to parse the provided string as a value in accordance with this 412 * syntax. 413 * 414 * @param valueString The string to be parsed. 415 * 416 * @return The value that was parsed. 417 * 418 * @throws RedactedValueException If the provided value has been redacted 419 * (either the complete value or one or more 420 * of its components), and the redacted form 421 * cannot be represented in this syntax. 422 * 423 * @throws TokenizedValueException If the provided value has been tokenized 424 * (either the complete value or one or more 425 * of its components), and the redacted form 426 * cannot be represented in this syntax. 427 * 428 * @throws LogSyntaxException If the provided value cannot be parsed in 429 * accordance with this syntax. 430 */ 431 @NotNull() 432 public abstract T parseValue(@NotNull final String valueString) 433 throws RedactedValueException, TokenizedValueException, 434 LogSyntaxException; 435 436 437 438 /** 439 * Determines whether the provided value string represents a value that has 440 * been completely redacted. 441 * 442 * @param valueString The value for which to make the determination. It 443 * must not be {@code null}. 444 * 445 * @return {@code true} if the provided value string represents a value that 446 * has been completely redacted, or {@code false} if not. 447 */ 448 public boolean valueStringIsCompletelyRedacted( 449 @NotNull final String valueString) 450 { 451 return valueString.equals(REDACTED_STRING); 452 } 453 454 455 456 /** 457 * Indicates whether values that have been completely redacted still conform 458 * to this syntax. 459 * 460 * @return {@code true} if values that have been completely redacted still 461 * conform to this syntax, or {@code false} if not. 462 */ 463 public abstract boolean completelyRedactedValueConformsToSyntax(); 464 465 466 467 /** 468 * Retrieves a string that may be included in a log message to indicate that 469 * the entire value for a field with this syntax has been redacted. 470 * 471 * @return A string that may be included in a log message to 472 * indicate that the entire value for a field with this syntax has 473 * been redacted. 474 */ 475 @NotNull() 476 public String redactEntireValue() 477 { 478 final ByteStringBuffer buffer = getTemporaryBuffer(); 479 try 480 { 481 redactEntireValue(buffer); 482 return buffer.toString(); 483 } 484 finally 485 { 486 releaseTemporaryBuffer(buffer); 487 } 488 } 489 490 491 492 /** 493 * Appends a string representation of a redacted entire value to the provided 494 * buffer. 495 * 496 * @param buffer The buffer to which the redacted string representation 497 * should be appended. It must not be {@code null}. 498 */ 499 public void redactEntireValue(@NotNull final ByteStringBuffer buffer) 500 { 501 buffer.append(REDACTED_STRING); 502 } 503 504 505 506 /** 507 * Appends a completely redacted representation of the specified field (both 508 * field name and value) for a text-formatted log message to the given buffer. 509 * 510 * @param fieldName The name for the field. It must not be {@code null}. 511 * @param buffer The buffer to which the sanitized log field should be 512 * appended. It must not be {@code null}. 513 */ 514 public abstract void logCompletelyRedactedFieldToTextFormattedLog( 515 @NotNull final String fieldName, 516 @NotNull final ByteStringBuffer buffer); 517 518 519 520 /** 521 * Appends a completely redacted representation of the specified field (both 522 * field name and value) for a JSON-formatted log message to the given buffer. 523 * 524 * @param fieldName The name for the field. It must not be {@code null}. 525 * @param buffer The buffer to which the sanitized log field should be 526 * appended. It must not be {@code null}. 527 */ 528 public abstract void logCompletelyRedactedFieldToJSONFormattedLog( 529 @NotNull final String fieldName, 530 @NotNull final JSONBuffer buffer); 531 532 533 534 /** 535 * Appends a completely redacted representation of a value (without a field 536 * name, as might be suitable for a value included in a JSON array) for a 537 * JSON-formatted log message to the given buffer. 538 * 539 * @param buffer The buffer to which the redacted value should be appended. 540 * It must not be {@code null}. 541 */ 542 public abstract void logCompletelyRedactedValueToJSONFormattedLog( 543 @NotNull final JSONBuffer buffer); 544 545 546 547 /** 548 * Indicates whether this syntax supports redacting individual components of 549 * the entire value. 550 * 551 * @return {@code true} if this syntax supports redacting individual 552 * components of the entire value, or {@code false} if not. 553 */ 554 public abstract boolean supportsRedactedComponents(); 555 556 557 558 /** 559 * Determines whether the provided value string represents a value that has 560 * had one or more components redacted. 561 * 562 * @param valueString The value for which to make the determination. It 563 * must not be {@code null}. 564 * 565 * @return {@code true} if the provided value string represents a value that 566 * has had one or more components redacted, or {@code false} if not. 567 */ 568 public boolean valueStringIncludesRedactedComponent( 569 @NotNull final String valueString) 570 { 571 return valueString.contains(REDACTED_STRING); 572 } 573 574 575 576 /** 577 * Indicates whether values with one or more redacted components still conform 578 * to this syntax. 579 * 580 * @return {@code true} if values with one or more redacted components still 581 * conform to this syntax. 582 */ 583 public abstract boolean valueWithRedactedComponentsConformsToSyntax(); 584 585 586 587 /** 588 * Retrieves a string that provides a representation of the given value with 589 * zero or more of its components redacted. If this syntax does not support 590 * redacted components, then the entire value should be redacted. 591 * 592 * @param value The value for which to obtain the redacted representation. 593 * It must not be {@code null}. 594 * 595 * @return A string representation of the given value with zero or more of 596 * its components redacted. 597 */ 598 @NotNull() 599 public String redactComponents(@NotNull final T value) 600 { 601 final ByteStringBuffer buffer = getTemporaryBuffer(); 602 try 603 { 604 redactComponents(value, buffer); 605 return buffer.toString(); 606 } 607 finally 608 { 609 releaseTemporaryBuffer(buffer); 610 } 611 } 612 613 614 615 /** 616 * Appends a string representation of the given value with redacted components 617 * to the provided buffer. 618 * 619 * @param value The value for which to obtain the redacted representation. 620 * It must not be {@code null}. 621 * @param buffer The buffer to which the redacted string representation 622 * should be appended. It must not be {@code null}. 623 */ 624 public void redactComponents(@NotNull final T value, 625 @NotNull final ByteStringBuffer buffer) 626 { 627 redactEntireValue(buffer); 628 } 629 630 631 632 /** 633 * Appends a representation of the specified field (both field name and value) 634 * with redacted value components for a text-formatted log message to the 635 * given buffer. If this syntax does not support redacting components within 636 * a value, then it should redact the entire value. 637 * 638 * @param fieldName The name for the field. It must not be {@code null}. 639 * @param fieldValue The value to use for the field. It must not be 640 * {@code null}. 641 * @param buffer The buffer to which the sanitized log field should be 642 * appended. It must not be {@code null}. 643 */ 644 public abstract void logRedactedComponentsFieldToTextFormattedLog( 645 @NotNull final String fieldName, 646 @NotNull final T fieldValue, 647 @NotNull final ByteStringBuffer buffer); 648 649 650 651 /** 652 * Appends a representation of the specified field (both field name and value) 653 * with redacted value components for a JSON-formatted log message to the 654 * given buffer. If this syntax does not support redacting components within 655 * a value, then it should redact the entire value. 656 * 657 * @param fieldName The name for the field. It must not be {@code null}. 658 * @param fieldValue The value to use for the field. It must not be 659 * {@code null}. 660 * @param buffer The buffer to which the sanitized log field should be 661 * appended. It must not be {@code null}. 662 */ 663 public abstract void logRedactedComponentsFieldToJSONFormattedLog( 664 @NotNull final String fieldName, 665 @NotNull final T fieldValue, 666 @NotNull final JSONBuffer buffer); 667 668 669 670 /** 671 * Appends a representation of the provided value (without a field name, as 672 * might be suitable for a value included in a JSON array) with redacted 673 * components for a JSON-formatted log message to the given buffer. If this 674 * syntax does not support redacting components within a value, then it should 675 * redact the entire value. 676 * 677 * @param value The value to be appended to the buffer in redacted form. 678 * It must not be {@code null}. 679 * @param buffer The buffer to which the redacted value should be appended. 680 * It must not be {@code null}. 681 */ 682 public abstract void logRedactedComponentsValueToJSONFormattedLog( 683 @NotNull final T value, 684 @NotNull final JSONBuffer buffer); 685 686 687 688 /** 689 * Determines whether the provided value string represents a value that has 690 * been completely tokenized. 691 * 692 * @param valueString The value for which to make the determination. It 693 * must not be {@code null}. 694 * 695 * @return {@code true} if the provided value string represents a value that 696 * has been completely tokenized, or {@code false} if not. 697 */ 698 public boolean valueStringIsCompletelyTokenized( 699 @NotNull final String valueString) 700 { 701 return (valueString.startsWith(TOKEN_PREFIX_STRING) && 702 valueString.endsWith(TOKEN_SUFFIX_STRING) && 703 (valueString.indexOf(TOKEN_PREFIX_STRING, 704 TOKEN_PREFIX_STRING.length()) < 0)); 705 } 706 707 708 709 /** 710 * Indicates whether values that have been completely tokenized still conform 711 * to this syntax. 712 * 713 * @return {@code true} if values that have been completely tokenized still 714 * conform to this syntax, or {@code false} if not. 715 */ 716 public abstract boolean completelyTokenizedValueConformsToSyntax(); 717 718 719 720 /** 721 * Retrieves a string that represents a tokenized representation of the 722 * provided value. 723 * <BR><BR> 724 * The resulting token will protect the provided value by representing it in a 725 * way that makes it at infeasible to determine what the original value was. 726 * However, tokenizing the same value with the same pepper should consistently 727 * yield the same token value, so that it will be possible to identify the 728 * same value across multiple log messages. 729 * 730 * @param value The value for which to generate the token. It must not be 731 * {@code null}. 732 * @param pepper A pepper used to provide brute-force protection for the 733 * resulting token. The pepper value should be kept secret so 734 * that it is not available to unauthorized users who might be 735 * able to view log information, although the same pepper 736 * value should be consistently provided when tokenizing 737 * values so that the same value will consistently yield the 738 * same token. It must not be {@code null} and should not be 739 * empty. 740 * 741 * @return A string that represents a tokenized representation of the 742 * provided value. 743 */ 744 @NotNull() 745 public String tokenizeEntireValue(@NotNull final T value, 746 @NotNull final byte[] pepper) 747 { 748 final ByteStringBuffer buffer = getTemporaryBuffer(); 749 try 750 { 751 tokenizeEntireValue(value, pepper, buffer); 752 return buffer.toString(); 753 } 754 finally 755 { 756 releaseTemporaryBuffer(buffer); 757 } 758 } 759 760 761 762 /** 763 * Appends a tokenized representation of the provided value to the given 764 * buffer. 765 * <BR><BR> 766 * The resulting token will protect the provided value by representing it in a 767 * way that makes it at infeasible to determine what the original value was. 768 * However, tokenizing the same value with the same pepper should consistently 769 * yield the same token value, so that it will be possible to identify the 770 * same value across multiple log messages. 771 * 772 * @param value The value for which to generate the token. It must not be 773 * {@code null}. 774 * @param pepper A pepper used to provide brute-force protection for the 775 * resulting token. The pepper value should be kept secret so 776 * that it is not available to unauthorized users who might be 777 * able to view log information, although the same pepper 778 * value should be consistently provided when tokenizing 779 * values so that the same value will consistently yield the 780 * same token. It must not be {@code null} and should not be 781 * empty. 782 * @param buffer The buffer to which the tokenized representation should be 783 * appended. It must not be {@code null}. 784 */ 785 public abstract void tokenizeEntireValue(@NotNull final T value, 786 @NotNull final byte[] pepper, 787 @NotNull final ByteStringBuffer buffer); 788 789 790 791 /** 792 * Appends a completely tokenized representation of the specified field (both 793 * field name and value) for a text-formatted log message to the given buffer. 794 * 795 * @param fieldName The name for the field. It must not be {@code null}. 796 * @param fieldValue The value to use for the field. It must not be 797 * {@code null}. 798 * @param pepper A pepper used to provide brute-force protection for the 799 * resulting token. The pepper value should be kept 800 * secret so that it is not available to unauthorized 801 * users who might be able to view log information, 802 * although the same pepper value should be consistently 803 * provided when tokenizing values so that the same value 804 * will consistently yield the same token. It must not be 805 * {@code null} and should not be empty. 806 * @param buffer The buffer to which the sanitized log field should be 807 * appended. It must not be {@code null}. 808 */ 809 public abstract void logCompletelyTokenizedFieldToTextFormattedLog( 810 @NotNull final String fieldName, 811 @NotNull final T fieldValue, 812 @NotNull final byte[] pepper, 813 @NotNull final ByteStringBuffer buffer); 814 815 816 817 /** 818 * Appends a completely tokenized representation of the specified field (both 819 * field name and value) for a JSON-formatted log message to the given buffer. 820 * 821 * @param fieldName The name for the field. It must not be {@code null}. 822 * @param fieldValue The value to use for the field. It must not be 823 * {@code null}. 824 * @param pepper A pepper used to provide brute-force protection for the 825 * resulting token. The pepper value should be kept 826 * secret so that it is not available to unauthorized 827 * users who might be able to view log information, 828 * although the same pepper value should be consistently 829 * provided when tokenizing values so that the same value 830 * will consistently yield the same token. It must not be 831 * {@code null} and should not be empty. 832 * @param buffer The buffer to which the sanitized log field should be 833 * appended. It must not be {@code null}. 834 */ 835 public abstract void logCompletelyTokenizedFieldToJSONFormattedLog( 836 @NotNull final String fieldName, 837 @NotNull final T fieldValue, 838 @NotNull final byte[] pepper, 839 @NotNull final JSONBuffer buffer); 840 841 842 843 /** 844 * Appends a completely tokenized representation of the provided value 845 * (without a field name, as might be suitable for a value included in a JSON 846 * array) for a JSON-formatted log message to the given buffer. 847 * 848 * @param value The value to be appended to the buffer in tokenized form. 849 * It must not be {@code null}. 850 * @param pepper A pepper used to provide brute-force protection for the 851 * resulting token. The pepper value should be kept secret so 852 * that it is not available to unauthorized users who might be 853 * able to view log information, although the same pepper 854 * value should be consistently provided when tokenizing 855 * values so that the same value will consistently yield the 856 * same token. It must not be {@code null} and should not be 857 * empty. 858 * @param buffer The buffer to which the tokenized value should be appended. 859 * It must not be {@code null}. 860 */ 861 public abstract void logCompletelyTokenizedValueToJSONFormattedLog( 862 @NotNull final T value, 863 @NotNull final byte[] pepper, 864 @NotNull final JSONBuffer buffer); 865 866 867 868 /** 869 * Indicates whether this syntax supports tokenizing individual components of 870 * the entire value. 871 * 872 * @return {@code true} if this syntax supports tokenizing individual 873 * components of the entire value, or {@code false} if not. 874 */ 875 public abstract boolean supportsTokenizedComponents(); 876 877 878 879 /** 880 * Determines whether the provided value string represents a value that has 881 * had one or more components tokenized. 882 * 883 * @param valueString The value for which to make the determination. It 884 * must not be {@code null}. 885 * 886 * @return {@code true} if the provided value string represents a value that 887 * has had one or more components tokenized, or {@code false} if not. 888 */ 889 public boolean valueStringIncludesTokenizedComponent( 890 @NotNull final String valueString) 891 { 892 final int tokenStartPos = valueString.indexOf(TOKEN_PREFIX_STRING); 893 return ((tokenStartPos >= 0) && 894 (valueString.indexOf(TOKEN_SUFFIX_STRING, 895 TOKEN_PREFIX_STRING.length()) > 0)); 896 } 897 898 899 900 /** 901 * Indicates whether values with one or more tokenized components still 902 * conform to this syntax. 903 * 904 * @return {@code true} if values with one or more tokenized components still 905 * conform to this syntax. 906 */ 907 public abstract boolean valueWithTokenizedComponentsConformsToSyntax(); 908 909 910 911 /** 912 * Retrieves a string that provides a representation of the given value with 913 * zero or more of its components tokenized. If this syntax does not support 914 * tokenized components, then the entire value should be tokenized. 915 * <BR><BR> 916 * The resulting tokens will protect components of the provided value by 917 * representing them in a way that makes it at infeasible to determine what 918 * the original components were. However, tokenizing the same value with the 919 * same pepper should consistently yield the same token value, so that it will 920 * be possible to identify the same value across multiple log messages. 921 * 922 * @param value The value whose components should be tokenized. It must 923 * not be {@code null}. 924 * @param pepper A pepper used to provide brute-force protection for the 925 * resulting token. The pepper value should be kept secret so 926 * that it is not available to unauthorized users who might be 927 * able to view log information, although the same pepper 928 * value should be consistently provided when tokenizing 929 * values so that the same value will consistently yield the 930 * same token. It must not be {@code null} and should not be 931 * empty. 932 * 933 * @return A string that represents a tokenized representation of the 934 * provided value. 935 */ 936 @NotNull() 937 public String tokenizeComponents(@NotNull final T value, 938 @NotNull final byte[] pepper) 939 { 940 final ByteStringBuffer buffer = getTemporaryBuffer(); 941 try 942 { 943 tokenizeComponents(value, pepper, buffer); 944 return buffer.toString(); 945 } 946 finally 947 { 948 releaseTemporaryBuffer(buffer); 949 } 950 } 951 952 953 954 /** 955 * Appends a string representation of the given value with zero or more of its 956 * components tokenized to the provided buffer. If this syntax does not 957 * support tokenized components, then the entire value should be tokenized. 958 * <BR><BR> 959 * The resulting tokens will protect components of the provided value by 960 * representing them in a way that makes it at infeasible to determine what 961 * the original components were. However, tokenizing the same value with the 962 * same pepper should consistently yield the same token value, so that it will 963 * be possible to identify the same value across multiple log messages. 964 * 965 * @param value The value whose components should be tokenized. It must 966 * not be {@code null}. 967 * @param pepper A pepper used to provide brute-force protection for the 968 * resulting token. The pepper value should be kept secret so 969 * that it is not available to unauthorized users who might be 970 * able to view log information, although the same pepper 971 * value should be consistently provided when tokenizing 972 * values so that the same value will consistently yield the 973 * same token. It must not be {@code null} and should not be 974 * empty. 975 * @param buffer The buffer to which the tokenized representation should be 976 * appended. It must not be {@code null}. 977 */ 978 public void tokenizeComponents(@NotNull final T value, 979 @NotNull final byte[] pepper, 980 @NotNull final ByteStringBuffer buffer) 981 { 982 tokenizeEntireValue(value, pepper, buffer); 983 } 984 985 986 987 /** 988 * Appends a representation of the specified field (both field name and value) 989 * with tokenized value components for a text-formatted log message to the 990 * given buffer. If this syntax does not support tokenizing components within 991 * a value, then it should tokenize the entire value. 992 * 993 * @param fieldName The name for the field. It must not be {@code null}. 994 * @param fieldValue The value to use for the field. It must not be 995 * {@code null}. 996 * @param pepper A pepper used to provide brute-force protection for the 997 * resulting token. The pepper value should be kept 998 * secret so that it is not available to unauthorized 999 * users who might be able to view log information, 1000 * although the same pepper value should be consistently 1001 * provided when tokenizing values so that the same value 1002 * will consistently yield the same token. It must not be 1003 * {@code null} and should not be empty. 1004 * @param buffer The buffer to which the sanitized log field should be 1005 * appended. It must not be {@code null}. 1006 */ 1007 public abstract void logTokenizedComponentsFieldToTextFormattedLog( 1008 @NotNull final String fieldName, 1009 @NotNull final T fieldValue, 1010 @NotNull final byte[] pepper, 1011 @NotNull final ByteStringBuffer buffer); 1012 1013 1014 1015 /** 1016 * Appends a representation of the specified field (both field name and value) 1017 * with tokenized value components for a JSON-formatted log message to the 1018 * given buffer. If this syntax does not support tokenizing components within 1019 * a value, then it should tokenize the entire value. 1020 * 1021 * @param fieldName The name for the field. It must not be {@code null}. 1022 * @param fieldValue The value to use for the field. It must not be 1023 * {@code null}. 1024 * @param pepper A pepper used to provide brute-force protection for the 1025 * resulting token. The pepper value should be kept 1026 * secret so that it is not available to unauthorized 1027 * users who might be able to view log information, 1028 * although the same pepper value should be consistently 1029 * provided when tokenizing values so that the same value 1030 * will consistently yield the same token. It must not be 1031 * {@code null} and should not be empty. 1032 * @param buffer The buffer to which the sanitized log field should be 1033 * appended. It must not be {@code null}. 1034 */ 1035 public abstract void logTokenizedComponentsFieldToJSONFormattedLog( 1036 @NotNull final String fieldName, 1037 @NotNull final T fieldValue, 1038 @NotNull final byte[] pepper, 1039 @NotNull final JSONBuffer buffer); 1040 1041 1042 1043 /** 1044 * Appends a representation of the provided value (without a field name, as 1045 * might be suitable for a value included in a JSON array) with tokenized 1046 * value components for a JSON-formatted log message to the given buffer. If 1047 * this syntax does not support tokenizing components within a value, then it 1048 * should tokenize the entire value. 1049 * 1050 * @param value The value to be appended to the buffer in tokenized form. 1051 * It must not be {@code null}. 1052 * @param pepper A pepper used to provide brute-force protection for the 1053 * resulting token. The pepper value should be kept secret so 1054 * that it is not available to unauthorized users who might be 1055 * able to view log information, although the same pepper 1056 * value should be consistently provided when tokenizing 1057 * values so that the same value will consistently yield the 1058 * same token. It must not be {@code null} and should not be 1059 * empty. 1060 * @param buffer The buffer to which the tokenized value should be appended. 1061 * It must not be {@code null}. 1062 */ 1063 public abstract void logTokenizedComponentsValueToJSONFormattedLog( 1064 @NotNull final T value, 1065 @NotNull final byte[] pepper, 1066 @NotNull final JSONBuffer buffer); 1067 1068 1069 1070 /** 1071 * Retrieves a tokenized representation of the provided string. 1072 * 1073 * @param string The string to be tokenized. It must not be {@code null}. 1074 * @param pepper A pepper used to provide brute-force protection for the 1075 * resulting token. The pepper value should be kept secret so 1076 * that it is not available to unauthorized users who might be 1077 * able to view log information, although the same pepper 1078 * value should be consistently provided when tokenizing 1079 * values so that the same value will consistently yield the 1080 * same token. It must not be {@code null} and should not be 1081 * empty. 1082 * 1083 * @return A tokenized representation of the provided string. 1084 */ 1085 @NotNull() 1086 protected final String tokenize(@NotNull final String string, 1087 @NotNull final byte[] pepper) 1088 { 1089 final ByteStringBuffer buffer = getTemporaryBuffer(); 1090 try 1091 { 1092 tokenize(string, pepper, buffer); 1093 return buffer.toString(); 1094 } 1095 finally 1096 { 1097 releaseTemporaryBuffer(buffer); 1098 } 1099 1100 } 1101 1102 1103 1104 /** 1105 * Appends a tokenized representation of the provided string to the given 1106 * buffer. 1107 * 1108 * @param string The string to be tokenized. It must not be {@code null}. 1109 * @param pepper A pepper used to provide brute-force protection for the 1110 * resulting token. The pepper value should be kept secret so 1111 * that it is not available to unauthorized users who might be 1112 * able to view log information, although the same pepper 1113 * value should be consistently provided when tokenizing 1114 * values so that the same value will consistently yield the 1115 * same token. It must not be {@code null} and should not be 1116 * empty. 1117 * @param buffer The buffer to which the tokenized representation should be 1118 * appended. It must not be {@code null}. 1119 */ 1120 protected final void tokenize(@NotNull final String string, 1121 @NotNull final byte[] pepper, 1122 @NotNull final ByteStringBuffer buffer) 1123 { 1124 tokenize(StaticUtils.getBytes(string), pepper, buffer); 1125 } 1126 1127 1128 1129 /** 1130 * Appends a tokenized representation of the provided bytes to the given 1131 * buffer. 1132 * 1133 * @param bytes The bytes to be tokenized. It must not be {@code null}. 1134 * @param pepper A pepper used to provide brute-force protection for the 1135 * resulting token. The pepper value should be kept secret so 1136 * that it is not available to unauthorized users who might be 1137 * able to view log information, although the same pepper 1138 * value should be consistently provided when tokenizing 1139 * values so that the same value will consistently yield the 1140 * same token. It must not be {@code null} and should not be 1141 * empty. 1142 * @param buffer The buffer to which the tokenized representation should be 1143 * appended. It must not be {@code null}. 1144 */ 1145 protected final void tokenize(@NotNull final byte[] bytes, 1146 @NotNull final byte[] pepper, 1147 @NotNull final ByteStringBuffer buffer) 1148 { 1149 // Concatenate the provided bytes and the pepper. 1150 final ByteStringBuffer concatBuffer = getTemporaryBuffer(); 1151 try 1152 { 1153 concatBuffer.append(bytes); 1154 concatBuffer.append(pepper); 1155 1156 1157 // Compute a digest of the concatenated value. 1158 final byte[] digestBytes = sha256(concatBuffer); 1159 1160 1161 // Base64-encode a portion of the digest to use as the token. Use the 1162 // base64url syntax to avoid including the plus and slash characters, 1163 // which might cause issues in certain cases (for example, the plus sign 1164 // needs to be escaped in DNs because it would otherwise represent the 1165 // start of the next component of a multivalued RDN). 1166 buffer.append(TOKEN_PREFIX_STRING); 1167 Base64.urlEncode(digestBytes, 0, TOKEN_DIGEST_BYTES_LENGTH, buffer, 1168 false); 1169 buffer.append(TOKEN_SUFFIX_STRING); 1170 } 1171 finally 1172 { 1173 releaseTemporaryBuffer(concatBuffer); 1174 } 1175 } 1176 1177 1178 1179 /** 1180 * Retrieves a SHA-256 digest of the contents of the provided buffer. 1181 * 1182 * @param buffer The buffer containing the data to digest. It must not be 1183 * {@code null}. 1184 * 1185 * @return The bytes that comprise the SHA-256 digest. 1186 */ 1187 @NotNull() 1188 protected final byte[] sha256(@NotNull final ByteStringBuffer buffer) 1189 { 1190 MessageDigest digest = threadLocalDigests.get(); 1191 if (digest == null) 1192 { 1193 try 1194 { 1195 digest = CryptoHelper.getMessageDigest(TOKEN_DIGEST_ALGORITHM); 1196 } 1197 catch (final Exception e) 1198 { 1199 Debug.debugException(e); 1200 throw new LDAPRuntimeException(new LDAPException( 1201 ResultCode.ENCODING_ERROR, 1202 ERR_LOG_SYNTAX_TOKENIZE_DIGEST_ERROR.get(TOKEN_DIGEST_ALGORITHM), 1203 e)); 1204 } 1205 } 1206 1207 digest.update(buffer.getBackingArray(), 0, buffer.length()); 1208 return digest.digest(); 1209 } 1210 1211 1212 1213 /** 1214 * Retrieves a temporary thread-local buffer that may be used during 1215 * processing. When it is no longer needed, the buffer should be returned 1216 * with the {@link #releaseTemporaryBuffer(ByteStringBuffer)} method. 1217 * 1218 * @return A temporary thread-local buffer that may be used during 1219 * processing. 1220 */ 1221 @NotNull() 1222 protected ByteStringBuffer getTemporaryBuffer() 1223 { 1224 LinkedList<ByteStringBuffer> bufferList = threadLocalBuffers.get(); 1225 if (bufferList == null) 1226 { 1227 bufferList = new LinkedList<>(); 1228 threadLocalBuffers.set(bufferList); 1229 } 1230 1231 if (bufferList.isEmpty()) 1232 { 1233 return new ByteStringBuffer(); 1234 } 1235 1236 return bufferList.remove(); 1237 } 1238 1239 1240 1241 /** 1242 * Releases the provided temporary buffer. 1243 * 1244 * @param buffer The buffer to release. It must not be {@code null}. 1245 */ 1246 protected void releaseTemporaryBuffer(@NotNull final ByteStringBuffer buffer) 1247 { 1248 buffer.clear(); 1249 1250 LinkedList<ByteStringBuffer> bufferList = threadLocalBuffers.get(); 1251 if (bufferList == null) 1252 { 1253 bufferList = new LinkedList<>(); 1254 threadLocalBuffers.set(bufferList); 1255 } 1256 1257 bufferList.add(buffer); 1258 } 1259}