001/* 002 * Copyright (C) 2022-present The Prometheus jmx_exporter Authors 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package io.prometheus.jmx.common.http.authenticator; 018 019import com.sun.net.httpserver.BasicAuthenticator; 020import io.prometheus.jmx.common.util.Precondition; 021import java.nio.charset.StandardCharsets; 022import java.security.GeneralSecurityException; 023import java.security.NoSuchAlgorithmException; 024import javax.crypto.SecretKeyFactory; 025import javax.crypto.spec.PBEKeySpec; 026 027/** Class to implement a username / salted message digest password BasicAuthenticator */ 028public class PBKDF2Authenticator extends BasicAuthenticator { 029 030 private static final int MAXIMUM_VALID_CACHE_SIZE_BYTES = 1000000; // 1 MB 031 private static final int MAXIMUM_INVALID_CACHE_SIZE_BYTES = 10000000; // 10 MB 032 033 private final String username; 034 private final String passwordHash; 035 private final String algorithm; 036 private final String salt; 037 private final int iterations; 038 private final int keyLength; 039 private final CredentialsCache validCredentialsCache; 040 private final CredentialsCache invalidCredentialsCache; 041 042 /** 043 * Constructor 044 * 045 * @param realm realm 046 * @param username username 047 * @param passwordHash passwordHash 048 * @param algorithm algorithm 049 * @param salt salt 050 * @param iterations iterations 051 * @param keyLength keyLength 052 * @throws NoSuchAlgorithmException NoSuchAlgorithmException 053 */ 054 public PBKDF2Authenticator( 055 String realm, 056 String username, 057 String passwordHash, 058 String algorithm, 059 String salt, 060 int iterations, 061 int keyLength) 062 throws GeneralSecurityException { 063 super(realm); 064 065 Precondition.notNullOrEmpty(username); 066 Precondition.notNullOrEmpty(passwordHash); 067 Precondition.notNullOrEmpty(algorithm); 068 Precondition.notNullOrEmpty(salt); 069 Precondition.isGreaterThanOrEqualTo(iterations, 1); 070 Precondition.isGreaterThanOrEqualTo(keyLength, 1); 071 072 SecretKeyFactory.getInstance(algorithm); 073 074 this.username = username; 075 this.passwordHash = passwordHash.toLowerCase().replace(":", ""); 076 this.algorithm = algorithm; 077 this.salt = salt; 078 this.iterations = iterations; 079 this.keyLength = keyLength; 080 this.validCredentialsCache = new CredentialsCache(MAXIMUM_VALID_CACHE_SIZE_BYTES); 081 this.invalidCredentialsCache = new CredentialsCache(MAXIMUM_INVALID_CACHE_SIZE_BYTES); 082 } 083 084 /** 085 * called for each incoming request to verify the given name and password in the context of this 086 * Authenticator's realm. Any caching of credentials must be done by the implementation of this 087 * method 088 * 089 * @param username the username from the request 090 * @param password the password from the request 091 * @return <code>true</code> if the credentials are valid, <code>false</code> otherwise. 092 */ 093 @Override 094 public boolean checkCredentials(String username, String password) { 095 if (username == null || password == null) { 096 return false; 097 } 098 099 Credentials credentials = new Credentials(username, password); 100 if (validCredentialsCache.contains(credentials)) { 101 return true; 102 } else if (invalidCredentialsCache.contains(credentials)) { 103 return false; 104 } 105 106 boolean isValid = 107 this.username.equals(username) 108 && this.passwordHash.equals( 109 generatePasswordHash( 110 algorithm, salt, iterations, keyLength, password)); 111 if (isValid) { 112 validCredentialsCache.add(credentials); 113 } else { 114 invalidCredentialsCache.add(credentials); 115 } 116 117 return isValid; 118 } 119 120 /** 121 * Method to generate a hash based on the configured secret key algorithm 122 * 123 * @param algorithm algorithm 124 * @param salt salt 125 * @param iterations iterations 126 * @param keyLength keyLength 127 * @param password password 128 * @return the hash 129 */ 130 private static String generatePasswordHash( 131 String algorithm, String salt, int iterations, int keyLength, String password) { 132 try { 133 PBEKeySpec pbeKeySpec = 134 new PBEKeySpec( 135 password.toCharArray(), 136 salt.getBytes(StandardCharsets.UTF_8), 137 iterations, 138 keyLength * 8); 139 SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithm); 140 byte[] secretKeyBytes = secretKeyFactory.generateSecret(pbeKeySpec).getEncoded(); 141 return HexString.toHex(secretKeyBytes); 142 } catch (GeneralSecurityException e) { 143 throw new RuntimeException(e); 144 } 145 } 146}