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}