aws-amplify / aws-sdk-ios

AWS SDK for iOS. For more information, see our web site:
https://aws-amplify.github.io/docs
Other
1.67k stars 878 forks source link

Unable to Correctly Sign HTTP Request #474

Closed ghost closed 7 years ago

ghost commented 7 years ago

This is in Xcode 7.3.1 (7D1014) with CocoaPods 0.39.0 on OS X 10.11.6. We are writing exclusively in Objective-C.

We are converting a legacy application to use AWS instead of a home-grown server. That application does arbitrary HTTP requests, which means that we are going to want to sign HTTP requests. We have been trying to build this based on the documentation, along with some help from the implementation here.

We are doing this against Cognito User Pools. So we set up the pool:

[AWSLogger defaultLogger].logLevel = AWSLogLevelInfo;
AWSCognitoCredentialsProvider *credentialsProvider = [[AWSCognitoCredentialsProvider alloc] initWithRegionType:CognitoIdentityUserPoolRegion
                                                                                                identityPoolId:CognitoCredentialsIdentityPoolId];
AWSServiceConfiguration *serviceConfiguration = [[AWSServiceConfiguration alloc] initWithRegion:CognitoIdentityUserPoolRegion
                                                                            credentialsProvider:credentialsProvider];
AWSCognitoIdentityUserPoolConfiguration *userPoolConfiguration = [[AWSCognitoIdentityUserPoolConfiguration alloc] initWithClientId:CognitoIdentityUserPoolAppClientId
                                                                                                                      clientSecret:CognitoIdentityUserPoolAppClientSecret
                                                                                                                            poolId:CognitoIdentityUserPoolId];
[AWSCognitoIdentityUserPool registerCognitoIdentityUserPoolWithConfiguration:serviceConfiguration
                                                           userPoolConfiguration:userPoolConfiguration
                                                                          forKey:@"UserPool"];
AWSCognitoIdentityUserPool *pool = [AWSCognitoIdentityUserPool CognitoIdentityUserPoolForKey:@"UserPool"];
[credentialsProvider setIdentityProviderManagerOnce:self.pool];

Along with using -getDetails to log the user in with no code that's particularly interesting.

After the user is logged in, we make sure to grab the credentials and cache them locally:

[credentialsProvider.credentials continueWithBlock:^id _Nullable(AWSTask<AWSCredentials *> * _Nonnull task) {
    if (task.error != nil) {
        NSLog(@"Credentials Error: %@", task.error);
    } else {
        NSLog(@"Credentials Response %@", task.result);
        self.cachedCredentials = task.result;
    }
    return nil;
}];

We are getting back credentials from that call (digits changed to invalidate):

Credentials Response {
AWSCredentials
AccessKey: ASIAI6VOTAD99CTYJMFQ
SecretKey: rQ9999AkRoOjl/F7y7Km5lfRzD1ptAiI1UJfeNau
SessionKey: AgoGb3JpZ2luELr//////////wEaCXVzLWVhc3QtMSKAApgNvvECaT7sCrI15143V1I4kLYY/eLmSu1I7dGTI2lu999Dt+Lu0amnP1r2vzm7cZKcseJbTDxaT9BoCZJbLiK3NiDBSEH3NKT9HzhASu4fClcL6bpx5cItXOWksuuC9bqS19/RDBoAcWLfrbwhhQHKM45BLKZQNLFj7FLoJSKtJCwiKEV1zQIQm8JMgXPjHzM42QEGWODSgz9PwlB9TIzWe9Jkm+4MB2jSfNALzDAYM3pootNGDDX4JRsbDUgjjMZXVb+vs7PkvvK2NONsSb3Tb3RiOpIcuc8YqderomqpKKYtVRgHy3cmjaIm8Ws2oKpUetBPxkt3c3Clrnfc0VwqnQUIXxAAGgw2NjQwNjAzODM0NDkiDJrezoCDtMVu93G39Cr6BHcHUdmYllS6pNM27+wFikFQTQ/VvaaSyDqne9cmB14VOEXT9zxMe6lhAdK7SRCrJaa4ObpiTg0iJT7ZO6WCMGO01eRMZ2/dWjWOUxPrxohDb2bs2/yRpqez29cYcdn6X69Lz3o+CVQu5JEa9Zc25/bFxbr0Ho0bwpJ2W2jpAm8nA0CEon/HssD4TCk5VFMeH3+gWzx0h/uCCea2BCvO1Fd9lbwDtgbqXQV5f/n+kzRuNUtnBEIWXYFT3X/U2sOk5vMVqLQqmlfH6P/RR2wluyUi/Ok7q/rkIm0/y+WV2bQZoiGowotm5y/NxDe54mzW8srg6UGURYJz+qvxv9RB/YQuGf4CddJ1oBaharGZF+dOfVlDyjFIEj2SK8xt8RgqQjzpP7RF9zUJtcpcPvqZetEb81ktXzn5OorpaI6c0g1noTeX7DFd/4/Ltq+QUhgxgjza30iz0lSbWBysjS9kTfSSnVFCs9r1WHRgrSFfbP8t3cf8iuzH+pyV9pQDLt6Wjz294l9VfoI6DPDoLmAxdrPJQZobFuG30Bq8uRSnj5WzRG88u03H6FiPJSdIQuB1JWIFu3EK8ejVA57lHFLeHJzhGE5JCYsVCMNNsxsXw4I87D9CsowP0h0fPDQdr0YY91soKLci+ZJDM5y97jvPhOdkYjof3yJWvryxPTDIE0WgU+TSWwV4c75oNJHdr1Jf9cBDb25/Cl1P/0JdaOE9EGCUuat36IJmEctjIyb+Q2BIt9X9hdf6xILnIloIAJNLZjE2liP8VgFy7Eapm14Cac5GpP1z9hlX0378uWKDOj0GSSOOSjum6OKUh99Et6MOjDBVxvS1kGRhaAEwqKuKvwU=
Expiration: 2016-09-21 15:14:00 +0000
}

