I am trying to work with a gRPC api and I have to send credentials securely. I am having issues to figure this out. I am using the swift-grpc library. I will link the docs and maybe someone can explain what I am supposed to do.
I am still unsure of what makes this secure through ssl(are we sending certificates).
docs from the swift-grpc using tlc library here
If anyone can give an explenation of how the ssl works and what to do that would be great
// code
import Foundation
import GRPC
import NIO
class Networking {
var authServiceClient: PartnerApi2_PartnerApiClient?
let port: Int = 50052
init() {
// build a fountain of EventLoops
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
do {
// open a channel to the gPRC server
let channel = try GRPCChannelPool.with(
target: .host("partner-api.com", port: self.port),
transportSecurity: .plaintext,
eventLoopGroup: eventLoopGroup
)
// create a Client
self.authServiceClient = PartnerApi2_PartnerApiClient.init(channel: channel) //AuthService_AuthServiceRoutesClient(channel: channel)
print("grpc connection initialized")
login(username: "email", password: "password")
} catch {
print("Couldn’t connect to gRPC server")
}
}
func login(username: String, password: String) -> String {
print("Login: username=\(username)")
// build the AccountCredentials object
let accountCredentials: PartnerApi2_AuthenticationRequest = .with {
$0.partnerEmail = username
$0.password = password
}
// grab the login() method from the gRPC client
let call = self.authServiceClient!.authenticate(accountCredentials)
// prepare an empty response object
let oauthCredentials: PartnerApi2_AuthenticationResponse
// execute the gRPC call and grab the result
do {
oauthCredentials = try call.response.wait()
} catch {
print("RPC method ‘login’ failed: \(error)")
// it would be better to throw an error here, but
// let’s keep this simple for demo purposes
return ""
}
// Do something interesting with the result
let oauthToken = oauthCredentials.authToken
print("Logged in with oauth token \(oauthToken)")
// return a value so we can use it in the app
return oauthToken
}
}
Related
I'm new to Vapor and I've implemented register and login routes. Register works fine. Every time I call the login route it seems to have a problem. Every time, I try to login with a registered user using Basic Auth it just returns 401. Attaching my code below.
App user model:
extension AppUser: ModelAuthenticatable {
static let usernameKey = \AppUser.$email
static let passwordHashKey = \AppUser.$passwordHash
func verify(password: String) throws -> Bool {
try Bcrypt.verify(password, created: self.passwordHash)
}
}
extension AppUser: JWTPayload {
func verify(using signer: JWTSigner) throws {
}
}
Routes configuration:
//MARK: Unprotected API
let unprotectedApi = app.routes
try unprotectedApi.register(collection: AppUserController.Unprotected())
//MARK: Password Protected API
let passwordProtectedApi = unprotectedApi.grouped(AppUser.authenticator())
try passwordProtectedApi.register(collection: AppUserController.PasswordProtected())
login logic:
extension AppUserController.PasswordProtected: RouteCollection {
func login(req: Request) throws -> EventLoopFuture<Response> {
let user = try req.auth.require(AppUser.self)
let token = try req.jwt.sign(user)
let loginResponse = AppUserLoginResponse(user: user.response, accessToken: token)
return DataWrapper.encodeResponse(data: loginResponse, for: req)
}
func boot(routes: RoutesBuilder) throws {
routes.post(Endpoint.API.Users.login, use: login)
}
}
Your login route as it stands is returning 401 because you have included it in the protected group, which requires the user to be logged in already. It would normally be unprotected. You need some code to do the logging in. This function assumes the user is identified by an email address and has supplied a password somehow:
private func loginExample( email: String, password: String, on: req Request) -> EventLoopFuture<Bool> {
return AppUser.query(on: req).filter(\.$email == email).first().flatMap { user in
// user will be nil if not found, following line test for this
if let user = user {
// user was identified by email
if try! user.verify(password: password) {
// password matches what is stored
request.auth.login(user)
// login has succeeded
return true
}
}
// login has failed - because either email did not match a user or the password was incorrect
return false
}
}
I've kept it simple by forcing the try in the call to verify (avoiding do-catch, etc.). You need to use something like this code in your login route, perhaps decoding the email and password from an HTML form.
In my app, I'm using Alamofire to make network calls, but now, I want to test the calls.
I do the network call like this :
AF.request(myURL).responseData { response in
// Decode the data
// Translate the data into an object
}
I tried to fake the AF session and override his request, but I don't know what should go inside.
class FakeAFSession: Session {
override func request(_ convertible: URLRequestConvertible, interceptor: RequestInterceptor? = nil) -> DataRequest {
// Fake the network request here
}
}
Here is how I want the test to run.
func testGivenCallbackFailed_WhenErrorInNetworkCall_ThenObjectReturnAreNil(){
// MARK: - Given
// I want to create a fake request
let fakeRequest = FakeNetworkRequest()
// I want to pass the request to the fake session of Alamofire
let fakeSession = FakeAFSession(request: fakeRequest)
// Finally, I want to pass the session to the service
let service = RecipeService(session: fakeSession)
// MARK: - When
let expectation = XCTestExpectation(description: "Error in network call")
let fakeIngredients = ["Mozzarella", "Tomato"]
service.getRecipe(containing: fakeIngredients) { _recipes, success, _error in
// MARK: - Then
// Here is where I should test the network call
XCTAssertNil(_recipes)
XCTAssertFalse(success)
XCTAssertNotNil(_error)
}
wait(for: [expectation], timeout: 0.01)
}
What technique should I use if this one is not recommended or the best ?
PS : I don't want to use only Xcode expectations to wait for the response,I really want to use faking or mocking process.
Thank's in advance for your answers.
I am making an app that activates a VPN connection based on OpenVPN, retrieves a certificate from the database, and opens a tunnel using NEPacketTunnelProvider and NetworkExtension.
I used the following repository, and now my VPN is working fine.
But the problem is that I want to allow only one app to use this VPN when enabled (WhatsApp precisely), and I want to restrict all other apps of using it.
On Android it's possible by giving the bundle identifier of the allowed apps to the PackageManager.
Can you please help me?
This is my PacketTunnelProvider class:
import NetworkExtension
import OpenVPNAdapter
extension NEPacketTunnelFlow: OpenVPNAdapterPacketFlow {}
class PacketTunnelProvider: NEPacketTunnelProvider {
lazy var vpnAdapter: OpenVPNAdapter = {
let adapter = OpenVPNAdapter()
adapter.delegate = self
return adapter
}()
let vpnReachability = OpenVPNReachability()
var startHandler: ((Error?) -> Void)?
var stopHandler: (() -> Void)?
override func startTunnel(options: [String : NSObject]?, completionHandler: #escaping (Error?) -> Void) {
// There are many ways to provide OpenVPN settings to the tunnel provider. For instance,
// you can use `options` argument of `startTunnel(options:completionHandler:)` method or get
// settings from `protocolConfiguration.providerConfiguration` property of `NEPacketTunnelProvider`
// class. Also you may provide just content of a ovpn file or use key:value pairs
// that may be provided exclusively or in addition to file content.
// In our case we need providerConfiguration dictionary to retrieve content
// of the OpenVPN configuration file. Other options related to the tunnel
// provider also can be stored there.
print("started!")
guard
let protocolConfiguration = protocolConfiguration as? NETunnelProviderProtocol,
let providerConfiguration = protocolConfiguration.providerConfiguration
else {
fatalError()
}
guard let ovpnFileContent: Data = providerConfiguration["ovpn"] as? Data else {
fatalError()
}
let configuration = OpenVPNConfiguration()
configuration.fileContent = ovpnFileContent
// configuration.settings = [
// // Additional parameters as key:value pairs may be provided here
// ]
// Uncomment this line if you want to keep TUN interface active during pauses or reconnections
// configuration.tunPersist = true
// Apply OpenVPN configuration
let evaluation: OpenVPNConfigurationEvaluation
do {
evaluation = try vpnAdapter.apply(configuration: configuration)
} catch {
completionHandler(error)
return
}
// Provide credentials if needed
if !evaluation.autologin {
// If your VPN configuration requires user credentials you can provide them by
// `protocolConfiguration.username` and `protocolConfiguration.passwordReference`
// properties. It is recommended to use persistent keychain reference to a keychain
// item containing the password.
guard let username: String = protocolConfiguration.username else {
fatalError()
}
// Retrieve a password from the keychain
// guard let password: String = ... {
// fatalError()
// }
let credentials = OpenVPNCredentials()
credentials.username = username
// credentials.password = password
do {
try vpnAdapter.provide(credentials: credentials)
} catch {
completionHandler(error)
return
}
}
// Checking reachability. In some cases after switching from cellular to
// WiFi the adapter still uses cellular data. Changing reachability forces
// reconnection so the adapter will use actual connection.
vpnReachability.startTracking { [weak self] status in
guard status == .reachableViaWiFi else { return }
self?.vpnAdapter.reconnect(afterTimeInterval: 5)
}
// Establish connection and wait for .connected event
startHandler = completionHandler
vpnAdapter.connect(using: packetFlow)
}
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: #escaping () -> Void) {
stopHandler = completionHandler
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
vpnAdapter.disconnect()
}
}
extension PacketTunnelProvider: OpenVPNAdapterDelegate {
// OpenVPNAdapter calls this delegate method to configure a VPN tunnel.
// `completionHandler` callback requires an object conforming to `OpenVPNAdapterPacketFlow`
// protocol if the tunnel is configured without errors. Otherwise send nil.
// `OpenVPNAdapterPacketFlow` method signatures are similar to `NEPacketTunnelFlow` so
// you can just extend that class to adopt `OpenVPNAdapterPacketFlow` protocol and
// send `self.packetFlow` to `completionHandler` callback.
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, configureTunnelWithNetworkSettings networkSettings: NEPacketTunnelNetworkSettings?, completionHandler: #escaping (Error?) -> Void) {
// In order to direct all DNS queries first to the VPN DNS servers before the primary DNS servers
// send empty string to NEDNSSettings.matchDomains
networkSettings?.dnsSettings?.matchDomains = [""]
// Set the network settings for the current tunneling session.
setTunnelNetworkSettings(networkSettings, completionHandler: completionHandler)
}
// Process events returned by the OpenVPN library
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleEvent event: OpenVPNAdapterEvent, message: String?) {
switch event {
case .connected:
if reasserting {
reasserting = false
}
guard let startHandler = startHandler else { return }
startHandler(nil)
self.startHandler = nil
case .disconnected:
guard let stopHandler = stopHandler else { return }
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
stopHandler()
self.stopHandler = nil
case .reconnecting:
reasserting = true
default:
break
}
}
// Handle errors thrown by the OpenVPN library
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleError error: Error) {
// Handle only fatal errors
guard let fatal = (error as NSError).userInfo[OpenVPNAdapterErrorFatalKey] as? Bool, fatal == true else {
return
}
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
if let startHandler = startHandler {
startHandler(error)
self.startHandler = nil
} else {
cancelTunnelWithError(error)
}
}
// Use this method to process any log message returned by OpenVPN library.
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleLogMessage logMessage: String) {
// Handle log messages
print(logMessage)
}
}
This is the function used in my VPN View Model to start a tunnel:
func configureVPN(serverAddress: String, username: String, password: String) {
var configData:Data = Data.init()
self.getCertificate{certificate in
configData = certificate!
guard
//If we want to read from a file
// let configData = self.readFile(name: "vtest2"),
let providerManager = self.providerManager
else {
return
}
self.providerManager?.loadFromPreferences { error in
if error == nil {
let tunnelProtocol = NETunnelProviderProtocol()
tunnelProtocol.username = username
tunnelProtocol.serverAddress = serverAddress
tunnelProtocol.providerBundleIdentifier = self.providerId // bundle id of the network extension target
tunnelProtocol.providerConfiguration = ["ovpn": configData]
tunnelProtocol.disconnectOnSleep = false
providerManager.protocolConfiguration = tunnelProtocol
providerManager.localizedDescription = "Slyfone Guard" // the title of the VPN profile which will appear on Settings
providerManager.isEnabled = true
providerManager.saveToPreferences(completionHandler: { (error) in
if error == nil {
providerManager.loadFromPreferences(completionHandler: { (error) in
do {
try providerManager.connection.startVPNTunnel(options: nil) // starts the VPN tunnel.
} catch let error {
print(error.localizedDescription)
}
})
}
})
}
}
}
}
As an engineer from Apple said:
The way to do it is to use Per-App VPN. See the Per-App VPN On Demand section in the NETunnelProviderManager documentation.
With NEPacketTunnelProvider on macOS (as of 10.15.4) you can set this up yourself with NEAppRule. A very generic example of setting up Safari to trigger the VPN would be:
var perAppManager = NETunnelProviderManager.forPerAppVPN()
/* ... */
NETunnelProviderManager.forPerAppVPN().loadFromPreferences(completionHandler: { error in
precondition(Thread.isMainThread)
/* ... */
let proto = (perAppManager.protocolConfiguration as? NETunnelProviderProtocol) ?? NETunnelProviderProtocol()
proto.serverAddress = "server.vpn.com"
proto.providerBundleIdentifier = "com.perapp-vpn.macOSPacketTunnel.PacketTunnelTest"
var appRules = [NEAppRule]()
let appRule = NEAppRule(signingIdentifier: "com.apple.Safari", designatedRequirement: "identifier \"com.apple.Safari\" and anchor apple")
appRule.matchDomains = ["example.com"]
appRules.append(appRule)
perAppManager.appRules = appRules
perAppManager.isOnDemandEnabled = true
perAppManager.protocolConfiguration = proto
perAppManager.isEnabled = true
perAppManager.localizedDescription = "Testing Per-App VPN"
self.perAppManager.saveToPreferences { saveError in
/* Proceed to connect */
}
})
That was a very generic case and forPerAppVPN() is only available on macOS. A more real-world case world case for iOS would be to create this process through MDM. That entire flow is explained in the documentation I mentioned previously. I would start by just creating a configuration profile in Configurator 2 and testing it out.
No idea if it works on OpenVPN
I am trying to connect programmatically to a VPN. I got the code below on this website: http://blog.moatazthenervous.com/create-a-vpn-connection-with-apple-swift/
I am trying to develop a command line application for macOS to connect programatically to a VPN.
import Foundation
import NetworkExtension
class VPN {
let vpnManager = NEVPNManager.shared();
let userName: String;
let passString: String;
let secret: String;
let server: String;
init (_ user: String, _ pass: String, _ secret: String, _ server: String){
userName = user
passString = pass
self.secret = secret
self.server = server
}
private var vpnLoadHandler: (Error?) -> Void { return
{ (error:Error?) in
if ((error) != nil) {
print("Could not load VPN Configurations")
return;
}
print ("me! me! ")
let p = NEVPNProtocolIPSec()
p.username = self.userName
p.serverAddress = self.server
p.authenticationMethod = NEVPNIKEAuthenticationMethod.sharedSecret
let kcs = KeychainService();
kcs.save(key: "SHARED", value: self.secret )
kcs.save(key: "VPN_PASSWORD", value: self.passString)
p.sharedSecretReference = kcs.load(key: "SHARED")
p.passwordReference = kcs.load(key: "VPN_PASSWORD")
p.useExtendedAuthentication = true
p.disconnectOnSleep = false
p.localIdentifier = "uio"
self.vpnManager.protocolConfiguration = p
self.vpnManager.localizedDescription = "Contensi"
self.vpnManager.isEnabled = true
self.vpnManager.saveToPreferences(completionHandler: self.vpnSaveHandler)
} }
private var vpnSaveHandler: (Error?) -> Void { return
{ (error:Error?) in
if (error != nil) {
print("Could not save VPN Configurations")
return
} else {
do {
try self.vpnManager.connection.startVPNTunnel()
} catch let error {
print("Error starting VPN Connection \(error.localizedDescription)");
}
}
}
}
public func connectVPN() {
//For no known reason the process of saving/loading the VPN configurations fails.On the 2nd time it works
do {
self.vpnManager.loadFromPreferences(completionHandler: self.vpnLoadHandler)
// self.vpnManager.saveToPreferences(completionHandler: nil)
}
}
public func disconnectVPN() ->Void {
self.vpnManager.connection.stopVPNTunnel()
}
public func status() -> NEVPNStatus {
return vpnManager.connection.status
}
}
My problem is that I my completion handlers are not working. Take the function connectVPN(), for instance. It's completion handler is vpnLoadHandler. However, I never get that handler executed. See the print statement on the handler? Nothing ever gets printed.
Reading Apple's documentation, I know that I have to configure XCode with an entitlement in order to allow VPN - I haven't got the "capabilities"-tab, so I don't know how to add that entitlement. I am not sure if that's the problem, since the app compiles file and executes. I just don't get the completion handler to execute.
So, basically:
why isn't the handler being called? And what can I do to debug?
Could this be due the lack an entitlement to allow the configuration of a personal vpn in xcode? If so, how can I configure that?
Best,
Francis
UPDATE I actually managed to create the entitlements file, and I guess it has the proper settings to allow a personal vpn. So I guess that part is solved. But why isn't the completion handler called?
I am using SwiftHTTP and try to get a JSON Feed from my Server. The Server has a self signed Certificate and Access via User and Password.
I tryed with Alamofire but I found no Solution
here's my Code
HTTP.globalRequest { req in
req.timeoutInterval = 5
}
//set a global SSL pinning setting
HTTP.globalSecurity(HTTPSecurity()) //see the SSL section for more info
//set global auth handler. See the Auth section for more info
HTTP.globalAuth { challenge in
return URLCredential(user: "user", password: "passwd", persistence: .forSession)
}
do {
let opt = try HTTP.GET("https://10.0.1.2:4711/fhem/?cmd=jsonlist2&XHR=1",requestSerializer: JSONParameterSerializer())
//the auth closures will continually be called until a successful auth or rejection
var attempted = false
opt.auth = { challenge in
if !attempted {
attempted = true
return URLCredential(forTrust: challenge.proposedCredential?.certificates)
}
return nil
}
opt.start { response in
print("success")
print("opt finished: \(response.data)")
}
} catch let error {
print("got an error creating the request: \(error)")
}
}
Anyone an Idea ??