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}