Skip to content
Open
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 Sources/Crypto/Digests/BoringSSL/Digest_boring.swift
Original file line number Diff line number Diff line change
Expand Up @@ -173,10 +173,10 @@ private final class DigestContext<H: BoringSSLBackedHashFunction> {
private var context: H.Context

init() {
guard let contex = H.initialize() else {
guard let context = H.initialize() else {
preconditionFailure("Unable to initialize digest state")
}
self.context = contex
self.context = context
}

init(copying original: DigestContext) {
Expand Down
5 changes: 5 additions & 0 deletions Sources/CryptoExtras/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ add_library(CryptoExtras
"ARC/ARCServer.swift"
"ChaCha20CTR/BoringSSL/ChaCha20CTR_boring.swift"
"ChaCha20CTR/ChaCha20CTR.swift"
"Digests/BoringSSL/BoringSSLSHA512256Context.swift"
"Digests/BoringSSL/BoringSSLSHA512256HashFunction.swift"
"Digests/SHA512256.swift"
"Digests/SHA512256Digest.swift"
"EC/Curve25519+PEM.swift"
"EC/ObjectIdentifier.swift"
"EC/PKCS8DERRepresentation.swift"
Expand All @@ -58,6 +62,7 @@ add_library(CryptoExtras
"RSA/RSA.swift"
"RSA/RSA_boring.swift"
"Reexport.swift"
"Util/BoringSSL/Zeroization_boring.swift"
"Util/BoringSSLHelpers.swift"
"Util/CryptoKitErrors_boring.swift"
"Util/Data+Extensions.swift"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftCrypto open source project
//
// Copyright (c) 2026 Apple Inc. and the SwiftCrypto project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftCrypto project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

@_implementationOnly import CCryptoBoringSSL
import Crypto

#if canImport(Darwin)
import Darwin
#endif

@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *)
final class BoringSSLSHA512256Context {
private var context: SHA512_CTX

init() {
guard let context = BoringSSLSHA512256HashFunction.initialize() else {
preconditionFailure("Unable to initialize digest state")
}
self.context = context
}

deinit {
withUnsafeMutablePointer(to: &self.context) {
$0.zeroize()
}
}

init(copying original: BoringSSLSHA512256Context) {
self.context = original.context
}

func update(bufferPointer data: UnsafeRawBufferPointer) {
guard BoringSSLSHA512256HashFunction.update(&self.context, data: data) else {
preconditionFailure("Unable to update digest state")
}
}

func finalize() -> SHA512256Digest {
var copyContext = self.context
defer {
withUnsafeMutablePointer(to: &copyContext) {
$0.zeroize()
}
}
return withUnsafeTemporaryAllocation(byteCount: BoringSSLSHA512256HashFunction.digestSize, alignment: 1) {
digestPointer in
defer {
digestPointer.zeroize()
}

guard BoringSSLSHA512256HashFunction.finalize(&copyContext, digest: digestPointer) else {
preconditionFailure("Unable to finalize digest state")
}
// We force unwrap here because if the digest size is wrong it's an internal error.
return SHA512256Digest(bufferPointer: UnsafeRawBufferPointer(digestPointer))!
}
}
}

extension UnsafeMutablePointer {
fileprivate func zeroize() {
let size = MemoryLayout.size(ofValue: Pointee.self)
memset_s(self, size, 0, size)
}
}

extension UnsafeMutableRawBufferPointer {
fileprivate func zeroize() {
memset_s(self.baseAddress!, self.count, 0, self.count)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftCrypto open source project
//
// Copyright (c) 2026 Apple Inc. and the SwiftCrypto project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftCrypto project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

@_implementationOnly import CCryptoBoringSSL
import Crypto

struct BoringSSLSHA512256HashFunction {
static var digestSize: Int {
Int(SHA256_DIGEST_LENGTH)
}

static func initialize() -> SHA512_CTX? {
var context = SHA512_CTX()
guard CCryptoBoringSSL_SHA512_256_Init(&context) == 1 else {
return nil
}
return context
}

static func update(_ context: inout SHA512_CTX, data: UnsafeRawBufferPointer) -> Bool {
let result = CCryptoBoringSSL_SHA512_256_Update(&context, data.baseAddress, data.count)
return result == 1
}

static func finalize(_ context: inout SHA512_CTX, digest: UnsafeMutableRawBufferPointer) -> Bool {
guard let baseAddress = digest.baseAddress, digest.count == Self.digestSize else {
return false
}
let result = CCryptoBoringSSL_SHA512_256_Final(baseAddress, &context)
return result == 1
}
}
88 changes: 88 additions & 0 deletions Sources/CryptoExtras/Digests/SHA512256.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftCrypto open source project
//
// Copyright (c) 2026 Apple Inc. and the SwiftCrypto project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftCrypto project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import Crypto

/// An implementation of Secure Hashing Algorithm 2 (SHA-2) hashing with a
/// 256-bit digest using the SHA-512/256 variant.
///
/// The ``SHA512256`` hash implements the ``HashFunction`` protocol for the
/// specific case of SHA-512/256 hashing with a 256-bit digest
/// (``SHA512256Digest``). SHA-512/256 is a truncated variant of SHA-512 that
/// provides the same security level as SHA-256 but can be faster on 64-bit
/// platforms.
///
/// You can compute the digest by calling the static ``hash(data:)`` method
/// once. Alternatively, if the data that you want to hash is too large to fit
/// in memory, you can compute the digest iteratively by creating a new hash
/// instance, calling the ``update(data:)`` method repeatedly with blocks of
/// data, and then calling the ``finalize()`` method to get the result.
@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *)
public struct SHA512256: HashFunction, @unchecked Sendable {
/// The number of bytes that represents the hash function's internal state.
public static var blockByteCount: Int {
128
}

private var context = BoringSSLSHA512256Context()

/// Creates a SHA512-256 hash function.
///
/// Initialize a new hash function by calling this method if you want to
/// hash data iteratively, such as when you don't have a buffer large enough
/// to hold all the data at once. Provide data blocks to the hash function
/// using the ``update(data:)`` or ``update(bufferPointer:)`` method. After
/// providing all the data, call ``finalize()`` to get the digest.
///
/// If your data fits into a single buffer, you can use the ``hash(data:)``
/// method instead, to compute the digest in a single call.
public init() {}

/// Incrementally updates the hash function with the contents of the buffer.
///
/// Call this method one or more times to provide data to the hash function
/// in blocks. After providing the last block of data, call the
/// ``finalize()`` method to get the computed digest. Don't call the update
/// method again after finalizing the hash function.
///
/// - Note: Typically, it's safer to use an instance of
/// <doc://com.apple.documentation/documentation/foundation/data>, or some
/// other type that conforms to the
/// <doc://com.apple.documentation/documentation/foundation/dataprotocol>,
/// to hold your data. When possible, use the ``update(data:)`` method
/// instead.
///
/// - Parameters:
/// - bufferPointer: A pointer to the next block of data for the ongoing
/// digest calculation.
public mutating func update(bufferPointer data: UnsafeRawBufferPointer) {
if !isKnownUniquelyReferenced(&self.context) {
self.context = BoringSSLSHA512256Context(copying: self.context)
}
self.context.update(bufferPointer: data)
}

/// Finalizes the hash function and returns the computed digest.
///
/// Call this method after you provide the hash function with all the data
/// to hash by making one or more calls to the ``update(data:)`` or
/// ``update(bufferPointer:)`` method. After finalizing the hash function,
/// discard it. To compute a new digest, create a new hash function with a
/// call to the ``init()`` method.
///
/// - Returns: The computed digest of the data.
public func finalize() -> SHA512256Digest {
self.context.finalize()
}
}
99 changes: 99 additions & 0 deletions Sources/CryptoExtras/Digests/SHA512256Digest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftCrypto open source project
//
// Copyright (c) 2026 Apple Inc. and the SwiftCrypto project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftCrypto project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

/// The output of a Secure Hashing Algorithm 2 (SHA-2) hash with a 256-bit digest
/// using the SHA-512/256 variant.
@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *)
public struct SHA512256Digest: Digest {
let bytes: (UInt64, UInt64, UInt64, UInt64)

/// The number of bytes in the digest.
public static var byteCount: Int {
32
}

init?(bufferPointer: UnsafeRawBufferPointer) {
guard bufferPointer.count == 32 else {
return nil
}

var bytes = (UInt64(0), UInt64(0), UInt64(0), UInt64(0))
withUnsafeMutableBytes(of: &bytes) { targetPtr in
targetPtr.copyBytes(from: bufferPointer)
}
self.bytes = bytes
}

/// Invokes the given closure with a buffer pointer covering the raw bytes of
/// the digest.
///
/// - Parameters:
/// - body: A closure that takes a raw buffer pointer to the bytes of the digest
/// and returns the digest.
///
/// - Returns: The digest, as returned from the body closure.
#if !hasFeature(Embedded)
public func withUnsafeBytes<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R {
try Swift.withUnsafeBytes(of: bytes) {
let boundsCheckedPtr = UnsafeRawBufferPointer(
start: $0.baseAddress,
count: Self.byteCount
)
return try body(boundsCheckedPtr)
}
}
#else
public func withUnsafeBytes<R, E: Error>(_ body: (UnsafeRawBufferPointer) throws(E) -> R) throws(E) -> R {
try Swift.withUnsafeBytes(of: bytes) { ptr throws(E) -> R in
let boundsCheckedPtr = UnsafeRawBufferPointer(
start: ptr.baseAddress,
count: Self.byteCount
)
return try body(boundsCheckedPtr)
}
}
#endif

private func toArray() -> ArraySlice<UInt8> {
var array = [UInt8]()
array.appendByte(bytes.0)
array.appendByte(bytes.1)
array.appendByte(bytes.2)
array.appendByte(bytes.3)
return array.prefix(SHA512256Digest.byteCount)
}

#if !hasFeature(Embedded)
/// A human-readable description of the digest.
public var description: String {
"SHA512-256 digest: \(toArray().hexString)"
}
#endif

/// Hashes the essential components of the digest by feeding them into the
/// given hash function.
///
/// This method is part of the digest’s conformance to Swift standard library’s
/// <doc://com.apple.documentation/documentation/swift/hashable> protocol, making
/// it possible to compare digests. Don’t confuse that hashing with the
/// cryptographically secure hashing that you use to create the digest in the
/// first place by, for example, calling ``SHA512256/hash(data:)``.
///
/// - Parameters:
/// - hasher: The hash function to use when combining the components of
/// the digest.
public func hash(into hasher: inout Hasher) {
self.withUnsafeBytes { hasher.combine(bytes: $0) }
}
}
29 changes: 29 additions & 0 deletions Sources/CryptoExtras/Util/BoringSSL/Zeroization_boring.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftCrypto open source project
//
// Copyright (c) 2019 Apple Inc. and the SwiftCrypto project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftCrypto project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
#if !canImport(Darwin)
@_implementationOnly import CCryptoBoringSSL

@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *)
typealias errno_t = CInt

// This is a Swift wrapper for the libc function that does not exist on Linux. We shim it via a call to OPENSSL_cleanse.
// We have the same syntax, but mostly ignore it.
@discardableResult
func memset_s(_ s: UnsafeMutableRawPointer!, _ smax: Int, _ byte: CInt, _ n: Int) -> errno_t {
assert(smax == n, "memset_s invariant not met")
assert(byte == 0, "memset_s used to not zero anything")
CCryptoBoringSSL_OPENSSL_cleanse(s, smax)
return 0
}
#endif
Loading