From 1d1e26835cb10c46299022be5d9fb1eca33943e9 Mon Sep 17 00:00:00 2001 From: Orkhan Alikhanov Date: Wed, 14 Mar 2018 00:47:59 +0400 Subject: [PATCH] Added ssl/tls support, closes #4 --- .travis.yml | 8 ++++- Http.swift.podspec | 4 +-- HttpSwift.xcodeproj/project.pbxproj | 4 +++ LICENSE | 2 +- Package.swift | 4 +-- Podfile | 10 +++---- README.md | 5 ++-- Sources/Server.swift | 32 ++++++++++++++++++-- Tests/Http.swift.csr | 21 +++++++++++++ Tests/Http.swift.key | 27 +++++++++++++++++ Tests/Http.swift.pfx | Bin 0 -> 2501 bytes Tests/HttpSwiftTests/HttpSwiftTests.swift | 35 +++++++++++++++++++++- 12 files changed, 134 insertions(+), 18 deletions(-) create mode 100644 Tests/Http.swift.csr create mode 100644 Tests/Http.swift.key create mode 100644 Tests/Http.swift.pfx diff --git a/.travis.yml b/.travis.yml index dbd93ab..05ad13b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,8 +4,14 @@ matrix: - os: linux dist: trusty sudo: required + addons: + apt: + sources: + - sourceline: 'deb [trusted=yes] http://apt.orkhanalikhanov.com ./' + packages: + - libressl install: eval "$(curl -sL https://gist.githubusercontent.com/kylef/5c0475ff02b7c7671d2a/raw/9f442512a46d7a2af7b850d65a7e9bd31edfb09b/swiftenv-install.sh)" - script: swift test + script: env LD_LIBRARY_PATH='/usr/local/lib:/usr/local/opt/libressl/lib:$LD_LIBRARY_PATH' swift test - os: osx osx_image: xcode9 diff --git a/Http.swift.podspec b/Http.swift.podspec index cde0256..c266c8d 100644 --- a/Http.swift.podspec +++ b/Http.swift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'Http.swift' - s.version = '2.0.0' + s.version = '2.1.0' s.summary = 'A tiny http server engine written in swift.' s.homepage = 'https://github.com/BiAtoms/Http.swift' s.license = { :type => 'MIT', :file => 'LICENSE' } @@ -12,5 +12,5 @@ Pod::Spec.new do |s| s.osx.deployment_target = '10.9' s.tvos.deployment_target = '9.0' s.source_files = 'Sources/*.swift' - s.dependency 'Socket.swift', '~> 2.0' + s.dependency 'Socket.swift', '~> 2.2' end diff --git a/HttpSwift.xcodeproj/project.pbxproj b/HttpSwift.xcodeproj/project.pbxproj index d63eda0..5eaaee1 100644 --- a/HttpSwift.xcodeproj/project.pbxproj +++ b/HttpSwift.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 4F923E83F798EE11C2FF1A02 /* Pods_HttpSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BA7750543CF7C03E03B5F6EB /* Pods_HttpSwift.framework */; }; + 9D9773D72056746200DA7E4F /* Http.swift.pfx in Resources */ = {isa = PBXBuildFile; fileRef = 9D9773D62056745E00DA7E4F /* Http.swift.pfx */; }; 9DDDF8501F0C14CF00C3D4A6 /* HttpSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9DDDF8461F0C14CF00C3D4A6 /* HttpSwift.framework */; }; 9DDDF8551F0C14CF00C3D4A6 /* HttpSwiftTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DDDF8541F0C14CF00C3D4A6 /* HttpSwiftTests.swift */; }; 9DDDF8571F0C14CF00C3D4A6 /* HttpSwift.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DDDF8491F0C14CF00C3D4A6 /* HttpSwift.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -42,6 +43,7 @@ /* Begin PBXFileReference section */ 8F009482253A4114E69534EC /* Pods-HttpSwiftTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-HttpSwiftTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-HttpSwiftTests/Pods-HttpSwiftTests.release.xcconfig"; sourceTree = ""; }; 9931E996D4871BE9672A6FDE /* Pods-HttpSwiftTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-HttpSwiftTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-HttpSwiftTests/Pods-HttpSwiftTests.debug.xcconfig"; sourceTree = ""; }; + 9D9773D62056745E00DA7E4F /* Http.swift.pfx */ = {isa = PBXFileReference; lastKnownFileType = file; path = Http.swift.pfx; sourceTree = ""; }; 9DDDF8461F0C14CF00C3D4A6 /* HttpSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = HttpSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 9DDDF8491F0C14CF00C3D4A6 /* HttpSwift.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HttpSwift.h; sourceTree = ""; }; 9DDDF84A1F0C14CF00C3D4A6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -168,6 +170,7 @@ children = ( 9D05A0D21F14ECF500E2956C /* HttpSwiftTests */, 9DDDF8561F0C14CF00C3D4A6 /* Info.plist */, + 9D9773D62056745E00DA7E4F /* Http.swift.pfx */, ); name = Tests; path = ../Tests; @@ -282,6 +285,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 9D9773D72056746200DA7E4F /* Http.swift.pfx in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/LICENSE b/LICENSE index 9670504..60ac889 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2017 BiAtoms +Copyright (c) 2018 BiAtoms Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Package.swift b/Package.swift index 49f878e..b36f589 100644 --- a/Package.swift +++ b/Package.swift @@ -3,7 +3,7 @@ import PackageDescription let package = Package( name: "HttpSwift", dependencies: [ - .Package(url: "https://github.com/BiAtoms/Socket.swift.git", majorVersion: 2, minor: 0), - .Package(url: "https://github.com/BiAtoms/Request.swift.git", majorVersion: 2, minor: 0),//for tests + .Package(url: "https://github.com/BiAtoms/Socket.swift.git", majorVersion: 2, minor: 2), + .Package(url: "https://github.com/BiAtoms/Request.swift.git", majorVersion: 2, minor: 1), // for tests ] ) diff --git a/Podfile b/Podfile index 2ec5638..c6f3f43 100644 --- a/Podfile +++ b/Podfile @@ -1,20 +1,18 @@ -# Uncomment the next line to define a global platform for your project -platform :ios, '8.0' #links Foundation.framework for targets +platform :ios, '8.0' # links Foundation.framework for targets target 'HttpSwift' do - # Comment the next line if you're not using Swift and don't want to use dynamic frameworks use_frameworks! # Pods for HttpSwift - pod 'Socket.swift', '~> 2.0' + pod 'Socket.swift' target 'HttpSwiftTests' do inherit! :search_paths - pod 'Request.swift', '~> 2.0' + pod 'Request.swift' end end -#This is just from making project multiplatform. You should not use below code +# Below code is just used to make project multi-platform. You won't need it post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| diff --git a/README.md b/README.md index d51559e..a232085 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ A tiny HTTP server engine written in swift. ## Features +* SSL/TLS support * Error handling * Global middlewares * Route middlewares @@ -25,7 +26,7 @@ server.get("/hello/{id}") { request in return .ok(request.routeParams["id"]!) } -server.run() //go to http://localhost:8080/hello/1?state=active in the browser +try server.run() //go to http://localhost:8080/hello/1?state=active in the browser ``` ## Installation @@ -43,7 +44,7 @@ To integrate Http.swift into your Xcode project using CocoaPods, specify it in y ```ruby source 'https://github.com/CocoaPods/Specs.git' target '' do - pod 'Http.swift', '~> 2.0' + pod 'Http.swift', '~> 2.1' end ``` diff --git a/Sources/Server.swift b/Sources/Server.swift index f4fbf5d..8c986da 100644 --- a/Sources/Server.swift +++ b/Sources/Server.swift @@ -20,10 +20,26 @@ open class Server { open var middlewares: [MiddlewareHandler] = [] public init() {} + public var tlsConfig: TLS.Configuration? - open func run(port: SocketSwift.Port = 8080, address: String? = nil) { + #if os(Linux) + public typealias CertificatePath = (path: URL, keyPath: URL, password: String?) + #else + public typealias CertificatePath = (path: URL, password: String) + #endif + + open func run(port: SocketSwift.Port = 8080, address: String? = nil, certifiatePath: CertificatePath? = nil) throws { + if let path = certifiatePath { + #if os(Linux) + let cert = TLS.importCert(at: path.path, withKey: path.keyPath, password: path.password) + try TLS.initialize() + #else + let cert = TLS.importCert(at: path.path, password: path.password) + #endif + tlsConfig = TLS.Configuration(certificate: cert) + } + socket = try Socket.tcpListening(port: port, address: address) queue.async { - self.socket = try! Socket.tcpListening(port: port, address: address) while let client = try? self.socket.accept() { self.handleConnection(client) client.close() @@ -32,6 +48,16 @@ open class Server { } open func handleConnection(_ client: Socket) { + if let config = tlsConfig { + do { + try client.startTls(config) + } catch { + print("Failed to establish secure connection", error) + return + } + } + + var request: Request! do { request = try RequestParser.parse(socket: client) @@ -42,7 +68,7 @@ open class Server { let response = errorHandler.onError(request: request, error: error) ?? .internalServerError(error) try client.write(response) } catch { - print(error) + print("Connection error", error) } } } diff --git a/Tests/Http.swift.csr b/Tests/Http.swift.csr new file mode 100644 index 0000000..888e2f2 --- /dev/null +++ b/Tests/Http.swift.csr @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDgzCCAmugAwIBAgIJAM/iflg8ZRKoMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNV +BAYTAkFaMQ0wCwYDVQQIDARCYWt1MQ0wCwYDVQQHDARCYWt1MRAwDgYDVQQKDAdC +aUF0b21zMRgwFgYDVQQDDA93d3cuYmlhdG9tcy5jb20wIBcNMTgwMzExMTI0MDAx +WhgPNDc1NjAyMDYxMjQwMDFaMFcxCzAJBgNVBAYTAkFaMQ0wCwYDVQQIDARCYWt1 +MQ0wCwYDVQQHDARCYWt1MRAwDgYDVQQKDAdCaUF0b21zMRgwFgYDVQQDDA93d3cu +YmlhdG9tcy5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDB5uog +e8hEQMCqnbqHlioGaMi8S2+ku/qwEUSn/5Z/gtUAdKeTMNp2RRrk6tA1l8qVeex7 +wEqoz1ucTeWIJICI2ppgtNfpBBz+AxOt9k8ctrI0RXHPs+Nb3p63XsQxYqiUziLM +wFS0ic4gSPVwaPlfGoubNj5AZMdL7Lwz+S9bavQ3OlD9txPgD69M4yfqsq+QxYJ8 +N9tlld/ygxIjSkWv3WpFdm2mQ+gB3k4YalwjBvg3rD01L5iYQwwMxbVCTvjrfgJO +drgGHxHWiaMzZbp6iMqaxWOhT7GZaeNeDPmSdb4LRXNeGu2OsC4RUM+trVO5T3BW +HLAuXQosHd6DGdPxAgMBAAGjUDBOMB0GA1UdDgQWBBTYLhwiC6B1V2wDxKJzS1w5 +70xz2zAfBgNVHSMEGDAWgBTYLhwiC6B1V2wDxKJzS1w570xz2zAMBgNVHRMEBTAD +AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCd6fRexm5ytxC3UTRs41YY3bT9tY0CVC9Q +YjMojTuKjfV2mpJRbZ/hdf8nWeSZL45cEDXztOaLBQ4TQQgpdpY/LX4aTjceMRj3 +pQRSi1CI1ZK6IFKHXznrQ0rllw+ow2N3s/3C7T1fA8di/x6RvlAxKMA6Xai69Cs5 +PJDcpO0TzIg3UtcIhh7fNZkMuiaW4km2AR6qI4h5kj6SBCN5jto0cxC9bZ36ZZwG +ArrTH4sxXBVNAavEXXGewTBfRl9zkcHFd8yeaVnUGccPAeD1q4TialBlcXRAAD4+ +yzVW/B1WMI8+v0z9eSe0u6sa8I5vHLtFJbZo+7yne5RbXqU/dfvV +-----END CERTIFICATE----- diff --git a/Tests/Http.swift.key b/Tests/Http.swift.key new file mode 100644 index 0000000..eb771cd --- /dev/null +++ b/Tests/Http.swift.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAwebqIHvIREDAqp26h5YqBmjIvEtvpLv6sBFEp/+Wf4LVAHSn +kzDadkUa5OrQNZfKlXnse8BKqM9bnE3liCSAiNqaYLTX6QQc/gMTrfZPHLayNEVx +z7PjW96et17EMWKolM4izMBUtInOIEj1cGj5XxqLmzY+QGTHS+y8M/kvW2r0NzpQ +/bcT4A+vTOMn6rKvkMWCfDfbZZXf8oMSI0pFr91qRXZtpkPoAd5OGGpcIwb4N6w9 +NS+YmEMMDMW1Qk74634CTna4Bh8R1omjM2W6eojKmsVjoU+xmWnjXgz5knW+C0Vz +XhrtjrAuEVDPra1TuU9wVhywLl0KLB3egxnT8QIDAQABAoIBAHoc64ujiN77DJsm +lEc5WaQJr4lt1JShUeI6LcvDWctZ4LzVuocm+B4NrT9HZfKqcomqbDUPR7WCSUZp +hEk1+8C0Mw7+NQ9paLzLwUQIX5BMunPS3DcbKe88hyYiR1p2sIZuP1pFMdM1Chwo +9nFhmtK7C/qmsZOqJBz7rRbqMlnWK5TmIG2ImRyQyDc3Vtc1zWpiupQKah/kgWm0 +grVO7u/hK4GLVtNqDFawQI1NsgRLAnvlBfbnmxWua+xbpzvnjlZNQNvAWzfegwGH +J4xgVN8dGPlWdUk82xPD9mXX+v/Kr4IfGM/wY7h9wr26GGLl/tF3NrLkVj1px5y5 +sUCHQNUCgYEA6lsFBARiPhIRqQyKLLvo9x1gKIR9WKyGRg422225ma9sGdz3IJDo +/vST574I1m6aTH4YCPJZG1HHIMQOHHLJC8JsUI+CLXtMS6gITTgEwi02v3lIjX+T +ELvcDdZL+M7guMlY703ArAcNRWHnhjP4AMNo5oCja6FIiBVQ2vAkW5MCgYEA089u +wnJ30e7naT0bO+KJD4pTdp5yxsnRA9ZOG/CfUJBqsYrvYTy5TADwBSxmRxbvHn4E +tTODje8JYMmaik401po5yXVcKxIHgmsBIKrTivjieHSGgJ/lmc8p6+xCL4CD7J85 +01U5mfRyFKJBrsUpHBurATu6xcvYGDt5lTjhrOsCgYAhcwBSRHXpOhb/M+T84Y0s +yCqTXeWuJGG92gWGaDDXeKxLPLihE03OJgZ+Sydjw8GOLWkszbpeJdvwF0uUT+XW +Idfn37PK3hBTtBYLP3WeaWmpBpyOZakN+GI+L1oElzKH7WUeMtDPPOpt/r1W8E/z +e/CbBb1NfWWm1rQFq8TktQKBgAw8bX+aMUXVcVTpDMu22IgnS48MtEC7o/F+zeBb +VDjJPwCmsBGD+ohjfXovCHGO551xVkBJi44FgxsuSlk3D2JeYnw65WovjnOATv5e +H/5lRmADC0oe8pqiFx/j9CbeW8Ctqh+FSuCT+IssnHLGPQu8pXJayv1mO0ObG/j8 +4jylAoGARR9ZyYSzg3NLfAq23wBU4TThogHFbs7jFfIG56I/2kW9prbZWKuGqZ+t +cbspA09f3Vu2WUBSpfOEZM8QdcBT/X216atVVhLXKuYFRsJnlhAndo0Sf3S6OTGe +mm8VyKMc2U/Dy63cFfUs3AedC9g0sr8kX5e2rR88lt6ce2xaHSE= +-----END RSA PRIVATE KEY----- diff --git a/Tests/Http.swift.pfx b/Tests/Http.swift.pfx new file mode 100644 index 0000000000000000000000000000000000000000..3035f0c7c2ed565e5728d2ca67db78e65adecd7a GIT binary patch literal 2501 zcmV;$2|D&Lf(gL_0Ru3C35Ny=Duzgg_YDCD0ic2jcm#q8bTEPma4>=dCk6>BhDe6@ z4FLxRpn?P#FoFaW0s#Opf&>i)2`Yw2hW8Bt2LUh~1_~;MNQU8(_pGgPXM=ty@9f|* z{sJ;1N<;7r-(%%}DIi4f9V?T+#vaBRCEg`-&+Ig|3s@r3?NtJ~m=HtuA@OUt+Xi

