'How to access Huawei API gateway by authorization in swift?

As per Huawei API Gateway App Authentication documentation I'm trying to access an API through app authentication, standardize the request content, and then sign the request. As the client I must follow the same request specifications as API Gateway so that each HTTP request can obtain the same signing result from the frontend and backend to complete identity authentication.

I complete it using following class. However the documentation is really poor to understand. It returns error Incorrect app authentication information: Authorization format is incorrect APIG.0303.

I couldn't complete the process and I'm really thankful if you can give any sample to do this.

import Foundation
import CommonCrypto

public class SimpleHMACAuth {
    
    /// APP key to authenticate with
    public var appKey: String?
    
    /// Secret key to authenticate with
    public var secret: String?
    
    /// Algorithm to generate HMAC hash with
    public var algorithm: HMACAlgorithm = .sha256
    
    public enum HMACAlgorithm: String {
        case sha256 = "sha256"
        case sha512 = "sha512"
    }
    
    enum RequestSigningError: Error {
        case missingAPPKey
        case missingSecret
        case invalidAlgorithm
        case invalidURL
    }
    
    fileprivate let dateFormatter: DateFormatter
    
    /// Instantiate with an APP key and secret
    /// - Parameters:
    ///   - appKey: APP key
    ///   - secret: Secret key
    public convenience init(appKey: String, secret: String) {
        self.init()
        
        self.appKey = appKey
        self.secret = secret
    }
    
    /// Instantiate
    public init() {
        self.dateFormatter = DateFormatter()
        self.dateFormatter.dateFormat = "dd MMM yyyy HH:mm:ss zzz"
    }
    
    /// Returns a signed version of an input request
    /// - Parameter request: Request to sign
    /// - Throws: If the request, APP key, secret, or algorithm are invalid
    /// - Returns: Signed version of the request
    public func sign(_ request: URLRequest) throws -> URLRequest {
        
        guard let signedRequest = (request as NSURLRequest).mutableCopy() as? NSMutableURLRequest else {
            throw RequestSigningError.invalidURL
        }
        
        guard let appKey = self.appKey else {
            throw RequestSigningError.missingAPPKey
        }
        
        guard let secret = self.secret else {
            throw RequestSigningError.missingSecret
        }
        
        // Add the "Authorization" header
        
        signedRequest.addValue("APP key \(appKey)", forHTTPHeaderField: "Authorization")
        
        // Confirm the "Date" header exists, and add it if not
        
        if signedRequest.value(forHTTPHeaderField: "Date") == nil {
            
            signedRequest.addValue(self.dateFormatter.string(from: Date()), forHTTPHeaderField: "X-Sdk-Date")
        }
        
        // If this request has a body but does not yet have the "Content-Length" header, calculate and append it
        
        if let data = signedRequest.httpBody, signedRequest.value(forHTTPHeaderField: "Content-Length") == nil {
            
            signedRequest.addValue("\(data.count)", forHTTPHeaderField: "Content-Length")
        }
        
        // Canonicalize the request
        
        let canonicalized = try self.canonicalize(signedRequest as URLRequest)
        
        // Generate a signature from the canonicalized representation of the request
        
        let signature = try self.signature(canonicalized: canonicalized, secret: secret, algorithm: algorithm);
        
        // Append the "Signature" header
        
        signedRequest.addValue("SDK-HMAC-SHA256", forHTTPHeaderField: "Authorization")
        signedRequest.addValue(appKey, forHTTPHeaderField: "Access")
        signedRequest.addValue("SDK-HMAC-SHA256 \(signature)", forHTTPHeaderField: "Signature")
        
        return signedRequest as URLRequest
    }
    
