Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IoTDataManager Connection with x509 and Private Key provided at run-time #5300

Open
michael-aiphone opened this issue Apr 23, 2024 · 6 comments
Labels
feature-request Request a new feature iot Issues related to the IoT SDK pending-triage Issue is pending triage question General question

Comments

@michael-aiphone
Copy link

State your question
I am new to the AWS ecosystem, this framework and IoT communication in general so please forgive me if I confuse some terminology here.

After following the IoT tutorials, I was able to establish communication with a test AWS IoT Thing using a bundled p12 cert via AWSIoTManager.importIdentity(...) and AWSIoTDataManager.connect(withClientId: cleanSession: certificateId).

However, due to some quirky legacy APIs that my client is working with I will be getting the certificate(x509 format) information and private key at runtime from the server. Is there some convenient way I can configure my AWSIoTManager/AWSIoTDataManager to handle this need without having to resort to something like finding a good copy of openSSL and converting the format on the fly or...?

Which AWS Services are you utilizing?
AWS IoT
Provide code snippets (if applicable)

Environment(please complete the following information):

  • SDK Version: [e.g. 2.6.29]
    AWSiOSSDKV2 2.35.0
  • Dependency Manager: [e.g. Cocoapods, Carthage]
    SPM (/aws-sdk-ios-spm)
  • Swift Version : [e.g. 4.0]
    5.0
    Device Information (please complete the following information):
  • Device: [e.g. iPhone6, Simulator]
    Simulator -> iPhone 15
  • iOS Version: [e.g. iOS 11.4]
@lawmicha lawmicha added question General question iot Issues related to the IoT SDK pending-triage Issue is pending triage labels Apr 24, 2024
@lawmicha
Copy link
Member

Hi @michael-aiphone, thanks for opening this question. I'm not too familiar with the IoT APIs myself but if you could provide us more details as to what you'd expect it to look like when using the AWSIoTManager/AWSIoTDataManager, such as what inputs you are looking to provide it, what it should do, code examples, we may be able to provide more guidance on the topic.

@michael-aiphone
Copy link
Author

michael-aiphone commented Apr 24, 2024

Thank you for getting back to me on this so swiftly 😁.

This is the crux of what I have currently:

App Delegate

// fake info so that .defaultServiceConfiguration is not nil when accessed later
let credentials = AWSStaticCredentialsProvider(accessKey: "fake1", secretKey: "fake2")
let configuration = AWSServiceConfiguration(region: .USEast2, credentialsProvider: credentials)
AWSServiceManager.default().defaultServiceConfiguration = configuration

View Controller

// configure IoTDataManager
let iotEndpoint = AWSEndpoint(urlString: "https://abcdef12345-ats.iot.us-east-2.amazonaws.com")!
let credentialsProvider = AWSServiceManager.default().defaultServiceConfiguration.credentialsProvider
let iotDataConfiguration = AWSServiceConfiguration(region: .USEast2, endpoint: iotEndpoint, credentialsProvider: credentialsProvider)!

AWSIoTDataManager.register(with: iotDataConfiguration, forKey: "ios-sdk")
self.iotDataManager = AWSIoTDataManager(forKey: "ios-sdk")
// connect with bundled cert
let paths = Bundle.main.paths(forResourcesOfType: "p12", inDirectory: nil)
let uuid = UUID().uuidString
guard let certId = paths.first else { fatalError("missing p12 cert in bundle") }
let data = try! Data(contentsOf: URL(filePath: certId))

DispatchQueue.global(qos: .background).async {
      if AWSIoTManager.importIdentity(fromPKCS12Data: data, passPhrase: "abcdef", certificateId: certId) {
             self.iotDataManager.connect(withClientId: uuid, cleanSession: true, certificateId: certId, statusCallback: self.mqttStatusEventCallback(_:))
       }
}

A nice to have for me would be some function on the AWSIoTManager that imports an identity via a x509 cert and private key. This would still allow me to use the same .connect method on the iotDataManager. Something like:

AWSIoTManager.importIdentity(fromX509Cert: String/Data, privateKey: String, certId: String)...

But maybe there is already a way to do this that I cannot seem to find in the documentation, I'm just not sure. 😅

@lawmicha lawmicha added the feature-request Request a new feature label Apr 29, 2024
@lawmicha
Copy link
Member

