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.StandardCharsets;
026import java.util.Arrays;
027
028import org.apache.directory.api.i18n.I18n;
029
030
031/**
032 * A buffer for storing sensitive information like passwords.  It provides 
033 * useful operations for characters such as character encoding/decoding, 
034 * whitespace trimming, and lowercasing.  It can be cleared out when operations
035 * are complete.
036 *
037 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
038 */
039public final class MemoryClearingBuffer
040{
041    private byte[] computedBytes;
042    private char[] computedChars;
043    private byte[] originalBytes;
044    private char[] originalChars;
045    private char[] precomputedChars;
046
047
048    private MemoryClearingBuffer( byte[] originalBytes, char[] originalChars, boolean trim, boolean lowerCase )
049    {
050        this.originalBytes = originalBytes;
051        this.originalChars = originalChars;
052
053        if ( trim || lowerCase )
054        {
055            if ( this.originalChars == null )
056            {
057                throw new UnsupportedOperationException( I18n.err( I18n.ERR_04168_TRIM_LOWERCASE_FOR_CHAR_ARRAY ) );
058            }
059
060            char[] working = Arrays.copyOf( originalChars, originalChars.length );
061            int startIndex = 0;
062            int endIndex = working.length;
063
064            if ( trim )
065            {
066                // ltrim
067                for ( ; startIndex < working.length; startIndex++ )
068                {
069                    if ( !Character.isWhitespace( working[startIndex] ) )
070                    {
071                        break;
072                    }
073                }
074
075                // rtrim
076                for ( endIndex--; endIndex > startIndex; endIndex-- )
077                {
078                    if ( !Character.isWhitespace( working[endIndex] ) )
079                    {
080                        break;
081                    }
082                }
083                endIndex++;
084            }
085
086            if ( lowerCase )
087            {
088                // lower case
089                for ( int i = startIndex; i < endIndex; i++ )
090                {
091                    working[i] = Character.toLowerCase( working[i] );
092                }
093            }
094
095            this.precomputedChars = new char[endIndex - startIndex];
096            System.arraycopy( working, startIndex, this.precomputedChars, 0, endIndex - startIndex );
097        }
098        else
099        {
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}