diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index aadcebc..abda1e1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,8 +12,8 @@ Each pull request will be reviewed by a code owner before merging. * Pull requests should contain small, incremental changes. * Focus on one task. If a pull request contains several unrelated commits, we will ask for the pull request to be split up. -* Please squash work-in-progress commits. Each commit should stand on its own (including the addition of tests if possible). This allows us to bisect issues more effectively. -* After addressing review feedback, please rebase your commit so that we create a clean history in the `main` branch. +* Squash work-in-progress commits. Each commit should stand on its own (including the addition of tests if possible). This allows us to bisect issues more effectively. +* After addressing review feedback, rebase your commit so that we create a clean history in the `main` branch. ## Tests diff --git a/README.md b/README.md index 7e81fc9..ca4ccb2 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,18 @@ # Swift TLS -SwiftTLS provides a Swift-native minimal implementation of the TLS 1.3 handshake, specifically aimed at providing support for the QUIC transport protocol. This package is specifically intended to support the implementation of QUIC in [SwiftNetwork](https://github.com/apple/swift-network-evolution). +SwiftTLS provides a minimal, Swift-native implementation of the TLS 1.3 handshake to support the QUIC transport protocol. This package is intended to support the implementation of QUIC in [SwiftNetwork](https://github.com/apple/swift-network-evolution). SwiftTLS supports a minimal set of features and intentionally does not allow negotiation of older versions of TLS. > [!NOTE] > At this time, all types exposed in this package are marked as SPI and subject to change at any time. -## Building and Testing +## Building and testing > [!NOTE] > Building this package requires the Swift 6.3 toolchain or later. You can download toolchains from [the Swift website](https://swift.org/install). -To build via the command line (for all platforms), run `swift build` at the root of package. +To build via the command line (for all platforms), run `swift build` at the root of the package. To run all unit tests, run `swift test`. Unit tests can also be run by filtering a specific class or function: @@ -21,7 +21,7 @@ To run all unit tests, run `swift test`. Unit tests can also be run by filtering % swift test --filter HandshakeStateMachineTests.testStartHandshake ``` -All unit tests are run automatically upon creation or update of a Pull Request. See [CONTRIBUTING](https://github.com/apple/swift-tls/blob/main/CONTRIBUTING.md) for details. +GitHub runs the unit tests automatically when you open or update a pull request. See [CONTRIBUTING](https://github.com/apple/swift-tls/blob/main/CONTRIBUTING.md) for details. ## Contributions diff --git a/Sources/ArraySpanUtilities.swift b/Sources/ArraySpanUtilities.swift index befd30f..c7d8676 100644 --- a/Sources/ArraySpanUtilities.swift +++ b/Sources/ArraySpanUtilities.swift @@ -13,8 +13,9 @@ //===----------------------------------------------------------------------===// extension Array where Element == UInt8 { - /// Copy of the bytes of the given raw span into this array. The span - /// must have exactly count bytes in it. + /// Creates an array by copying the bytes of the given raw span. + /// + /// The span must contain exactly `count` bytes. init(copying bytes: RawSpan) { self.init(unsafeUninitializedCapacity: bytes.byteCount) { outputBuffer, initializedCount in bytes.withUnsafeBytes { inputBuffer in @@ -26,8 +27,9 @@ extension Array where Element == UInt8 { } extension InlineArray where Element == UInt8 { - /// Copy of the bytes of the given raw span into this array. The span - /// must have exactly count bytes in it. + /// Creates an inline array by copying the bytes of the given raw span. + /// + /// The span must contain exactly `count` bytes. init(copying bytes: RawSpan) { precondition(count == bytes.byteCount) self.init { outputSpan in @@ -59,7 +61,7 @@ extension InlineArray where Element: Equatable { } extension OutputRawSpan { - /// Append the contents of the given raw span to this output span. + /// Appends the contents of the given raw span to this output span. mutating func append(contentsOf bytes: RawSpan) { for i in 0..( diff --git a/Sources/Clock.swift b/Sources/Clock.swift index 353a85d..810b080 100644 --- a/Sources/Clock.swift +++ b/Sources/Clock.swift @@ -25,7 +25,7 @@ internal struct EmbeddedDateStub: Equatable, Comparable { } internal var timeIntervalSinceReferenceDate: TimeInterval { - fatalError("EmbeddedDateStub should not be used. timeIntervalSinceReferenceDate( not supported.") } + fatalError("EmbeddedDateStub should not be used. timeIntervalSinceReferenceDate not supported.") } init () { fatalError("EmbeddedDateStub should not be used. init() not supported") @@ -47,10 +47,10 @@ internal struct EmbeddedDateStub: Equatable, Comparable { internal typealias Date = EmbeddedDateStub #endif -/// This protocol exists for testing purposes only, and is declared internal for that reason. +/// A clock abstraction used to inject deterministic time during testing. /// -/// This method defines a way to get hold of time. In release builds, we always use Foundation for this, but -/// when testing we want a way to stub out the results. +/// This protocol exists for testing purposes only, and is declared internal for that reason. In +/// release builds, we always use Foundation; when testing we want a way to stub out the results. internal protocol SwiftTLSClock { func now() -> Date } diff --git a/Sources/Cryptography/KeyScheduler.swift b/Sources/Cryptography/KeyScheduler.swift index 5ece9b0..0f247b4 100644 --- a/Sources/Cryptography/KeyScheduler.swift +++ b/Sources/Cryptography/KeyScheduler.swift @@ -212,7 +212,7 @@ struct ServerSessionKeyManager { } -/// `SessionKeyManager` is responsible for implementing the TLS 1.3 Session Key Schedule. +/// Manages the TLS 1.3 session key schedule and exposes the derived secrets to the rest of the handshake. /// /// The TLS 1.3 key schedule builds out a ratchet of keys and secrets for various purposes. /// This object encapsulates the current state in the key schedule and provides access to the @@ -300,7 +300,7 @@ fileprivate struct SessionKeyManager { } } - /// This exporter master secret. + /// This is the exporter master secret. var exporterMasterSecret: SymmetricKey? { switch self.state { case .idle, .earlySecret, .handshakeSecret: @@ -521,22 +521,22 @@ fileprivate struct SessionKeyManager { extension SessionKeyManager { fileprivate enum State { - /// The dialog has not yet begun, we have no keying material. + /// The dialogue has not yet begun; no keying material is available. case idle - /// The client hello was sent or received. We have the early secret and the derived - /// early secrets. + /// The client hello was sent or received. The early secret and the derived early + /// secrets are available. case earlySecret(SessionKeyManager.State.EarlySecret) - /// The server hello was sent or received. We have the handshake secret and the derived - /// handshake secrets. + /// The server hello was sent or received. The handshake secret and the derived + /// handshake secrets are available. case handshakeSecret(SessionKeyManager.State.HandshakeSecret) - /// The server finished was sent or received. We have the master secret and the derived master - /// secrets, but not the resumption secret. + /// The server finished was sent or received. The master secret and the derived + /// master secrets are available, but not the resumption secret. case masterSecret(SessionKeyManager.State.MasterSecret) - /// The client finished was sent or received. We have all the secrets. + /// The client finished was sent or received. All the secrets are available. case allSecrets(SessionKeyManager.State.AllSecrets) var logDescription: String { @@ -558,8 +558,9 @@ extension SessionKeyManager { extension SessionKeyManager.State { fileprivate struct EarlySecret { - /// This is the current state of the transcript hash. For this state, this contains the - /// transcipt hash through the client hello only. + /// The current state of the transcript hash. + /// + /// For this state, this contains the transcript hash through the client hello only. fileprivate var transcriptHasher: HF /// This is the tail derived secret. @@ -620,9 +621,9 @@ extension SessionKeyManager.State { let calculatedBinderValue = HMAC.authenticationCode(bytes: helloDigest, using: binderKey) if !(calculatedBinderValue == binderValue.readableBytesView) { if calculatedBinderValue.byteCount != binderValue.readableBytes { - logger.error("psk binder value not of expected length. likely epsk hash algorithm mismatch.") + logger.error("PSK binder value not of expected length. Likely epsk hash algorithm mismatch.") } - logger.error("client binder value incorrect. aborting handshake.") + logger.error("Client binder value incorrect. Aborting handshake.") throw TLSError.decryptError } } @@ -661,7 +662,7 @@ extension SessionKeyManager.State { ) -> (earlySecretState: EarlySecret, clientHelloBytes: ByteBuffer) { let zeros = Array(repeating: UInt8(0), count: HF.Digest.byteCount) - // Client uses a the resumption psk or first imported psk as psk input to key schedule if available. Otherwise it uses all zeros. + // Client uses the resumption psk or first imported psk as psk input to key schedule if available. Otherwise it uses all zeros. // If server does not select one of the psks the key schedule will be recomputed with all zeros. var preSharedKey: SymmetricKey var label = PSKSource.resumption.secretLabel @@ -776,8 +777,8 @@ extension SessionKeyManager.State { /// - binderSecret: The binder secret to use to calculate a HMAC /// - clientHello: The client hello message to attach resumption to. This message will be mutated to contain the full /// set of extensions. - /// - returns: The serialized bytes of the ClientHello containing the session ticket. We return this to avoid needing to serialize the - /// ClientHello more than once. + /// - returns: The serialized bytes of the client hello containing the session ticket. We return this to avoid needing to serialize the + /// client hello more than once. private static func tryToResume(session: SessionTicket, binderSecret: SymmetricKey, clientHello: inout ClientHello, currentTime: Date) -> ByteBuffer { // Step 1: compute the PSK binder. To do this we write a fake binder value that is all zeros, and then // serialize the client hello. @@ -788,15 +789,15 @@ extension SessionKeyManager.State { return calculateFinalClientHello(binderSecret: binderSecret, clientHello: &clientHello, obfuscatedTicketAge: obfuscatedTicketAge, identity: identity) } - /// Attempts to use an imported psk. + /// Attempts to use an imported PSK. /// /// - parameters: /// - epsk: The imported or raw ePSK being offered. /// - binderSecret: The binder secret to use to calculate a HMAC /// - clientHello: The client hello message to attach the psk to. This message will be mutated to contain the full /// set of extensions. - /// - returns: The serialized bytes of the ClientHello containing the offered psk. We return this to avoid needing to serialize the - /// ClientHello more than once. + /// - returns: The serialized bytes of the client hello containing the offered psk. We return this to avoid needing to serialize the + /// client hello more than once. private static func useEPSK (epsk: GeneralEPSK, binderSecret: SymmetricKey, clientHello: inout ClientHello) -> ByteBuffer { // Step 1: compute the PSK binder. To do this we write a fake binder value that is all zeros, and then // serialize the client hello. @@ -811,7 +812,7 @@ extension SessionKeyManager.State { fileprivate struct HandshakeSecret { /// This is the current state of the transcript hash. For this state, this contains the - /// transcipt hash through the server hello at construction, and then potentially up to + /// transcript hash through the server hello at construction, and then potentially up to /// but not including the server Finished. fileprivate var transcriptHasher: HF @@ -887,7 +888,7 @@ extension SessionKeyManager.State { fileprivate struct MasterSecret { /// This is the current state of the transcript hash. For this state, this contains the - /// transcipt hash through the server Finished. + /// transcript hash through the server Finished. fileprivate var transcriptHasher: HF /// This is the master secret. @@ -905,7 +906,7 @@ extension SessionKeyManager.State { /// This is the server application traffic secret. fileprivate var serverApplicationTrafficSecret: SymmetricKey - /// This exporter master secret. + /// This is the exporter master secret. fileprivate var exporterMasterSecret: SymmetricKey init(handshakeSecret: HandshakeSecret, serverFinishedBytes: ByteBuffer) { @@ -964,7 +965,7 @@ extension SessionKeyManager.State { /// This is the server application traffic secret. We save this from the previous state. fileprivate var serverApplicationTrafficSecret: SymmetricKey - /// This exporter master secret. We save this from the previous state. + /// This is the exporter master secret. We save this from the previous state. fileprivate var exporterMasterSecret: SymmetricKey /// The resumption master secret. diff --git a/Sources/Cryptography/PrivateKey.swift b/Sources/Cryptography/PrivateKey.swift index 8a0216f..5a46922 100644 --- a/Sources/Cryptography/PrivateKey.swift +++ b/Sources/Cryptography/PrivateKey.swift @@ -21,8 +21,9 @@ import CryptoKit @preconcurrency import Crypto #endif -/// Generic type for all supported PrivateKey types. -/// Loosely based on implementation in Swift Certificates. +/// A generic wrapper over the supported private-key types. +/// +/// Loosely based on the implementation in Swift Certificates. struct PrivateKey { var backing: BackingPrivateKey @@ -114,19 +115,19 @@ extension PrivateKey: CustomStringConvertible { @available(SwiftTLS 0.1.0, *) public typealias SwiftTLSSignatureScheme = UInt16 -/// Callback that takes in bytes to sign and the negotiated TLS signature scheme. -/// Returns the signature or nil if it is unable to sign. +/// A callback that accepts the bytes to sign and the negotiated TLS signature scheme, +/// and returns the signature, or `nil` when signing fails. @_spi(SwiftTLSOptions) @available(SwiftTLS 0.1.0, *) public typealias SwiftTLSRefKeySignCallback = (Data, SwiftTLSSignatureScheme) -> Data? -/// Underlying key types that are supported by SwiftTLSOpaqueReferenceKey +/// The underlying key types supported by `SwiftTLSOpaqueReferenceKey`. enum SwiftTLSOpaqueReferenceKeyType: Sendable { case p256 } -/// Generic Private Key type that allows a caller to use any key from which it can get signatures -/// over data from. +/// A generic private-key type that lets a caller produce signatures over data using any key +/// the caller can access. @_spi(SwiftTLSOptions) @available(SwiftTLS 0.1.0, *) public struct SwiftTLSOpaqueReferenceKey { @@ -141,17 +142,17 @@ public struct SwiftTLSOpaqueReferenceKey { } } - /// Construct a private key wrapping a P256 private key we do not have direct access to - /// - Parameter p256: The P256 public key - /// - Parameter signCallback: `SwiftTLSRefKeySignCallback` callback that can sign data using the private key + /// Construct a private key handle for a P256 key that the caller does not directly hold. + /// - Parameter p256: The P256 public key corresponding to the underlying private key. + /// - Parameter signCallback: `SwiftTLSRefKeySignCallback` callback that signs data using the underlying private key. public init(_ p256: P256.Signing.PublicKey, _ signCallback: @escaping SwiftTLSRefKeySignCallback) { publicKey = PublicKey(p256) sign = signCallback keyType = .p256 } - /// Helper function to verify the SwiftTLSOpaqueReferenceKey has a key type - /// that is compatible with the negotiated TLS signature scheme. + /// Returns whether this `SwiftTLSOpaqueReferenceKey` has a key type + /// compatible with the negotiated TLS signature scheme. func supportsSignatureScheme(_ sigScheme: SignatureScheme) -> Bool { switch self.keyType { case .p256: @@ -180,7 +181,7 @@ extension PrivateKey { return l.dataRepresentation == r.dataRepresentation #endif case (.opaqueReference(let l), .opaqueReference(let r)): - // compare public keys since we don't have acces to the real private + // compare public keys since we don't have access to the real private // keys return l.publicKey == r.publicKey && l.keyType == r.keyType default: diff --git a/Sources/Cryptography/PublicKey.swift b/Sources/Cryptography/PublicKey.swift index 12cb9e9..7c4fb76 100644 --- a/Sources/Cryptography/PublicKey.swift +++ b/Sources/Cryptography/PublicKey.swift @@ -21,8 +21,9 @@ import CryptoKit @preconcurrency import Crypto #endif -/// This type provides an opaque wrapper around the various public key types. -/// Roughly based on Swift Certificate `PublicKey` +/// An opaque wrapper around the supported public-key types. +/// +/// Loosely based on Swift Certificates' `PublicKey`. struct PublicKey { var backing: BackingPublicKey @@ -31,8 +32,8 @@ struct PublicKey { self.backing = backing } - /// Construct a public key wrapping a P256 public key. - /// - Parameter p256: The P256 public key to wrap. + /// Construct a public key wrapping a P-256 public key. + /// - Parameter p256: The P-256 public key to wrap. init(_ p256: P256.Signing.PublicKey) { self.backing = .p256(p256) } diff --git a/Sources/DataUtilities.swift b/Sources/DataUtilities.swift index 01aeb19..3ac28e3 100644 --- a/Sources/DataUtilities.swift +++ b/Sources/DataUtilities.swift @@ -17,7 +17,7 @@ import Foundation #endif extension Data { - /// Copy the raw bytes from the given span into a new Data instance. + /// Creates a `Data` instance by copying the raw bytes from the given span. init(copying bytes: RawSpan) { if bytes.byteCount == 0 { self = Data() @@ -33,15 +33,16 @@ extension Data { } } - /// Append the contents of the given span to this Data instance. + /// Appends the contents of the given span to this `Data` instance. mutating func append(contentsOf bytes: RawSpan) { bytes.withUnsafeBytes { buffer in self.append(contentsOf: buffer) } } - /// Execute the given closure with a RawSpan describing the bytes. - /// Used as a workaround for SwiftSystem's Data missing the "bytes" property. + /// Calls `body` with a `RawSpan` describing the bytes. + /// + /// Used as a workaround for SwiftSystem's `Data` missing the `bytes` property. func withBytes(_ body: (RawSpan) throws(E) -> R) throws(E) -> R { let result: Result = self.withUnsafeBytes { bufferPointer in do throws(E) { diff --git a/Sources/EmbeddedPrint.swift b/Sources/EmbeddedPrint.swift index e8153f3..ddf3ff1 100644 --- a/Sources/EmbeddedPrint.swift +++ b/Sources/EmbeddedPrint.swift @@ -17,14 +17,14 @@ private let logger = Logger(label: "com.apple.security.swifttls.EmbeddedPrint") -/// This file provides functionality for logging interpolated strings without -/// requiring construction of String types (which are not available in Embedded -/// Swift). +// This file provides functionality for logging interpolated strings without +// requiring construction of String types (which are not available in Embedded +// Swift). -/// Types that implement `Loggable` are able to be logged using the +/// A type that conforms to `Loggable` can be logged using the /// `StreamingInterpolation` mechanisms. /// -/// The `write` function should write a human-readable instance of the object to +/// The `write` function writes a human-readable representation of the object to /// the passed-in `Printer` type: /// /// struct MyType: Loggable { @@ -42,9 +42,9 @@ protocol Loggable { /// A type that supports printing individual characters. /// /// Characters can either be streamed directly out to a log (e.g. stdout), or -/// buffered any manually written out by the user. +/// buffered and manually written out by the user. protocol CharacterPrinter { - /// Initialize a new instance. + /// Creates a new printer with no instance state. /// /// Unfortunately, Swift calls this from within the compiler's generated /// code, with a fresh object created each time string interpolation is @@ -62,9 +62,9 @@ protocol CharacterPrinter { /// /// Implementations of `CharacterPrinter` that don't buffer anything (for /// example, if they just forward characters directly to `stdout`) need not - /// do anything here. However, implementations of `CharacterPrinter` that - /// are attempting to buffer the text will need to append the contents of - /// the child `CharacterPrinter` into the parent `CharacterPrinter`. + /// do anything here. However, implementations that buffer text must append + /// the contents of the child `CharacterPrinter` to the parent + /// `CharacterPrinter`. func write(contentsOf: Self) } @@ -87,18 +87,18 @@ extension CharacterPrinter { /// Write the given buffer to the printer. /// - /// This function will print the entire buffer, including NUL bytes and - /// anything following them. To print NUL-terminated strings, see the - /// overload `write(nulTerminated)`. + /// This function will print the entire buffer, including NULL bytes and + /// anything following them. To print NULL-terminated strings, see the + /// overload `write(nullTerminated)`. func write(contentsOf buffer: UnsafeBufferPointer) { self.write(contentsOf: UnsafeRawBufferPointer(buffer)) } /// Write the given buffer to the printer. /// - /// This function will print the entire buffer, including NUL bytes and - /// anything following them. To print NUL-terminated strings, see the - /// overload `write(nulTerminated)`. + /// This function will print the entire buffer, including NULL bytes and + /// anything following them. To print NULL-terminated strings, see the + /// overload `write(nullTerminated)`. @inline(never) // avoid aggressive inlining of non-perf-sensitive code func write(contentsOf buffer: UnsafeRawBufferPointer) { for c in buffer { @@ -106,7 +106,7 @@ extension CharacterPrinter { } } - /// Write a NULL-terminated (C style) string to the printer. + /// Writes a NULL-terminated, C-style string to the printer. @inline(never) // avoid aggressive inlining of non-perf-sensitive code func write(nullTerminated value: UnsafeBufferPointer) { for c in value { diff --git a/Sources/Handshake/HandshakeState.swift b/Sources/Handshake/HandshakeState.swift index 0fabff8..d095034 100644 --- a/Sources/Handshake/HandshakeState.swift +++ b/Sources/Handshake/HandshakeState.swift @@ -37,19 +37,19 @@ enum HandshakeState { /// Ready, but the handshake has not yet started case idle(IdleState) - /// `ClientHello` has been sent to the server + /// The client hello has been sent to the server case clientHello(ClientHelloState) - /// The client has recieved `ServerHello` + /// The client has received the server hello case serverHello(ServerHelloState) - /// The client has recieved the server's encrypted extensions + /// The client has received the server's encrypted extensions case serverEncryptedExtensions(EncryptedExtensionsState) - /// The client has recieved a certificate request from the server + /// The client has received a certificate request from the server case serverCertificateRequest(ServerCertificateRequestState) - /// The client has recieved the server's certificate. + /// The client has received the server's certificate. /// /// When using callbacks for verification, the verification result might not be directly available. /// The state machine transitions to `awaitingVerification` in case it needs to wait, @@ -63,15 +63,16 @@ enum HandshakeState { /// available, transition to `serverCertificateVerify`. case awaitingVerification(AwaitingVerificationState) - /// The client has recieved the server's certificate verification + /// The client has received the server's certificate verification case serverCertificateVerify(ServerCertificateVerifyState) - /// The client has recieved `ServerFinished`, verified the server, sent - /// its second flight including: - /// - `ClientCertificate` (if a certificate request was received. Will be empty if no real cert/rpk was available) - /// - `ClientCertificateVerify` (if a certificate/rpk was available) - /// - `ClientFinished` - /// and the handshake is now complete in the client's view. + /// The client has received `ServerFinished`, verified the server, and sent + /// the client's second flight, which includes: + /// - `ClientCertificate` (if a certificate request was received; empty if no real cert/rpk was available). + /// - `ClientCertificateVerify` (if a certificate or rpk was available). + /// - `ClientFinished`. + /// + /// The handshake is now complete in the client's view. case readyForData(ReadyState) mutating func sendingClientHello( @@ -244,10 +245,10 @@ enum HandshakeState { } } - /// Returns bytes for `ClientFinished` - /// Or bytes for `Client Certificate | [Client Certificate Verify] | Client Finished` - /// if client auth with cert/rpk is requested - mutating func receievedServerFinished(serverFinished: FinishedMessage, serverFinishedBytes: ByteBuffer, serializer: inout TLSMessageSerializer) throws(TLSError) -> PartialHandshakeResult { + /// Returns bytes for `ClientFinished`, or bytes for + /// `Client Certificate | [Client Certificate Verify] | Client Finished` + /// if client authentication with a certificate or RPK is requested. + mutating func receivedServerFinished(serverFinished: FinishedMessage, serverFinishedBytes: ByteBuffer, serializer: inout TLSMessageSerializer) throws(TLSError) -> PartialHandshakeResult { switch self { case .serverCertificateVerify(var state) where state.sendClientCertificateMessage: // process Server finished: @@ -484,7 +485,7 @@ extension HandshakeState { case .preSharedKey(.serverHello(let acceptedIndex)): if let session = clientHelloState.sessionToResume { guard acceptedIndex == 0 else { - logger.error("server hello is trying to resume a session we didnt offer") + logger.error("server hello is trying to resume a session we didn't offer") throw TLSError.negotiationFailed } @@ -1234,8 +1235,9 @@ extension HandshakeState { return (state, clientFinishedBuffer) } - /// Creates a `ReadyState` and returns `ClientFinished` bytes - /// called when client auth with cert/rpk happening + /// Creates a `ReadyState` and returns `ClientFinished` bytes. + /// + /// Called when client authentication with a certificate or RPK is in progress. static func receivingServerFinished(originalState state: ClientCertificateVerifyState, serverFinished: FinishedMessage, serverFinishedBytes: ByteBuffer, diff --git a/Sources/Handshake/HandshakeStateMachine.swift b/Sources/Handshake/HandshakeStateMachine.swift index 28a3774..ae9b3d7 100644 --- a/Sources/Handshake/HandshakeStateMachine.swift +++ b/Sources/Handshake/HandshakeStateMachine.swift @@ -150,11 +150,11 @@ struct HandshakeStateMachine { if certificateTypes.count > 1 || (certificateTypes.count == 1 && certificateTypes.first != .x509) { logger.debug("client sending server_certificate_types extension") - helloExtensions.append(.serverCertificateType(.offer(certificateTypes)),) + helloExtensions.append(.serverCertificateType(.offer(certificateTypes))) } case .rawPublicKey: logger.debug("client sending server_certificate_types extension") - helloExtensions.append(.serverCertificateType(PeerCertificateBundle.verificationCertificateTypes),) + helloExtensions.append(.serverCertificateType(PeerCertificateBundle.verificationCertificateTypes)) case .none: break } @@ -253,9 +253,9 @@ struct HandshakeStateMachine { self.parser.appendBytes(&data) } - /// Call with an input buffer that we've been parsing from to save any - /// bytes remaining from the input buffer. These generally correspond to a - /// partial message. + /// Call with an input buffer that has been parsed from to save any + /// bytes remaining from the input buffer. The saved bytes + /// typically represent a partial message. mutating func saveUnprocessedIncomingBytes(_ data: inout InputBuffer) { let byteCount = data.byteCount logger.debug("saving unprocessed network data (\(byteCount) bytes)") @@ -315,7 +315,7 @@ struct HandshakeStateMachine { continue } case .serverCertificate: - // This will transition to an intermediate state when perform asynchronous verification without and immediate result. + // This will transition to an intermediate state when performing asynchronous verification without an immediate result. switch try self.handleReadServerCertificateVerify(incomingBytes: &incomingBytes) { case .waitingForMoreData: return nil @@ -358,7 +358,7 @@ struct HandshakeStateMachine { /// Obtain the value of the QUIC transport parameters set by the peer, if any. /// - /// Returns `nil` if the peer didn't set any, or if the handshake has not progressed to the point + /// Returns `nil` if the peer did not set any, or if the handshake has not progressed to the point /// of having a value yet. var peerQUICTransportParameters: ByteBuffer? { switch self.state { @@ -494,7 +494,7 @@ struct HandshakeStateMachine { } } - /// Obtain whether we offered an EPSK. + /// Reports whether the client offered an EPSK. /// /// Returns `false` if the handshake has not progressed to the point of having a value yet. var epskOffered: Bool { @@ -549,8 +549,8 @@ struct HandshakeStateMachine { /// Signals whether the peer has accepted early data. /// - /// Will be `nil` if the handshake has not proceeded to the point of receiving the EncryptedExtensions message, - /// `false` if the ServerHello did not contain an `early_data` extension, or `true` if it did. + /// This property is `nil` if the handshake has not proceeded to the point of receiving the EncryptedExtensions message, + /// `false` if the server hello did not contain an `early_data` extension, or `true` if it did. var earlyDataAccepted: Bool? { switch self.state { case .idle, .clientHello, .serverHello: @@ -570,7 +570,7 @@ struct HandshakeStateMachine { } } - /// Determine if the state machine is await an asynchronous local computation. + /// Determine if the state machine is awaiting an asynchronous local computation. public var awaitingAsyncComputation: Bool { switch self.state { case .idle, .clientHello, .serverHello, .serverEncryptedExtensions, .serverCertificateRequest, .serverCertificate, .serverCertificateVerify, .readyForData: @@ -580,7 +580,7 @@ struct HandshakeStateMachine { } } - /// Determine if the handshake is fully complete (sent Finished and validated peer's) + /// Determine if the handshake is fully complete: the local side sent its finished message and validated the peer's finished message. var handshakeComplete: Bool { switch self.state { case .readyForData: @@ -766,9 +766,9 @@ extension HandshakeStateMachine { case .finished(let finished): serverFinished = finished } - logger.info("client got server finished ") + logger.info("client got server finished") - let result = try self.state.receievedServerFinished(serverFinished: serverFinished, serverFinishedBytes: message.messageBytes, serializer: &self.serializer) + let result = try self.state.receivedServerFinished(serverFinished: serverFinished, serverFinishedBytes: message.messageBytes, serializer: &self.serializer) return .complete(result) } diff --git a/Sources/Handshake/HandshakeStateMachineConfiguration.swift b/Sources/Handshake/HandshakeStateMachineConfiguration.swift index 8085f81..1aa64b8 100644 --- a/Sources/Handshake/HandshakeStateMachineConfiguration.swift +++ b/Sources/Handshake/HandshakeStateMachineConfiguration.swift @@ -74,20 +74,20 @@ extension HandshakeStateMachine { /// The value of the ALPN extension to send to the peer. let alpn: [ApplicationLayerProtocol]? - /// Fixed key exchange group to use for the handshake + /// The fixed key exchange group to use for the handshake. let fixedKeyExchangeGroup: NamedGroup? - /// List of cipher suites to use for the handshake + /// The list of cipher suites to use for the handshake. let supportedCipherSuites: [CipherSuite]? - /// An optional client ticket request + /// An optional client ticket request. var ticketRequest: ClientTicketRequest? = nil var authenticationMethod: AuthenticationMethod var verificationMethod: VerificationMethod - /// Public keys that will be accepted for this server name. + /// The public keys this client accepts for the configured server name. private var _validPeerPublicKeys: [P256.Signing.PublicKey]? { get { if case .rawPublicKey(let publicKeys) = self.verificationMethod { diff --git a/Sources/Handshake/PartialHandshakeResult.swift b/Sources/Handshake/PartialHandshakeResult.swift index 0fe6951..15fcf7d 100644 --- a/Sources/Handshake/PartialHandshakeResult.swift +++ b/Sources/Handshake/PartialHandshakeResult.swift @@ -36,10 +36,10 @@ struct PartialHandshakeResult { /// This is provided whenever the encryption level changes. It represents the new encryption level, as well /// as the data associated with that encryption level. /// -/// Each time a new encryption level comes along we have new secrets. +/// Each new encryption level brings new secrets. /// -/// Note that this excludes the "initial" level. That level is implicit: until we have seen one of -/// these, that's the level we're in. +/// Note that this excludes the "initial" level. That level is implicit: until you observe one +/// of these values, the connection remains at the `initial` level. enum EncryptionLevel { case earlyData(secret: SymmetricKey) case handshake(secret: SymmetricKey) diff --git a/Sources/Handshake/PeerCertificateBundle.swift b/Sources/Handshake/PeerCertificateBundle.swift index cf81993..b35ec6a 100644 --- a/Sources/Handshake/PeerCertificateBundle.swift +++ b/Sources/Handshake/PeerCertificateBundle.swift @@ -35,8 +35,8 @@ private let logger = Logger(label: "com.apple.security.swifttls.PeerCertificateB /// `PeerCertificateBundle` represents the bundle of certificates sent by the peer. /// -/// Depending on extensions, the `PeerCertificateBundle` may not contain X.509 certificates -/// per se. For our current use-case, we only support one kind of certificate. +/// Depending on negotiated extensions, the bundle holds either X.509 certificates or raw +/// public keys; a single bundle uses exactly one of these representations. struct PeerCertificateBundle { fileprivate var bundle: Bundle @@ -202,7 +202,7 @@ struct PeerCertificateBundle { } extension PeerCertificateBundle { - /// The kinds of certificate bundle we support. + /// The kinds of certificate bundle this package supports. fileprivate enum Bundle { case rawPublicKey(P256.Signing.PublicKey) case x509([CertificateMessage.CertificateEntry]) @@ -210,18 +210,20 @@ extension PeerCertificateBundle { } extension PeerCertificateBundle { - /// Whether we support unverified X.509. + /// Whether this package supports unverified X.509. /// - /// This can only be true if a) the appropriate compile flag is passed + /// This is `true` only when the `SWIFTTLS_SUPPORT_UNVERIFIED_X509` compile flag is passed. #if SWIFTTLS_SUPPORT_UNVERIFIED_X509 fileprivate static let supportsUnverifiedX509 = true #else fileprivate static let supportsUnverifiedX509 = false #endif - /// The certificate types we are willing to verify. - /// The client will offer these in the client hello under `server_certificate_types`if it is configured to expect a server raw public key - /// The server uses this same list to find whether it supports any of the offered certificate types in `client_certificate_types` + /// The certificate types this peer is willing to verify. + /// + /// The client offers these in the client hello under `server_certificate_types` when it is + /// configured to expect a server raw public key. The server uses this same list to determine + /// whether it supports any of the offered certificate types in `client_certificate_types`. static let verificationCertificateTypes: Extension.CertificateTypeExt = { if Self.supportsUnverifiedX509 { // This is a bit weird. When we really support x509 we will want to @@ -233,9 +235,11 @@ extension PeerCertificateBundle { } }() - /// The certificate types we can provide to our peer - /// The client will offer this in the client hello under `client_certificate_types` if it is configured with a raw public key - /// The server uses this list to find whether it supports any of the offered certificate types in `client_certificate_types` + /// The certificate types this peer can provide to its peer. + /// + /// The client offers this in the client hello under `client_certificate_types` when it is + /// configured with a raw public key. The server uses this list to determine whether it + /// supports any of the offered certificate types in `client_certificate_types`. static let availableCertificateTypes: Extension.CertificateTypeExt = { // we don't offer .x509 even when we support // unverified x509 because that only involves whether diff --git a/Sources/Handshake/ServerHandshakeState.swift b/Sources/Handshake/ServerHandshakeState.swift index 7aaf2b3..6e51e28 100644 --- a/Sources/Handshake/ServerHandshakeState.swift +++ b/Sources/Handshake/ServerHandshakeState.swift @@ -38,10 +38,10 @@ enum ServerHandshakeState { /// Ready, but the handshake has not yet started case idle(IdleState) - /// The server has recieved `ClientHello` and negotiated parameters + /// The server has received the client hello and negotiated parameters case clientHello(ClientHelloState) - /// The `ServerHello` has been sent to the client. + /// The server hello has been sent to the client. case serverHello(ServerHelloState) /// The `ServerEncryptedExtensions` has been sent to the client. @@ -56,7 +56,7 @@ enum ServerHandshakeState { /// The `ServerCertificate` has been sent to the client. case serverCertificate(ServerCertificateState) - /// The server is awaiting the the signature to include in the `ServerCertificateVerify` message. + /// The server is awaiting the signature to include in the `ServerCertificateVerify` message. case awaitingSignature(AwaitingSignatureState) /// The `ServerCertificateVerify` has been sent to the client. @@ -71,7 +71,7 @@ enum ServerHandshakeState { /// The `CertificateVerify` message has been read from the client. case clientCertificateVerify(ClientCertificateVerifyState) - /// The server has recieved `ClientFinished`, and the handshake is now complete + /// The server has received `ClientFinished`, and the handshake is now complete case readyForData(ReadyState) mutating func receivedClientHello(_ clientHello: ClientHello, bytes: ByteBuffer) throws(TLSError) -> PartialHandshakeResult? { @@ -301,9 +301,9 @@ extension ServerHandshakeState { var clientOfferedPSKs: Extension.PreSharedKey.OfferedPSKs? = nil var clientIndicatedEarlyData: Bool = false let serverSupportedGroups: [NamedGroup] - let serverSupportedSignatureAlgs: [SignatureScheme] /* must include at least one elemnt */ + let serverSupportedSignatureAlgs: [SignatureScheme] /* must include at least one element */ let serverSupportedCertificateTypes: [CertificateType] /* must include at least one element */ - let serverSupportedClientCertificateTypes: [CertificateType] /* must include at least on element */ + let serverSupportedClientCertificateTypes: [CertificateType] /* must include at least one element */ let serverSupportedPSKKexModes: [Extension.PreSharedKeyKexModes.Mode] let serverSupportedPSKs: [GeneralEPSK] let useRawEPSKs: Bool @@ -379,7 +379,7 @@ extension ServerHandshakeState { } guard clientRequestPSKKexModes.modes.contains(.pskAndDHE) else { // ignore pre shared keys if client did not offer psk_dhe - logger.debug("server ignoring offererd pre shared keys because it did not offer psk_dhe mode") + logger.debug("server ignoring offered pre shared keys because it did not offer psk_dhe mode") return nil } @@ -416,7 +416,7 @@ extension ServerHandshakeState { bindersArrayLength: bindersArrayLength) } } - // if it didn't match a pre-imported psk, store it to later pass to to the external psk selection callback (if set) + // if it didn't match a pre-imported psk, store it to later pass to the external psk selection callback (if set) offeredEPSKs.append(SwiftOfferedEPSK(external_identity: importedIdentity.externalIdentity, context: importedIdentity.context)) offeredEPSKIndices.append(UInt16(i)) } @@ -430,7 +430,7 @@ extension ServerHandshakeState { bindersArrayLength: bindersArrayLength) } } - // if it didn't match a pre-imported psk, store it to later pass to to the external psk selection callback (if set) + // if it didn't match a pre-imported psk, store it to later pass to the external psk selection callback (if set) offeredEPSKs.append(SwiftOfferedEPSK(external_identity: pskIdentity.identity, context: nil)) offeredEPSKIndices.append(UInt16(i)) } @@ -758,7 +758,7 @@ extension ServerHandshakeState { // // - legacy_version MUST be set to 0x0303 (TLSv1.2) // - supported_versions extension is present - // - supported_versions extension includes 0x03034 (TLSv1.3) + // - supported_versions extension includes 0x0304 (TLSv1.3) // - cipherSuites contains supported values // - legacy_compression_methods must contain one byte set to zero // - key_shares extension present (required for ECDHE or DHE key exchange) @@ -865,7 +865,7 @@ extension ServerHandshakeState { if clientHelloVerifier.clientIndicatedEarlyData && idleState.configuration.enableEarlyData { logger.debug("Client requested early data and server configured to allow early data. Checking conditions...") if let negotiatedPSKResult, negotiatedPSKResult.pskIndex == 0 { - logger.debug("Early data compatibly psk negotiated. Continuing checks...") + logger.debug("A PSK compatible with early data was negotiated. Continuing checks...") if clientHelloVerifier.clientALPN != nil { // client attempted to negotiate ALPN so early data corresponds to app protocol at index 0 if alpnSelectionResult?.1 == 0 { @@ -1174,7 +1174,7 @@ extension ServerHandshakeState { self.authenticationDetails = authenticationDetails } - // Build the CertificateMessag + // Build the CertificateMessage static func buildCertificateMessage(configuration: ServerHandshakeStateMachine.Configuration, keyScheduler: inout ServerSessionKeyManager, certificateList: [CertificateMessage.CertificateEntry]) throws(TLSError) -> ByteBuffer { let serverCertificateMessage = CertificateMessage( certificateRequestContext: ByteBuffer(), diff --git a/Sources/Handshake/ServerHandshakeStateMachine.swift b/Sources/Handshake/ServerHandshakeStateMachine.swift index 6d1358d..c23b642 100644 --- a/Sources/Handshake/ServerHandshakeStateMachine.swift +++ b/Sources/Handshake/ServerHandshakeStateMachine.swift @@ -85,8 +85,8 @@ struct ServerHandshakeStateMachine { self.parser.appendBytes(&data) } - /// Call with an input buffer that we've been parsing from to save any - /// bytes remaining from the input buffer. These generally correspond to a + /// Call with an input buffer that the parser has been reading from to save any + /// bytes remaining from the input buffer. The saved bytes typically represent a /// partial message. mutating func saveUnprocessedIncomingBytes(_ data: inout InputBuffer) { let byteCount = data.byteCount @@ -349,9 +349,9 @@ struct ServerHandshakeStateMachine { } } - /// Signals whether we have accepted early data. + /// Signals whether the server has accepted early data. /// - /// `false` if the ServerHello did not contain an `early_data` extension, or `true` if it did. + /// `false` if the server hello did not contain an `early_data` extension, or `true` if it did. var earlyDataAccepted: Bool? { switch self.state { @@ -388,7 +388,7 @@ struct ServerHandshakeStateMachine { return self.state.description } - /// Determine if the state machine is await an asynchronous local computation. + /// Determine if the state machine is awaiting an asynchronous local computation. public var awaitingAsyncComputation: Bool { switch self.state { case .idle: diff --git a/Sources/Handshake/ServerHandshakeStateMachineConfiguration.swift b/Sources/Handshake/ServerHandshakeStateMachineConfiguration.swift index 8596343..4199e51 100644 --- a/Sources/Handshake/ServerHandshakeStateMachineConfiguration.swift +++ b/Sources/Handshake/ServerHandshakeStateMachineConfiguration.swift @@ -73,19 +73,19 @@ extension ServerHandshakeStateMachine { /// List of cipher suites to use for the handshake let supportedCipherSuites: [CipherSuite]? - /// Whether external PSKs should be treated as imported or raw + /// Whether to treat external PSKs as imported or raw. var useRawEPSKs: Bool = false - /// Whether the server is willing to accept early data + /// Whether the server is willing to accept early data. var enableEarlyData: Bool = false - /// True if used within QUIC, false otherwise + /// `true` when used within QUIC; `false` otherwise. var transportIsQUIC: Bool - /// True if client required to authenticate with RPK/Cert + /// `true` when the client is required to authenticate with RPK or a certificate. var clientAuthRequired: Bool - /// Public keys for trusted clients + /// Public keys for trusted clients. var validPeerPublicKeys: [P256.Signing.PublicKey]? { return _validPeerPublicKeys } diff --git a/Sources/Handshake/SessionTicket.swift b/Sources/Handshake/SessionTicket.swift index f6f9e47..40acad1 100644 --- a/Sources/Handshake/SessionTicket.swift +++ b/Sources/Handshake/SessionTicket.swift @@ -37,7 +37,7 @@ private let logger = Logger(label: "com.apple.security.swifttls.SessionTicket") /// This structure encodes all the information needed to resume a session. This means it includes ticket data, /// ticket age information, and details about the underlying handshake so that resumption can be validated. /// -/// Critically, session tickets are capable of being serialized and deserialized. +/// Critically, you can serialize and deserialize session tickets. struct SessionTicket { var issued: Date @@ -166,9 +166,9 @@ struct SessionTicket { self.certificateBundle = certificateBundle } - /// Whether this SessionTicket can be used to resume with this client hello. + /// Reports whether this `SessionTicket` can resume with the provided client hello. /// - /// Note: This always returns `false` when using certificate callbacks for peer verification as it requires peer public keys to be set on the configuration. + /// Note: This always returns `false` when using certificate callbacks for peer verification, because resumption requires peer public keys to be set on the configuration. func isCompatibleWith(_ clientHello: ClientHello, configuration: HandshakeStateMachine.Configuration, currentTime: Date) -> Bool { // Gotta confirm this session is suitable. Start with the cheap ones: do the algorithms line up? // For now we only resume if this matches the _first_ outcomes, because it avoids needing to deal with @@ -217,7 +217,7 @@ struct SessionTicket { } extension SessionTicket { - /// RFC 8446 says we must not cache longer than this. + /// The maximum cache lifetime allowed by RFC 8446. static fileprivate let maxLifetime = UInt32(604800) } diff --git a/Sources/HandshakeMessageParser.swift b/Sources/HandshakeMessageParser.swift index 006f4f4..1f71be6 100644 --- a/Sources/HandshakeMessageParser.swift +++ b/Sources/HandshakeMessageParser.swift @@ -60,8 +60,8 @@ struct HandshakeMessageParser { } /// Parse a handshake message using the bytes we've buffered already plus the - /// provided `incomingBytes`. On exit, any bytes not consumed by the parsing - /// will be buffered for the next call. + /// provided `incomingBytes`. On exit, any bytes the parser doesn't consume + /// remain buffered for the next call. mutating func parseHandshakeMessage(incomingBytes: inout InputBuffer) throws(TLSError) -> ParseResult? { // If we didn't buffer any bytes, we can parse directly from the input buffer // without copying anything. diff --git a/Sources/InputBuffer.swift b/Sources/InputBuffer.swift index 7f2c78d..14db555 100644 --- a/Sources/InputBuffer.swift +++ b/Sources/InputBuffer.swift @@ -20,7 +20,7 @@ /// it is always clear which code is actively reading the data, preventing /// mistakes where the same data is processed multiple times unnecessarily. struct InputBuffer: ~Escapable, ~Copyable { - /// Reference to the underlying storage that we're reading from, + /// Reference to the underlying storage that we're reading from. private let storage: RawSpan /// The current reading position within the storage. @@ -32,10 +32,10 @@ struct InputBuffer: ~Escapable, ~Copyable { self.position = position } - /// Determine the number of remaining, readable bytes. + /// The number of remaining, readable bytes. var byteCount: Int { storage.byteCount - position } - /// Access all of the remaining, readable bytes + /// All of the remaining, readable bytes. var bytes: RawSpan { @_lifetime(borrow self) get { @@ -46,10 +46,10 @@ struct InputBuffer: ~Escapable, ~Copyable { // MARK: Reading extension InputBuffer { - /// Read the given number of bytes from the input buffer, - /// consuming those bytes and returning them in the resulting - /// input buffer. If there aren't enough bytes in the original - /// buffer, returns nil. + /// Reads the given number of bytes from the input buffer. + /// + /// Consumes the bytes and returns them in the resulting input buffer. Returns `nil` if the + /// original buffer does not contain enough bytes. @_lifetime(copy self) mutating func read(length: Int) -> InputBuffer? { guard self.byteCount >= length else { @@ -130,12 +130,12 @@ extension InputBuffer { } } - /// Yup, double optional! Is this really necessary? + /// Reads a value through `readFunction` while distinguishing three outcomes. /// - /// Yes. The underlying data is necessarily optional: it may be there or it may not. But _also_ we may not - /// have enough data in the buffer to read either the optional discriminator or the main data. In this case, we - /// need another layer of optionality. The "outer" optional is whether there was enough data in the buffer: - /// the "inner" optional is the underlying data type. + /// The underlying data is necessarily optional: it may be there or it may not. But the buffer + /// may also not contain enough data to read either the optional discriminator or the main + /// data. The outer optional indicates whether the buffer held enough data; the inner optional + /// is the underlying data type. mutating func readOptional(_ readFunction: (inout InputBuffer) throws(E) -> DataType?) throws(E) -> DataType?? { guard let discriminator = self.readInteger(as: UInt8.self) else { return nil @@ -247,12 +247,10 @@ extension InputBuffer { // MARK: Copying out data extension InputBuffer { - /// Copies the bytes from the buffer into the given raw - /// output span. This operation will copy - /// min(byteCount, output.freeCapacity) bytes, returning the - /// number of bytes written. + /// Copies the bytes from the buffer into the given raw output span. /// - /// Note that this operation does not consume any bytes. + /// Copies `min(byteCount, output.freeCapacity)` bytes and returns the number of bytes + /// written. This operation does not consume any bytes. @discardableResult func copy(to output: inout OutputRawSpan) -> Int { let bytesToWrite = min(byteCount, output.freeCapacity) diff --git a/Sources/Logger.swift b/Sources/Logger.swift index 5c746e8..f03a5f2 100644 --- a/Sources/Logger.swift +++ b/Sources/Logger.swift @@ -71,7 +71,7 @@ struct SerialPrinter: CharacterPrinter { // already written out to serial. } } -// + // String interpolation objects cast to this type will be streamed // to serial via calls to `putchar`. typealias StreamingSerialMessage = StreamingMessage diff --git a/Sources/SwiftTLSProtocol.swift b/Sources/SwiftTLSProtocol.swift index 33a6d82..dd18ab8 100644 --- a/Sources/SwiftTLSProtocol.swift +++ b/Sources/SwiftTLSProtocol.swift @@ -435,8 +435,10 @@ public class SwiftTLSHandshaker { fileprivate var asyncContinuationHandler: (@Sendable (PendingAsyncResult) -> Void)? - /// Certificate callbacks may yield results asynchronously. In that case they will call this handler with the pending result. - /// It is expected to do the following: + /// Handles asynchronous results yielded by certificate callbacks. + /// + /// Certificate callbacks may yield results asynchronously, in which case they call this handler with the pending result. + /// The handler is expected to do the following: /// - Apply the result to the handshake via `setAsyncResult`. /// - Call `continueHandshake` to drive the TLS handshake forward. /// @@ -447,7 +449,7 @@ public class SwiftTLSHandshaker { fileprivate var asyncResult: PendingAsyncResult? - /// Set the result of an async callback. It will automatically be set to `nil` after the handshake consumes it. + /// Sets the result of an async callback. The result resets to `nil` after the handshake consumes it. /// The pending result will be supplied by the `asyncContinuationHandler`. public func setAsyncResult(_ result: PendingAsyncResult) { asyncResult = result