Quellcode durchsuchen

Added DER class for decoding certificate files (to get public key).
Added a new extension for Socket class to perform TLS handshake.

Damian Kołakowski vor 9 Jahren
Ursprung
Commit
1281e45621
3 geänderte Dateien mit 359 neuen und 0 gelöschten Zeilen
  1. 130 0
      Sources/TLS/DER.swift
  2. 213 0
      Sources/TLS/Socket+TLS.swift
  3. 16 0
      XCode/Swifter.xcodeproj/project.pbxproj

+ 130 - 0
Sources/TLS/DER.swift

@@ -0,0 +1,130 @@
+//
+//  DER.swift
+//  Swifter
+//
+//  Copyright © 2016 Damian Kołakowski. All rights reserved.
+//
+
+import Foundation
+
+public struct DER {
+    
+    //
+    // Distinguished Encoding Rules (DER)
+    //
+    // https://en.wikipedia.org/wiki/X.690
+    //
+    
+    public enum DecodeError: Error { case invalidData }
+    
+    public enum DERClass: UInt8 { case universal = 0, application = 1, context = 2, priv = 3 }
+    
+    public static func decode(_ input: [UInt8]) throws -> DEROObject {
+        var iterator = input[0..<input.count].makeIterator()
+        return try parseObject(&iterator)
+    }
+    
+    public struct DEROObject {
+        public let clazz: DERClass
+        public let primitive: Bool
+        public let tag: UInt32
+        public let data: [UInt8]
+    }
+    
+    private static func parseObject(_ generator: inout IndexingIterator<ArraySlice<UInt8>>) throws -> DEROObject {
+        
+        guard let first = generator.next() else { throw DecodeError.invalidData }
+        
+        let clazz = try parseClass(first)
+        let primitive = try parsePrimitive(first)
+        let tag = try parseTag(first, &generator)
+        let data = try parseContent(&generator)
+        
+        return DEROObject(clazz: clazz, primitive: primitive, tag: tag, data: data)
+    }
+    
+    private static func parsePrimitive(_ first: UInt8) throws -> Bool {
+        return (first & 0x20) == 0
+    }
+    
+    private static func parseClass(_ first: UInt8) throws -> DERClass {
+        if let cls = DERClass(rawValue: (first & 0xC0) >> 6) {
+            return cls
+        }
+        throw DecodeError.invalidData
+    }
+    
+    private static func parseTag(_ first: UInt8, _ generator: inout IndexingIterator<ArraySlice<UInt8>>) throws -> UInt32 {
+        
+        switch first & 0x1F {
+        
+        case let short where short < 0x1F:
+            
+            return UInt32(short)
+            
+        case let long where long == 0x1F:
+            // Arbitrary allow only for UInt32 as max TAG value.
+            var buffer = [UInt8](repeating: 0, count: 4)
+            for i in 0..<buffer.count {
+                guard let b = generator.next() else { throw DecodeError.invalidData }
+                buffer[i] = (b & 0x7F)
+                if b & 0x80 != 0 { break }
+            }
+            // Validate if the last byte has leading 0 bit. We read 4 tag bytes but there could be more.
+            guard buffer[3] & 0x80 != 0 else {
+                throw DecodeError.invalidData
+            }
+            
+            return buffer.withUnsafeBufferPointer { UnsafePointer<UInt32>($0.baseAddress!).pointee.littleEndian }
+            
+        default:
+            
+            throw DecodeError.invalidData
+        }
+    }
+    
+    private static func parseContent(_ generator: inout IndexingIterator<ArraySlice<UInt8>>) throws -> [UInt8] {
+        
+        guard let length = generator.next() else { throw DecodeError.invalidData }
+        
+        switch length {
+            
+            case 0..<0x80:
+                
+                var content = [UInt8]()
+                for _ in 0..<length {
+                    guard let b = generator.next() else { throw DecodeError.invalidData }
+                    content.append(b)
+                }
+                return content
+            
+            case 0x80:
+
+                throw DecodeError.invalidData // DER - Length encoding must use the definite form
+            
+            default:
+        
+                let numberOfDigits = Int(length & 0x7F)
+                
+                if numberOfDigits > 4 { /* Arbitrary allow only for UInt32 max content size. */ throw DecodeError.invalidData }
+                
+                var buffer = [UInt8]()
+                
+                for _ in 0..<numberOfDigits {
+                    guard let b = generator.next() else { throw DecodeError.invalidData }
+                    buffer.append(b)
+                }
+                
+                buffer = buffer.reversed() + /* padding */ [UInt8](repeating: 0, count: 4 - numberOfDigits)
+                let length = buffer.withUnsafeBufferPointer { UnsafePointer<UInt32>($0.baseAddress!).pointee.littleEndian }
+            
+                var content = [UInt8]()
+                for _ in 0..<length {
+                    guard let b = generator.next() else { throw DecodeError.invalidData }
+                    content.append(b)
+                }
+                return content
+        }
+    }
+  
+}

