From 86e32b5f600062e17c46654fc109028f5a70b922 Mon Sep 17 00:00:00 2001 From: MacOMNI <414294494@qq.com> Date: Wed, 23 Oct 2024 12:19:49 +0800 Subject: [PATCH] more peer test (#191) * update * update netaddr test * update peer test * update peer test * update test * update peer --- Networking/Sources/CHelpers/helpers.c | 83 +++++++++++ Networking/Sources/CHelpers/helpers.h | 10 +- Networking/Sources/MsQuicSwift/NetAddr.swift | 4 +- Networking/Sources/Networking/PKCS12.swift | 45 ++++-- Networking/Sources/Networking/Peer.swift | 4 +- .../Tests/MsQuicSwiftTests/NetAddrTests.swift | 12 +- ...eerTest.swift => MockPeerEventTests.swift} | 62 +++++++- .../Tests/NetworkingTests/PKCS12Tests.swift | 6 +- .../Tests/NetworkingTests/PeerTests.swift | 138 ++++++++++++++++++ 9 files changed, 341 insertions(+), 23 deletions(-) rename Networking/Tests/NetworkingTests/{PeerTest.swift => MockPeerEventTests.swift} (76%) create mode 100644 Networking/Tests/NetworkingTests/PeerTests.swift diff --git a/Networking/Sources/CHelpers/helpers.c b/Networking/Sources/CHelpers/helpers.c index 0e449fc9..d0a9fbc8 100644 --- a/Networking/Sources/CHelpers/helpers.c +++ b/Networking/Sources/CHelpers/helpers.c @@ -7,6 +7,89 @@ #include "helpers.h" +int parse_certificate( + const unsigned char *data, + size_t length, + unsigned char **public_key, + size_t *public_key_len, + char **alt_name, + char **error_message) +{ + int ret = -1; + BIO *bio = NULL; + X509 *cert = NULL; + STACK_OF(GENERAL_NAME) *alt_names = NULL; + + // Create BIO from certificate data + bio = BIO_new_mem_buf(data, (int)length); + if (!bio) { + *error_message = "Failed to create BIO."; + goto cleanup; + } + + // Parse the X509 certificate from the BIO + cert = d2i_X509_bio(bio, NULL); + if (!cert) { + *error_message = "Failed to parse X509 certificate."; + goto cleanup; + } + + // Extract the public key from the certificate + EVP_PKEY *pkey = X509_get_pubkey(cert); + if (!pkey) { + *error_message = "Failed to get public key from certificate."; + goto cleanup; + } + + // Get the raw public key + if (EVP_PKEY_get_raw_public_key(pkey, NULL, public_key_len) <= 0) { + *error_message = "Failed to get public key length."; + goto cleanup; + } + *public_key = (unsigned char *)malloc(*public_key_len); + if (!*public_key) { + *error_message = "Failed to allocate memory for public key."; + goto cleanup; + } + + if (EVP_PKEY_get_raw_public_key(pkey, *public_key, public_key_len) <= 0) { + *error_message = "Failed to extract public key."; + goto cleanup; + } + + // Extract the alternative name from the certificate (if present) + alt_names = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); + if (alt_names) { + for (int i = 0; i < sk_GENERAL_NAME_num(alt_names); i++) { + GENERAL_NAME *gen_name = sk_GENERAL_NAME_value(alt_names, i); + if (gen_name->type == GEN_DNS) { + ASN1_STRING *name = gen_name->d.dNSName; + *alt_name = strdup((char *)ASN1_STRING_get0_data(name)); + break; + } + } + sk_GENERAL_NAME_pop_free(alt_names, GENERAL_NAME_free); + } + + if (!*alt_name) { + *error_message = "No alternative name found."; + goto cleanup; + } + + ret = EXIT_SUCCESS; // Success + +cleanup: + if (ret != 0 && *public_key) { + free(*public_key); + *public_key = NULL; + } + BIO_free(bio); + EVP_PKEY_free(pkey); + X509_free(cert); + + return ret; +} + // Function to parse certificate and extract public key and alternative name int parse_pkcs12_certificate( const unsigned char *data, diff --git a/Networking/Sources/CHelpers/helpers.h b/Networking/Sources/CHelpers/helpers.h index 3c269797..869f6659 100644 --- a/Networking/Sources/CHelpers/helpers.h +++ b/Networking/Sources/CHelpers/helpers.h @@ -1,6 +1,14 @@ #include #include +int parse_certificate( + const unsigned char *data, + size_t length, + unsigned char **public_key, + size_t *public_key_len, + char **alt_name, + char **error_message); + int parse_pkcs12_certificate( const unsigned char *data, size_t length, @@ -21,4 +29,4 @@ char *get_error_string(int error); static inline uint16_t helper_ntohs(in_port_t netport) { return ntohs(netport); -} \ No newline at end of file +} diff --git a/Networking/Sources/MsQuicSwift/NetAddr.swift b/Networking/Sources/MsQuicSwift/NetAddr.swift index e6593558..245e201d 100644 --- a/Networking/Sources/MsQuicSwift/NetAddr.swift +++ b/Networking/Sources/MsQuicSwift/NetAddr.swift @@ -104,7 +104,7 @@ private func parseQuicAddr(_ addr: QUIC_ADDR) -> (String, UInt16, Bool)? { private func parseIpv4Addr(_ address: String) -> (String, UInt16)? { let ipv4Pattern = #"((?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)"# - let ipv4WithPortPattern = #"(\#(ipv4Pattern)):(\d{1,5})"# + let ipv4WithPortPattern = #"(\#(ipv4Pattern)):(\d{1,5})(?=\s*$|\s+)"# let regex = try? NSRegularExpression(pattern: ipv4WithPortPattern, options: []) let range = NSRange(location: 0, length: address.utf16.count) @@ -142,7 +142,7 @@ private func parseIpv6Addr(_ address: String) -> (String, UInt16)? { "|(?:(?:[0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})?::", ")", ].reduce("", +) - let ipv6WithPortPattern = #"\[(\#(ipv6Pattern))\]:(\d{1,5})"# + let ipv6WithPortPattern = #"\[(\#(ipv6Pattern))\]:(\d{1,5})(?=\s*$|\s+)"# let regex = try? NSRegularExpression(pattern: ipv6WithPortPattern, options: []) let range = NSRange(location: 0, length: address.utf16.count) diff --git a/Networking/Sources/Networking/PKCS12.swift b/Networking/Sources/Networking/PKCS12.swift index 0120c824..a352477f 100644 --- a/Networking/Sources/Networking/PKCS12.swift +++ b/Networking/Sources/Networking/PKCS12.swift @@ -7,22 +7,45 @@ enum CryptoError: Error { case parseFailed(String) } -public func parseCertificate(data: Data) throws -> (publicKey: Data, alternativeName: String) { +public enum CertificateType { + case x509 + case p12 +} + +public func parseCertificate(data: Data, type: CertificateType) throws -> ( + publicKey: Data, alternativeName: String +) { var publicKeyPointer: UnsafeMutablePointer! var publicKeyLen = 0 var altNamePointer: UnsafeMutablePointer! var errorMessage: UnsafeMutablePointer? defer { free(altNamePointer) } - let result = data.withUnsafeBytes { (bytes: UnsafeRawBufferPointer) in - parse_pkcs12_certificate( - bytes.baseAddress!.assumingMemoryBound(to: UInt8.self), - data.count, - &publicKeyPointer, - &publicKeyLen, - &altNamePointer, - &errorMessage - ) - } + + let result: Int32 = + switch type { + case .x509: + data.withUnsafeBytes { (bytes: UnsafeRawBufferPointer) in + parse_certificate( + bytes.baseAddress!.assumingMemoryBound(to: UInt8.self), + data.count, + &publicKeyPointer, + &publicKeyLen, + &altNamePointer, + &errorMessage + ) + } + case .p12: + data.withUnsafeBytes { (bytes: UnsafeRawBufferPointer) in + parse_pkcs12_certificate( + bytes.baseAddress!.assumingMemoryBound(to: UInt8.self), + data.count, + &publicKeyPointer, + &publicKeyLen, + &altNamePointer, + &errorMessage + ) + } + } guard result == 0 else { throw CryptoError.parseFailed(String(cString: errorMessage!)) diff --git a/Networking/Sources/Networking/Peer.swift b/Networking/Sources/Networking/Peer.swift index 33f026fe..43e10752 100644 --- a/Networking/Sources/Networking/Peer.swift +++ b/Networking/Sources/Networking/Peer.swift @@ -266,7 +266,7 @@ private struct PeerEventHandler: QuicEventHandler { return .code(.requiredCert) } do { - let (publicKey, alternativeName) = try parseCertificate(data: certificate) + let (publicKey, alternativeName) = try parseCertificate(data: certificate, type: .x509) logger.debug( "Certificate parsed", metadata: ["publicKey": "\(publicKey.toHexString())", "alternativeName": "\(alternativeName)"] @@ -385,7 +385,7 @@ public final class MockPeerEventHandler: QuicEventHandler { return .code(.requiredCert) } do { - let (publicKey, alternativeName) = try parseCertificate(data: certificate) + let (publicKey, alternativeName) = try parseCertificate(data: certificate, type: .x509) if alternativeName != generateSubjectAlternativeName(pubkey: publicKey) { return .code(.badCert) } diff --git a/Networking/Tests/MsQuicSwiftTests/NetAddrTests.swift b/Networking/Tests/MsQuicSwiftTests/NetAddrTests.swift index 7ef858b0..dbd3104c 100644 --- a/Networking/Tests/MsQuicSwiftTests/NetAddrTests.swift +++ b/Networking/Tests/MsQuicSwiftTests/NetAddrTests.swift @@ -51,9 +51,15 @@ struct NetAddrTests { @Test func parseInvalidFormat() async throws { - let address = "abcd:::" - let netAddr = NetAddr(address: address) - #expect(netAddr == nil) + let address1 = "abcd:::" + let netAddr1 = NetAddr(address: address1) + #expect(netAddr1 == nil) + let address2 = "127.0.0.1:12,awef" + let netAddr2 = NetAddr(address: address2) + #expect(netAddr2 == nil) + let address3 = "[2001:db8:85a3::8a2e:370:7334]:8080,8081,8082" + let netAddr3 = NetAddr(address: address3) + #expect(netAddr3 == nil) } @Test diff --git a/Networking/Tests/NetworkingTests/PeerTest.swift b/Networking/Tests/NetworkingTests/MockPeerEventTests.swift similarity index 76% rename from Networking/Tests/NetworkingTests/PeerTest.swift rename to Networking/Tests/NetworkingTests/MockPeerEventTests.swift index d35f166a..1ad9adda 100644 --- a/Networking/Tests/NetworkingTests/PeerTest.swift +++ b/Networking/Tests/NetworkingTests/MockPeerEventTests.swift @@ -5,7 +5,7 @@ import Utils @testable import Networking -final class PeerTests { +final class MockPeerEventTests { let registration: QuicRegistration let certData: Data let badCertData: Data @@ -98,6 +98,66 @@ final class PeerTests { ) } + @Test + func connected() async throws { + let serverHandler = MockPeerEventHandler() + let clientHandler = MockPeerEventHandler() + let privateKey1 = try Ed25519.SecretKey(from: Data32()) + let cert = try generateSelfSignedCertificate(privateKey: privateKey1) + let serverConfiguration = try QuicConfiguration( + registration: registration, + pkcs12: certData, + alpns: [Data("testalpn".utf8)], + client: false, + settings: QuicSettings.defaultSettings + ) + + let listener = try QuicListener( + handler: serverHandler, + registration: registration, + configuration: serverConfiguration, + listenAddress: NetAddr(ipAddress: "127.0.0.1", port: 0)!, + alpns: [Data("testalpn".utf8)] + ) + + let listenAddress = try listener.listenAddress() + // Client setup with certificate + let clientConfiguration = try QuicConfiguration( + registration: registration, + pkcs12: cert, + alpns: [Data("testalpn".utf8)], + client: true, + settings: QuicSettings.defaultSettings + ) + + let clientConnection = try QuicConnection( + handler: clientHandler, + registration: registration, + configuration: clientConfiguration + ) + + // Attempt to connect + try clientConnection.connect(to: listenAddress) + let stream1 = try clientConnection.createStream() + try stream1.send(data: Data("test data 1".utf8)) + + try? await Task.sleep(for: .milliseconds(100)) + let (_, info) = serverHandler.events.value.compactMap { + switch $0 { + case let .newConnection(_, connection, info): + (connection, info) as (QuicConnection, ConnectionInfo)? + default: + nil + } + }.first! + let (ipAddress2, _) = info.remoteAddress.getAddressAndPort() + + #expect(info.negotiatedAlpn == Data("testalpn".utf8)) + #expect(info.serverName == "127.0.0.1") + #expect(info.localAddress == listenAddress) + #expect(ipAddress2 == "127.0.0.1") + } + @Test func rejectsConDueToBadClientCert() async throws { let serverHandler = MockPeerEventHandler() diff --git a/Networking/Tests/NetworkingTests/PKCS12Tests.swift b/Networking/Tests/NetworkingTests/PKCS12Tests.swift index a884b6de..19b5801d 100644 --- a/Networking/Tests/NetworkingTests/PKCS12Tests.swift +++ b/Networking/Tests/NetworkingTests/PKCS12Tests.swift @@ -8,14 +8,14 @@ import Utils struct PKCS12Tests { @Test func invalidParseCertificate() async throws { #expect(throws: CryptoError.self) { - _ = try parseCertificate(data: Data("wrong cert data".utf8)) + _ = try parseCertificate(data: Data("wrong cert data".utf8), type: .p12) } } - @Test func vailidParseCertificate() async throws { + @Test func vailidParseP12Certificate() async throws { let privateKey = try Ed25519.SecretKey(from: Data32()) let cert = try generateSelfSignedCertificate(privateKey: privateKey) - let (publicKey, alternativeName) = try parseCertificate(data: cert) + let (publicKey, alternativeName) = try parseCertificate(data: cert, type: .p12) #expect(alternativeName == generateSubjectAlternativeName(publicKey: privateKey.publicKey)) #expect(Data32(publicKey) == privateKey.publicKey.data) } diff --git a/Networking/Tests/NetworkingTests/PeerTests.swift b/Networking/Tests/NetworkingTests/PeerTests.swift new file mode 100644 index 00000000..0846d22f --- /dev/null +++ b/Networking/Tests/NetworkingTests/PeerTests.swift @@ -0,0 +1,138 @@ +// import Foundation +// import MsQuicSwift +// import Testing +// import Utils +// +// @testable import Networking +// +// struct PeerTests { +// struct MockMessage: MessageProtocol { +// let data: Data +// func encode() throws -> Data { +// data +// } +// } +// +// struct MockRequest: RequestProtocol { +// var kind: Kind +// var data: Data +// func encode() throws -> Data { +// data +// } +// +// typealias StreamKind = Kind +// } +// +// public enum UniquePresistentStreamKind: UInt8, StreamKindProtocol { +// case uniqueA = 0x01 +// case uniqueB = 0x02 +// case uniqueC = 0x03 +// } +// +// public enum EphemeralStreamKind: UInt8, StreamKindProtocol { +// case typeA = 0x04 +// case typeB = 0x05 +// case typeC = 0x06 +// } +// +// struct MockMessageDecoder: MessageDecoder { +// typealias Message = MockMessage +// +// func decode(data: Data) throws -> Message { +// MockMessage(data: data) +// } +// +// func finish() -> Data? { +// print("MockMessageDecoder finish") +// return nil +// } +// } +// +// struct MockEphemeralStreamHandler: EphemeralStreamHandler { +// typealias StreamKind = EphemeralStreamKind +// typealias Request = MockRequest +// +// func createDecoder(kind _: StreamKind) -> any MessageDecoder { +// return MockMessageDecoder() as! any MessageDecoder +// } +// +// // deal with data +// func handle(connection _: any ConnectionInfoProtocol, request _: Request) async throws -> Data { +// print("MockEphemeralStreamHandler handle") +// return Data() +// } +// } +// +// struct MockPresentStreamHandler: PresistentStreamHandler { +// func streamOpened( +// connection _: any Networking.ConnectionInfoProtocol, +// stream _: any Networking.StreamProtocol, kind _: PeerTests.UniquePresistentStreamKind +// ) async throws { +// print("streamOpened") +// } +// +// func handle( +// connection _: any Networking.ConnectionInfoProtocol, +// message _: PeerTests.MockRequest +// ) async throws { +// print("handle") +// } +// +// typealias StreamKind = UniquePresistentStreamKind +// typealias Request = MockRequest +// +// func createDecoder(kind _: StreamKind) -> any MessageDecoder { +// return MockMessageDecoder() as! any MessageDecoder +// } +// +// func handle(connection _: any ConnectionInfoProtocol, request _: Request) async throws -> Data { +// Data() +// } +// } +// +// struct MockStreamHandler: StreamHandler { +// typealias PresistentHandler = MockPresentStreamHandler +// +// typealias EphemeralHandler = MockEphemeralStreamHandler +// } +// +// @Test +// func peerInit() async throws { +// let peer1 = try Peer( +// options: PeerOptions( +// mode: .validator, +// listenAddress: NetAddr(ipAddress: "127.0.0.1", port: 8081)!, +// genesisHeader: Data32(), +// secretKey: Ed25519.SecretKey(from: Data32()), +// presistentStreamHandler: MockPresentStreamHandler(), +// ephemeralStreamHandler: MockEphemeralStreamHandler(), +// serverSettings: .defaultSettings, +// clientSettings: .defaultSettings +// ) +// ) +// let peer2 = try Peer( +// options: PeerOptions( +// mode: .validator, +// listenAddress: NetAddr(ipAddress: "127.0.0.1", port: 8082)!, +// genesisHeader: Data32(), +// secretKey: Ed25519.SecretKey(from: Data32()), +// presistentStreamHandler: MockPresentStreamHandler(), +// ephemeralStreamHandler: MockEphemeralStreamHandler(), +// serverSettings: .defaultSettings, +// clientSettings: .defaultSettings +// ) +// ) +// try? await Task.sleep(for: .seconds(1)) +// let connection = try peer1.connect( +// to: NetAddr(ipAddress: "127.0.0.1", port: 8082)!, mode: .validator +// ) +// try? await Task.sleep(for: .seconds(2)) +// let data = try await connection.request(MockRequest(kind: .typeA, data: Data("hello world".utf8))) +//// if let string = String(data: data, encoding: .utf8) { +//// print(string) +//// } else { +//// print("Failed to convert Data to String") +//// } +// try? await Task.sleep(for: .seconds(10)) +// } +// }