From a09ee52207be073797ca66d82243a9611023466e Mon Sep 17 00:00:00 2001 From: Matt Rubin Date: Thu, 22 Dec 2022 22:10:46 -0500 Subject: [PATCH 1/2] Refactor HMAC generation to avoid code repetition --- Sources/Crypto.swift | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Sources/Crypto.swift b/Sources/Crypto.swift index 6f463ae..3982ec4 100644 --- a/Sources/Crypto.swift +++ b/Sources/Crypto.swift @@ -29,12 +29,16 @@ import CryptoKit func HMAC(algorithm: Generator.Algorithm, key: Data, data: Data) -> Data { let key = SymmetricKey(data: key) + func authenticationCode(with _: H.Type) -> Data { + return Data(CryptoKit.HMAC.authenticationCode(for: data, using: key)) + } + switch algorithm { case .sha1: - return Data(CryptoKit.HMAC.authenticationCode(for: data, using: key)) + return authenticationCode(with: Insecure.SHA1.self) case .sha256: - return Data(CryptoKit.HMAC.authenticationCode(for: data, using: key)) + return authenticationCode(with: SHA256.self) case .sha512: - return Data(CryptoKit.HMAC.authenticationCode(for: data, using: key)) + return authenticationCode(with: SHA512.self) } } From 0254da27aa483ae4e61e4fdaf75acf84d341ebc2 Mon Sep 17 00:00:00 2001 From: Matt Rubin Date: Wed, 28 Dec 2022 16:29:06 -0500 Subject: [PATCH 2/2] Refactor HMAC generation into Generator Now that the code required is much simpler, there's no need for a separate file. --- OneTimePassword.xcodeproj/project.pbxproj | 6 ---- Sources/Crypto.swift | 44 ----------------------- Sources/Generator.swift | 22 ++++++++++-- 3 files changed, 20 insertions(+), 52 deletions(-) delete mode 100644 Sources/Crypto.swift diff --git a/OneTimePassword.xcodeproj/project.pbxproj b/OneTimePassword.xcodeproj/project.pbxproj index 9285df5..d0c19e9 100644 --- a/OneTimePassword.xcodeproj/project.pbxproj +++ b/OneTimePassword.xcodeproj/project.pbxproj @@ -23,7 +23,6 @@ /* Begin PBXBuildFile section */ 5B39F49C1DBD06EB00CD2DAB /* Token.swift in Sources */ = {isa = PBXBuildFile; fileRef = C93A2519196B1BA400F86892 /* Token.swift */; }; 5B39F49D1DBD06EE00CD2DAB /* Generator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC7EC7196BDF3B00B50C82 /* Generator.swift */; }; - 5B39F49E1DBD06F100CD2DAB /* Crypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F544AC1C8391630023CCF0 /* Crypto.swift */; }; 5B39F49F1DBD06F500CD2DAB /* Token+URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC7EC3196BD5DF00B50C82 /* Token+URL.swift */; }; 5B39F4A01DBD06F900CD2DAB /* PersistentToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = C95F9FB81C03D6BC00CEA286 /* PersistentToken.swift */; }; 5B39F4A11DBD06FC00CD2DAB /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9003417196F7046009733E8 /* Keychain.swift */; }; @@ -45,7 +44,6 @@ C9B77D771C03078B00BAF6BF /* KeychainTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C93A2514196AFE1100F86892 /* KeychainTests.swift */; }; C9DC7EC4196BD5DF00B50C82 /* Token+URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC7EC3196BD5DF00B50C82 /* Token+URL.swift */; }; C9DC7EC8196BDF3B00B50C82 /* Generator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC7EC7196BDF3B00B50C82 /* Generator.swift */; }; - C9F544AD1C8391630023CCF0 /* Crypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F544AC1C8391630023CCF0 /* Crypto.swift */; }; FD6C3C0F1E0200F800EC4528 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6C3C0E1E0200F800EC4528 /* AppDelegate.swift */; }; FD6C3C341E02033600EC4528 /* OneTimePassword.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C97C82381946E51D00FD9F4C /* OneTimePassword.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; FDA64C751E020ABF004AD993 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FDA64C741E020ABF004AD993 /* Launch Screen.storyboard */; }; @@ -141,7 +139,6 @@ C9E829531C62DFDA003F5FC9 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = ""; }; C9E829541C62FFBD003F5FC9 /* CONTRIBUTING.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CONTRIBUTING.md; sourceTree = ""; }; C9E829551C630514003F5FC9 /* CONDUCT.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CONDUCT.md; sourceTree = ""; }; - C9F544AC1C8391630023CCF0 /* Crypto.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Crypto.swift; sourceTree = ""; }; FD6C3C0C1E0200F800EC4528 /* OneTimePasswordTestApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OneTimePasswordTestApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; FD6C3C0E1E0200F800EC4528 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; FD6C3C1A1E0200F800EC4528 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -260,7 +257,6 @@ children = ( C93A2519196B1BA400F86892 /* Token.swift */, C9DC7EC7196BDF3B00B50C82 /* Generator.swift */, - C9F544AC1C8391630023CCF0 /* Crypto.swift */, C9307A8619A8522F00609B02 /* Serialization */, C9A9B09A1A81EF4B00F3C4DC /* Persistence */, C97C823B1946E51D00FD9F4C /* Supporting Files */, @@ -572,7 +568,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 5B39F49E1DBD06F100CD2DAB /* Crypto.swift in Sources */, 5B39F49F1DBD06F500CD2DAB /* Token+URL.swift in Sources */, 5B39F49C1DBD06EB00CD2DAB /* Token.swift in Sources */, 5B39F49D1DBD06EE00CD2DAB /* Generator.swift in Sources */, @@ -585,7 +580,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - C9F544AD1C8391630023CCF0 /* Crypto.swift in Sources */, C93A251A196B1BA400F86892 /* Token.swift in Sources */, C95F9FB91C03D6BC00CEA286 /* PersistentToken.swift in Sources */, C9DC7EC8196BDF3B00B50C82 /* Generator.swift in Sources */, diff --git a/Sources/Crypto.swift b/Sources/Crypto.swift deleted file mode 100644 index 3982ec4..0000000 --- a/Sources/Crypto.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// Crypto.swift -// OneTimePassword -// -// Copyright (c) 2016-2018 Matt Rubin and the OneTimePassword authors -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// - -import Foundation -import CryptoKit - -func HMAC(algorithm: Generator.Algorithm, key: Data, data: Data) -> Data { - let key = SymmetricKey(data: key) - - func authenticationCode(with _: H.Type) -> Data { - return Data(CryptoKit.HMAC.authenticationCode(for: data, using: key)) - } - - switch algorithm { - case .sha1: - return authenticationCode(with: Insecure.SHA1.self) - case .sha256: - return authenticationCode(with: SHA256.self) - case .sha512: - return authenticationCode(with: SHA512.self) - } -} diff --git a/Sources/Generator.swift b/Sources/Generator.swift index eb2f1f5..5e54fa9 100644 --- a/Sources/Generator.swift +++ b/Sources/Generator.swift @@ -2,7 +2,7 @@ // Generator.swift // OneTimePassword // -// Copyright (c) 2014-2018 Matt Rubin and the OneTimePassword authors +// Copyright (c) 2014-2022 Matt Rubin and the OneTimePassword authors // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -23,6 +23,7 @@ // SOFTWARE. // +import CryptoKit import Foundation /// A `Generator` contains all of the parameters needed to generate a one-time password. @@ -76,7 +77,7 @@ public struct Generator: Equatable { // Generate an HMAC value from the key and counter let counterData = Data(bytes: &bigCounter, count: MemoryLayout.size) - let hash = HMAC(algorithm: algorithm, key: secret, data: counterData) + let hash = Self.generateHMAC(for: counterData, using: secret, with: algorithm) var truncatedHash = hash.withUnsafeBytes { ptr -> UInt32 in // Use the last 4 bits of the hash as an offset (0 <= offset <= 15) @@ -98,6 +99,23 @@ public struct Generator: Equatable { return String(truncatedHash).padded(with: "0", toLength: digits) } + private static func generateHMAC(for data: Data, using keyData: Data, with algorithm: Generator.Algorithm) -> Data { + func authenticationCode(with _: H.Type) -> Data { + let key = SymmetricKey(data: keyData) + let authenticationCode = HMAC.authenticationCode(for: data, using: key) + return Data(authenticationCode) + } + + switch algorithm { + case .sha1: + return authenticationCode(with: Insecure.SHA1.self) + case .sha256: + return authenticationCode(with: SHA256.self) + case .sha512: + return authenticationCode(with: SHA512.self) + } + } + // MARK: Update /// Returns a `Generator` configured to generate the *next* password, which follows the password