All Projects → Building42 → Telegraph

Building42 / Telegraph

Licence: mit
Secure Web Server for iOS, tvOS and macOS

Programming Languages

swift
15916 projects

Projects that are alternatives of or similar to Telegraph

Beetlex
high performance dotnet core socket tcp communication components, support TLS, HTTP, HTTPS, WebSocket, RPC, Redis protocols, custom protocols and 1M connections problem solution
Stars: ✭ 802 (+69.2%)
Mutual labels:  websocket, https, ssl, webserver
Agoo
A High Performance HTTP Server for Ruby
Stars: ✭ 679 (+43.25%)
Mutual labels:  websocket, https, webserver
Mutual Tls Ssl
🔐 Tutorial of setting up Security for your API with one way authentication with TLS/SSL and mutual mutual authentication for a java based web server and a client with both Spring Boot. Different clients are provided such as Apache HttpClient, OkHttp, Spring RestTemplate, Spring WebFlux WebClient Jetty and Netty, the old and the new JDK HttpClient, the old and the new Jersey Client, Google HttpClient, Unirest, Retrofit, Feign, Methanol, vertx, Scala client Finagle, Featherbed, Dispatch Reboot, AsyncHttpClient, Sttp, Akka, Requests Scala, Http4s Blaze, Kotlin client Fuel, http4k, Kohttp and ktor. Also other server examples are available such as jersey with grizzly. Also gRPC examples are included
Stars: ✭ 163 (-65.61%)
Mutual labels:  https, ssl, certificate
Netcoreserver
Ultra fast and low latency asynchronous socket server & client C# .NET Core library with support TCP, SSL, UDP, HTTP, HTTPS, WebSocket protocols and 10K connections problem solution
Stars: ✭ 799 (+68.57%)
Mutual labels:  websocket, https, ssl
Microwebsrv2
The last Micro Web Server for IoTs (MicroPython) or large servers (CPython), that supports WebSockets, routes, template engine and with really optimized architecture (mem allocations, async I/Os). Ready for ESP32, STM32 on Pyboard, Pycom's chipsets (WiPy, LoPy, ...). Robust, efficient and documented!
Stars: ✭ 295 (-37.76%)
Mutual labels:  websocket, https, ssl
Certificaat
General-purpose ACME client
Stars: ✭ 88 (-81.43%)
Mutual labels:  https, ssl, certificate
Cppserver
Ultra fast and low latency asynchronous socket server & client C++ library with support TCP, SSL, UDP, HTTP, HTTPS, WebSocket protocols and 10K connections problem solution
Stars: ✭ 528 (+11.39%)
Mutual labels:  websocket, https, ssl
Echo
High performance, minimalist Go web framework
Stars: ✭ 21,297 (+4393.04%)
Mutual labels:  websocket, https, ssl
sslcontext-kickstart
🔐 A lightweight high level library for configuring a http client or server based on SSLContext or other properties such as TrustManager, KeyManager or Trusted Certificates to communicate over SSL TLS for one way authentication or two way authentication provided by the SSLFactory. Support for Java, Scala and Kotlin based clients with examples. Av…
Stars: ✭ 295 (-37.76%)
Mutual labels:  ssl, certificate, https
Kvantum
An intellectual (HTTP/HTTPS) web server with support for server side templating (Crush, Apache Velocity and JTwig)
Stars: ✭ 17 (-96.41%)
Mutual labels:  ssl, webserver, https
Pric
Simple zero-config tool to create Private Certificate Authority & issue locally-trusted development server certificates with any domain names you'd like. SSL certificates for development purposes.
Stars: ✭ 87 (-81.65%)
Mutual labels:  https, ssl, webserver
letsencrypt-www
Probably the easiest way to create | renew | deploy certificate
Stars: ✭ 27 (-94.3%)
Mutual labels:  ssl, certificate, https
Acme client
Java ACME Client application
Stars: ✭ 77 (-83.76%)
Mutual labels:  https, ssl, certificate
Https Localhost
HTTPS server running on localhost
Stars: ✭ 122 (-74.26%)
Mutual labels:  https, ssl, certificate
Ansible Role Certbot
Ansible Role - Certbot (for Let's Encrypt)
Stars: ✭ 477 (+0.63%)
Mutual labels:  https, ssl, certificate
Nico
A HTTP2 web server for reverse proxy and single page application, automatically apply for ssl certificate, Zero-Configuration.
Stars: ✭ 43 (-90.93%)
Mutual labels:  ssl, certificate, webserver
Libhv
🔥 比libevent、libuv更易用的国产网络库。A c/c++ network library for developing TCP/UDP/SSL/HTTP/WebSocket client/server.
Stars: ✭ 3,355 (+607.81%)
Mutual labels:  websocket, https, webserver
website-checks
check your website for issues with multiple tools and get PDF reports of the results
Stars: ✭ 69 (-85.44%)
Mutual labels:  ssl, certificate, https
Crypt Le
Crypt::LE - Let's Encrypt / Buypass / ACME client and library in Perl for obtaining free SSL certificates (inc. generating RSA/ECC keys and CSRs). HTTP/DNS verification is supported out of the box, easily extended with plugins, easily dockerized.
Stars: ✭ 277 (-41.56%)
Mutual labels:  https, ssl, certificate
Ilruntime hotgames
基于ILRuntime的热更新能力实现的可以直接使用的框架,友情赠送C# WebService + WebSocketServer服务器端。
Stars: ✭ 293 (-38.19%)
Mutual labels:  websocket, webserver

Telegraph: Secure Web Server for iOS, tvOS and macOS

Telegraph CI CocoaPods compatible Carthage compatible License Platform

Telegraph is a Secure Web Server for iOS, tvOS and macOS written in Swift.

Features

  • [x] Handles HTTP 1.0/1.1 requests
  • [x] Secure traffic, HTTPS/TLS encryption
  • [x] WebSocket client and server
  • [x] Uses well tested socket library CocoaAsyncSocket
  • [x] Uses performant, low memory, HTTP parser C library http-parser
  • [x] Customizable, from time-outs to message handlers
  • [x] Simple, well commented code

Platforms

  • iOS 8.0+
  • tvOS 9.0+
  • macOS 10.10+

Versions

Installation

Swift Package Manager

The Swift Package Manager is a tool for automating the distribution of Swift code.

Xcode 11 or later has integrated support for the Swift Package Manager. You can add Telegraph to your project by choosing the File - Swift Packages - Add Package Dependency option. Use the repository url as specified below and select the version you want to use.

Or you can manually add a Package.swift file to your project with:

dependencies: [
    .package(url: "https://github.com/Building42/Telegraph.git")
]

Carthage

Carthage is a decentralized dependency manager that builds your dependencies into binary frameworks.

github "Building42/Telegraph"

See Carthage - Quick Start for more information.

CocoaPods

CocoaPods is a dependency manager for Cocoa projects that makes dependencies a part of your workspace.

source 'https://cdn.cocoapods.org/'
use_frameworks!

target '<Your Target Name>'

pod 'Telegraph'

See CocoaPods - Getting Started for more information.

Build

You can build the Telegraph framework and the examples with these steps:

  1. clone the repository
  2. open Telegraph.xcworkspace
  3. make sure that Xcode downloads the Swift Package Dependencies
  4. select one of the example schemes and build

This is only necessary if you want to make changes to the framework or try out the examples.

Usage

Configure App Transport Security

With iOS 9 Apple introduced APS (App Transport Security) in an effort to improve user security and privacy by requiring apps to use secure network connections over HTTPS. It means that without additional configuration unsecure HTTP requests in apps that target iOS 9 or higher will fail. In iOS 9, APS is unfortunately also activated for LAN connections. Apple fixed this in iOS 10, by adding NSAllowsLocalNetworking.

Even though we are using HTTPS, we have to consider the following:

  1. when we are communicating between iPads, it is likely that we'll connect on IP addresses, or at least that the hostname of the device won't match the common name of the certificate.
  2. our server will use a certificate signed by our own Certificate Authority instead of a well recognized root Certificate Authority.

You can disable APS by adding the key App Transport Security Settings to your Info.plist, with a sub-key where Allow Arbitrary Loads is set to Yes. For more information see ATS Configuration Basics.

Prepare the certificates

For a secure web server you'll need two things:

  1. one or more Certificate Authority certificates in DER format.
  2. a PKCS12 bundle containing a private key and certificate (signed by the CA)

Telegraph contains classes to make it easier to load the certificates:

let caCertificateURL = Bundle.main.url(forResource: "ca", withExtension: "der")!
let caCertificate = Certificate(derURL: caCertificateURL)!

let identityURL = Bundle.main.url(forResource: "localhost", withExtension: "p12")!
let identity = CertificateIdentity(p12URL: identityURL, passphrase: "test")!

Note: macOS doesn't accept P12 files without passphrase.

HTTP: Server

You most likely want to create a secure server by passing in the certificates:

serverHTTPs = Server(identity: identity, caCertificates: [caCertificate])
try! server.start(port: 9000)

Or for a quick test, create an unsecure server:

serverHTTP = Server()
try! server.start(port: 9000)

You can limit the server to localhost connections by specifying an interface when you start it:

try! server.start(port: 9000, interface: "localhost")

HTTP: Routes

Routes consist of three parts: HTTP method, path and a handler:

server.route(.POST, "test", handleTest)
server.route(.GET, "hello/:name", handleGreeting)
server.route(.GET, "secret/*") { .forbidden }
server.route(.GET, "status") { (.ok, "Server is running") }

server.serveBundle(.main, "/")

// You can also serve custom urls, for example the Demo folder in your bundle
let demoBundleURL = Bundle.main.url(forResource: "Demo", withExtension: nil)!
server.serveDirectory(demoBundleURL, "/demo")

Slashes at the start of the path are optional. Routes are case insensitive. You can specify custom regular expressions for more advanced route matching. When none of the routes are matched, the server will return a 404 not found.

The first route in the example above has a route parameter (name). When the server matches the incoming request to that route it will place the parameter in the params array of the request:

func handleGreeting(request: HTTPRequest) -> HTTPResponse {
  let name = request.params["name"] ?? "stranger"
  return HTTPResponse(content: "Hello \(name.capitalized)")
}

HTTP: Middleware

When a HTTP request is handled by the Server it is passed through a chain of message handlers. If you don't change the default configuration, requests will first be passed to the HTTPWebSocketHandler and then to the HTTPRouteHandler.

Below is an example of a message handler:

public class HTTPGETOnlyHandler: HTTPRequestHandler {
  public func respond(to request: HTTPRequest, nextHandler: HTTPRequest.Handler) throws -> HTTPResponse? {
    // If this is a GET request, pass it to the next handler
    if request.method == .GET {
      return try nextHandler(request)
    }

    // Otherwise return 403 - Forbidden
    return HTTPResponse(.forbidden, content: "Only GET requests are allowed")
  }
}

You can enable the message handler by setting it in the HTTP configuration:

server.httpConfig.requestHandlers.insert(HTTPGETOnlyHandler(), at: 0)

Note that the order of the request handlers is important. You'll probably want to have the HTTPRouteHandler as the last request handler or your Server won't handle any route requests. The HTTPRouteHandler doesn't call any handlers, so don't specify any handlers after the HTTPRouteHandler.

You can also modify the request in handlers. This handler copies the QueryString items into the request's params dictionary:

public class HTTPRequestParamsHandler: HTTPRequestHandler {
  public func respond(to request: HTTPRequest, nextHandler: HTTPRequest.Handler) throws -> HTTPResponse? {
    // Extract the query string items and put them in the HTTPRequest params
    request.uri.queryItems?.forEach { item in
      request.params[item.name] = item.value
    }

    // Continue with the rest of the handlers
    return try nextHandler(request)
  }
}

But what if you want to add headers to a response? Simply call the chain and modify the result:

public class HTTPAppDetailsHandler: HTTPRequestHandler {
  public func respond(to request: HTTPRequest, nextHandler: HTTPRequest.Handler) throws -> HTTPResponse? {
    //  Let the other handlers create a response
    let response = try nextHandler(request)

    // Add our own bit of magic
    response.headers["X-App-Version"] = "My App 1.0"
    return response
  }
}

HTTP: Cross-Origin Resource Sharing (CORS)

The CORS mechanism controls which sites will have permission to access the resources of your server. You can set CORS by sending the Access-Control-Allow-Origin header to the client. For development purposes it can be useful to allow all sites with the * value.

response.headers.accessControlAllowOrigin = "*"

If you want to make it a bit fancier, you can create a handler:

public class HTTPCORSHandler: HTTPRequestHandler {
  public func respond(to request: HTTPRequest, nextHandler: HTTPRequest.Handler) throws -> HTTPResponse? {
    let response = try nextHandler(request)

    // Add access control header for GET requests
    if request.method == .GET {
      response?.headers.accessControlAllowOrigin = "*"
    }

    return response
  }
}

For increased security you can add additional checks, dig down into the request, and send back different CORS headers for different clients.

HTTP: Client

For client connections we'll use Apple's URLSession class. Ray Wenderlich has an excellent tutorial on it. We're going to have to manually verify the TLS handshake (App Transport Security needs to be disabled for this):

let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
let tlsPolicy = TLSPolicy(commonName: "localhost", certificates: [caCertificate])

extension YourClass: URLSessionDelegate {
  func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge,
      completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
    // The TLSPolicy class will do most of the work for us
    let credential = tlsPolicy.evaluateSession(trust: challenge.protectionSpace.serverTrust)
    completionHandler(credential == nil ? .cancelAuthenticationChallenge : .useCredential, credential)
  }
}