Once we have the credentials cached, we are using them to sign the HTTP request using fancy math:

self.urlSession = [NSURLSession sessionWithConfiguration:configuration
                                                delegate:self
                                           delegateQueue:nil];

NSURL *rawUrl = [NSURL URLWithString:@"https://s3.amazonaws.com/bucket/alligator.jpg"];

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:rawUrl
                                                       cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
                                                   timeoutInterval:30];

[RequestSigner signURLRequestUsingV4:request
                              atDate:[NSDate date]
                            inRegion:UAAWSRegionUSEast1
                       withAccessKey:self.cachedCredentials.accessKey
                           secretKey:self.cachedCredentials.secretKey];

NSLog(@"Headers: %@", request.allHTTPHeaderFields);
+ (void)signURLRequestUsingV4:(NSMutableURLRequest *)urlRequest
                       atDate:(NSDate *)requestDate
                     inRegion:(UAAWSRegion)region
                withAccessKey:(NSString *)accessKey
                    secretKey:(NSString *)secretKey {
    NSLog(@"Signing request to '%@' at time '%@' to region '%@' with access key '%@' secret key '%@'", urlRequest.URL.absoluteString, requestDate, @(region), accessKey, secretKey);

    // Set the X-Amz-Date header
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setDateFormat:@"yyyyMMdd'T'HHmmss'Z'"];
    [dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]];
    [dateFormatter setTimeZone:[NSTimeZone timeZoneWithName:@"UTC"]];

    NSString *xAmzDate = [dateFormatter stringFromDate:requestDate];
    [urlRequest setValue:xAmzDate forHTTPHeaderField:@"X-Amz-Date"];

    NSString *bodyHash = urlRequest.HTTPBody ? [[urlRequest.HTTPBody UA_sha256] UA_hexString] : @"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
    NSLog(@"Body hash is %@", bodyHash);

    [urlRequest setValue:bodyHash forHTTPHeaderField:@"x-amz-content-sha256"];

    // Make sure we manually set the host header
    [urlRequest setValue:urlRequest.URL.host forHTTPHeaderField:@"Host"];

    // make a canonical copy of the headers for signing
    NSDictionary *headers = [self canonicalHeadersWithHeaders:urlRequest.allHTTPHeaderFields];

    // create the canonical request for signing
    NSString *canonicalRequest = [NSString stringWithFormat:@"%@\n%@\n%@\n%@\n%@\n%@",
                                  urlRequest.HTTPMethod,
                                  (urlRequest.URL.path ?: @"/"),
                                  (urlRequest.URL.query ?: @""),
                                  [self canonicalHeaderStringWithHeaders:headers],
                                  [self signedHeaderStringForCanonicalHeaders:headers],
                                  [[urlRequest.HTTPBody UA_sha256] UA_hexString]];

    // Now we have to sign all

    // Assemble the Authorization Header

    // reuse the date formatter to set this one
    [dateFormatter setDateFormat:@"yyyyMMdd"];
    NSString *dateString = [dateFormatter stringFromDate:requestDate];

    // Scope
    NSString *scope = [NSString stringWithFormat:@"%@/%@/%@/aws4_request",
                       dateString,
                       [NSString UA_regionStringForRegionValue:region],
                       [urlRequest.URL UA_AWSServiceName]];

    // The string that gets signed
    NSString *signme = [NSString stringWithFormat:@"AWS4-HMAC-SHA256\n%@\n%@\n%@",
                        xAmzDate,
                        scope,
                        [[[canonicalRequest UA_UTF8Data] UA_sha256] UA_hexString]];

    // We run the key through several HMAC's before arriving at the final one
    NSData *key = [[@"AWS4" stringByAppendingString:secretKey] UA_UTF8Data];
    key = [[dateString UA_UTF8Data] UA_hmacSHA256WithDataKey:key];
    key = [[[NSString UA_regionStringForRegionValue:region] UA_UTF8Data] UA_hmacSHA256WithDataKey:key];
    key = [[[urlRequest.URL UA_AWSServiceName] UA_UTF8Data] UA_hmacSHA256WithDataKey:key];
    key = [[@"aws4_request" UA_UTF8Data] UA_hmacSHA256WithDataKey:key];

    // now that we have the derived key, finally sign the payload string
    NSData *signature = [[signme UA_UTF8Data] UA_hmacSHA256WithDataKey:key];

    // Finally, the Authorization header!
    NSString *authorization = [NSString stringWithFormat:@"AWS4-HMAC-SHA256 Credential=%@/%@, SignedHeaders=%@, Signature=%@",
                               accessKey,
                               scope,
                               [self signedHeaderStringForCanonicalHeaders:headers],
                               [signature UA_hexString]];
    [urlRequest setValue:authorization forHTTPHeaderField:@"Authorization"];
}

