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