Reformatting

This commit is contained in:
Leon Böttger committed 2025-05-14 15:06:32 +02:00
1 parent 05fa6043fe
commit 6c6c79cdad
5 files changed
+1279 -1307

No files matched your search

+38 -38
View File
@@ -1,47 +1,47 @@
//
// Data+URLSafeBase64.swift
// Data+Extensions.swift
// QuickDrop
//
// Created by Grishka on 08.04.2023.
//
import Foundation
import CoreFoundation
import Foundation
extension Data{
func urlSafeBase64EncodedString() -> String {
return String(base64EncodedString().replacingOccurrences(of: "=", with: "").map {
if $0=="/"{
return "_"
} else if $0=="+" {
return "-"
} else {
return $0
}
})
}
static func randomData(length: Int) -> Data{
var data=Data(count: length)
data.withUnsafeMutableBytes {
guard 0 == SecRandomCopyBytes(kSecRandomDefault, length, $0.baseAddress!) else { fatalError() }
}
return data
}
static func dataFromUrlSafeBase64(_ str:String)->Data?{
var regularB64=String(str.map{
if $0=="_"{
return "/"
}else if $0=="-"{
return "+"
}else{
return $0
}
})
while (regularB64.count%4) != 0{
regularB64=regularB64+"="
}
return Data(base64Encoded: regularB64, options: .ignoreUnknownCharacters)
}
extension Data {
func urlSafeBase64EncodedString() -> String {
return String(base64EncodedString().replacingOccurrences(of: "=", with: "").map {
if $0 == "/" {
return "_"
} else if $0 == "+" {
return "-"
} else {
return $0
}
})
}
static func randomData(length: Int) -> Data {
var data = Data(count: length)
data.withUnsafeMutableBytes {
guard SecRandomCopyBytes(kSecRandomDefault, length, $0.baseAddress!) == 0 else { fatalError() }
}
return data
}
static func dataFromUrlSafeBase64(_ str: String) -> Data? {
var regularB64 = String(str.map {
if $0 == "_" {
return "/"
} else if $0 == "-" {
return "+"
} else {
return $0
}
})
while (regularB64.count % 4) != 0 {
regularB64 = regularB64 + "="
}
return Data(base64Encoded: regularB64, options: .ignoreUnknownCharacters)
}
}
+231 -252
View File
@@ -5,94 +5,89 @@
// Created by Grishka on 08.04.2023.
//
import AppKit
import CommonCrypto
import CryptoKit
import Foundation
import Network
import CryptoKit
import CommonCrypto
import System
import AppKit
import SwiftECC
import BigInt
import SwiftECC
class InboundNearbyConnection: NearbyConnection{
private var currentState:State = .initial
public var delegate:InboundNearbyConnectionDelegate?
private var cipherCommitment:Data?
private var textPayloadID:Int64=0
enum State{
class InboundNearbyConnection: NearbyConnection {
private var currentState: State = .initial
public var delegate: InboundNearbyConnectionDelegate?
private var cipherCommitment: Data?
private var textPayloadID: Int64 = 0
enum State {
case initial, receivedConnectionRequest, sentUkeyServerInit, receivedUkeyClientFinish, sentConnectionResponse, sentPairedKeyResult, receivedPairedKeyResult, waitingForUserConsent, receivingFiles, disconnected
}
override init(connection: NWConnection, id:String) {
override init(connection: NWConnection, id: String) {
super.init(connection: connection, id: id)
}
override func handleConnectionClosure() {
super.handleConnectionClosure()
currentState = .disconnected
do {
try deletePartiallyReceivedFiles()
} catch{
} catch {
log("Error deleting partially received files: \(error)")
}
DispatchQueue.main.async {
self.delegate?.connectionWasTerminated(connection: self, error: self.lastError)
SaveFilesManager.shared.movePendingFilesToTarget()
SaveFilesManager.shared.stopAccessingSecurityScopedResource()
}
}
override internal func processReceivedFrame(frameData:Data){
override func processReceivedFrame(frameData: Data) {
if currentState != .receivingFiles {
log("Received frame in state \(currentState)...")
}
do {
switch currentState {
case .initial:
let frame=try Location_Nearby_Connections_OfflineFrame(serializedBytes: frameData)
let frame = try Location_Nearby_Connections_OfflineFrame(serializedBytes: frameData)
try processConnectionRequestFrame(frame)
case .receivedConnectionRequest:
let msg=try Securegcm_Ukey2Message(serializedBytes: frameData)
ukeyClientInitMsgData=frameData
let msg = try Securegcm_Ukey2Message(serializedBytes: frameData)
ukeyClientInitMsgData = frameData
try processUkey2ClientInit(msg)
case .sentUkeyServerInit:
let msg=try Securegcm_Ukey2Message(serializedBytes: frameData)
let msg = try Securegcm_Ukey2Message(serializedBytes: frameData)
try processUkey2ClientFinish(msg, raw: frameData)
case .receivedUkeyClientFinish:
let frame=try Location_Nearby_Connections_OfflineFrame(serializedBytes: frameData)
let frame = try Location_Nearby_Connections_OfflineFrame(serializedBytes: frameData)
try processConnectionResponseFrame(frame)
default:
var smsg: Securemessage_SecureMessage
do {
smsg = try Securemessage_SecureMessage(serializedBytes: frameData)
}
catch {
} catch {
log("Error deserializing secure message. Trying again....")
// last 32 bytes = HMAC key
// change 2 bytes before to 0x12 0x20 for Protobuf to succeed
if frameData.count < 34 {
throw NearbyError.protocolError("Frame too short")
}
var newData = frameData
let count = newData.count
newData[count - 34] = 0x12
newData[count - 33] = 0x20
smsg = try Securemessage_SecureMessage(serializedBytes: newData)
log("Secure message deserialized successfully after fixing Protobuf message")
}
try decryptAndProcessReceivedSecureMessage(smsg)
@@ -100,27 +95,27 @@ class InboundNearbyConnection: NearbyConnection{
} catch {
lastError = error
log("Deserialization error: \(error) in state \(currentState). Payload: \(frameData.hex)")
//log("Public Key: \(publicKey?.pem ?? "nil")")
//log("Private Key: \(privateKey?.pem ?? "nil")")
//log("Ukey Client Init Msg: \(ukeyClientInitMsgData?.hex ?? "nil")")
//log("Ukey Server Init Msg: \(ukeyServerInitMsgData?.hex ?? "nil")")
// log("Public Key: \(publicKey?.pem ?? "nil")")
// log("Private Key: \(privateKey?.pem ?? "nil")")
// log("Ukey Client Init Msg: \(ukeyClientInitMsgData?.hex ?? "nil")")
// log("Ukey Server Init Msg: \(ukeyServerInitMsgData?.hex ?? "nil")")
log("S1: \(decryptKey?.map { String(format: "%02x", $0) }.joined() ?? "nil")")
//log("Encrypt Key: \(encryptKey?.map { String(format: "%02x", $0) }.joined() ?? "nil")")
// log("Encrypt Key: \(encryptKey?.map { String(format: "%02x", $0) }.joined() ?? "nil")")
log("Received HMAC/Signature Key: \(recvHmacKey?.withUnsafeBytes { Data(Array($0)) }.map { String(format: "%02x", $0) }.joined() ?? "nil")")
log("Sent HMAC/Signature Key: \(sendHmacKey?.withUnsafeBytes { Data(Array($0)) }.map { String(format: "%02x", $0) }.joined() ?? "nil")")
self.protocolError()
protocolError()
}
}
override internal func processTransferSetupFrame(_ frame:Sharing_Nearby_Frame) throws{
override func processTransferSetupFrame(_ frame: Sharing_Nearby_Frame) throws {
if frame.hasV1 && frame.v1.hasType, case .cancel = frame.v1.type {
log("Transfer canceled")
try sendDisconnectionAndDisconnect()
return
}
switch currentState{
switch currentState {
case .sentConnectionResponse:
try processPairedKeyEncryptionFrame(frame)
case .sentPairedKeyResult:
@@ -132,47 +127,47 @@ class InboundNearbyConnection: NearbyConnection{
log(frame.debugDescription)
}
}
override func isServer() -> Bool {
return true
}
override func processFileChunk(frame: Location_Nearby_Connections_PayloadTransferFrame) throws{
let id=frame.payloadHeader.id
guard let fileInfo=transferredFiles[id] else { throw NearbyError.protocolError("File payload ID \(id) is not known") }
let currentOffset=fileInfo.bytesTransferred
guard frame.payloadChunk.offset==currentOffset else { throw NearbyError.protocolError("Invalid offset into file \(frame.payloadChunk.offset), expected \(currentOffset)") }
guard currentOffset+Int64(frame.payloadChunk.body.count)<=fileInfo.meta.size else { throw NearbyError.protocolError("Transferred file size exceeds previously specified value") }
if frame.payloadChunk.body.count>0{
override func processFileChunk(frame: Location_Nearby_Connections_PayloadTransferFrame) throws {
let id = frame.payloadHeader.id
guard let fileInfo = transferredFiles[id] else { throw NearbyError.protocolError("File payload ID \(id) is not known") }
let currentOffset = fileInfo.bytesTransferred
guard frame.payloadChunk.offset == currentOffset else { throw NearbyError.protocolError("Invalid offset into file \(frame.payloadChunk.offset), expected \(currentOffset)") }
guard currentOffset + Int64(frame.payloadChunk.body.count) <= fileInfo.meta.size else { throw NearbyError.protocolError("Transferred file size exceeds previously specified value") }
if frame.payloadChunk.body.count > 0 {
fileInfo.fileHandle?.write(frame.payloadChunk.body)
transferredFiles[id]!.bytesTransferred+=Int64(frame.payloadChunk.body.count)
fileInfo.progress?.completedUnitCount=transferredFiles[id]!.bytesTransferred
}else if (frame.payloadChunk.flags & 1)==1{
transferredFiles[id]!.bytesTransferred += Int64(frame.payloadChunk.body.count)
fileInfo.progress?.completedUnitCount = transferredFiles[id]!.bytesTransferred
} else if (frame.payloadChunk.flags & 1) == 1 {
try fileInfo.fileHandle?.close()
transferredFiles[id]!.fileHandle=nil
transferredFiles[id]!.fileHandle = nil
fileInfo.progress?.unpublish()
SaveFilesManager.shared.registerFileFinishedDownloading(fileInfo.destinationURL)
transferredFiles.removeValue(forKey: id)
if transferredFiles.isEmpty{
if transferredFiles.isEmpty {
try sendDisconnectionAndDisconnect()
}
}
}
override func processBytesPayload(payload: Data, id: Int64) throws -> Bool {
if id==textPayloadID{
if let urlStr=String(data: payload, encoding: .utf8), let url=URL(string: urlStr){
if id == textPayloadID {
if let urlStr = String(data: payload, encoding: .utf8), let url = URL(string: urlStr) {
NSWorkspace.shared.open(url)
}
try sendDisconnectionAndDisconnect()
return true
}else if let fileInfo=transferredFiles[id]{
} else if let fileInfo = transferredFiles[id] {
fileInfo.fileHandle?.write(payload)
transferredFiles[id]!.bytesTransferred+=Int64(payload.count)
fileInfo.progress?.completedUnitCount=transferredFiles[id]!.bytesTransferred
transferredFiles[id]!.bytesTransferred += Int64(payload.count)
fileInfo.progress?.completedUnitCount = transferredFiles[id]!.bytesTransferred
try fileInfo.fileHandle?.close()
transferredFiles[id]!.fileHandle=nil
transferredFiles[id]!.fileHandle = nil
fileInfo.progress?.unpublish()
transferredFiles.removeValue(forKey: id)
SaveFilesManager.shared.registerFileFinishedDownloading(fileInfo.destinationURL)
@@ -181,247 +176,243 @@ class InboundNearbyConnection: NearbyConnection{
}
return false
}
private func processConnectionRequestFrame(_ frame:Location_Nearby_Connections_OfflineFrame) throws{
private func processConnectionRequestFrame(_ frame: Location_Nearby_Connections_OfflineFrame) throws {
guard frame.hasV1 && frame.v1.hasConnectionRequest && frame.v1.connectionRequest.hasEndpointInfo else { throw NearbyError.requiredFieldMissing("connectionRequest.endpointInfo") }
guard case .connectionRequest = frame.v1.type else { throw NearbyError.protocolError("Unexpected frame type \(frame.v1.type)") }
let endpointInfo=frame.v1.connectionRequest.endpointInfo
guard endpointInfo.count>17 else { throw NearbyError.protocolError("Endpoint info too short") }
let deviceNameLength=Int(endpointInfo[17])
guard endpointInfo.count>=deviceNameLength+18 else { throw NearbyError.protocolError("Endpoint info too short to contain the device name") }
guard let deviceName=String(data: endpointInfo[18..<(18+deviceNameLength)], encoding: .utf8) else { throw NearbyError.protocolError("Device name is not valid UTF-8") }
let rawDeviceType:Int=Int(endpointInfo[0] & 7) >> 1
remoteDeviceInfo=RemoteDeviceInfo(name: deviceName, type: RemoteDeviceInfo.DeviceType.fromRawValue(value: rawDeviceType))
let endpointInfo = frame.v1.connectionRequest.endpointInfo
guard endpointInfo.count > 17 else { throw NearbyError.protocolError("Endpoint info too short") }
let deviceNameLength = Int(endpointInfo[17])
guard endpointInfo.count >= deviceNameLength + 18 else { throw NearbyError.protocolError("Endpoint info too short to contain the device name") }
guard let deviceName = String(data: endpointInfo[18 ..< (18 + deviceNameLength)], encoding: .utf8) else { throw NearbyError.protocolError("Device name is not valid UTF-8") }
let rawDeviceType = Int(endpointInfo[0] & 7) >> 1
remoteDeviceInfo = RemoteDeviceInfo(name: deviceName, type: RemoteDeviceInfo.DeviceType.fromRawValue(value: rawDeviceType))
currentState = .receivedConnectionRequest
}
private func processUkey2ClientInit(_ msg:Securegcm_Ukey2Message) throws{
private func processUkey2ClientInit(_ msg: Securegcm_Ukey2Message) throws {
guard msg.hasMessageType, msg.hasMessageData else { throw NearbyError.requiredFieldMissing("clientInit ukey2message.type|data") }
guard case .clientInit = msg.messageType else{
guard case .clientInit = msg.messageType else {
sendUkey2Alert(type: .badMessageType)
throw NearbyError.ukey2
}
let clientInit:Securegcm_Ukey2ClientInit
do{
clientInit=try Securegcm_Ukey2ClientInit(serializedBytes: msg.messageData)
}catch{
let clientInit: Securegcm_Ukey2ClientInit
do {
clientInit = try Securegcm_Ukey2ClientInit(serializedBytes: msg.messageData)
} catch {
sendUkey2Alert(type: .badMessageData)
throw NearbyError.ukey2
}
guard clientInit.version==1 else{
guard clientInit.version == 1 else {
sendUkey2Alert(type: .badVersion)
throw NearbyError.ukey2
}
guard clientInit.random.count==32 else{
guard clientInit.random.count == 32 else {
sendUkey2Alert(type: .badRandom)
throw NearbyError.ukey2
}
var found=false
for commitment in clientInit.cipherCommitments{
if case .p256Sha512 = commitment.handshakeCipher{
found=true
cipherCommitment=commitment.commitment
var found = false
for commitment in clientInit.cipherCommitments {
if case .p256Sha512 = commitment.handshakeCipher {
found = true
cipherCommitment = commitment.commitment
break
}
}
guard found else{
guard found else {
sendUkey2Alert(type: .badHandshakeCipher)
throw NearbyError.ukey2
}
guard clientInit.nextProtocol=="AES_256_CBC-HMAC_SHA256" else{
guard clientInit.nextProtocol == "AES_256_CBC-HMAC_SHA256" else {
sendUkey2Alert(type: .badNextProtocol)
throw NearbyError.ukey2
}
let domain=Domain.instance(curve: .EC256r1)
let (pubKey, privKey)=domain.makeKeyPair()
publicKey=pubKey
privateKey=privKey
var serverInit=Securegcm_Ukey2ServerInit()
serverInit.version=1
serverInit.random=Data.randomData(length: 32)
let domain = Domain.instance(curve: .EC256r1)
let (pubKey, privKey) = domain.makeKeyPair()
publicKey = pubKey
privateKey = privKey
var serverInit = Securegcm_Ukey2ServerInit()
serverInit.version = 1
serverInit.random = Data.randomData(length: 32)
serverInit.handshakeCipher = .p256Sha512
var pkey=Securemessage_GenericPublicKey()
var pkey = Securemessage_GenericPublicKey()
pkey.type = .ecP256
pkey.ecP256PublicKey=Securemessage_EcP256PublicKey()
pkey.ecP256PublicKey.x=Data(pubKey.w.x.asSignedBytes())
pkey.ecP256PublicKey.y=Data(pubKey.w.y.asSignedBytes())
serverInit.publicKey=try pkey.serializedData()
var serverInitMsg=Securegcm_Ukey2Message()
pkey.ecP256PublicKey = Securemessage_EcP256PublicKey()
pkey.ecP256PublicKey.x = Data(pubKey.w.x.asSignedBytes())
pkey.ecP256PublicKey.y = Data(pubKey.w.y.asSignedBytes())
serverInit.publicKey = try pkey.serializedData()
var serverInitMsg = Securegcm_Ukey2Message()
serverInitMsg.messageType = .serverInit
serverInitMsg.messageData=try serverInit.serializedData()
let serverInitData=try serverInitMsg.serializedData()
ukeyServerInitMsgData=serverInitData
serverInitMsg.messageData = try serverInit.serializedData()
let serverInitData = try serverInitMsg.serializedData()
ukeyServerInitMsgData = serverInitData
sendFrameAsync(serverInitData)
currentState = .sentUkeyServerInit
}
private func processUkey2ClientFinish(_ msg:Securegcm_Ukey2Message, raw:Data) throws{
private func processUkey2ClientFinish(_ msg: Securegcm_Ukey2Message, raw: Data) throws {
guard msg.hasMessageType, msg.hasMessageData else { throw NearbyError.requiredFieldMissing("clientFinish ukey2message.type|data") }
guard case .clientFinish = msg.messageType else { throw NearbyError.ukey2 }
var sha=SHA512()
var sha = SHA512()
sha.update(data: raw)
guard cipherCommitment==Data(sha.finalize()) else { throw NearbyError.ukey2 }
let clientFinish=try Securegcm_Ukey2ClientFinished(serializedBytes: msg.messageData)
guard clientFinish.hasPublicKey else {throw NearbyError.requiredFieldMissing("ukey2clientFinish.publicKey") }
let clientKey=try Securemessage_GenericPublicKey(serializedBytes: clientFinish.publicKey)
guard cipherCommitment == Data(sha.finalize()) else { throw NearbyError.ukey2 }
let clientFinish = try Securegcm_Ukey2ClientFinished(serializedBytes: msg.messageData)
guard clientFinish.hasPublicKey else { throw NearbyError.requiredFieldMissing("ukey2clientFinish.publicKey") }
let clientKey = try Securemessage_GenericPublicKey(serializedBytes: clientFinish.publicKey)
try finalizeKeyExchange(peerKey: clientKey)
currentState = .receivedUkeyClientFinish
}
private func processConnectionResponseFrame(_ frame:Location_Nearby_Connections_OfflineFrame) throws{
private func processConnectionResponseFrame(_ frame: Location_Nearby_Connections_OfflineFrame) throws {
guard frame.hasV1, frame.v1.hasType else { throw NearbyError.requiredFieldMissing("offlineFrame.v1.type") }
if case .connectionResponse = frame.v1.type {
var resp=Location_Nearby_Connections_OfflineFrame()
var resp = Location_Nearby_Connections_OfflineFrame()
resp.version = .v1
resp.v1=Location_Nearby_Connections_V1Frame()
resp.v1 = Location_Nearby_Connections_V1Frame()
resp.v1.type = .connectionResponse
resp.v1.connectionResponse=Location_Nearby_Connections_ConnectionResponseFrame()
resp.v1.connectionResponse = Location_Nearby_Connections_ConnectionResponseFrame()
resp.v1.connectionResponse.response = .accept
resp.v1.connectionResponse.status=0
resp.v1.connectionResponse.osInfo=Location_Nearby_Connections_OsInfo()
resp.v1.connectionResponse.status = 0
resp.v1.connectionResponse.osInfo = Location_Nearby_Connections_OsInfo()
resp.v1.connectionResponse.osInfo.type = .apple
sendFrameAsync(try resp.serializedData())
encryptionDone=true
var pairedEncryption=Sharing_Nearby_Frame()
try sendFrameAsync(resp.serializedData())
encryptionDone = true
var pairedEncryption = Sharing_Nearby_Frame()
pairedEncryption.version = .v1
pairedEncryption.v1=Sharing_Nearby_V1Frame()
pairedEncryption.v1 = Sharing_Nearby_V1Frame()
pairedEncryption.v1.type = .pairedKeyEncryption
pairedEncryption.v1.pairedKeyEncryption=Sharing_Nearby_PairedKeyEncryptionFrame()
pairedEncryption.v1.pairedKeyEncryption = Sharing_Nearby_PairedKeyEncryptionFrame()
// Presumably used for all the phone number stuff that no one needs anyway
pairedEncryption.v1.pairedKeyEncryption.secretIDHash=Data.randomData(length: 6)
pairedEncryption.v1.pairedKeyEncryption.signedData=Data.randomData(length: 72)
pairedEncryption.v1.pairedKeyEncryption.secretIDHash = Data.randomData(length: 6)
pairedEncryption.v1.pairedKeyEncryption.signedData = Data.randomData(length: 72)
try sendTransferSetupFrame(pairedEncryption)
currentState = .sentConnectionResponse
} else {
log("Unhandled offline frame plaintext: \(frame)")
}
}
private func processPairedKeyEncryptionFrame(_ frame:Sharing_Nearby_Frame) throws{
private func processPairedKeyEncryptionFrame(_ frame: Sharing_Nearby_Frame) throws {
guard frame.hasV1, frame.v1.hasPairedKeyEncryption else { throw NearbyError.requiredFieldMissing("shareNearbyFrame.v1.pairedKeyEncryption") }
var pairedResult=Sharing_Nearby_Frame()
var pairedResult = Sharing_Nearby_Frame()
pairedResult.version = .v1
pairedResult.v1=Sharing_Nearby_V1Frame()
pairedResult.v1 = Sharing_Nearby_V1Frame()
pairedResult.v1.type = .pairedKeyResult
pairedResult.v1.pairedKeyResult=Sharing_Nearby_PairedKeyResultFrame()
pairedResult.v1.pairedKeyResult = Sharing_Nearby_PairedKeyResultFrame()
pairedResult.v1.pairedKeyResult.status = .unable
try sendTransferSetupFrame(pairedResult)
currentState = .sentPairedKeyResult
}
private func processPairedKeyResultFrame(_ frame:Sharing_Nearby_Frame) throws{
private func processPairedKeyResultFrame(_ frame: Sharing_Nearby_Frame) throws {
guard frame.hasV1, frame.v1.hasPairedKeyResult else { throw NearbyError.requiredFieldMissing("shareNearbyFrame.v1.pairedKeyResult") }
currentState = .receivedPairedKeyResult
}
private func makeFileDestinationURL(_ initialDest:URL) -> URL {
var dest=initialDest
if FileManager.default.fileExists(atPath: dest.path){
var counter=1
var path:String
let ext=dest.pathExtension
let baseUrl=dest.deletingPathExtension()
repeat{
path="\(baseUrl.path) (\(counter))"
if !ext.isEmpty{
path+=".\(ext)"
private func makeFileDestinationURL(_ initialDest: URL) -> URL {
var dest = initialDest
if FileManager.default.fileExists(atPath: dest.path) {
var counter = 1
var path: String
let ext = dest.pathExtension
let baseUrl = dest.deletingPathExtension()
repeat {
path = "\(baseUrl.path) (\(counter))"
if !ext.isEmpty {
path += ".\(ext)"
}
counter+=1
}while FileManager.default.fileExists(atPath: path)
dest=URL(fileURLWithPath: path)
counter += 1
} while FileManager.default.fileExists(atPath: path)
dest = URL(fileURLWithPath: path)
}
return dest
}
private func processIntroductionFrame(_ frame:Sharing_Nearby_Frame) throws{
private func processIntroductionFrame(_ frame: Sharing_Nearby_Frame) throws {
guard frame.hasV1, frame.v1.hasIntroduction else { throw NearbyError.requiredFieldMissing("shareNearbyFrame.v1.introduction") }
currentState = .waitingForUserConsent
if frame.v1.introduction.fileMetadata.count>0 && frame.v1.introduction.textMetadata.isEmpty{
if frame.v1.introduction.fileMetadata.count > 0 && frame.v1.introduction.textMetadata.isEmpty {
let saveDirectory = SaveFilesManager.shared.getSaveDirectory()
for file in frame.v1.introduction.fileMetadata {
let dest = makeFileDestinationURL(saveDirectory.appendingPathComponent(file.name))
let info = InternalFileInfo(meta: FileMetadata(name: file.name, size: file.size, mimeType: file.mimeType),
payloadID: file.payloadID,
destinationURL: dest)
transferredFiles[file.payloadID] = info
}
let metadata=TransferMetadata(files: transferredFiles.map({$0.value.meta}), id: id, pinCode: pinCode)
let metadata = TransferMetadata(files: transferredFiles.map { $0.value.meta }, id: id, pinCode: pinCode)
DispatchQueue.main.async {
self.delegate?.obtainUserConsent(for: metadata, from: self.remoteDeviceInfo!, connection: self)
}
}else if frame.v1.introduction.textMetadata.count==1{
let meta=frame.v1.introduction.textMetadata[0]
if case .url=meta.type{
let metadata=TransferMetadata(files: [], id: id, pinCode: pinCode, textDescription: meta.textTitle)
textPayloadID=meta.payloadID
} else if frame.v1.introduction.textMetadata.count == 1 {
let meta = frame.v1.introduction.textMetadata[0]
if case .url = meta.type {
let metadata = TransferMetadata(files: [], id: id, pinCode: pinCode, textDescription: meta.textTitle)
textPayloadID = meta.payloadID
DispatchQueue.main.async {
self.delegate?.obtainUserConsent(for: metadata, from: self.remoteDeviceInfo!, connection: self)
}
}else if case .text=meta.type{
} else if case .text = meta.type {
let saveDirectory = SaveFilesManager.shared.getSaveDirectory()
let dateFormatter=DateFormatter()
dateFormatter.dateFormat="yyyy-MM-dd HH.mm.ss"
let dest=makeFileDestinationURL(saveDirectory.appendingPathComponent("\(dateFormatter.string(from: Date())).txt"))
let info=InternalFileInfo(meta: FileMetadata(name: dest.lastPathComponent, size: meta.size, mimeType: "text/plain"),
payloadID: meta.payloadID,
destinationURL: dest)
transferredFiles[meta.payloadID]=info
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH.mm.ss"
let dest = makeFileDestinationURL(saveDirectory.appendingPathComponent("\(dateFormatter.string(from: Date())).txt"))
let info = InternalFileInfo(meta: FileMetadata(name: dest.lastPathComponent, size: meta.size, mimeType: "text/plain"),
payloadID: meta.payloadID,
destinationURL: dest)
transferredFiles[meta.payloadID] = info
DispatchQueue.main.async {
self.delegate?.obtainUserConsent(for: TransferMetadata(files: [info.meta], id: self.id, pinCode: self.pinCode), from: self.remoteDeviceInfo!, connection: self)
}
}else{
} else {
rejectTransfer(with: .unsupportedAttachmentType)
}
}else{
} else {
rejectTransfer(with: .unsupportedAttachmentType)
}
}
func submitUserConsent(accepted:Bool, storeInTemp: Bool = false){
func submitUserConsent(accepted: Bool, storeInTemp: Bool = false) {
DispatchQueue.global(qos: .utility).async {
if accepted{
if accepted {
self.acceptTransfer(storeInTemp: storeInTemp)
}else{
} else {
self.rejectTransfer()
}
}
}
private func acceptTransfer(storeInTemp: Bool) {
if currentState == .disconnected {
log("Detected timeout, not accepting transfer")
return
}
do {
if storeInTemp {
try FileManager.default.createDirectory(at: SaveFilesManager.shared.tempDirectory, withIntermediateDirectories: true)
}
for (id, file) in transferredFiles {
let targetURL = storeInTemp ? SaveFilesManager.shared.tempDirectory.appendingPathComponent(file.destinationURL.lastPathComponent) : file.destinationURL
FileManager.default.createFile(atPath: targetURL.path, contents: nil)
let handle = try FileHandle(forWritingTo: targetURL)
transferredFiles[id]!.fileHandle = handle
let progress = Progress()
progress.fileURL = targetURL
progress.totalUnitCount = file.meta.size
@@ -431,7 +422,7 @@ class InboundNearbyConnection: NearbyConnection{
transferredFiles[id]!.progress = progress
transferredFiles[id]!.created = true
}
var frame = Sharing_Nearby_Frame()
frame.version = .v1
frame.v1.type = .response
@@ -443,24 +434,22 @@ class InboundNearbyConnection: NearbyConnection{
protocolError()
}
}
private func rejectTransfer(with reason:Sharing_Nearby_ConnectionResponseFrame.Status = .reject){
var frame=Sharing_Nearby_Frame()
private func rejectTransfer(with reason: Sharing_Nearby_ConnectionResponseFrame.Status = .reject) {
var frame = Sharing_Nearby_Frame()
frame.version = .v1
frame.v1.type = .response
frame.v1.connectionResponse.status = reason
do{
do {
try sendTransferSetupFrame(frame)
try sendDisconnectionAndDisconnect()
}catch{
} catch {
log("Error \(error)")
protocolError()
}
}
private func deletePartiallyReceivedFiles() throws{
private func deletePartiallyReceivedFiles() throws {
for (_, file) in transferredFiles {
guard file.created else { continue }
try FileManager.default.removeItem(at: file.destinationURL)
@@ -468,48 +457,42 @@ class InboundNearbyConnection: NearbyConnection{
}
}
protocol InboundNearbyConnectionDelegate{
func obtainUserConsent(for transfer:TransferMetadata, from device:RemoteDeviceInfo, connection:InboundNearbyConnection)
func connectionWasTerminated(connection:InboundNearbyConnection, error:Error?)
protocol InboundNearbyConnectionDelegate {
func obtainUserConsent(for transfer: TransferMetadata, from device: RemoteDeviceInfo, connection: InboundNearbyConnection)
func connectionWasTerminated(connection: InboundNearbyConnection, error: Error?)
}
public class SaveFilesManager {
private init() {}
public static let shared = SaveFilesManager()
internal let tempDirectory: URL = FileManager.default.temporaryDirectory.appendingPathComponent("Pending")
let tempDirectory: URL = FileManager.default.temporaryDirectory.appendingPathComponent("Pending")
private var securityScopeUrl: URL?
private var filesFinishedDownloading = [URL]()
public func registerFileFinishedDownloading(_ fileURL: URL) {
filesFinishedDownloading.append(fileURL)
}
public func movePendingFilesToTarget() {
if !isPlusVersion() {
return
}
log("Moving pending files to target directory")
do {
let fileManager = FileManager.default
let files = try fileManager.contentsOfDirectory(at: tempDirectory, includingPropertiesForKeys: nil)
let target = getSaveDirectory()
for file in files {
let fileName = file.lastPathComponent
let destinationURL = target.appendingPathComponent(fileName)
if !filesFinishedDownloading.contains(destinationURL) {
log("File \(file) not finished downloading, skipping")
continue
@@ -517,74 +500,69 @@ public class SaveFilesManager {
log("Moving file: \(file.lastPathComponent) to \(destinationURL.lastPathComponent)")
try fileManager.copyItem(at: file, to: destinationURL)
let progress = Progress()
progress.fileURL = destinationURL
progress.totalUnitCount = 10
progress.kind = .file
progress.isPausable = false
progress.publish()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
progress.completedUnitCount = 10
progress.unpublish()
}
try fileManager.removeItem(at: file)
}
stopAccessingSecurityScopedResource()
log("Moved all pending files to target directory")
} catch {
log("Error moving pending files: \(error)")
}
}
public func stopAccessingSecurityScopedResource() {
guard let url = securityScopeUrl else {
return
}
log("Stopping access to security scoped resource: \(url)")
url.stopAccessingSecurityScopedResource()
self.securityScopeUrl = nil
securityScopeUrl = nil
}
public func getSaveDirectory() -> URL {
if let securityScopeUrl = securityScopeUrl {
log("Using existing security scope URL: \(securityScopeUrl)")
return securityScopeUrl
}
if let bookmarkData = UserDefaults.standard.data(forKey: UserDefaultsKeys.saveFolderBookmark.rawValue) {
var isStale = false
do {
let url = try URL(resolvingBookmarkData: bookmarkData, options: .withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &isStale)
if !isStale {
if url.startAccessingSecurityScopedResource() {
log("Successfully accessed security scoped resource: \(url)")
self.securityScopeUrl = url
securityScopeUrl = url
return url
}
} else {
print("Bookmark is stale, using default downloads folder.")
}
} catch {
print("Failed to resolve bookmark: \(error), using default downloads folder.")
}
}
do {
return try FileManager.default.url(for: .downloadsDirectory, in: .userDomainMask, appropriateFor: nil, create: true).resolvingSymlinksInPath()
} catch {
@@ -606,7 +584,8 @@ extension String {
while index < endIndex {
let nextIndex = self.index(index, offsetBy: 2)
if nextIndex <= endIndex,
let byte = UInt8(self[index..<nextIndex], radix: 16) {
let byte = UInt8(self[index ..< nextIndex], radix: 16)
{
data.append(byte)
} else {
break // or handle invalid hex gracefully
+280 -280
View File
@@ -5,134 +5,134 @@
// Created by Grishka on 09.04.2023.
//
import Foundation
import Network
import CommonCrypto
import CryptoKit
import Foundation
import Network
import System
import SwiftECC
import BigInt
import SwiftECC
class NearbyConnection {
static let SANE_FRAME_LENGTH = 5 * 1024 * 1024
private static let dispatchQueue = DispatchQueue(label: "com.leonboettger.quickdrop.queue", qos: .utility) // FIFO (non-concurrent) queue to avoid those exciting concurrency bugs
let connection: NWConnection
var remoteDeviceInfo: RemoteDeviceInfo?
private var payloadBuffers: [Int64: NSMutableData] = [:]
var encryptionDone: Bool = false
var transferredFiles: [Int64: InternalFileInfo] = [:]
public let id: String
var lastError: Error?
private var connectionClosed: Bool = false
class NearbyConnection{
internal static let SANE_FRAME_LENGTH=5*1024*1024
private static let dispatchQueue=DispatchQueue(label: "com.leonboettger.quickdrop.queue", qos: .utility) // FIFO (non-concurrent) queue to avoid those exciting concurrency bugs
internal let connection:NWConnection
internal var remoteDeviceInfo:RemoteDeviceInfo?
private var payloadBuffers:[Int64:NSMutableData]=[:]
internal var encryptionDone:Bool=false
internal var transferredFiles:[Int64:InternalFileInfo]=[:]
public let id:String
internal var lastError:Error?
private var connectionClosed:Bool=false
// UKEY2-related state
internal var publicKey:ECPublicKey?
internal var privateKey:ECPrivateKey?
internal var ukeyClientInitMsgData:Data?
internal var ukeyServerInitMsgData:Data?
var publicKey: ECPublicKey?
var privateKey: ECPrivateKey?
var ukeyClientInitMsgData: Data?
var ukeyServerInitMsgData: Data?
// SecureMessage encryption keys
internal var decryptKey:[UInt8]?
internal var encryptKey:[UInt8]?
internal var recvHmacKey:SymmetricKey?
internal var sendHmacKey:SymmetricKey?
var decryptKey: [UInt8]?
var encryptKey: [UInt8]?
var recvHmacKey: SymmetricKey?
var sendHmacKey: SymmetricKey?
// SecureMessage sequence numbers
private var serverSeq:Int32=0
private var clientSeq:Int32=0
private(set) var pinCode:String?
init(connection:NWConnection, id:String) {
self.connection=connection
self.id=id
private var serverSeq: Int32 = 0
private var clientSeq: Int32 = 0
private(set) var pinCode: String?
init(connection: NWConnection, id: String) {
self.connection = connection
self.id = id
}
func start(){
connection.stateUpdateHandler={state in
func start() {
connection.stateUpdateHandler = { state in
if case .ready = state {
self.connectionReady()
self.receiveFrameAsync()
} else if case .failed(let err) = state {
self.lastError=err
} else if case let .failed(err) = state {
self.lastError = err
log("Error opening socket: \(err)")
self.handleConnectionClosure()
}
}
//connection.start(queue: .global(qos: .utility))
// connection.start(queue: .global(qos: .utility))
connection.start(queue: NearbyConnection.dispatchQueue)
}
func connectionReady(){}
internal func handleConnectionClosure(){
func connectionReady() {}
func handleConnectionClosure() {
log("Connection closed")
}
internal func protocolError(){
func protocolError() {
disconnect()
}
internal func processReceivedFrame(frameData:Data){
func processReceivedFrame(frameData _: Data) {
fatalError()
}
internal func processTransferSetupFrame(_ frame:Sharing_Nearby_Frame) throws{
func processTransferSetupFrame(_: Sharing_Nearby_Frame) throws {
fatalError()
}
internal func isServer() -> Bool{
func isServer() -> Bool {
fatalError()
}
internal func processFileChunk(frame:Location_Nearby_Connections_PayloadTransferFrame) throws{
func processFileChunk(frame _: Location_Nearby_Connections_PayloadTransferFrame) throws {
protocolError()
}
internal func processBytesPayload(payload:Data, id:Int64)throws ->Bool{
func processBytesPayload(payload _: Data, id _: Int64) throws -> Bool {
return false
}
private func receiveFrameAsync(){
connection.receive(minimumIncompleteLength: 4, maximumLength: 4) { content, contentContext, isComplete, error in
if self.connectionClosed{
private func receiveFrameAsync() {
connection.receive(minimumIncompleteLength: 4, maximumLength: 4) { content, _, isComplete, error in
if self.connectionClosed {
return
}
if isComplete{
if isComplete {
self.handleConnectionClosure()
return
}
if !(error==nil){
self.lastError=error
if !(error == nil) {
self.lastError = error
self.protocolError()
return
}
guard let content=content else {
guard let content = content else {
assertionFailure()
return
}
let frameLength:UInt32=UInt32(content[0]) << 24 | UInt32(content[1]) << 16 | UInt32(content[2]) << 8 | UInt32(content[3])
guard frameLength<NearbyConnection.SANE_FRAME_LENGTH else {
self.lastError=NearbyError.protocolError("Unexpected packet length")
let frameLength = UInt32(content[0]) << 24 | UInt32(content[1]) << 16 | UInt32(content[2]) << 8 | UInt32(content[3])
guard frameLength < NearbyConnection.SANE_FRAME_LENGTH else {
self.lastError = NearbyError.protocolError("Unexpected packet length")
self.protocolError()
return
}
self.receiveFrameAsync(length: frameLength)
}
}
private func receiveFrameAsync(length:UInt32){
connection.receive(minimumIncompleteLength: Int(length), maximumLength: Int(length)) { content, contentContext, isComplete, error in
if self.connectionClosed{
private func receiveFrameAsync(length: UInt32) {
connection.receive(minimumIncompleteLength: Int(length), maximumLength: Int(length)) { content, _, isComplete, _ in
if self.connectionClosed {
return
}
if isComplete{
if isComplete {
self.handleConnectionClosure()
return
}
guard let content=content else {
guard let content = content else {
self.protocolError()
return
}
@@ -140,39 +140,39 @@ class NearbyConnection{
self.receiveFrameAsync()
}
}
internal func sendFrameAsync(_ frame:Data, completion:(()->Void)?=nil){
if connectionClosed{
func sendFrameAsync(_ frame: Data, completion: (() -> Void)? = nil) {
if connectionClosed {
return
}
var lengthPrefixedData=Data(capacity: frame.count+4)
let length:Int=frame.count
var lengthPrefixedData = Data(capacity: frame.count + 4)
let length: Int = frame.count
lengthPrefixedData.append(contentsOf: [
UInt8(truncatingIfNeeded: length >> 24),
UInt8(truncatingIfNeeded: length >> 16),
UInt8(truncatingIfNeeded: length >> 8),
UInt8(truncatingIfNeeded: length)
UInt8(truncatingIfNeeded: length),
])
lengthPrefixedData.append(frame)
connection.send(content: lengthPrefixedData, completion: .contentProcessed({ error in
if let completion=completion{
connection.send(content: lengthPrefixedData, completion: .contentProcessed { _ in
if let completion = completion {
completion()
}
}))
})
}
internal func encryptAndSendOfflineFrame(_ frame:Location_Nearby_Connections_OfflineFrame, completion:(()->Void)?=nil) throws{
var d2dMsg=Securegcm_DeviceToDeviceMessage()
serverSeq+=1
d2dMsg.sequenceNumber=serverSeq
d2dMsg.message=try frame.serializedData()
let serializedMsg=[UInt8](try d2dMsg.serializedData())
let iv=Data.randomData(length: 16)
var encryptedData=Data(count: serializedMsg.count+16)
var encryptedLength:size_t=0
encryptedData.withUnsafeMutableBytes({
let status=CCCrypt(
func encryptAndSendOfflineFrame(_ frame: Location_Nearby_Connections_OfflineFrame, completion: (() -> Void)? = nil) throws {
var d2dMsg = Securegcm_DeviceToDeviceMessage()
serverSeq += 1
d2dMsg.sequenceNumber = serverSeq
d2dMsg.message = try frame.serializedData()
let serializedMsg = try [UInt8](d2dMsg.serializedData())
let iv = Data.randomData(length: 16)
var encryptedData = Data(count: serializedMsg.count + 16)
var encryptedLength: size_t = 0
encryptedData.withUnsafeMutableBytes {
let status = CCCrypt(
CCOperation(kCCEncrypt),
CCAlgorithm(kCCAlgorithmAES128),
CCOptions(kCCOptionPKCS7Padding),
@@ -182,69 +182,69 @@ class NearbyConnection{
$0.baseAddress, $0.count,
&encryptedLength
)
guard status==kCCSuccess else { fatalError("CCCrypt error: \(status)") }
})
var hb=Securemessage_HeaderAndBody()
hb.body=encryptedData.prefix(encryptedLength)
hb.header=Securemessage_Header()
guard status == kCCSuccess else { fatalError("CCCrypt error: \(status)") }
}
var hb = Securemessage_HeaderAndBody()
hb.body = encryptedData.prefix(encryptedLength)
hb.header = Securemessage_Header()
hb.header.encryptionScheme = .aes256Cbc
hb.header.signatureScheme = .hmacSha256
hb.header.iv=iv
var md=Securegcm_GcmMetadata()
hb.header.iv = iv
var md = Securegcm_GcmMetadata()
md.type = .deviceToDeviceMessage
md.version=1
hb.header.publicMetadata=try md.serializedData()
var smsg=Securemessage_SecureMessage()
smsg.headerAndBody=try hb.serializedData()
smsg.signature=Data(HMAC<SHA256>.authenticationCode(for: smsg.headerAndBody, using: sendHmacKey!))
sendFrameAsync(try smsg.serializedData(), completion: completion)
md.version = 1
hb.header.publicMetadata = try md.serializedData()
var smsg = Securemessage_SecureMessage()
smsg.headerAndBody = try hb.serializedData()
smsg.signature = Data(HMAC<SHA256>.authenticationCode(for: smsg.headerAndBody, using: sendHmacKey!))
try sendFrameAsync(smsg.serializedData(), completion: completion)
}
internal func sendTransferSetupFrame(_ frame:Sharing_Nearby_Frame) throws{
try sendBytesPayload(data: try frame.serializedData(), id: Int64.random(in: Int64.min...Int64.max))
func sendTransferSetupFrame(_ frame: Sharing_Nearby_Frame) throws {
try sendBytesPayload(data: frame.serializedData(), id: Int64.random(in: Int64.min ... Int64.max))
}
internal func sendBytesPayload(data:Data, id:Int64) throws{
var transfer=Location_Nearby_Connections_PayloadTransferFrame()
func sendBytesPayload(data: Data, id: Int64) throws {
var transfer = Location_Nearby_Connections_PayloadTransferFrame()
transfer.packetType = .data
transfer.payloadChunk.offset=0
transfer.payloadChunk.flags=0
transfer.payloadChunk.body=data
transfer.payloadHeader.id=id
transfer.payloadChunk.offset = 0
transfer.payloadChunk.flags = 0
transfer.payloadChunk.body = data
transfer.payloadHeader.id = id
transfer.payloadHeader.type = .bytes
transfer.payloadHeader.totalSize=Int64(transfer.payloadChunk.body.count)
transfer.payloadHeader.isSensitive=false
var wrapper=Location_Nearby_Connections_OfflineFrame()
transfer.payloadHeader.totalSize = Int64(transfer.payloadChunk.body.count)
transfer.payloadHeader.isSensitive = false
var wrapper = Location_Nearby_Connections_OfflineFrame()
wrapper.version = .v1
wrapper.v1=Location_Nearby_Connections_V1Frame()
wrapper.v1 = Location_Nearby_Connections_V1Frame()
wrapper.v1.type = .payloadTransfer
wrapper.v1.payloadTransfer=transfer
wrapper.v1.payloadTransfer = transfer
try encryptAndSendOfflineFrame(wrapper)
transfer.payloadChunk.flags=1 // .lastChunk
transfer.payloadChunk.offset=Int64(transfer.payloadChunk.body.count)
transfer.payloadChunk.flags = 1 // .lastChunk
transfer.payloadChunk.offset = Int64(transfer.payloadChunk.body.count)
transfer.payloadChunk.clearBody()
wrapper.v1.payloadTransfer=transfer
wrapper.v1.payloadTransfer = transfer
try encryptAndSendOfflineFrame(wrapper)
}
internal func decryptAndProcessReceivedSecureMessage(_ smsg:Securemessage_SecureMessage) throws{
func decryptAndProcessReceivedSecureMessage(_ smsg: Securemessage_SecureMessage) throws {
guard smsg.hasSignature, smsg.hasHeaderAndBody else { throw NearbyError.requiredFieldMissing("secureMessage.signature|headerAndBody") }
if NearbyConnectionManager.shared.checkSignature {
let hmac = Data(HMAC<SHA256>.authenticationCode(for: smsg.headerAndBody, using: recvHmacKey!))
guard hmac == smsg.signature else { throw NearbyError.protocolError("hmac!=signature: Expected \(hmac), got \(smsg.signature)") }
}
let headerAndBody=try Securemessage_HeaderAndBody(serializedBytes: smsg.headerAndBody)
var decryptedData=Data(count: headerAndBody.body.count)
var decryptedLength:Int=0
decryptedData.withUnsafeMutableBytes({
let status=CCCrypt(
let headerAndBody = try Securemessage_HeaderAndBody(serializedBytes: smsg.headerAndBody)
var decryptedData = Data(count: headerAndBody.body.count)
var decryptedLength = 0
decryptedData.withUnsafeMutableBytes {
let status = CCCrypt(
CCOperation(kCCDecrypt),
CCAlgorithm(kCCAlgorithmAES128),
CCOptions(kCCOptionPKCS7Padding),
@@ -254,209 +254,209 @@ class NearbyConnection{
$0.baseAddress, $0.count,
&decryptedLength
)
guard status==kCCSuccess else { fatalError("CCCrypt error: \(status)") }
})
decryptedData=decryptedData.prefix(decryptedLength)
let d2dMsg=try Securegcm_DeviceToDeviceMessage(serializedBytes: decryptedData)
guard status == kCCSuccess else { fatalError("CCCrypt error: \(status)") }
}
decryptedData = decryptedData.prefix(decryptedLength)
let d2dMsg = try Securegcm_DeviceToDeviceMessage(serializedBytes: decryptedData)
guard d2dMsg.hasMessage, d2dMsg.hasSequenceNumber else { throw NearbyError.requiredFieldMissing("d2dMessage.message|sequenceNumber") }
clientSeq+=1
guard d2dMsg.sequenceNumber==clientSeq else { throw NearbyError.protocolError("Wrong sequence number. Expected \(clientSeq), got \(d2dMsg.sequenceNumber)") }
let offlineFrame=try Location_Nearby_Connections_OfflineFrame(serializedBytes: d2dMsg.message)
if offlineFrame.hasV1 && offlineFrame.v1.hasType, case .payloadTransfer = offlineFrame.v1.type {
clientSeq += 1
guard d2dMsg.sequenceNumber == clientSeq else { throw NearbyError.protocolError("Wrong sequence number. Expected \(clientSeq), got \(d2dMsg.sequenceNumber)") }
let offlineFrame = try Location_Nearby_Connections_OfflineFrame(serializedBytes: d2dMsg.message)
if offlineFrame.hasV1, offlineFrame.v1.hasType, case .payloadTransfer = offlineFrame.v1.type {
guard offlineFrame.v1.hasPayloadTransfer else { throw NearbyError.requiredFieldMissing("offlineFrame.v1.payloadTransfer") }
let payloadTransfer=offlineFrame.v1.payloadTransfer
let header=payloadTransfer.payloadHeader;
let chunk=payloadTransfer.payloadChunk;
let payloadTransfer = offlineFrame.v1.payloadTransfer
let header = payloadTransfer.payloadHeader
let chunk = payloadTransfer.payloadChunk
guard header.hasType, header.hasID else { throw NearbyError.requiredFieldMissing("payloadHeader.type|id") }
guard payloadTransfer.hasPayloadChunk, chunk.hasOffset, chunk.hasFlags else { throw NearbyError.requiredFieldMissing("payloadTransfer.payloadChunk|offset|flags") }
if case .bytes = header.type{
let payloadID=header.id
if header.totalSize>InboundNearbyConnection.SANE_FRAME_LENGTH{
if case .bytes = header.type {
let payloadID = header.id
if header.totalSize > InboundNearbyConnection.SANE_FRAME_LENGTH {
payloadBuffers.removeValue(forKey: payloadID)
throw NearbyError.protocolError("Payload too large (\(header.totalSize) bytes)")
}
if payloadBuffers[payloadID]==nil {
payloadBuffers[payloadID]=NSMutableData(capacity: Int(header.totalSize))
if payloadBuffers[payloadID] == nil {
payloadBuffers[payloadID] = NSMutableData(capacity: Int(header.totalSize))
}
let buffer=payloadBuffers[payloadID]!
guard chunk.offset==buffer.count else {
let buffer = payloadBuffers[payloadID]!
guard chunk.offset == buffer.count else {
payloadBuffers.removeValue(forKey: payloadID)
throw NearbyError.protocolError("Unexpected chunk offset \(chunk.offset), expected \(buffer.count)")
}
if chunk.hasBody {
buffer.append(chunk.body)
}
if (chunk.flags & 1)==1 {
if (chunk.flags & 1) == 1 {
payloadBuffers.removeValue(forKey: payloadID)
if !(try processBytesPayload(payload: Data(buffer), id: payloadID)){
let innerFrame=try Sharing_Nearby_Frame(serializedBytes: buffer as Data)
if try !processBytesPayload(payload: Data(buffer), id: payloadID) {
let innerFrame = try Sharing_Nearby_Frame(serializedBytes: buffer as Data)
try processTransferSetupFrame(innerFrame)
}
}
}else if case .file = header.type{
} else if case .file = header.type {
try processFileChunk(frame: payloadTransfer)
}
}else if offlineFrame.hasV1 && offlineFrame.v1.hasType, case .keepAlive = offlineFrame.v1.type{
} else if offlineFrame.hasV1, offlineFrame.v1.hasType, case .keepAlive = offlineFrame.v1.type {
log("Sent keep-alive")
sendKeepAlive(ack: true)
}else{
} else {
log("Unhandled offline frame encrypted: \(offlineFrame)")
}
}
internal static func pinCodeFromAuthKey(_ key:SymmetricKey) -> String{
var hash:Int=0
var multiplier:Int=1
let keyBytes:[UInt8]=key.withUnsafeBytes({
return [UInt8]($0)
})
for _byte in keyBytes {
let byte=Int(Int8(bitPattern: _byte))
hash=(hash+byte*multiplier)%9973
multiplier=(multiplier*31)%9973
static func pinCodeFromAuthKey(_ key: SymmetricKey) -> String {
var hash = 0
var multiplier = 1
let keyBytes: [UInt8] = key.withUnsafeBytes {
[UInt8]($0)
}
for _byte in keyBytes {
let byte = Int(Int8(bitPattern: _byte))
hash = (hash + byte * multiplier) % 9973
multiplier = (multiplier * 31) % 9973
}
return String(format: "%04d", abs(hash))
}
internal static func hkdfExtract(salt:Data, ikm:Data) -> Data{
return HMAC<SHA256>.authenticationCode(for: ikm, using: SymmetricKey(data: salt)).withUnsafeBytes({return Data(bytes: $0.baseAddress!, count: $0.count)})
static func hkdfExtract(salt: Data, ikm: Data) -> Data {
return HMAC<SHA256>.authenticationCode(for: ikm, using: SymmetricKey(data: salt)).withUnsafeBytes { Data(bytes: $0.baseAddress!, count: $0.count) }
}
internal static func hkdfExpand(prk:Data, info:Data, length:Int) -> Data{
var okm=Data()
var t=Data()
var i=0
while okm.count<length{
i=i+1
let toDigest=t+info+Data([UInt8(truncatingIfNeeded: i)])
t=HMAC<SHA256>.authenticationCode(for: toDigest, using: SymmetricKey(data: prk)).withUnsafeBytes({return Data(bytes: $0.baseAddress!, count: $0.count)})
okm=okm+t
static func hkdfExpand(prk: Data, info: Data, length: Int) -> Data {
var okm = Data()
var t = Data()
var i = 0
while okm.count < length {
i = i + 1
let toDigest = t + info + Data([UInt8(truncatingIfNeeded: i)])
t = HMAC<SHA256>.authenticationCode(for: toDigest, using: SymmetricKey(data: prk)).withUnsafeBytes { Data(bytes: $0.baseAddress!, count: $0.count) }
okm = okm + t
}
return okm.subdata(in: 0..<length)
return okm.subdata(in: 0 ..< length)
}
internal static func hkdf(inputKeyMaterial:SymmetricKey, salt:Data, info:Data, outputByteCount:Int) -> SymmetricKey{
if #available(macOS 11.0, *){
static func hkdf(inputKeyMaterial: SymmetricKey, salt: Data, info: Data, outputByteCount: Int) -> SymmetricKey {
if #available(macOS 11.0, *) {
return HKDF<SHA256>.deriveKey(inputKeyMaterial: inputKeyMaterial, salt: salt, info: info, outputByteCount: outputByteCount)
}else{
return SymmetricKey(data: hkdfExpand(prk: hkdfExtract(salt: salt, ikm: inputKeyMaterial.withUnsafeBytes({return Data(bytes: $0.baseAddress!, count: $0.count)})), info: info, length: outputByteCount))
} else {
return SymmetricKey(data: hkdfExpand(prk: hkdfExtract(salt: salt, ikm: inputKeyMaterial.withUnsafeBytes { Data(bytes: $0.baseAddress!, count: $0.count) }), info: info, length: outputByteCount))
}
}
internal func finalizeKeyExchange(peerKey:Securemessage_GenericPublicKey) throws{
func finalizeKeyExchange(peerKey: Securemessage_GenericPublicKey) throws {
guard peerKey.hasEcP256PublicKey else { throw NearbyError.requiredFieldMissing("peerKey.ecP256PublicKey") }
let domain=Domain.instance(curve: .EC256r1)
var clientX=peerKey.ecP256PublicKey.x
var clientY=peerKey.ecP256PublicKey.y
if clientX.count>32{
clientX=clientX.suffix(32)
let domain = Domain.instance(curve: .EC256r1)
var clientX = peerKey.ecP256PublicKey.x
var clientY = peerKey.ecP256PublicKey.y
if clientX.count > 32 {
clientX = clientX.suffix(32)
}
if clientY.count>32{
clientY=clientY.suffix(32)
if clientY.count > 32 {
clientY = clientY.suffix(32)
}
let key=try ECPublicKey(domain: domain, w: Point(BInt(magnitude: [UInt8](clientX)), BInt(magnitude: [UInt8](clientY))))
let dhs=(try privateKey?.domain.multiplyPoint(key.w, privateKey!.s).x.asMagnitudeBytes())!
var sha=SHA256()
let key = try ECPublicKey(domain: domain, w: Point(BInt(magnitude: [UInt8](clientX)), BInt(magnitude: [UInt8](clientY))))
let dhs = try (privateKey?.domain.multiplyPoint(key.w, privateKey!.s).x.asMagnitudeBytes())!
var sha = SHA256()
sha.update(data: dhs)
let derivedSecretKey=Data(sha.finalize())
var ukeyInfo=Data()
let derivedSecretKey = Data(sha.finalize())
var ukeyInfo = Data()
ukeyInfo.append(ukeyClientInitMsgData!)
ukeyInfo.append(ukeyServerInitMsgData!)
let authString=NearbyConnection.hkdf(inputKeyMaterial: SymmetricKey(data: derivedSecretKey), salt: "UKEY2 v1 auth".data(using: .utf8)!, info: ukeyInfo, outputByteCount: 32)
let nextSecret=NearbyConnection.hkdf(inputKeyMaterial: SymmetricKey(data: derivedSecretKey), salt: "UKEY2 v1 next".data(using: .utf8)!, info: ukeyInfo, outputByteCount: 32)
pinCode=NearbyConnection.pinCodeFromAuthKey(authString)
let salt:Data=Data([0x82, 0xAA, 0x55, 0xA0, 0xD3, 0x97, 0xF8, 0x83, 0x46, 0xCA, 0x1C,
0xEE, 0x8D, 0x39, 0x09, 0xB9, 0x5F, 0x13, 0xFA, 0x7D, 0xEB, 0x1D,
0x4A, 0xB3, 0x83, 0x76, 0xB8, 0x25, 0x6D, 0xA8, 0x55, 0x10])
let d2dClientKey=NearbyConnection.hkdf(inputKeyMaterial: nextSecret, salt: salt, info: "client".data(using: .utf8)!, outputByteCount: 32)
let d2dServerKey=NearbyConnection.hkdf(inputKeyMaterial: nextSecret, salt: salt, info: "server".data(using: .utf8)!, outputByteCount: 32)
sha=SHA256()
let authString = NearbyConnection.hkdf(inputKeyMaterial: SymmetricKey(data: derivedSecretKey), salt: "UKEY2 v1 auth".data(using: .utf8)!, info: ukeyInfo, outputByteCount: 32)
let nextSecret = NearbyConnection.hkdf(inputKeyMaterial: SymmetricKey(data: derivedSecretKey), salt: "UKEY2 v1 next".data(using: .utf8)!, info: ukeyInfo, outputByteCount: 32)
pinCode = NearbyConnection.pinCodeFromAuthKey(authString)
let salt = Data([0x82, 0xAA, 0x55, 0xA0, 0xD3, 0x97, 0xF8, 0x83, 0x46, 0xCA, 0x1C,
0xEE, 0x8D, 0x39, 0x09, 0xB9, 0x5F, 0x13, 0xFA, 0x7D, 0xEB, 0x1D,
0x4A, 0xB3, 0x83, 0x76, 0xB8, 0x25, 0x6D, 0xA8, 0x55, 0x10])
let d2dClientKey = NearbyConnection.hkdf(inputKeyMaterial: nextSecret, salt: salt, info: "client".data(using: .utf8)!, outputByteCount: 32)
let d2dServerKey = NearbyConnection.hkdf(inputKeyMaterial: nextSecret, salt: salt, info: "server".data(using: .utf8)!, outputByteCount: 32)
sha = SHA256()
sha.update(data: "SecureMessage".data(using: .utf8)!)
let smsgSalt=Data(sha.finalize())
let clientKey=NearbyConnection.hkdf(inputKeyMaterial: d2dClientKey, salt: smsgSalt, info: "ENC:2".data(using: .utf8)!, outputByteCount: 32).withUnsafeBytes({return [UInt8]($0)})
let clientHmacKey=NearbyConnection.hkdf(inputKeyMaterial: d2dClientKey, salt: smsgSalt, info: "SIG:1".data(using: .utf8)!, outputByteCount: 32)
let serverKey=NearbyConnection.hkdf(inputKeyMaterial: d2dServerKey, salt: smsgSalt, info: "ENC:2".data(using: .utf8)!, outputByteCount: 32).withUnsafeBytes({return [UInt8]($0)})
let serverHmacKey=NearbyConnection.hkdf(inputKeyMaterial: d2dServerKey, salt: smsgSalt, info: "SIG:1".data(using: .utf8)!, outputByteCount: 32)
if isServer(){
decryptKey=clientKey
recvHmacKey=clientHmacKey
encryptKey=serverKey
sendHmacKey=serverHmacKey
}else{
decryptKey=serverKey
recvHmacKey=serverHmacKey
encryptKey=clientKey
sendHmacKey=clientHmacKey
let smsgSalt = Data(sha.finalize())
let clientKey = NearbyConnection.hkdf(inputKeyMaterial: d2dClientKey, salt: smsgSalt, info: "ENC:2".data(using: .utf8)!, outputByteCount: 32).withUnsafeBytes { [UInt8]($0) }
let clientHmacKey = NearbyConnection.hkdf(inputKeyMaterial: d2dClientKey, salt: smsgSalt, info: "SIG:1".data(using: .utf8)!, outputByteCount: 32)
let serverKey = NearbyConnection.hkdf(inputKeyMaterial: d2dServerKey, salt: smsgSalt, info: "ENC:2".data(using: .utf8)!, outputByteCount: 32).withUnsafeBytes { [UInt8]($0) }
let serverHmacKey = NearbyConnection.hkdf(inputKeyMaterial: d2dServerKey, salt: smsgSalt, info: "SIG:1".data(using: .utf8)!, outputByteCount: 32)
if isServer() {
decryptKey = clientKey
recvHmacKey = clientHmacKey
encryptKey = serverKey
sendHmacKey = serverHmacKey
} else {
decryptKey = serverKey
recvHmacKey = serverHmacKey
encryptKey = clientKey
sendHmacKey = clientHmacKey
}
}
internal func disconnect(){
connection.send(content: nil, isComplete: true, completion: .contentProcessed({ error in
func disconnect() {
connection.send(content: nil, isComplete: true, completion: .contentProcessed { _ in
self.handleConnectionClosure()
}))
connectionClosed=true
})
connectionClosed = true
}
internal func sendDisconnectionAndDisconnect() throws{
var offlineFrame=Location_Nearby_Connections_OfflineFrame()
func sendDisconnectionAndDisconnect() throws {
var offlineFrame = Location_Nearby_Connections_OfflineFrame()
offlineFrame.version = .v1
offlineFrame.v1.type = .disconnection
offlineFrame.v1.disconnection=Location_Nearby_Connections_DisconnectionFrame()
if encryptionDone{
offlineFrame.v1.disconnection = Location_Nearby_Connections_DisconnectionFrame()
if encryptionDone {
try encryptAndSendOfflineFrame(offlineFrame)
}else{
sendFrameAsync(try offlineFrame.serializedData())
} else {
try sendFrameAsync(offlineFrame.serializedData())
}
disconnect()
}
internal func sendUkey2Alert(type:Securegcm_Ukey2Alert.AlertType){
var alert=Securegcm_Ukey2Alert()
alert.type=type
var msg=Securegcm_Ukey2Message()
func sendUkey2Alert(type: Securegcm_Ukey2Alert.AlertType) {
var alert = Securegcm_Ukey2Alert()
alert.type = type
var msg = Securegcm_Ukey2Message()
msg.messageType = .alert
msg.messageData = try! alert.serializedData()
sendFrameAsync(try! msg.serializedData())
disconnect()
}
internal func sendKeepAlive(ack:Bool){
var offlineFrame=Location_Nearby_Connections_OfflineFrame()
func sendKeepAlive(ack: Bool) {
var offlineFrame = Location_Nearby_Connections_OfflineFrame()
offlineFrame.version = .v1
offlineFrame.v1.type = .keepAlive
offlineFrame.v1.keepAlive.ack=ack
do{
if encryptionDone{
offlineFrame.v1.keepAlive.ack = ack
do {
if encryptionDone {
try encryptAndSendOfflineFrame(offlineFrame)
}else{
sendFrameAsync(try offlineFrame.serializedData())
} else {
try sendFrameAsync(offlineFrame.serializedData())
}
}catch{
} catch {
log("Error sending KEEP_ALIVE: \(error)")
}
}
}
struct InternalFileInfo{
let meta:FileMetadata
let payloadID:Int64
let destinationURL:URL
var bytesTransferred:Int64=0
var fileHandle:FileHandle?
var progress:Progress?
var created:Bool=false
struct InternalFileInfo {
let meta: FileMetadata
let payloadID: Int64
let destinationURL: URL
var bytesTransferred: Int64 = 0
var fileHandle: FileHandle?
var progress: Progress?
var created: Bool = false
}
+313 -320
View File
@@ -9,147 +9,145 @@ import Foundation
import Network
import System
public struct RemoteDeviceInfo{
public let name:String
public let type:DeviceType
public var id:String?
init(name: String, type: DeviceType, id: String? = nil) {
self.name = name
self.type = type
self.id = id
}
init(info:EndpointInfo){
self.name=info.name
self.type=info.deviceType
}
public enum DeviceType:Int32{
case unknown=0
case phone
case tablet
case computer
public static func fromRawValue(value:Int) -> DeviceType{
switch value {
case 0:
return .unknown
case 1:
return .phone
case 2:
return .tablet
case 3:
return .computer
default:
return .unknown
}
}
}
public struct RemoteDeviceInfo {
public let name: String
public let type: DeviceType
public var id: String?
init(name: String, type: DeviceType, id: String? = nil) {
self.name = name
self.type = type
self.id = id
}
init(info: EndpointInfo) {
name = info.name
type = info.deviceType
}
public enum DeviceType: Int32 {
case unknown = 0
case phone
case tablet
case computer
public static func fromRawValue(value: Int) -> DeviceType {
switch value {
case 0:
return .unknown
case 1:
return .phone
case 2:
return .tablet
case 3:
return .computer
default:
return .unknown
}
}
}
}
public enum NearbyError: Error {
case protocolError(_ message: String)
case requiredFieldMissing(_ message: String)
case ukey2
case inputOutput
case canceled(reason: CancellationReason)
public enum NearbyError:Error{
case protocolError(_ message:String)
case requiredFieldMissing(_ message:String)
case ukey2
case inputOutput
case canceled(reason:CancellationReason)
public enum CancellationReason{
case userRejected, userCanceled, notEnoughSpace, unsupportedType, timedOut
}
public enum CancellationReason {
case userRejected, userCanceled, notEnoughSpace, unsupportedType, timedOut
}
}
public struct TransferMetadata{
public let files:[FileMetadata]
public let id:String
public let pinCode:String?
public let textDescription:String?
init(files: [FileMetadata], id: String, pinCode: String?, textDescription: String?=nil){
self.files = files
self.id = id
self.pinCode = pinCode
self.textDescription = textDescription
}
public struct TransferMetadata {
public let files: [FileMetadata]
public let id: String
public let pinCode: String?
public let textDescription: String?
init(files: [FileMetadata], id: String, pinCode: String?, textDescription: String? = nil) {
self.files = files
self.id = id
self.pinCode = pinCode
self.textDescription = textDescription
}
}
public struct FileMetadata{
public let name:String
public let size:Int64
public let mimeType:String
public struct FileMetadata {
public let name: String
public let size: Int64
public let mimeType: String
}
struct FoundServiceInfo{
let service:NWBrowser.Result
var device:RemoteDeviceInfo?
struct FoundServiceInfo {
let service: NWBrowser.Result
var device: RemoteDeviceInfo?
}
struct OutgoingTransferInfo{
let service:NWBrowser.Result
let device:RemoteDeviceInfo
let connection:OutboundNearbyConnection
let delegate:ShareExtensionDelegate
struct OutgoingTransferInfo {
let service: NWBrowser.Result
let device: RemoteDeviceInfo
let connection: OutboundNearbyConnection
let delegate: ShareExtensionDelegate
}
struct EndpointInfo{
let name:String
let deviceType:RemoteDeviceInfo.DeviceType
init(name: String, deviceType: RemoteDeviceInfo.DeviceType){
self.name = name
self.deviceType = deviceType
}
init?(data:Data){
guard data.count>17 else {return nil}
let deviceNameLength=Int(data[17])
guard data.count>=deviceNameLength+18 else {return nil}
guard let deviceName=String(data: data[18..<(18+deviceNameLength)], encoding: .utf8) else {return nil}
let rawDeviceType:Int=Int(data[0] & 7) >> 1
self.name=deviceName
self.deviceType=RemoteDeviceInfo.DeviceType.fromRawValue(value: rawDeviceType)
}
func serialize()->Data{
// 1 byte: Version(3 bits)|Visibility(1 bit)|Device Type(3 bits)|Reserved(1 bits)
// Device types: unknown=0, phone=1, tablet=2, laptop=3
var endpointInfo:[UInt8]=[UInt8(deviceType.rawValue << 1)]
// 16 bytes: unknown random bytes
for _ in 0...15{
endpointInfo.append(UInt8.random(in: 0...255))
}
// Device name in UTF-8 prefixed with 1-byte length
var nameChars=[UInt8](name.utf8)
if nameChars.count>255{
nameChars=[UInt8](nameChars[0..<255])
}
endpointInfo.append(UInt8(nameChars.count))
for ch in nameChars{
endpointInfo.append(UInt8(ch))
}
return Data(endpointInfo)
}
struct EndpointInfo {
let name: String
let deviceType: RemoteDeviceInfo.DeviceType
init(name: String, deviceType: RemoteDeviceInfo.DeviceType) {
self.name = name
self.deviceType = deviceType
}
init?(data: Data) {
guard data.count > 17 else { return nil }
let deviceNameLength = Int(data[17])
guard data.count >= deviceNameLength + 18 else { return nil }
guard let deviceName = String(data: data[18 ..< (18 + deviceNameLength)], encoding: .utf8) else { return nil }
let rawDeviceType = Int(data[0] & 7) >> 1
name = deviceName
deviceType = RemoteDeviceInfo.DeviceType.fromRawValue(value: rawDeviceType)
}
func serialize() -> Data {
// 1 byte: Version(3 bits)|Visibility(1 bit)|Device Type(3 bits)|Reserved(1 bits)
// Device types: unknown=0, phone=1, tablet=2, laptop=3
var endpointInfo: [UInt8] = [UInt8(deviceType.rawValue << 1)]
// 16 bytes: unknown random bytes
for _ in 0 ... 15 {
endpointInfo.append(UInt8.random(in: 0 ... 255))
}
// Device name in UTF-8 prefixed with 1-byte length
var nameChars = [UInt8](name.utf8)
if nameChars.count > 255 {
nameChars = [UInt8](nameChars[0 ..< 255])
}
endpointInfo.append(UInt8(nameChars.count))
for ch in nameChars {
endpointInfo.append(UInt8(ch))
}
return Data(endpointInfo)
}
}
public protocol MainAppDelegate{
func obtainUserConsent(for transfer:TransferMetadata, from device:RemoteDeviceInfo)
func incomingTransfer(id:String, didFinishWith error:Error?)
public protocol MainAppDelegate {
func obtainUserConsent(for transfer: TransferMetadata, from device: RemoteDeviceInfo)
func incomingTransfer(id: String, didFinishWith error: Error?)
}
public protocol ShareExtensionDelegate:AnyObject{
func addDevice(device:RemoteDeviceInfo)
func removeDevice(id:String)
func connectionWasEstablished(pinCode:String)
func connectionFailed(with error:Error)
func transferAccepted()
func transferProgress(progress:Double)
func transferFinished()
public protocol ShareExtensionDelegate: AnyObject {
func addDevice(device: RemoteDeviceInfo)
func removeDevice(id: String)
func connectionWasEstablished(pinCode: String)
func connectionFailed(with error: Error)
func transferAccepted()
func transferProgress(progress: Double)
func transferFinished()
}
public class NearbyConnectionManager : NSObject, NetServiceDelegate, InboundNearbyConnectionDelegate, OutboundNearbyConnectionDelegate{
public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearbyConnectionDelegate, OutboundNearbyConnectionDelegate {
public var checkSignature = true {
didSet {
if !checkSignature {
@@ -157,127 +155,125 @@ public class NearbyConnectionManager : NSObject, NetServiceDelegate, InboundNear
}
}
}
private var tcpListener:NWListener;
public let endpointID:[UInt8]=generateEndpointID()
private var tcpListener: NWListener
public let endpointID: [UInt8] = generateEndpointID()
private var mdnsServices: [NetService] = []
private var activeConnections:[String:InboundNearbyConnection]=[:]
private var foundServices:[String:FoundServiceInfo]=[:]
private var shareExtensionDelegates:[ShareExtensionDelegate]=[]
private var outgoingTransfers:[String:OutgoingTransferInfo]=[:]
public var mainAppDelegate:(any MainAppDelegate)?
private var discoveryRefCount=0
private var activeConnections: [String: InboundNearbyConnection] = [:]
private var foundServices: [String: FoundServiceInfo] = [:]
private var shareExtensionDelegates: [ShareExtensionDelegate] = []
private var outgoingTransfers: [String: OutgoingTransferInfo] = [:]
public var mainAppDelegate: (any MainAppDelegate)?
private var discoveryRefCount = 0
let serviceTypes = ["_FC9F5ED42C8A._tcp."]
private var browsers: [NWBrowser] = []
public static let shared=NearbyConnectionManager()
override init() {
tcpListener=try! NWListener(using: NWParameters(tls: .none))
super.init()
}
public func becomeVisible(){
startTCPListener()
}
private func startTCPListener(){
tcpListener.stateUpdateHandler={(state:NWListener.State) in
if case .ready = state {
self.initMDNS()
}
}
tcpListener.newConnectionHandler={(connection:NWConnection) in
let id=UUID().uuidString
let conn=InboundNearbyConnection(connection: connection, id: id)
self.activeConnections[id]=conn
conn.delegate=self
conn.start()
}
tcpListener.start(queue: .global(qos: .utility))
}
public static let shared = NearbyConnectionManager()
override init() {
tcpListener = try! NWListener(using: NWParameters(tls: .none))
super.init()
}
public func becomeVisible() {
startTCPListener()
}
private func startTCPListener() {
tcpListener.stateUpdateHandler = { (state: NWListener.State) in
if case .ready = state {
self.initMDNS()
}
}
tcpListener.newConnectionHandler = { (connection: NWConnection) in
let id = UUID().uuidString
let conn = InboundNearbyConnection(connection: connection, id: id)
self.activeConnections[id] = conn
conn.delegate = self
conn.start()
}
tcpListener.start(queue: .global(qos: .utility))
}
private static func generateEndpointID() -> [UInt8] {
let userDefaultsKey = UserDefaultsKeys.endpointID.rawValue
// Try to retrieve from UserDefaults
if let savedString = UserDefaults.standard.string(forKey: userDefaultsKey),
let savedData = savedString.data(using: .utf8) {
let savedData = savedString.data(using: .utf8)
{
return [UInt8](savedData)
}
// Generate a new random ID
var id: [UInt8] = []
let alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".compactMap { UInt8($0.asciiValue!) }
for _ in 0..<4 { // use 0..<4 instead of 0...3 for clarity
id.append(alphabet[Int.random(in: 0..<alphabet.count)])
for _ in 0 ..< 4 { // use 0..<4 instead of 0...3 for clarity
id.append(alphabet[Int.random(in: 0 ..< alphabet.count)])
}
// Save to UserDefaults as String
let idString = String(bytes: id, encoding: .utf8) ?? ""
UserDefaults.standard.set(idString, forKey: userDefaultsKey)
return id
}
private func initMDNS() {
let nameBytes: [UInt8] = [
0x23, // PCP
endpointID[0], endpointID[1], endpointID[2], endpointID[3],
0xFC, 0x9F, 0x5E, // Service ID hash
0, 0
0, 0,
]
let name = Data(nameBytes).urlSafeBase64EncodedString()
let endpointInfo = EndpointInfo(name: Host.current().localizedName!, deviceType: .computer)
let port: Int32 = Int32(tcpListener.port!.rawValue)
let port = Int32(tcpListener.port!.rawValue)
mdnsServices = serviceTypes.map { serviceType in
let service = NetService(domain: "", type: serviceType, name: name, port: port)
service.delegate = self
service.setTXTRecord(NetService.data(fromTXTRecord: [
"n": endpointInfo.serialize().urlSafeBase64EncodedString().data(using: .utf8)!
"n": endpointInfo.serialize().urlSafeBase64EncodedString().data(using: .utf8)!,
]))
service.publish()
return service
}
}
func obtainUserConsent(for transfer: TransferMetadata, from device: RemoteDeviceInfo, connection: InboundNearbyConnection) {
guard let delegate=mainAppDelegate else {return}
delegate.obtainUserConsent(for: transfer, from: device)
}
func connectionWasTerminated(connection:InboundNearbyConnection, error:Error?){
guard let delegate=mainAppDelegate else {return}
delegate.incomingTransfer(id: connection.id, didFinishWith: error)
activeConnections.removeValue(forKey: connection.id)
}
public func submitUserConsent(transferID:String, accept:Bool, storeInTemp: Bool = false){
guard let conn=activeConnections[transferID] else {return}
func obtainUserConsent(for transfer: TransferMetadata, from device: RemoteDeviceInfo, connection _: InboundNearbyConnection) {
guard let delegate = mainAppDelegate else { return }
delegate.obtainUserConsent(for: transfer, from: device)
}
func connectionWasTerminated(connection: InboundNearbyConnection, error: Error?) {
guard let delegate = mainAppDelegate else { return }
delegate.incomingTransfer(id: connection.id, didFinishWith: error)
activeConnections.removeValue(forKey: connection.id)
}
public func submitUserConsent(transferID: String, accept: Bool, storeInTemp: Bool = false) {
guard let conn = activeConnections[transferID] else { return }
conn.submitUserConsent(accepted: accept, storeInTemp: storeInTemp)
}
}
public func startDeviceDiscovery() {
log("Device discovery requested.")
if discoveryRefCount == 0 {
foundServices.removeAll()
log("Starting device discovery")
if browsers.isEmpty {
for type in serviceTypes {
log("Starting browser for type \(type)")
let browser = NWBrowser(for: .bonjourWithTXTRecord(type: type, domain: nil), using: .tcp)
browser.browseResultsChangedHandler = { newResults, changes in
browser.browseResultsChangedHandler = { _, changes in
for change in changes {
switch change {
case let .added(res):
@@ -286,7 +282,6 @@ public class NearbyConnectionManager : NSObject, NetServiceDelegate, InboundNear
self.removeFoundDevice(service: res)
default:
log("Ignoring change \(change)")
break
}
}
}
@@ -298,138 +293,136 @@ public class NearbyConnectionManager : NSObject, NetServiceDelegate, InboundNear
discoveryRefCount += 1
}
public func stopDeviceDiscovery(){
discoveryRefCount -= 1
assert(discoveryRefCount >= 0)
public func stopDeviceDiscovery() {
discoveryRefCount -= 1
assert(discoveryRefCount >= 0)
if discoveryRefCount == 0 {
for browser in browsers {
browser.cancel()
}
browsers.removeAll()
}
}
public func addShareExtensionDelegate(_ delegate:ShareExtensionDelegate){
shareExtensionDelegates.append(delegate)
for service in foundServices.values{
guard let device=service.device else {continue}
delegate.addDevice(device: device)
}
}
public func removeShareExtensionDelegate(_ delegate:ShareExtensionDelegate){
shareExtensionDelegates.removeAll(where: {$0===delegate})
}
public func cancelOutgoingTransfer(id:String){
guard let transfer=outgoingTransfers[id] else {return}
transfer.connection.cancel()
}
private func endpointID(for service:NWBrowser.Result)->String?{
guard case let NWEndpoint.service(name: serviceName, type: _, domain: _, interface: _)=service.endpoint else {return nil}
guard let nameData=Data.dataFromUrlSafeBase64(serviceName) else {return nil}
guard nameData.count>=10 else {return nil}
let pcp=nameData[0]
guard pcp==0x23 else {return nil}
let endpointID=String(data: nameData.subdata(in: 1..<5), encoding: .ascii)!
let serviceIDHash=nameData.subdata(in: 5..<8)
guard serviceIDHash==Data([0xFC, 0x9F, 0x5E]) else {return nil}
return endpointID
}
private func addFoundDevice(service:NWBrowser.Result){
}
}
public func addShareExtensionDelegate(_ delegate: ShareExtensionDelegate) {
shareExtensionDelegates.append(delegate)
for service in foundServices.values {
guard let device = service.device else { continue }
delegate.addDevice(device: device)
}
}
public func removeShareExtensionDelegate(_ delegate: ShareExtensionDelegate) {
shareExtensionDelegates.removeAll(where: { $0 === delegate })
}
public func cancelOutgoingTransfer(id: String) {
guard let transfer = outgoingTransfers[id] else { return }
transfer.connection.cancel()
}
private func endpointID(for service: NWBrowser.Result) -> String? {
guard case let NWEndpoint.service(name: serviceName, type: _, domain: _, interface: _) = service.endpoint else { return nil }
guard let nameData = Data.dataFromUrlSafeBase64(serviceName) else { return nil }
guard nameData.count >= 10 else { return nil }
let pcp = nameData[0]
guard pcp == 0x23 else { return nil }
let endpointID = String(data: nameData.subdata(in: 1 ..< 5), encoding: .ascii)!
let serviceIDHash = nameData.subdata(in: 5 ..< 8)
guard serviceIDHash == Data([0xFC, 0x9F, 0x5E]) else { return nil }
return endpointID
}
private func addFoundDevice(service: NWBrowser.Result) {
log("found service \(service)")
for interface in service.interfaces{
if case .loopback=interface.type{
for interface in service.interfaces {
if case .loopback = interface.type {
log("ignoring localhost service")
return
}
}
guard let endpointID=endpointID(for: service) else {return}
return
}
}
guard let endpointID = endpointID(for: service) else { return }
log("service name is valid, endpoint ID \(endpointID)")
var foundService=FoundServiceInfo(service: service)
guard case let NWBrowser.Result.Metadata.bonjour(txtRecord)=service.metadata else {return}
guard let endpointInfoEncoded=txtRecord.dictionary["n"] else {return}
guard let endpointInfo=Data.dataFromUrlSafeBase64(endpointInfoEncoded) else {return}
guard endpointInfo.count>=19 else {return}
let deviceType=RemoteDeviceInfo.DeviceType.fromRawValue(value: (Int(endpointInfo[0]) >> 1) & 7)
let deviceNameLength=Int(endpointInfo[17])
guard endpointInfo.count>=deviceNameLength+17 else {return}
guard let deviceName=String(data: endpointInfo.subdata(in: 18..<(18+deviceNameLength)), encoding: .utf8) else {return}
let deviceInfo=RemoteDeviceInfo(name: deviceName, type: deviceType, id: endpointID)
foundService.device=deviceInfo
foundServices[endpointID]=foundService
for delegate in shareExtensionDelegates{
delegate.addDevice(device: deviceInfo)
}
}
private func removeFoundDevice(service:NWBrowser.Result){
guard let endpointID=endpointID(for: service) else {return}
guard let _=foundServices.removeValue(forKey: endpointID) else {return}
for delegate in shareExtensionDelegates {
delegate.removeDevice(id: endpointID)
}
}
public func startOutgoingTransfer(deviceID:String, delegate:ShareExtensionDelegate, urls:[URL]){
guard let info=foundServices[deviceID] else {return}
let tcp=NWProtocolTCP.Options.init()
tcp.noDelay=true
let nwconn=NWConnection(to: info.service.endpoint, using: NWParameters(tls: .none, tcp: tcp))
let conn=OutboundNearbyConnection(connection: nwconn, id: deviceID, urlsToSend: urls)
conn.delegate=self
let transfer=OutgoingTransferInfo(service: info.service, device: info.device!, connection: conn, delegate: delegate)
outgoingTransfers[deviceID]=transfer
conn.start()
}
func outboundConnectionWasEstablished(connection: OutboundNearbyConnection) {
guard let transfer=outgoingTransfers[connection.id] else {return}
DispatchQueue.main.async {
transfer.delegate.connectionWasEstablished(pinCode: connection.pinCode!)
}
}
func outboundConnectionTransferAccepted(connection: OutboundNearbyConnection) {
guard let transfer=outgoingTransfers[connection.id] else {return}
DispatchQueue.main.async {
transfer.delegate.transferAccepted()
}
}
func outboundConnection(connection: OutboundNearbyConnection, transferProgress: Double) {
guard let transfer=outgoingTransfers[connection.id] else {return}
DispatchQueue.main.async {
transfer.delegate.transferProgress(progress: transferProgress)
}
}
func outboundConnection(connection: OutboundNearbyConnection, failedWithError: Error) {
guard let transfer=outgoingTransfers[connection.id] else {return}
DispatchQueue.main.async {
transfer.delegate.connectionFailed(with: failedWithError)
}
outgoingTransfers.removeValue(forKey: connection.id)
}
func outboundConnectionTransferFinished(connection: OutboundNearbyConnection) {
guard let transfer=outgoingTransfers[connection.id] else {return}
DispatchQueue.main.async {
transfer.delegate.transferFinished()
}
outgoingTransfers.removeValue(forKey: connection.id)
}
var foundService = FoundServiceInfo(service: service)
guard case let NWBrowser.Result.Metadata.bonjour(txtRecord) = service.metadata else { return }
guard let endpointInfoEncoded = txtRecord.dictionary["n"] else { return }
guard let endpointInfo = Data.dataFromUrlSafeBase64(endpointInfoEncoded) else { return }
guard endpointInfo.count >= 19 else { return }
let deviceType = RemoteDeviceInfo.DeviceType.fromRawValue(value: (Int(endpointInfo[0]) >> 1) & 7)
let deviceNameLength = Int(endpointInfo[17])
guard endpointInfo.count >= deviceNameLength + 17 else { return }
guard let deviceName = String(data: endpointInfo.subdata(in: 18 ..< (18 + deviceNameLength)), encoding: .utf8) else { return }
let deviceInfo = RemoteDeviceInfo(name: deviceName, type: deviceType, id: endpointID)
foundService.device = deviceInfo
foundServices[endpointID] = foundService
for delegate in shareExtensionDelegates {
delegate.addDevice(device: deviceInfo)
}
}
private func removeFoundDevice(service: NWBrowser.Result) {
guard let endpointID = endpointID(for: service) else { return }
guard let _ = foundServices.removeValue(forKey: endpointID) else { return }
for delegate in shareExtensionDelegates {
delegate.removeDevice(id: endpointID)
}
}
public func startOutgoingTransfer(deviceID: String, delegate: ShareExtensionDelegate, urls: [URL]) {
guard let info = foundServices[deviceID] else { return }
let tcp = NWProtocolTCP.Options()
tcp.noDelay = true
let nwconn = NWConnection(to: info.service.endpoint, using: NWParameters(tls: .none, tcp: tcp))
let conn = OutboundNearbyConnection(connection: nwconn, id: deviceID, urlsToSend: urls)
conn.delegate = self
let transfer = OutgoingTransferInfo(service: info.service, device: info.device!, connection: conn, delegate: delegate)
outgoingTransfers[deviceID] = transfer
conn.start()
}
func outboundConnectionWasEstablished(connection: OutboundNearbyConnection) {
guard let transfer = outgoingTransfers[connection.id] else { return }
DispatchQueue.main.async {
transfer.delegate.connectionWasEstablished(pinCode: connection.pinCode!)
}
}
func outboundConnectionTransferAccepted(connection: OutboundNearbyConnection) {
guard let transfer = outgoingTransfers[connection.id] else { return }
DispatchQueue.main.async {
transfer.delegate.transferAccepted()
}
}
func outboundConnection(connection: OutboundNearbyConnection, transferProgress: Double) {
guard let transfer = outgoingTransfers[connection.id] else { return }
DispatchQueue.main.async {
transfer.delegate.transferProgress(progress: transferProgress)
}
}
func outboundConnection(connection: OutboundNearbyConnection, failedWithError: Error) {
guard let transfer = outgoingTransfers[connection.id] else { return }
DispatchQueue.main.async {
transfer.delegate.connectionFailed(with: failedWithError)
}
outgoingTransfers.removeValue(forKey: connection.id)
}
func outboundConnectionTransferFinished(connection: OutboundNearbyConnection) {
guard let transfer = outgoingTransfers[connection.id] else { return }
DispatchQueue.main.async {
transfer.delegate.transferFinished()
}
outgoingTransfers.removeValue(forKey: connection.id)
}
public func getActiveConnectionsCount() -> Int {
return activeConnections.count
}
}
+417 -417
View File
@@ -5,433 +5,433 @@
// Created by Grishka on 23.09.2023.
//
import CommonCrypto
import CryptoKit
import Foundation
import Network
import CryptoKit
import CommonCrypto
import System
import UniformTypeIdentifiers
import SwiftECC
import BigInt
import SwiftECC
class OutboundNearbyConnection:NearbyConnection{
private var currentState:State = .initial
private let urlsToSend:[URL]
private var ukeyClientFinishMsgData:Data?
private var queue:[OutgoingFileTransfer]=[]
private var currentTransfer:OutgoingFileTransfer?
public var delegate:OutboundNearbyConnectionDelegate?
private var totalBytesToSend:Int64=0
private var totalBytesSent:Int64=0
private var cancelled:Bool=false
private var textPayloadID:Int64=0
enum State{
case initial, sentUkeyClientInit, sentUkeyClientFinish, sentPairedKeyEncryption, sentPairedKeyResult, sentIntroduction, sendingFiles
}
init(connection: NWConnection, id: String, urlsToSend:[URL]){
self.urlsToSend=urlsToSend
super.init(connection: connection, id: id)
if urlsToSend.count==1 && !urlsToSend[0].isFileURL{
textPayloadID=Int64.random(in: Int64.min...Int64.max)
}
}
deinit {
if let transfer=currentTransfer, let handle=transfer.handle{
try? handle.close()
}
for transfer in queue{
if let handle=transfer.handle{
try? handle.close()
}
}
}
public func cancel(){
cancelled=true
if encryptionDone{
var cancel=Sharing_Nearby_Frame()
cancel.version = .v1
cancel.v1=Sharing_Nearby_V1Frame()
cancel.v1.type = .cancel
try? sendTransferSetupFrame(cancel)
}
try? sendDisconnectionAndDisconnect()
}
override func connectionReady() {
super.connectionReady()
do{
try sendConnectionRequest()
try sendUkey2ClientInit()
}catch{
lastError=error
protocolError()
}
}
override func isServer() -> Bool {
return false
}
override func processReceivedFrame(frameData: Data) {
do{
log("received \(frameData), state is \(currentState)")
switch currentState {
case .initial:
protocolError()
case .sentUkeyClientInit:
try processUkey2ServerInit(frame: try Securegcm_Ukey2Message(serializedBytes: frameData), raw: frameData)
case .sentUkeyClientFinish:
try processConnectionResponse(frame: try Location_Nearby_Connections_OfflineFrame(serializedBytes: frameData))
default:
let smsg=try Securemessage_SecureMessage(serializedBytes: frameData)
try decryptAndProcessReceivedSecureMessage(smsg)
}
}catch{
if case NearbyError.ukey2=error{
}else if currentState == .sentUkeyClientInit{
sendUkey2Alert(type: .badMessage)
}
lastError=error
protocolError()
}
}
override func processTransferSetupFrame(_ frame: Sharing_Nearby_Frame) throws {
if frame.hasV1 && frame.v1.hasType, case .cancel = frame.v1.type {
log("Transfer canceled")
try sendDisconnectionAndDisconnect()
delegate?.outboundConnection(connection: self, failedWithError: NearbyError.canceled(reason: .userCanceled))
return
}
class OutboundNearbyConnection: NearbyConnection {
private var currentState: State = .initial
private let urlsToSend: [URL]
private var ukeyClientFinishMsgData: Data?
private var queue: [OutgoingFileTransfer] = []
private var currentTransfer: OutgoingFileTransfer?
public var delegate: OutboundNearbyConnectionDelegate?
private var totalBytesToSend: Int64 = 0
private var totalBytesSent: Int64 = 0
private var cancelled: Bool = false
private var textPayloadID: Int64 = 0
enum State {
case initial, sentUkeyClientInit, sentUkeyClientFinish, sentPairedKeyEncryption, sentPairedKeyResult, sentIntroduction, sendingFiles
}
init(connection: NWConnection, id: String, urlsToSend: [URL]) {
self.urlsToSend = urlsToSend
super.init(connection: connection, id: id)
if urlsToSend.count == 1 && !urlsToSend[0].isFileURL {
textPayloadID = Int64.random(in: Int64.min ... Int64.max)
}
}
deinit {
if let transfer = currentTransfer, let handle = transfer.handle {
try? handle.close()
}
for transfer in queue {
if let handle = transfer.handle {
try? handle.close()
}
}
}
public func cancel() {
cancelled = true
if encryptionDone {
var cancel = Sharing_Nearby_Frame()
cancel.version = .v1
cancel.v1 = Sharing_Nearby_V1Frame()
cancel.v1.type = .cancel
try? sendTransferSetupFrame(cancel)
}
try? sendDisconnectionAndDisconnect()
}
override func connectionReady() {
super.connectionReady()
do {
try sendConnectionRequest()
try sendUkey2ClientInit()
} catch {
lastError = error
protocolError()
}
}
override func isServer() -> Bool {
return false
}
override func processReceivedFrame(frameData: Data) {
do {
log("received \(frameData), state is \(currentState)")
switch currentState {
case .initial:
protocolError()
case .sentUkeyClientInit:
try processUkey2ServerInit(frame: Securegcm_Ukey2Message(serializedBytes: frameData), raw: frameData)
case .sentUkeyClientFinish:
try processConnectionResponse(frame: Location_Nearby_Connections_OfflineFrame(serializedBytes: frameData))
default:
let smsg = try Securemessage_SecureMessage(serializedBytes: frameData)
try decryptAndProcessReceivedSecureMessage(smsg)
}
} catch {
if case NearbyError.ukey2 = error {
} else if currentState == .sentUkeyClientInit {
sendUkey2Alert(type: .badMessage)
}
lastError = error
protocolError()
}
}
override func processTransferSetupFrame(_ frame: Sharing_Nearby_Frame) throws {
if frame.hasV1 && frame.v1.hasType, case .cancel = frame.v1.type {
log("Transfer canceled")
try sendDisconnectionAndDisconnect()
delegate?.outboundConnection(connection: self, failedWithError: NearbyError.canceled(reason: .userCanceled))
return
}
log(frame.debugDescription)
switch currentState{
case .sentPairedKeyEncryption:
try processPairedKeyEncryption(frame: frame)
case .sentPairedKeyResult:
try processPairedKeyResult(frame: frame)
case .sentIntroduction:
try processConsent(frame: frame)
case .sendingFiles:
break
default:
assertionFailure("Unexpected state \(currentState)")
}
}
override func protocolError() {
super.protocolError()
delegate?.outboundConnection(connection: self, failedWithError: lastError!)
}
private func sendConnectionRequest() throws {
var frame=Location_Nearby_Connections_OfflineFrame()
frame.version = .v1
frame.v1=Location_Nearby_Connections_V1Frame()
frame.v1.type = .connectionRequest
frame.v1.connectionRequest=Location_Nearby_Connections_ConnectionRequestFrame()
frame.v1.connectionRequest.endpointID=String(bytes: NearbyConnectionManager.shared.endpointID, encoding: .ascii)!
frame.v1.connectionRequest.endpointName=Host.current().localizedName!
let endpointInfo=EndpointInfo(name: Host.current().localizedName!, deviceType: .computer)
frame.v1.connectionRequest.endpointInfo=endpointInfo.serialize()
frame.v1.connectionRequest.mediums=[.wifiLan]
sendFrameAsync(try frame.serializedData())
}
private func sendUkey2ClientInit() throws {
let domain=Domain.instance(curve: .EC256r1)
let (pubKey, privKey)=domain.makeKeyPair()
publicKey=pubKey
privateKey=privKey
var finishFrame=Securegcm_Ukey2Message()
finishFrame.messageType = .clientFinish
var finish=Securegcm_Ukey2ClientFinished()
var pkey=Securemessage_GenericPublicKey()
pkey.type = .ecP256
pkey.ecP256PublicKey=Securemessage_EcP256PublicKey()
pkey.ecP256PublicKey.x=Data(pubKey.w.x.asSignedBytes())
pkey.ecP256PublicKey.y=Data(pubKey.w.y.asSignedBytes())
finish.publicKey=try pkey.serializedData()
finishFrame.messageData=try finish.serializedData()
ukeyClientFinishMsgData=try finishFrame.serializedData()
var frame=Securegcm_Ukey2Message()
frame.messageType = .clientInit
var clientInit=Securegcm_Ukey2ClientInit()
clientInit.version=1
clientInit.random=Data.randomData(length: 32)
clientInit.nextProtocol="AES_256_CBC-HMAC_SHA256"
var sha=SHA512()
sha.update(data: ukeyClientFinishMsgData!)
var commitment=Securegcm_Ukey2ClientInit.CipherCommitment()
commitment.commitment=Data(sha.finalize())
commitment.handshakeCipher = .p256Sha512
clientInit.cipherCommitments.append(commitment)
frame.messageData=try clientInit.serializedData()
ukeyClientInitMsgData=try frame.serializedData()
sendFrameAsync(ukeyClientInitMsgData!)
currentState = .sentUkeyClientInit
}
private func processUkey2ServerInit(frame:Securegcm_Ukey2Message, raw:Data) throws{
ukeyServerInitMsgData=raw
guard frame.messageType == .serverInit else{
sendUkey2Alert(type: .badMessageType)
throw NearbyError.ukey2
}
let serverInit=try Securegcm_Ukey2ServerInit(serializedBytes: frame.messageData)
guard serverInit.version==1 else{
sendUkey2Alert(type: .badVersion)
throw NearbyError.ukey2
}
guard serverInit.random.count==32 else{
sendUkey2Alert(type: .badRandom)
throw NearbyError.ukey2
}
guard serverInit.handshakeCipher == .p256Sha512 else{
sendUkey2Alert(type: .badHandshakeCipher)
throw NearbyError.ukey2
}
let serverKey=try Securemessage_GenericPublicKey(serializedBytes: serverInit.publicKey)
try finalizeKeyExchange(peerKey: serverKey)
sendFrameAsync(ukeyClientFinishMsgData!)
currentState = .sentUkeyClientFinish
var resp=Location_Nearby_Connections_OfflineFrame()
resp.version = .v1
resp.v1=Location_Nearby_Connections_V1Frame()
resp.v1.type = .connectionResponse
resp.v1.connectionResponse=Location_Nearby_Connections_ConnectionResponseFrame()
resp.v1.connectionResponse.response = .accept
resp.v1.connectionResponse.status=0
resp.v1.connectionResponse.osInfo=Location_Nearby_Connections_OsInfo()
resp.v1.connectionResponse.osInfo.type = .apple
sendFrameAsync(try resp.serializedData())
encryptionDone=true
delegate?.outboundConnectionWasEstablished(connection: self)
}
private func processConnectionResponse(frame:Location_Nearby_Connections_OfflineFrame) throws{
log("connection response: \(frame)")
guard frame.version == .v1 else {throw NearbyError.protocolError("Unexpected offline frame version \(frame.version)")}
guard frame.v1.type == .connectionResponse else {throw NearbyError.protocolError("Unexpected frame type \(frame.v1.type)")}
guard frame.v1.connectionResponse.response == .accept else {throw NearbyError.protocolError("Connection was rejected by recipient")}
var pairedEncryption=Sharing_Nearby_Frame()
pairedEncryption.version = .v1
pairedEncryption.v1=Sharing_Nearby_V1Frame()
pairedEncryption.v1.type = .pairedKeyEncryption
pairedEncryption.v1.pairedKeyEncryption=Sharing_Nearby_PairedKeyEncryptionFrame()
pairedEncryption.v1.pairedKeyEncryption.secretIDHash=Data.randomData(length: 6)
pairedEncryption.v1.pairedKeyEncryption.signedData=Data.randomData(length: 72)
try sendTransferSetupFrame(pairedEncryption)
currentState = .sentPairedKeyEncryption
}
private func processPairedKeyEncryption(frame:Sharing_Nearby_Frame) throws{
guard frame.hasV1, frame.v1.hasPairedKeyEncryption else { throw NearbyError.requiredFieldMissing("sharingNearbyFrame.v1.pairedKeyEncryption") }
var pairedResult=Sharing_Nearby_Frame()
pairedResult.version = .v1
pairedResult.v1=Sharing_Nearby_V1Frame()
pairedResult.v1.type = .pairedKeyResult
pairedResult.v1.pairedKeyResult=Sharing_Nearby_PairedKeyResultFrame()
pairedResult.v1.pairedKeyResult.status = .unable
try sendTransferSetupFrame(pairedResult)
currentState = .sentPairedKeyResult
}
private func processPairedKeyResult(frame:Sharing_Nearby_Frame) throws{
guard frame.hasV1, frame.v1.hasPairedKeyResult else { throw NearbyError.requiredFieldMissing("sharingNearbyFrame.v1.pairedKeyResult") }
var introduction=Sharing_Nearby_Frame()
introduction.version = .v1
introduction.v1.type = .introduction
if urlsToSend.count==1 && !urlsToSend[0].isFileURL{
var meta=Sharing_Nearby_TextMetadata()
meta.type = .url
meta.textTitle=urlsToSend[0].host ?? "URL"
meta.size=Int64(urlsToSend[0].absoluteString.utf8.count)
meta.payloadID=textPayloadID
introduction.v1.introduction.textMetadata.append(meta)
}else{
for url in urlsToSend{
guard url.isFileURL else {continue}
var meta=Sharing_Nearby_FileMetadata()
meta.name=OutboundNearbyConnection.sanitizeFileName(name: url.lastPathComponent)
let attrs=try FileManager.default.attributesOfItem(atPath: url.path)
meta.size=(attrs[FileAttributeKey.size] as! NSNumber).int64Value
let typeID=try? url.resourceValues(forKeys: [.typeIdentifierKey]).typeIdentifier
meta.mimeType="application/octet-stream"
if let typeID=typeID{
if #available(macOS 11.0, *){
let type=UTType(typeID)
if let type=type, let mimeType=type.preferredMIMEType{
meta.mimeType=mimeType
}
}else{
if let mimeType=UTTypeCopyPreferredTagWithClass(typeID as CFString, kUTTagClassMIMEType){
meta.mimeType=(mimeType.takeRetainedValue() as NSString) as String
}
}
}
if meta.mimeType.starts(with: "image/"){
meta.type = .image
}else if meta.mimeType.starts(with: "video/"){
meta.type = .video
}else if(meta.mimeType.starts(with: "audio/")){
meta.type = .audio
}else if(url.pathExtension.lowercased()=="apk"){
meta.type = .app
}else{
meta.type = .unknown
}
meta.payloadID=Int64.random(in: Int64.min...Int64.max)
queue.append(OutgoingFileTransfer(url: url, payloadID: meta.payloadID, handle: try FileHandle(forReadingFrom: url), totalBytes: meta.size, currentOffset: 0))
introduction.v1.introduction.fileMetadata.append(meta)
totalBytesToSend+=meta.size
}
}
log("sent introduction: \(introduction)")
try sendTransferSetupFrame(introduction)
currentState = .sentIntroduction
}
private func processConsent(frame:Sharing_Nearby_Frame) throws{
guard frame.version == .v1, frame.v1.type == .response else {throw NearbyError.requiredFieldMissing("sharingNearbyFrame.v1.type==response")}
switch frame.v1.connectionResponse.status{
case .accept:
currentState = .sendingFiles
delegate?.outboundConnectionTransferAccepted(connection: self)
if urlsToSend.count==1 && !urlsToSend[0].isFileURL{
try sendURL()
}else{
try sendNextFileChunk()
}
case .reject, .unknown:
delegate?.outboundConnection(connection: self, failedWithError: NearbyError.canceled(reason: .userRejected))
try sendDisconnectionAndDisconnect()
case .notEnoughSpace:
delegate?.outboundConnection(connection: self, failedWithError: NearbyError.canceled(reason: .notEnoughSpace))
try sendDisconnectionAndDisconnect()
case .timedOut:
delegate?.outboundConnection(connection: self, failedWithError: NearbyError.canceled(reason: .timedOut))
try sendDisconnectionAndDisconnect()
case .unsupportedAttachmentType:
delegate?.outboundConnection(connection: self, failedWithError: NearbyError.canceled(reason: .unsupportedType))
try sendDisconnectionAndDisconnect()
}
}
private func sendURL() throws{
try sendBytesPayload(data: Data(urlsToSend[0].absoluteString.utf8), id: textPayloadID)
delegate?.outboundConnectionTransferFinished(connection: self)
try sendDisconnectionAndDisconnect()
}
private func sendNextFileChunk() throws{
if cancelled{
return
}
if currentTransfer==nil || currentTransfer?.currentOffset==currentTransfer?.totalBytes{
if currentTransfer != nil && currentTransfer?.handle != nil{
try currentTransfer?.handle?.close()
}
if queue.isEmpty{
log("Disconnecting because all files have been transferred")
try sendDisconnectionAndDisconnect()
delegate?.outboundConnectionTransferFinished(connection: self)
return
}
currentTransfer=queue.removeFirst()
}
let fileBuffer:Data
if #available(macOS 10.15.4, *) {
guard let _fileBuffer=try currentTransfer!.handle!.read(upToCount: 512*1024) else{
throw NearbyError.inputOutput
}
fileBuffer=_fileBuffer
} else {
fileBuffer=currentTransfer!.handle!.readData(ofLength: 512*1024)
}
var transfer=Location_Nearby_Connections_PayloadTransferFrame()
transfer.packetType = .data
transfer.payloadChunk.offset=currentTransfer!.currentOffset
transfer.payloadChunk.flags=0
transfer.payloadChunk.body=fileBuffer
transfer.payloadHeader.id=currentTransfer!.payloadID
transfer.payloadHeader.type = .file
transfer.payloadHeader.totalSize=Int64(currentTransfer!.totalBytes)
transfer.payloadHeader.isSensitive=false
currentTransfer!.currentOffset+=Int64(fileBuffer.count)
var wrapper=Location_Nearby_Connections_OfflineFrame()
wrapper.version = .v1
wrapper.v1=Location_Nearby_Connections_V1Frame()
wrapper.v1.type = .payloadTransfer
wrapper.v1.payloadTransfer=transfer
try encryptAndSendOfflineFrame(wrapper, completion: {
do{
try self.sendNextFileChunk()
}catch{
self.lastError=error
self.protocolError()
}
})
log("sent file chunk, current transfer: \(String(describing: currentTransfer))")
totalBytesSent+=Int64(fileBuffer.count)
delegate?.outboundConnection(connection: self, transferProgress: Double(totalBytesSent)/Double(totalBytesToSend))
if currentTransfer!.currentOffset==currentTransfer!.totalBytes{
// Signal end of file (yes, all this for one bit)
var transfer=Location_Nearby_Connections_PayloadTransferFrame()
transfer.packetType = .data
transfer.payloadChunk.offset=currentTransfer!.currentOffset
transfer.payloadChunk.flags=1 // <- this one here
transfer.payloadHeader.id=currentTransfer!.payloadID
transfer.payloadHeader.type = .file
transfer.payloadHeader.totalSize=Int64(currentTransfer!.totalBytes)
transfer.payloadHeader.isSensitive=false
var wrapper=Location_Nearby_Connections_OfflineFrame()
wrapper.version = .v1
wrapper.v1=Location_Nearby_Connections_V1Frame()
wrapper.v1.type = .payloadTransfer
wrapper.v1.payloadTransfer=transfer
try encryptAndSendOfflineFrame(wrapper)
log("sent EOF, current transfer: \(String(describing: currentTransfer))")
}
}
private static func sanitizeFileName(name:String)->String{
return name.replacingOccurrences(of: "[\\/\\\\?%\\*:\\|\"<>=]", with: "_", options: .regularExpression)
}
switch currentState {
case .sentPairedKeyEncryption:
try processPairedKeyEncryption(frame: frame)
case .sentPairedKeyResult:
try processPairedKeyResult(frame: frame)
case .sentIntroduction:
try processConsent(frame: frame)
case .sendingFiles:
break
default:
assertionFailure("Unexpected state \(currentState)")
}
}
override func protocolError() {
super.protocolError()
delegate?.outboundConnection(connection: self, failedWithError: lastError!)
}
private func sendConnectionRequest() throws {
var frame = Location_Nearby_Connections_OfflineFrame()
frame.version = .v1
frame.v1 = Location_Nearby_Connections_V1Frame()
frame.v1.type = .connectionRequest
frame.v1.connectionRequest = Location_Nearby_Connections_ConnectionRequestFrame()
frame.v1.connectionRequest.endpointID = String(bytes: NearbyConnectionManager.shared.endpointID, encoding: .ascii)!
frame.v1.connectionRequest.endpointName = Host.current().localizedName!
let endpointInfo = EndpointInfo(name: Host.current().localizedName!, deviceType: .computer)
frame.v1.connectionRequest.endpointInfo = endpointInfo.serialize()
frame.v1.connectionRequest.mediums = [.wifiLan]
try sendFrameAsync(frame.serializedData())
}
private func sendUkey2ClientInit() throws {
let domain = Domain.instance(curve: .EC256r1)
let (pubKey, privKey) = domain.makeKeyPair()
publicKey = pubKey
privateKey = privKey
var finishFrame = Securegcm_Ukey2Message()
finishFrame.messageType = .clientFinish
var finish = Securegcm_Ukey2ClientFinished()
var pkey = Securemessage_GenericPublicKey()
pkey.type = .ecP256
pkey.ecP256PublicKey = Securemessage_EcP256PublicKey()
pkey.ecP256PublicKey.x = Data(pubKey.w.x.asSignedBytes())
pkey.ecP256PublicKey.y = Data(pubKey.w.y.asSignedBytes())
finish.publicKey = try pkey.serializedData()
finishFrame.messageData = try finish.serializedData()
ukeyClientFinishMsgData = try finishFrame.serializedData()
var frame = Securegcm_Ukey2Message()
frame.messageType = .clientInit
var clientInit = Securegcm_Ukey2ClientInit()
clientInit.version = 1
clientInit.random = Data.randomData(length: 32)
clientInit.nextProtocol = "AES_256_CBC-HMAC_SHA256"
var sha = SHA512()
sha.update(data: ukeyClientFinishMsgData!)
var commitment = Securegcm_Ukey2ClientInit.CipherCommitment()
commitment.commitment = Data(sha.finalize())
commitment.handshakeCipher = .p256Sha512
clientInit.cipherCommitments.append(commitment)
frame.messageData = try clientInit.serializedData()
ukeyClientInitMsgData = try frame.serializedData()
sendFrameAsync(ukeyClientInitMsgData!)
currentState = .sentUkeyClientInit
}
private func processUkey2ServerInit(frame: Securegcm_Ukey2Message, raw: Data) throws {
ukeyServerInitMsgData = raw
guard frame.messageType == .serverInit else {
sendUkey2Alert(type: .badMessageType)
throw NearbyError.ukey2
}
let serverInit = try Securegcm_Ukey2ServerInit(serializedBytes: frame.messageData)
guard serverInit.version == 1 else {
sendUkey2Alert(type: .badVersion)
throw NearbyError.ukey2
}
guard serverInit.random.count == 32 else {
sendUkey2Alert(type: .badRandom)
throw NearbyError.ukey2
}
guard serverInit.handshakeCipher == .p256Sha512 else {
sendUkey2Alert(type: .badHandshakeCipher)
throw NearbyError.ukey2
}
let serverKey = try Securemessage_GenericPublicKey(serializedBytes: serverInit.publicKey)
try finalizeKeyExchange(peerKey: serverKey)
sendFrameAsync(ukeyClientFinishMsgData!)
currentState = .sentUkeyClientFinish
var resp = Location_Nearby_Connections_OfflineFrame()
resp.version = .v1
resp.v1 = Location_Nearby_Connections_V1Frame()
resp.v1.type = .connectionResponse
resp.v1.connectionResponse = Location_Nearby_Connections_ConnectionResponseFrame()
resp.v1.connectionResponse.response = .accept
resp.v1.connectionResponse.status = 0
resp.v1.connectionResponse.osInfo = Location_Nearby_Connections_OsInfo()
resp.v1.connectionResponse.osInfo.type = .apple
try sendFrameAsync(resp.serializedData())
encryptionDone = true
delegate?.outboundConnectionWasEstablished(connection: self)
}
private func processConnectionResponse(frame: Location_Nearby_Connections_OfflineFrame) throws {
log("connection response: \(frame)")
guard frame.version == .v1 else { throw NearbyError.protocolError("Unexpected offline frame version \(frame.version)") }
guard frame.v1.type == .connectionResponse else { throw NearbyError.protocolError("Unexpected frame type \(frame.v1.type)") }
guard frame.v1.connectionResponse.response == .accept else { throw NearbyError.protocolError("Connection was rejected by recipient") }
var pairedEncryption = Sharing_Nearby_Frame()
pairedEncryption.version = .v1
pairedEncryption.v1 = Sharing_Nearby_V1Frame()
pairedEncryption.v1.type = .pairedKeyEncryption
pairedEncryption.v1.pairedKeyEncryption = Sharing_Nearby_PairedKeyEncryptionFrame()
pairedEncryption.v1.pairedKeyEncryption.secretIDHash = Data.randomData(length: 6)
pairedEncryption.v1.pairedKeyEncryption.signedData = Data.randomData(length: 72)
try sendTransferSetupFrame(pairedEncryption)
currentState = .sentPairedKeyEncryption
}
private func processPairedKeyEncryption(frame: Sharing_Nearby_Frame) throws {
guard frame.hasV1, frame.v1.hasPairedKeyEncryption else { throw NearbyError.requiredFieldMissing("sharingNearbyFrame.v1.pairedKeyEncryption") }
var pairedResult = Sharing_Nearby_Frame()
pairedResult.version = .v1
pairedResult.v1 = Sharing_Nearby_V1Frame()
pairedResult.v1.type = .pairedKeyResult
pairedResult.v1.pairedKeyResult = Sharing_Nearby_PairedKeyResultFrame()
pairedResult.v1.pairedKeyResult.status = .unable
try sendTransferSetupFrame(pairedResult)
currentState = .sentPairedKeyResult
}
private func processPairedKeyResult(frame: Sharing_Nearby_Frame) throws {
guard frame.hasV1, frame.v1.hasPairedKeyResult else { throw NearbyError.requiredFieldMissing("sharingNearbyFrame.v1.pairedKeyResult") }
var introduction = Sharing_Nearby_Frame()
introduction.version = .v1
introduction.v1.type = .introduction
if urlsToSend.count == 1 && !urlsToSend[0].isFileURL {
var meta = Sharing_Nearby_TextMetadata()
meta.type = .url
meta.textTitle = urlsToSend[0].host ?? "URL"
meta.size = Int64(urlsToSend[0].absoluteString.utf8.count)
meta.payloadID = textPayloadID
introduction.v1.introduction.textMetadata.append(meta)
} else {
for url in urlsToSend {
guard url.isFileURL else { continue }
var meta = Sharing_Nearby_FileMetadata()
meta.name = OutboundNearbyConnection.sanitizeFileName(name: url.lastPathComponent)
let attrs = try FileManager.default.attributesOfItem(atPath: url.path)
meta.size = (attrs[FileAttributeKey.size] as! NSNumber).int64Value
let typeID = try? url.resourceValues(forKeys: [.typeIdentifierKey]).typeIdentifier
meta.mimeType = "application/octet-stream"
if let typeID = typeID {
if #available(macOS 11.0, *) {
let type = UTType(typeID)
if let type = type, let mimeType = type.preferredMIMEType {
meta.mimeType = mimeType
}
} else {
if let mimeType = UTTypeCopyPreferredTagWithClass(typeID as CFString, kUTTagClassMIMEType) {
meta.mimeType = (mimeType.takeRetainedValue() as NSString) as String
}
}
}
if meta.mimeType.starts(with: "image/") {
meta.type = .image
} else if meta.mimeType.starts(with: "video/") {
meta.type = .video
} else if meta.mimeType.starts(with: "audio/") {
meta.type = .audio
} else if url.pathExtension.lowercased() == "apk" {
meta.type = .app
} else {
meta.type = .unknown
}
meta.payloadID = Int64.random(in: Int64.min ... Int64.max)
try queue.append(OutgoingFileTransfer(url: url, payloadID: meta.payloadID, handle: FileHandle(forReadingFrom: url), totalBytes: meta.size, currentOffset: 0))
introduction.v1.introduction.fileMetadata.append(meta)
totalBytesToSend += meta.size
}
}
log("sent introduction: \(introduction)")
try sendTransferSetupFrame(introduction)
currentState = .sentIntroduction
}
private func processConsent(frame: Sharing_Nearby_Frame) throws {
guard frame.version == .v1, frame.v1.type == .response else { throw NearbyError.requiredFieldMissing("sharingNearbyFrame.v1.type==response") }
switch frame.v1.connectionResponse.status {
case .accept:
currentState = .sendingFiles
delegate?.outboundConnectionTransferAccepted(connection: self)
if urlsToSend.count == 1 && !urlsToSend[0].isFileURL {
try sendURL()
} else {
try sendNextFileChunk()
}
case .reject, .unknown:
delegate?.outboundConnection(connection: self, failedWithError: NearbyError.canceled(reason: .userRejected))
try sendDisconnectionAndDisconnect()
case .notEnoughSpace:
delegate?.outboundConnection(connection: self, failedWithError: NearbyError.canceled(reason: .notEnoughSpace))
try sendDisconnectionAndDisconnect()
case .timedOut:
delegate?.outboundConnection(connection: self, failedWithError: NearbyError.canceled(reason: .timedOut))
try sendDisconnectionAndDisconnect()
case .unsupportedAttachmentType:
delegate?.outboundConnection(connection: self, failedWithError: NearbyError.canceled(reason: .unsupportedType))
try sendDisconnectionAndDisconnect()
}
}
private func sendURL() throws {
try sendBytesPayload(data: Data(urlsToSend[0].absoluteString.utf8), id: textPayloadID)
delegate?.outboundConnectionTransferFinished(connection: self)
try sendDisconnectionAndDisconnect()
}
private func sendNextFileChunk() throws {
if cancelled {
return
}
if currentTransfer == nil || currentTransfer?.currentOffset == currentTransfer?.totalBytes {
if currentTransfer != nil && currentTransfer?.handle != nil {
try currentTransfer?.handle?.close()
}
if queue.isEmpty {
log("Disconnecting because all files have been transferred")
try sendDisconnectionAndDisconnect()
delegate?.outboundConnectionTransferFinished(connection: self)
return
}
currentTransfer = queue.removeFirst()
}
let fileBuffer: Data
if #available(macOS 10.15.4, *) {
guard let _fileBuffer = try currentTransfer!.handle!.read(upToCount: 512 * 1024) else {
throw NearbyError.inputOutput
}
fileBuffer = _fileBuffer
} else {
fileBuffer = currentTransfer!.handle!.readData(ofLength: 512 * 1024)
}
var transfer = Location_Nearby_Connections_PayloadTransferFrame()
transfer.packetType = .data
transfer.payloadChunk.offset = currentTransfer!.currentOffset
transfer.payloadChunk.flags = 0
transfer.payloadChunk.body = fileBuffer
transfer.payloadHeader.id = currentTransfer!.payloadID
transfer.payloadHeader.type = .file
transfer.payloadHeader.totalSize = Int64(currentTransfer!.totalBytes)
transfer.payloadHeader.isSensitive = false
currentTransfer!.currentOffset += Int64(fileBuffer.count)
var wrapper = Location_Nearby_Connections_OfflineFrame()
wrapper.version = .v1
wrapper.v1 = Location_Nearby_Connections_V1Frame()
wrapper.v1.type = .payloadTransfer
wrapper.v1.payloadTransfer = transfer
try encryptAndSendOfflineFrame(wrapper, completion: {
do {
try self.sendNextFileChunk()
} catch {
self.lastError = error
self.protocolError()
}
})
log("sent file chunk, current transfer: \(String(describing: currentTransfer))")
totalBytesSent += Int64(fileBuffer.count)
delegate?.outboundConnection(connection: self, transferProgress: Double(totalBytesSent) / Double(totalBytesToSend))
if currentTransfer!.currentOffset == currentTransfer!.totalBytes {
// Signal end of file (yes, all this for one bit)
var transfer = Location_Nearby_Connections_PayloadTransferFrame()
transfer.packetType = .data
transfer.payloadChunk.offset = currentTransfer!.currentOffset
transfer.payloadChunk.flags = 1 // <- this one here
transfer.payloadHeader.id = currentTransfer!.payloadID
transfer.payloadHeader.type = .file
transfer.payloadHeader.totalSize = Int64(currentTransfer!.totalBytes)
transfer.payloadHeader.isSensitive = false
var wrapper = Location_Nearby_Connections_OfflineFrame()
wrapper.version = .v1
wrapper.v1 = Location_Nearby_Connections_V1Frame()
wrapper.v1.type = .payloadTransfer
wrapper.v1.payloadTransfer = transfer
try encryptAndSendOfflineFrame(wrapper)
log("sent EOF, current transfer: \(String(describing: currentTransfer))")
}
}
private static func sanitizeFileName(name: String) -> String {
return name.replacingOccurrences(of: "[\\/\\\\?%\\*:\\|\"<>=]", with: "_", options: .regularExpression)
}
}
fileprivate struct OutgoingFileTransfer{
let url:URL
let payloadID:Int64
let handle:FileHandle?
let totalBytes:Int64
var currentOffset:Int64
private struct OutgoingFileTransfer {
let url: URL
let payloadID: Int64
let handle: FileHandle?
let totalBytes: Int64
var currentOffset: Int64
}
protocol OutboundNearbyConnectionDelegate{
func outboundConnectionWasEstablished(connection:OutboundNearbyConnection)
func outboundConnection(connection:OutboundNearbyConnection, transferProgress:Double)
func outboundConnectionTransferAccepted(connection:OutboundNearbyConnection)
func outboundConnection(connection:OutboundNearbyConnection, failedWithError:Error)
func outboundConnectionTransferFinished(connection:OutboundNearbyConnection)
protocol OutboundNearbyConnectionDelegate {
func outboundConnectionWasEstablished(connection: OutboundNearbyConnection)
func outboundConnection(connection: OutboundNearbyConnection, transferProgress: Double)
func outboundConnectionTransferAccepted(connection: OutboundNearbyConnection)
func outboundConnection(connection: OutboundNearbyConnection, failedWithError: Error)
func outboundConnectionTransferFinished(connection: OutboundNearbyConnection)
}