001/* 002 * Copyright 2017-2018 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2017-2018 Ping Identity Corporation 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU General Public License (GPLv2 only) 010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 011 * as published by the Free Software Foundation. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Public License for more details. 017 * 018 * You should have received a copy of the GNU General Public License 019 * along with this program; if not, see <http://www.gnu.org/licenses>. 020 */ 021package com.unboundid.ldap.listener; 022 023 024 025import java.security.MessageDigest; 026import java.util.Arrays; 027import java.util.List; 028 029import com.unboundid.ldap.sdk.LDAPException; 030import com.unboundid.ldap.sdk.Modification; 031import com.unboundid.ldap.sdk.ReadOnlyEntry; 032import com.unboundid.ldap.sdk.ResultCode; 033import com.unboundid.util.ThreadSafety; 034import com.unboundid.util.ThreadSafetyLevel; 035import com.unboundid.util.Validator; 036 037import static com.unboundid.ldap.listener.ListenerMessages.*; 038 039 040 041/** 042 * This class provides an implementation of an in-memory directory server 043 * password encoder that uses a message digest to encode passwords. No salt 044 * will be used when generating the digest, so the same clear-text password will 045 * always result in the same encoded representation. 046 */ 047@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 048public final class UnsaltedMessageDigestInMemoryPasswordEncoder 049 extends InMemoryPasswordEncoder 050{ 051 // The length of the generated message digest, in bytes. 052 private final int digestLengthBytes; 053 054 // The message digest instance tha will be used to actually perform the 055 // encoding. 056 private final MessageDigest messageDigest; 057 058 059 060 061 /** 062 * Creates a new instance of this in-memory directory server password encoder 063 * with the provided information. 064 * 065 * @param prefix The string that will appear at the beginning of 066 * encoded passwords. It must not be {@code null} or 067 * empty. 068 * @param outputFormatter The output formatter that will be used to format 069 * the encoded representation of clear-text 070 * passwords. It may be {@code null} if no 071 * special formatting should be applied to the raw 072 * bytes. 073 * @param messageDigest The message digest that will be used to actually 074 * perform the encoding. It must not be 075 * {@code null}, it must have a fixed length, and it 076 * must properly report that length via the 077 * {@code MessageDigest.getDigestLength} method.. 078 */ 079 public UnsaltedMessageDigestInMemoryPasswordEncoder(final String prefix, 080 final PasswordEncoderOutputFormatter outputFormatter, 081 final MessageDigest messageDigest) 082 { 083 super(prefix, outputFormatter); 084 085 Validator.ensureNotNull(messageDigest); 086 this.messageDigest = messageDigest; 087 088 digestLengthBytes = messageDigest.getDigestLength(); 089 Validator.ensureTrue((digestLengthBytes > 0), 090 "The message digest use a fixed digest length, and that " + 091 "length must be greater than zero."); 092 } 093 094 095 096 /** 097 * Retrieves the digest algorithm that will be used when encoding passwords. 098 * 099 * @return The message digest 100 */ 101 public String getDigestAlgorithm() 102 { 103 return messageDigest.getAlgorithm(); 104 } 105 106 107 108 /** 109 * Retrieves the digest length, in bytes. 110 * 111 * @return The digest length, in bytes. 112 */ 113 public int getDigestLengthBytes() 114 { 115 return digestLengthBytes; 116 } 117 118 119 120 /** 121 * {@inheritDoc} 122 */ 123 @Override() 124 protected byte[] encodePassword(final byte[] clearPassword, 125 final ReadOnlyEntry userEntry, 126 final List<Modification> modifications) 127 throws LDAPException 128 { 129 return messageDigest.digest(clearPassword); 130 } 131 132 133 134 /** 135 * {@inheritDoc} 136 */ 137 @Override() 138 protected void ensurePreEncodedPasswordAppearsValid( 139 final byte[] unPrefixedUnFormattedEncodedPasswordBytes, 140 final ReadOnlyEntry userEntry, 141 final List<Modification> modifications) 142 throws LDAPException 143 { 144 // Make sure that the length of the array containing the encoded password 145 // matches the digest length. 146 if (unPrefixedUnFormattedEncodedPasswordBytes.length != digestLengthBytes) 147 { 148 throw new LDAPException(ResultCode.PARAM_ERROR, 149 ERR_UNSALTED_DIGEST_PW_ENCODER_PRE_ENCODED_LENGTH_MISMATCH.get( 150 messageDigest.getAlgorithm(), 151 unPrefixedUnFormattedEncodedPasswordBytes.length, 152 digestLengthBytes)); 153 } 154 } 155 156 157 158 /** 159 * {@inheritDoc} 160 */ 161 @Override() 162 protected boolean passwordMatches(final byte[] clearPasswordBytes, 163 final byte[] unPrefixedUnFormattedEncodedPasswordBytes, 164 final ReadOnlyEntry userEntry) 165 throws LDAPException 166 { 167 final byte[] expectedEncodedPassword = 168 messageDigest.digest(clearPasswordBytes); 169 return Arrays.equals(unPrefixedUnFormattedEncodedPasswordBytes, 170 expectedEncodedPassword); 171 } 172 173 174 175 /** 176 * {@inheritDoc} 177 */ 178 @Override() 179 protected byte[] extractClearPassword( 180 final byte[] unPrefixedUnFormattedEncodedPasswordBytes, 181 final ReadOnlyEntry userEntry) 182 throws LDAPException 183 { 184 throw new LDAPException(ResultCode.NOT_SUPPORTED, 185 ERR_UNSALTED_DIGEST_PW_ENCODER_NOT_REVERSIBLE.get()); 186 } 187 188 189 190 /** 191 * {@inheritDoc} 192 */ 193 @Override() 194 public void toString(final StringBuilder buffer) 195 { 196 buffer.append("SaltedMessageDigestInMemoryPasswordEncoder(prefix='"); 197 buffer.append(getPrefix()); 198 buffer.append("', outputFormatter="); 199 200 final PasswordEncoderOutputFormatter outputFormatter = 201 getOutputFormatter(); 202 if (outputFormatter == null) 203 { 204 buffer.append("null"); 205 } 206 else 207 { 208 outputFormatter.toString(buffer); 209 } 210 211 buffer.append(", digestAlgorithm='"); 212 buffer.append(messageDigest.getAlgorithm()); 213 buffer.append("', digestLengthBytes="); 214 buffer.append(messageDigest.getDigestLength()); 215 buffer.append(')'); 216 } 217}