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 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                        break;
070                }
071
072                // rtrim
073                for ( endIndex--; endIndex > startIndex; endIndex-- )
074                {
075                    if ( !Character.isWhitespace( working[endIndex] ) )
076                        break;
077                }
078                endIndex++;
079            }
080
081            if ( lowerCase )
082            {
083                // lower case
084                for ( int i = startIndex; i < endIndex; i++ )
085                {
086                    working[i] = Character.toLowerCase( working[i] );
087                }
088            }
089
090            this.precomputedChars = new char[endIndex - startIndex];
091            System.arraycopy( working, startIndex, this.precomputedChars, 0, endIndex - startIndex );
092        }
093        else
094        {
095            this.precomputedChars = this.originalChars;
096        }
097    }
098
099
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}