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 aesChiper = CCAESChiper()
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()
completionHandler(nil)
}
@ -79,52 +77,4 @@ extension PacketTunnelProvider {
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
// AES
nonisolated let aesCipher: AESCipher
// aes
private var aesKey: Data?
private var dataCipher: CCDataCipher?
// session token
private var sessionToken: Data?
// rsa, public_key
//
nonisolated let rsaCipher: RSACipher
//
@ -84,11 +82,10 @@ actor SDLContextActor {
//
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.config = config
self.rsaCipher = rsaCipher
self.aesCipher = aesCipher
self.puncherActor = SDLPuncherActor()
self.proberActor = SDLNATProberActor(addressArray: config.stunProbeSocketAddressArray)
@ -392,10 +389,28 @@ actor SDLContextActor {
private func handleRegisterSuperAck(registerSuperAck: SDLRegisterSuperAck) async {
// 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
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
do {
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 {
guard let aesKey = self.aesKey else {
guard let dataCipher = self.dataCipher else {
return
}
@ -521,7 +536,7 @@ actor SDLContextActor {
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
}
@ -683,7 +698,7 @@ actor SDLContextActor {
let networkAddr = self.config.networkAddress
// 2
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
}

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 {
case socketClosed
case socketError
case invalidKey
case unsupportedAlgorithm(algorithm: String)
}

View File

@ -127,7 +127,13 @@ struct SDLRegisterSuperAck: @unchecked Sendable {
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()
@ -268,7 +274,7 @@ struct SDLPolicyResponse: @unchecked Sendable {
/// ;
var version: UInt32 = 0
/// 4+1+2
/// 1 + 2, : <<Proto:8, Port:16>> ; allowdeny
var rules: Data = Data()
var unknownFields = SwiftProtobuf.UnknownStorage()
@ -725,8 +731,10 @@ extension SDLRegisterSuperAck: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl
static let protoMessageName: String = "SDLRegisterSuperAck"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .standard(proto: "pkt_id"),
2: .standard(proto: "aes_key"),
3: .standard(proto: "session_token"),
2: .same(proto: "algorithm"),
3: .same(proto: "key"),
4: .standard(proto: "region_id"),
5: .standard(proto: "session_token"),
]
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
switch fieldNumber {
case 1: try { try decoder.decodeSingularUInt32Field(value: &self.pktID) }()
case 2: try { try decoder.decodeSingularBytesField(value: &self.aesKey) }()
case 3: try { try decoder.decodeSingularBytesField(value: &self.sessionToken) }()
case 2: try { try decoder.decodeSingularStringField(value: &self.algorithm) }()
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
}
}
@ -747,18 +757,26 @@ extension SDLRegisterSuperAck: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl
if self.pktID != 0 {
try visitor.visitSingularUInt32Field(value: self.pktID, fieldNumber: 1)
}
if !self.aesKey.isEmpty {
try visitor.visitSingularBytesField(value: self.aesKey, fieldNumber: 2)
if !self.algorithm.isEmpty {
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 {
try visitor.visitSingularBytesField(value: self.sessionToken, fieldNumber: 3)
try visitor.visitSingularBytesField(value: self.sessionToken, fieldNumber: 5)
}
try unknownFields.traverse(visitor: &visitor)
}
static func ==(lhs: SDLRegisterSuperAck, rhs: SDLRegisterSuperAck) -> Bool {
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.unknownFields != rhs.unknownFields {return false}
return true