The common name in the TLSPolicy should match the common name of the certificate of the server (that was provided in the P12 archive). Although I don't recommend it, you can disable the common name check by providing an empty string (if you provide nil, the common name will be compared to the device's hostname).

For the common name you aren't limited to the hostname or IP address of the device. Your backend could, for example, generate a certificate with a common name that matches the UUID of the device. If the client knows the UUID of device it is connecting to, you could make it part of the TLSPolicy check.

WebSockets: Server-side

Your Server will automatically recognize WebSocket requests, thanks to the HTTPWebSocketHandler that is by default in the list of HTTP request handlers. Set the WebSocket delegate to handle incoming messages:

server.webSocketDelegate = self

Next step is to implement the ServerWebSocketDelegate methods:

func server(_ server: Server, webSocketDidConnect webSocket: WebSocket, handshake: HTTPRequest) {
  // A web socket connected, you can extract additional information from the handshake request
  webSocket.send(text: "Welcome!")
}

func server(_ server: Server, webSocketDidDisconnect webSocket: WebSocket, error: Error?) {
  // One of our web sockets disconnected
}

func server(_ server: Server, webSocket: WebSocket, didReceiveMessage message: WebSocketMessage) {
  // One of our web sockets sent us a message
}

func server(_ server: Server, webSocket: WebSocket, didSendMessage message: WebSocketMessage) {
  // We sent one of our web sockets a message (often you won't need to implement this one)
}

