Skip to content
This repository has been archived by the owner on Sep 29, 2024. It is now read-only.

Commit

Permalink
Merge pull request #11 from keeshux/add-ncp-support
Browse files Browse the repository at this point in the history
Add initial NCP support
  • Loading branch information
keeshux authored Sep 2, 2018
2 parents 0360a32 + e7e0e95 commit 7df229c
Show file tree
Hide file tree
Showing 10 changed files with 92 additions and 71 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ The client is known to work with [OpenVPN®][openvpn] 2.3+ servers. Key renegoti
- [x] Handshake and tunneling over UDP or TCP
- [x] Ciphers
- AES-CBC (128 and 256 bit)
- AES-GCM (128 and 256 bit)
- AES-GCM (128 and 256 bit, 2.4)
- [x] HMAC digests
- SHA-1
- SHA-256
- [x] NCP (Negotiable Crypto Parameters, 2.4)
- Server-side
- [x] TLS handshake
- CA validation
- Client certificate
Expand Down
3 changes: 2 additions & 1 deletion TunnelKit/Sources/Core/CoreConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,9 @@ struct CoreConfiguration {
// MARK: Authentication

static let peerInfo = [
"IV_VER=2.3.99",
"IV_VER=2.4",
"IV_PROTO=2",
"IV_NCP=2",
""
].joined(separator: "\n")

Expand Down
5 changes: 2 additions & 3 deletions TunnelKit/Sources/Core/DataPath.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,11 @@

- (nonnull instancetype)initWithEncrypter:(nonnull id<DataPathEncrypter>)encrypter
decrypter:(nonnull id<DataPathDecrypter>)decrypter
peerId:(uint32_t)peerId // 24-bit, discard most significant byte
compressionFraming:(CompressionFramingNative)compressionFraming
maxPackets:(NSInteger)maxPackets
usesReplayProtection:(BOOL)usesReplayProtection;

- (void)setPeerId:(uint32_t)peerId; // 24-bit, discard most significant byte
- (void)setCompressionFraming:(CompressionFramingNative)compressionFraming;

- (NSArray<NSData *> *)encryptPackets:(nonnull NSArray<NSData *> *)packets key:(uint8_t)key error:(NSError **)error;
- (NSArray<NSData *> *)decryptPackets:(nonnull NSArray<NSData *> *)packets keepAlive:(nullable bool *)keepAlive error:(NSError **)error;

Expand Down
15 changes: 4 additions & 11 deletions TunnelKit/Sources/Core/DataPath.m
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ + (uint8_t *)alignedPointer:(uint8_t *)pointer
return (uint8_t *)addr;
}

- (instancetype)initWithEncrypter:(id<DataPathEncrypter>)encrypter decrypter:(id<DataPathDecrypter>)decrypter maxPackets:(NSInteger)maxPackets usesReplayProtection:(BOOL)usesReplayProtection
- (instancetype)initWithEncrypter:(id<DataPathEncrypter>)encrypter decrypter:(id<DataPathDecrypter>)decrypter peerId:(uint32_t)peerId compressionFraming:(CompressionFramingNative)compressionFraming maxPackets:(NSInteger)maxPackets usesReplayProtection:(BOOL)usesReplayProtection
{
NSParameterAssert(encrypter);
NSParameterAssert(decrypter);
Expand All @@ -103,7 +103,9 @@ - (instancetype)initWithEncrypter:(id<DataPathEncrypter>)encrypter decrypter:(id
self.inReplay = [[ReplayProtector alloc] init];
}

self.compressionFraming = CompressionFramingNativeDisabled;
[self.encrypter setPeerId:peerId];
[self.decrypter setPeerId:peerId];
[self setCompressionFraming:compressionFraming];
}
return self;
}
Expand Down Expand Up @@ -150,15 +152,6 @@ - (uint8_t *)decBufferAligned
return [[self class] alignedPointer:self.decBuffer];
}

- (void)setPeerId:(uint32_t)peerId
{
NSAssert(self.encrypter, @"Setting peer-id to nil encrypter");
NSAssert(self.decrypter, @"Setting peer-id to nil decrypter");

[self.encrypter setPeerId:peerId];
[self.decrypter setPeerId:peerId];
}

