/**
* Copyright 2005-2011 Noelios Technologies.
*
* The contents of this file are subject to the terms of one of the following
* open source licenses: LGPL 3.0 or LGPL 2.1 or CDDL 1.0 or EPL 1.0 (the
* "Licenses"). You can select the license that you prefer but you may not use
* this file except in compliance with one of these Licenses.
*
* You can obtain a copy of the LGPL 3.0 license at
* http://www.opensource.org/licenses/lgpl-3.0.html
*
* You can obtain a copy of the LGPL 2.1 license at
* http://www.opensource.org/licenses/lgpl-2.1.php
*
* You can obtain a copy of the CDDL 1.0 license at
* http://www.opensource.org/licenses/cddl1.php
*
* You can obtain a copy of the EPL 1.0 license at
* http://www.opensource.org/licenses/eclipse-1.0.php
*
* See the Licenses for the specific language governing permissions and
* limitations under the Licenses.
*
* Alternatively, you can obtain a royalty free commercial license with less
* limitations, transferable or non-transferable, directly at
* http://www.noelios.com/products/restlet-engine
*
* Restlet is a registered trademark of Noelios Technologies.
*/
package org.restlet.ext.crypto.internal;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.data.Form;
import org.restlet.engine.header.HeaderConstants;
import org.restlet.engine.util.DateUtils;
import org.restlet.security.LocalVerifier;
import org.restlet.security.SecretVerifier;
import org.restlet.security.User;
/**
* Wrapped verifier that can verify HTTP requests utilizing the Amazon S3
* authentication scheme. Verifies the user by computing the request signature
* using the local secret and comparing it to the signature provided in the
* request.
* <p>
* Per the Amazon S3 specification the {@code Date} header is required. If the
* {@code Date} header is missing or the request is older than the allowed time
* limit, specified by the {@code maxRequestAge} property, the request fails
* verification.
*
* @author Jean-Philippe Steinmetz <caskater47@gmail.com>
* @see <a
* href="http://docs.amazonwebservices.com/AmazonS3/latest/RESTAuthentication.html">
* Authenticating REST Requests</a>
*/
public class AwsVerifier extends SecretVerifier {
/**
* Default maximum request age (15 minutes)
*/
private static final long DEFAULT_MAX_REQUEST_AGE = 15 * 60 * 1000L;
/**
* The maximum age of a request, in milliseconds, before it is considered
* stale.
*/
private long maxRequestAge;
/** The local secret verifier. */
private LocalVerifier wrappedVerifier;
/**
* Creates a new HttpAwsS3Verifier instance.
*
* @param wrappedVerifier
* The wrapped verifier containing local identifier/secret
* couples
*/
public AwsVerifier(LocalVerifier wrappedVerifier) {
this(wrappedVerifier, DEFAULT_MAX_REQUEST_AGE);
}
/**
* Creates a new HttpAwsS3Verifier instance.
*
* @param wrappedVerifier
* The wrapped verifier containing local identifier/secret
* couples
* @param maxRequestAge
* The maximum age of a request, in milliseconds, before it is
* considered stale
*/
public AwsVerifier(LocalVerifier wrappedVerifier, long maxRequestAge) {
super();
setMaxRequestAge(maxRequestAge);
setWrappedVerifier(wrappedVerifier);
}
/**
* Returns the user identifier portion of an Amazon S3 compatible
* {@code Authorization} header.
* <p>
* An Amazon S3 compatible {@code Authorization} header has the following
* pattern.<br/>
* {@code Authorization: AWS id:signature}
*/
@Override
protected String getIdentifier(Request request, Response response) {
if (request.getChallengeResponse() == null
|| request.getChallengeResponse().getRawValue() == null)
return null;
String[] parts = request.getChallengeResponse().getRawValue()
.split(":");
if (parts != null && parts.length == 2)
return parts[0];
else
return null;
}
/**
* Returns the local secret associated to a given identifier.
*
* @param identifier
* The identifier to lookup.
* @return The secret associated to the identifier or null.
*/
public char[] getLocalSecret(String identifier) {
char[] result = null;
result = getWrappedVerifier().getLocalSecret(identifier);
return result;
}
/**
* Returns the maximum age of a request, in milliseconds, before it is
* considered stale.
* <p>
* A negative or zero value indicates no age restriction. The default value
* is 15 minutes.
*/
public long getMaxRequestAge() {
return this.maxRequestAge;
}
/**
* Returns the signature portion of an Amazon S3 compatible
* {@code Authorization} header.
* <p>
* An Amazon S3 compatible {@code Authorization} header has the following
* pattern.<br/>
* {@code Authorization: AWS id:signature}
*/
@Override
protected char[] getSecret(Request request, Response response) {
if (request.getChallengeResponse() == null
|| request.getChallengeResponse().getRawValue() == null)
return null;
String[] parts = request.getChallengeResponse().getRawValue()
.split(":");
if (parts != null && parts.length == 2)
return parts[1].toCharArray();
else
return null;
}
/**
* Returns the wrapped local secret verifier.
*
* @return The local secret verifier.
*/
public LocalVerifier getWrappedVerifier() {
return wrappedVerifier;
}
/**
* Sets the maximum age of a request, in milliseconds, before it is
* considered stale.
* <p>
* A negative or zero value indicates no age restriction. The default value
* is 15 minutes.
*/
public void setMaxRequestAge(long value) {
if (value < 0)
value = 0;
this.maxRequestAge = value;
}
/**
* Sets the wrapped local secret verifier.
*
* @param wrappedVerifier
* The local secret verifier.
*/
public void setWrappedVerifier(LocalVerifier wrappedVerifier) {
this.wrappedVerifier = wrappedVerifier;
}
@Override
public int verify(Request request, Response response) {
if (request.getChallengeResponse() == null)
return RESULT_MISSING;
Form headers = (Form) request.getAttributes().get(
"org.restlet.http.headers");
String userId = getIdentifier(request, response);
if (userId == null || (userId.length() == 0))
return RESULT_MISSING;
// A date header is always required
if (headers.getFirstValue(HeaderConstants.HEADER_DATE, true) == null)
return RESULT_INVALID;
// Make sure the date is not stale
if (getMaxRequestAge() > 0) {
Long date = DateUtils.parse(
headers.getFirstValue(HeaderConstants.HEADER_DATE, true))
.getTime();
Long now = System.currentTimeMillis();
if (now - date > getMaxRequestAge())
return RESULT_STALE;
}
char[] userSecret = getLocalSecret(userId);
char[] signature = getSecret(request, response);
String sigToCompare = AwsUtils.getSignature(request, userSecret);
if (!compare(signature, sigToCompare.toCharArray()))
return RESULT_INVALID;
request.getClientInfo().setUser(new User(userId));
return RESULT_VALID;
}
/**
* This function is not implemented because the authorization scheme
* requires direct access to the request. See
* {@link #verify(Request, Response)}.
*/
@Override
public boolean verify(String identifier, char[] secret)
throws IllegalArgumentException {
throw new RuntimeException("Method not implemented");
}
}