From 6f6149ecebd0ada867a58de9e9ed0344f4431531 Mon Sep 17 00:00:00 2001 From: Joannis Orlandos Date: Tue, 4 Nov 2025 19:13:02 +0100 Subject: [PATCH] Add support for Windows Fix compilation of NIOSSL on Windows Note: This code adds some things we might not want to keep around. - OPENSSL_NO_ASM flag which I neededto get it to work - Windows directory listing using FindFirstFileA (not properly tested) - mlock -> VirtualLock on Windows (doesn't yet check if locking was successful) --- Package.swift | 2 ++ .../include/CNIOBoringSSL_base.h | 33 +++++++++++++++++++ Sources/NIOSSL/ByteBufferBIO.swift | 2 ++ Sources/NIOSSL/IdentityVerification.swift | 2 ++ Sources/NIOSSL/NIOSSLClientHandler.swift | 2 ++ Sources/NIOSSL/PosixPort.swift | 21 ++++++++++-- Sources/NIOSSL/SSLCallbacks.swift | 2 ++ Sources/NIOSSL/SSLCertificate.swift | 4 ++- Sources/NIOSSL/SSLContext.swift | 33 +++++++++++++++++-- Sources/NIOSSL/SSLPKCS12Bundle.swift | 10 +++++- Sources/NIOSSL/SSLPrivateKey.swift | 2 +- Sources/NIOSSL/SSLPublicKey.swift | 2 +- Sources/NIOSSL/SubjectAlternativeName.swift | 16 +++++++-- Sources/NIOSSLHTTP1Client/main.swift | 2 ++ Sources/NIOTLSServer/main.swift | 4 +++ 15 files changed, 127 insertions(+), 10 deletions(-) diff --git a/Package.swift b/Package.swift index 833ecea6..04d2272d 100644 --- a/Package.swift +++ b/Package.swift @@ -83,6 +83,8 @@ let package = Package( .define("_GNU_SOURCE"), .define("_POSIX_C_SOURCE", to: "200112L"), .define("_DARWIN_C_SOURCE"), + // Disable assembly on Windows as SPM doesn't support .S files on Windows + .define("OPENSSL_NO_ASM", .when(platforms: [.windows])), ] ), .target( diff --git a/Sources/CNIOBoringSSL/include/CNIOBoringSSL_base.h b/Sources/CNIOBoringSSL/include/CNIOBoringSSL_base.h index a9a99fee..e651240a 100644 --- a/Sources/CNIOBoringSSL/include/CNIOBoringSSL_base.h +++ b/Sources/CNIOBoringSSL/include/CNIOBoringSSL_base.h @@ -12,6 +12,39 @@ #define BORINGSSL_PREFIX CNIOBoringSSL +// On Windows, prevent conflicts between winsock.h and winsock2.h +// Also prevent min/max macro conflicts with std::numeric_limits +#if defined(_WIN32) || defined(_WIN64) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#ifndef NOMINMAX +#define NOMINMAX +#endif +#ifndef _WINSOCKAPI_ +#define _WINSOCKAPI_ // Prevent winsock.h from being included +#endif +#include +#include + +// Undefine Windows CryptoAPI macros that conflict with BoringSSL types +// wincrypt.h defines these as LPCSTR constants for CertGetNameString +#ifdef X509_NAME +#undef X509_NAME +#endif +#ifdef X509_EXTENSIONS +#undef X509_EXTENSIONS +#endif +#ifdef X509_CERT_PAIR +#undef X509_CERT_PAIR +#endif +#ifdef X509_NAME_VALUE +#undef X509_NAME_VALUE +#endif +#ifdef PKCS7_SIGNER_INFO +#undef PKCS7_SIGNER_INFO +#endif +#endif // This file should be the first included by all BoringSSL headers. diff --git a/Sources/NIOSSL/ByteBufferBIO.swift b/Sources/NIOSSL/ByteBufferBIO.swift index 0f82413d..1704c556 100644 --- a/Sources/NIOSSL/ByteBufferBIO.swift +++ b/Sources/NIOSSL/ByteBufferBIO.swift @@ -23,6 +23,8 @@ import Musl import Glibc #elseif canImport(Bionic) import Bionic +#elseif os(Windows) +import WinSDK #else #error("unsupported os") #endif diff --git a/Sources/NIOSSL/IdentityVerification.swift b/Sources/NIOSSL/IdentityVerification.swift index 8f30a045..1413a38d 100644 --- a/Sources/NIOSSL/IdentityVerification.swift +++ b/Sources/NIOSSL/IdentityVerification.swift @@ -23,6 +23,8 @@ import Musl import Glibc #elseif canImport(Android) import Android +#elseif os(Windows) +import WinSDK #else #error("unsupported os") #endif diff --git a/Sources/NIOSSL/NIOSSLClientHandler.swift b/Sources/NIOSSL/NIOSSLClientHandler.swift index f4d0a363..1098ad85 100644 --- a/Sources/NIOSSL/NIOSSLClientHandler.swift +++ b/Sources/NIOSSL/NIOSSLClientHandler.swift @@ -22,6 +22,8 @@ import Musl import Glibc #elseif canImport(Android) import Android +#elseif os(Windows) +import WinSDK #else #error("unsupported os") #endif diff --git a/Sources/NIOSSL/PosixPort.swift b/Sources/NIOSSL/PosixPort.swift index d8d7c2f0..c71e9e66 100644 --- a/Sources/NIOSSL/PosixPort.swift +++ b/Sources/NIOSSL/PosixPort.swift @@ -30,6 +30,9 @@ import Musl import Glibc #elseif canImport(Android) import Android +#elseif os(Windows) +import ucrt +import WinSDK #else #error("unsupported os") #endif @@ -41,12 +44,14 @@ internal typealias FILEPointer = UnsafeMutablePointer #endif private let sysFopen = fopen -private let sysMlock = mlock -private let sysMunlock = munlock private let sysFclose = fclose private let sysStat = { @Sendable in stat($0, $1) } +#if !os(Windows) +private let sysMlock = mlock +private let sysMunlock = munlock private let sysLstat = lstat private let sysReadlink = readlink +#endif // MARK:- Copied code from SwiftNIO private func isUnacceptableErrno(_ code: CInt) -> Bool { @@ -65,7 +70,11 @@ internal func wrapSyscall(where function: String = #functi while true { let res = try body() if res == -1 { + #if os(Windows) + let err = Int32(bitPattern: GetLastError()) + #else let err = errno + #endif if err == EINTR { continue } @@ -84,7 +93,11 @@ internal func wrapErrorIsNullReturnCall( ) throws -> T { while true { guard let res = try body() else { + #if os(Windows) + let err = Int32(bitPattern: GetLastError()) + #else let err = errno + #endif if err == EINTR { continue } @@ -113,6 +126,7 @@ internal enum Posix { } } + #if !os(Windows) @inline(never) internal static func readlink( path: UnsafePointer, @@ -123,6 +137,7 @@ internal enum Posix { sysReadlink(path, buf, bufSize) } } + #endif @inline(never) @discardableResult @@ -132,6 +147,7 @@ internal enum Posix { } } + #if !os(Windows) @inline(never) @discardableResult internal static func lstat(path: UnsafePointer, buf: UnsafeMutablePointer) throws -> Int32 { @@ -155,4 +171,5 @@ internal enum Posix { sysMunlock(addr, len) } } + #endif } diff --git a/Sources/NIOSSL/SSLCallbacks.swift b/Sources/NIOSSL/SSLCallbacks.swift index 5dc10a81..05ada703 100644 --- a/Sources/NIOSSL/SSLCallbacks.swift +++ b/Sources/NIOSSL/SSLCallbacks.swift @@ -23,6 +23,8 @@ import Musl import Glibc #elseif canImport(Bionic) import Bionic +#elseif os(Windows) +import WinSDK #else #error("unsupported os") #endif diff --git a/Sources/NIOSSL/SSLCertificate.swift b/Sources/NIOSSL/SSLCertificate.swift index e64c6f32..f53d336f 100644 --- a/Sources/NIOSSL/SSLCertificate.swift +++ b/Sources/NIOSSL/SSLCertificate.swift @@ -24,6 +24,8 @@ import Musl import Glibc #elseif canImport(Bionic) import Bionic +#elseif os(Windows) +import WinSDK #else #error("unsupported os") #endif @@ -427,7 +429,7 @@ extension NIOSSLCertificate { var dataPtr: UnsafeMutablePointer? = nil let length = CNIOBoringSSL_BIO_get_mem_data(bio, &dataPtr) - guard let bytes = dataPtr.map({ UnsafeRawBufferPointer(start: $0, count: length) }) else { + guard let bytes = dataPtr.map({ UnsafeRawBufferPointer(start: $0, count: .init(length)) }) else { fatalError("Failed to map bytes from a certificate") } diff --git a/Sources/NIOSSL/SSLContext.swift b/Sources/NIOSSL/SSLContext.swift index 8aa929c8..e3adbb26 100644 --- a/Sources/NIOSSL/SSLContext.swift +++ b/Sources/NIOSSL/SSLContext.swift @@ -24,6 +24,9 @@ import Musl import Glibc #elseif canImport(Android) import Android +#elseif os(Windows) +import ucrt +import WinSDK #else #error("unsupported os") #endif @@ -45,8 +48,8 @@ internal enum FileSystemObject { return nil } - #if os(Android) && arch(arm) - return (statObj.st_mode & UInt32(S_IFDIR)) != 0 ? .directory : .file + #if (os(Android) && arch(arm)) || os(Windows) + return (UInt32(statObj.st_mode) & UInt32(S_IFDIR)) != 0 ? .directory : .file #else return (statObj.st_mode & S_IFDIR) != 0 ? .directory : .file #endif @@ -788,6 +791,7 @@ extension NIOSSLContext { // Check if the element is a symlink. If it is not, return false. var buffer = stat() + #if !os(Windows) // Windows has no symlinks let _ = try Posix.lstat(path: path, buf: &buffer) // Check the mode to make sure this is a symlink #if os(Android) && arch(arm) @@ -795,6 +799,7 @@ extension NIOSSLContext { #else if (buffer.st_mode & S_IFMT) != S_IFLNK { return false } #endif + #endif // Return true at this point because the file format is considered to be in rehash format and a symlink. // Rehash format being "%08lx.%d" or HHHHHHHH.D @@ -919,16 +924,35 @@ internal class DirectoryContents: Sequence, IteratorProtocol { // Otherwise an OpaquePointer needs to be used to account for the non-defined type in glibc. #if canImport(Darwin) let dir: UnsafeMutablePointer + #elseif os(Windows) + var fileData = WIN32_FIND_DATA() + var dir: HANDLE? = nil #else let dir: OpaquePointer #endif init(path: String) { self.path = path + #if os(Windows) + self.dir = FindFirstFileA(path, &fileData) + #else self.dir = opendir(path)! + #endif } func next() -> String? { + #if os(Windows) + if dir != INVALID_HANDLE_VALUE { + let name = withUnsafePointer(to: &fileData.cFileName) { ptr in + // Pointers to homogeneous tuples in Swift are always bound to both the tuple type and the element type, + // so the assumption below is safe. + let elementPointer = UnsafeRawPointer(ptr).assumingMemoryBound(to: CChar.self) + return String(cString: elementPointer) + } + FindNextFileA(dir, &fileData) + return self.path + name + } + #else if let dirent: UnsafeMutablePointer = readdir(self.dir) { let name = withUnsafePointer(to: &dirent.pointee.d_name) { (ptr) -> String in // Pointers to homogeneous tuples in Swift are always bound to both the tuple type and the element type, @@ -938,11 +962,16 @@ internal class DirectoryContents: Sequence, IteratorProtocol { } return self.path + name } + #endif return nil } deinit { + #if os(Windows) + FindClose(dir) + #else closedir(dir) + #endif } } diff --git a/Sources/NIOSSL/SSLPKCS12Bundle.swift b/Sources/NIOSSL/SSLPKCS12Bundle.swift index f4eece87..28c0e69f 100644 --- a/Sources/NIOSSL/SSLPKCS12Bundle.swift +++ b/Sources/NIOSSL/SSLPKCS12Bundle.swift @@ -245,7 +245,7 @@ extension NIOSSLPKCS12Bundle { var dataPtr: UnsafeMutablePointer? = nil let length = CNIOBoringSSL_BIO_get_mem_data(bio, &dataPtr) - guard let bytes = dataPtr.map({ UnsafeMutableRawBufferPointer(start: $0, count: length) }) else { + guard let bytes = dataPtr.map({ UnsafeMutableRawBufferPointer(start: $0, count: .init(length)) }) else { fatalError("Failed to get bytes from private key") } @@ -269,11 +269,19 @@ extension Collection where Element == UInt8 { bufferPtr.deallocate() } + #if os(Windows) + // TODO: throw an error on failure + VirtualLock(bufferPtr.baseAddress!, UInt64(bufferPtr.count)) + defer { + VirtualUnlock(bufferPtr.baseAddress!, UInt64(bufferPtr.count)) + } + #else try Posix.mlock(addr: bufferPtr.baseAddress!, len: bufferPtr.count) defer { // If munlock fails take out the process. try! Posix.munlock(addr: bufferPtr.baseAddress!, len: bufferPtr.count) } + #endif let (_, nextIndex) = bufferPtr.initialize(from: self) assert(nextIndex == (bufferPtr.endIndex - 1)) diff --git a/Sources/NIOSSL/SSLPrivateKey.swift b/Sources/NIOSSL/SSLPrivateKey.swift index a959943e..45ef77dd 100644 --- a/Sources/NIOSSL/SSLPrivateKey.swift +++ b/Sources/NIOSSL/SSLPrivateKey.swift @@ -382,7 +382,7 @@ extension NIOSSLPrivateKey { var dataPtr: UnsafeMutablePointer? = nil let length = CNIOBoringSSL_BIO_get_mem_data(bio, &dataPtr) - guard let bytes = dataPtr.map({ UnsafeRawBufferPointer(start: $0, count: length) }) else { + guard let bytes = dataPtr.map({ UnsafeRawBufferPointer(start: $0, count: .init(length)) }) else { fatalError("Failed to map bytes from a private key") } diff --git a/Sources/NIOSSL/SSLPublicKey.swift b/Sources/NIOSSL/SSLPublicKey.swift index 86f749ce..38cdca03 100644 --- a/Sources/NIOSSL/SSLPublicKey.swift +++ b/Sources/NIOSSL/SSLPublicKey.swift @@ -76,7 +76,7 @@ extension NIOSSLPublicKey { var dataPtr: UnsafeMutablePointer? = nil let length = CNIOBoringSSL_BIO_get_mem_data(bio, &dataPtr) - guard let bytes = dataPtr.map({ UnsafeMutableRawBufferPointer(start: $0, count: length) }) else { + guard let bytes = dataPtr.map({ UnsafeMutableRawBufferPointer(start: $0, count: .init(length)) }) else { fatalError("Failed to map bytes from a public key") } diff --git a/Sources/NIOSSL/SubjectAlternativeName.swift b/Sources/NIOSSL/SubjectAlternativeName.swift index 425b5bb1..a31bb771 100644 --- a/Sources/NIOSSL/SubjectAlternativeName.swift +++ b/Sources/NIOSSL/SubjectAlternativeName.swift @@ -24,6 +24,8 @@ import Musl import Glibc #elseif canImport(Android) import Android +#elseif os(Windows) +import ucrt #else #error("unsupported os") #endif @@ -208,7 +210,12 @@ extension _SubjectAlternativeName.IPAddress: CustomStringConvertible { var address = address var dest: [CChar] = Array(repeating: 0, count: Self.ipv4AddressLength) dest.withUnsafeMutableBufferPointer { pointer in - let result = inet_ntop(AF_INET, &address, pointer.baseAddress!, socklen_t(pointer.count)) + #if os(Windows) + let size = pointer.count + #else + let size = socklen_t(pointer.count) + #endif + let result = inet_ntop(AF_INET, &address, pointer.baseAddress!, size) precondition( result != nil, "The IP address was invalid. This should never happen as we're within the IP address struct." @@ -221,7 +228,12 @@ extension _SubjectAlternativeName.IPAddress: CustomStringConvertible { var address = address var dest: [CChar] = Array(repeating: 0, count: Self.ipv6AddressLength) dest.withUnsafeMutableBufferPointer { pointer in - let result = inet_ntop(AF_INET6, &address, pointer.baseAddress!, socklen_t(pointer.count)) + #if os(Windows) + let size = pointer.count + #else + let size = socklen_t(pointer.count) + #endif + let result = inet_ntop(AF_INET6, &address, pointer.baseAddress!, size) precondition( result != nil, "The IP address was invalid. This should never happen as we're within the IP address struct." diff --git a/Sources/NIOSSLHTTP1Client/main.swift b/Sources/NIOSSLHTTP1Client/main.swift index df3c9dd6..a9030701 100644 --- a/Sources/NIOSSLHTTP1Client/main.swift +++ b/Sources/NIOSSLHTTP1Client/main.swift @@ -106,7 +106,9 @@ tlsConfiguration.renegotiationSupport = .once let sslContext = try! NIOSSLContext(configuration: tlsConfiguration) let bootstrap = ClientBootstrap(group: eventLoopGroup) + #if !os(Windows) .channelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1) + #endif .channelInitializer { channel in channel.eventLoop.makeCompletedFuture { let openSslHandler = try NIOSSLClientHandler(context: sslContext, serverHostname: url.host) diff --git a/Sources/NIOTLSServer/main.swift b/Sources/NIOTLSServer/main.swift index da1ca6de..e10f7126 100644 --- a/Sources/NIOTLSServer/main.swift +++ b/Sources/NIOTLSServer/main.swift @@ -43,7 +43,9 @@ let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) let bootstrap = ServerBootstrap(group: group) // Specify backlog and enable SO_REUSEADDR for the server itself .serverChannelOption(ChannelOptions.backlog, value: 256) + #if !os(Windows) .serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1) + #endif // Set the handlers that are applied to the accepted channels. .childChannelInitializer { channel in @@ -53,8 +55,10 @@ let bootstrap = ServerBootstrap(group: group) } // Enable TCP_NODELAY and SO_REUSEADDR for the accepted Channels + #if !os(Windows) .childChannelOption(ChannelOptions.socket(IPPROTO_TCP, TCP_NODELAY), value: 1) .childChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1) + #endif defer { try! group.syncShutdownGracefully()