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 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                          break;
70                  }
71  
72                  // rtrim
73                  for ( endIndex--; endIndex > startIndex; endIndex-- )
74                  {
75                      if ( !Character.isWhitespace( working[endIndex] ) )
76                          break;
77                  }
78                  endIndex++;
79              }
80  
81              if ( lowerCase )
82              {
83                  // lower case
84                  for ( int i = startIndex; i < endIndex; i++ )
85                  {
86                      working[i] = Character.toLowerCase( working[i] );
87                  }
88              }
89  
90              this.precomputedChars = new char[endIndex - startIndex];
91              System.arraycopy( working, startIndex, this.precomputedChars, 0, endIndex - startIndex );
92          }
93          else
94          {
95              this.precomputedChars = this.originalChars;
96          }
97      }
98  
99  
100     /**
101      * Creates a new instance of MemoryClearingBuffer from a 
102      * <code>byte[]</code>.
103      *
104      * @param bytes A byte[]
105      * @return A buffer
106      */
107     public static MemoryClearingBuffer newInstance( byte[] bytes )
108     {
109         return new MemoryClearingBuffer( bytes, null, false, false );
110     }
111 
112 
113     /**
114      * Creates a new instance of MemoryClearingBuffer from a 
115      * <code>char[]</code>.
116      *
117      * @param chars A char[]
118      * @return A buffer
119      */
120     public static MemoryClearingBuffer newInstance( char[] chars )
121     {
122         return new MemoryClearingBuffer( null, chars, false, false );
123     }
124 
125 
126     /**
127      * Creates a new instance of MemoryClearingBuffer from a 
128      * <code>char[]</code>, optionally performing whitespace trimming and
129      * conversion to lower case.
130      *
131      * @param chars A char[]
132      * @param trim If true, whitespace will be trimmed off of both ends of the
133      * <code>char[]</code>
134      * @param lowerCase If true, the characters will be converted to lower case
135      * @return A buffer
136      */
137     public static MemoryClearingBuffer newInstance( char[] chars, boolean trim, boolean lowerCase )
138     {
139         return new MemoryClearingBuffer( null, chars, trim, lowerCase );
140     }
141 
142 
143     /**
144      *  Clears the buffer out, filling its cells with null.
145      */
146     public void clear()
147     {
148         // clear out computed memory
149         if ( computedBytes != null )
150         {
151             Arrays.fill( computedBytes, ( byte ) 0 );
152         }
153         if ( computedChars != null )
154         {
155             Arrays.fill( computedChars, '0' );
156         }
157         if ( precomputedChars != null )
158         {
159             Arrays.fill( precomputedChars, '0' );
160         }
161 
162         computedBytes = null;
163         computedChars = null;
164         originalBytes = null;
165         originalChars = null;
166         precomputedChars = null;
167     }
168 
169 
170     /**
171      * Returns a UTF8 encoded <code>byte[]</code> representation of the 
172      * <code>char[]</code> used to create this buffer.
173      * 
174      * @return A byte[]
175      */
176     byte[] getComputedBytes()
177     {
178         if ( computedBytes == null )
179         {
180             ByteBuffer byteBuffer = UTF8.encode(
181                 CharBuffer.wrap( precomputedChars, 0, precomputedChars.length ) );
182             computedBytes = new byte[byteBuffer.remaining()];
183             byteBuffer.get( computedBytes );
184 
185             // clear out the temporary bytebuffer
186             byteBuffer.flip();
187             byte[] nullifier = new byte[byteBuffer.limit()];
188             Arrays.fill( nullifier, ( byte ) 0 );
189             byteBuffer.put( nullifier );
190         }
191         return computedBytes;
192     }
193 
194 
195     /**
196      * Returns a UTF8 decoded <code>char[]</code> representation of the 
197      * <code>byte[]</code> used to create this buffer.
198      *
199      * @return A char[]
200      */
201     private char[] getComputedChars()
202     {
203         if ( computedChars == null )
204         {
205             CharBuffer charBuffer = UTF8.decode(
206                 ByteBuffer.wrap( originalBytes, 0, originalBytes.length ) );
207             computedChars = new char[charBuffer.remaining()];
208             charBuffer.get( computedChars );
209 
210             // clear out the temporary bytebuffer
211             charBuffer.flip();
212             char[] nullifier = new char[charBuffer.limit()];
213             Arrays.fill( nullifier, ( char ) 0 );
214             charBuffer.put( nullifier );
215         }
216         return computedChars;
217     }
218 
219 
220     /**
221      * Returns the <code>byte[]</code> used to create this buffer, or 
222      * {@link #getComputedBytes()} if created with a <code>char[]</code>.
223      *
224      * @return A byte[]
225      */
226     public byte[] getBytes()
227     {
228         return originalBytes == null
229             ? getComputedBytes()
230             : originalBytes;
231     }
232 
233     /**
234      * Returns the <code>char[]</code> used to create this buffer, or 
235      * {@link #getComputedChars()} if created with a <code>byte[]</code>.
236      *
237      * @return A byte[]
238      */
239     public char[] getChars()
240     {
241         return precomputedChars == null
242             ? getComputedChars()
243             : precomputedChars;
244     }
245 }