+ (NSDictionary *)canonicalHeadersWithHeaders:(NSDictionary *)headers
{
    NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] initWithCapacity:[headers count]];
    NSCharacterSet *whitespaces = [NSCharacterSet whitespaceAndNewlineCharacterSet];

    // create a dictionary by trimming the headers and values appropriately. Also lowercase the header itself
    for (NSString *header in headers)
        [dictionary setObject:[[headers objectForKey:header] stringByTrimmingCharactersInSet:whitespaces]
                       forKey:[[header stringByTrimmingCharactersInSet:whitespaces] lowercaseString]];

    return [dictionary copy];
}

+ (NSString *)canonicalHeaderStringWithHeaders:(NSDictionary *)headers
{
    NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:0];

    for (NSString *header in [[headers allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)])
        [array addObject:[NSString stringWithFormat:@"%@:%@", header, [headers objectForKey:header]]];

    return [[array componentsJoinedByString:@"\n"] stringByAppendingString:@"\n"];
}

+ (NSString *)signedHeaderStringForCanonicalHeaders:(NSDictionary *)headers
{
    NSArray *allKeys = [[headers allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
    return [allKeys componentsJoinedByString:@";"];
}

And then we implement the delegate methods for NSURLSession in uninteresting ways (they simply log what they get called with).

Running this with a valid user that is logged in and can access the file in S3 using the transfer manager (so we know for a fact the user can download the file), we end up getting the following in the logs:

2016-09-21 09:14:13.857 CognitoSpike[2797:1177194] Signing request to 'https://s3.amazonaws.com/prios.spike.media.lockeddown/alligator.jpg' at time '2016-09-21 14:14:13 +0000' to region '1' with access key 'ASIAI6VOTAD99CTYJMFQ' secret key 'rQ9999AkRoOjl/F7y7Km5lfRzD1ptAiI1UJfeNau'
2016-09-21 09:14:13.858 CognitoSpike[2797:1177194] Body hash is e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
2016-09-21 09:14:13.858 CognitoSpike[2797:1177194] Headers: {
    Authorization = "AWS4-HMAC-SHA256 Credential=ASIAI6VOTAD99CTYJMFQ/20160921/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=fbd5b50514c4289254b289a9d25a817f72ab9b727fbf7ee770c8be90c83c80db";
    Host = "s3.amazonaws.com";
    "X-Amz-Date" = 20160921T141413Z;
    "x-amz-content-sha256" = e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855;
}
2016-09-21 09:14:14.085 CognitoSpike[2797:1177194] Looks like it says '<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>InvalidAccessKeyId</Code><Message>The AWS Access Key Id you provided does not exist in our records.</Message><AWSAccessKeyId>ASIAI6VOTAD99CTYJMFQ</AWSAccessKeyId><RequestId>C226FD3CD92AC3A3</RequestId><HostId>m1vgvC+rn8mBdeYSmq8rMuVYSRI7hCTibaqpVi2NWzrS2SAaFKHbBC38F4/KsJn959lnBszGCPo=</HostId></Error>'

This seems to imply that somewhere we are not hooking something up correctly. However, I cannot find any other place where we could conceivably get that user's Access Key and Secret Key, other than the credentials on the credentialsProvider; that would seem to be the most logical place for them.

So:

  1. Where do we get the user's actual Access Key and Secret Key?
  2. Where is it documented how to sign arbitrary HTTP requests in iOS?
  3. Where is it documented how to use Cognito User Pools for making arbitrary signed HTTP requests?
  4. Is there a working sample that demonstrates using both Cognito User Pools and signed HTTP requests? 4a. In particular, signing a POST request because that's our next step after we make sure GET works.
behrooziAWS commented 7 years ago

If you need to get presigned urls, use the AWSS3PreSignedURLBuilder. More info is in the docs here http://docs.aws.amazon.com/mobile/sdkforios/developerguide/s3transfermanager.html As long as you have a credentials provider hooked to user pools it should satisfy all of your needs.

ghost commented 7 years ago

We cannot use S3 Presigned URLs for multiple reasons:

  1. Ultimately, we are going to create a pod that is consumed by a dozen or so pods and a host application. Those pods are already building HTTP requests to an internal server, which they are running themselves. Though we are converting to AWS, we do also need to have the legacy application running in parallel. There is only time and money in the budget to modify those pods to check if they're in AWS mode or Legacy mode, and if AWS mode, ask our pod to sign their HTTP request before sending (otherwise, just send and manage it themselves). The NSURLRequest is already fully-formed by the time it gets to us; all we will need to do is sign it with the appropriate credentials and return it.
  2. While we started out working with S3 because file delivery is one component and it seemed easiest to start, the end result will not to the best of my knowledge have everything in S3. There will be non-S3 web services we need to talk to in AWS, and it appears that the only way to do that arbitrarily is through HTTP request signing.
  3. The presigned URLs appear to only do GET and PUT; we will also be needing POST and DELETE. In other words, we need a general-purpose "talk with server over HTTP" solution, and it appears per the documentation that HTTP request signing is the way to go.

This is why we want to arbitrarily sign HTTP requests to AWS.

karthiksaligrama commented 7 years ago

S3 has slightly different signature protocol than the rest of the aws services, take a look @ https://github.com/aws/aws-sdk-ios/blob/master/AWSCore/Authentication/AWSSignature.m#L178. which is the specific part which does the signing for S3 requests.

ghost commented 7 years ago

I was not aware of AWSSignature. Is there any way we can leverage that to accomplish what we want?

karthiksaligrama commented 7 years ago

you should be able to use that code verbatim. Although you may or may not want the streaming bit

ghost commented 7 years ago

Copying the code verbatim did not work. I had to modify it to include the x-amz-date header, and also include the host (which I just grabbed from the library I linked above). Here is what the function looks like after my modifications:

+ (NSString *)signS3RequestV4:(NSMutableURLRequest *)urlRequest
                  credentials:(AWSCredentials *)credentials
                     inRegion:(UAAWSRegion)region {
    if ( [urlRequest valueForHTTPHeaderField:@"Content-Type"] == nil) {
        [urlRequest addValue:@"binary/octet-stream" forHTTPHeaderField:@"Content-Type"];
    }

    // fix query string
    // @"?location" -> @"?location="

    //    NSString *subResource = request.subResource;
    //    if (nil != subResource
    //        && [subResource length] > 0
    //        && [subResource rangeOfString:@"="].location == NSNotFound) {
    //        [request setSubResource:[subResource stringByAppendingString:@"="]];
    //        [request.urlRequest setURL:request.url];
    //    }

    NSDate *date = [NSDate aws_clockSkewFixedDate];
    NSString *dateStamp = [date aws_stringValue:AWSDateShortDateFormat1];
    //NSString *dateTime  = [date aws_stringValue:AWSDateAmzDateFormat];

    // Set the X-Amz-Date header
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setDateFormat:@"yyyyMMdd'T'HHmmss'Z'"];
    [dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]];
    [dateFormatter setTimeZone:[NSTimeZone timeZoneWithName:@"UTC"]];

    NSString *xAmzDate = [dateFormatter stringFromDate:date];
    [urlRequest setValue:xAmzDate forHTTPHeaderField:@"X-Amz-Date"];

    // Make sure we manually set the host header
    [urlRequest setValue:urlRequest.URL.host forHTTPHeaderField:@"Host"];

    NSString *scope = [NSString stringWithFormat:@"%@/%@/%@/%@", dateStamp, [NSString UA_regionStringForRegionValue:region],
                       [urlRequest.URL UA_AWSServiceName], AWSSignatureV4Terminator];
    NSString *signingCredentials = [NSString stringWithFormat:@"%@/%@", credentials.accessKey, scope];

    // compute canonical request
    NSString *httpMethod = urlRequest.HTTPMethod;
    // URL.path returns unescaped path
    // For S3,  url-encoded URI need to be decoded before generate  CanonicalURI, otherwise, signature doesn't match occurs. (I.e. CanonicalURI for "/ios-v2-test-445901470/name%3A" will still be  "/ios-v2-test-445901470/name%3A".  "%3A" -> ":" -> "%3A")
    NSString *cfPath = (NSString*)CFBridgingRelease(CFURLCopyPath((CFURLRef)urlRequest.URL)) ;
    NSString *path = [cfPath aws_stringWithURLEncodingPath];

    if (path.length == 0) {
        path = [NSString stringWithFormat:@"/"];
    }

    NSString *query = urlRequest.URL.query;
    if (query == nil) {
        query = [NSString stringWithFormat:@""];
    }

    // Compute contentSha256
    NSString *contentSha256;
    NSInputStream *stream = [urlRequest HTTPBodyStream];
    NSUInteger contentLength = [[urlRequest allHTTPHeaderFields][@"Content-Length"] integerValue];
    if (nil != stream) {
        contentSha256 = @"STREAMING-AWS4-HMAC-SHA256-PAYLOAD";
        [urlRequest setValue:[NSString stringWithFormat:@"%lu", (unsigned long)[AWSS3ChunkedEncodingInputStream computeContentLengthForChunkedData:contentLength]]
          forHTTPHeaderField:@"Content-Length"];
        [urlRequest setValue:nil forHTTPHeaderField:@"Content-Length"]; //remove Content-Length header if it is a HTTPBodyStream
        [urlRequest setValue:@"Chunked" forHTTPHeaderField:@"Transfer-Encoding"];
        [urlRequest addValue:@"aws-chunked" forHTTPHeaderField:@"Content-Encoding"]; //add aws-chunked keyword for s3 chunk upload
        [urlRequest setValue:[NSString stringWithFormat:@"%lu", (unsigned long)contentLength] forHTTPHeaderField:@"x-amz-decoded-content-length"];
    } else {
        contentSha256 = [AWSSignatureSignerUtility hexEncode:[[NSString alloc] initWithData:[AWSSignatureSignerUtility hash:[urlRequest HTTPBody]] encoding:NSASCIIStringEncoding]];
        //using Content-Length with value of '0' cause auth issue, remove it.
        if (contentLength == 0) {
            [urlRequest setValue:nil forHTTPHeaderField:@"Content-Length"];
        } else {
            [urlRequest setValue:[NSString stringWithFormat:@"%lu", (unsigned long)[[urlRequest HTTPBody] length]] forHTTPHeaderField:@"Content-Length"];
        }
    }

    //[request.urlRequest setValue:dateTime forHTTPHeaderField:@"X-Amz-Date"];
    [urlRequest setValue:contentSha256 forHTTPHeaderField:@"x-amz-content-sha256"];

    //Set Content-MD5 header field if required by server.
    if (([ urlRequest.HTTPMethod isEqualToString:@"PUT"] && ([[[urlRequest URL] query] hasPrefix:@"tagging"] ||
                                                             [[[urlRequest URL] query] hasPrefix:@"lifecycle"] ||
                                                             [[[urlRequest URL] query] hasPrefix:@"cors"]))
        || ([urlRequest.HTTPMethod isEqualToString:@"POST"] && [[[urlRequest URL] query] hasPrefix:@"delete"])
        ) {
        if (![urlRequest valueForHTTPHeaderField:@"Content-MD5"]) {
            [urlRequest setValue:[NSString aws_base64md5FromData:urlRequest.HTTPBody] forHTTPHeaderField:@"Content-MD5"];
        }

    }

    NSMutableDictionary *headers = [[urlRequest allHTTPHeaderFields] mutableCopy];

    NSString *canonicalRequest = [AWSSignatureV4Signer getCanonicalizedRequest:httpMethod
                                                                          path:path
                                                                         query:query
                                                                       headers:headers
                                                                 contentSha256:contentSha256];
    AWSLogVerbose(@"Canonical request: [%@]", canonicalRequest);

    NSString *stringToSign = [NSString stringWithFormat:@"%@\n%@\n%@\n%@",
                              AWSSignatureV4Algorithm,
                              [urlRequest valueForHTTPHeaderField:@"X-Amz-Date"],
                              scope,
                              [AWSSignatureSignerUtility hexEncode:[AWSSignatureSignerUtility hashString:canonicalRequest]]];
    AWSLogVerbose(@"AWS4 String to Sign: [%@]", stringToSign);

    NSData *kSigning  = [AWSSignatureV4Signer getV4DerivedKey:credentials.secretKey
                                                         date:dateStamp
                                                       region:[NSString UA_regionStringForRegionValue:region]
                                                      service:[urlRequest.URL UA_AWSServiceName]];

    NSData *signature = [AWSSignatureSignerUtility sha256HMacWithData:[stringToSign dataUsingEncoding:NSUTF8StringEncoding]
                                                              withKey:kSigning];
    NSString *signatureString = [AWSSignatureSignerUtility hexEncode:[[NSString alloc] initWithData:signature
                                                                                           encoding:NSASCIIStringEncoding]];

    NSString *authorization = [NSString stringWithFormat:@"%@ Credential=%@, SignedHeaders=%@, Signature=%@",
                               AWSSignatureV4Algorithm,
                               signingCredentials,
                               [AWSSignatureV4Signer getSignedHeadersString:headers],
                               signatureString];

    if (nil != stream) {
        AWSS3ChunkedEncodingInputStream *chunkedStream = [[AWSS3ChunkedEncodingInputStream alloc] initWithInputStream:stream
                                                                                                                 date:date
                                                                                                                scope:scope
                                                                                                             kSigning:kSigning
                                                                                                      headerSignature:signatureString];
        [urlRequest setHTTPBodyStream:chunkedStream];
    }

    [urlRequest setValue:authorization forHTTPHeaderField:@"Authorization"];

    return authorization;
}

