Skip to content

Commit ea0b31e

Browse files
authored
Refactor generation of SHA256 checksums (#3116)
* Add sha256Checksum property to ByteString in extension * Adopt sha256Checksum property in RepositorySpecifier.fileSystemIdentifier * Adopt sha256Checksum property in PIF.sign(_:) * Adopt sha256Checksum property in ManifestLoader * Use CryptoKitSHA256 as workspace checksum algorithm when available
1 parent 9305cd7 commit ea0b31e

File tree

7 files changed

+86
-29
lines changed

7 files changed

+86
-29
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
This source file is part of the Swift.org open source project
3+
Copyright (c) 2020 Apple Inc. and the Swift project authors
4+
Licensed under Apache License v2.0 with Runtime Library Exception
5+
See http://swift.org/LICENSE.txt for license information
6+
See http://swift.org/CONTRIBUTORS.txt for Swift project authors
7+
*/
8+
9+
import TSCBasic
10+
11+
extension ByteString {
12+
/// A lowercase, hexadecimal representation of the SHA256 hash
13+
/// generated for the byte string's contents.
14+
///
15+
/// This property uses the CryptoKit implementation of
16+
/// Secure Hashing Algorithm 2 (SHA-2) hashing with a 256-bit digest, when available,
17+
/// falling back on a native implementation in Swift provided by TSCBasic.
18+
public var sha256Checksum: String {
19+
#if canImport(CryptoKit)
20+
if #available(macOS 10.15, *) {
21+
return CryptoKitSHA256().hash(self).hexadecimalRepresentation
22+
}
23+
#endif
24+
25+
return SHA256().hash(self).hexadecimalRepresentation
26+
}
27+
}

Sources/Basics/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
# See http://swift.org/CONTRIBUTORS.txt for Swift project authors
88

