emqx / CocoaMQTT

MQTT 5.0 client library for iOS and macOS written in Swift
https://www.emqx.com/en
Other
1.6k stars 419 forks source link

Connect to MQTT with .pem certificate #546

Open PrLion opened 1 year ago

PrLion commented 1 year ago

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?

matthew-ely commented 1 year ago

Also interested in a solution.

MelnykovDenys commented 1 year ago

also faced this problem

MelnykovDenys commented 1 year ago

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

PrLion commented 1 year ago

@MelnykovDenys No solutions with CocoaMQTT at this moment ;(

wtdu commented 1 year ago

@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

matthew-ely commented 1 year ago

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 commented 1 year ago

@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.

PrLion commented 1 year ago

@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.

wtdu commented 1 year ago

@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.

PrLion commented 1 year ago

Try to provide your certificate like CFArray

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

    mqtt.sslSettings = sslSettings
JaylinYu commented 1 year ago

@leeway1208 we should provide an example

PrLion commented 1 year ago

With 3 certificates please. Cert, key and ca.

PrLion commented 12 months ago

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

leeway1208 commented 12 months ago

@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 commented 12 months ago

How about converting .pem files to .p12 file?

MelnykovDenys commented 12 months ago

@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

PrLion commented 12 months ago

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

}

MelnykovDenys commented 12 months ago

@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

PrLion commented 12 months ago

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

leeway1208 commented 11 months ago

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.

PrLion commented 11 months ago

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 commented 11 months ago

@leeway1208

JaylinYu commented 11 months ago

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.

PrLion commented 11 months ago

@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.
JaylinYu commented 11 months ago

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.

PrLion commented 11 months ago

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.

leeway1208 commented 11 months ago

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

        }
    }
PrLion commented 11 months ago

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 commented 4 months ago

@leeway1208 Any updates

matthew-ely commented 4 months ago

@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.

PrLion commented 4 months ago

@matthew-ely could you share that library please ?

matthew-ely commented 4 months ago

@PrLion https://github.com/krzyzanowskim/OpenSSL <-- package. Again I have not tested this package's functionality or security.

PrLion commented 4 months ago

Got it. Really, I don't understand why such a powerful team can't implement the connection in different ways? @matthew-ely @leeway1208

PrLion commented 22 hours ago

Hello @matthew-ely @leeway1208 Any solution?