Skip to content

Digitally Signed QR Codes

Introduction

QR codes generated by the mobile services cockpit are not digitally signed by default. This may allow a vulnerability to arise, whereby an attacker could create a QR code with a malicious URL and share it with users via email or by other means. If the compromised QR code is used for onboarding, the app will connect to the malicious URL and, as a result, sensitive data breaches could occur.

In order to mitigate this vulnerability, you can use digitally-signed QR codes, where QR codes generated by the mobile services cockpit get digitally signed.

How Does It Work

The SAP BTP SDK for iOS doesn't rely on any kind of wizardry. Under the hood it makes use of well known concepts in Public key cryptography and JWS (JSON Web Signature) to accomplish this.

When this feature is enabled (either via mobile services cockpit or SAP BTP SDK Assistant for iOS), a private-public key pair is generated by the mobile services cockpit. The private key is used by mobile services cockpit to sign the QR codes. The public key is used by the application / SDK for digital signature verification.

When the SDK scans a signed QR code, the result is a JWS string. The digital signature on JWS string is verified first, which in turn ascertains the authenticity and integrity of the actual message. If signature verification is successful, the onboarding flow will proceed to the next steps in the flow. If verification is unsuccessful, authentication is identified as failed.

Below is the list of supported key-pair and algorithm.

Key Pair Type Key Length JWS Algorithm Used Hash
RSA 2048 RS256 SHA-256
RSA 3072 RS384 SHA-384
RSA 4096 RS512 SHA-512
EC 256 ES256 SHA-256
EC 384 ES384 SHA-384
EC 512 ES512 SHA-512

Prerequisites

  • If the feature is enabled using the SAP BTP SDK Assistant for iOS while generating the app, a public key is made available in the Xcode project and is added to the app target as part of the app generation process.
  • If the feature is enabled using the mobile services cockpit, then the public key should be made available to the app and SDK. In order to do so, add the Digital Signature Public Key key in AppParameters.plist with a value that points to the file name in which the public key is stored. Make sure that the public key file is added to the app target.

Enabling QR Code Digital Signature Verification

To enable signature verification, you need to make changes to the WelcomeScreenStep:

let welcomeScreenStep = WelcomeScreenStep(transformer: discoveryConfigurationTransformer, providers: [JSONConfigurationProvider()])
welcomeScreenStep.welcomeScreenCustomizationHandler = { welcomeStepUI in
            ...
            if let welcomeScreen = welcomeStepUI as? FUIWelcomeScreen {
            ...
                welcomeScreen.configurationOptions = [.barcodeScanner]
            }
        }

This enables opening the scanner in order to scan the QR code.

To enable signature verification, add the following:

welcomeScreenStep.isDigitallySignedQRCodeEnabled = true

On a successful verification the user can continue with rest of the onboarding process. verification-successful

For a failed verification appropriate reason for failure is logged and onboarding fails. verification-failed

Digital Signature Verification Using Cross Context SSO

Using Cross Context SSO, the user can open the onboarding URL in a device browser.

If QR Code Signature Settings is enabled in the mobile services cockpit, then the URL that opens the app is encoded.

The signature on the encoded URL has to be verified for the onboarding process to start.

To accomplish this, add the following in AppDelegate.swift:

func application(_: UIApplication, open url: URL, options _: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
            let searchString = "?jws="
            if incomingURL.absoluteString.range(of: searchString) != nil {
                let extractedJws = incomingURL.absoluteString.components(separatedBy: searchString).last ?? ""

                guard extractedJws.isEmpty == false else {
                    logger.info("Extracted JWS is not valid.")
                    return false
                }
                let appParameters = FileConfigurationProvider("AppParameters").provideConfiguration().configuration
                guard let publicKeyFileName = appParameters["Digital Signature Public Key"] as? String else {
                    logger.info("'Digital Signature Public Key' not found in AppParameters.plist.")
                    return false
                }
                let publicKey = readFrom(file: publicKeyFileName)
                guard !publicKey.isEmpty else {
                    logger.debug("publicKey is not valid!")
                    return false
                }
                let verifier = DigitalSignatureVerifier()
                do {
                    let jws = try JWSParser.parse(extractedJws) as! JWS
                    _ = try JWS.isSupported(algorithm: jws.algorithm.rawValue)
                    let digitalSignatureParameter = DigitalSignatureParameter(algorithm: jws.algorithm, padding: .PKCS1v1_5)
                    let verificationResult = try verifier.verify(signature: jws.signature, on: jws.payload, usingPublicKey: publicKey, parameter: digitalSignatureParameter)

                    if verificationResult {
                        logger.debug("Digital Signature Verification Succeeded!")
                        if let jsonString = try JSONSerialization.jsonObject(with: jws.jwsPayload) as? [String: String] {
                            let configString =
                            """
                                {"auth":[{"type":"\(jsonString["authType"] ?? "")", \
                                "config": \
                                {"oauth2.clients": \
                                [{"clientID":"\(jsonString["oauth.0.clientID"] ?? "")", \
                                "redirectURL":"\(jsonString["oauth.0.redirectURL"] ?? "")", \
                                "grantType":"\(jsonString["oauth.0.grantType"] ?? "")", \
                                "passcode":"\(jsonString["oauth.0.readonlypasscode"] ?? "")"}],\
                                "oauth2.tokenEndpoint":"\(jsonString["tokenEndpoint"] ?? "")", \
                             "oauth2.authorizationEndpoint":"\(jsonString["authorizationEndpoint"] ?? "")"}}], \
                                "protocol":"\(jsonString["protocol"] ?? "")", \
                                "host":"\(jsonString["host"] ?? "")", \
                                "port":"\(jsonString["port"] ?? "")"}
                                """
                            let mainConfig: [String: [String: Any]] = [ConfigurationProviderNames.json: [ConfigurationProviderInputKeys.JSONstring.rawValue: configString]]
                            handoffData = mainConfig
                            // regular onboarding code
                            return true
                        }
                    } else {
                        logger.debug("Digital Signature Verification Failed!")
                    }
                } catch {
                    return false
                }
            } else {
                return false
            }
    }

This code makes use of the newly available SAPFoundation.DigitalSignatureVerifier public class introduced in SAP BTP SDK for iOS v8.0.

Note

If you use SAP BTP SDK Assistant for iOS, the required code described above is automatically created as part of the app generation process.


Last update: June 24, 2022