View Javadoc
1   /*
2    *   Licensed to the Apache Software Foundation (ASF) under one
3    *   or more contributor license agreements.  See the NOTICE file
4    *   distributed with this work for additional information
5    *   regarding copyright ownership.  The ASF licenses this file
6    *   to you under the Apache License, Version 2.0 (the
7    *   "License"); you may not use this file except in compliance
8    *   with the License.  You may obtain a copy of the License at
9    *
10   *     https://www.apache.org/licenses/LICENSE-2.0
11   *
12   *   Unless required by applicable law or agreed to in writing,
13   *   software distributed under the License is distributed on an
14   *   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *   KIND, either express or implied.  See the License for the
16   *   specific language governing permissions and limitations
17   *   under the License.
18   *
19   */
20  package org.apache.directory.ldap.client.template;
21  
22  
23  import java.nio.ByteBuffer;
24  import java.nio.CharBuffer;
25  import java.nio.charset.StandardCharsets;
26  import java.util.Arrays;
27  
28  import org.apache.directory.api.i18n.I18n;
29  
30  
31  /**
32   * A buffer for storing sensitive information like passwords.  It provides 
33   * useful operations for characters such as character encoding/decoding, 
34   * whitespace trimming, and lowercasing.  It can be cleared out when operations
35   * are complete.
36   *
37   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
38   */
39  public final class MemoryClearingBuffer
40  {
41      private byte[] computedBytes;
42      private char[] computedChars;
43      private byte[] originalBytes;
44      private char[] originalChars;
45      private char[] precomputedChars;
46  
47  
48      private MemoryClearingBuffer( byte[] originalBytes, char[] originalChars, boolean trim, boolean lowerCase )
49      {
50          this.originalBytes = originalBytes;
51          this.originalChars = originalChars;
52  
53          if ( trim || lowerCase )
54          {
55              if ( this.originalChars == null )
56              {
57                  throw new UnsupportedOperationException( I18n.err( I18n.ERR_04168_TRIM_LOWERCASE_FOR_CHAR_ARRAY ) );
58              }
59  
60              char[] working = Arrays.copyOf( originalChars, originalChars.length );
61              int startIndex = 0;
62              int endIndex = working.length;
63  
64              if ( trim )
65              {
66                  // ltrim
67                  for ( ; startIndex < working.length; startIndex++ )
68                  {
69                      if ( !Character.isWhitespace( working[startIndex] ) )
70                      {
71                          break;
72                      }
73                  }
74  
75                  // rtrim
76                  for ( endIndex--; endIndex > startIndex; endIndex-- )
77                  {
78                      if ( !Character.isWhitespace( working[endIndex] ) )
79                      {
80                          break;
81                      }
82                  }
83                  endIndex++;
84              }
85  
86              if ( lowerCase )
87              {
88                  // lower case
89                  for ( int i = startIndex; i < endIndex; i++ )
90                  {
91                      working[i] = Character.toLowerCase( working[i] );
92                  }
93              }
94  
95              this.precomputedChars = new char[endIndex - startIndex];
96              System.arraycopy( working, startIndex, this.precomputedChars, 0, endIndex - startIndex );
97          }
98          else
99          {
100             this.precomputedChars = this.originalChars;
101         }
102     }
103 
104 
105     /**
106      * Creates a new instance of MemoryClearingBuffer from a 
107      * <code>byte[]</code>.
108      *
109      * @param bytes A byte[]
110      * @return A buffer
111      */
112     public static MemoryClearingBuffer newInstance( byte[] bytes )
113     {
114         return new MemoryClearingBuffer( bytes, null, false, false );
115     }
116 
117 
118     /**
119      * Creates a new instance of MemoryClearingBuffer from a 
120      * <code>char[]</code>.
121      *
122      * @param chars A char[]
123      * @return A buffer
124      */
125     public static MemoryClearingBuffer newInstance( char[] chars )
126     {
127         return new MemoryClearingBuffer( null, chars, false, false );
128     }
129 
130 
131     /**
132      * Creates a new instance of MemoryClearingBuffer from a 
133      * <code>char[]</code>, optionally performing whitespace trimming and
134      * conversion to lower case.
135      *
136      * @param chars A char[]
137      * @param trim If true, whitespace will be trimmed off of both ends of the
138      * <code>char[]</code>
139      * @param lowerCase If true, the characters will be converted to lower case
140      * @return A buffer
141      */
142     public static MemoryClearingBuffer newInstance( char[] chars, boolean trim, boolean lowerCase )
143     {
144         return new MemoryClearingBuffer( null, chars, trim, lowerCase );
145     }
146 
147 
148     /**
149      *  Clears the buffer out, filling its cells with null.
150      */
151     public void clear()
152     {
153         // clear out computed memory
154         if ( computedBytes != null )
155         {
156             Arrays.fill( computedBytes, ( byte ) 0 );
157         }
158         if ( computedChars != null )
159         {
160             Arrays.fill( computedChars, '0' );
161         }
162         if ( precomputedChars != null && precomputedChars != this.originalChars )
163         {
164             // only nullify if NOT originalChars
165             Arrays.fill( precomputedChars, '0' );
166         }
167 
168         computedBytes = null;
169         computedChars = null;
170         originalBytes = null;
171         originalChars = null;
172         precomputedChars = null;
173     }
174 
175 
176     /**
177      * Returns a UTF8 encoded <code>byte[]</code> representation of the 
178      * <code>char[]</code> used to create this buffer.
179      * 
180      * @return A byte[]
181      */
182     byte[] getComputedBytes()
183     {
184         if ( computedBytes == null )
185         {
186             ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode(
187                 CharBuffer.wrap( precomputedChars, 0, precomputedChars.length ) );
188             computedBytes = new byte[byteBuffer.remaining()];
189             byteBuffer.get( computedBytes );
190 
191             // clear out the temporary bytebuffer
192             byteBuffer.flip();
193             byte[] nullifier = new byte[byteBuffer.limit()];
194             Arrays.fill( nullifier, ( byte ) 0 );
195             byteBuffer.put( nullifier );
196         }
197         return computedBytes;
198     }
199 
200 
201     /**
202      * Returns a UTF8 decoded <code>char[]</code> representation of the 
203      * <code>byte[]</code> used to create this buffer.
204      *
205      * @return A char[]
206      */
207     private char[] getComputedChars()
208     {
209         if ( computedChars == null )
210         {
211             CharBuffer charBuffer = StandardCharsets.UTF_8.decode(
212                 ByteBuffer.wrap( originalBytes, 0, originalBytes.length ) );
213             computedChars = new char[charBuffer.remaining()];
214             charBuffer.get( computedChars );
215 
216             // clear out the temporary bytebuffer
217             charBuffer.flip();
218             char[] nullifier = new char[charBuffer.limit()];
219             Arrays.fill( nullifier, ( char ) 0 );
220             charBuffer.put( nullifier );
221         }
222         return computedChars;
223     }
224 
225 
226     /**
227      * Returns the <code>byte[]</code> used to create this buffer, or 
228      * getComputedBytes() if created with a <code>char[]</code>.
229      *
230      * @return A byte[]
231      */
232     public byte[] getBytes()
233     {
234         return originalBytes == null
235             ? getComputedBytes()
236             : originalBytes;
237     }
238 
239     /**
240      * Returns the <code>char[]</code> used to create this buffer, or 
241      * getComputedChars() if created with a <code>byte[]</code>.
242      *
243      * @return A byte[]
244      */
245     public char[] getChars()
246     {
247         return precomputedChars == null
248             ? getComputedChars()
249             : precomputedChars;
250     }
251 }