/*
* Copyright 2010-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.amazonaws.tvm.identity;
import java.util.logging.Logger;
import com.amazonaws.services.securitytoken.model.Credentials;
import com.amazonaws.tvm.identity.exception.DataAccessException;
import com.amazonaws.tvm.identity.exception.UnauthorizedException;
import com.amazonaws.tvm.identity.DeviceAuthentication.DeviceInfo;
import com.amazonaws.tvm.identity.UserAuthentication.UserInfo;
import com.amazonaws.tvm.Utilities;
import com.amazonaws.tvm.TokenVendingMachineLogger;
/**
* This class implements functions for Identity mode. Identity mode is more
* useful when application developer needs to track their customer and how much
* resources each of them is using. This mode is also suitable to when
* application developer wants to charge as per usage. It allows new users to
* register by providing username and password combination. Registered users can
* then obtain encryption key after login. This key is used to encrypt tokens in
* future communication. Since a username can have many devices associated with
* it each login request must explicitly specify the UID. The generated key is
* then associated to this UID.
*/
public class IdentityTokenVendingMachine {
private static final Logger log = TokenVendingMachineLogger.getLogger();
private final DeviceAuthentication deviceAuthenticator;
private final UserAuthentication userAuthenticator;
private final TemporaryCredentialManagement credentialManagement;
public IdentityTokenVendingMachine() {
deviceAuthenticator = new DeviceAuthentication();
userAuthenticator = new UserAuthentication();
credentialManagement = new TemporaryCredentialManagement();
}
/**
* Verify if the given signature is valid.
*
* @param stringToSign
* The string to sign
* @param key
* The key used in the signature process
* @param signature
* Base64 encoded HMAC-SHA256 signature derived from key and
* string
* @return true if computed signature matches with the given signature,
* false otherwise
*/
public boolean validateSignature(String stringToSign, String key, String targetSignature) {
String computedSignature = Utilities.identitySign(stringToSign, key);
return Utilities.slowStringComparison(targetSignature, computedSignature);
}
/**
* Allows users to register with Token Vending Machine (TVM). This function
* is useful in Identity mode
*
* @param username
* Unique alphanumeric string of length between 3 to 128
* characters with special characters limited to underscore (_),
* period (.) and (@).
* @param password
* String of length between 6 to 128 characters
* @param endpoint
* DNS name of host machine
* @return boolean indicating if the registration was successful or not
* @throws DataAccessException
*/
public boolean registerUser(String username, String password, String endpoint) throws DataAccessException {
return userAuthenticator.registerUser(username, password, endpoint);
}
/**
* Verify if the token request is valid. UID is authenticated. The timestamp
* is checked to see it falls within the valid timestamp window. The
* signature is computed and matched against the given signature. Useful in
* Anonymous and Identity modes
*
* @param uid
* Unique device identifier
* @param signature
* Base64 encoded HMAC-SHA256 signature derived from key and
* timestamp
* @param timestamp
* Timestamp of the request in ISO8601 format
* @throws DataAccessException
* @throws UnauthorizedException
*/
public void validateTokenRequest(String uid, String signature, String timestamp) throws DataAccessException,
UnauthorizedException {
if (!Utilities.isTimestampValid(timestamp)) {
throw new UnauthorizedException("Invalid timestamp: " + timestamp);
}
log.info(String.format("Timestamp [ %s ] is valid", timestamp));
DeviceInfo device = deviceAuthenticator.getDeviceInfo(uid);
if (device == null) {
throw new UnauthorizedException("Couldn't find device: " + uid);
}
if (!validateSignature(timestamp, device.getKey(), signature)) {
throw new UnauthorizedException("Invalid signature: " + signature);
}
log.info("Signature matched!!!");
}
/**
* Generate tokens for given UID. The tokens are encrypted using the key
* corresponding to UID. Encrypted tokens are then wrapped in JSON object
* before returning it. Useful in Anonymous and Identity modes
*
* @param uid
* Unique device identifier
* @return encrypted tokens as JSON object
* @throws DataAccessException
* @throws UnauthorizedException
*/
public String getToken(String uid) throws DataAccessException, UnauthorizedException {
DeviceInfo device = deviceAuthenticator.getDeviceInfo(uid);
if (device == null) {
throw new UnauthorizedException("Couldn't find device: " + uid);
}
UserInfo user = userAuthenticator.getUserInfo(device.getUsername());
if (user == null) {
throw new UnauthorizedException("Couldn't find user: " + device.getUsername());
}
log.info("Creating temporary credentials");
Credentials sessionCredentials = credentialManagement.getTemporaryCredentials(user.getUsername());
log.info("Generating session tokens for UID : " + uid);
return Utilities.prepareJsonResponseForTokens(sessionCredentials, device.getKey());
}
/**
* Verify if the login request is valid. Username and UID are authenticated.
* The timestamp is checked to see it falls within the valid timestamp
* window. The signature is computed and matched against the given
* signature. Also its checked to see if the UID belongs to the username.
* This function is useful in Identity mode
*
* @param username
* Unique user identifier
* @param uid
* Unique device identifier
* @param signature
* Base64 encoded HMAC-SHA256 signature derived from hash of
* salted-password and timestamp
* @param timestamp
* Timestamp of the request in ISO8601 format
* @return status code indicating if login request is valid or not
* @throws DataAccessException
* @throws UnauthorizedException
*/
public void validateLoginRequest(String username, String uid, String signature, String timestamp)
throws DataAccessException, UnauthorizedException {
if (!Utilities.isTimestampValid(timestamp)) {
throw new UnauthorizedException("Invalid timestamp: " + timestamp);
}
log.info(String.format("Timestamp [ %s ] is valid", timestamp));
// Validate signature
log.info("Validate signature: " + signature);
UserInfo user = userAuthenticator.getUserInfo(username);
if (user == null) {
throw new UnauthorizedException("Couldn't find user: " + username);
}
if (!validateSignature(timestamp, user.getHashedPassword(), signature)) {
throw new UnauthorizedException("Invalid signature: " + signature);
}
log.info("Signature matched!!!");
// Register device
DeviceInfo device = regenerateKey(uid, user.getUsername());
log.info("Device found/registered successfully!!!");
if (!deviceBelongsToUser(user.getUsername(), device.getUsername())) {
throw new UnauthorizedException(String.format("User [ %s ] doesn't match the device's owner [ %s ]",
user.getUsername(), device.getUsername()));
}
}
/**
* Generate key for device UID. The key is encrypted by hash of salted
* password of the user. Encrypted key is then wrapped in JSON object before
* returning it. This function is useful in Identity mode
*
* @param username
* Unique user identifier
* @param uid
* Unique device identifier
* @return encrypted key as JSON object
* @throws DataAccessException
* @throws UnauthorizedException
*/
public String getKey(String username, String uid) throws DataAccessException, UnauthorizedException {
DeviceInfo device = deviceAuthenticator.getDeviceInfo(uid);
if (device == null) {
throw new UnauthorizedException("Couldn't find device: " + uid);
}
UserInfo user = userAuthenticator.getUserInfo(username);
if (user == null) {
throw new UnauthorizedException("Couldn't find user: " + username);
}
log.info("Responding with encrypted key for UID : " + uid);
return Utilities.prepareJsonResponseForKey(device.getKey(), user.getHashedPassword());
}
/**
* This method regenerates the key each time. It lookups up device details
* of a registered device. Also registers device if it is not already
* registered.
*
* @param uid
* Unique device identifier
* @param username
* Userid of the current user
* @return device info i.e. key and userid
* @throws DataAccessException
*/
private DeviceInfo regenerateKey(String uid, String username)
throws DataAccessException {
log.info("Generating encryption key");
String encryptionKey = Utilities.generateRandomString();
if (deviceAuthenticator.registerDevice(uid, encryptionKey, username)) {
return deviceAuthenticator.getDeviceInfo(uid);
}
return null;
}
/**
* Checks of the device UID belongs to the given user
*
* @param useridFromUser
* Userid of the current user
* @param useridFromDevice
* Userid associated with the given UID
* @return true if device UID belongs to current user, false otherwise
*/
private boolean deviceBelongsToUser(String useridFromUser, String useridFromDevice) {
return useridFromUser.equals(useridFromDevice);
}
}