add chacha20

This commit is contained in:
anlicheng 2026-03-17 16:19:51 +08:00
parent cb33d81428
commit 8538e89f92
9 changed files with 216 additions and 85 deletions

View File

@ -31,10 +31,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
// //
let rsaCipher = try! CCRSACipher(keySize: 1024) let rsaCipher = try! CCRSACipher(keySize: 1024)
let aesChiper = CCAESChiper()
self.rootTask = Task { self.rootTask = Task {
self.contextActor = SDLContextActor(provider: self, config: config, rsaCipher: rsaCipher, aesCipher: aesChiper) self.contextActor = SDLContextActor(provider: self, config: config, rsaCipher: rsaCipher)
await self.contextActor?.start() await self.contextActor?.start()
completionHandler(nil) completionHandler(nil)
} }
@ -79,52 +77,4 @@ extension PacketTunnelProvider {
return interfaces.first {$0.name == "en0"} return interfaces.first {$0.name == "en0"}
}() }()
struct CCRSACipher: RSACipher {
var pubKey: String
let privateKeyDER: Data
init(keySize: Int) throws {
let (privateKey, publicKey) = try Self.loadKeys(keySize: keySize)
let privKeyStr = SwKeyConvert.PrivateKey.derToPKCS1PEM(privateKey)
self.pubKey = SwKeyConvert.PublicKey.derToPKCS8PEM(publicKey)
self.privateKeyDER = try SwKeyConvert.PrivateKey.pemToPKCS1DER(privKeyStr)
}
public func decode(data: Data) throws -> Data {
let tag = Data()
let (decryptedData, _) = try CC.RSA.decrypt(data, derKey: self.privateKeyDER, tag: tag, padding: .pkcs1, digest: .none)
return decryptedData
}
private static func loadKeys(keySize: Int) throws -> (Data, Data) {
if let privateKey = UserDefaults.standard.data(forKey: "privateKey"),
let publicKey = UserDefaults.standard.data(forKey: "publicKey") {
return (privateKey, publicKey)
} else {
let (privateKey, publicKey) = try CC.RSA.generateKeyPair(keySize)
UserDefaults.standard.setValue(privateKey, forKey: "privateKey")
UserDefaults.standard.setValue(publicKey, forKey: "publicKey")
return (privateKey, publicKey)
}
}
}
struct CCAESChiper: AESCipher {
func decypt(aesKey: Data, data: Data) throws -> Data {
let ivData = Data(aesKey.prefix(16))
return try CC.crypt(.decrypt, blockMode: .cbc, algorithm: .aes, padding: .pkcs7Padding, data: data, key: aesKey, iv: ivData)
}
func encrypt(aesKey: Data, data: Data) throws -> Data {
let ivData = Data(aesKey.prefix(16))
return try CC.crypt(.encrypt, blockMode: .cbc, algorithm: .aes, padding: .pkcs7Padding, data: data, key: aesKey, iv: ivData)
}
}
} }

View File

@ -1,13 +0,0 @@
//
// AESCipher.swift
// sdlan
//
// Created by on 2025/7/14.
//
import Foundation
public protocol AESCipher {
func decypt(aesKey: Data, data: Data) throws -> Data
func encrypt(aesKey: Data, data: Data) throws -> Data
}

View File

