Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -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:

Expand All @@ -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

Expand Down
12 changes: 7 additions & 5 deletions Sources/ArraySpanUtilities.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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..<bytes.byteCount {
append(bytes.unsafeLoad(fromByteOffset: i, as: UInt8.self))
Expand Down
12 changes: 6 additions & 6 deletions Sources/ByteBuffer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ extension Data {
}
#endif

/// A type that brings over many of the conveniences of NIO's ByteBuffer, reimplemented on Data.
/// A type that brings over many of the conveniences of SwiftNIO's `ByteBuffer`, reimplemented on `Data`.
///
/// This is not a truly hardened version of NIO's ByteBuffer, and lacks some flexibility, but it's good enough
/// for what we need.
/// This is not a truly hardened version of NIO's ByteBuffer. It lacks some flexibility, but it's
/// good enough for what we need.
struct ByteBuffer {
private var backingData: Data
private(set) var readerIndex: Data.Index
Expand Down Expand Up @@ -63,7 +63,7 @@ struct ByteBuffer {
return self.writerIndex - self.readerIndex
}

/// Access the readable bytes as a RawSpan
/// The readable bytes exposed as a `RawSpan`.
var readableBytesSpan: RawSpan {
let startOffset = self.readerIndex - self.backingData.startIndex
let endOffset = startOffset + self.readableBytes
Expand Down Expand Up @@ -293,8 +293,8 @@ extension ByteBuffer: Hashable {

extension ByteBuffer {
/// Execute the given `body` function with an input buffer that can access
/// the readable part of the byte buffer. Anything consumed from the input
/// buffer will be consumed from this byte buffer as well.
/// the readable part of the byte buffer. Bytes consumed from the input
/// buffer are also consumed from this byte buffer.
///
/// Use this when you have a byte buffer and want to read from it.
mutating func withInputBuffer<R: ~Copyable, E: Error>(
Expand Down
8 changes: 4 additions & 4 deletions Sources/Clock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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
}
Expand Down
49 changes: 25 additions & 24 deletions Sources/Cryptography/KeyScheduler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ struct ServerSessionKeyManager<HF: HashFunction> {
}


/// `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
Expand Down Expand Up @@ -300,7 +300,7 @@ fileprivate struct SessionKeyManager<HF: HashFunction> {
}
}

/// This exporter master secret.
/// This is the exporter master secret.
var exporterMasterSecret: SymmetricKey? {
switch self.state {
case .idle, .earlySecret, .handshakeSecret:
Expand Down Expand Up @@ -521,22 +521,22 @@ fileprivate struct SessionKeyManager<HF: HashFunction> {

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 {
Expand All @@ -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.
Expand Down Expand Up @@ -620,9 +621,9 @@ extension SessionKeyManager.State {
let calculatedBinderValue = HMAC<HF>.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
}
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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

Expand Down Expand Up @@ -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.
Expand All @@ -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) {
Expand Down Expand Up @@ -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.
Expand Down
27 changes: 14 additions & 13 deletions Sources/Cryptography/PrivateKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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 {
Expand All @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
9 changes: 5 additions & 4 deletions Sources/Cryptography/PublicKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
}
Expand Down
Loading
Loading