However, even after doing this, I am still getting the same problem:

2016-09-22 12:29:14.818 CognitoSpike[39060:1862304] Headers: {
    Authorization = "AWS4-HMAC-SHA256 Credential=ASIAJEI42VHAPROHOZZA/20160922/us-east-1/s3/aws4_request, SignedHeaders=content-type;host;x-amz-content-sha256;x-amz-date, Signature=dc64e560a6534bfa28e861f238d1f9f8a1ae13830d143fa3585a03ae2ec67e43";
    "Content-Type" = "binary/octet-stream";
    Host = "s3.amazonaws.com";
    "X-Amz-Date" = 20160922T172914Z;
    "x-amz-content-sha256" = e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855;
}
2016-09-22 12:29:15.032 CognitoSpike[39060:1863006] Downloaded data: '<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>InvalidAccessKeyId</Code><Message>The AWS Access Key Id you provided does not exist in our records.</Message><AWSAccessKeyId>ASIAJEI42VHAPROHOZZA</AWSAccessKeyId><RequestId>9394FED7FC3F16C1</RequestId><HostId>+VHHuPKnmt4LgQN535lCC2P/zxcouhTDP1V/fldL8fw3s7rBJW24Pg2hvwlOGBhiqGtt5wkY/MA=</HostId></Error>'

The credentials are the ones we get from -credentials we call on the instance of AWSCognitoCredentialsProvider we have and have hooked up to the User Pool.

Should we be getting credentials from somewhere else?

karthiksaligrama commented 7 years ago

no the credentials from your instance of credential provider should do the trick

ghost commented 7 years ago

Hm. Any idea why they're not, then?

ghost commented 7 years ago

Sniffing the difference between using the transfer manager and our raw signer, it looks like the transfer manager also includes an x-amz-security-token header.

behrooziAWS commented 7 years ago

You should also add the session header https://github.com/aws/aws-sdk-ios/blob/master/AWSCore/Authentication/AWSSignature.m#L160

ghost commented 7 years ago

Adding the session header looks like it worked. Let me run a few tests to make sure.

ghost commented 7 years ago

This does appear to work now. Thank you for your help.

It is worth future reference and search engines to be clear:

If you get the error message

The AWS Access Key Id you provided does not exist in our records.

Then you should make sure you are setting all of the headers correctly, especially making sure there is a session key.

I am closing this issue.