Skip to content

Commit

Permalink
Add OneOfPolicies and AllOfPolicies (#172)
Browse files Browse the repository at this point in the history
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
hamzahrmalik authored May 1, 2024
1 parent 4975de4 commit 83640c8
Show file tree
Hide file tree
Showing 5 changed files with 501 additions and 0 deletions.
2 changes: 2 additions & 0 deletions Sources/X509/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,10 @@ add_library(X509
"SEC1PrivateKey.swift"
"Signature.swift"
"SignatureAlgorithm.swift"
"Verifier/AllOfPolicies.swift"
"Verifier/AnyPolicy.swift"
"Verifier/CertificateStore.swift"
"Verifier/OneOfPolicies.swift"
"Verifier/PolicyBuilder.swift"
"Verifier/RFC5280/BasicConstraintsPolicy.swift"
"Verifier/RFC5280/DNSNames.swift"
Expand Down
3 changes: 3 additions & 0 deletions Sources/X509/Docs.docc/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ certificate authorities, authenticating peers, and more.
- ``PolicyBuilder``
- ``PolicyFailureReason``
- ``AnyPolicy``
- ``OneOfPolicyBuilder``
- ``OneOfPolicies``
- ``AllOfPolicies``
- ``CertificateStore``
- ``UnverifiedCertificateChain``
- ``VerificationDiagnostic``
Expand Down
54 changes: 54 additions & 0 deletions Sources/X509/Verifier/AllOfPolicies.swift
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)
}
}
194 changes: 194 additions & 0 deletions Sources/X509/Verifier/OneOfPolicies.swift
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)
}
}
Loading

0 comments on commit 83640c8

Please sign in to comment.