| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| */ |
| package org.apache.directory.ldap.client.template; |
| |
| |
| import java.nio.ByteBuffer; |
| import java.nio.CharBuffer; |
| import java.nio.charset.Charset; |
| import java.util.Arrays; |
| |
| |
| /** |
| * A buffer for storing sensitive information like passwords. It provides |
| * useful operations for characters such as character encoding/decoding, |
| * whitespace trimming, and lowercasing. It can be cleared out when operations |
| * are complete. |
| * |
| * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> |
| */ |
| public final class MemoryClearingBuffer |
| { |
| private static final Charset UTF8 = Charset.forName( "UTF-8" ); |
| private byte[] computedBytes; |
| private char[] computedChars; |
| private byte[] originalBytes; |
| private char[] originalChars; |
| private char[] precomputedChars; |
| |
| |
| private MemoryClearingBuffer( byte[] originalBytes, char[] originalChars, boolean trim, boolean lowerCase ) |
| { |
| this.originalBytes = originalBytes; |
| this.originalChars = originalChars; |
| |
| if ( trim || lowerCase ) |
| { |
| if ( this.originalChars == null ) |
| { |
| throw new UnsupportedOperationException( "trim and lowerCase only applicable to char[]" ); |
| } |
| |
| char[] working = Arrays.copyOf( originalChars, originalChars.length ); |
| int startIndex = 0; |
| int endIndex = working.length; |
| |
| if ( trim ) |
| { |
| // ltrim |
| for ( ; startIndex < working.length; startIndex++ ) |
| { |
| if ( !Character.isWhitespace( working[startIndex] ) ) |
| { |
| break; |
| } |
| } |
| |
| // rtrim |
| for ( endIndex--; endIndex > startIndex; endIndex-- ) |
| { |
| if ( !Character.isWhitespace( working[endIndex] ) ) |
| { |
| break; |
| } |
| } |
| endIndex++; |
| } |
| |
| if ( lowerCase ) |
| { |
| // lower case |
| for ( int i = startIndex; i < endIndex; i++ ) |
| { |
| working[i] = Character.toLowerCase( working[i] ); |
| } |
| } |
| |
| this.precomputedChars = new char[endIndex - startIndex]; |
| System.arraycopy( working, startIndex, this.precomputedChars, 0, endIndex - startIndex ); |
| } |
| else |
| { |
| this.precomputedChars = this.originalChars; |
| } |
| } |
| |
| |
| /** |
| * Creates a new instance of MemoryClearingBuffer from a |
| * <code>byte[]</code>. |
| * |
| * @param bytes A byte[] |
| * @return A buffer |
| */ |
| public static MemoryClearingBuffer newInstance( byte[] bytes ) |
| { |
| return new MemoryClearingBuffer( bytes, null, false, false ); |
| } |
| |
| |
| /** |
| * Creates a new instance of MemoryClearingBuffer from a |
| * <code>char[]</code>. |
| * |
| * @param chars A char[] |
| * @return A buffer |
| */ |
| public static MemoryClearingBuffer newInstance( char[] chars ) |
| { |
| return new MemoryClearingBuffer( null, chars, false, false ); |
| } |
| |
| |
| /** |
| * Creates a new instance of MemoryClearingBuffer from a |
| * <code>char[]</code>, optionally performing whitespace trimming and |
| * conversion to lower case. |
| * |
| * @param chars A char[] |
| * @param trim If true, whitespace will be trimmed off of both ends of the |
| * <code>char[]</code> |
| * @param lowerCase If true, the characters will be converted to lower case |
| * @return A buffer |
| */ |
| public static MemoryClearingBuffer newInstance( char[] chars, boolean trim, boolean lowerCase ) |
| { |
| return new MemoryClearingBuffer( null, chars, trim, lowerCase ); |
| } |
| |
| |
| /** |
| * Clears the buffer out, filling its cells with null. |
| */ |
| public void clear() |
| { |
| // clear out computed memory |
| if ( computedBytes != null ) |
| { |
| Arrays.fill( computedBytes, ( byte ) 0 ); |
| } |
| if ( computedChars != null ) |
| { |
| Arrays.fill( computedChars, '0' ); |
| } |
| if ( precomputedChars != null && precomputedChars != this.originalChars ) |
| { |
| // only nullify if NOT originalChars |
| Arrays.fill( precomputedChars, '0' ); |
| } |
| |
| computedBytes = null; |
| computedChars = null; |
| originalBytes = null; |
| originalChars = null; |
| precomputedChars = null; |
| } |
| |
| |
| /** |
| * Returns a UTF8 encoded <code>byte[]</code> representation of the |
| * <code>char[]</code> used to create this buffer. |
| * |
| * @return A byte[] |
| */ |
| byte[] getComputedBytes() |
| { |
| if ( computedBytes == null ) |
| { |
| ByteBuffer byteBuffer = UTF8.encode( |
| CharBuffer.wrap( precomputedChars, 0, precomputedChars.length ) ); |
| computedBytes = new byte[byteBuffer.remaining()]; |
| byteBuffer.get( computedBytes ); |
| |
| // clear out the temporary bytebuffer |
| byteBuffer.flip(); |
| byte[] nullifier = new byte[byteBuffer.limit()]; |
| Arrays.fill( nullifier, ( byte ) 0 ); |
| byteBuffer.put( nullifier ); |
| } |
| return computedBytes; |
| } |
| |
| |
| /** |
| * Returns a UTF8 decoded <code>char[]</code> representation of the |
| * <code>byte[]</code> used to create this buffer. |
| * |
| * @return A char[] |
| */ |
| private char[] getComputedChars() |
| { |
| if ( computedChars == null ) |
| { |
| CharBuffer charBuffer = UTF8.decode( |
| ByteBuffer.wrap( originalBytes, 0, originalBytes.length ) ); |
| computedChars = new char[charBuffer.remaining()]; |
| charBuffer.get( computedChars ); |
| |
| // clear out the temporary bytebuffer |
| charBuffer.flip(); |
| char[] nullifier = new char[charBuffer.limit()]; |
| Arrays.fill( nullifier, ( char ) 0 ); |
| charBuffer.put( nullifier ); |
| } |
| return computedChars; |
| } |
| |
| |
| /** |
| * Returns the <code>byte[]</code> used to create this buffer, or |
| * {@link #getComputedBytes()} if created with a <code>char[]</code>. |
| * |
| * @return A byte[] |
| */ |
| public byte[] getBytes() |
| { |
| return originalBytes == null |
| ? getComputedBytes() |
| : originalBytes; |
| } |
| |
| /** |
| * Returns the <code>char[]</code> used to create this buffer, or |
| * {@link #getComputedChars()} if created with a <code>byte[]</code>. |
| * |
| * @return A byte[] |
| */ |
| public char[] getChars() |
| { |
| return precomputedChars == null |
| ? getComputedChars() |
| : precomputedChars; |
| } |
| } |