99
add_library(Basics
10+
ByteString+Extensions.swift
1011
ConcurrencyHelpers.swift
1112
Dictionary+Extensions.swift
1213
DispatchTimeInterval+Extensions.swift

Sources/PackageLoading/ManifestLoader.swift

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -664,6 +664,7 @@ public final class ManifestLoader: ManifestLoaderProtocol {
664664
let toolsVersion: ToolsVersion
665665
let env: [String: String]
666666
let swiftpmVersion: String
667+
let sha256Checksum: String
667668

668669
init (packageIdentity: PackageIdentity,
669670
manifestPath: AbsolutePath,
@@ -672,37 +673,38 @@ public final class ManifestLoader: ManifestLoaderProtocol {
672673
swiftpmVersion: String,
673674
fileSystem: FileSystem
674675
) throws {
676+
let manifestContents = try fileSystem.readFileContents(manifestPath).contents
677+
let sha256Checksum = try Self.computeSHA256Checksum(packageIdentity: packageIdentity, manifestContents: manifestContents, toolsVersion: toolsVersion, env: env, swiftpmVersion: swiftpmVersion)
678+
675679
self.packageIdentity = packageIdentity
676680
self.manifestPath = manifestPath
677-
self.manifestContents = try fileSystem.readFileContents(manifestPath).contents
681+
self.manifestContents = manifestContents
678682
self.toolsVersion = toolsVersion
679683
self.env = env
680684
self.swiftpmVersion = swiftpmVersion
685+
self.sha256Checksum = sha256Checksum
681686
}
682687

683-
// TODO: is there a way to avoid the dual hashing?
684688
func hash(into hasher: inout Hasher) {
685-
hasher.combine(self.packageIdentity)
686-
hasher.combine(self.manifestContents)
687-
hasher.combine(self.toolsVersion.description)
688-
for key in self.env.keys.sorted(by: >) {
689-
hasher.combine(key)
690-
hasher.combine(env[key]!) // forced unwrap safe
691-
}
692-
hasher.combine(self.swiftpmVersion)
689+
hasher.combine(self.sha256Checksum)
693690
}
694691

695-
// TODO: is there a way to avoid the dual hashing?
696-
func computeHash() throws -> ByteString {
692+
private static func computeSHA256Checksum(
693+
packageIdentity: PackageIdentity,
694+
manifestContents: [UInt8],
695+
toolsVersion: ToolsVersion,
696+
env: [String: String],
697+
swiftpmVersion: String
698+
) throws -> String {
697699
let stream = BufferedOutputByteStream()
698-
stream <<< self.packageIdentity
699-
stream <<< self.manifestContents
700-
stream <<< self.toolsVersion.description
701-
for key in self.env.keys.sorted(by: >) {
702-
stream <<< key <<< env[key]! // forced unwrap safe
700+
stream <<< packageIdentity
701+
stream <<< manifestContents
702+
stream <<< toolsVersion.description
703+
for key in env.keys.sorted(by: >) {
704+
stream <<< key <<< env[key]! // forced unwrap safe
703705
}
704-
stream <<< self.swiftpmVersion
705-
return SHA256().hash(stream.bytes)
706+
stream <<< swiftpmVersion
707+
return stream.bytes.sha256Checksum
706708
}
707709
}
708710

@@ -849,7 +851,7 @@ public final class ManifestLoader: ManifestLoaderProtocol {
849851
if compilerResult.exitStatus != .terminated(code: 0) {
850852
return
851853
}
852-
854+
853855
// Pass an open file descriptor of a file to which the JSON representation of the manifest will be written.
854856
let jsonOutputFile = tmpDir.appending(component: "\(packageIdentity)-output.json")
855857
guard let jsonOutputFileDesc = fopen(jsonOutputFile.pathString, "w") else {
@@ -1135,12 +1137,11 @@ private final class SQLiteManifestCache: Closable {
11351137
}
11361138

11371139
func put(key: ManifestLoader.ManifestCacheKey, manifest: ManifestLoader.ManifestParseResult) throws {
1138-
let query = "INSERT OR IGNORE INTO MANIFEST_CACHE VALUES (?, ?);"
1140+
let query = "INSERT OR IGNORE INTO MANIFEST_CACHE VALUES (?, ?);"
11391141
try self.executeStatement(query) { statement -> Void in
1140-
let keyHash = try key.computeHash()
11411142
let data = try self.jsonEncoder.encode(manifest)
11421143
let bindings: [SQLite.SQLiteValue] = [
1143-
.string(keyHash.hexadecimalRepresentation),
1144+
.string(key.sha256Checksum),
11441145
.blob(data),
11451146
]
11461147
try statement.bind(bindings)
@@ -1151,8 +1152,7 @@ private final class SQLiteManifestCache: Closable {
11511152
func get(key: ManifestLoader.ManifestCacheKey) throws -> ManifestLoader.ManifestParseResult? {
11521153
let query = "SELECT value FROM MANIFEST_CACHE WHERE key == ? LIMIT 1;"
11531154
return try self.executeStatement(query) { statement -> ManifestLoader.ManifestParseResult? in
1154-
let keyHash = try key.computeHash()
1155-
try statement.bind([.string(keyHash.hexadecimalRepresentation)])
1155+
try statement.bind([.string(key.sha256Checksum)])
11561156
let data = try statement.step()?.blob(at: 0)
11571157
return try data.flatMap {
11581158
try self.jsonDecoder.decode(ManifestLoader.ManifestParseResult.self, from: $0)

Sources/SourceControl/Repository.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,9 @@ public struct RepositorySpecifier: Hashable, Codable {
2626
/// unique for each repository.
2727
public var fileSystemIdentifier: String {
2828
// Use first 8 chars of a stable hash.
29-
let hash = SHA256().hash(url).hexadecimalRepresentation
30-
let suffix = hash.dropLast(hash.count - 8)
29+
let suffix = ByteString(encodingAsUTF8: url).sha256Checksum.prefix(8)
3130

32-
return basename + "-" + suffix
31+
return "\(basename)-\(suffix)"
3332
}
3433

3534
/// Returns the cleaned basename for the specifier.

Sources/Workspace/Workspace.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,13 @@ public class Workspace {
303303
self.downloader = downloader
304304
self.netrcFilePath = netrcFilePath
305305
self.archiver = archiver
306+
307+
var checksumAlgorithm = checksumAlgorithm
308+
#if canImport(CryptoKit)
309+
if checksumAlgorithm is SHA256, #available(macOS 10.15, *) {
310+
checksumAlgorithm = CryptoKitSHA256()
311+
}
312+
#endif
306313
self.checksumAlgorithm = checksumAlgorithm
307314
self.isResolverPrefetchingEnabled = isResolverPrefetchingEnabled
308315
self.skipUpdate = skipUpdate

Sources/XCBuildSupport/PIF.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1205,7 +1205,7 @@ extension PIF {
12051205
func sign<T: PIFSignableObject & Encodable>(_ obj: T) throws {
12061206
let signatureContent = try encoder.encode(obj)
12071207
let bytes = ByteString(signatureContent)
1208-
obj.signature = SHA256().hash(bytes).hexadecimalRepresentation
1208+
obj.signature = bytes.sha256Checksum
12091209
}
12101210

12111211
let projects = workspace.projects
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
This source file is part of the Swift.org open source project
3+
4+
Copyright (c) 2020 Apple Inc. and the Swift project authors
5+
Licensed under Apache License v2.0 with Runtime Library Exception
6+
7+
See http://swift.org/LICENSE.txt for license information
8+
See http://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
*/
10+
11+
import Basics
12+
import TSCBasic
13+
import XCTest
14+
15+
final class ByteStringExtensionsTests: XCTestCase {
16+
func testSHA256Checksum() {
17+
let byteString = ByteString(encodingAsUTF8: "abc")
18+
XCTAssertEqual(byteString.contents, [0x61, 0x62, 0x63])
19+
20+
// See https://csrc.nist.gov/csrc/media/projects/cryptographic-standards-and-guidelines/documents/examples/sha_all.pdf
21+
XCTAssertEqual(byteString.sha256Checksum, "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad")
22+
}
23+
}

0 commit comments

Comments
 (0)