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}