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}