    /// Generate a string for a request
    /// - Parameter request: Request to sign
    /// - Throws: Throws if request is invalid
    /// - Returns: Signed request
    internal func canonicalize(_ request: URLRequest) throws -> String {
        
        guard let url = request.url else {
            throw RequestSigningError.invalidURL
        }
        
        let method = (request.httpMethod ?? "GET").uppercased()
        var path = url.path
        let queryString = url.query != nil ? "?\(url.query!)" : ""
        let allHeaders = request.allHTTPHeaderFields ?? [String : String]()
        let data = request.httpBody ?? Data()
        
        if let component = URLComponents(string: url.absoluteString) {
            path = component.path
        }
        
        // Only sign these headers
        
        let allowedHeaders = [
            "authorization",
            "date",
            "content-length",
            "content-type"
        ]
        
        // Create a new list of headers, with the keys all lower case
        
        var headers = [String: String]();
        
        for (key, value) in allHeaders {
            
            let lowerCaseKey = key.lowercased()
            
            if allowedHeaders.contains(lowerCaseKey) == false {
                continue
            }
            
            if lowerCaseKey == "content-length" && value == "0" {
                continue
            }
            
            headers[lowerCaseKey] = value;
        }
        
        // Sort the header keys alphabetically
        
        let headerKeys = headers.keys.sorted()
        
        // Create a string of all headers, arranged alphabetically, seperated by newlines
        
        var headerString = ""
        
        for (index, key) in headerKeys.enumerated() {
            
            guard let value = headers[key] else {
                continue
            }
            
            headerString += "\(key):\(value)"
            
            if index != headerKeys.count - 1 {
                headerString += "\n"
            }
        }
        
        // Hash the data payload
        
        var digest = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
        data.withUnsafeBytes { bytes in
            _ = CC_SHA256(bytes.baseAddress, CC_LONG(data.count), &digest)
        }
        
        let dataHash = Data(digest).map { String(format: "%02hhx", $0) }.joined()
        
        // Combine all components of this request into a string
        
        let components: [String] = [method, path, queryString, headerString, dataHash]
        
        return components.joined(separator: "\n");
    }
    
    /// Generate a HMAC hash for a canonicalized request
    /// - Parameters:
    ///   - canonicalized: canonicalized version of a request
    ///   - secret: Secret key
    ///   - algorithm: Algorithm to use to generate the hmac
    /// - Throws: If algorithm is not supported
    /// - Returns: Signature for the request
    internal func signature(canonicalized: String, secret: String, algorithm: HMACAlgorithm) throws -> String {
        
        let supportedAlgorithms = ["sha1":   (kCCHmacAlgSHA1,   CC_SHA1_DIGEST_LENGTH),
                                   "sha256": (kCCHmacAlgSHA256, CC_SHA256_DIGEST_LENGTH),
                                   "sha512": (kCCHmacAlgSHA512, CC_SHA512_DIGEST_LENGTH)]
        
        guard let (algorithmKey, digestLength) = supportedAlgorithms[algorithm.rawValue] else {
            throw RequestSigningError.invalidAlgorithm
        }
        
        var digest = [UInt8](repeating: 0, count: Int(digestLength))
        
        CCHmac(CCHmacAlgorithm(algorithmKey), secret, secret.count, canonicalized, canonicalized.count, &digest)
        
        let data = Data(digest)
        
        return data.map { String(format: "%02hhx", $0) }.joined()
    }
}


Solution 1:[1]

Error code APIG.303 is for: Incorrect app authentication information, usually Incorrect app authentication information. In your case, it could be Authorization format is incorrect

Please check whether the request method, path, query strings, and request body are consistent with those used for signing; check whether the date and time on the client are correct; and check whether the signing code is correct by referring to Calling APIs Through App Authentication.

Here is the detailed API Gateway user guide for your reference: https://support.huaweicloud.com/intl/en-us/ae-ad-1-usermanual-apig/ae-APIG-usermanual.pdf

Below are two examples for Signature verification, see if they can give you some ideas on why your error occurs:

private static finà Pattern authorizationPattern = Pattern.compile("SDK-HMAC-SHA256\\s+Access=([^,]+),\
\s?SignedHeaders=([^,]+),\\s?Signature=(\\w+)");
...
String authorization = request.getHeader("Authorization");
if (authorization == null || authorization.length() == 0) {
 response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authorization not found.");
 return;
}
Matcher m = authorizationPattern.matcher(authorization);
if (?m?fin()) {
 response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authorization format incorrect.");
 return;
}
String signingKey = m.group(1);
String signingSecret = secrets.get(signingKey);
if (signingSecret == null) {
 response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Signing key not found.");
 return;
}
String[] signedHeaders = m.group(2).split(";")
;

another one:

if "authorization" not in request.headers:
 return 'Authorization not found.', 401
authorization = request.headers['authorization']
m = authorizationPattern.match(authorization)
if m is None:
 return 'Authorization format incorrect.', 401
signingKey = m.group(1)
signedHeaders = m.group(2).split(";")if "authorization" not in request.headers:
 return 'Authorization not found.', 401
authorization = request.headers['authorization']
m = authorizationPattern.match(authorization)
if m is None:
 return 'Authorization format incorrect.', 401
signingKey = m.group(1)
signedHeaders = m.group(2).split(";")

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Zinna