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

Add initial NCP support #11

Merged
merged 10 commits into from
Sep 2, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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