Hi @michael-aiphone, thanks for the additional details. I found this sample https://github.com/awslabs/aws-sdk-ios-samples/blob/main/IoT-Sample/Swift/README.md seems to reference some pre-build time steps to add a cert

Add the IoT certificate (IoT identity) to the Xcode project

Follow the instructions below to create the certificate
Place the PKCS #12 archive (.p12) in the same directory as Info.plist
Add it to the same group in Xcode as Info.plist
Select the build targets which will use this IoT identity

I'm not too sure all the details at the moment, but this does seem to imply that AWSIoTManager may be reading the cert from the main Bundle.

Are you currently implementing or have implemented a .importIdentity(fromPKCS12Data:passPhrase:certificateId:) method in your app? We can evaluate consolidating your logic with the existing (if we confirm AWSIoTManager does infact read from the Bundle), and moving that logic over to the AWSIoTManager class as a new API that explicitly takes Data type

@michael-aiphone
Copy link
Author

michael-aiphone commented Apr 29, 2024

To clarify, the code above is the solution where the identity is included in bundle. The steps I did are outlined as follows.

What I can currently do and confirmed that it works: (and what the tutorials show)

  1. Create a test environment for AWS IoT
  2. Create an IoT thing
  3. Create and Attach a certificate to the IoT thing.
  4. Create and Attach a policy to the certificate.
  5. Download the certificate (comes in X509 format)
  6. In Terminal use open SSL to convert that X509 to p12.
  7. Add the created p12 to app bundle.
  8. Use the provided .importIdentity(fromPKCS12Data: passPhrase: certificateId:) to get the identity into KeyChain with a recognizable ID.
  9. Use the provided .connect(withClientId: cleanSession: certificateId: statusCallback:) to pass authentication.

The issue I am trying to solve:

  1. The real AWS IoT environment I need to connect to is already established.
  2. An IoT thing in that environment is created when the mobile app reaches the end of a sign up flow.
  3. A backend server will send the certificate and private key information (as Strings) to the mobile app after sign up (while the app is still running)
  4. Therefore I cannot use the .importIdentity(fromPKCS12Data: passPhrase: certificateId:) function because those credentials are not p12, nor are they included in the bundle at compile/build time. Additionally, they are the default format that AWS provides -- X509 (PEM RSA)

Summary

I am wondering if there is a way that I can manually create an identity from those X509 credentials and add it to KeyChain and then call the .connect() method (since the connect method requires cert be in KeyChain). If this is possible, what would I use for the certId? --(in the provided p12 import the cert id was relative path) -- Or would it be possible to add a new method to the library that will create and add an identity to the keychain from that format?

Another alternative would be a different .connect() method that allows me to provide those credentials at runtime.

For further context, my Android colleague is using the SDK for Java and says they include a method for Android to provide the credentials like the following. In this snippet we can see they are using a builder pattern so admittedly it a bit different but it does allow for providing the credentials from X509 string data. In essence, I am looking to replicate this functionality on the iOS side in whatever way is possible/best with the stipulation that I cannot include the certificate information in the app bundle.

Android doing what I want my iOS to do

override fun connect(clientEndpoint: String, certificateData: String, keyData: String): Result<Nothing?> {
    val builder = AwsIotMqtt5ClientBuilder.newDirectMqttBuilderWithMtlsFromMemory(clientEndpoint, certificateData, keyData)
        .withLifeCycleEvents(AWSLifecycleEvents())
        .withPublishEvents(AWSPublishEvents())

    try { client = builder.build() }
    catch (e: Exception) {
        Log.d("AWSClientManager","Failed to build client, likely due to authentication issues.")
        return Result.failure(Exception("Failed to build client, likely due to authentication issues."))
    }
    client?.start()

    return Result.success(null)
}

Thanks for reading this short novel :)

@michael-aiphone
Copy link
Author

@lawmicha
I just wanted to follow up on this and see if you might be able to put me in contact with someone at AWS on the IoT Team. Would that be possible? Thank you for your time.

@5d
Copy link
Member

5d commented May 30, 2024

Hi @michael-aiphone ,

I am not familiar with AWS IoT, but upon reviewing the delaration of AWSIoTManager, I noticed there is a method called createKeysAndCertificateFromCsr that dynamically creates certificates. I also found documentation on this method. Have you tried this solution?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature-request Request a new feature iot Issues related to the IoT SDK pending-triage Issue is pending triage question General question
Projects
None yet
Development

No branches or pull requests

3 participants