*i_8tgI^Xyl`~2SJUzAiCET*5_eI|HXLp(7_X~UN6U{Z?mW}}$xNXhyosc+-AZ}= zTJi)N(D|9y@^Af>?{^lVOr796b>I0OYhS>@UPqus!}F_~>`GARt3G2OQcLqVQ?4 zfLirUjO0)}mc#&bw471rL-`W?n9Xw-pF}7HxRp+{JqmY(Axowz%yY>c_VUTs`86vO9y*|ouzJK!f5fJ5PJ7;J1&MsP6)e#k;e>&`7!x6^iII{`BcYLxRk0{ zLPDxLHQz{&9cyFZnd<5ObM&LNQUcrjRGc!Pp6=tQUl^DQy_u=TpY1LBB73axlQXCv&;V0pUM@} zjz-Nep?ldGSPNaMjk84*W4FXv%wD#|>ikNej!2Or5vq~+x9ju}Yd@OHIryaD zQ9yaz1{!N`v@L%SzQu;`BA8Cj(CodelX3GDOkDo6(0Rx=i4YDU&(c|iSC=7R@1ys6 zc$up`&<9gb76n4MgWq}5TBg?ZQ+v|bNqJIu&JBp{`rT!x%*eqF1El$yas_>w+G&?d zSgQO#VJ*TrkbcxDofO|N`h(`Hl1<1TIcpUyB`BN$ca;t(;yVyJh2SJab9nR^R~Gc4 zgkL62MpVPmq4=ECm;a^hGF#u1( zn8-(E*9R_zWF&fYcqW>57Z2%F5BGOKuxOFTq=mw|45nd$EeJ|?n)DifZOpqbdm%AV zR+f?aQ%KUjrz=Y5!iMrfEYM(+XcH|g*eA|)jXPHAiCqfSLdc3sIEl1FD;ap7{^VA+ zoSLL-dj>~BHEj}Q{|*1OY%rBqBDEDD?xA?g9bF_0he^U=nItH^FPbe-08vrAt-CR8 zl_90Oa5JT(h0;x7mMUB)*F;_BqaoZ--Eq%WWcW}I@Ry^Ll7R2sF zWky+e#chu|qPt)R8k?HD|Jod`=PaM(_*3KAY-zfwzlS(5W%P6qQ7j7BS&Z)l*dQOY zn&)0TChHsE6PqsFY~#2X&iilBN|=GKbwJJ(p%2uU=1KIl{L*rotniT^vM=RAHzS#V z9HNL%UZg-73da^Sh`UL>0IXnGAIi^9<>#34QB^AFP|Z@$i>?$YGy|Qs&V8-l`2E*K zq53GrX#k`c#5_1|_cG{k$>Zfg;wTzcBuP0~cH_n9vAnQ5DR;fFpo+i3uWxtQ)L_}V zPGGY(ruE?6Oj5)`j%8dURQG!XP-`8^V{Vy}10lA!T}*e|^J#BUFE`uZwIJZDrzS!t zbB7aU8NpNI3t90=Y0GdT!Qk?Cc55c}dPXrNFe3&DDuzgg_YDCF6)_eB6eAyG7WMC( z++W9iEtKQ7Q#~u?X)rM`AutIB1uG5%0vZJX1QgB!Lp-sH^z|VaZ{}n`BOYeZ46FnQ P_;D>oV0dd=0s;sC3#^}E literal 0 HcmV?d00001 diff --git a/Tests/HttpSwiftTests/HttpSwiftTests.swift b/Tests/HttpSwiftTests/HttpSwiftTests.swift index 8207679..65662e5 100644 --- a/Tests/HttpSwiftTests/HttpSwiftTests.swift +++ b/Tests/HttpSwiftTests/HttpSwiftTests.swift @@ -20,9 +20,25 @@ class HttpSwiftTests: XCTestCase { struct a { static let server: Server = { let server = Server() - server.run() + try! server.run() return server }() + + static let secureServer: Server = { + let server = Server() + #if os(Linux) + let rootPath = URL(string: #file)!.appendingPathComponent("../Http.swift") + let path = rootPath.appendingPathExtension("csr") + let keyPath = rootPath.appendingPathExtension("key") + let certPath = (path, keyPath, nil as String?) + #else + let path = Bundle(for: HttpSwiftTests.self).url(forResource: "Http.swift", withExtension: "pfx")! + let certPath = (path, "orkhan1234") + #endif + try! server.run(port: 4443, certifiatePath: certPath) + return server + }() + static let client = Client(baseUrl: "http://localhost:8080") } @@ -30,6 +46,9 @@ class HttpSwiftTests: XCTestCase { var server: Server { return a.server } + var secureServer: Server { + return a.secureServer + } var client: Client { return a.client } @@ -253,6 +272,20 @@ class HttpSwiftTests: XCTestCase { waitForExpectations() } + func testSSL() { + secureServer.get("/") { _ in + return .ok("Securely connected") + } + + let socket = try! Socket(.inet) + try! socket.connect(port: 4443) + try! socket.startTls(.init(peer: "www.biatoms.com", allowSelfSigned: true)) + try! socket.write("GET / HTTP/1.0\r\n\r\n".bytes) + + while let _ = try? socket.readLine() { } // ignore upto body + XCTAssertEqual(try? socket.readLine(), "Securely connected") + } + static var allTests = [ ("testRoute", testRoute), ("testRequestAndResponse", testRequestAndResponse),