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