Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Choose hash algorithm #80

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
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
99 changes: 85 additions & 14 deletions Heimdall/Heimdall.swift
Original file line number Diff line number Diff line change
Expand Up @@ -344,11 +344,12 @@ open class Heimdall {
/// - parameters:
/// - string: Message to generate the signature for
/// - urlEncode: True if the resulting Base64 data should be URL encoded
/// - algorithm: Hashing algorith which will be chosen
///
/// - returns: Signature as a Base64 string
///
open func sign(_ string: String, urlEncode: Bool = false) -> String? {
if let data = string.data(using: String.Encoding.utf8, allowLossyConversion: false), let signatureData = self.sign(data) {
open func sign(_ string: String, urlEncode: Bool = false, algorithm: HashAlgorithm = .sha256) -> String? {
if let data = string.data(using: String.Encoding.utf8, allowLossyConversion: false), let signatureData = self.sign(data, algorithm: algorithm) {

var signature = signatureData.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0))

Expand All @@ -368,14 +369,15 @@ open class Heimdall {
///
/// - parameters:
/// - data: Data to sign
/// - algorithm: Hashing algorith which will be chosen
///
/// - returns: Signature as NSData
///
open func sign(_ data: Data) -> Data? {
if let key = obtainKey(.private), let hash = NSMutableData(length: Int(CC_SHA256_DIGEST_LENGTH)) {
open func sign(_ data: Data, algorithm: HashAlgorithm = .sha256) -> Data? {
if let key = obtainKey(.private), let hash = NSMutableData(length: Int(algorithm.digestLength())) {

// Create SHA256 hash of the message
CC_SHA256((data as NSData).bytes, CC_LONG(data.count), hash.mutableBytes.assumingMemoryBound(to: UInt8.self))
// Create hash of the message
_ = algorithm.computeHash((data as NSData).bytes, CC_LONG(data.count), hash.mutableBytes.assumingMemoryBound(to: UInt8.self))

// Sign the hash with the private key
let blockSize = SecKeyGetBlockSize(key)
Expand All @@ -387,7 +389,7 @@ open class Heimdall {
let encryptedData = result.mutableBytes.assumingMemoryBound(to: UInt8.self)
var encryptedDataLength = blockSize

let status = SecKeyRawSign(key, .PKCS1SHA256, hashData, hashDataLength, encryptedData, &encryptedDataLength)
let status = SecKeyRawSign(key, algorithm.padding(), hashData, hashDataLength, encryptedData, &encryptedDataLength)

if status == noErr {
// Create Base64 string of the result
Expand All @@ -406,12 +408,13 @@ open class Heimdall {
///
/// - parameters:
/// - message: Message that was used to generate the signature
/// - signatureBase64: Base64 of the signature data, signature is made of the SHA256 hash of message
/// - signatureBase64: Base64 of the signature data, signature is made of the message hash
/// - urlEncoded: True, if the signature is URL encoded and has to be reversed before manipulating
/// - algorithm: Hashing algorith which will be chosen
///
/// - returns: true if the signature is valid (and can be validated)
///
open func verify(_ message: String, signatureBase64: String, urlEncoded: Bool = true) -> Bool {
open func verify(_ message: String, signatureBase64: String, urlEncoded: Bool = true, algorithm: HashAlgorithm = .sha256) -> Bool {
var string = signatureBase64

if urlEncoded {
Expand All @@ -420,7 +423,7 @@ open class Heimdall {
}

if let data = message.data(using: String.Encoding.utf8, allowLossyConversion: false), let signature = Data(base64Encoded: string, options: NSData.Base64DecodingOptions(rawValue: 0)) {
return self.verify(data, signatureData: signature)
return self.verify(data, signatureData: signature, algorithm: algorithm)
}

return false
Expand All @@ -432,18 +435,19 @@ open class Heimdall {
/// - parameters:
/// - data: Data the signature should be verified against
/// - signatureData: Data of the signature
/// - algorithm: Hashing algorith which will be chosen
///
/// - returns: True if the signature is valid
///
open func verify(_ data: Data, signatureData: Data) -> Bool {
if let key = obtainKey(.public), let hashData = NSMutableData(length: Int(CC_SHA256_DIGEST_LENGTH)) {
CC_SHA256((data as NSData).bytes, CC_LONG(data.count), hashData.mutableBytes.assumingMemoryBound(to: UInt8.self))
open func verify(_ data: Data, signatureData: Data, algorithm: HashAlgorithm = .sha256) -> Bool {
if let key = obtainKey(.public), let hashData = NSMutableData(length: Int(algorithm.digestLength())) {
_ = algorithm.computeHash((data as NSData).bytes, CC_LONG(data.count), hashData.mutableBytes.assumingMemoryBound(to: UInt8.self))

let signedData = hashData.bytes.bindMemory(to: UInt8.self, capacity: hashData.length)
let signatureLength = Int(signatureData.count)
let signatureData = (signatureData as NSData).bytes.bindMemory(to: UInt8.self, capacity: signatureData.count)

let result = SecKeyRawVerify(key, .PKCS1SHA256, signedData, Int(CC_SHA256_DIGEST_LENGTH), signatureData, signatureLength)
let result = SecKeyRawVerify(key, algorithm.padding(), signedData, Int(algorithm.digestLength()), signatureData, signatureLength)

switch result {
case noErr:
Expand Down Expand Up @@ -985,3 +989,70 @@ private extension Data {
}
}

public enum HashAlgorithm {
case sha1
case sha224
case sha256
case sha384
case sha512


///
/// The type of padding to use when you create or verify a digital signature.
///
fileprivate func padding() -> SecPadding {
switch self {
case .sha1:
return .PKCS1SHA1
case .sha224:
return .PKCS1SHA224
case .sha256:
return .PKCS1SHA256
case .sha384:
return .PKCS1SHA384
case .sha512:
return .PKCS1SHA512
}
}

///
/// Digest length in bytes
///
fileprivate func digestLength() -> Int32 {
switch self {
case .sha1:
return CC_SHA1_DIGEST_LENGTH
case .sha224:
return CC_SHA224_DIGEST_LENGTH
case .sha256:
return CC_SHA256_DIGEST_LENGTH
case .sha384:
return CC_SHA384_DIGEST_LENGTH
case .sha512:
return CC_SHA512_DIGEST_LENGTH
}
}

///
/// Compute hash of message
///
/// - Parameters:
/// - data: input string
/// - len: length of the input string
/// - md: output hash
fileprivate func computeHash(_ data: UnsafeRawPointer!, _ len: CC_LONG, _ md: UnsafeMutablePointer<UInt8>!) -> UnsafeMutablePointer<UInt8>! {
switch self {
case .sha1:
return CC_SHA1(data, len, md)
case .sha224:
return CC_SHA224(data, len, md)
case .sha256:
return CC_SHA256(data, len, md)
case .sha384:
return CC_SHA384(data, len, md)
case .sha512:
return CC_SHA512(data, len, md)
}
}
}

46 changes: 31 additions & 15 deletions HeimdallTests/HeimdallTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,43 +92,59 @@ class HeimdallTests: XCTestCase {
}

func testSigning() {
testSigning(.sha1)
testSigning(.sha224)
testSigning(.sha256)
testSigning(.sha384)
testSigning(.sha512)
}

func testSigning(_ hash: HashAlgorithm) {
let testData = "This is a test string".data(using: String.Encoding.utf8)!

// Test signing with an instance that should have the means to do so (private key based instance)
XCTAssertNotNil(self.privateHeimdall.sign(testData))
XCTAssertNotNil(self.privateHeimdall.sign(testData, algorithm: hash))

// Signing should fail for an instance which is created based on a public key
XCTAssertNil(self.publicHeimdall.sign(testData))
XCTAssertNil(self.publicHeimdall.sign(testData, algorithm: hash))

// Signing should also fail for any destroyed kind of heimdall
XCTAssertNil(self.destroyedPublicHeimdall.sign(testData))
XCTAssertNil(self.destroyedPrivateHeimdall.sign(testData))
XCTAssertNil(self.destroyedPublicHeimdall.sign(testData, algorithm: hash))
XCTAssertNil(self.destroyedPrivateHeimdall.sign(testData, algorithm: hash))

// We can also test that signature is always the same (it is solely dependent on the input string)
let first = self.privateHeimdall.sign(testData)!
let second = self.privateHeimdall.sign(testData)!
let first = self.privateHeimdall.sign(testData, algorithm: hash)!
let second = self.privateHeimdall.sign(testData, algorithm: hash)!
XCTAssertEqual(first, second)
}

func testVerifying() {
testVerifying(.sha1)
testVerifying(.sha224)
testVerifying(.sha256)
testVerifying(.sha384)
testVerifying(.sha512)
}

func testVerifying(_ hash: HashAlgorithm) {
// Verification should work with both private and public heimdalls
let testData = "This is a test string".data(using: String.Encoding.utf8)!
let corruptedData = "This is a test injected string".data(using: String.Encoding.utf8)!
let testSignature = self.privateHeimdall.sign(testData)!
let testSignature = self.privateHeimdall.sign(testData, algorithm: hash)!

// Valid signature test
XCTAssertTrue(self.privateHeimdall.verify(testData, signatureData: testSignature))
XCTAssertTrue(self.publicHeimdall.verify(testData, signatureData: testSignature))
XCTAssertTrue(self.privateHeimdall.verify(testData, signatureData: testSignature, algorithm: hash))
XCTAssertTrue(self.publicHeimdall.verify(testData, signatureData: testSignature, algorithm: hash))

// Invalid signature test
XCTAssertFalse(self.privateHeimdall.verify(corruptedData, signatureData: testSignature))
XCTAssertFalse(self.publicHeimdall.verify(corruptedData, signatureData: testSignature))
XCTAssertFalse(self.privateHeimdall.verify(corruptedData, signatureData: testSignature, algorithm: hash))
XCTAssertFalse(self.publicHeimdall.verify(corruptedData, signatureData: testSignature, algorithm: hash))

// Destroyed Heimdalls should always fail
XCTAssertFalse(self.destroyedPrivateHeimdall.verify(testData, signatureData: testSignature))
XCTAssertFalse(self.destroyedPublicHeimdall.verify(testData, signatureData: testSignature))
XCTAssertFalse(self.destroyedPrivateHeimdall.verify(corruptedData, signatureData: testSignature))
XCTAssertFalse(self.destroyedPublicHeimdall.verify(corruptedData, signatureData: testSignature))
XCTAssertFalse(self.destroyedPrivateHeimdall.verify(testData, signatureData: testSignature, algorithm: hash))
XCTAssertFalse(self.destroyedPublicHeimdall.verify(testData, signatureData: testSignature, algorithm: hash))
XCTAssertFalse(self.destroyedPrivateHeimdall.verify(corruptedData, signatureData: testSignature, algorithm: hash))
XCTAssertFalse(self.destroyedPublicHeimdall.verify(corruptedData, signatureData: testSignature, algorithm: hash))
}

func testEncrypting() {
Expand Down