WebSockets: Middleware

Web sockets can have custom handlers too, although instead of a whole chain of handlers you specify a single class. The WebSocketMessageDefaultHandler will respond to connection-close messages and handle ping messages.

I recommend creating custom handlers by inheriting from the default handler:

public class AwesomeWebSocketHandler: WebSocketMessageHandler {
  public func incoming(message: WebSocketMessage, from webSocket: WebSocket) throws {
    // Don't forget to call super (for ping-pong etc.)
    super.incoming(message: message, from: webSocket)

    // Echo incoming text messages
    switch message.payload {
    case let .text(text): webSocket.send(text: text)
    default: break
    }
  }
}

WebSockets: Client-side

Now that we have a secure WebSocket server, we can use a secure WebSocket client as well by passing the CA certificate. Note that you only have to specify the CA certificate if the certificate isn't a root CA trusted by Apple or if you want to benefit from certificate pinning.

client = try! WebSocketClient("wss://localhost:9000", certificates: [caCertificate])
client.delegate = self

// You can specify headers too
client.headers.authorization = "Bearer secret-token"

The delegate methods look like this:

func webSocketClient(_ client: WebSocketClient, didConnectToHost host: String) {
  // The connection starts off as a HTTP request and then is upgraded to a
  // web socket connection. This method is called if the handshake was succesful.
}