+ 213 - 0
Sources/TLS/Socket+TLS.swift

@@ -0,0 +1,213 @@
+//
+//  Socket+TLS.swift
+//  Swifter
+//
+//  Copyright © 2016 Damian Kołakowski. All rights reserved.
+//
+
+import Foundation
+
+public enum TLSError: Error {
+    case UnknownTLSRecordType(String)
+    case UnknownHandshakeType(String)
+    case InvalidData(String)
+}
+
+public protocol HasBigEndian {
+    var bigEndian: Self { get }
+}
+
+public func nextBytes(_ socket: Socket, _ n: Int) throws -> [UInt8] {
+    var result = [UInt8]()
+    for _ in 0..<n {
+        result.append(try socket.read())
+    }
+    return result
+}
+
+public func nextGeneric2<T: HasBigEndian>(_ socket: Socket) throws -> T {
+    return try nextBytes(socket, sizeof(T.self)).withUnsafeBufferPointer() { UnsafePointer<T>($0.baseAddress!).pointee }.bigEndian
+}
+
+public func nextUInt16(_ socket: Socket) throws -> UInt16 {
+    return try nextBytes(socket, sizeof(UInt16.self)).withUnsafeBufferPointer() { UnsafePointer<UInt16>($0.baseAddress!).pointee }.bigEndian
+}
+
+public struct DataIterator {
+    
+    private var iterator: IndexingIterator<ArraySlice<UInt8>>
+    
+    public init(_ slice: ArraySlice<UInt8>) {
+        self.iterator = slice.makeIterator()
+    }
+    
+    public mutating func next(_ n: Int) -> [UInt8]? {
+        var result = [UInt8]()
+        for _ in 0..<n {
+            guard let nextByte = self.iterator.next() else {
+                return nil
+            }
+            result.append(nextByte)
+        }
+        return result
+    }
+    
+    public mutating func nextByte() -> UInt8? {
+        return self.iterator.next()
+    }
+    
+    public mutating func nextUInt16() -> UInt16? {
+        return next(sizeof(UInt16.self))?.withUnsafeBufferPointer() { UnsafePointer<UInt16>($0.baseAddress!).pointee }.bigEndian
+    }
+}
+
+extension Socket {
+    
+    public func acceptTLSClientSocket() throws -> Socket {
+        let socket = try self.acceptClientSocket()
+        let record = try readRecord(socket)
+        switch record.type {
+            case .HANDSHAKE:
+                let handshake = try readHandshake(socket)
+                switch handshake.type {
+                case Handshake.Typo.CLIENT_HELLO:
+                    let _ = try readClientHello(handshake.message)
+                default:
+                    print("default")
+                }
+                print("handshake")
+            case .CHANGE_CIPHER_SPEC:
+                print("TODO")
+            case .ALERT:
+                print("TODO")
+            case .APPLICATION_DATA:
+                print("TODO")
+        }
+        return socket
+    }
+    
+    public struct Record {
+        
+        public enum Typo: UInt8 { case CHANGE_CIPHER_SPEC = 20, ALERT = 21, HANDSHAKE = 22, APPLICATION_DATA = 23 }
+        
+        public var type: Typo
+        public var version: UInt16
+        public var length: UInt16
+    }
+    
+    public func readRecord(_ socket: Socket) throws -> Record {
+
+        let type = try socket.read()
+        
+        guard let validType = Record.Typo(rawValue: type) else {
+            throw TLSError.UnknownTLSRecordType("Unknown record type: \(type)")
+        }
+        
+        let version = try nextUInt16(socket)
+        let lengthh = try nextUInt16(socket)
+        
+        return Record(type: validType, version: version, length: lengthh)
+    }
+    
+    public struct Handshake {
+        
+        public enum Typo: UInt8 {
+            case HELLO_REQUEST = 0, CLIENT_HELLO = 1, SERVER_HELLO = 2, FINISHED = 20
+            case CERTIFICATE = 11, SERVER_KEY_EXCHANGE = 12, CERTIFICATE_REQUEST = 13
+            case SERVER_DONE = 14, CERTIFICATE_VERIFY = 15, CLIENT_KEY_EXCHANGE = 16
+        }
+        
+        public var type = Typo.HELLO_REQUEST
+        public var message = [UInt8]()
+    }
+    
+    public func readHandshake(_ socket: Socket) throws -> Handshake {
+        
+        let type = try socket.read()
+        
+        guard let validType = Handshake.Typo(rawValue: type) else {
+            throw TLSError.UnknownHandshakeType("Unknown record type: \(type)")
+        }
+        
+        var handshake = Handshake()
+        
+        handshake.type = validType
+        
+        let length2 = try socket.read()
+        let length1 = try socket.read()
+        let length0 = try socket.read()
+        
+        let length = [length0, length1, length2, 0].withUnsafeBufferPointer() { UnsafePointer<UInt32>($0.baseAddress!).pointee }.littleEndian
+        
+        while UInt32(handshake.message.count) < length { handshake.message.append(try socket.read()) }
+        
+        return handshake
+    }
+    
+    public struct ClientHello {
+        
+        public var version: UInt16 = 0
+        public var random = [UInt8]()
+        public var sessionId = [UInt8]()
+        
+        public var cipherSuites = [UInt16]()
+        public var compressionMethods = [UInt8]()
+        
+        public var extensions = [(id: UInt16, data: [UInt8])]()
+    }
+    
+    
+    public func readClientHello(_ data: [UInt8]) throws -> ClientHello {
+
+        var iterator = DataIterator(data[0..<data.count])
+        
+        guard let version = iterator.nextUInt16() else { throw TLSError.InvalidData("No version field.") }
+        
+        guard let random = iterator.next(32) else { throw TLSError.InvalidData("No random field.") }
+        
+        guard let sessionIdLen = iterator.nextByte() else { throw TLSError.InvalidData("No Session Id Length field.") }
+        
+        guard let sessionId = iterator.next(Int(sessionIdLen)) else { throw TLSError.InvalidData("No Session Id field.") }
+        
+        guard let cipherSuitesCount = iterator.nextUInt16(), cipherSuitesCount % 2 == 0 else {
+            throw TLSError.InvalidData("No Cipher Suites Count field.")
+        }
+        
+        var cipherSuites = [UInt16]()
+        
+        for _ in 0..<cipherSuitesCount/2 {
+            guard let cipherSuiteId = iterator.nextUInt16() else { throw TLSError.InvalidData("No Cipher Suite Id field.") }
+            cipherSuites.append(cipherSuiteId)
+        }
+        guard let compressionMethodsCount = iterator.nextByte() else {
+            throw TLSError.InvalidData("No first byte of the version field in Hello message \(data)")
+        }
+        guard let compressionMethods = iterator.next(Int(compressionMethodsCount)) else {
+            throw TLSError.InvalidData("No Compression Method field.")
+        }
+        
+        guard let extensionsLength = iterator.nextUInt16() else { throw TLSError.InvalidData("No Extension Length field.") }
+        guard let extensionsData = iterator.next(Int(extensionsLength)) else { throw TLSError.InvalidData("No Extension Data field.") }
+        
+        var extensionDataIterator = DataIterator(extensionsData[0..<extensionsData.count])
+        
+        var extensions = [(id: UInt16, data: [UInt8])]()
+        
+        while true {
+            guard let extensionId = extensionDataIterator.nextUInt16() else {
+                break
+            }
+            guard let extensionDataLength = extensionDataIterator.nextUInt16() else {
+                throw TLSError.InvalidData("No first byte of the version field in Hello message \(data)")
+            }
+            guard let extensionData = extensionDataIterator.next(Int(extensionDataLength)) else {
+                throw TLSError.InvalidData("No first byte of the version field in Hello message \(data)")
+            }
+            extensions.append((id: extensionId, data: extensionData))
+        }
+        
+        return ClientHello(version: version, random: random, sessionId: sessionId,
+                           cipherSuites: cipherSuites, compressionMethods: compressionMethods, extensions: extensions)
+    }
+}
+

