001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019package org.apache.hadoop.security.token; 020 021import java.io.IOException; 022import java.security.InvalidKeyException; 023import java.security.NoSuchAlgorithmException; 024 025import javax.crypto.KeyGenerator; 026import javax.crypto.Mac; 027import javax.crypto.SecretKey; 028import javax.crypto.spec.SecretKeySpec; 029 030import org.apache.hadoop.classification.InterfaceAudience; 031import org.apache.hadoop.classification.InterfaceStability; 032import org.apache.hadoop.ipc.RetriableException; 033import org.apache.hadoop.ipc.StandbyException; 034 035 036/** 037 * The server-side secret manager for each token type. 038 * @param <T> The type of the token identifier 039 */ 040@InterfaceAudience.Public 041@InterfaceStability.Evolving 042public abstract class SecretManager<T extends TokenIdentifier> { 043 /** 044 * The token was invalid and the message explains why. 045 */ 046 @SuppressWarnings("serial") 047 @InterfaceStability.Evolving 048 public static class InvalidToken extends IOException { 049 public InvalidToken(String msg) { 050 super(msg); 051 } 052 } 053 054 /** 055 * Create the password for the given identifier. 056 * identifier may be modified inside this method. 057 * @param identifier the identifier to use 058 * @return the new password 059 */ 060 protected abstract byte[] createPassword(T identifier); 061 062 /** 063 * Retrieve the password for the given token identifier. Should check the date 064 * or registry to make sure the token hasn't expired or been revoked. Returns 065 * the relevant password. 066 * @param identifier the identifier to validate 067 * @return the password to use 068 * @throws InvalidToken the token was invalid 069 */ 070 public abstract byte[] retrievePassword(T identifier) 071 throws InvalidToken; 072 073 /** 074 * The same functionality with {@link #retrievePassword}, except that this 075 * method can throw a {@link RetriableException} or a {@link StandbyException} 076 * to indicate that client can retry/failover the same operation because of 077 * temporary issue on the server side. 078 * 079 * @param identifier the identifier to validate 080 * @return the password to use 081 * @throws InvalidToken the token was invalid 082 * @throws StandbyException the server is in standby state, the client can 083 * try other servers 084 * @throws RetriableException the token was invalid, and the server thinks 085 * this may be a temporary issue and suggests the client to retry 086 * @throws IOException to allow future exceptions to be added without breaking 087 * compatibility 088 */ 089 public byte[] retriableRetrievePassword(T identifier) 090 throws InvalidToken, StandbyException, RetriableException, IOException { 091 return retrievePassword(identifier); 092 } 093 094 /** 095 * Create an empty token identifier. 096 * @return the newly created empty token identifier 097 */ 098 public abstract T createIdentifier(); 099 100 /** 101 * No-op if the secret manager is available for reading tokens, throw a 102 * StandbyException otherwise. 103 * 104 * @throws StandbyException if the secret manager is not available to read 105 * tokens 106 */ 107 public void checkAvailableForRead() throws StandbyException { 108 // Default to being available for read. 109 } 110 111 /** 112 * The name of the hashing algorithm. 113 */ 114 private static final String DEFAULT_HMAC_ALGORITHM = "HmacSHA1"; 115 116 /** 117 * The length of the random keys to use. 118 */ 119 private static final int KEY_LENGTH = 64; 120 121 /** 122 * A thread local store for the Macs. 123 */ 124 private static final ThreadLocal<Mac> threadLocalMac = 125 new ThreadLocal<Mac>(){ 126 @Override 127 protected Mac initialValue() { 128 try { 129 return Mac.getInstance(DEFAULT_HMAC_ALGORITHM); 130 } catch (NoSuchAlgorithmException nsa) { 131 throw new IllegalArgumentException("Can't find " + DEFAULT_HMAC_ALGORITHM + 132 " algorithm."); 133 } 134 } 135 }; 136 137 /** 138 * Key generator to use. 139 */ 140 private final KeyGenerator keyGen; 141 { 142 try { 143 keyGen = KeyGenerator.getInstance(DEFAULT_HMAC_ALGORITHM); 144 keyGen.init(KEY_LENGTH); 145 } catch (NoSuchAlgorithmException nsa) { 146 throw new IllegalArgumentException("Can't find " + DEFAULT_HMAC_ALGORITHM + 147 " algorithm."); 148 } 149 } 150 151 /** 152 * Generate a new random secret key. 153 * @return the new key 154 */ 155 protected SecretKey generateSecret() { 156 SecretKey key; 157 synchronized (keyGen) { 158 key = keyGen.generateKey(); 159 } 160 return key; 161 } 162 163 /** 164 * Compute HMAC of the identifier using the secret key and return the 165 * output as password 166 * @param identifier the bytes of the identifier 167 * @param key the secret key 168 * @return the bytes of the generated password 169 */ 170 protected static byte[] createPassword(byte[] identifier, 171 SecretKey key) { 172 Mac mac = threadLocalMac.get(); 173 try { 174 mac.init(key); 175 } catch (InvalidKeyException ike) { 176 throw new IllegalArgumentException("Invalid key to HMAC computation", 177 ike); 178 } 179 return mac.doFinal(identifier); 180 } 181 182 /** 183 * Convert the byte[] to a secret key 184 * @param key the byte[] to create a secret key from 185 * @return the secret key 186 */ 187 protected static SecretKey createSecretKey(byte[] key) { 188 return new SecretKeySpec(key, DEFAULT_HMAC_ALGORITHM); 189 } 190}