func webSocketClient(_ client: WebSocketClient, didDisconnectWithError error: Error?) {
  // We were disconnected from the server
}

func webSocketClient(_ client: WebSocketClient, didReceiveData data: Data) {
  // We received a binary message. Ping, pong and the other opcodes are handled for us.
}

func webSocketClient(_ client: WebSocketClient, didReceiveText text: String) {
  // We received a text message, let's send one back
  client.send(text: "Message received")
}

FAQ

Why Telegraph?

There are only a few web servers available for iOS and many of them don't have SSL support. Our main goal with Telegraph is to offer secure HTTP and Web Socket traffic between iPads. The name is a tribute to the electrical telegraph, the first form of electrical telecommunications.

How do I create the certificates?

Basically what you want is a Certificate Authority and a Device certificate signed by that authority. You can find a nice tutorial at: https://jamielinux.com/docs/openssl-certificate-authority/

See the Tools folder for a script that can create self-signed certificates for you. The script is very basic and you will probably need to edit some information in the config-ca.cnf and config-localhost.cnf files. The certificates generated with the script are meant for development purposes only.

Why aren't my certificates working on iOS 13 or macOS 10.15 (or later)?

Apple has introduced new security requirements with iOS 13 and macOS 10.15. For more information see: https://support.apple.com/en-us/HT210176

