You said this previously
If you want to authenticate the device, then each device needs its own identity. In that case, your identity generation code will need to include something device specific into the identity’s certificate so that a peer can tell that remote peer is the device that it’s expecting.
If you want to authenticate the user, then you can use a single identity for that. A peer can tell that the remote peer is the same user by checking that the certificate matches the certificate in the digital identity that it’s using.
The tricky part of about the latter is that your server has to store the digital identity (so, the certificate and the private key) so that new clients can access it. OTOH, if you authenticate the device then each device retains its own private key and the server only has to issue the certificate and store that.
I am getting stuck on this step. I used this online resource to create a certificate for testing https://www.samltool.com/self_signed_certs.php. I tried to base64 encode it and make it a constant in my codebase. I then tried to base64 decode it and then use it to create a certificate. I was able to do that successfully. The connection failed and I think it's because I don't have an identity but when I go to create an identity it's asking for a private key.
Between the two options you shared above I would think id want the "authenticate the user" because I don't care about the device - just that the same user is trying to connect.
When you say "then you can use a single identity" does that mean I send the client the certificate I created on my backend and then the client uses a private key it creates on its device to then create an identity? Do I send both the private key and certificate used on the backend to the client which is then used to create an identity.
If the "authenticate the device" is easier im open to that. The goal is as i shared above "bob's devices cant connect to tom's devices and vice versa". The "something device specific into the identity’s certificate" could be some hash identifier similar to what I shared about TXT_RECORD.
Post
Replies
Boosts
Views
Activity
Can I please have some assistance on how to properly setup an NWParameters extension to accept a base64Encoded public certificate and then use it to correctly secure connections?
I have this so far
extension NWParameters {
convenience init(base64EncodedCert: String) throws {
// Create QUIC parameters with the TLS options
let quicOptions = NWProtocolQUIC.Options(alpn: ["h3"])
// Convert the base64 string to a string to handle PEM format
guard let pemData = Data(base64Encoded: base64EncodedCert),
let pemString = String(data: pemData, encoding: .utf8)
else {
print("Failed to decode initial base64 string")
throw CertificateError.invalidBase64String
}
// Extract the certificate content between the PEM markers
let lines = pemString.components(separatedBy: .newlines)
let certificateLines = lines.filter { line in
!line.contains("BEGIN CERTIFICATE") && !line.contains("END CERTIFICATE") && !line.isEmpty
}
// Join the lines and create certificate data
let certificateString = certificateLines.joined()
guard let certificateData = Data(base64Encoded: certificateString) else {
print("Failed to decode certificate content")
throw CertificateError.invalidBase64String
}
print("Successfully decoded certificate content, data length: \(certificateData.count)")
// Try to create certificate
guard let certificate = SecCertificateCreateWithData(nil, certificateData as CFData) else {
print("Failed to create certificate from data")
throw CertificateError.certificateCreationFailed
}
sec_protocol_options_set_min_tls_protocol_version(quicOptions.securityProtocolOptions, .TLSv13)
sec_protocol_options_set_max_tls_protocol_version(quicOptions.securityProtocolOptions, .TLSv13)
print("Successfully created certificate")
// Get certificate summary for verification
let certificateSummary = SecCertificateCopySubjectSummary(certificate) as? String
print("Certificate Summary: \(certificateSummary ?? "No summary available")")
// Create identity from certificate
sec_protocol_options_set_verify_block(quicOptions.securityProtocolOptions, { _, sec_trust, sec_protocol_verify_complete in
// Get the certificates from the trust object
let trust = sec_trust_copy_ref(sec_trust).takeRetainedValue()
// Get the certificate chain
guard let certificates = SecTrustCopyCertificateChain(trust) as? [SecCertificate],
let peerCertificate = certificates.first
else {
print("Failed to get peer certificate from chain")
sec_protocol_verify_complete(false)
return
}
// Compare the peer's certificate with our pinned certificate
let peerCertificateData = SecCertificateCopyData(peerCertificate) as Data
let pinnedCertificateData = SecCertificateCopyData(certificate) as Data
// Verify the certificates match
let certificatesMatch = peerCertificateData == pinnedCertificateData
print("Certificate verification result: \(certificatesMatch)")
sec_protocol_verify_complete(certificatesMatch)
}, DispatchQueue(label: "com.example.certificate.verification"))
self.init(quic: quicOptions)
}
}
I am seeing "Certificate Summary: " printed to the console but I am getting the following errors
Connection state changed to: failed(-9858: handshake failed)
Connection failed: -9858: handshake failed
I came here for help and advise and I got just that. Thank you. I want to head your input so I'll be attempting to get the basics working with QUIC. In my mind they are as follows:
Generate a digital identity per user and store it on my backend
Be able to send generated digital identity to ios app after the user authenticates
Store digital identity in keychain
Use digital identity to setup TLS for QUIC NWListener and NWConnection
Use SHA256(user.id + "TXT_RECORD" + user.email + "TXT_RECORD" + user.id) for the TXT Record
Be able to validate / verify the certificate chain while in sec_protocol_options_set_verify_block
Am I missing anything?
Also, do you think you guys will ever support PSK for TLS 1.3 so it can be used with QUIC and if so is there an estimated time when it will be available?
I would still greatly appreciate a response to my last question so this thread can be complete for me if I need to come back here later.
I did some testing with TCP and was able to download a 30 second video from a peer that was 36 feet away using only peer to peer wifi (both devices were NOT on a wifi network) in around 2 seconds. At this point this is sufficient for me to get going. I'm sure I can clean up the code and make it more robust and maybe "faster".
I say all this to say I'm going to move forward with two connections per peer. One TCP connection, which will be used for sending commands and downloading content and one UDP connection, which will be used for video streaming. I plan to create both of these connection right when the peer is discovered.
On the topic of TCP and UDP. Would the NWBrowser need to be UDP or TCP or does it matter? For example _p2pchat._udp or _p2pchat._tcp? Is using PSK (with both TCP and UDP) enough security to protect against malicious devices trying to connect? Should I still use the bonjourWithTXTRecord(type:domain:)? For the PSK I was planning on making it SHA256(user.id + "PSK" + user.email + "PSK" + user.id). The user.id is something I generate on the backend (appwrite generates it actually). Can I use something similar for the TXT record, like SHA256(user.id + "TXT_RECORD" + user.email + "TXT_RECORD" + user.id)?
Thank you for all your time and input - it is really appreciated!
Get the certificate from the remote peer and check that it matches the certificate from the digital identity you’ve saved. As long as your back end provisions each user with their own unique certificate, that should be sufficient to ensure that users can only talk to themselves.
How can I get the certificate from the remote peer if I can't make a connect with the peer until I've verified its certificate? It feels like a chicken and the egg type of thing. Isn't the sec_protocol_options_set_verify_block part of the NWParameters setup process? How would I get the certificate from the remote peer while in that callback?
After doing research about the benefits QUIC has over TCP I would like to fully move forward with the QUIC implementation.
Before I start coding I like to get a clear mental model of what needs to happen and all the moving parts. After reading your response many times I think I understand.
To give a bit more background into how I want my app to operate I want to share some expected behavior.
Side note - on the Appwrite side (my backend) I am going to limit the user to having a max of 7 auth sessions (including web auth sessions and ios device auth sessions). So if they are authenticated on 2 browsers they would only be allows to run my app on 5 physical ios devices.
Example behavior:
Bob has my app on 3 of his devices. He has signed in and is authenticated (via the Appwrite swift SDK). Tom has my app on 3 of his devices and he too has signed in and is authenticated. Both Bob and Tom are in the same room. While in the same room Bob and Tom could be on no wifi or they could be on the same wifi or one is on wifi and one is not. The expectation is that Bob cannot discover and connect to Tom's devices and vice versa.
Seeing how the the serviceName (ie "_p2pchat._udp") needs to be set prior to the app starting and since it's also set in the Bonjour Info.plist field could, in theory, any device using my app discover one another?
Using the TickTackToe example code I was thinking I would use the hash of the user's email concatenated with their userID as the PSK. So I guess Bob and Tom's devices could all be discovered but only Bob's devices could connect to each other and only Tom's devices could connect to each other. I would then only show devices to the user that I've connected to.
Now when it comes to using QUIC and digital identify I'm a bit confused about how to achieve the desired behavior (ie Bob cannot discover and connect to Tom's devices and vice versa).
My first thought would be when a new user signs up I generate them a digital identity on my backend. When they go to the app and sign in they would request their digital identity and I would then store in UserDefaults to persist it. I would then use that digital identity to setup TLS for QUIC.
I know I can use a NWBrowser without TLS - it's just NWListener and NWConnection that require TLS. So going back to the above example. For Bob's devices I would use his downloaded digital identity to listen for connections and use it to create connections. If the identities don't match then the connection cannot be made.
Is my thinking on the right track?
Thanks for the response!
For UDP, there’s DTLS but I’ve never looked as to whether that supports PSK or not.
I tried this and it worked
extension NWParameters {
convenience init(passcode: String) {
let udpOptions = NWProtocolUDP.Options()
self.init(dtls: NWParameters.tlsOptions(passcode: passcode), udp: udpOptions)
self.includePeerToPeer = true
}
private static func tlsOptions(passcode: String) -> NWProtocolTLS.Options {
let tlsOptions = NWProtocolTLS.Options()
let authenticationKey = SymmetricKey(data: passcode.data(using: .utf8)!)
let authenticationCode = HMAC<SHA256>.authenticationCode(for: "HI".data(using: .utf8)!, using: authenticationKey)
let authenticationDispatchData = authenticationCode.withUnsafeBytes {
DispatchData(bytes: $0)
}
sec_protocol_options_add_pre_shared_key(tlsOptions.securityProtocolOptions,
authenticationDispatchData as __DispatchData,
stringToDispatchData("HI")! as __DispatchData)
sec_protocol_options_append_tls_ciphersuite(tlsOptions.securityProtocolOptions,
tls_ciphersuite_t(rawValue: TLS_PSK_WITH_AES_128_GCM_SHA256)!)
return tlsOptions
}
// Create a utility function to encode strings as preshared key data.
private static func stringToDispatchData(_ string: String) -> DispatchData? {
guard let stringData = string.data(using: .utf8) else {
return nil
}
let dispatchData = stringData.withUnsafeBytes {
DispatchData(bytes: $0)
}
return dispatchData
}
}
I was able to send messages back and forth.
However, it sounds like you’re just testing out QUIC right now, and hard coding a digital identity for the purposes of your test is fine.
I would like to hard code a digital identity to be able to test - do you have a resource that could help along that process?
If it comes time to do this correctly, you’ll want to find a way to:
Generate the digital identity on the server side.
Distribute that digital identity to all the clients.
I am using AppWrite as my backend service (it's similar to firebase). Would the idea / flow be as follows:
user signs in on the mobile app
make request to my backend requesting identity to be generated
respond with generated identity
use identity to secure QUIC connections
In the extreme example the user would have 7 devices where they use the same credentials to sign in. Would each device need to have the same identity that was generated on my backend in order to properly connect to each other?
would doing this be wrong in a production app?
sec_protocol_options_set_verify_block(options.securityProtocolOptions, { (sec_protocol_metadata, sec_trust, sec_protocol_verify_complete) in
sec_protocol_verify_complete(true)
}, queue)
I found this from this app https://github.com/paxsonsa/quic-swift-demo/blob/main/Sources/main.swift - I think he posted on this forum before.
Thanks for the reply and information. I am early in the development of my app so things can change. I want to be careful to not get captivated by what QUIC can offer if I don't really need it.
A high level description of the app:
up to 7 devices can all discover each other and connect to each other
command messages are sent between the devices (ie Device A sends out a "take photo" command and it's sent to all the connected devices)
media content is downloaded between devices (ie Device A download the photo that was taken by each peer)
stream what the camera is see so the main / controller device can get a preview of what the other devices are seeing
As I'm describing what the app does I'm thinking when a device is discovered two connections are opened up - a TCP connection used for commands and downloading and a UDP connection used for camera preview.
When it comes to QUIC and your advice of hardcoding a digital identify - would that be ill-advised in production? Would a user be able to inspect the app payload and extract the hard coded digital identity and be a bad actor with it? No sensitive PPI data will be sent over the QUIC connection.
I appreciate your input and advice.
I plan to only allow up to 6 connections (ie 7 devices all connected to each other) and the connections will be used for the following
send command messages (ie "start recording", "stop recording", "take photo")
fetch thumbnails of media on device and its URI to fetch
download media content (ie photos and videos) from Device A to Device B
streaming video (ie I will also want to stream video from Device B, C and D to Device A (ie Device A can get a preview of what Device B, C and D are seeing))
There could be a scenario where Device A is the "controller" and its connected to 3 other devices (B, C, D) and Device A requests a video from each other device - so it would be downloading 3 videos from 3 different devices.
Is TCP "enough" for my use cases and is it worth it to try and use QUIC for the performance gains.
Also, both the physical devices I am running the app on (iphone and ipad) are on iOS 18
If it's easier I can post my demo code in a public github repo.
Also, both the physical devices I am running the app on (iphone and ipad) are on iOS 18
Oh, I will also want to stream video from Device B, C and D to Device A (ie Device A can get a preview of what Device B, C and D are seeing)