CocoaMQTT icon indicating copy to clipboard operation
CocoaMQTT copied to clipboard

Connect to MQTT with .pem certificate

Open PrLion opened this issue 1 year ago • 34 comments

Do we have any options for connect to MQTT with cert.pem, privateKey.pem and ca.pem ? Like I see In documentation we should use only .p12.

In the documentation you has method getClientCertFromP12File() That method receive .p12 and extract kSecImportItemIdentity (certificate)

But what we should do if we have private key and ca inside for authentication?

PrLion avatar Aug 17 '23 13:08 PrLion

Also interested in a solution.

matthew-ely avatar Sep 13 '23 18:09 matthew-ely

also faced this problem

MelnykovDenys avatar Sep 13 '23 20:09 MelnykovDenys

@PrLion @matthew-ely Guys, do you have any solutions?

MelnykovDenys avatar Sep 15 '23 21:09 MelnykovDenys

@MelnykovDenys No solutions with CocoaMQTT at this moment ;(

PrLion avatar Sep 19 '23 16:09 PrLion

@MelnykovDenys @PrLion Firstly, you need to use openssl command ,in order to convert pem to der. 👇🏻

openssl x509 -outform der -in certificate.pem -out certificate.der

And There is my sample code . Now I can connect mqtt broker And send message to MQTTX Client , but I can't received any message, cocoMQTT api not any response . you can try to debug with my demo code.

MQTT_Test 2.zip

wtdu avatar Sep 22 '23 03:09 wtdu

I still have not found a solution for authentication with certificate, private key and ca files, SO instead we decided to just use openssl to generate a p12 file serverside and request the p12 file bytes and password. I wrote a function mimicking the sample code p12 decryption but takes a byte array and password as arguments:

''' private func getClientCertFromByteArray(bytes: [UInt8], certPassword: String) -> CFArray? {

    // create key dictionary for reading p12 file
    let key = kSecImportExportPassphrase as String
    let options : NSDictionary = [key: certPassword]

    var items : CFArray?
    let securityError = SecPKCS12Import(NSData(bytes: bytes, length: bytes.count), options, &items)

    guard securityError == errSecSuccess else {
        if securityError == errSecAuthFailed {
            print("ERROR: SecPKCS12Import returned errSecAuthFailed. Incorrect password?")
        } else {
            print("Failed to decode bytes")
        }
        return nil
    }

    guard let theArray = items, CFArrayGetCount(theArray) > 0 else {
        return nil
    }

    let dictionary = (theArray as NSArray).object(at: 0)
    guard let identity = (dictionary as AnyObject).value(forKey: kSecImportItemIdentity as String) else {
        return nil
    }
    let certArray = [identity] as CFArray

    return certArray
}

'''

matthew-ely avatar Sep 22 '23 14:09 matthew-ely

@MelnykovDenys @PrLion Firstly, you need to use openssl command ,in order to convert pem to der. 👇🏻

openssl x509 -outform der -in certificate.pem -out certificate.der

And There is my sample code . Now I can connect mqtt broker And send message to MQTTX Client , but I can't received any message, cocoMQTT api not any response . you can try to debug with my demo code.

MQTT_Test 2.zip

I'm afraid I am not permitted to unzip any files so I cannot look it over, but my MQTT broker with AWS works properly now so I can share mindnumbing issues I ran into while implementing the socket.

  • Make sure these properties are enabled prior to connecting: 'mqttClient.enableSSL = true' and 'mqttClient.allowUntrustCACertificates = true' (if your certificates are self signed)
  • If your MQTT connection connects successfully and immediately disconnects, it is likely you are not using a unique Client ID, so use some format like 'let CLIENT_ID = "your_company" + UUID().uuidstring' to ensure a unique ID is always being used.
  • Ensure you are subscribing to all relevant topics upon successful connect and enable 'mqttClient.autoReconnect = true' in case your connection is prone to dropping.
  • Lastly, if you connect and do not receive any messages, ensure another connection is publishing messages to the same topic your device is subscribed to.

matthew-ely avatar Sep 22 '23 14:09 matthew-ely

@wtdu Your code isn't work. You are publishing it in the space. That is a reason why you don't receive any message.

@matthew-ely If you are using just 1 certificate for connection your mrthod from documentation should work, but if you are use Key, Certificate and CA for connection it won't.

For example: You can create certificate self.

let pem = """ -----BEGIN CERTIFICATE----- MIIC2jCCAkMCAg38MA0GCSqGSIb3DQEBBQUAMIGbMQswCQYDVQQGEwJKUDEOMAwG (...) +tZ9KynmrbJpTSi0+BM= -----END CERTIFICATE----- """ `// remove header, footer and newlines from pem string` let certData = Data(base64Encoded: pemWithoutHeaderFooterNewlines)! `` guard let certificate = SecCertificateCreateWithData(nil, data as CFData) else { // handle error }

but in method private func getClientCertFromByteArray(bytes: [UInt8], certPassword: String) -> CFArray? you are trying extract SecCertificate

it will not work.

PrLion avatar Sep 22 '23 16:09 PrLion

@PrLion @matthew-ely I guessed the ‘ getClientCertFromByteArray ’ method only support ' two way certificate ' with P12 file ,so I ignored it.

Otherwise,I want to connect mqtt broker by One way cartificate , I needn't to use P12 file.

as your question ‘That is a reason why you don't receive any message.’ which I didn't find reason.

wtdu avatar Sep 23 '23 02:09 wtdu

Try to provide your certificate like CFArray

var sslSettings: [String: NSObject] = [:] sslSettings[kCFStreamSSLCertificates as String] = clientCertArray

    mqtt.sslSettings = sslSettings

PrLion avatar Sep 23 '23 03:09 PrLion

@leeway1208 we should provide an example

JaylinYu avatar Sep 27 '23 13:09 JaylinYu

With 3 certificates please. Cert, key and ca.

PrLion avatar Sep 28 '23 22:09 PrLion

@leeway1208 @JaylinYu Hello, What about example? How could we wrap all three or two certificates for connection?

PrLion avatar Oct 06 '23 16:10 PrLion

@leeway1208 @JaylinYu Hello, What about example? How could we wrap all three or two certificates for connection?

I will do some example when I'm not busy these days 😭😭

leeway1208 avatar Oct 08 '23 16:10 leeway1208

How about converting *.pem files to *.p12 file?

leeway1208 avatar Oct 09 '23 16:10 leeway1208

@leeway1208 I didn't find a way how to do it in code. If you would show us an example we would be very grateful

MelnykovDenys avatar Oct 09 '23 18:10 MelnykovDenys

What do you mean "*.pem files to *.p12 file" In your code you are extract .pem from .p12

// create key dictionary for reading p12 file
let key = kSecImportExportPassphrase as String
let options : NSDictionary = [key: certPassword]

var items : CFArray?
let securityError = SecPKCS12Import(NSData(bytes: bytes, length: bytes.count), options, &items)

guard securityError == errSecSuccess else {
    if securityError == errSecAuthFailed {
        print("ERROR: SecPKCS12Import returned errSecAuthFailed. Incorrect password?")
    } else {
        print("Failed to decode bytes")
    }
    return nil
}

guard let theArray = items, CFArrayGetCount(theArray) > 0 else {
    return nil
}

let dictionary = (theArray as NSArray).object(at: 0)
guard let identity = (dictionary as AnyObject).value(forKey: kSecImportItemIdentity as String) else {
    return nil
}
let certArray = [identity] as CFArray

return certArray

}

PrLion avatar Oct 09 '23 18:10 PrLion

@PrLion We have 3 files: cert.pem, privateKey.pem (I have RSA PRIVATE KEY) and ca.pem. We don't understand how we can bind these files (maybe to array as CFArray) and set to sslSettings

MelnykovDenys avatar Oct 09 '23 18:10 MelnykovDenys

@leeway1208 @MelnykovDenys I'm in the same situation.

PrLion avatar Oct 09 '23 18:10 PrLion

Using OpenSSL, which you can download at www.openssl.org. The following instructions assume that you retain the default certificate filename of "cert_key_pem.txt."

  1. Open a command prompt and navigate to the directory that contains the cert_key_pem.txt file.

  2. Execute the following OpenSSL command to create a PKCS12 (.p12) file:

openssl pkcs12 -export -inkey cert_key_pem.txt -in cert_key_pem.txt -out cert_key.p12

Note: To convert a PKCS12 certificate to PEM, use the following command:

openssl pkcs12 -in cert_key.p12 -out cert_key.pem -nodes

  1. After you enter the command, you'll be prompted to enter an Export Password. Choose a password or phrase and note the value you enter

  2. A file called cert_key.p12 is created in this directory. This is your .p12 file.

leeway1208 avatar Oct 11 '23 13:10 leeway1208

Okay and then we should insert it in code, right? Then you method will extract cert.pem from .p12 and use it for connection But we need 3 certificates cert, key and ca.

Thanks and Regards

PrLion avatar Oct 13 '23 19:10 PrLion

@leeway1208

PrLion avatar Oct 19 '23 13:10 PrLion

he has already told you the way out. Convert your files into p12, and put that file into sslsettings. Then connect and see how does it work.while you can learn more knowledge about TLS from wiki.

he Will provide a general tls example base on x509 certificate.

JaylinYu avatar Oct 19 '23 14:10 JaylinYu

@JaylinYu we tested it.

@wtdu wrote it above:

Firstly, you need to use openssl command ,in order to convert pem to der. 👇🏻

openssl x509 -outform der -in certificate.pem -out certificate.der

And There is my sample code . Now I can connect mqtt broker And send message to MQTTX Client , but I can't received any message, cocoMQTT api not any response . you can try to debug with my demo code.

PrLion avatar Oct 19 '23 15:10 PrLion

As long as your client gets CONNACK and trigger the connect_cb, it proves MQTT connection is already working. Plz check Broker side on receiving issue, verify there is a Publish msg to client first.

JaylinYu avatar Oct 19 '23 15:10 JaylinYu

No MQTT connection doesn't work. Because I tested it on Android and it works. I asked you to help us, because we can't to figure out how to connect with three certificates.

PrLion avatar Oct 19 '23 16:10 PrLion

Hello. you can set the x509 certificate like this.

      func readX509Certificates() -> CFArray? {
        guard let certURL = Bundle.main.url(forResource: "certificate", withExtension: "cer") else {
            print("Certificate file not found")
            return nil
        }

        do {
            let certData = try Data(contentsOf: certURL)
            let certOptions: NSDictionary = [kSecImportExportPassphrase as NSString: ""]

            var rawItems: CFArray?
            let status = SecPKCS12Import(certData as CFData, certOptions, &rawItems)

            if status == errSecSuccess, let items = rawItems {
                return items
            } else {
                print("Failed to import certificate. Status code: \\(status)")
                return nil
            }
        } catch {
            print("Failed to read certificate data: \\(error)")
            return nil
        }
    }

/// this shows how to use function
   if let certificates = readX509Certificates() {
        for index in 0..<CFArrayGetCount(certificates) {
            let certificate = unsafeBitCast(CFArrayGetValueAtIndex(certificates, index), to: SecCertificate.self)
            var sslSettings: [String: NSObject] = [:]
            sslSettings[kCFStreamSSLCertificates as String] = clientCertArray

        }
    }

leeway1208 avatar Oct 19 '23 16:10 leeway1208

On my side we are receiving .pem certificates by API, and we can't convert it to .p12. How could I use cert.pem and key for connection.

Could you provide some example?

CC: @leeway1208 @JaylinYu

PrLion avatar Oct 27 '23 19:10 PrLion

@leeway1208 Any updates

PrLion avatar May 21 '24 15:05 PrLion

@PrLion Converting the .pem file from the API to .p12 requires OpenSSL Im fairly certain. A quick google search yields Swift packages that contain OpenSSL C functions for iOS that could solve the problem but I have not tested the functionality or security of these techniques.

matthew-ely avatar May 21 '24 15:05 matthew-ely