001/* 002 * Copyright 2017-2019 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2017-2019 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 * Creates a new instance of this in-memory directory server password encoder 062 * with the provided information. 063 * 064 * @param prefix The string that will appear at the beginning of 065 * encoded passwords. It must not be {@code null} or 066 * empty. 067 * @param outputFormatter The output formatter that will be used to format 068 * the encoded representation of clear-text 069 * passwords. It may be {@code null} if no 070 * special formatting should be applied to the raw 071 * bytes. 072 * @param messageDigest The message digest that will be used to actually 073 * perform the encoding. It must not be 074 * {@code null}, it must have a fixed length, and it 075 * must properly report that length via the 076 * {@code MessageDigest.getDigestLength} method.. 077 */ 078 public UnsaltedMessageDigestInMemoryPasswordEncoder(final String prefix, 079 final PasswordEncoderOutputFormatter outputFormatter, 080 final MessageDigest messageDigest) 081 { 082 super(prefix, outputFormatter); 083 084 Validator.ensureNotNull(messageDigest); 085 this.messageDigest = messageDigest; 086 087 digestLengthBytes = messageDigest.getDigestLength(); 088 Validator.ensureTrue((digestLengthBytes > 0), 089 "The message digest use a fixed digest length, and that " + 090 "length must be greater than zero."); 091 } 092 093 094 095 /** 096 * Retrieves the digest algorithm that will be used when encoding passwords. 097 * 098 * @return The message digest 099 */ 100 public String getDigestAlgorithm() 101 { 102 return messageDigest.getAlgorithm(); 103 } 104 105 106 107 /** 108 * Retrieves the digest length, in bytes. 109 * 110 * @return The digest length, in bytes. 111 */ 112 public int getDigestLengthBytes() 113 { 114 return digestLengthBytes; 115 } 116 117 118 119 /** 120 * {@inheritDoc} 121 */ 122 @Override() 123 protected byte[] encodePassword(final byte[] clearPassword, 124 final ReadOnlyEntry userEntry, 125 final List<Modification> modifications) 126 throws LDAPException 127 { 128 return messageDigest.digest(clearPassword); 129 } 130 131 132 133 /** 134 * {@inheritDoc} 135 */ 136 @Override() 137 protected void ensurePreEncodedPasswordAppearsValid( 138 final byte[] unPrefixedUnFormattedEncodedPasswordBytes, 139 final ReadOnlyEntry userEntry, 140 final List<Modification> modifications) 141 throws LDAPException 142 { 143 // Make sure that the length of the array containing the encoded password 144 // matches the digest length. 145 if (unPrefixedUnFormattedEncodedPasswordBytes.length != digestLengthBytes) 146 { 147 throw new LDAPException(ResultCode.PARAM_ERROR, 148 ERR_UNSALTED_DIGEST_PW_ENCODER_PRE_ENCODED_LENGTH_MISMATCH.get( 149 messageDigest.getAlgorithm(), 150 unPrefixedUnFormattedEncodedPasswordBytes.length, 151 digestLengthBytes)); 152 } 153 } 154 155 156 157 /** 158 * {@inheritDoc} 159 */ 160 @Override() 161 protected boolean passwordMatches(final byte[] clearPasswordBytes, 162 final byte[] unPrefixedUnFormattedEncodedPasswordBytes, 163 final ReadOnlyEntry userEntry) 164 throws LDAPException 165 { 166 final byte[] expectedEncodedPassword = 167 messageDigest.digest(clearPasswordBytes); 168 return Arrays.equals(unPrefixedUnFormattedEncodedPasswordBytes, 169 expectedEncodedPassword); 170 } 171 172 173 174 /** 175 * {@inheritDoc} 176 */ 177 @Override() 178 protected byte[] extractClearPassword( 179 final byte[] unPrefixedUnFormattedEncodedPasswordBytes, 180 final ReadOnlyEntry userEntry) 181 throws LDAPException 182 { 183 throw new LDAPException(ResultCode.NOT_SUPPORTED, 184 ERR_UNSALTED_DIGEST_PW_ENCODER_NOT_REVERSIBLE.get()); 185 } 186 187 188 189 /** 190 * {@inheritDoc} 191 */ 192 @Override() 193 public void toString(final StringBuilder buffer) 194 { 195 buffer.append("SaltedMessageDigestInMemoryPasswordEncoder(prefix='"); 196 buffer.append(getPrefix()); 197 buffer.append("', outputFormatter="); 198 199 final PasswordEncoderOutputFormatter outputFormatter = 200 getOutputFormatter(); 201 if (outputFormatter == null) 202 { 203 buffer.append("null"); 204 } 205 else 206 { 207 outputFormatter.toString(buffer); 208 } 209 210 buffer.append(", digestAlgorithm='"); 211 buffer.append(messageDigest.getAlgorithm()); 212 buffer.append("', digestLengthBytes="); 213 buffer.append(messageDigest.getDigestLength()); 214 buffer.append(')'); 215 } 216}