Merge pull request #11 from keeshux/add-ncp-support

Add initial NCP support
This commit is contained in:
Davide De Rosa 2018-09-02 02:24:34 +02:00 committed by GitHub
commit 7df229c115
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 92 additions and 71 deletions

View File

@ -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

View File

@ -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")

View File

@ -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;

View File

@ -80,7 +80,7 @@
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);
@ -103,7 +103,9 @@
self.inReplay = [[ReplayProtector alloc] init];
}
self.compressionFraming = CompressionFramingNativeDisabled;
[self.encrypter setPeerId:peerId];
[self.decrypter setPeerId:peerId];
[self setCompressionFraming:compressionFraming];
}
return self;
}
@ -150,15 +152,6 @@
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) {

View File

@ -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 {
@ -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?
@ -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
@ -207,6 +220,7 @@ extension SessionProxy {
var dnsServers: [String] = []
var authToken: String?
var peerId: UInt32?
var cipher: SessionProxy.Cipher?
// MARK: Routing (IPv4)
@ -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
}
}
}

View File

@ -74,8 +74,6 @@ extension SessionProxy {
private var isTLSConnected: Bool
private var canHandlePackets: Bool
init(id: UInt8) {
self.id = id
@ -83,7 +81,6 @@ extension SessionProxy {
state = .invalid
softReset = false
isTLSConnected = false
canHandlePackets = false
}
// Ruby: Key.hard_reset_timeout
@ -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)
}
@ -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 {

View File

@ -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?
@ -229,7 +227,7 @@ public class SessionProxy {
- Returns: `true` if supports link rebinding.
*/
public func canRebindLink() -> Bool {
return (peerId != nil)
return (pushReply?.peerId != nil)
}
/**
@ -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
}
@ -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
@ -614,7 +611,6 @@ public class SessionProxy {
controlPacketIdOut = 0
controlPacketIdIn = 0
authenticator = nil
peerId = nil
bytesIn = 0
bytesOut = 0
}
@ -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 {
@ -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)
@ -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)
}
@ -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
@ -848,8 +849,6 @@ public class SessionProxy {
return
}
setupKeys()
negotiationKey.controlState = .preIfConfig
nextPushRequestDate = Date().addingTimeInterval(negotiationKey.softReset ? CoreConfiguration.softResetDelay : CoreConfiguration.retransmissionLimit)
pushRequest()
@ -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")
@ -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())")
@ -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,
@ -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
)

View File

@ -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)

View File

@ -54,7 +54,14 @@ class DataPathPerformanceTests: XCTestCase {
encrypter = crypto.encrypter()
decrypter = crypto.decrypter()
dataPath = DataPath(encrypter: encrypter, decrypter: decrypter, maxPackets: 200, usesReplayProtection: false)
dataPath = DataPath(
encrypter: encrypter,
decrypter: decrypter,
peerId: PacketPeerIdDisabled,
compressionFraming: .disabled,
maxPackets: 200,
usesReplayProtection: false
)
}
override func tearDown() {

View File

@ -91,4 +91,12 @@ class PushTests: XCTestCase {
XCTAssertEqual(reply.ipv6?.defaultGateway, "fe80::601:30ff:feb7:dc02")
XCTAssertEqual(reply.dnsServers, ["2001:4860:4860::8888", "2001:4860:4860::8844"])
}
func testNCP() {
let msg = "PUSH_REPLY,dhcp-option DNS 8.8.8.8,dhcp-option DNS 4.4.4.4,comp-lzo no,route 10.8.0.1,topology net30,ping 10,ping-restart 120,ifconfig 10.8.0.6 10.8.0.5,peer-id 0,cipher AES-256-CBC"
let reply = try! SessionProxy.PushReply(message: msg)!
reply.debug()
XCTAssertEqual(reply.cipher, .aes256cbc)
}
}