My server isn't working properly

Please check the following:

  1. is Application Transport Security disabled?
  2. are your certificates valid?
  3. are your routes valid?
  4. have you customized any handlers? Is the route handler still included?

Have a look at the example project in this repository for a working starting point.

My browser doesn't display images from my bundle

During the build of your project Apple tries to optimize your images to reduce the size of your bundle. This optimization process sometimes causes the images to become unreadable by Chrome. Test your image url in Safari to double check if this is the case.

To solve this you can go to the property inspector in Xcode and change the type of the resource from Default - PNG image to Data. After that the build process won't optimize your file. I've also done this with logo.png in the example projects.

If you want to reduce the size of your images, I highly recommend ImageOptim.

Why can't I use port 80 and 443?

The first 1024 port numbers are restricted to root access only and your app doesn't have root access on the device. If you try to open a Server on those ports you will get a permission denied error when you start the server. For more information, read why are the first 1024 ports restricted to the root user only.

What if the device goes on standby?

If your app is send to the background or if the device goes on standby you typically have about 3 minutes to handle requests and close connections. You can create a background task with UIApplication.beginBackgroundTask to let iOS know that you need extra time to complete your operations. The property UIApplication.shared.backgroundTimeRemaining tells you the time that is left until a possible forced-kill of your app.

What about HTTP/2 support?

Ever wondered how the remote server knows that your browser is HTTP/2 compatible? During TLS negotiation, the application-layer protocol negotiation (ALPN) extension field contains "h2" to signal that HTTP/2 is going be used. Apple doesn't offer any (public) methods in Secure Transport or CFNetwork to configure ALPN extensions. A secure HTTP/2 iOS implementation is therefor not possible at the moment.

Can I use this in my Objective-C project?

This library was written in Swift and for performance reasons I haven't decorated classes with NSObject unless absolutely necessary (no dynamic dispatch). If you are willing to add Swift code to your project, you can integrate the server by adding a Swift wrapper class that inherits from NSObject and has a Telegraph Server variable.

Authors

This library was created by:

Code and design was inspired by:

Thank you to our contributors, your pull requests are most welcome and appreciated!

License

Telegraph is released under the MIT license. See LICENSE for details.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].