This commit is contained in:
2026-09-13 12:15:36 -07:00
commit e473d00f4f
104 changed files with 16080 additions and 0 deletions
@@ -0,0 +1,16 @@
<?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.application-groups</key>
<array>
<string>$(TeamIdentifierPrefix)com.kovtash.portalcam</string>
</array>
<key>com.apple.security.network.client</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)com.kovtash.portalcam</string>
</array>
</dict>
</plist>
@@ -0,0 +1,622 @@
//
// CamExtensionProvider.swift
// CamExtension
//
// Created by Vlad on 9/12/26.
//
import Foundation
import CoreMediaIO
import CoreVideo
import CoreGraphics
import CoreImage
import IOKit.audio
import IOSurface
import Security
import ObjectiveC
import os.log
let kFrameRate: Int = 30
let kPortalCamDeviceUUID = UUID(uuidString: "7B1E8C4B-8D2E-4A0D-9B95-8F5C1C7D9B11")!
let kPortalCamSourceStreamUUID = UUID(uuidString: "7B1E8C4B-8D2E-4A0D-9B95-8F5C1C7D9B12")!
let kPortalCamSinkStreamUUID = UUID(uuidString: "7B1E8C4B-8D2E-4A0D-9B95-8F5C1C7D9B13")!
let kStreamStartedNotification = "com.kovtash.portalcam.streamStarted"
let kStreamStoppedNotification = "com.kovtash.portalcam.streamStopped"
@_silgen_name("csops")
func csops(_ pid: pid_t, _ ops: UInt32, _ useraddr: UnsafeMutableRawPointer?, _ usersize: Int) -> Int32
// MARK: - Device Source
class CamExtensionDeviceSource: NSObject, CMIOExtensionDeviceSource {
private(set) var device: CMIOExtensionDevice!
private var _streamSource: CamExtensionStreamSource!
private var _sinkStreamSource: CamExtensionSinkStreamSource!
private var _streamingCounter: UInt32 = 0
private var _videoDescription: CMFormatDescription!
private var _bufferPool: CVPixelBufferPool!
private var _placeholderPixelBuffer: CVPixelBuffer?
private var _lastSinkFrameDate: Date?
private let _lastSinkFrameLock = NSLock()
private var _placeholderTimer: DispatchSourceTimer?
private let _timerQueue = DispatchQueue(label: "com.kovtash.portalcam.cmio", qos: .userInteractive)
private var _sentFrames = 0
init(localizedName: String) {
super.init()
self.device = CMIOExtensionDevice(
localizedName: localizedName,
deviceID: kPortalCamDeviceUUID,
legacyDeviceID: nil,
source: self
)
let dims = CMVideoDimensions(width: 1280, height: 720)
CMVideoFormatDescriptionCreate(
allocator: kCFAllocatorDefault,
codecType: kCVPixelFormatType_32BGRA,
width: dims.width,
height: dims.height,
extensions: nil,
formatDescriptionOut: &_videoDescription
)
let pixelBufferAttributes: NSDictionary = [
kCVPixelBufferWidthKey: dims.width,
kCVPixelBufferHeightKey: dims.height,
kCVPixelBufferPixelFormatTypeKey: _videoDescription.mediaSubType,
kCVPixelBufferIOSurfacePropertiesKey: [:] as NSDictionary
]
CVPixelBufferPoolCreate(kCFAllocatorDefault, nil, pixelBufferAttributes, &_bufferPool)
let videoStreamFormat = CMIOExtensionStreamFormat(
formatDescription: _videoDescription,
maxFrameDuration: CMTime(value: 1, timescale: Int32(kFrameRate)),
minFrameDuration: CMTime(value: 1, timescale: Int32(kFrameRate)),
validFrameDurations: nil
)
_streamSource = CamExtensionStreamSource(
localizedName: "PortalCam",
streamID: kPortalCamSourceStreamUUID,
streamFormat: videoStreamFormat,
device: device
)
_sinkStreamSource = CamExtensionSinkStreamSource(
localizedName: "PortalCam Sink",
streamID: kPortalCamSinkStreamUUID,
streamFormat: videoStreamFormat,
device: device,
deviceSource: self
)
do {
try device.addStream(_streamSource.stream)
try device.addStream(_sinkStreamSource.stream)
} catch {
fatalError("Failed to add streams: \(error.localizedDescription)")
}
createPlaceholderBuffer(width: Int(dims.width), height: Int(dims.height))
}
private func createPlaceholderBuffer(width: Int, height: Int) {
let attrs: [CFString: Any] = [
kCVPixelBufferCGImageCompatibilityKey: true,
kCVPixelBufferCGBitmapContextCompatibilityKey: true,
kCVPixelBufferIOSurfacePropertiesKey: [:] as NSDictionary
]
let status = CVPixelBufferCreate(
kCFAllocatorDefault,
width,
height,
kCVPixelFormatType_32BGRA,
attrs as CFDictionary,
&_placeholderPixelBuffer
)
guard status == kCVReturnSuccess, let buffer = _placeholderPixelBuffer else { return }
CVPixelBufferLockBaseAddress(buffer, [])
defer { CVPixelBufferUnlockBaseAddress(buffer, []) }
guard let base = CVPixelBufferGetBaseAddress(buffer) else { return }
let bytesPerRow = CVPixelBufferGetBytesPerRow(buffer)
let colorSpace = CGColorSpaceCreateDeviceRGB()
let bitmapInfo = CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue
guard let ctx = CGContext(
data: base,
width: width,
height: height,
bitsPerComponent: 8,
bytesPerRow: bytesPerRow,
space: colorSpace,
bitmapInfo: bitmapInfo
) else { return }
// Fill dark slate background
ctx.setFillColor(red: 0.10, green: 0.12, blue: 0.16, alpha: 1.0)
ctx.fill(CGRect(x: 0, y: 0, width: width, height: height))
// Rounded border
let insetRect = CGRect(x: 40, y: 40, width: width - 80, height: height - 80)
let path = CGPath(roundedRect: insetRect, cornerWidth: 20, cornerHeight: 20, transform: nil)
ctx.addPath(path)
ctx.setStrokeColor(red: 0.28, green: 0.45, blue: 0.75, alpha: 0.8)
ctx.setLineWidth(4)
ctx.strokePath()
}
var availableProperties: Set<CMIOExtensionProperty> {
return [.deviceTransportType, .deviceModel]
}
func deviceProperties(forProperties properties: Set<CMIOExtensionProperty>) throws -> CMIOExtensionDeviceProperties {
let deviceProperties = CMIOExtensionDeviceProperties(dictionary: [:])
if properties.contains(.deviceTransportType) {
deviceProperties.transportType = kIOAudioDeviceTransportTypeVirtual
}
if properties.contains(.deviceModel) {
deviceProperties.model = "PortalCam Camera"
}
return deviceProperties
}
func setDeviceProperties(_ deviceProperties: CMIOExtensionDeviceProperties) throws {
// Settable properties
}
func startStreaming() {
_streamingCounter += 1
os_log(.default, "PortalCam extension: startStreaming (counter=%{public}u)", _streamingCounter)
if _streamingCounter == 1 {
// Notify main app that a client has opened the camera
if let center = CFNotificationCenterGetDarwinNotifyCenter() {
CFNotificationCenterPostNotification(
center,
CFNotificationName(kStreamStartedNotification as CFString),
nil,
nil,
true
)
}
// Schedule fallback/standby timer
_placeholderTimer = DispatchSource.makeTimerSource(queue: _timerQueue)
_placeholderTimer?.schedule(deadline: .now(), repeating: 1.0 / Double(kFrameRate), leeway: .milliseconds(2))
_placeholderTimer?.setEventHandler { [weak self] in
self?.sendPlaceholderIfNeeded()
}
_placeholderTimer?.resume()
}
}
func stopStreaming() {
if _streamingCounter > 1 {
_streamingCounter -= 1
} else {
_streamingCounter = 0
os_log(.default, "PortalCam extension: stopStreaming - posting stop notification")
_placeholderTimer?.cancel()
_placeholderTimer = nil
_lastSinkFrameLock.lock()
_lastSinkFrameDate = nil
_lastSinkFrameLock.unlock()
if let center = CFNotificationCenterGetDarwinNotifyCenter() {
CFNotificationCenterPostNotification(
center,
CFNotificationName(kStreamStoppedNotification as CFString),
nil,
nil,
true
)
}
}
}
private func sendPlaceholderIfNeeded() {
guard _streamingCounter > 0 else { return }
_lastSinkFrameLock.lock()
let lastDate = _lastSinkFrameDate
_lastSinkFrameLock.unlock()
// If live sink frames are actively arriving within the last 500ms, don't send placeholder
if let lastDate = lastDate, Date().timeIntervalSince(lastDate) < 0.5 {
return
}
guard let placeholder = _placeholderPixelBuffer else { return }
let hostTime = CMClockGetTime(CMClockGetHostTimeClock())
var timing = CMSampleTimingInfo(
duration: CMTime(value: 1, timescale: Int32(kFrameRate)),
presentationTimeStamp: hostTime,
decodeTimeStamp: .invalid
)
var sample: CMSampleBuffer?
let status = CMSampleBufferCreateForImageBuffer(
allocator: kCFAllocatorDefault,
imageBuffer: placeholder,
dataReady: true,
makeDataReadyCallback: nil,
refcon: nil,
formatDescription: _videoDescription,
sampleTiming: &timing,
sampleBufferOut: &sample
)
if status == noErr, let sample = sample {
let nano = UInt64(hostTime.seconds * Double(NSEC_PER_SEC))
_streamSource.stream.send(sample, discontinuity: [], hostTimeInNanoseconds: nano)
}
}
func deliverSinkSampleBuffer(_ sampleBuffer: CMSampleBuffer) {
_lastSinkFrameLock.lock()
_lastSinkFrameDate = Date()
_lastSinkFrameLock.unlock()
guard _streamingCounter > 0 else { return }
let hostTime = CMClockGetTime(CMClockGetHostTimeClock())
let nano = UInt64(hostTime.seconds * Double(NSEC_PER_SEC))
_sentFrames += 1
if _sentFrames <= 3 {
os_log(.default, "PortalCam extension: forwarded sink frame %{public}d to source stream", self._sentFrames)
}
_streamSource.stream.send(sampleBuffer, discontinuity: [], hostTimeInNanoseconds: nano)
}
}
// MARK: - Source Stream (FaceTime, Zoom, Photo Booth)
class CamExtensionStreamSource: NSObject, CMIOExtensionStreamSource {
private(set) var stream: CMIOExtensionStream!
let device: CMIOExtensionDevice
private let _streamFormat: CMIOExtensionStreamFormat
init(localizedName: String, streamID: UUID, streamFormat: CMIOExtensionStreamFormat, device: CMIOExtensionDevice) {
self.device = device
self._streamFormat = streamFormat
super.init()
self.stream = CMIOExtensionStream(
localizedName: localizedName,
streamID: streamID,
direction: .source,
clockType: .hostTime,
source: self
)
}
var formats: [CMIOExtensionStreamFormat] {
return [_streamFormat]
}
var activeFormatIndex: Int = 0 {
didSet {
if activeFormatIndex >= 1 {
os_log(.error, "Invalid index: %{public}d", activeFormatIndex)
}
}
}
var availableProperties: Set<CMIOExtensionProperty> {
return [.streamActiveFormatIndex, .streamFrameDuration]
}
func streamProperties(forProperties properties: Set<CMIOExtensionProperty>) throws -> CMIOExtensionStreamProperties {
let streamProperties = CMIOExtensionStreamProperties(dictionary: [:])
if properties.contains(.streamActiveFormatIndex) {
streamProperties.activeFormatIndex = 0
}
if properties.contains(.streamFrameDuration) {
streamProperties.frameDuration = CMTime(value: 1, timescale: Int32(kFrameRate))
}
return streamProperties
}
func setStreamProperties(_ streamProperties: CMIOExtensionStreamProperties) throws {
if let activeFormatIndex = streamProperties.activeFormatIndex {
self.activeFormatIndex = activeFormatIndex
}
}
func authorizedToStartStream(for client: CMIOExtensionClient) -> Bool {
return true
}
func startStream() throws {
guard let deviceSource = device.source as? CamExtensionDeviceSource else {
fatalError("Unexpected source type \(String(describing: device.source))")
}
deviceSource.startStreaming()
}
func stopStream() throws {
guard let deviceSource = device.source as? CamExtensionDeviceSource else {
fatalError("Unexpected source type \(String(describing: device.source))")
}
deviceSource.stopStreaming()
}
}
// MARK: - Sink Stream (fed by PortalCam.app)
class CamExtensionSinkStreamSource: NSObject, CMIOExtensionStreamSource {
private(set) var stream: CMIOExtensionStream!
let device: CMIOExtensionDevice
weak var deviceSource: CamExtensionDeviceSource?
private let _streamFormat: CMIOExtensionStreamFormat
private let _consumeQueue = DispatchQueue(label: "com.kovtash.portalcam.sink.consume", qos: .userInteractive)
private var _isStreaming: Bool = false
private var _activeClient: CMIOExtensionClient?
init(
localizedName: String,
streamID: UUID,
streamFormat: CMIOExtensionStreamFormat,
device: CMIOExtensionDevice,
deviceSource: CamExtensionDeviceSource
) {
self.device = device
self.deviceSource = deviceSource
self._streamFormat = streamFormat
super.init()
self.stream = CMIOExtensionStream(
localizedName: localizedName,
streamID: streamID,
direction: .sink,
clockType: .hostTime,
source: self
)
}
var formats: [CMIOExtensionStreamFormat] {
return [_streamFormat]
}
var activeFormatIndex: Int = 0 {
didSet {
if activeFormatIndex >= 1 {
os_log(.error, "Invalid sink format index: %{public}d", activeFormatIndex)
}
}
}
var availableProperties: Set<CMIOExtensionProperty> {
return [
.streamActiveFormatIndex,
.streamFrameDuration,
.streamSinkBufferQueueSize,
.streamSinkBuffersRequiredForStartup
]
}
func streamProperties(forProperties properties: Set<CMIOExtensionProperty>) throws -> CMIOExtensionStreamProperties {
let streamProperties = CMIOExtensionStreamProperties(dictionary: [:])
if properties.contains(.streamActiveFormatIndex) {
streamProperties.activeFormatIndex = 0
}
if properties.contains(.streamFrameDuration) {
streamProperties.frameDuration = CMTime(value: 1, timescale: Int32(kFrameRate))
}
if properties.contains(.streamSinkBufferQueueSize) {
streamProperties.sinkBufferQueueSize = 8
}
if properties.contains(.streamSinkBuffersRequiredForStartup) {
streamProperties.sinkBuffersRequiredForStartup = 1
}
return streamProperties
}
func setStreamProperties(_ streamProperties: CMIOExtensionStreamProperties) throws {
if let activeFormatIndex = streamProperties.activeFormatIndex {
self.activeFormatIndex = activeFormatIndex
}
}
func authorizedToStartStream(for client: CMIOExtensionClient) -> Bool {
guard isValidPortalCamClient(client: client) else {
os_log(.error, "PortalCam extension: rejected unauthorized sink client PID %d, signingID %{public}@",
client.pid, client.signingID ?? "unknown")
return false
}
_activeClient = client
os_log(.default, "PortalCam extension: authorized verified sink client PID %d, signingID %{public}@",
client.pid, client.signingID ?? "unknown")
return true
}
private func isValidPortalCamClient(client: CMIOExtensionClient) -> Bool {
let pid = client.pid
// 1. Check code signing status via kernel csops
// CS_OPS_STATUS = 0, CS_VALID = 0x00000001
var status: UInt32 = 0
let statusRet = csops(pid, 0, &status, MemoryLayout<UInt32>.size)
guard statusRet == 0 else {
os_log(.error, "PortalCam extension: csops CS_OPS_STATUS failed for PID %d: errno %d", pid, errno)
return false
}
guard (status & 0x00000001) != 0 else {
os_log(.error, "PortalCam extension: process %d code signature is not valid (status 0x%{public}x)", pid, status)
return false
}
// 2. Check code signing identity (Bundle Identifier)
struct CSHeader {
var magic: UInt32 = 0
var length: UInt32 = 0
}
let CS_OPS_IDENTITY: UInt32 = 11
var idHeader = CSHeader()
let idHeaderRet = csops(pid, CS_OPS_IDENTITY, &idHeader, MemoryLayout<CSHeader>.size)
guard idHeaderRet == 0 || errno == ERANGE else {
os_log(.error, "PortalCam extension: csops CS_OPS_IDENTITY header failed for PID %d: errno %d", pid, errno)
return false
}
let idLen = Int(UInt32(bigEndian: idHeader.length))
guard idLen >= MemoryLayout<CSHeader>.size else {
os_log(.error, "PortalCam extension: invalid identity length %d for PID %d", idLen, pid)
return false
}
var idBuf = [UInt8](repeating: 0, count: idLen)
guard csops(pid, CS_OPS_IDENTITY, &idBuf, idLen) == 0 else {
os_log(.error, "PortalCam extension: csops CS_OPS_IDENTITY failed for PID %d", pid)
return false
}
let identity = String(cString: Array(idBuf[MemoryLayout<CSHeader>.size...]))
// 3. Check Team Identifier
let CS_OPS_TEAMID: UInt32 = 14
var teamHeader = CSHeader()
let teamHeaderRet = csops(pid, CS_OPS_TEAMID, &teamHeader, MemoryLayout<CSHeader>.size)
guard teamHeaderRet == 0 || errno == ERANGE else {
os_log(.error, "PortalCam extension: csops CS_OPS_TEAMID header failed for PID %d: errno %d", pid, errno)
return false
}
let teamLen = Int(UInt32(bigEndian: teamHeader.length))
guard teamLen >= MemoryLayout<CSHeader>.size else {
os_log(.error, "PortalCam extension: invalid team length %d for PID %d", teamLen, pid)
return false
}
var teamBuf = [UInt8](repeating: 0, count: teamLen)
guard csops(pid, CS_OPS_TEAMID, &teamBuf, teamLen) == 0 else {
os_log(.error, "PortalCam extension: csops CS_OPS_TEAMID failed for PID %d", pid)
return false
}
let teamID = String(cString: Array(teamBuf[MemoryLayout<CSHeader>.size...]))
os_log(.default, "PortalCam extension: verified client PID %d: identity='%{public}@', teamID='%{public}@', flags=0x%{public}x",
pid, identity, teamID, status)
guard identity == "com.kovtash.portalcam", teamID == "ENT9X9U544" else {
os_log(.error, "PortalCam extension: rejected client PID %d: identity mismatch ('%{public}@' != 'com.kovtash.portalcam') or team mismatch ('%{public}@' != 'ENT9X9U544')",
pid, identity, teamID)
return false
}
return true
}
func startStream() throws {
os_log(.default, "PortalCam extension: sink startStream called")
_isStreaming = true
_consumeQueue.async { [weak self] in
self?.consumeNext()
}
}
func stopStream() throws {
os_log(.default, "PortalCam extension: sink stopStream called")
_isStreaming = false
_activeClient = nil
}
private func consumeNext() {
guard _isStreaming, let stream = self.stream else { return }
guard let client = _activeClient ?? stream.streamingClients.first else {
// No client connected yet; wait briefly and check again
_consumeQueue.asyncAfter(deadline: .now() + .milliseconds(50)) { [weak self] in
self?.consumeNext()
}
return
}
stream.consumeSampleBuffer(from: client) { [weak self] sampleBuffer, sequenceNumber, discontinuity, hasMore, error in
guard let self = self, self._isStreaming else { return }
if let error = error {
os_log(.default, "PortalCam extension: sink consume error: %{public}@", error.localizedDescription)
self._consumeQueue.asyncAfter(deadline: .now() + .milliseconds(100)) { [weak self] in
self?.consumeNext()
}
return
}
if let sampleBuffer = sampleBuffer {
self.deviceSource?.deliverSinkSampleBuffer(sampleBuffer)
let pts = sampleBuffer.presentationTimeStamp
let nano = pts.isValid && pts.seconds > 0 ? UInt64(pts.seconds * Double(NSEC_PER_SEC)) : mach_absolute_time()
let output = CMIOExtensionScheduledOutput(sequenceNumber: sequenceNumber, hostTimeInNanoseconds: nano)
stream.notifyScheduledOutputChanged(output)
// Pull next buffer immediately
self._consumeQueue.async { [weak self] in
self?.consumeNext()
}
} else {
// Queue is empty, wait half a frame duration (~16ms)
self._consumeQueue.asyncAfter(deadline: .now() + .milliseconds(16)) { [weak self] in
self?.consumeNext()
}
}
}
}
}
// MARK: - Provider Source
class CamExtensionProviderSource: NSObject, CMIOExtensionProviderSource {
private(set) var provider: CMIOExtensionProvider!
private var deviceSource: CamExtensionDeviceSource!
init(clientQueue: DispatchQueue?) {
super.init()
provider = CMIOExtensionProvider(source: self, clientQueue: clientQueue)
deviceSource = CamExtensionDeviceSource(localizedName: "PortalCam")
do {
try provider.addDevice(deviceSource.device)
} catch {
fatalError("Failed to add device: \(error.localizedDescription)")
}
}
func connect(to client: CMIOExtensionClient) throws {
// Client connected
}
func disconnect(from client: CMIOExtensionClient) {
// Client disconnected
}
var availableProperties: Set<CMIOExtensionProperty> {
return [.providerManufacturer]
}
func providerProperties(forProperties properties: Set<CMIOExtensionProperty>) throws -> CMIOExtensionProviderProperties {
let providerProperties = CMIOExtensionProviderProperties(dictionary: [:])
if properties.contains(.providerManufacturer) {
providerProperties.manufacturer = "PortalCam"
}
return providerProperties
}
func setProviderProperties(_ providerProperties: CMIOExtensionProviderProperties) throws {
// Settable properties
}
}
+11
View File
@@ -0,0 +1,11 @@
<?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>CMIOExtension</key>
<dict>
<key>CMIOExtensionMachServiceName</key>
<string>$(TeamIdentifierPrefix)$(PRODUCT_BUNDLE_IDENTIFIER)</string>
</dict>
</dict>
</plist>
@@ -0,0 +1,225 @@
import Foundation
import VideoToolbox
import CoreImage
import IOSurface
import os.log
final class NativePortalStream: NSObject, URLSessionDataDelegate {
private var session: URLSession!
private var task: URLSessionDataTask?
private var pending = Data()
private let decoder = H264Decoder()
private var dataCallbacks = 0
var onFrame: ((CVPixelBuffer) -> Void)?
var onStatus: ((String) -> Void)?
func start(host: String, token: String? = PortalAuth.token) {
stop()
dataCallbacks = 0
decoder.onFrame = { [weak self] b in self?.onFrame?(b) }
let cfg = URLSessionConfiguration.default
cfg.timeoutIntervalForRequest = .greatestFiniteMagnitude
cfg.timeoutIntervalForResource = 7 * 24 * 60 * 60
session = URLSession(configuration: cfg, delegate: self, delegateQueue: OperationQueue())
guard let u = URL(string: "https://\(host)/video.h264") else {
onStatus?("Invalid Portal address")
return
}
onStatus?("Connecting native H.264 (HTTPS)…")
var req = URLRequest(url: u)
if let token {
req.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
}
task = session.dataTask(with: req)
task?.resume()
}
func stop() {
task?.cancel()
task = nil
session?.invalidateAndCancel()
session = nil
pending.removeAll()
decoder.stop()
}
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
PortalTlsPinning.evaluate(challenge: challenge, completionHandler: completionHandler)
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
os_log(.default, "PortalCam stream: response %{public}@", String(describing: response))
completionHandler(.allow)
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
dataCallbacks += 1
if dataCallbacks <= 3 {
os_log(.default, "PortalCam stream: received %{public}d bytes", data.count)
}
pending.append(data)
consume()
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let error {
onStatus?("Stream stopped: \(error.localizedDescription)")
} else {
onStatus?("Stream completed")
}
}
private func consume() {
while let first = startCode(in: pending, from: 0) {
guard let next = startCode(in: pending, from: first.0 + first.1) else { break }
let sc = next.0
let nal = pending[(first.0 + first.1)..<sc]
decoder.decode(nal: Data(nal))
pending.removeSubrange(0..<sc)
}
if pending.count > 2 * 1024 * 1024 {
pending = pending.suffix(256 * 1024)
}
}
private func startCode(in d: Data, from: Int) -> (Int, Int)? {
if d.count < 4 || from >= d.count { return nil }
for i in from..<(d.count - 2) {
if d[i] == 0 && d[i+1] == 0 && d[i+2] == 1 { return (i, 3) }
if i + 3 < d.count && d[i] == 0 && d[i+1] == 0 && d[i+2] == 0 && d[i+3] == 1 { return (i, 4) }
}
return nil
}
}
private final class H264Decoder {
private var sps = Data(), pps = Data(), session: VTDecompressionSession?, format: CMVideoFormatDescription?
var onFrame: ((CVPixelBuffer) -> Void)?
private var decodedFrames = 0
func stop() {
if let session { VTDecompressionSessionInvalidate(session) }
session = nil
format = nil
}
func decode(nal: Data) {
guard let type = nal.first.map({ $0 & 0x1f }) else { return }
if type == 7 {
os_log(.default, "PortalCam decoder: SPS %{public}d bytes", nal.count)
sps = nal
rebuild()
return
}
if type == 8 {
os_log(.default, "PortalCam decoder: PPS %{public}d bytes", nal.count)
pps = nal
rebuild()
return
}
guard (type == 1 || type == 5), let format else { return }
var size = UInt32(nal.count).bigEndian
var avcc = Data(bytes: &size, count: 4)
avcc.append(nal)
var block: CMBlockBuffer?
avcc.withUnsafeBytes { raw in
_ = CMBlockBufferCreateWithMemoryBlock(
allocator: kCFAllocatorDefault,
memoryBlock: UnsafeMutableRawPointer(mutating: raw.baseAddress!),
blockLength: raw.count,
blockAllocator: kCFAllocatorNull,
customBlockSource: nil,
offsetToData: 0,
dataLength: raw.count,
flags: 0,
blockBufferOut: &block
)
}
guard let block else { return }
var sample: CMSampleBuffer?
var timing = CMSampleTimingInfo(
duration: CMTime(value: 1, timescale: 30),
presentationTimeStamp: CMClockGetTime(CMClockGetHostTimeClock()),
decodeTimeStamp: .invalid
)
var sizes = [avcc.count]
_ = CMSampleBufferCreateReady(
allocator: kCFAllocatorDefault,
dataBuffer: block,
formatDescription: format,
sampleCount: 1,
sampleTimingEntryCount: 1,
sampleTimingArray: &timing,
sampleSizeEntryCount: 1,
sampleSizeArray: &sizes,
sampleBufferOut: &sample
)
if let sample {
let rc = VTDecompressionSessionDecodeFrame(
session!,
sampleBuffer: sample,
flags: [],
frameRefcon: Unmanaged.passUnretained(self).toOpaque(),
infoFlagsOut: nil
)
if rc != noErr {
os_log(.error, "PortalCam decoder: decode returned %{public}d", rc)
}
}
}
private func rebuild() {
guard !sps.isEmpty, !pps.isEmpty else { return }
stop()
var f: CMVideoFormatDescription?
let rc: OSStatus = sps.withUnsafeBytes { sr in
pps.withUnsafeBytes { pr in
var ps = [sr.bindMemory(to: UInt8.self).baseAddress!, pr.bindMemory(to: UInt8.self).baseAddress!]
var lens = [sps.count, pps.count]
return CMVideoFormatDescriptionCreateFromH264ParameterSets(
allocator: kCFAllocatorDefault,
parameterSetCount: 2,
parameterSetPointers: &ps,
parameterSetSizes: &lens,
nalUnitHeaderLength: 4,
formatDescriptionOut: &f
)
}
}
guard rc == noErr, let f else {
os_log(.error, "PortalCam decoder: format creation failed %{public}d", rc)
return
}
os_log(.default, "PortalCam decoder: format ready")
format = f
let callback: VTDecompressionOutputCallback = { refCon, _, status, _, image, _, _ in
if status == noErr, let image, let refCon {
let decoder = Unmanaged<H264Decoder>.fromOpaque(refCon).takeUnretainedValue()
decoder.decodedFrames += 1
if decoder.decodedFrames <= 3 {
os_log(.default, "PortalCam decoder: frame decoded (#%{public}d)", decoder.decodedFrames)
}
decoder.onFrame?(image)
}
}
var cb = VTDecompressionOutputCallbackRecord(
decompressionOutputCallback: callback,
decompressionOutputRefCon: Unmanaged.passUnretained(self).toOpaque()
)
var attrs: CFDictionary = [
kCVPixelBufferPixelFormatTypeKey: kCVPixelFormatType_32BGRA,
kCVPixelBufferIOSurfacePropertiesKey: [:]
] as CFDictionary
var ds: VTDecompressionSession?
let rc2 = VTDecompressionSessionCreate(
allocator: kCFAllocatorDefault,
formatDescription: f,
decoderSpecification: nil,
imageBufferAttributes: attrs,
outputCallback: &cb,
decompressionSessionOut: &ds
)
os_log(.default, "PortalCam decoder: session create %{public}d", rc2)
session = ds
}
}
@@ -0,0 +1,83 @@
import Foundation
import Security
import os.log
/// Authentication shared with the containing app through the Keychain access group.
/// The extension accesses the bearer token and pinned certificate fingerprint stored by the app.
enum PortalAuth {
static let suite = "ENT9X9U544.com.kovtash.portalcam"
static let accessGroup = "ENT9X9U544.com.kovtash.portalcam"
static let service = "com.kovtash.portalcam.auth"
static let tokenKey = "portalAuthToken"
static let certKey = "portalPinnedCertSha256"
static var token: String? {
return readItem(account: tokenKey)
}
static var pinnedCertSha256: String? {
return readItem(account: certKey)
}
private static func readItem(account: String) -> String? {
// 1. Try modern Data Protection Keychain with explicit access group
let dpQuery: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: account,
kSecAttrAccessGroup as String: accessGroup,
kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitOne,
kSecUseDataProtectionKeychain as String: true
]
var item: CFTypeRef?
let dpStatus = SecItemCopyMatching(dpQuery as CFDictionary, &item)
if dpStatus == errSecSuccess, let data = item as? Data,
let value = String(data: data, encoding: .utf8) {
os_log(.default, "PortalCam extension: %{public}@ found in DP Keychain with access group", account)
return value
}
// 2. Try Data Protection Keychain without explicit access group (matches all groups in entitlement)
let defaultGroupQuery: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: account,
kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitOne,
kSecUseDataProtectionKeychain as String: true
]
var defaultItem: CFTypeRef?
let defaultStatus = SecItemCopyMatching(defaultGroupQuery as CFDictionary, &defaultItem)
if defaultStatus == errSecSuccess, let data = defaultItem as? Data,
let value = String(data: data, encoding: .utf8) {
os_log(.default, "PortalCam extension: %{public}@ found in DP Keychain default group", account)
return value
}
// 3. Try legacy file-based keychain
let legacyQuery: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: account,
kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitOne
]
var legacyItem: CFTypeRef?
let legacyStatus = SecItemCopyMatching(legacyQuery as CFDictionary, &legacyItem)
if legacyStatus == errSecSuccess, let data = legacyItem as? Data,
let value = String(data: data, encoding: .utf8) {
os_log(.default, "PortalCam extension: %{public}@ found in Legacy Keychain", account)
return value
}
// 4. Compatibility fallback: App Group UserDefaults
if let value = UserDefaults(suiteName: suite)?.string(forKey: account) {
os_log(.default, "PortalCam extension: %{public}@ found in App Group fallback", account)
return value
}
os_log(.default, "PortalCam extension: %{public}@ unavailable (DP status: %{public}d, default status: %{public}d, legacy status: %{public}d)", account, dpStatus, defaultStatus, legacyStatus)
return nil
}
}
@@ -0,0 +1,67 @@
//
// PortalTlsPinning.swift
// CamExtension
//
// TLS Certificate Pinning for Camera Extension.
//
import Foundation
import Security
import CryptoKit
import os.log
public enum PortalTlsPinning {
public static func extractLeafCert(from serverTrust: SecTrust) -> SecCertificate? {
if #available(macOS 12.0, *) {
if let chain = SecTrustCopyCertificateChain(serverTrust) as? [SecCertificate], !chain.isEmpty {
return chain[0]
}
}
let count = SecTrustGetCertificateCount(serverTrust)
guard count > 0 else { return nil }
return SecTrustGetCertificateAtIndex(serverTrust, 0)
}
public static func computeCertSha256(cert: SecCertificate) -> (data: Data, hex: String) {
let certDer = SecCertificateCopyData(cert) as Data
let digest = SHA256.hash(data: certDer)
let data = Data(digest)
let hex = data.map { String(format: "%02x", $0) }.joined()
return (data, hex)
}
public static func evaluate(
challenge: URLAuthenticationChallenge,
pinnedFingerprint: String? = nil,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
) {
guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
let serverTrust = challenge.protectionSpace.serverTrust else {
completionHandler(.cancelAuthenticationChallenge, nil)
return
}
guard let cert = extractLeafCert(from: serverTrust) else {
os_log(.error, "PortalCam Extension TLS: No certificate found in server trust chain")
completionHandler(.cancelAuthenticationChallenge, nil)
return
}
let (_, certHashHex) = computeCertSha256(cert: cert)
let targetFingerprint = pinnedFingerprint ?? PortalAuth.pinnedCertSha256
guard let pinned = targetFingerprint, !pinned.isEmpty else {
os_log(.error, "PortalCam Extension TLS: No pinned certificate configured; rejecting connection")
completionHandler(.cancelAuthenticationChallenge, nil)
return
}
guard certHashHex.caseInsensitiveCompare(pinned) == .orderedSame else {
os_log(.error, "PortalCam Extension TLS Pinning Mismatch! Expected: %{public}@, Got: %{public}@", pinned, certHashHex)
completionHandler(.cancelAuthenticationChallenge, nil)
return
}
completionHandler(.useCredential, URLCredential(trust: serverTrust))
}
}
+14
View File
@@ -0,0 +1,14 @@
//
// main.swift
// CamExtension
//
// Created by Vlad on 9/12/26.
//
import Foundation
import CoreMediaIO
let providerSource = CamExtensionProviderSource(clientQueue: nil)
CMIOExtensionProvider.startService(provider: providerSource.provider)
CFRunLoopRun()