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}