001/*
002 *   Licensed to the Apache Software Foundation (ASF) under one
003 *   or more contributor license agreements.  See the NOTICE file
004 *   distributed with this work for additional information
005 *   regarding copyright ownership.  The ASF licenses this file
006 *   to you under the Apache License, Version 2.0 (the
007 *   "License"); you may not use this file except in compliance
008 *   with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *   Unless required by applicable law or agreed to in writing,
013 *   software distributed under the License is distributed on an
014 *   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *   KIND, either express or implied.  See the License for the
016 *   specific language governing permissions and limitations
017 *   under the License.
018 *
019 */
020package org.apache.directory.ldap.client.template;
021
022
023import java.nio.ByteBuffer;
024import java.nio.CharBuffer;
025import java.nio.charset.Charset;
026import java.util.Arrays;
027
028
029/**
030 * A buffer for storing sensitive information like passwords.  It provides 
031 * useful operations for characters such as character encoding/decoding, 
032 * whitespace trimming, and lowercasing.  It can be cleared out when operations
033 * are complete.
034 *
035 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
036 */
037public final class MemoryClearingBuffer
038{
039    private static final Charset UTF8 = Charset.forName( "UTF-8" );
040    private byte[] computedBytes;
041    private char[] computedChars;
042    private byte[] originalBytes;
043    private char[] originalChars;
044    private char[] precomputedChars;
045
046
047    private MemoryClearingBuffer( byte[] originalBytes, char[] originalChars, boolean trim, boolean lowerCase )
048    {
049        this.originalBytes = originalBytes;
050        this.originalChars = originalChars;
051
052        if ( trim || lowerCase )
053        {
054            if ( this.originalChars == null )
055            {
056                throw new UnsupportedOperationException( "trim and lowerCase only applicable to char[]" );
057            }
058
059            char[] working = Arrays.copyOf( originalChars, originalChars.length );
060            int startIndex = 0;
061            int endIndex = working.length;
062
063            if ( trim )
064            {
065                // ltrim
066                for ( ; startIndex < working.length; startIndex++ )
067                {
068                    if ( !Character.isWhitespace( working[startIndex] ) )
069                    {
070                        break;
071                    }
072                }
073
074                // rtrim
075                for ( endIndex--; endIndex > startIndex; endIndex-- )
076                {
077                    if ( !Character.isWhitespace( working[endIndex] ) )
078                    {
079                        break;
080                    }
081                }
082                endIndex++;
083            }
084
085            if ( lowerCase )
086            {
087                // lower case
088                for ( int i = startIndex; i < endIndex; i++ )
089                {
090                    working[i] = Character.toLowerCase( working[i] );
091                }
092            }
093
094            this.precomputedChars = new char[endIndex - startIndex];
095            System.arraycopy( working, startIndex, this.precomputedChars, 0, endIndex - startIndex );
096        }
097        else
098        {
099            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}