Skip to content

Commit

Permalink
Trusted Entitlements: updated signature format
Browse files Browse the repository at this point in the history
  • Loading branch information
NachoSoto committed Jun 30, 2023
1 parent 8f48922 commit 1700575
Show file tree
Hide file tree
Showing 6 changed files with 319 additions and 64 deletions.
26 changes: 26 additions & 0 deletions Sources/Logging/Strings/SigningStrings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,20 @@ enum SigningStrings {

case signature_not_base64(String)

case signature_invalid_size(Data)

case signature_failed_verification

case signature_was_requested_but_not_provided(HTTPRequest)

case request_date_missing_from_headers(HTTPRequest)

#if DEBUG
case verifying_signature(signature: Data,
parameters: Signing.SignatureParameters,
salt: Data,
payload: Data,
message: Data)
case invalid_signature_data(HTTPRequest, Data, HTTPClient.ResponseHeaders, HTTPStatusCode)
#endif

Expand All @@ -42,6 +49,10 @@ extension SigningStrings: LogMessage {
case let .signature_not_base64(signature):
return "Signature is not base64: \(signature)"

case let .signature_invalid_size(signature):
return "Signature '\(signature)' does not have expected size (\(Signing.SignatureComponent.totalSize)). " +
"Verification failed."

case .signature_failed_verification:
return "Signature failed verification"

Expand All @@ -64,6 +75,21 @@ extension SigningStrings: LogMessage {
Body (length: \(data.count)): \(data.hashString)
"""

case let .verifying_signature(
signature,
parameters,
salt,
payload,
message
):
return """
Verifying signature '\(signature.base64EncodedString())'
Parameters: \(parameters),
Salt: \(salt.base64EncodedString()),
Payload: \(payload.base64EncodedString()),
Message: \(message.base64EncodedString())
"""

#endif
}
}
Expand Down
7 changes: 7 additions & 0 deletions Sources/Networking/HTTPClient/HTTPResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@ extension HTTPResponse {
return Self.value(forCaseInsensitiveHeaderField: field, in: self.responseHeaders)
}

static func value(
forCaseInsensitiveHeaderField field: HTTPClient.ResponseHeader,
in headers: Headers
) -> String? {
return Self.value(forCaseInsensitiveHeaderField: field.rawValue, in: headers)
}

static func value(forCaseInsensitiveHeaderField field: String, in headers: Headers) -> String? {
let header = headers
.first { (key, _) in
Expand Down
27 changes: 12 additions & 15 deletions Sources/Security/Signing+ResponseVerification.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,20 +70,23 @@ extension HTTPResponse where Body == Data {
publicKey: Signing.PublicKey?,
signing: SigningType.Type
) -> VerificationResult {
guard let nonce = request.nonce,
let publicKey = publicKey,
guard let publicKey = publicKey,
statusCode.isSuccessfulResponse,
#available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.2, *) else {
return .notRequested
}

guard let signature = HTTPResponse.value(
forCaseInsensitiveHeaderField: HTTPClient.ResponseHeader.signature.rawValue,
forCaseInsensitiveHeaderField: .signature,
in: headers
) else {
Logger.warn(Strings.signing.signature_was_requested_but_not_provided(request))

return .failed
let signatureRequested = request.nonce != nil
if signatureRequested {
Logger.warn(Strings.signing.signature_was_requested_but_not_provided(request))
return .failed
} else {
return .notRequested
}
}

guard let requestDate = requestDate else {
Expand All @@ -92,17 +95,11 @@ extension HTTPResponse where Body == Data {
return .failed
}

let eTag = HTTPResponse.value(forCaseInsensitiveHeaderField: HTTPClient.ResponseHeader.eTag.rawValue,
in: headers)

let messageToSign = statusCode == .notModified
? eTag?.asData ?? .init()
: body

if signing.verify(signature: signature,
with: .init(
message: messageToSign,
nonce: nonce,
message: body,
nonce: request.nonce,
etag: HTTPResponse.value(forCaseInsensitiveHeaderField: .eTag, in: headers),
requestDate: requestDate.millisecondsSince1970
),
publicKey: publicKey) {
Expand Down
92 changes: 85 additions & 7 deletions Sources/Security/Signing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ enum Signing: SigningType {
struct SignatureParameters {

let message: Data
let nonce: Data
let nonce: Data?
let etag: String?
let requestDate: UInt64

}
Expand Down Expand Up @@ -70,11 +71,28 @@ enum Signing: SigningType {
return false
}

let salt = signature.subdata(in: 0..<Self.saltSize)
let signatureToVerify = signature.subdata(in: Self.saltSize..<signature.count)
guard signature.count == SignatureComponent.totalSize else {
Logger.warn(Strings.signing.signature_invalid_size(signature))
return false
}

// Fixme: verify public key

let salt = signature.component(.salt)
let payload = signature.component(.payload)
let messageToVerify = salt + parameters.asData

let isValid = publicKey.isValidSignature(signatureToVerify, for: messageToVerify)
#if DEBUG
Logger.verbose(Strings.signing.verifying_signature(
signature: signature,
parameters: parameters,
salt: salt,
payload: payload,
message: messageToVerify
))
#endif

let isValid = publicKey.isValidSignature(payload, for: messageToVerify)

if !isValid {
Logger.warn(Strings.signing.signature_failed_verification)
Expand Down Expand Up @@ -103,9 +121,8 @@ enum Signing: SigningType {

// MARK: -

private static let publicKey = "UC1upXWg5QVmyOSwozp755xLqquBKjjU+di6U8QhMlM="
private static let publicKey = "drCCA+6YAKOAjT7b2RosYNTrRexVWnu+dR5fw/JuKeA="

internal static let saltSize = 16
}

extension Signing {
Expand Down Expand Up @@ -159,12 +176,59 @@ extension CryptoKit.Curve25519.Signing.PublicKey: SigningPublicKey {}

// MARK: - Internal implementation (visible for tests)

extension Signing {

enum SignatureComponent: CaseIterable, Comparable {

case intermediatePublicKey
case intermediateKeyExpiration
case intermediateKeySignature
case salt
case payload

var size: Int {
switch self {
case .intermediatePublicKey: return 32
case .intermediateKeyExpiration: return 4
case .intermediateKeySignature: return 64
case .salt: return 16
case .payload: return 64
}
}

static let signedPublicKeySize: Int = [Self]([
.intermediatePublicKey,
.intermediateKeyExpiration,
.intermediateKeySignature
])
.map(\.size)
.sum()

static let totalSize: Int = Self.allCases.map(\.size).sum()

/// Number of bytes where the component begins
fileprivate var offset: Int {
return Self.offsets[self]!
}

fileprivate static let offsets: [SignatureComponent: Int] = Set(Self.allCases)
.dictionaryWithValues { component in
Self.allCases
.prefix(while: { $0 != component })
.map(\.size)
.sum()
}
}

}

extension Signing.SignatureParameters {

var asData: Data {
return (
self.nonce +
(self.nonce ?? .init()) +
String(self.requestDate).asData +
(self.etag ?? "").asData +
self.message
)
}
Expand All @@ -174,3 +238,17 @@ extension Signing.SignatureParameters {
// MARK: - Private

private final class BundleToken: NSObject {}

// MARK: - Data extensions

private extension Data {

/// Extracts `Signing.SignatureComponent` from the receiver.
func component(_ component: Signing.SignatureComponent) -> Data {
let offset = component.offset
let size = component.size

return self.subdata(in: offset ..< offset + size)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,8 @@ final class InformationalSignatureVerificationHTTPClientTests: BaseSignatureVeri
}

func testValidSignatureWithETagResponse() throws {
XCTExpectFailure("Not yet implemented")

let body = "body".asData

self.mockPath(statusCode: .notModified, requestDate: Self.date1)
Expand All @@ -242,7 +244,7 @@ final class InformationalSignatureVerificationHTTPClientTests: BaseSignatureVeri
expect(MockSigning.requests).to(haveCount(1))
let signingRequest = try XCTUnwrap(MockSigning.requests.onlyElement)

expect(signingRequest.parameters.message) == Self.eTag.asData
expect(signingRequest.parameters.message) == body
expect(signingRequest.parameters.nonce) == request.nonce
expect(signingRequest.parameters.requestDate) == Self.date1.millisecondsSince1970
expect(signingRequest.signature) == Self.sampleSignature
Expand Down
Loading

0 comments on commit 1700575

Please sign in to comment.