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   *     http://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.Charset;
26  import java.util.Arrays;
27  
28  
29  /**
30   * A buffer for storing sensitive information like passwords.  It provides 
31   * useful operations for characters such as character encoding/decoding, 
32   * whitespace trimming, and lowercasing.  It can be cleared out when operations
33   * are complete.
34   *
35   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
36   */
37  public final class MemoryClearingBuffer
38  {
39      private static final Charset UTF8 = Charset.forName( "UTF-8" );
40      private byte[] computedBytes;
41      private char[] computedChars;
42      private byte[] originalBytes;
43      private char[] originalChars;
44      private char[] precomputedChars;
45  
46  
47      private MemoryClearingBuffer( byte[] originalBytes, char[] originalChars, boolean trim, boolean lowerCase )
48      {
49          this.originalBytes = originalBytes;
50          this.originalChars = originalChars;
51  
52          if ( trim || lowerCase )
53          {
54              if ( this.originalChars == null )
55              {
56                  throw new UnsupportedOperationException( "trim and lowerCase only applicable to char[]" );
57              }
58  
59              char[] working = Arrays.copyOf( originalChars, originalChars.length );
60              int startIndex = 0;
61              int endIndex = working.length;
62  
63              if ( trim )
64              {
65                  // ltrim
66                  for ( ; startIndex < working.length; startIndex++ )
67                  {
68                      if ( !Character.isWhitespace( working[startIndex] ) )
69                      {
70                          break;
71                      }
72                  }
73  
74                  // rtrim
75                  for ( endIndex--; endIndex > startIndex; endIndex-- )
76                  {
77                      if ( !Character.isWhitespace( working[endIndex] ) )
78                      {
79                          break;
80                      }
81                  }
82                  endIndex++;
83              }
84  
85              if ( lowerCase )
86              {
87                  // lower case
88                  for ( int i = startIndex; i < endIndex; i++ )
89                  {
90                      working[i] = Character.toLowerCase( working[i] );
91                  }
92              }
93  
94              this.precomputedChars = new char[endIndex - startIndex];
95              System.arraycopy( working, startIndex, this.precomputedChars, 0, endIndex - startIndex );
96          }
97          else
98          {
99              this.precomputedChars = this.originalChars;
100         }
101     }
102 
103 
104     /**
105      * Creates a new instance of MemoryClearingBuffer from a 
106      * <code>byte[]</code>.
107      *
108      * @param bytes A byte[]
109      * @return A buffer
110      */
111     public static MemoryClearingBuffer newInstance( byte[] bytes )
112     {
113         return new MemoryClearingBuffer( bytes, null, false, false );
114     }
115 
116 
117     /**
118      * Creates a new instance of MemoryClearingBuffer from a 
119      * <code>char[]</code>.
120      *
121      * @param chars A char[]
122      * @return A buffer
123      */
124     public static MemoryClearingBuffer newInstance( char[] chars )
125     {
126         return new MemoryClearingBuffer( null, chars, false, false );
127     }
128 
129 
130     /**
131      * Creates a new instance of MemoryClearingBuffer from a 
132      * <code>char[]</code>, optionally performing whitespace trimming and
133      * conversion to lower case.
134      *
135      * @param chars A char[]
136      * @param trim If true, whitespace will be trimmed off of both ends of the
137      * <code>char[]</code>
138      * @param lowerCase If true, the characters will be converted to lower case
139      * @return A buffer
140      */
141     public static MemoryClearingBuffer newInstance( char[] chars, boolean trim, boolean lowerCase )
142     {
143         return new MemoryClearingBuffer( null, chars, trim, lowerCase );
144     }
145 
146 
147     /**
148      *  Clears the buffer out, filling its cells with null.
149      */
150     public void clear()
151     {
152         // clear out computed memory
153         if ( computedBytes != null )
154         {
155             Arrays.fill( computedBytes, ( byte ) 0 );
156         }
157         if ( computedChars != null )
158         {
159             Arrays.fill( computedChars, '0' );
160         }
161         if ( precomputedChars != null && precomputedChars != this.originalChars )
162         {
163             // only nullify if NOT originalChars
164             Arrays.fill( precomputedChars, '0' );
165         }
166 
167         computedBytes = null;
168         computedChars = null;
169         originalBytes = null;
170         originalChars = null;
171         precomputedChars = null;
172     }
173 
174 
175     /**
176      * Returns a UTF8 encoded <code>byte[]</code> representation of the 
177      * <code>char[]</code> used to create this buffer.
178      * 
179      * @return A byte[]
180      */
181     byte[] getComputedBytes()
182     {
183         if ( computedBytes == null )
184         {
185             ByteBuffer byteBuffer = UTF8.encode(
186                 CharBuffer.wrap( precomputedChars, 0, precomputedChars.length ) );
187             computedBytes = new byte[byteBuffer.remaining()];
188             byteBuffer.get( computedBytes );
189 
190             // clear out the temporary bytebuffer
191             byteBuffer.flip();
192             byte[] nullifier = new byte[byteBuffer.limit()];
193             Arrays.fill( nullifier, ( byte ) 0 );
194             byteBuffer.put( nullifier );
195         }
196         return computedBytes;
197     }
198 
199 
200     /**
201      * Returns a UTF8 decoded <code>char[]</code> representation of the 
202      * <code>byte[]</code> used to create this buffer.
203      *
204      * @return A char[]
205      */
206     private char[] getComputedChars()
207     {
208         if ( computedChars == null )
209         {
210             CharBuffer charBuffer = UTF8.decode(
211                 ByteBuffer.wrap( originalBytes, 0, originalBytes.length ) );
212             computedChars = new char[charBuffer.remaining()];
213             charBuffer.get( computedChars );
214 
215             // clear out the temporary bytebuffer
216             charBuffer.flip();
217             char[] nullifier = new char[charBuffer.limit()];
218             Arrays.fill( nullifier, ( char ) 0 );
219             charBuffer.put( nullifier );
220         }
221         return computedChars;
222     }
223 
224 
225     /**
226      * Returns the <code>byte[]</code> used to create this buffer, or 
227      * getComputedBytes() if created with a <code>char[]</code>.
228      *
229      * @return A byte[]
230      */
231     public byte[] getBytes()
232     {
233         return originalBytes == null
234             ? getComputedBytes()
235             : originalBytes;
236     }
237 
238     /**
239      * Returns the <code>char[]</code> used to create this buffer, or 
240      * getComputedChars() if created with a <code>byte[]</code>.
241      *
242      * @return A byte[]
243      */
244     public char[] getChars()
245     {
246         return precomputedChars == null
247             ? getComputedChars()
248             : precomputedChars;
249     }
250 }