-
Notifications
You must be signed in to change notification settings - Fork 73
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add OneOfPolicies and AllOfPolicies (#172)
Created a new OneOfPolicyBuilder This is similar to the existing PolicyBuilder The difference is that the new OneOfPolicyBuilder requires only one of the child policies to be valid, not necessarily all of them A new struct, OneOfPolicies implements VerifierPolicy and can be constructed using OneOfPolicyBuilder This can be used within a larger policy e.g. let x = Verifier(rootCertificates: .init([])) { RFC5280Policy(validationTime: .init()) // this must be met OneOfPolicies { SomethingPolicy() // one of these 2 must be met SomethingElsePolicy() // one of these 2 must be met } } The verifyingCriticalExtensions of OneOfPolicies is intersection of the verifyingCriticalExtensions of each of the child policies, because the overall policy will only be guaranteed to verify an extension if every child policy does so, because we don't know which child policies will actually be evaluated (we stop evaluating when any one passes) OneOfPolicies will only fail if all of the child policies fail. In this case, the failure reason will be a concatenation of all the child failure reasons, with the word 'and' in between them This PR also introduces AllOfPolicies This is to allow users to specify a set of policies which must always be set, as a single item within a OneOfPolicies
- Loading branch information
1 parent
4975de4
commit 83640c8
Showing
5 changed files
with
501 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the SwiftCertificates open source project | ||
// | ||
// Copyright (c) 2024 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 SwiftASN1 | ||
|
||
/// Use this to build a policy where all of the sub-policies must be met for the overall policy to be met. | ||
/// This is only useful within a OneOfPolicies block, because at the top-level, it is already required for all policies | ||
/// to be met, so adding this at the top-level is redundant. | ||
/// For example, the following policy requires that RFC5280Policy is always met, and then either policy C is met, or | ||
/// A and B are both met. If A and B are both met, then C does not have to be met. If C is met, then neither A nor B | ||
/// need to be met. | ||
/// ```swift | ||
/// let verifier = Verifier(rootCertificates: CertificateStore()) { | ||
/// RFC5280Policy(validationTime: Date()) | ||
/// OneOfPolicies { | ||
/// AllOfPolicies { | ||
/// PolicyA() | ||
/// PolicyB() | ||
/// } | ||
/// PolicyC() | ||
/// } | ||
/// } | ||
/// ``` | ||
public struct AllOfPolicies<Policy: VerifierPolicy>: VerifierPolicy { | ||
@usableFromInline | ||
var policy: Policy | ||
|
||
@inlinable | ||
public init(@PolicyBuilder policy: () -> Policy) { | ||
self.policy = policy() | ||
} | ||
|
||
@inlinable | ||
public var verifyingCriticalExtensions: [ASN1ObjectIdentifier] { | ||
self.policy.verifyingCriticalExtensions | ||
} | ||
|
||
@inlinable | ||
public mutating func chainMeetsPolicyRequirements(chain: UnverifiedCertificateChain) async -> PolicyEvaluationResult | ||
{ | ||
await self.policy.chainMeetsPolicyRequirements(chain: chain) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the SwiftCertificates open source project | ||
// | ||
// Copyright (c) 2024 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 SwiftASN1 | ||
|
||
/// Provides a result-builder style DSL for constructing a ``VerifierPolicy`` in which one of the specified policies must match. | ||
/// | ||
/// This DSL allows us to construct ``OneOfPolicies`` within a ``PolicyBuilder``. | ||
/// ```swift | ||
/// let verifier = Verifier(rootCertificates: CertificateStore()) { | ||
/// RFC5280Policy(validationTime: Date()) | ||
/// OneOfPolicies { | ||
/// PolicyA() | ||
/// PolicyB() | ||
/// } | ||
/// } | ||
/// ``` | ||
@resultBuilder | ||
public struct OneOfPolicyBuilder {} | ||
|
||
extension OneOfPolicyBuilder { | ||
@inlinable | ||
public static func buildLimitedAvailability<Policy: VerifierPolicy>(_ component: Policy) -> Policy { | ||
component | ||
} | ||
} | ||
|
||
// MARK: empty policy | ||
extension OneOfPolicyBuilder { | ||
@usableFromInline | ||
struct Empty: VerifierPolicy { | ||
@inlinable | ||
var verifyingCriticalExtensions: [SwiftASN1.ASN1ObjectIdentifier] { [] } | ||
|
||
@inlinable | ||
init() {} | ||
|
||
@inlinable | ||
mutating func chainMeetsPolicyRequirements(chain: UnverifiedCertificateChain) async -> PolicyEvaluationResult { | ||
.failsToMeetPolicy(reason: "No policies specified in OneOfPolicies block") | ||
} | ||
} | ||
|
||
@inlinable | ||
public static func buildBlock() -> some VerifierPolicy { | ||
Empty() | ||
} | ||
} | ||
|
||
// MARK: concatenated policies | ||
extension OneOfPolicyBuilder { | ||
@usableFromInline | ||
struct Tuple2<First: VerifierPolicy, Second: VerifierPolicy>: VerifierPolicy { | ||
@usableFromInline | ||
var first: First | ||
|
||
@usableFromInline | ||
var second: Second | ||
|
||
@inlinable | ||
init(first: First, second: Second) { | ||
self.first = first | ||
self.second = second | ||
} | ||
|
||
@inlinable | ||
var verifyingCriticalExtensions: [SwiftASN1.ASN1ObjectIdentifier] { | ||
let firstExtensions = first.verifyingCriticalExtensions | ||
let secondExtensions = second.verifyingCriticalExtensions | ||
return firstExtensions.filter { secondExtensions.contains($0) } | ||
} | ||
|
||
@inlinable | ||
mutating func chainMeetsPolicyRequirements(chain: UnverifiedCertificateChain) async -> PolicyEvaluationResult { | ||
let firstResult = await self.first.chainMeetsPolicyRequirements(chain: chain) | ||
switch firstResult { | ||
case .meetsPolicy: | ||
return .meetsPolicy | ||
case .failsToMeetPolicy(let firstReason): | ||
let secondResult = await self.second.chainMeetsPolicyRequirements(chain: chain) | ||
switch secondResult { | ||
case .meetsPolicy: | ||
return .meetsPolicy | ||
case .failsToMeetPolicy(let secondReason): | ||
return .failsToMeetPolicy(reason: "\(firstReason) and \(secondReason)") | ||
} | ||
} | ||
} | ||
} | ||
|
||
@inlinable | ||
public static func buildPartialBlock<Policy: VerifierPolicy>(first: Policy) -> Policy { | ||
first | ||
} | ||
|
||
@inlinable | ||
public static func buildPartialBlock( | ||
accumulated: some VerifierPolicy, | ||
next: some VerifierPolicy | ||
) -> some VerifierPolicy { | ||
Tuple2(first: accumulated, second: next) | ||
} | ||
} | ||
|
||
// MARK: if | ||
extension OneOfPolicyBuilder { | ||
@usableFromInline | ||
struct WrappedOptional<Wrapped>: VerifierPolicy where Wrapped: VerifierPolicy { | ||
@usableFromInline | ||
var wrapped: Wrapped? | ||
|
||
@inlinable | ||
init(_ wrapped: Wrapped?) { | ||
self.wrapped = wrapped | ||
} | ||
|
||
@inlinable | ||
var verifyingCriticalExtensions: [SwiftASN1.ASN1ObjectIdentifier] { | ||
self.wrapped?.verifyingCriticalExtensions ?? [] | ||
} | ||
|
||
@inlinable | ||
mutating func chainMeetsPolicyRequirements(chain: UnverifiedCertificateChain) async -> PolicyEvaluationResult { | ||
await self.wrapped?.chainMeetsPolicyRequirements(chain: chain) | ||
?? .failsToMeetPolicy(reason: "\(Wrapped.self) in OneOfPolicy is disabled") | ||
} | ||
} | ||
|
||
@inlinable | ||
public static func buildOptional(_ component: (some VerifierPolicy)?) -> some VerifierPolicy { | ||
WrappedOptional(component) | ||
} | ||
} | ||
|
||
// MARK: if/else and switch | ||
extension OneOfPolicyBuilder { | ||
@inlinable | ||
public static func buildEither<First: VerifierPolicy, Second: VerifierPolicy>( | ||
first component: First | ||
) -> PolicyBuilder._Either<First, Second> { | ||
PolicyBuilder._Either<First, Second>(storage: .first(component)) | ||
} | ||
|
||
@inlinable | ||
public static func buildEither<First: VerifierPolicy, Second: VerifierPolicy>( | ||
second component: Second | ||
) -> PolicyBuilder._Either<First, Second> { | ||
PolicyBuilder._Either<First, Second>(storage: .second(component)) | ||
} | ||
} | ||
|
||
/// Use this to build a policy where any one of the sub-policies must be met for the overall policy to be met. | ||
/// For example, the following policy requires that RFC5280Policy is always met, and either PolicyA or PolicyB is met. | ||
/// It does not require that both PolicyA and PolicyB are met. | ||
/// ```swift | ||
/// let verifier = Verifier(rootCertificates: CertificateStore()) { | ||
/// RFC5280Policy(validationTime: Date()) | ||
/// OneOfPolicies { | ||
/// PolicyA() | ||
/// PolicyB() | ||
/// } | ||
/// } | ||
/// ``` | ||
public struct OneOfPolicies<Policy: VerifierPolicy>: VerifierPolicy { | ||
@usableFromInline | ||
var policy: Policy | ||
|
||
@inlinable | ||
public init(@OneOfPolicyBuilder policy: () -> Policy) { | ||
self.policy = policy() | ||
} | ||
|
||
@inlinable | ||
public var verifyingCriticalExtensions: [ASN1ObjectIdentifier] { | ||
policy.verifyingCriticalExtensions | ||
} | ||
|
||
@inlinable | ||
public mutating func chainMeetsPolicyRequirements(chain: UnverifiedCertificateChain) async -> PolicyEvaluationResult | ||
{ | ||
await self.policy.chainMeetsPolicyRequirements(chain: chain) | ||
} | ||
} |
Oops, something went wrong.