Skip to content

Commit

Permalink
Support loading trust root CAs on Linux (#136)
Browse files Browse the repository at this point in the history
* Support loading trust roots on Linux

* Fix linux

* run `swift-format`

* Apply review feedback

* Implement `_TinyArray2`

* fix tests

* Update benchmarks for _TinyArray2

* run swift-format

* use two properties instead of TinyArray2

* remove usage of `_TinyArray2` and rename `insert` to `append`

* remove TinyArray2 and rename TinyArray1 back to just TinyArray

* run swift-format

* fix tests

* Fix review comments
  • Loading branch information
dnadoba authored Oct 18, 2023
1 parent 3956075 commit 3c33085
Show file tree
Hide file tree
Showing 15 changed files with 4,001 additions and 21 deletions.
1 change: 1 addition & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ let package = Package(
.copy("OCSP Test Resources/www.apple.com.intermediate.ocsp-response.der"),
.copy("PEMTestRSACertificate.pem"),
.copy("CSR Vectors/"),
.copy("ca-certificates.crt")
]),
.target(
name: "_CertificateInternals",
Expand Down
3 changes: 3 additions & 0 deletions Sources/X509/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ add_library(X509
"Extensions.swift"
"ExtensionsBuilder.swift"
"GeneralName.swift"
"LockedValueBox.swift"
"OCSP/BasicOCSPResponse.swift"
"OCSP/DirectoryString.swift"
"OCSP/OCSPCertID.swift"
Expand All @@ -75,6 +76,7 @@ add_library(X509
"OCSP/OCSPTBSRequest.swift"
"OCSP/OCSPVersion.swift"
"PKCS8PrivateKey.swift"
"PromiseAndFuture.swift"
"RDNAttribute.swift"
"RandomNumberGenerator+bytes.swift"
"RelativeDistinguishedName.swift"
Expand All @@ -93,6 +95,7 @@ add_library(X509
"Verifier/RFC5280/RFC5280Policy.swift"
"Verifier/RFC5280/URIConstraints.swift"
"Verifier/RFC5280/VersionPolicy.swift"
"Verifier/TrustRootLoading.swift"
"Verifier/UnverifiedChain.swift"
"Verifier/VerificationDiagnostic.swift"
"Verifier/Verifier.swift"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ public enum CMS {
// Ok, the signature was signed by the private key associated with this cert. Now we need to validate the certificate.
// This force-unwrap is safe: we know there are certificates because we've located at least one certificate from this set!
var untrustedIntermediates = CertificateStore(signedData.certificates!)
untrustedIntermediates.insert(contentsOf: additionalIntermediateCertificates)
untrustedIntermediates.append(contentsOf: additionalIntermediateCertificates)

var verifier = try Verifier(rootCertificates: trustRoots, policy: policy)
let result = await verifier.validate(
Expand Down
23 changes: 23 additions & 0 deletions Sources/X509/Error.swift
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,25 @@ public struct CertificateError: Error, Hashable, CustomStringConvertible {
)
)
}

/// The system trust store could not be found or failed to load from disk.
/// - Parameter reason: A detailed reason included which locations were tried and which error got thrown.
/// - Returns: A ``CertificateError`` with ``code`` set to ``ErrorCode/failedToLoadSystemTrustStore``.
@inline(never)
public static func failedToLoadSystemTrustStore(
reason: String,
file: String = #fileID,
line: UInt = #line
) -> CertificateError {
return CertificateError(
backing: .init(
code: .failedToLoadSystemTrustStore,
reason: reason,
file: file,
line: line
)
)
}
}

extension CertificateError {
Expand All @@ -247,6 +266,7 @@ extension CertificateError {
case incorrectOIDForAttribute
case invalidCSRAttribute
case duplicateOID
case failedToLoadSystemTrustStore
}

fileprivate var backingCode: BackingCode
Expand Down Expand Up @@ -282,6 +302,9 @@ extension CertificateError {
/// An OID is present twice.
public static let duplicateOID = ErrorCode(.duplicateOID)

/// The system trust store could not be located or failed to load from disk.
public static let failedToLoadSystemTrustStore = ErrorCode(.failedToLoadSystemTrustStore)

public var description: String {
return String(describing: self.backingCode)
}
Expand Down
56 changes: 56 additions & 0 deletions Sources/X509/LockedValueBox.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftCertificates open source project
//
// Copyright (c) 2023 Apple Inc. and the SwiftCertificates project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftCertificates project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import Foundation

final class LockedValueBox<Value> {
private let _lock: NSLock = .init()
private var _value: Value

var unsafeUnlockedValue: Value {
get { _value }
set { _value = newValue }
}

func lock() {
_lock.lock()
}

func unlock() {
_lock.unlock()
}

init(_ value: Value) {
self._value = value
}

func withLockedValue<Result>(
_ body: (inout Value) throws -> Result
) rethrows -> Result {
try _lock.withLock {
try body(&_value)
}
}
}

extension LockedValueBox: @unchecked Sendable where Value: Sendable {}

extension NSLock {
// this API doesn't exist on Linux and therefore we have a copy of it here
func withLock<Result>(_ body: () throws -> Result) rethrows -> Result {
self.lock()
defer { self.unlock() }
return try body()
}
}
125 changes: 125 additions & 0 deletions Sources/X509/PromiseAndFuture.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftCertificates open source project
//
// Copyright (c) 2023 Apple Inc. and the SwiftCertificates project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftCertificates project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

// MARK: - Promise
final class Promise<Value, Failure: Error> {
private enum State {
case unfulfilled(observers: [CheckedContinuation<Result<Value, Failure>, Never>])
case fulfilled(Result<Value, Failure>)
}

private let state = LockedValueBox(State.unfulfilled(observers: []))

init() {}

fileprivate var result: Result<Value, Failure> {
get async {
self.state.lock()

switch self.state.unsafeUnlockedValue {
case .fulfilled(let result):
defer { self.state.unlock() }
return result

case .unfulfilled(var observers):
return await withCheckedContinuation {
(continuation: CheckedContinuation<Result<Value, Failure>, Never>) in
observers.append(continuation)
self.state.unsafeUnlockedValue = .unfulfilled(observers: observers)
self.state.unlock()
}
}
}
}

func fulfil(with result: Result<Value, Failure>) {
self.state.withLockedValue { state in
switch state {
case .fulfilled(let oldResult):
fatalError("tried to fulfil Promise that is already fulfilled to \(oldResult). New result: \(result)")
case .unfulfilled(let observers):
for observer in observers {
observer.resume(returning: result)
}
state = .fulfilled(result)
}
}
}

deinit {
self.state.withLockedValue {
switch $0 {
case .fulfilled:
break
case .unfulfilled:
fatalError("unfulfilled Promise leaked")
}
}
}
}

extension Promise: Sendable where Value: Sendable {}

extension Promise {
func succeed(with value: Value) {
self.fulfil(with: .success(value))
}

func fail(with error: Failure) {
self.fulfil(with: .failure(error))
}
}

// MARK: - Future

struct Future<Value, Failure: Error> {
private let promise: Promise<Value, Failure>

init(_ promise: Promise<Value, Failure>) {
self.promise = promise
}

var result: Result<Value, Failure> {
get async {
await promise.result
}
}
}

extension Future: Sendable where Value: Sendable {}

extension Future {
var value: Value {
get async throws {
try await result.get()
}
}
}

extension Future where Failure == Never {
var value: Value {
get async {
await result.get()
}
}
}

extension Result where Failure == Never {
func get() -> Success {
switch self {
case .success(let success):
return success
}
}
}
Loading

0 comments on commit 3c33085

Please sign in to comment.