AliyunOSSSDK/Signer/OSSV4Signer.m (293 lines of code) (raw):
//
// OSSV4Signer.m
// AliyunOSSSDK
//
// Created by ws on 2023/12/26.
// Copyright © 2023 aliyun. All rights reserved.
//
#import "OSSV4Signer.h"
#import "NSDate+OSS.h"
#import "OSSDefine.h"
#import "OSSAllRequestNeededMessage.h"
#import "NSMutableDictionary+OSS.h"
#import "OSSSignerParams.h"
#import "OSSUtil.h"
#import "NSSet+OSS.h"
#import "NSData+OSS.h"
#import "OSSServiceSignature.h"
#import "OSSLog.h"
#define ISO8601DateTimeFormat @"yyyyMMdd'T'HHmmss'Z'"
#define ISO8601DateFormat @"yyyyMMdd"
#define NewLine @"\n"
#define SeparatorBackslash @"/"
#define Terminator @"aliyun_v4_request"
#define OSS4HMacSHA256 @"OSS4-HMAC-SHA256"
#define SecretKeyPrefix @"aliyun_v4"
@interface OSSV4Signer()
@property (nonatomic, copy) NSDate *requestDateTime;
@property (nonatomic, copy) NSArray<NSString *> *additionalSignedHeaders;
@end
@implementation OSSV4Signer
- (NSString *)getDateTime {
return [self.requestDateTime oss_asStringValueWithDateFormat:ISO8601DateTimeFormat];
}
- (NSString *)getDate {
return [self.requestDateTime oss_asStringValueWithDateFormat:ISO8601DateFormat];
}
- (BOOL)hasDefaultSignedHeaders:(NSString *)header {
if ([@[OSSHttpHeaderContentType.lowercaseString, OSSHttpHeaderContentMD5.lowercaseString] containsObject:header]) {
return YES;
}
return [header hasPrefix:OSSPrefix];
}
- (BOOL)hasSignedHeaders:(NSString *)header {
if ([self hasDefaultSignedHeaders:header]) {
return YES;
}
return [self.additionalSignedHeaders containsObject:header.lowercaseString];
}
- (BOOL)hasAdditionalSignedHeaders {
return self.additionalSignedHeaders != nil && self.additionalSignedHeaders.count != 0;
}
- (void)resolveAdditionalSignedHeaders:(OSSAllRequestNeededMessage *)request
headerNames:(NSSet<NSString *> *)headerNames {
NSMutableArray<NSString *> *signedHeaders = [NSMutableArray new];
if (headerNames) {
for (NSString *additionalHeader in headerNames) {
NSString *additionalHeaderKey = additionalHeader.lowercaseString;
[request.headerParams enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
if ([key isKindOfClass:[NSString class]] &&
[[key lowercaseString] isEqualToString:additionalHeaderKey] &&
![self hasDefaultSignedHeaders:additionalHeaderKey]) {
[signedHeaders addObject:additionalHeaderKey];
}
}];
}
}
self.additionalSignedHeaders = [signedHeaders sortedArrayUsingSelector:@selector(compare:)];
}
- (void)addSignedHeaderIfNeeded:(OSSAllRequestNeededMessage *)request {
if ([self.additionalSignedHeaders containsObject:OSSHttpHeaderHost.lowercaseString] &&
[request.headerParams.allKeys containsObject:OSSHttpHeaderHost.lowercaseString]) {
[request.headerParams oss_setObject:[[[NSURL alloc] initWithString:request.endpoint] host] forKey:OSSHttpHeaderHost];
}
}
- (void)addOSSContentSha256Header:(OSSAllRequestNeededMessage *)request {
request.headerParams[OSSHttpHeaderContentSha256] = @"UNSIGNED-PAYLOAD";
}
- (void)addDateHeaderIfNeeded:(OSSAllRequestNeededMessage *)request {
[self initRequestDateTime];
NSString *date = [self getDateTime];
request.date = date;
[request.headerParams oss_setObject:date forKey:OSSHttpHeaderDateEx];
[request.headerParams oss_setObject:date forKey:OSSHttpHeaderDate];
}
- (void)initRequestDateTime {
self.requestDateTime = [NSDate oss_clockSkewFixedDate];
}
- (NSString *)buildCanonicalRequest:(OSSAllRequestNeededMessage *)request {
NSString *method = request.httpMethod;
NSString *resourcePath = self.signerParams.resourcePath;
NSMutableString *canonicalString = [NSMutableString new];
//http method + "\n"
[canonicalString appendString:method];
[canonicalString appendString:NewLine];
//Canonical URI + "\n"
[canonicalString appendString:[OSSUtil encodeResourcePath:resourcePath]];
[canonicalString appendString:NewLine];
//Canonical Query String + "\n" +
NSMutableArray * params = [NSMutableArray new];
NSMutableDictionary *encodedParams = @{}.mutableCopy;
for (NSString *key in request.params.allKeys) {
NSString *encodedValue = [OSSUtil encodeQuery:[request.params[key] oss_trim]];
NSString *encodedKey = [OSSUtil encodeQuery:key];
[encodedParams oss_setObject:encodedValue forKey:encodedKey];
}
NSArray *allParamsKey = [encodedParams.allKeys sortedArrayUsingSelector:@selector(compare:)];
for (NSString *key in allParamsKey) {
NSString *value = encodedParams[key];
if ([value oss_isNotEmpty]) {
[params addObject:[NSString stringWithFormat:@"%@=%@", key, value]];
} else {
[params addObject:[NSString stringWithFormat:@"%@", key]];
}
}
[canonicalString appendString:[params componentsJoinedByString:@"&"]];
[canonicalString appendString:NewLine];
//Canonical Headers + "\n" +
NSMutableArray * headers = [NSMutableArray new];
NSMutableDictionary *headerParams = request.headerParams.mutableCopy;
if (request.contentType) {
headerParams[OSSHttpHeaderContentType.lowercaseString] = request.contentType;
}
if (request.contentMd5) {
headerParams[OSSHttpHeaderContentMD5.lowercaseString] = request.contentMd5;
}
NSMutableDictionary *lowercaseHeaders = @{}.mutableCopy;
for (NSString *key in headerParams.allKeys) {
[lowercaseHeaders oss_setObject:headerParams[key] forKey:key.lowercaseString];
}
NSArray *allHeaderKey = [lowercaseHeaders.allKeys sortedArrayUsingSelector:@selector(compare:)];
for (NSString *key in allHeaderKey) {
NSString *keyStr = [key oss_trim];
NSString *valueStr = [lowercaseHeaders[key] oss_trim];
if ([self hasSignedHeaders:keyStr] && [valueStr oss_isNotEmpty]) {
[headers addObject:[NSString stringWithFormat:@"%@:%@%@", keyStr, valueStr, NewLine]];
}
}
[canonicalString appendString:[headers componentsJoinedByString:@""]];
[canonicalString appendString:NewLine];
//Additional Headers + "\n" +
if (self.additionalSignedHeaders) {
NSString *canonicalPartStr = [self.additionalSignedHeaders componentsJoinedByString:@";"];
[canonicalString appendString:canonicalPartStr];
[canonicalString appendString:NewLine];
}
//Hashed PayLoad
NSString *hashedPayLoad = request.headerParams[OSSHttpHeaderContentSha256];
if (![hashedPayLoad oss_isNotEmpty]) {
hashedPayLoad = @"UNSIGNED-PAYLOAD";
}
[canonicalString appendString:hashedPayLoad];
return canonicalString;
}
- (NSString *)getSignRegion {
if ([self.signerParams.cloudBoxId oss_isNotEmpty]) {
return self.signerParams.cloudBoxId;
}
return self.signerParams.region;
}
- (NSString *)getSignProduct {
if ([self.signerParams.cloudBoxId oss_isNotEmpty]) {
return OSSProductCloudBox;
}
return OSSProductDefault;
}
- (NSString *)buildScope {
NSString *build = [[self getDate] stringByAppendingString:SeparatorBackslash];
build = [[build stringByAppendingString:[self getSignRegion]] stringByAppendingString:SeparatorBackslash];
build = [[build stringByAppendingString:[self getSignProduct]] stringByAppendingString:SeparatorBackslash];
build = [build stringByAppendingString:Terminator];
return build;
}
- (NSString *)buildStringToSign:(NSString *)canonicalString {
NSString *build = [OSS4HMacSHA256 stringByAppendingString:NewLine];
build = [[build stringByAppendingString:[self getDateTime]] stringByAppendingString:NewLine];
build = [[build stringByAppendingString:[self buildScope]] stringByAppendingString:NewLine];
build = [build stringByAppendingString:[[[canonicalString dataUsingEncoding:NSUTF8StringEncoding] oss_calculateSha256] oss_hexString]];
return build;
}
- (NSData *)buildSigningKey:(OSSFederationToken *)federationToken {
id<OSSServiceSignature> signature = [HmacSHA256Signature new];
NSData *signingSecret = [[SecretKeyPrefix stringByAppendingString:federationToken.tSecretKey] dataUsingEncoding:NSUTF8StringEncoding];
NSData *signingDate = [signature computeHash:signingSecret data:[[self getDate] dataUsingEncoding:NSUTF8StringEncoding]];
NSData *signingRegion = [signature computeHash:signingDate data:[[self getSignRegion] dataUsingEncoding:NSUTF8StringEncoding]];
NSData *signingService = [signature computeHash:signingRegion data:[[self getSignProduct] dataUsingEncoding:NSUTF8StringEncoding]];
return [signature computeHash:signingService data:[Terminator dataUsingEncoding:NSUTF8StringEncoding]];
}
- (NSString *)buildSignature:(NSData *)signingKey
stringToSign:(NSString *)stringToSign {
NSData *result = [[HmacSHA256Signature new] computeHash:signingKey
data:[stringToSign dataUsingEncoding:NSUTF8StringEncoding]];
return [result oss_hexString];
}
- (NSString *)buildAuthorization:(NSString *)signature
federationToken:(OSSFederationToken *)federationToken {
NSString *credential = [@"Credential=" stringByAppendingFormat:@"%@%@%@", federationToken.tAccessKey, SeparatorBackslash, [self buildScope]];
NSString *signedHeaders = ![self hasAdditionalSignedHeaders] ? @"" : [@",AdditionalHeaders=" stringByAppendingString:[self.additionalSignedHeaders componentsJoinedByString:@";"]];
NSString *sign = [@",Signature=" stringByAppendingString:signature];
return [@"OSS4-HMAC-SHA256 " stringByAppendingFormat:@"%@%@%@", credential, signedHeaders, sign];
}
- (void)addAuthorizationHeader:(OSSAllRequestNeededMessage *)request
federationToken:(OSSFederationToken *)federationToken {
NSString *stringToSign = [self buildStringToSignWithRequest:request];
NSData *signingKey = [self buildSigningKey:federationToken];
NSString *signature = [self buildSignature:signingKey stringToSign:stringToSign];
NSString *authorization = [self buildAuthorization:signature
federationToken:federationToken];
[request.headerParams oss_setObject:authorization forKey:OSSHttpHeaderAuthorization];
}
- (NSString *)buildStringToSignWithRequest:(OSSAllRequestNeededMessage *)request {
NSString *canonicalRequest = [self buildCanonicalRequest:request];
OSSLogVerbose(@"canonicalRequest: %@", canonicalRequest);
NSString *stringToSign = [self buildStringToSign:canonicalRequest];
return stringToSign;
}
- (OSSTask *)sign:(OSSAllRequestNeededMessage *)requestMessage {
id<OSSCredentialProvider> credentialProvider = self.signerParams.credentialProvider;
if ([credentialProvider isKindOfClass:[OSSCustomSignerCredentialProvider class]]) {
return [OSSTask taskWithError:[NSError errorWithDomain:OSSClientErrorDomain
code:OSSClientErrorCodeSignFailed
userInfo:@{OSSErrorMessageTOKEN: @"V4 signature does not support OSSCustomSignerCredentialProvider"}]];
}
OSSFederationToken *federationToken;
NSError * error = nil;
if ([credentialProvider isKindOfClass:[OSSFederationCredentialProvider class]]) {
federationToken = [(OSSFederationCredentialProvider *)credentialProvider getToken:&error];
if (error) {
return [OSSTask taskWithError:error];
}
} else if ([credentialProvider isKindOfClass:[OSSStsTokenCredentialProvider class]]) {
federationToken = [(OSSStsTokenCredentialProvider *)credentialProvider getToken];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
} else if ([credentialProvider isKindOfClass:[OSSPlainTextAKSKPairCredentialProvider class]]) {
federationToken = [[OSSFederationToken alloc] init];
federationToken.tAccessKey = ((OSSPlainTextAKSKPairCredentialProvider *)credentialProvider).accessKey;
federationToken.tSecretKey = ((OSSPlainTextAKSKPairCredentialProvider *)credentialProvider).secretKey;
}
#pragma clang diagnostic pop
[self addDateHeaderIfNeeded:requestMessage];
if (federationToken == nil) {
return [OSSTask taskWithError:[NSError errorWithDomain:OSSClientErrorDomain
code:OSSClientErrorCodeSignFailed
userInfo:@{OSSErrorMessageTOKEN: @"Can't get a federation token"}]];
}
[self resolveAdditionalSignedHeaders:requestMessage
headerNames:requestMessage.additionalHeaderNames];
[self addSignedHeaderIfNeeded:requestMessage];
[self addSecurityTokenHeaderIfNeeded:requestMessage
federationToken:federationToken];
[self addOSSContentSha256Header:requestMessage];
[self addAuthorizationHeader:requestMessage
federationToken:federationToken];
return [OSSTask taskWithResult:nil];
}
- (OSSTask *)presign:(OSSAllRequestNeededMessage *)requestMessage {
id<OSSCredentialProvider> credentialProvider = self.signerParams.credentialProvider;
if ([credentialProvider isKindOfClass:[OSSCustomSignerCredentialProvider class]]) {
return [OSSTask taskWithError:[NSError errorWithDomain:OSSClientErrorDomain
code:OSSClientErrorCodeSignFailed
userInfo:@{OSSErrorMessageTOKEN: @"V4 signature does not support OSSCustomSignerCredentialProvider"}]];
}
OSSFederationToken *federationToken;
NSError * error = nil;
if ([credentialProvider isKindOfClass:[OSSFederationCredentialProvider class]]) {
federationToken = [(OSSFederationCredentialProvider *)credentialProvider getToken:&error];
if (error) {
return [OSSTask taskWithError:error];
}
} else if ([credentialProvider isKindOfClass:[OSSStsTokenCredentialProvider class]]) {
federationToken = [(OSSStsTokenCredentialProvider *)credentialProvider getToken];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
} else if ([credentialProvider isKindOfClass:[OSSPlainTextAKSKPairCredentialProvider class]]) {
federationToken = [[OSSFederationToken alloc] init];
federationToken.tAccessKey = ((OSSPlainTextAKSKPairCredentialProvider *)credentialProvider).accessKey;
federationToken.tSecretKey = ((OSSPlainTextAKSKPairCredentialProvider *)credentialProvider).secretKey;
}
#pragma clang diagnostic pop
NSMutableDictionary *params = requestMessage.params.mutableCopy;
// date
[self initRequestDateTime];
NSString *expires = [NSString stringWithFormat:@"%@", @(self.signerParams.expiration)];
params[@"x-oss-date"] = [self getDateTime];
params[@"x-oss-expires"] = expires;
// signed header
[self resolveAdditionalSignedHeaders:requestMessage
headerNames:self.signerParams.additionalHeaderNames];
[self addSignedHeaderIfNeeded:requestMessage];
if ([self hasAdditionalSignedHeaders]) {
params[@"x-oss-additional-headers"] = [self.additionalSignedHeaders componentsJoinedByString:@";"];
}
params[@"x-oss-signature-version"] = @"OSS4-HMAC-SHA256";
NSString *signature;
if ([federationToken useSecurityToken]) {
params[OSSHttpHeaderSecurityToken] = federationToken.tToken;
}
NSString *credential = [NSString stringWithFormat:@"%@%@%@", federationToken.tAccessKey, SeparatorBackslash, [self buildScope]];
params[@"x-oss-credential"] = credential;
requestMessage.params = params;
NSString *stringToSign = [self buildStringToSignWithRequest:requestMessage];
NSData *signingKey = [self buildSigningKey:federationToken];
signature = [self buildSignature:signingKey stringToSign:stringToSign];
params[@"x-oss-signature"] = signature;
requestMessage.params = params;
return [OSSTask taskWithResult:nil];
}
@end