+ 16 - 0
XCode/Swifter.xcodeproj/project.pbxproj

@@ -80,6 +80,9 @@
 		7C4785EA1C71D15600A9FE73 /* SwifterTestsWebSocketSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C4785E81C71D15600A9FE73 /* SwifterTestsWebSocketSession.swift */; };
 		7C5915221C92A99300D884BC /* SwifterTestsReflection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C5915211C92A99300D884BC /* SwifterTestsReflection.swift */; };
 		7C5915231C92A99300D884BC /* SwifterTestsReflection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C5915211C92A99300D884BC /* SwifterTestsReflection.swift */; };
+		7C5CFD921D5E353D001E1016 /* Socket+TLS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C5CFD911D5E353D001E1016 /* Socket+TLS.swift */; };
+		7C5CFD931D5E353D001E1016 /* Socket+TLS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C5CFD911D5E353D001E1016 /* Socket+TLS.swift */; };
+		7C5CFD941D5E353D001E1016 /* Socket+TLS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C5CFD911D5E353D001E1016 /* Socket+TLS.swift */; };
 		7C5F78EF1D54BB5600C514AA /* SwifterTestsAES128.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C5F78EE1D54BB5600C514AA /* SwifterTestsAES128.swift */; };
 		7C5F78F01D54BB5600C514AA /* SwifterTestsAES128.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C5F78EE1D54BB5600C514AA /* SwifterTestsAES128.swift */; };
 		7C5F78F91D54D24B00C514AA /* SwifterTestsRC4.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C5F78F51D54D21600C514AA /* SwifterTestsRC4.swift */; };
