/************************************************************************* * Copyright 2009-2015 Eucalyptus Systems, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses/. * * Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta * CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need * additional information or have any questions. * * This file may incorporate work covered under the following copyright * and permission notice: * * Software License Agreement (BSD License) * * Copyright (c) 2008, Regents of the University of California * All rights reserved. * * Redistribution and use of this software in source and binary forms, * with or without modification, are permitted provided that the * following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. USERS OF THIS SOFTWARE ACKNOWLEDGE * THE POSSIBLE PRESENCE OF OTHER OPEN SOURCE LICENSED MATERIAL, * COPYRIGHTED MATERIAL OR PATENTED MATERIAL IN THIS SOFTWARE, * AND IF ANY SUCH MATERIAL IS DISCOVERED THE PARTY DISCOVERING * IT MAY INFORM DR. RICH WOLSKI AT THE UNIVERSITY OF CALIFORNIA, * SANTA BARBARA WHO WILL THEN ASCERTAIN THE MOST APPROPRIATE REMEDY, * WHICH IN THE REGENTS' DISCRETION MAY INCLUDE, WITHOUT LIMITATION, * REPLACEMENT OF THE CODE SO IDENTIFIED, LICENSING OF THE CODE SO * IDENTIFIED, OR WITHDRAWAL OF THE CODE CAPABILITY TO THE EXTENT * NEEDED TO COMPLY WITH ANY SUCH LICENSES OR RIGHTS. ************************************************************************/ package com.eucalyptus.ws.util; import static com.eucalyptus.crypto.util.Timestamps.Type; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.EnumSet; import java.util.List; import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import com.eucalyptus.http.MappingHttpRequest; import com.eucalyptus.util.CollectionUtils; import com.eucalyptus.util.CompatFunction; import javaslang.control.Option; import org.apache.log4j.Logger; import org.jboss.netty.handler.codec.http.HttpHeaders; import com.eucalyptus.auth.login.AuthenticationException; import com.eucalyptus.crypto.Hmac; import com.eucalyptus.crypto.util.SecurityHeader; import com.eucalyptus.crypto.util.SecurityParameter; import com.eucalyptus.crypto.util.Timestamps; import com.eucalyptus.util.Strings; import com.google.common.base.Charsets; import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; public class HmacUtils { private static final Logger LOG = Logger.getLogger( HmacUtils.class ); private static final List<String> PARAMETERS_V1 = ImmutableList.of( SecurityParameter.SignatureVersion.parameter( ), SecurityParameter.Signature.parameter(), SecurityParameter.AWSAccessKeyId.parameter() ); private static final List<String> PARAMETERS_V2 = ImmutableList.of( SecurityParameter.SignatureVersion.parameter(), SecurityParameter.SignatureMethod.parameter(), SecurityParameter.Signature.parameter(), SecurityParameter.AWSAccessKeyId.parameter() ); private static final List<String> PARAMETERS_V4_QUERY = ImmutableList.of( SecurityParameter.X_Amz_Algorithm.parameter(), SecurityParameter.X_Amz_Credential.parameter(), SecurityParameter.X_Amz_Signature.parameter(), SecurityParameter.X_Amz_SignedHeaders.parameter() ); private static final List<String> DATE_PARAMETERS_V4_QUERY = ImmutableList.of( SecurityParameter.X_Amz_Date.parameter(), SecurityParameter.X_Amz_Expires.parameter() ); public enum SignatureVariant { SignatureV1Standard( SignatureVersion.SignatureV1 ) { @Override public Date getTimestamp( @Nonnull final Function<String, List<String>> headerLookup, @Nonnull final Function<String, List<String>> parameterLookup ) throws AuthenticationException { return getSignatureDateFrom( "parameter", parameterLookup, SecurityParameter.Timestamp.parameter(), Type.ISO_8601 ); } @Override public Collection<String> getParametersToRemove() { return PARAMETERS_V1; } @Override public Collection<String> getDateParametersToRemove() { return Collections.singleton( SecurityParameter.Timestamp.parameter() ); } @Override public String getSignature( @Nonnull final Function<String, List<String>> headerLookup, @Nonnull final Function<String, List<String>> parameterLookup ) throws AuthenticationException { return lookupUniqueRequired( parameterLookup, "parameter", SecurityParameter.Signature.parameter() ); } @Override public String getAccessKeyId( @Nonnull final Function<String, List<String>> headerLookup, @Nonnull final Function<String, List<String>> parameterLookup ) throws AuthenticationException { return lookupUniqueRequired( parameterLookup, "parameter", SecurityParameter.AWSAccessKeyId.parameter() ); } @Override public Hmac getSignatureMethod( @Nonnull final Function<String, List<String>> headerLookup, @Nonnull final Function<String, List<String>> parameterLookup ) throws AuthenticationException { return Hmac.HmacSHA1; } @Override public Map<String, String> getAuthorizationParameters( @Nonnull final Function<String, List<String>> headerLookup, @Nonnull final Function<String, List<String>> parameterLookup ) throws AuthenticationException { throw new AuthenticationException("Authorization parameters not found"); } @Override public String getSecurityToken( @Nonnull final Function<String, List<String>> headerLookup, @Nonnull final Function<String, List<String>> parameterLookup ) throws AuthenticationException { return lookupUnique( parameterLookup, "parameter", SecurityParameter.SecurityToken.parameter() ); } }, SignatureV2Standard( SignatureVersion.SignatureV2 ) { @Override public Date getTimestamp( @Nonnull final Function<String, List<String>> headerLookup, @Nonnull final Function<String, List<String>> parameterLookup ) throws AuthenticationException { return SignatureV1Standard.getTimestamp( headerLookup, parameterLookup ); } @Override public Collection<String> getParametersToRemove() { return PARAMETERS_V2; } @Override public Collection<String> getDateParametersToRemove() { return SignatureV1Standard.getDateParametersToRemove(); } @Override public String getSignature( @Nonnull final Function<String, List<String>> headerLookup, @Nonnull final Function<String, List<String>> parameterLookup ) throws AuthenticationException { return SignatureV1Standard.getSignature( headerLookup, parameterLookup ); } @Override public String getAccessKeyId( @Nonnull final Function<String, List<String>> headerLookup, @Nonnull final Function<String, List<String>> parameterLookup ) throws AuthenticationException { return SignatureV1Standard.getAccessKeyId( headerLookup, parameterLookup ); } @Override public Hmac getSignatureMethod( @Nonnull final Function<String, List<String>> headerLookup, @Nonnull final Function<String, List<String>> parameterLookup ) throws AuthenticationException { String sigMethod = lookupUnique( parameterLookup, "parameter", SecurityParameter.SignatureMethod.parameter() ); sigMethod = ( ( sigMethod == null ) ? "HMACSHA1" : sigMethod ); try { return Hmac.valueOf( "HmacSHA" + sigMethod.substring( 7 ) ); } catch ( IllegalArgumentException e ) { throw new AuthenticationException( "Invalid signature method: " + sigMethod ); } } @Override public Map<String, String> getAuthorizationParameters( @Nonnull final Function<String, List<String>> headerLookup, @Nonnull final Function<String, List<String>> parameterLookup ) throws AuthenticationException { return SignatureV1Standard.getAuthorizationParameters( headerLookup, parameterLookup ); } @Override public String getSecurityToken( @Nonnull final Function<String, List<String>> headerLookup, @Nonnull final Function<String, List<String>> parameterLookup ) throws AuthenticationException { return SignatureV1Standard.getSecurityToken( headerLookup, parameterLookup ); } }, SignatureV4Standard( SignatureVersion.SignatureV4 ) { private final Splitter CSV_SPLITTER = Splitter.on(',').trimResults().omitEmptyStrings(); private final Splitter NVP_SPLITTER = Splitter.on('=').limit(2).trimResults().omitEmptyStrings(); @Override public Date getTimestamp( @Nonnull final Function<String, List<String>> headerLookup, @Nonnull final Function<String, List<String>> parameterLookup ) throws AuthenticationException { Date timestamp = getSignatureDateFrom( "header", headerLookup, SecurityHeader.X_Amz_Date.header(), Type.ISO_8601 ); if ( timestamp == null ) { timestamp = getSignatureDateFrom( "header", headerLookup, SecurityHeader.Date.header(), Type.RFC_2616 ); } return timestamp; } @Override public Collection<String> getParametersToRemove() { return Collections.emptyList(); } @Override public Collection<String> getDateParametersToRemove() { return Collections.emptyList(); } @Override public String getSignature( @Nonnull final Function<String, List<String>> headerLookup, @Nonnull final Function<String, List<String>> parameterLookup ) throws AuthenticationException { return getAuthorizationParameters( headerLookup, parameterLookup ).get( "Signature" ); } @Override public String getAccessKeyId( @Nonnull final Function<String, List<String>> headerLookup, @Nonnull final Function<String, List<String>> parameterLookup ) throws AuthenticationException { return getSignatureCredential( headerLookup, parameterLookup ).getAccessKeyId(); } @Override public Hmac getSignatureMethod( @Nonnull final Function<String, List<String>> headerLookup, @Nonnull final Function<String, List<String>> parameterLookup ) throws AuthenticationException { return Hmac.HmacSHA256; } @Override public Map<String,String> getAuthorizationParameters( @Nonnull final Function<String, List<String>> headerLookup, @Nonnull final Function<String, List<String>> parameterLookup) throws AuthenticationException { final String auth = lookupUniqueRequired( headerLookup, "header", HttpHeaders.Names.AUTHORIZATION ) .replaceFirst(SecurityHeader.Value.AWS4_HMAC_SHA256.value(),"").trim(); final Iterable<String> authParts = CSV_SPLITTER.split( auth ); final Map<String,String> authParams = Maps.newHashMap(); for ( final String nvp : authParts ) { final Iterable<String> nameAndValue = NVP_SPLITTER.split( nvp ); final String name = Iterables.get( nameAndValue, 0, "" ); final String value = Iterables.get( nameAndValue, 1, "" ); if ( !name.isEmpty() && !value.isEmpty() ) { authParams.put( name, value ); } } if ( !authParams.keySet().containsAll( Lists.newArrayList( "Credential", "SignedHeaders", "Signature" ) ) ) { throw new AuthenticationException( "Invalid authorization header: " + auth ); } return authParams; } private SignatureCredential getSignatureCredential( @Nonnull final Function<String, List<String>> headerLookup, @Nonnull final Function<String, List<String>> parameterLookup ) throws AuthenticationException { return new SignatureCredential( getAuthorizationParameters( headerLookup, parameterLookup ).get( "Credential" ) ); } @Override public String getSecurityToken( @Nonnull final Function<String, List<String>> headerLookup, @Nonnull final Function<String, List<String>> parameterLookup ) throws AuthenticationException { return lookupUnique( headerLookup, "header", SecurityParameter.X_Amz_Security_Token.parameter() ); } }, SignatureV4Query( SignatureVersion.SignatureV4 ) { @Override public Date getTimestamp( @Nonnull final Function<String, List<String>> headerLookup, @Nonnull final Function<String, List<String>> parameterLookup ) throws AuthenticationException { Date timestamp = getSignatureDateFrom( "parameter", parameterLookup, SecurityParameter.X_Amz_Date.parameter(), Type.ISO_8601 ); if ( timestamp == null ) { timestamp = getSignatureDateFrom( "header", headerLookup, SecurityHeader.Date.header(), Type.RFC_2616 ); } return timestamp; } @Override public Collection<String> getParametersToRemove() { return PARAMETERS_V4_QUERY; } @Override public Collection<String> getDateParametersToRemove() { return DATE_PARAMETERS_V4_QUERY; } @Override public String getSignature( @Nonnull final Function<String, List<String>> headerLookup, @Nonnull final Function<String, List<String>> parameterLookup ) throws AuthenticationException { return lookupUniqueRequired( parameterLookup, "parameter", SecurityParameter.X_Amz_Signature.parameter() ); } @Override public String getAccessKeyId( @Nonnull final Function<String, List<String>> headerLookup, @Nonnull final Function<String, List<String>> parameterLookup ) throws AuthenticationException { return new SignatureCredential( lookupUniqueRequired( parameterLookup, "parameter", SecurityParameter.X_Amz_Credential.parameter() ) ).getAccessKeyId(); } @Override public Hmac getSignatureMethod( @Nonnull final Function<String, List<String>> headerLookup, @Nonnull final Function<String, List<String>> parameterLookup ) throws AuthenticationException { return Hmac.HmacSHA256; } @Override public Map<String, String> getAuthorizationParameters( @Nonnull final Function<String, List<String>> headerLookup, @Nonnull final Function<String, List<String>> parameterLookup ) throws AuthenticationException { return ImmutableMap.<String,String>builder() .put( "Credential", lookupUniqueRequired( parameterLookup, "parameter", SecurityParameter.X_Amz_Credential.parameter() ) ) .put( "Signature", getSignature( headerLookup, parameterLookup ) ) .put( "SignedHeaders", lookupUniqueRequired( parameterLookup, "parameter", SecurityParameter.X_Amz_SignedHeaders.parameter() ) ) .build(); } @Override public String getSecurityToken( @Nonnull final Function<String, List<String>> headerLookup, @Nonnull final Function<String, List<String>> parameterLookup ) throws AuthenticationException { return lookupUnique( parameterLookup, "parameter", SecurityParameter.X_Amz_Security_Token.parameter() ); } }; private final SignatureVersion version; private SignatureVariant( final SignatureVersion version ) { this.version = version; } @Nonnull public SignatureVersion getVersion() { return version; } @Nullable public abstract Date getTimestamp( @Nonnull final Function<String,List<String>> headerLookup, @Nonnull final Function<String,List<String>> parameterLookup ) throws AuthenticationException; @Nullable private static Date getSignatureDateFrom( final String where, final Function<String,List<String>> lookup, final String name, final Type type ) throws AuthenticationException { final String value = lookupUnique( lookup, where, name ); if ( value != null ) { return Timestamps.parseTimestamp( value, type ); } return null; } public abstract Collection<String> getParametersToRemove(); public abstract Collection<String> getDateParametersToRemove(); public abstract String getSignature( @Nonnull final Function<String, List<String>> headerLookup, @Nonnull final Function<String, List<String>> parameterLookup ) throws AuthenticationException; public abstract String getAccessKeyId( @Nonnull final Function<String, List<String>> headerLookup, @Nonnull final Function<String, List<String>> parameterLookup ) throws AuthenticationException; public abstract Hmac getSignatureMethod( @Nonnull final Function<String, List<String>> headerLookup, @Nonnull final Function<String, List<String>> parameterLookup ) throws AuthenticationException; public abstract Map<String,String> getAuthorizationParameters( @Nonnull final Function<String, List<String>> headerLookup, @Nonnull final Function<String, List<String>> parameterLookup ) throws AuthenticationException; public abstract String getSecurityToken( @Nonnull final Function<String, List<String>> headerLookup, @Nonnull final Function<String, List<String>> parameterLookup ) throws AuthenticationException; } public static Function<String,List<String>> headerLookup( final Map<String,List<String>> values ) { return forMap( values, Strings.lower() ); } public static Function<String,List<String>> parameterLookup( final Map<String,List<String>> values ) { return forMap( values, Functions.<String>identity() ); } private static Function<String,List<String>> forMap( final Map<String,List<String>> values, Function<String,String> keyConversion ) { return Functions.compose( Functions.forMap( values, Collections.<String>emptyList() ), keyConversion ); } public enum SignatureVersion { SignatureV1(1), SignatureV2(2), SignatureV4(4); private int value; private SignatureVersion( final int value ) { this.value = value; } public int value() { return value; } } /** * Detect a signature variant from headers / url parameters * * @param headerLookup Function to get an HTTP header value * @param parameterLookup Function to get a parameter value * @return The detected signature variant * @throws AuthenticationException if a signature variant was not detected or was invalid */ @Nonnull public static SignatureVariant detectSignatureVariant( @Nonnull final Function<String,List<String>> headerLookup, @Nonnull final Function<String,List<String>> parameterLookup ) throws AuthenticationException { final SignatureVariant variant; if ( SecurityHeader.Value.AWS4_HMAC_SHA256.matches( lookupUnique( headerLookup, "header", HttpHeaders.Names.AUTHORIZATION ) ) ) { variant = SignatureVariant.SignatureV4Standard; } else if ( lookupUnique( parameterLookup, "parameter", SecurityParameter.X_Amz_Algorithm.parameter() ) != null || lookupUnique( parameterLookup, "parameter", SecurityParameter.X_Amz_Date.parameter() ) != null ) { variant = SignatureVariant.SignatureV4Query; } else { String signatureVersion = lookupUnique( parameterLookup, "parameter", SecurityParameter.SignatureVersion.parameter() ); if ( "1".equals( signatureVersion ) ) { variant = SignatureVariant.SignatureV1Standard; } else if ( "2".equals( signatureVersion ) || signatureVersion == null ) { variant = SignatureVariant.SignatureV2Standard; } else { throw new AuthenticationException("Unsupported signature version " + signatureVersion ); } } return variant; } /** * Check if a date value can be found. * * @param versions The versions to allow * @param headerLookup Function to get an HTTP header value * @param parameterLookup Function to get a parameter value * @return True if a date value is present */ public static boolean hasSignatureDate( @Nonnull final EnumSet<SignatureVersion> versions, @Nonnull final Function<String,List<String>> headerLookup, @Nonnull final Function<String,List<String>> parameterLookup ) throws AuthenticationException { return getSignatureDateInternal( versions, headerLookup, parameterLookup ) != null; } /** * Locate and return the date for a signature. * * @param versions The versions to allow * @param headerLookup Function to get an HTTP header value * @param parameterLookup Function to get a parameter value * @return The date * @throws AuthenticationException If a date could not be located */ @Nonnull public static Date getSignatureDate( @Nonnull final EnumSet<SignatureVersion> versions, @Nonnull final Function<String,List<String>> headerLookup, @Nonnull final Function<String,List<String>> parameterLookup ) throws AuthenticationException { final Date signatureDate = getSignatureDateInternal( versions, headerLookup, parameterLookup ); if ( signatureDate == null) { throw new AuthenticationException("Date not found."); } return signatureDate; } public static byte[] getSignature( final String key, final String subject, final Hmac mac ) throws AuthenticationException { final SecretKeySpec signingKey = new SecretKeySpec( key.getBytes( Charsets.UTF_8 ), mac.toString( ) ); try { return mac.digestBinary( signingKey, subject.getBytes( Charsets.UTF_8 ) ); } catch ( Exception e ) { LOG.error( e, e ); throw new AuthenticationException( "Failed to compute signature" ); } } private static String lookupUnique( final Function<String,List<String>> lookup, String type, String name ) throws AuthenticationException { String result = null; final Iterable<String> values = lookup.apply( name ); if ( values != null ) for ( String value : values ) { if ( result == null ) { result = value; } else if ( value != null && !value.equals( result ) ) { throw new AuthenticationException("Duplicate " + type + " for " + name +" with incorrect value " + value + ", expected " + result); } } return result; } private static String lookupUniqueRequired( final Function<String,List<String>> lookup, String type, String name ) throws AuthenticationException { String value = lookupUnique( lookup, type, name ); if ( value == null ) { throw new AuthenticationException( "Missing required parameter: " +name ); } return value; } /** * TODO:GUAVA:Optional */ @Nullable private static Date getSignatureDateInternal( @Nonnull final EnumSet<SignatureVersion> versions, @Nonnull final Function<String,List<String>> headerLookup, @Nonnull final Function<String,List<String>> parameterLookup ) throws AuthenticationException { final SignatureVariant variant = detectSignatureVariant( headerLookup, parameterLookup ); if ( !versions.contains( variant.getVersion() ) ) { return null; } return variant.getTimestamp( headerLookup, parameterLookup ); } /** * A signature V4 credential. * * A slash('/')-separated string that is formed by concatenating your Access * Key ID and your credential scope components. Credential scope comprises * the date (YYYYMMDD), the AWS region, the service name, and a special * termination string (aws4_request). For example, the following string * represents the Credential parameter for an IAM request in the US East * Region. * * |-- Access Key ID --|------- Credential Scope ---------| * AKIAIOSFODNN7EXAMPLE/20111015/us-east-1/iam/aws4_request * * Important * * You must use lowercase characters for the region, service name, and * special termination string. */ public static class SignatureCredential { @Nonnull private final String accessKeyId; @Nonnull private final String date; @Nonnull private final String region; @Nonnull private final String serviceName; @Nonnull private final String terminator; public SignatureCredential( @Nonnull final String credential ) throws AuthenticationException { final String[] credentialParts = credential.trim().split( "/" ); if ( credentialParts.length != 5 ) { throw new AuthenticationException("Invalid credential (missing part): [" + credential + "]"); } accessKeyId = credentialParts[0]; date = credentialParts[1]; region = credentialParts[2]; serviceName = credentialParts[3]; terminator = credentialParts[4]; } public void verify( @Nonnull final Date timestamp, @Nullable final String region, @Nullable final String serviceName, @Nullable final String terminator ) throws AuthenticationException { final String expectedDate = Timestamps.formatShortIso8601Date( timestamp ); internalVerify( "date", expectedDate, this.date ); internalVerify( "region", region, this.region ); internalVerify( "service name", serviceName, this.serviceName ); internalVerify( "termination string", terminator, this.terminator ); } private void internalVerify( final String what, final String expect, final String value ) throws AuthenticationException { if ( expect != null && !expect.equals( value ) ) { throw new AuthenticationException("Expected "+what+" does not match credential "+what+" [" + expect + " != " + value + "]"); } } @Nonnull public String getAccessKeyId() { return accessKeyId; } @Nonnull public String getDate() { return date; } @Nonnull public String getRegion() { return region; } @Nonnull public String getServiceName() { return serviceName; } @Nonnull public String getTerminator() { return terminator; } @Nonnull public String getCredentialScope() { return date + "/" + region + "/" + serviceName + "/" + terminator; } } public static CompatFunction<String,List<String>> headerLookup(final MappingHttpRequest request ) { return request::getHeaders; } public static CompatFunction<String,List<String>> parameterLookup( final MappingHttpRequest request ) { final Map<String,String> parameters = request.getParameters(); return header -> CollectionUtils.<String>listUnit().apply( parameters.get( header ) ); } public static Option<SignatureVariant> signatureVariantOption( final MappingHttpRequest request ) { try { final CompatFunction<String, List<String>> headerLookup = headerLookup( request ); final CompatFunction<String, List<String>> parameterLookup = parameterLookup( request ); final SignatureVariant variant = detectSignatureVariant( headerLookup, parameterLookup ); variant.getAccessKeyId( headerLookup, parameterLookup ); return Option.of( variant ); } catch ( AuthenticationException e ) { return Option.none( ); } } @Nonnull public static SignatureVariant detectSignatureVariant( @Nonnull final MappingHttpRequest request ) throws AuthenticationException { return detectSignatureVariant( headerLookup( request ), parameterLookup( request ) ); } }