zhuhaow/NEKit

View on GitHub
src/Socket/AdapterSocket/Shadowsocks/CryptoStreamProcessor.swift

Summary

Maintainability
A
3 hrs
Test Coverage
import Foundation

extension ShadowsocksAdapter {
    public class CryptoStreamProcessor {
        public class Factory {
            let password: String
            let algorithm: CryptoAlgorithm
            let key: Data

            public init(password: String, algorithm: CryptoAlgorithm) {
                self.password = password
                self.algorithm = algorithm
                key = CryptoHelper.getKey(password, methodType: algorithm)
            }

            public func build() -> CryptoStreamProcessor {
                return CryptoStreamProcessor(key: key, algorithm: algorithm)
            }
        }

        public weak var inputStreamProcessor: StreamObfuscater.StreamObfuscaterBase!
        public weak var outputStreamProcessor: ProtocolObfuscater.ProtocolObfuscaterBase!

        var readIV: Data!
        let key: Data
        let algorithm: CryptoAlgorithm

        var sendKey = false

        var buffer = Buffer(capacity: 0)

        lazy var writeIV: Data = {
            [unowned self] in
            CryptoHelper.getIV(self.algorithm)
            }()
        lazy var ivLength: Int = {
            [unowned self] in
            CryptoHelper.getIVLength(self.algorithm)
            }()
        lazy var encryptor: StreamCryptoProtocol = {
            [unowned self] in
            self.getCrypto(.encrypt)
            }()
        lazy var decryptor: StreamCryptoProtocol = {
            [unowned self] in
            self.getCrypto(.decrypt)
            }()

        init(key: Data, algorithm: CryptoAlgorithm) {
            self.key = key
            self.algorithm = algorithm
        }

        func encrypt(data: inout Data) {
            return encryptor.update(&data)
        }

        func decrypt(data: inout Data) {
            return decryptor.update(&data)
        }

        public func input(data: Data) throws {
            var data = data

            if readIV == nil {
                buffer.append(data: data)
                readIV = buffer.get(length: ivLength)
                guard readIV != nil else {
                    try inputStreamProcessor!.input(data: Data())
                    return
                }

                data = buffer.get() ?? Data()
                buffer.release()
            }

            decrypt(data: &data)
            try inputStreamProcessor!.input(data: data)
        }

        public func output(data: Data) {
            var data = data
            encrypt(data: &data)
            if sendKey {
                return outputStreamProcessor!.output(data: data)
            } else {
                sendKey = true
                var out = Data(capacity: data.count + writeIV.count)
                out.append(writeIV)
                out.append(data)

                return outputStreamProcessor!.output(data: out)
            }
        }

        private func getCrypto(_ operation: CryptoOperation) -> StreamCryptoProtocol {
            switch algorithm {
            case .AES128CFB, .AES192CFB, .AES256CFB:
                switch operation {
                case .decrypt:
                    return CCCrypto(operation: .decrypt, mode: .cfb, algorithm: .aes, initialVector: readIV, key: key)
                case .encrypt:
                    return CCCrypto(operation: .encrypt, mode: .cfb, algorithm: .aes, initialVector: writeIV, key: key)
                }
            case .CHACHA20:
                switch operation {
                case .decrypt:
                    return SodiumStreamCrypto(key: key, iv: readIV, algorithm: .chacha20)
                case .encrypt:
                    return SodiumStreamCrypto(key: key, iv: writeIV, algorithm: .chacha20)
                }
            case .SALSA20:
                switch operation {
                case .decrypt:
                    return SodiumStreamCrypto(key: key, iv: readIV, algorithm: .salsa20)
                case .encrypt:
                    return SodiumStreamCrypto(key: key, iv: writeIV, algorithm: .salsa20)
                }
            case .RC4MD5:
                var combinedKey = Data(capacity: key.count + ivLength)
                combinedKey.append(key)
                switch operation {
                case .decrypt:
                    combinedKey.append(readIV)
                    return CCCrypto(operation: .decrypt, mode: .rc4, algorithm: .rc4, initialVector: nil, key: MD5Hash.final(combinedKey))
                case .encrypt:
                    combinedKey.append(writeIV)
                    return CCCrypto(operation: .encrypt, mode: .rc4, algorithm: .rc4, initialVector: nil, key: MD5Hash.final(combinedKey))
                }
            }
        }
    }
}