Skip to content

Commit

Permalink
Bind SHA256/SHA1/MD5 Update/Finalize API (#228)
Browse files Browse the repository at this point in the history
  • Loading branch information
waahm7 authored Feb 8, 2024
1 parent d137ff9 commit 0be0919
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 14 deletions.
77 changes: 67 additions & 10 deletions Source/AwsCommonRuntimeKit/crt/Hash.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,77 @@ import struct Foundation.Data
import struct Foundation.TimeInterval
import AwsCCal

public enum HashAlgorithm {
case SHA1
case SHA256
case MD5

var size: Int {
switch self {
case .SHA1:
return Int(AWS_SHA1_LEN)
case .SHA256:
return Int(AWS_SHA256_LEN)
case .MD5:
return Int(AWS_MD5_LEN)
}
}
}

public class Hash {
let rawValue: UnsafeMutablePointer<aws_hash>
let algorithm: HashAlgorithm
public init(algorithm: HashAlgorithm) {
self.algorithm = algorithm
switch algorithm {
case .SHA1:
rawValue = aws_sha1_new(allocator.rawValue)
case .SHA256:
rawValue = aws_sha256_new(allocator.rawValue)
case .MD5:
rawValue = aws_md5_new(allocator.rawValue)
}
}

public func update(data: Data) throws {
try data.withUnsafeBytes { bufferPointer in
var byteCursor = aws_byte_cursor_from_array(bufferPointer.baseAddress, data.count)
guard aws_hash_update(rawValue, &byteCursor) == AWS_OP_SUCCESS else {
throw CommonRunTimeError.crtError(.makeFromLastError())
}
}
}

public func finalize(truncateTo: Int = 0) throws -> Data {
var bufferData = Data(count: algorithm.size)
try bufferData.withUnsafeMutableBytes { bufferPointer in
var buffer = aws_byte_buf_from_empty_array(bufferPointer.baseAddress, algorithm.size)
guard aws_hash_finalize(rawValue, &buffer, truncateTo) == AWS_OP_SUCCESS else {
throw CommonRunTimeError.crtError(.makeFromLastError())
}
}
return bufferData
}

deinit {
aws_hash_destroy(rawValue)
}

}

extension Data {

/// Computes the md5 hash over data. Use this if you don't need to stream the data you're hashing and you can load
/// the entire input to hash into memory.
/// - Parameter truncate: If you specify truncate something other than 0, the output will be truncated to that number of bytes.
public func computeMD5(truncate: Int = 0) throws -> Data {
/// - Parameter truncateTo: If you specify truncate something other than 0, the output will be truncated to that number of bytes.
public func computeMD5(truncateTo: Int = 0) throws -> Data {
try self.withUnsafeBytes { bufferPointer in
var byteCursor = aws_byte_cursor_from_array(bufferPointer.baseAddress, count)

let bufferSize = 16
var bufferData = Data(count: bufferSize)
try bufferData.withUnsafeMutableBytes { bufferPointer in
var buffer = aws_byte_buf_from_empty_array(bufferPointer.baseAddress, bufferSize)
guard aws_md5_compute(allocator.rawValue, &byteCursor, &buffer, truncate) == AWS_OP_SUCCESS else {
guard aws_md5_compute(allocator.rawValue, &byteCursor, &buffer, truncateTo) == AWS_OP_SUCCESS else {
throw CommonRunTimeError.crtError(.makeFromLastError())
}
}
Expand All @@ -27,16 +84,16 @@ extension Data {
}

/// Computes the sha256 hash over data.
/// - Parameter truncate: If you specify truncate something other than 0, the output will be truncated to that number of bytes. For
/// - Parameter truncateTo: If you specify truncate something other than 0, the output will be truncated to that number of bytes. For
/// example, if you want a SHA256 digest as the first 16 bytes, set truncate to 16.
public func computeSHA256(truncate: Int = 0) throws -> Data {
public func computeSHA256(truncateTo: Int = 0) throws -> Data {
try self.withUnsafeBytes { bufferPointer in
var byteCursor = aws_byte_cursor_from_array(bufferPointer.baseAddress, count)
let bufferSize = Int(AWS_SHA256_LEN)
var bufferData = Data(count: bufferSize)
try bufferData.withUnsafeMutableBytes { bufferDataPointer in
var buffer = aws_byte_buf_from_empty_array(bufferDataPointer.baseAddress, bufferSize)
guard aws_sha256_compute(allocator.rawValue, &byteCursor, &buffer, truncate) == AWS_OP_SUCCESS else {
guard aws_sha256_compute(allocator.rawValue, &byteCursor, &buffer, truncateTo) == AWS_OP_SUCCESS else {
throw CommonRunTimeError.crtError(.makeFromLastError())
}
}
Expand All @@ -45,16 +102,16 @@ extension Data {
}

/// Computes the sha1 hash over data.
/// - Parameter truncate: If you specify truncate something other than 0, the output will be truncated to that number of bytes. For
/// - Parameter truncateTo: If you specify truncate something other than 0, the output will be truncated to that number of bytes. For
/// example, if you want a SHA1 digest as the first 10 bytes, set truncate to 10.
public func computeSHA1(truncate: Int = 0) throws -> Data {
public func computeSHA1(truncateTo: Int = 0) throws -> Data {
try self.withUnsafeBytes { bufferPointer in
var byteCursor = aws_byte_cursor_from_array(bufferPointer.baseAddress, count)
let bufferSize = Int(AWS_SHA1_LEN)
var bufferData = Data(count: bufferSize)
try bufferData.withUnsafeMutableBytes { bufferDataPointer in
var buffer = aws_byte_buf_from_empty_array(bufferDataPointer.baseAddress, bufferSize)
guard aws_sha1_compute(allocator.rawValue, &byteCursor, &buffer, truncate) == AWS_OP_SUCCESS else {
guard aws_sha1_compute(allocator.rawValue, &byteCursor, &buffer, truncateTo) == AWS_OP_SUCCESS else {
throw CommonRunTimeError.crtError(.makeFromLastError())
}
}
Expand Down
56 changes: 56 additions & 0 deletions Test/AwsCommonRuntimeKitTests/crt/HashTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0.
import XCTest
import AwsCCommon

@testable import AwsCommonRuntimeKit

class HashTests: XCBaseTestCase {
Expand All @@ -19,12 +20,48 @@ class HashTests: XCBaseTestCase {

XCTAssertEqual(md5, "iB0/3YSo7maijL0IGOgA9g==")
}

func testMd5PayloadWithUpdate() throws {
let hello = "Hello".data(using: .utf8)!
let world = "World".data(using: .utf8)!

let helloWorld = "HelloWorld".data(using: .utf8)!
let md5 = try! helloWorld.computeMD5().encodeToHexString()

let hash = Hash(algorithm: .MD5)
try hash.update(data: hello)
try hash.update(data: world)
let finalizedHash = try hash.finalize().encodeToHexString()


XCTAssertEqual(md5, finalizedHash)
XCTAssertEqual(finalizedHash, "68e109f0f40ca72a15e05cc22786f8e6")
}


func testSha256() throws {
let hello = "Hello".data(using: .utf8)!
let sha256 = try! hello.computeSHA256().base64EncodedString()
XCTAssertEqual(sha256, "GF+NsyJx/iX1Yab8k4suJkMG7DBO2lGAB9F2SCY4GWk=")
}

func testSha256WithUpdate() throws {
let hello = "Hello".data(using: .utf8)!
let world = "World".data(using: .utf8)!

let helloWorld = "HelloWorld".data(using: .utf8)!
let sha256 = try! helloWorld.computeSHA256().encodeToHexString()

let hash = Hash(algorithm: .SHA256)
try hash.update(data: hello)
try hash.update(data: world)
let finalizedHash = try hash.finalize().encodeToHexString()


XCTAssertEqual(sha256, finalizedHash)
XCTAssertEqual(finalizedHash, "872e4e50ce9990d8b041330c47c9ddd11bec6b503ae9386a99da8584e9bb12c4")
}


func testSha256EmptyString() throws {
let empty = "".data(using: .utf8)!
Expand All @@ -46,6 +83,25 @@ class HashTests: XCBaseTestCase {
let hello = "Hello".data(using: .utf8)!
XCTAssertEqual(try! hello.computeSHA1().encodeToHexString(), "f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0")
}


func testSha1WithUpdate() throws {
let hello = "Hello".data(using: .utf8)!
let world = "World".data(using: .utf8)!

let helloWorld = "HelloWorld".data(using: .utf8)!
let sha1 = try! helloWorld.computeSHA1().encodeToHexString()


let hash = Hash(algorithm: .SHA1)
try hash.update(data: hello)
try hash.update(data: world)
let finalizedHash = try hash.finalize().encodeToHexString()

XCTAssertEqual(sha1, finalizedHash)
XCTAssertEqual(finalizedHash, "db8ac1c259eb89d4a131b253bacfca5f319d54f2")
}


func testSha1EmptyString() throws {
let empty = "".data(using: .utf8)!
Expand Down
2 changes: 1 addition & 1 deletion aws-common-runtime/aws-c-io
2 changes: 1 addition & 1 deletion aws-common-runtime/s2n
Submodule s2n updated 177 files

0 comments on commit 0be0919

Please sign in to comment.