@@ -160,6 +163,9 @@
 		7CDF26FF1D5CD64600666F69 /* SHA1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CDF26E31D5CD64600666F69 /* SHA1.swift */; };
 		7CE0B9EF1D5BBAAD0070D292 /* SwifterTestsHMAC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CE0B9EE1D5BBAAD0070D292 /* SwifterTestsHMAC.swift */; };
 		7CE0B9F01D5BBAAD0070D292 /* SwifterTestsHMAC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CE0B9EE1D5BBAAD0070D292 /* SwifterTestsHMAC.swift */; };
+		7CECB5931D5F27040039A704 /* DER.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CECB5921D5F27040039A704 /* DER.swift */; };
+		7CECB5941D5F27040039A704 /* DER.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CECB5921D5F27040039A704 /* DER.swift */; };
+		7CECB5951D5F27040039A704 /* DER.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CECB5921D5F27040039A704 /* DER.swift */; };
 		7CFEA3541D5DFDA9009A9BF7 /* SHA256.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CFEA3531D5DFDA9009A9BF7 /* SHA256.swift */; };
 		7CFEA3551D5DFDA9009A9BF7 /* SHA256.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CFEA3531D5DFDA9009A9BF7 /* SHA256.swift */; };
 		7CFEA3561D5DFDA9009A9BF7 /* SHA256.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CFEA3531D5DFDA9009A9BF7 /* SHA256.swift */; };
@@ -255,6 +261,7 @@
 		7C3945631D256FDA003EEABA /* Scopes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Scopes.swift; sourceTree = "<group>"; };
 		7C4785E81C71D15600A9FE73 /* SwifterTestsWebSocketSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwifterTestsWebSocketSession.swift; sourceTree = "<group>"; };
 		7C5915211C92A99300D884BC /* SwifterTestsReflection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwifterTestsReflection.swift; sourceTree = "<group>"; };
+		7C5CFD911D5E353D001E1016 /* Socket+TLS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Socket+TLS.swift"; sourceTree = "<group>"; };
 		7C5F78EE1D54BB5600C514AA /* SwifterTestsAES128.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwifterTestsAES128.swift; sourceTree = "<group>"; };
 		7C5F78F51D54D21600C514AA /* SwifterTestsRC4.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwifterTestsRC4.swift; sourceTree = "<group>"; };
 		7C5F79031D55F44500C514AA /* SwifterTestsRSA.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwifterTestsRSA.swift; sourceTree = "<group>"; };
@@ -298,6 +305,7 @@
 		7CDF26E21D5CD64600666F69 /* RSA.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RSA.swift; sourceTree = "<group>"; };
 		7CDF26E31D5CD64600666F69 /* SHA1.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SHA1.swift; sourceTree = "<group>"; };
 		7CE0B9EE1D5BBAAD0070D292 /* SwifterTestsHMAC.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwifterTestsHMAC.swift; sourceTree = "<group>"; };
+		7CECB5921D5F27040039A704 /* DER.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DER.swift; sourceTree = "<group>"; };
 		7CFEA3531D5DFDA9009A9BF7 /* SHA256.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SHA256.swift; sourceTree = "<group>"; };
 		98630C061A1C9A9D00478D08 /* login.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = login.html; sourceTree = "<group>"; };
 /* End PBXFileReference section */