@ -26,15 +26,13 @@ actor SDLContextActor {
var natType: SDLNATProberActor.NatType = .blocked var natType: SDLNATProberActor.NatType = .blocked
// AES // AES
nonisolated let aesCipher: AESCipher private var dataCipher: CCDataCipher?
// aes
private var aesKey: Data?
// session token // session token
private var sessionToken: Data? private var sessionToken: Data?
// rsa, public_key // rsa, public_key
//
nonisolated let rsaCipher: RSACipher nonisolated let rsaCipher: RSACipher
// //
@ -84,11 +82,10 @@ actor SDLContextActor {
// //
private var registerTask: Task<Void, Never>? private var registerTask: Task<Void, Never>?
public init(provider: NEPacketTunnelProvider, config: SDLConfiguration, rsaCipher: RSACipher, aesCipher: AESCipher) { public init(provider: NEPacketTunnelProvider, config: SDLConfiguration, rsaCipher: RSACipher) {
self.provider = provider self.provider = provider
self.config = config self.config = config
self.rsaCipher = rsaCipher self.rsaCipher = rsaCipher
self.aesCipher = aesCipher
self.puncherActor = SDLPuncherActor() self.puncherActor = SDLPuncherActor()
self.proberActor = SDLNATProberActor(addressArray: config.stunProbeSocketAddressArray) self.proberActor = SDLNATProberActor(addressArray: config.stunProbeSocketAddressArray)
@ -392,10 +389,28 @@ actor SDLContextActor {
private func handleRegisterSuperAck(registerSuperAck: SDLRegisterSuperAck) async { private func handleRegisterSuperAck(registerSuperAck: SDLRegisterSuperAck) async {
// rsa // rsa
self.aesKey = try! self.rsaCipher.decode(data: Data(registerSuperAck.aesKey)) guard let key = try? self.rsaCipher.decode(data: Data(registerSuperAck.key)) else {
SDLLogger.shared.log("[SDLContext] registerSuperAck invalid key", level: .error)
self.provider.cancelTunnelWithError(SDLError.invalidKey)
return
}
let algorithm = registerSuperAck.algorithm.lowercased()
let regionId = registerSuperAck.regionID
self.sessionToken = registerSuperAck.sessionToken self.sessionToken = registerSuperAck.sessionToken
SDLLogger.shared.log("[SDLContext] get registerSuperAck, aes_key len: \(self.aesKey!.count)", level: .info) switch algorithm {
case "aes":
self.dataCipher = CCAESChiper(key: key)
case "chacha20":
self.dataCipher = CCChaCha20Cipher(regionId: regionId, keyData: key)
default:
SDLLogger.shared.log("[SDLContext] registerSuperAck invalid algorithm \(algorithm)", level: .error)
self.provider.cancelTunnelWithError(SDLError.unsupportedAlgorithm(algorithm: algorithm))
return
}
SDLLogger.shared.log("[SDLContext] get registerSuperAck, aes_key len: \(key.count)", level: .info)
// tun // tun
do { do {
try await self.setNetworkSettings(networkAddress: self.config.networkAddress, dnsServer: SDLDNSClient.Helper.dnsServer) try await self.setNetworkSettings(networkAddress: self.config.networkAddress, dnsServer: SDLDNSClient.Helper.dnsServer)
@ -511,7 +526,7 @@ actor SDLContextActor {
} }
private func handleHoleData(data: SDLData) async throws { private func handleHoleData(data: SDLData) async throws {
guard let aesKey = self.aesKey else { guard let dataCipher = self.dataCipher else {
return return
} }
@ -521,7 +536,7 @@ actor SDLContextActor {
return return
} }
guard let decyptedData = try? self.aesCipher.decypt(aesKey: aesKey, data: Data(data.data)) else { guard let decyptedData = try? dataCipher.decrypt(cipherText: Data(data.data)) else {
return return
} }
@ -683,7 +698,7 @@ actor SDLContextActor {
let networkAddr = self.config.networkAddress let networkAddr = self.config.networkAddress
// 2 // 2
let layerPacket = LayerPacket(dstMac: dstMac, srcMac: networkAddr.mac, type: type, data: data) let layerPacket = LayerPacket(dstMac: dstMac, srcMac: networkAddr.mac, type: type, data: data)
guard let udpHole = self.udpHole, let aesKey = self.aesKey, let encodedPacket = try? self.aesCipher.encrypt(aesKey: aesKey, data: layerPacket.marshal()) else { guard let udpHole = self.udpHole, let dataCipher = self.dataCipher, let encodedPacket = try? dataCipher.encrypt(plainText: layerPacket.marshal()) else {
return return
} }

View File

@ -0,0 +1,27 @@
//
// CCAESChiper.swift
// punchnet
//
// Created by on 2026/3/17.
//
import Foundation
struct CCAESChiper: CCDataCipher {
private let aesKey: Data
init(key: Data) {
self.aesKey = key
}
func decrypt(cipherText: Data) throws -> Data {
let ivData = Data(aesKey.prefix(16))
return try CC.crypt(.decrypt, blockMode: .cbc, algorithm: .aes, padding: .pkcs7Padding, data: cipherText, key: aesKey, iv: ivData)
}
func encrypt(plainText: Data) throws -> Data {
let ivData = Data(aesKey.prefix(16))
return try CC.crypt(.encrypt, blockMode: .cbc, algorithm: .aes, padding: .pkcs7Padding, data: plainText, key: aesKey, iv: ivData)
}
}

View File

@ -0,0 +1,77 @@
//
// NonceGenerator.swift
// punchnet
//
// Created by on 2026/3/17.
//
import Foundation
import CryptoKit
/// ChaCha20-Poly1305
struct CCChaCha20Cipher: CCDataCipher {
private let key: SymmetricKey
private let nonceGenerator: NonceGenerator
init(regionId: UInt32, keyData: Data) {
self.key = SymmetricKey(data: keyData)
self.nonceGenerator = NonceGenerator(regionId: regionId)
}
///
func encrypt(plainText: Data) throws -> Data {
let nonce = nonceGenerator.nextNonceData()
let sealedBox = try ChaChaPoly.seal(plainText, using: key, nonce: .init(data: nonce))
return sealedBox.combined
}
///
func decrypt(cipherText: Data) throws -> Data {
let sealedBox = try ChaChaPoly.SealedBox(combined: cipherText)
return try ChaChaPoly.open(sealedBox, using: key)
}
}
extension CCChaCha20Cipher {
/// NonceServerRange + + counter
final class NonceGenerator {
private let locker = NSLock()
private let regionId: UInt32 // 32-bit
private var counter: UInt64 = 0 // counter
init(regionId: UInt32) {
self.regionId = regionId
}
/// 64-bit Nonce
func nextNonceData() -> Data {
locker.lock()
defer {
locker.unlock()
}
let nowMillis = UInt64(Date().timeIntervalSince1970 * 1000)
// 40bit, id24
let timeMask: UInt64 = (1 << 40) - 1
let timeLow = nowMillis & timeMask
// Nonce
let counterMask: UInt64 = (1 << 24) - 1
let nonce = (timeLow << 24) | (counter & counterMask)
// counter
self.counter = (self.counter + 1) & counterMask // 0
var data = Data()
// region: UInt32 -> 4
data.append(contentsOf: withUnsafeBytes(of: regionId.bigEndian, Array.init))
// nonce: UInt64 -> 8
data.append(contentsOf: withUnsafeBytes(of: nonce.bigEndian, Array.init))
return data
}
}
}

View File

@ -0,0 +1,13 @@
//
// AESCipher.swift
// sdlan
//
// Created by on 2025/7/14.
//
import Foundation
public protocol CCDataCipher {
func decrypt(cipherText: Data) throws -> Data
func encrypt(plainText: Data) throws -> Data
}

View File

@ -0,0 +1,41 @@
//
// CCRSACipher.swift
// punchnet
//
// Created by on 2026/3/17.
//
import Foundation
struct CCRSACipher: RSACipher {
var pubKey: String
let privateKeyDER: Data
init(keySize: Int) throws {
let (privateKey, publicKey) = try Self.loadKeys(keySize: keySize)
let privKeyStr = SwKeyConvert.PrivateKey.derToPKCS1PEM(privateKey)
self.pubKey = SwKeyConvert.PublicKey.derToPKCS8PEM(publicKey)
self.privateKeyDER = try SwKeyConvert.PrivateKey.pemToPKCS1DER(privKeyStr)
}
public func decode(data: Data) throws -> Data {
let tag = Data()
let (decryptedData, _) = try CC.RSA.decrypt(data, derKey: self.privateKeyDER, tag: tag, padding: .pkcs1, digest: .none)
return decryptedData
}
private static func loadKeys(keySize: Int) throws -> (Data, Data) {
if let privateKey = UserDefaults.standard.data(forKey: "privateKey"),
let publicKey = UserDefaults.standard.data(forKey: "publicKey") {
return (privateKey, publicKey)
} else {
let (privateKey, publicKey) = try CC.RSA.generateKeyPair(keySize)
UserDefaults.standard.setValue(privateKey, forKey: "privateKey")
UserDefaults.standard.setValue(publicKey, forKey: "publicKey")
return (privateKey, publicKey)
}
}
}

View File

@ -8,4 +8,7 @@
enum SDLError: Error { enum SDLError: Error {
case socketClosed case socketClosed
case socketError case socketError
case invalidKey
case unsupportedAlgorithm(algorithm: String)
} }

View File

@ -127,7 +127,13 @@ struct SDLRegisterSuperAck: @unchecked Sendable {
var pktID: UInt32 = 0 var pktID: UInt32 = 0
var aesKey: Data = Data() /// aes, chacha20
var algorithm: String = String()
var key: Data = Data()
/// chacha20使
var regionID: UInt32 = 0
var sessionToken: Data = Data() var sessionToken: Data = Data()
@ -268,7 +274,7 @@ struct SDLPolicyResponse: @unchecked Sendable {
/// ; /// ;
var version: UInt32 = 0 var version: UInt32 = 0
/// 4+1+2 /// 1 + 2, : <<Proto:8, Port:16>> ; allowdeny
var rules: Data = Data() var rules: Data = Data()
var unknownFields = SwiftProtobuf.UnknownStorage() var unknownFields = SwiftProtobuf.UnknownStorage()
@ -725,8 +731,10 @@ extension SDLRegisterSuperAck: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl
static let protoMessageName: String = "SDLRegisterSuperAck" static let protoMessageName: String = "SDLRegisterSuperAck"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .standard(proto: "pkt_id"), 1: .standard(proto: "pkt_id"),
2: .standard(proto: "aes_key"), 2: .same(proto: "algorithm"),
3: .standard(proto: "session_token"), 3: .same(proto: "key"),
4: .standard(proto: "region_id"),
5: .standard(proto: "session_token"),
] ]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws { mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -736,8 +744,10 @@ extension SDLRegisterSuperAck: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl
// enabled. https://github.com/apple/swift-protobuf/issues/1034 // enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber { switch fieldNumber {
case 1: try { try decoder.decodeSingularUInt32Field(value: &self.pktID) }() case 1: try { try decoder.decodeSingularUInt32Field(value: &self.pktID) }()
case 2: try { try decoder.decodeSingularBytesField(value: &self.aesKey) }() case 2: try { try decoder.decodeSingularStringField(value: &self.algorithm) }()
case 3: try { try decoder.decodeSingularBytesField(value: &self.sessionToken) }() case 3: try { try decoder.decodeSingularBytesField(value: &self.key) }()
case 4: try { try decoder.decodeSingularUInt32Field(value: &self.regionID) }()
case 5: try { try decoder.decodeSingularBytesField(value: &self.sessionToken) }()
default: break default: break
} }
} }
@ -747,18 +757,26 @@ extension SDLRegisterSuperAck: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl
if self.pktID != 0 { if self.pktID != 0 {
try visitor.visitSingularUInt32Field(value: self.pktID, fieldNumber: 1) try visitor.visitSingularUInt32Field(value: self.pktID, fieldNumber: 1)
} }
if !self.aesKey.isEmpty { if !self.algorithm.isEmpty {
try visitor.visitSingularBytesField(value: self.aesKey, fieldNumber: 2) try visitor.visitSingularStringField(value: self.algorithm, fieldNumber: 2)
}
if !self.key.isEmpty {
try visitor.visitSingularBytesField(value: self.key, fieldNumber: 3)
}
if self.regionID != 0 {
try visitor.visitSingularUInt32Field(value: self.regionID, fieldNumber: 4)
} }
if !self.sessionToken.isEmpty { if !self.sessionToken.isEmpty {
try visitor.visitSingularBytesField(value: self.sessionToken, fieldNumber: 3) try visitor.visitSingularBytesField(value: self.sessionToken, fieldNumber: 5)
} }
try unknownFields.traverse(visitor: &visitor) try unknownFields.traverse(visitor: &visitor)
} }
static func ==(lhs: SDLRegisterSuperAck, rhs: SDLRegisterSuperAck) -> Bool { static func ==(lhs: SDLRegisterSuperAck, rhs: SDLRegisterSuperAck) -> Bool {
if lhs.pktID != rhs.pktID {return false} if lhs.pktID != rhs.pktID {return false}
if lhs.aesKey != rhs.aesKey {return false} if lhs.algorithm != rhs.algorithm {return false}
if lhs.key != rhs.key {return false}
if lhs.regionID != rhs.regionID {return false}
if lhs.sessionToken != rhs.sessionToken {return false} if lhs.sessionToken != rhs.sessionToken {return false}
if lhs.unknownFields != rhs.unknownFields {return false} if lhs.unknownFields != rhs.unknownFields {return false}
return true return true