'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 |
