Skip to content
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions keepassxc-browser-safari/KeePassXC-Browser/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
xcuserdata/

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>KeePassXC-Browser Extension</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.Safari.web-extension</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).SafariWebExtensionHandler</string>
</dict>
</dict>
</plist>
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>org.keepassxc.KeePassXC</string>
</array>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/*
* Copyright (C) 2023 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import SafariServices
import os.log

let SFExtensionMessageKey = "message"
let SocketFileName = "org.keepassxc.KeePassXC.BrowserServer"
var socketFD : Int32 = -1
var socketConnected = false
var maxMessageLength: Int = 1024 * 1024;
Comment thread
varjolintu marked this conversation as resolved.
Outdated
let backgroundQueue = DispatchQueue(label: "\(Bundle.main.bundleIdentifier!).readSocketQueue", qos: .background)

class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling {
private var logger = Logger(
subsystem: Bundle.main.bundleIdentifier!,
category: String(describing: SafariWebExtensionHandler.self)
)

var socketPath: String {
FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "org.keepassxc.KeePassXC")!.appending(component: SocketFileName).path(percentEncoded: false)
}

func closeSocket() {
if (socketFD != -1) {
logger.info("Closing socket")
close(socketFD)
socketFD = -1
}
}

func connectSocket() -> Bool {
// Reuse socket
guard socketFD == -1 else { return true }

socketFD = socket(AF_UNIX, SOCK_STREAM, 0)
guard socketFD > 0 else {
logger.error("Failed to create socket")
return false
}

var address = sockaddr_un()
address.sun_family = sa_family_t(AF_UNIX)

withUnsafeMutableBytes(of: &address.sun_path) { ptr in
socketPath.utf8CString.withUnsafeBytes { bytes in
ptr.copyBytes(from: bytes)
}
}

let result = withUnsafePointer(to: &address) {
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
connect(socketFD, $0, socklen_t(MemoryLayout<sockaddr_un>.size))
}
}

if result != 0 {
logger.error("Failed to connect to socket: \(errno)")
close(socketFD)
socketFD = -1
return false
}

return true
}

func beginRequest(with context: NSExtensionContext) {
guard let serializedRequest = parseRequest(with: context) else {
logger.error("Failed to parse request")
context.cancelRequest(withError: NSError())
return
}

if (!socketConnected) {
if (!connectSocket()) {
closeSocket()
logger.error("Socket not connected")
return
}

socketConnected = true
startSocketListener()
}

// Send message
let bytesWritten = serializedRequest.withUnsafeBytes {
write(socketFD, $0.baseAddress!, $0.count)
}


if bytesWritten <= 0 {
logger.error("Cannot write to socket \(errno)")
} else {
logger.debug("Written \(bytesWritten) bytes")
}

logger.debug("Written \(bytesWritten) bytes")

context.completeRequest(returningItems: [])
}

func parseRequest(with context: NSExtensionContext) -> Data? {
guard let item = context.inputItems.first as? NSExtensionItem else {
logger.error("Invalid amount of arguments for NSExtensionItem")
return nil
}

guard let serializedRequest = try? JSONSerialization.data(withJSONObject: item.userInfo?[SFExtensionMessageKey] as Any) else {
logger.error("JSON serialization error")
return nil
}

return serializedRequest
}

func startSocketListener() {
backgroundQueue.async {
while socketConnected {
var buffer = [UInt8](repeating: 0, count: maxMessageLength)
let bytesRead = read(socketFD, &buffer, buffer.count)

if bytesRead > 0 {
let data = Data(buffer[0..<bytesRead])
self.logger.debug("Received message: \(data)")

guard let jsonDict = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else {
self.logger.error("Failed to decode message")
return
}

SFSafariApplication.dispatchMessage(withName: "proxy_message", toExtensionWithIdentifier: "com.keepassxc.KeePassXC-Browser-Extension", userInfo: jsonDict)
} else {
self.logger.debug("No message received or connection closed")
}
}
}
}
}
Loading