- (void)setCompressionFraming:(CompressionFramingNative)compressionFraming
{
switch (compressionFraming) {
Expand Down
21 changes: 21 additions & 0 deletions TunnelKit/Sources/Core/SessionProxy+PushReply.swift
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,15 @@ public protocol SessionReply {

/// The DNS servers set up for this session.
var dnsServers: [String] { get }

/// The optional authentication token.
var authToken: String? { get }

/// The optional 24-bit peer-id.
var peerId: UInt32? { get }

/// The negotiated cipher if any (NCP).
var cipher: SessionProxy.Cipher? { get }
}

extension SessionProxy {
Expand Down Expand Up @@ -179,6 +188,8 @@ extension SessionProxy {

private static let peerIdRegexp = try! NSRegularExpression(pattern: "peer-id [0-9]+", options: [])

private static let cipherRegexp = try! NSRegularExpression(pattern: "cipher [^\\s]+", options: [])

let ipv4: IPv4Settings?

let ipv6: IPv6Settings?
Expand All @@ -189,6 +200,8 @@ extension SessionProxy {

let peerId: UInt32?

let cipher: SessionProxy.Cipher?

init?(message: String) throws {
guard message.hasPrefix("PUSH_REPLY") else {
return nil
Expand All @@ -207,6 +220,7 @@ extension SessionProxy {
var dnsServers: [String] = []
var authToken: String?
var peerId: UInt32?
var cipher: SessionProxy.Cipher?

// MARK: Routing (IPv4)

Expand Down Expand Up @@ -354,10 +368,17 @@ extension SessionProxy {
PushReply.peerIdRegexp.enumerateArguments(in: message) {
peerId = UInt32($0[0])
}

// MARK: NCP

PushReply.cipherRegexp.enumerateArguments(in: message) {
cipher = SessionProxy.Cipher(rawValue: $0[0].uppercased())
}

self.dnsServers = dnsServers
self.authToken = authToken
self.peerId = peerId
self.cipher = cipher
}
}
}
Expand Down
17 changes: 0 additions & 17 deletions TunnelKit/Sources/Core/SessionProxy+SessionKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,16 +74,13 @@ extension SessionProxy {

private var isTLSConnected: Bool

private var canHandlePackets: Bool

init(id: UInt8) {
self.id = id

startTime = Date()
state = .invalid
softReset = false
isTLSConnected = false
canHandlePackets = false
}

// Ruby: Key.hard_reset_timeout
Expand All @@ -109,21 +106,11 @@ extension SessionProxy {
return isTLSConnected
}

func startHandlingPackets(withPeerId peerId: UInt32? = nil, compressionFraming: CompressionFraming = .disabled) {
dataPath?.setPeerId(peerId ?? PacketPeerIdDisabled)
dataPath?.setCompressionFraming(compressionFraming.native)
canHandlePackets = true
}

func encrypt(packets: [Data]) throws -> [Data]? {
guard let dataPath = dataPath else {
log.warning("Data: Set dataPath first")
return nil
}
guard canHandlePackets else {
log.warning("Data: Invoke startHandlingPackets() before encrypting")
return nil
}
return try dataPath.encryptPackets(packets, key: id)
}

Expand All @@ -132,10 +119,6 @@ extension SessionProxy {
log.warning("Data: Set dataPath first")
return nil
}
guard canHandlePackets else {
log.warning("Data: Invoke startHandlingPackets() before decrypting")
return nil
}
var keepAlive = false
let decrypted = try dataPath.decryptPackets(packets, keepAlive: &keepAlive)
if keepAlive {
Expand Down
71 changes: 36 additions & 35 deletions TunnelKit/Sources/Core/SessionProxy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,7 @@ public class SessionProxy {

private var remoteSessionId: Data?

private var authToken: String?

private var peerId: UInt32?
private var pushReply: SessionReply?

private var nextPushRequestDate: Date?

Expand Down Expand Up @@ -229,7 +227,7 @@ public class SessionProxy {
- Returns: `true` if supports link rebinding.
*/
public func canRebindLink() -> Bool {
return (peerId != nil)
return (pushReply?.peerId != nil)
}

/**
Expand All @@ -241,7 +239,7 @@ public class SessionProxy {
- Seealso: `canRebindLink()`.
*/
public func rebindLink(_ link: LinkInterface) {
guard let _ = peerId else {
guard let _ = pushReply?.peerId else {
log.warning("Session doesn't support link rebinding!")
return
}
Expand Down Expand Up @@ -316,11 +314,10 @@ public class SessionProxy {

sessionId = nil
remoteSessionId = nil
authToken = nil
nextPushRequestDate = nil
connectedDate = nil
authenticator = nil
peerId = nil
pushReply = nil
link = nil
if !(tunnel?.isPersistent ?? false) {
tunnel = nil
Expand Down Expand Up @@ -614,7 +611,6 @@ public class SessionProxy {
controlPacketIdOut = 0
controlPacketIdIn = 0
authenticator = nil
peerId = nil
bytesIn = 0
bytesOut = 0
}
Expand All @@ -624,6 +620,7 @@ public class SessionProxy {
log.debug("Send hard reset")

resetControlChannel()
pushReply = nil
do {
try sessionId = SecureRandom.data(length: ProtocolMacros.sessionIdLength)
} catch let e {
Expand Down Expand Up @@ -662,7 +659,7 @@ public class SessionProxy {
negotiationKey.controlState = .preAuth

do {
authenticator = try Authenticator(configuration.username, authToken ?? configuration.password)
authenticator = try Authenticator(configuration.username, pushReply?.authToken ?? configuration.password)
try authenticator?.putAuth(into: negotiationKey.tls)
} catch let e {
deferStop(.shutdown, e)
Expand Down Expand Up @@ -701,11 +698,7 @@ public class SessionProxy {
enqueueControlPackets(code: .controlV1, key: negotiationKey.id, payload: cipherTextOut)

if negotiationKey.softReset {
authenticator = nil
negotiationKey.startHandlingPackets(withPeerId: peerId)
negotiationKey.controlState = .connected
connectedDate = Date()
transitionKeys()
completeConnection()
}
nextPushRequestDate = Date().addingTimeInterval(CoreConfiguration.retransmissionLimit)
}
Expand All @@ -725,6 +718,14 @@ public class SessionProxy {
}
}

private func completeConnection() {
setupEncryption()
authenticator = nil
negotiationKey.controlState = .connected
connectedDate = Date()
transitionKeys()
}

// MARK: Control

// Ruby: handle_ctrl_pkt
Expand Down Expand Up @@ -848,8 +849,6 @@ public class SessionProxy {
return
}

setupKeys()

negotiationKey.controlState = .preIfConfig
nextPushRequestDate = Date().addingTimeInterval(negotiationKey.softReset ? CoreConfiguration.softResetDelay : CoreConfiguration.retransmissionLimit)
pushRequest()
Expand Down Expand Up @@ -884,21 +883,13 @@ public class SessionProxy {
return
}
reply = optionalReply
authToken = reply.authToken
peerId = reply.peerId
} catch let e {
deferStop(.shutdown, e)
return
}

authenticator = nil
negotiationKey.startHandlingPackets(
withPeerId: peerId,
compressionFraming: configuration.compressionFraming
)
negotiationKey.controlState = .connected
connectedDate = Date()
transitionKeys()
pushReply = reply
completeConnection()

guard let remoteAddress = link?.remoteAddress else {
fatalError("Could not resolve link remote address")
Expand Down Expand Up @@ -1007,22 +998,25 @@ public class SessionProxy {
}

// Ruby: setup_keys
private func setupKeys() {
private func setupEncryption() {
guard let auth = authenticator else {
fatalError("Setting up keys without having authenticated")
fatalError("Setting up encryption without having authenticated")
}
guard let sessionId = sessionId else {
fatalError("Setting up keys without a local sessionId")
fatalError("Setting up encryption without a local sessionId")
}
guard let remoteSessionId = remoteSessionId else {
fatalError("Setting up keys without a remote sessionId")
fatalError("Setting up encryption without a remote sessionId")
}
guard let serverRandom1 = auth.serverRandom1, let serverRandom2 = auth.serverRandom2 else {
fatalError("Setting up keys without server randoms")
fatalError("Setting up encryption without server randoms")
}

guard let pushReply = pushReply else {
fatalError("Setting up encryption without a former PUSH_REPLY")
}

if CoreConfiguration.logsSensitiveData {
log.debug("Setup keys from the following components:")
log.debug("Set up encryption from the following components:")
log.debug("\tpreMaster: \(auth.preMaster.toHex())")
log.debug("\trandom1: \(auth.random1.toHex())")
log.debug("\trandom2: \(auth.random2.toHex())")
Expand All @@ -1031,13 +1025,18 @@ public class SessionProxy {
log.debug("\tsessionId: \(sessionId.toHex())")
log.debug("\tremoteSessionId: \(remoteSessionId.toHex())")
} else {
log.debug("Setup keys")
log.debug("Set up encryption")
}

let pushedCipher = pushReply.cipher
if let negCipher = pushedCipher {
log.debug("Negotiated cipher: \(negCipher.rawValue)")
}

let bridge: EncryptionBridge
do {
bridge = try EncryptionBridge(
configuration.cipher,
pushedCipher ?? configuration.cipher,
configuration.digest,
auth,
sessionId,
Expand All @@ -1051,6 +1050,8 @@ public class SessionProxy {
negotiationKey.dataPath = DataPath(
encrypter: bridge.encrypter(),
decrypter: bridge.decrypter(),
peerId: pushReply.peerId ?? PacketPeerIdDisabled,
compressionFraming: configuration.compressionFraming.native,
maxPackets: link?.packetBufferSize ?? 200,
usesReplayProtection: CoreConfiguration.usesReplayProtection
)
Expand Down
10 changes: 8 additions & 2 deletions TunnelKitTests/DataPathEncryptionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,14 @@ class DataPathEncryptionTests: XCTestCase {
}

func privateTestDataPathHigh(peerId: UInt32?) {
let path = DataPath(encrypter: enc, decrypter: dec, maxPackets: 1000, usesReplayProtection: false)
path.setCompressionFraming(.disabled)
let path = DataPath(
encrypter: enc,
decrypter: dec,
peerId: peerId ?? PacketPeerIdDisabled,
compressionFraming: .disabled,
maxPackets: 1000,
usesReplayProtection: false
)

if let peerId = peerId {
enc.setPeerId(peerId)
Expand Down
Loading

0 comments on commit 7df229c

Please sign in to comment.