summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStevenWdV <stevenwdv@gmail.com>2021-12-14 15:56:10 +0100
committerStevenWdV <stevenwdv@gmail.com>2021-12-14 15:56:10 +0100
commitae826fde04191d26af68b898cf4b2f537d24a8ec (patch)
treec41943907e6c880a4254106942fc2c399c462016
parentd9953dcc09ce61e249e40857bbf5cb98e0bb1fbf (diff)
Add Swift wrapper, support more platforms in Makefile
-rw-r--r--.github/workflows/test.yml3
-rw-r--r--exports/.gitignore1
-rw-r--r--exports/Makefile24
-rw-r--r--wrappers/swift/.gitignore7
-rw-r--r--wrappers/swift/CEduVpnCommon/Package.swift13
-rw-r--r--wrappers/swift/CEduVpnCommon/Sources/CEduVpnCommon/module.modulemap5
-rw-r--r--wrappers/swift/Makefile31
-rw-r--r--wrappers/swift/Package.swift24
-rw-r--r--wrappers/swift/README.md27
-rw-r--r--wrappers/swift/Sources/EduVpnCommon/EduVpnCommon.swift77
-rw-r--r--wrappers/swift/Tests/EduVpnCommonTests/EduVpnCommonTests.swift60
-rw-r--r--wrappers/swift/fix-path.cmd6
12 files changed, 267 insertions, 11 deletions
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index bdfd962..df83b6f 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -30,4 +30,7 @@ jobs:
with:
distribution: temurin
java-version: 11
+ - uses: fwal/setup-swift@v1
+ with:
+ swift-version: 5
- run: make test-wrappers
diff --git a/exports/.gitignore b/exports/.gitignore
index 63ea916..f916bcd 100644
--- a/exports/.gitignore
+++ b/exports/.gitignore
@@ -1 +1,2 @@
/*/
+*.h
diff --git a/exports/Makefile b/exports/Makefile
index 087d3a2..9664144 100644
--- a/exports/Makefile
+++ b/exports/Makefile
@@ -1,23 +1,25 @@
.PHONY: build copy-to clean
-lib_prefix_linux = lib
-lib_prefix_windows =
-lib_prefix_darwin = lib
+GOOS != go env GOHOSTOS
+GOARCH != go env GOHOSTARCH
-lib_suffix_linux = .so
-lib_suffix_windows = .dll
-lib_suffix_darwin = .dylib
-
-GOOS ?= $(shell go env GOHOSTOS)
-GOARCH ?= $(shell go env GOHOSTARCH)
-LIB_PREFIX = $(lib_prefix_$(GOOS))
-LIB_SUFFIX = $(lib_suffix_$(GOOS))
+ifeq (windows,$(GOOS))
+LIB_PREFIX =
+LIB_SUFFIX = .dll
+else ifeq (darwin,$(GOOS))
+LIB_PREFIX = lib
+LIB_SUFFIX = .dylib
+else
+LIB_PREFIX = lib
+LIB_SUFFIX = .so
+endif
# Creates targets like 'linux/amd64/eduvpn_verify.so'
build: $(GOOS)/$(GOARCH)/$(LIB_PREFIX)eduvpn_verify$(LIB_SUFFIX)
$(GOOS)/$(GOARCH)/$(LIB_PREFIX)eduvpn_verify$(LIB_SUFFIX): exports.go ../verify.go
CGO_ENABLED=1 GOOS=$(GOOS) GOARCH=$(GOARCH) go build -o $@ -buildmode=c-shared $<
+ cp $(GOOS)/$(GOARCH)/$(LIB_PREFIX)eduvpn_verify.h ./eduvpn_verify.h
copy-to: $(GOOS)/$(GOARCH)/$(LIB_PREFIX)eduvpn_verify$(LIB_SUFFIX)
install $< -Dt "$(COPY_TARGET)"
diff --git a/wrappers/swift/.gitignore b/wrappers/swift/.gitignore
new file mode 100644
index 0000000..6481577
--- /dev/null
+++ b/wrappers/swift/.gitignore
@@ -0,0 +1,7 @@
+.DS_Store
+.build/
+Packages/
+*.xcodeproj
+xcuserdata/
+DerivedData/
+*.xcworkspacedata
diff --git a/wrappers/swift/CEduVpnCommon/Package.swift b/wrappers/swift/CEduVpnCommon/Package.swift
new file mode 100644
index 0000000..884f042
--- /dev/null
+++ b/wrappers/swift/CEduVpnCommon/Package.swift
@@ -0,0 +1,13 @@
+// swift-tools-version:5.1
+
+import PackageDescription
+
+let package = Package(
+ name: "CEduVpnCommon",
+ products: [
+ .library(name: "CEduVpnCommon", targets: ["CEduVpnCommon"]),
+ ],
+ targets: [
+ .systemLibrary(name: "CEduVpnCommon"),
+ ]
+)
diff --git a/wrappers/swift/CEduVpnCommon/Sources/CEduVpnCommon/module.modulemap b/wrappers/swift/CEduVpnCommon/Sources/CEduVpnCommon/module.modulemap
new file mode 100644
index 0000000..f0f47a7
--- /dev/null
+++ b/wrappers/swift/CEduVpnCommon/Sources/CEduVpnCommon/module.modulemap
@@ -0,0 +1,5 @@
+module CEduVpnCommon {
+ header "../../../../../exports/eduvpn_verify.h"
+ link "eduvpn_verify"
+ export *
+}
diff --git a/wrappers/swift/Makefile b/wrappers/swift/Makefile
new file mode 100644
index 0000000..cf117f3
--- /dev/null
+++ b/wrappers/swift/Makefile
@@ -0,0 +1,31 @@
+.PHONY: build test clean
+
+ifneq (clean,$(MAKECMDGOALS))
+GOOS != go env GOHOSTOS
+GOARCH != go env GOHOSTARCH
+
+ifeq (Windows_NT,$(OS))
+export PATH := $(PATH):$(abspath ../../exports/$(GOOS)/$(GOARCH))
+else
+export LD_LIBRARY_PATH := $(LD_LIBRARY_PATH):$(abspath ../../exports/$(GOOS)/$(GOARCH))
+endif
+endif
+
+build:
+ $(MAKE) -C ../../exports
+ifeq (Windows_NT,$(OS))
+ "$(COMSPEC)" /c "\".\\fix-path.cmd & swift build --configuration release -Xlinker -L^\"../../exports/$(GOOS)/$(GOARCH)^\"\""
+else
+ swift build --configuration release -Xlinker -L"../../exports/$(GOOS)/$(GOARCH)"
+endif
+
+test:
+ $(MAKE) -C ../../exports
+ifeq (Windows_NT,$(OS))
+ "$(COMSPEC)" /c "\".\\fix-path.cmd & swift test --parallel -Xlinker -L^\"../../exports/$(GOOS)/$(GOARCH)^\"\""
+else
+ swift test --parallel -Xlinker -L"../../exports/$(GOOS)/$(GOARCH)"
+endif
+
+clean:
+ rm -rf .build/
diff --git a/wrappers/swift/Package.swift b/wrappers/swift/Package.swift
new file mode 100644
index 0000000..bb6f8e9
--- /dev/null
+++ b/wrappers/swift/Package.swift
@@ -0,0 +1,24 @@
+// swift-tools-version:5.1
+//TODO ^ find out minimal version
+
+import PackageDescription
+
+let package = Package(
+ name: "EduVpnCommon",
+ products: [
+ .library(
+ name: "EduVpnCommon",
+ targets: ["EduVpnCommon"]),
+ ],
+ dependencies: [
+ .package(path: "CEduVpnCommon"),
+ ],
+ targets: [
+ .target(
+ name: "EduVpnCommon",
+ dependencies: ["CEduVpnCommon"]),
+ .testTarget(
+ name: "EduVpnCommonTests",
+ dependencies: ["EduVpnCommon"]),
+ ]
+)
diff --git a/wrappers/swift/README.md b/wrappers/swift/README.md
new file mode 100644
index 0000000..8259592
--- /dev/null
+++ b/wrappers/swift/README.md
@@ -0,0 +1,27 @@
+# Swift wrapper
+
+## Requirements
+
+You will need to install the [Swift SDK](https://www.swift.org/getting-started), which includes the `swift` tool.
+
+## Build & test
+
+Build `EduVpnCommon` using shared Go library for current platform:
+
+```shell
+make
+```
+
+Build `EduVpnCommon` using shared Go library for specified platform, e.g.:
+
+```shell
+make GOOS=linux GOARCH=amd64
+```
+
+On Windows, you will also need to generate a .lib for the .dll.
+
+Test:
+
+```shell
+make test
+```
diff --git a/wrappers/swift/Sources/EduVpnCommon/EduVpnCommon.swift b/wrappers/swift/Sources/EduVpnCommon/EduVpnCommon.swift
new file mode 100644
index 0000000..340e94a
--- /dev/null
+++ b/wrappers/swift/Sources/EduVpnCommon/EduVpnCommon.swift
@@ -0,0 +1,77 @@
+import Foundation
+import CEduVpnCommon
+
+private extension Data {
+ /// Execute `body` with `GoSlice` pointing to `self`
+ /// - Important: `GoSlice` pointer must not be written to
+ func withSlice<ResultType>(_ body: (GoSlice) throws -> ResultType) rethrows -> ResultType {
+ // Could also use raw NSData.bytes, but then you have to be careful that the NSData must remain valid during the call
+ // This closure method guarantees this
+ try withUnsafeBytes { (pointer: UnsafeRawBufferPointer) -> ResultType in
+ // Note: UnsafeRawBufferPointer.startIndex will always be 0, see docs
+ // Cast to UnsafeMutableRawPointer, assumes it will not be written to
+ try body(GoSlice(data: UnsafeMutableRawPointer(mutating: pointer.baseAddress),
+ len: GoInt(pointer.count), cap: GoInt(pointer.count)))
+ }
+ }
+}
+
+private extension String {
+ /// Execute `body` with `GoSlice` pointing to UTF-8 bytes copied from `self`
+ func withSlice<ResultType>(_ body: (GoSlice) throws -> ResultType) rethrows -> ResultType {
+ try data(using: .utf8)!.withSlice(body)
+ }
+}
+
+public enum VerifyErr: Error, Equatable {
+ /// Expected file name is not one of the recognized values.
+ case ErrUnknownExpectedFileName
+ /// Signature is invalid (for the expected file type).
+ case ErrInvalidSignature
+ /// Signature was created with an unknown key and has not been verified.
+ case ErrInvalidSignatureUnknownKey
+ /// Signature has a timestamp lower than the specified minimum signing time.
+ case ErrTooOld
+ /// Other unknown error
+ case Unknown(code: GoInt)
+
+ static func fromCode(_ code: GoInt) -> VerifyErr {
+ precondition(code != 0)
+ switch code {
+ case 1: return ErrUnknownExpectedFileName
+ case 2: return ErrInvalidSignature
+ case 3: return ErrInvalidSignatureUnknownKey
+ case 4: return ErrTooOld
+ default: return Unknown(code: code)
+ }
+ }
+}
+
+/// Verifies the signature on the JSON server_list.json/organization_list.json file.
+/// If the function returns, the signature is valid for the given file type.
+///
+/// - Parameters:
+/// - signature: .minisig signature file contents.
+/// - signedJson: Signed .json file contents.
+/// - expectedFileName: The file type to be verified, one of "server_list.json" or "organization_list.json".
+/// - minSignTime: Minimum time for signature. Should be set to at least the time in a previously retrieved file.
+/// - Throws: VerifyErr: If signature verification fails or `expectedFileName` is not one of the allowed values.
+public func Verify(signature: Data, signedJson: Data, expectedFileName: String, minSignTime: Date) throws {
+ let result = signature.withSlice { signatureData in
+ signedJson.withSlice { jsonData in
+ expectedFileName.withSlice { expectedNameData in
+ CEduVpnCommon.Verify(signatureData, jsonData, expectedNameData, GoUint64(minSignTime.timeIntervalSince1970))
+ }
+ }
+ }
+ if result != 0 {
+ throw VerifyErr.fromCode(result)
+ }
+}
+
+/// Use for testing only, see Go documentation.
+internal func InsecureTestingSetExtraKey(keyString: String) {
+ keyString.withSlice { keyData in
+ CEduVpnCommon.InsecureTestingSetExtraKey(keyData);
+ }
+}
diff --git a/wrappers/swift/Tests/EduVpnCommonTests/EduVpnCommonTests.swift b/wrappers/swift/Tests/EduVpnCommonTests/EduVpnCommonTests.swift
new file mode 100644
index 0000000..a508023
--- /dev/null
+++ b/wrappers/swift/Tests/EduVpnCommonTests/EduVpnCommonTests.swift
@@ -0,0 +1,60 @@
+import XCTest
+@testable import EduVpnCommon
+
+final class EduVpnCommonTests: XCTestCase {
+ private static let testDataDir = "../../test_data"
+
+ override class func setUp() {
+ // Swift is confused by CRLF, so on some systems we cannot just take the second-to-last element
+ InsecureTestingSetExtraKey(keyString: try! String(contentsOfFile: "\(testDataDir)/dummy/public.key")
+ .components(separatedBy: .newlines).last(where: { !$0.isEmpty })!)
+ }
+
+ func testValid() throws {
+ try Verify(
+ signature: try! Data(contentsOf: URL(fileURLWithPath: "\(EduVpnCommonTests.testDataDir)/dummy/server_list.json.minisig")),
+ signedJson: try! Data(contentsOf: URL(fileURLWithPath: "\(EduVpnCommonTests.testDataDir)/dummy/server_list.json")),
+ expectedFileName: "server_list.json",
+ minSignTime: Date(timeIntervalSince1970: 0))
+ }
+
+ func testInvalidSignature() throws {
+ XCTAssertThrowsError(
+ try Verify(
+ signature: try! Data(contentsOf: URL(fileURLWithPath: "\(EduVpnCommonTests.testDataDir)/dummy/random.txt")),
+ signedJson: try! Data(contentsOf: URL(fileURLWithPath: "\(EduVpnCommonTests.testDataDir)/dummy/server_list.json")),
+ expectedFileName: "server_list.json",
+ minSignTime: Date(timeIntervalSince1970: 0)),
+ "", {err in XCTAssertEqual(err as? VerifyErr, VerifyErr.ErrInvalidSignature)});
+ }
+
+ func testWrongKey() throws {
+ XCTAssertThrowsError(
+ try Verify(
+ signature: try! Data(contentsOf: URL(fileURLWithPath: "\(EduVpnCommonTests.testDataDir)/dummy/server_list.json.wrong_key.minisig")),
+ signedJson: try! Data(contentsOf: URL(fileURLWithPath: "\(EduVpnCommonTests.testDataDir)/dummy/server_list.json")),
+ expectedFileName: "server_list.json",
+ minSignTime: Date(timeIntervalSince1970: 0)),
+ "", {err in XCTAssertEqual(err as? VerifyErr, VerifyErr.ErrInvalidSignatureUnknownKey)});
+ }
+
+ func testOldSignature() throws {
+ XCTAssertThrowsError(
+ try Verify(
+ signature: try! Data(contentsOf: URL(fileURLWithPath: "\(EduVpnCommonTests.testDataDir)/dummy/server_list.json.minisig")),
+ signedJson: try! Data(contentsOf: URL(fileURLWithPath: "\(EduVpnCommonTests.testDataDir)/dummy/server_list.json")),
+ expectedFileName: "server_list.json",
+ minSignTime: Date(timeIntervalSince1970: TimeInterval(1 << 31))),
+ "", {err in XCTAssertEqual(err as? VerifyErr, VerifyErr.ErrTooOld)});
+ }
+
+ func testUnknownExpectedFile() throws {
+ XCTAssertThrowsError(
+ try Verify(
+ signature: try! Data(contentsOf: URL(fileURLWithPath: "\(EduVpnCommonTests.testDataDir)/dummy/other_list.json.minisig")),
+ signedJson: try! Data(contentsOf: URL(fileURLWithPath: "\(EduVpnCommonTests.testDataDir)/dummy/other_list.json")),
+ expectedFileName: "other_list.json",
+ minSignTime: Date(timeIntervalSince1970: 0)),
+ "", {err in XCTAssertEqual(err as? VerifyErr, VerifyErr.ErrUnknownExpectedFileName)});
+ }
+}
diff --git a/wrappers/swift/fix-path.cmd b/wrappers/swift/fix-path.cmd
new file mode 100644
index 0000000..d8a1e42
--- /dev/null
+++ b/wrappers/swift/fix-path.cmd
@@ -0,0 +1,6 @@
+@echo off
+:: Rename PATH -> Path
+set _p=%PATH%
+set PATH=
+set Path=%_p%
+set _p=