@@ -575,6 +583,8 @@
 				7CDF26E31D5CD64600666F69 /* SHA1.swift */,
 				7C00AABD1D5D374700EDA547 /* HEX.swift */,
 				7CFEA3531D5DFDA9009A9BF7 /* SHA256.swift */,
+				7C5CFD911D5E353D001E1016 /* Socket+TLS.swift */,
+				7CECB5921D5F27040039A704 /* DER.swift */,
 			);
 			path = TLS;
 			sourceTree = "<group>";
@@ -907,9 +917,11 @@
 				7C3196071CC2C68F00DF5406 /* File.swift in Sources */,
 				7CDF26E51D5CD64600666F69 /* AES128.swift in Sources */,
 				7CDF26F11D5CD64600666F69 /* MD5.swift in Sources */,
+				7CECB5931D5F27040039A704 /* DER.swift in Sources */,
 				7C3196041CC2C68F00DF5406 /* DemoServer.swift in Sources */,
 				7C3196281CC2C68F00DF5406 /* Reflection.swift in Sources */,
 				7C3945641D256FDA003EEABA /* Scopes.swift in Sources */,
+				7C5CFD921D5E353D001E1016 /* Socket+TLS.swift in Sources */,
 				7CDF26FD1D5CD64600666F69 /* SHA1.swift in Sources */,
 				7C3196161CC2C68F00DF5406 /* HttpRequest.swift in Sources */,
 				7C5F79071D5627EE00C514AA /* Socket+Server.swift in Sources */,
@@ -946,9 +958,11 @@
 				7C3196081CC2C68F00DF5406 /* File.swift in Sources */,
 				7CDF26E61D5CD64600666F69 /* AES128.swift in Sources */,
 				7CDF26F21D5CD64600666F69 /* MD5.swift in Sources */,
+				7CECB5941D5F27040039A704 /* DER.swift in Sources */,
 				7C3196051CC2C68F00DF5406 /* DemoServer.swift in Sources */,
 				7C3196291CC2C68F00DF5406 /* Reflection.swift in Sources */,
 				7C3945651D256FDA003EEABA /* Scopes.swift in Sources */,
+				7C5CFD931D5E353D001E1016 /* Socket+TLS.swift in Sources */,
 				7CDF26FE1D5CD64600666F69 /* SHA1.swift in Sources */,
 				7C3196171CC2C68F00DF5406 /* HttpRequest.swift in Sources */,
 				7C5F79081D5627EE00C514AA /* Socket+Server.swift in Sources */,
@@ -1018,9 +1032,11 @@
 				7C31961E1CC2C68F00DF5406 /* HttpRouter.swift in Sources */,
 				7CDF26E71D5CD64600666F69 /* AES128.swift in Sources */,
 				7CDF26F31D5CD64600666F69 /* MD5.swift in Sources */,
+				7CECB5951D5F27040039A704 /* DER.swift in Sources */,
 				7C3196091CC2C68F00DF5406 /* File.swift in Sources */,
 				7C3196061CC2C68F00DF5406 /* DemoServer.swift in Sources */,
 				7C31962A1CC2C68F00DF5406 /* Reflection.swift in Sources */,
+				7C5CFD941D5E353D001E1016 /* Socket+TLS.swift in Sources */,
 				7CDF26FF1D5CD64600666F69 /* SHA1.swift in Sources */,
 				7C3196181CC2C68F00DF5406 /* HttpRequest.swift in Sources */,
 				7C5F79091D5627EE00C514AA /* Socket+Server.swift in Sources */,