From 2e744a760f3b3e75b11fa6604c510f1763c9ddd7 Mon Sep 17 00:00:00 2001 From: Danny Moesch Date: Sat, 1 Dec 2018 15:53:48 +0100 Subject: [PATCH] Introduce TokenBuilder to build up OTP tokens conveniently --- pass.xcodeproj/project.pbxproj | 8 + passKit/Models/Password.swift | 80 ++------ passKit/Parser/Constants.swift | 6 +- passKit/Parser/TokenBuilder.swift | 88 +++++++++ passKitTests/Models/PasswordTest.swift | 46 ++++- passKitTests/Parser/TokenBuilderTest.swift | 214 +++++++++++++++++++++ 6 files changed, 363 insertions(+), 79 deletions(-) create mode 100644 passKit/Parser/TokenBuilder.swift create mode 100644 passKitTests/Parser/TokenBuilderTest.swift diff --git a/pass.xcodeproj/project.pbxproj b/pass.xcodeproj/project.pbxproj index 0412134..5c90136 100644 --- a/pass.xcodeproj/project.pbxproj +++ b/pass.xcodeproj/project.pbxproj @@ -17,9 +17,11 @@ 302E85632125EE550031BA64 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 302E85622125EE550031BA64 /* Constants.swift */; }; 30A1D29C21AF451E00E2D1F7 /* PasswordGeneratorFlavourTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A1D29B21AF451E00E2D1F7 /* PasswordGeneratorFlavourTest.swift */; }; 30A1D29E21AF468F00E2D1F7 /* PasswordGeneratorFlavour.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A1D29D21AF468E00E2D1F7 /* PasswordGeneratorFlavour.swift */; }; + 30A1D2A221B2BC6F00E2D1F7 /* TokenBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A1D2A121B2BC6F00E2D1F7 /* TokenBuilder.swift */; }; 30A1D2A621B2D46100E2D1F7 /* OtpType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A1D2A521B2D46100E2D1F7 /* OtpType.swift */; }; 30A1D2A821B2D53200E2D1F7 /* PasswordChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A1D2A721B2D53200E2D1F7 /* PasswordChange.swift */; }; 30A1D2AA21B32A0100E2D1F7 /* OtpTypeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A1D2A921B32A0100E2D1F7 /* OtpTypeTest.swift */; }; + 30A1D2AC21B32C2A00E2D1F7 /* TokenBuilderTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A1D2AB21B32C2A00E2D1F7 /* TokenBuilderTest.swift */; }; 30B04860209A5141001013CA /* PasswordTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30B0485F209A5141001013CA /* PasswordTest.swift */; }; 30FD2F78214D9E0E005E0A92 /* ParserTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30FD2F77214D9E0E005E0A92 /* ParserTest.swift */; }; 61326CDA7A73757FB68DCB04 /* Pods_passKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DAB3F5541E51ADC8C6B56642 /* Pods_passKit.framework */; }; @@ -194,9 +196,11 @@ 302E85622125EE550031BA64 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 30A1D29B21AF451E00E2D1F7 /* PasswordGeneratorFlavourTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordGeneratorFlavourTest.swift; sourceTree = ""; }; 30A1D29D21AF468E00E2D1F7 /* PasswordGeneratorFlavour.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PasswordGeneratorFlavour.swift; path = Helpers/PasswordGeneratorFlavour.swift; sourceTree = ""; }; + 30A1D2A121B2BC6F00E2D1F7 /* TokenBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenBuilder.swift; sourceTree = ""; }; 30A1D2A521B2D46100E2D1F7 /* OtpType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OtpType.swift; sourceTree = ""; }; 30A1D2A721B2D53200E2D1F7 /* PasswordChange.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasswordChange.swift; sourceTree = ""; }; 30A1D2A921B32A0100E2D1F7 /* OtpTypeTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OtpTypeTest.swift; sourceTree = ""; }; + 30A1D2AB21B32C2A00E2D1F7 /* TokenBuilderTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenBuilderTest.swift; sourceTree = ""; }; 30B0485F209A5141001013CA /* PasswordTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordTest.swift; sourceTree = ""; }; 30FD2F77214D9E0E005E0A92 /* ParserTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParserTest.swift; sourceTree = ""; }; 31C3033E8868D05B2C55C8B1 /* Pods-passExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-passExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-passExtension/Pods-passExtension.debug.xcconfig"; sourceTree = ""; }; @@ -400,6 +404,7 @@ 30A1D2A521B2D46100E2D1F7 /* OtpType.swift */, 302E85602125ECC70031BA64 /* Parser.swift */, 30A1D2A721B2D53200E2D1F7 /* PasswordChange.swift */, + 30A1D2A121B2BC6F00E2D1F7 /* TokenBuilder.swift */, ); path = Parser; sourceTree = ""; @@ -411,6 +416,7 @@ 301F6467216165290071A4CE /* ConstantsTest.swift */, 30A1D2A921B32A0100E2D1F7 /* OtpTypeTest.swift */, 30FD2F77214D9E0E005E0A92 /* ParserTest.swift */, + 30A1D2AB21B32C2A00E2D1F7 /* TokenBuilderTest.swift */, ); path = Parser; sourceTree = ""; @@ -1152,6 +1158,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 30A1D2A221B2BC6F00E2D1F7 /* TokenBuilder.swift in Sources */, A2BEC1BB207D2EFE00F3051C /* UIViewExtension.swift in Sources */, 302E85632125EE550031BA64 /* Constants.swift in Sources */, 301F6463216162550071A4CE /* AdditionField.swift in Sources */, @@ -1183,6 +1190,7 @@ buildActionMask = 2147483647; files = ( 301F646A216166000071A4CE /* StringExtensionTest.swift in Sources */, + 30A1D2AC21B32C2A00E2D1F7 /* TokenBuilderTest.swift in Sources */, 301F646D216166AA0071A4CE /* AdditionFieldTest.swift in Sources */, 30FD2F78214D9E0E005E0A92 /* ParserTest.swift in Sources */, 30B04860209A5141001013CA /* PasswordTest.swift in Sources */, diff --git a/passKit/Models/Password.swift b/passKit/Models/Password.swift index 65f1f9d..bfb9b0b 100644 --- a/passKit/Models/Password.swift +++ b/passKit/Models/Password.swift @@ -143,84 +143,26 @@ public class Password { */ private func updateOtpToken() { - self.otpToken = nil - // get otpauth, if we are able to generate a token, return if var otpauthString = getAdditionValue(withKey: Constants.OTPAUTH, caseSensitive: true) { if !otpauthString.hasPrefix("\(Constants.OTPAUTH):") { otpauthString = "\(Constants.OTPAUTH):\(otpauthString)" } - if let otpauthUrl = URL(string: otpauthString), - let token = Token(url: otpauthUrl) { - self.otpToken = token + if let otpauthUrl = URL(string: otpauthString), let token = Token(url: otpauthUrl) { + otpToken = token return } } - // get secret data - guard let secretString = getAdditionValue(withKey: Constants.OTP_SECRET), - let secretData = MF_Base32Codec.data(fromBase32String: secretString), - !secretData.isEmpty else { - // Missing / Invalid otp secret - return - } - - // get type - guard let type = getAdditionValue(withKey: Constants.OTP_TYPE)?.lowercased(), - (type == Constants.TOTP || type == Constants.HOTP) else { - // Missing/Invalid OTP type - return - } - - // get algorithm (optional) - var algorithm = Generator.Algorithm.sha1 - if let algoString = getAdditionValue(withKey: Constants.OTP_ALGORITHM) { - switch algoString.lowercased() { - case Constants.SHA256: - algorithm = .sha256 - case Constants.SHA512: - algorithm = .sha512 - default: - algorithm = .sha1 - } - } - - // construct the token - if type == Constants.TOTP { - // HOTP - // default: 6 digits, 30 seconds - guard let digits = Int(getAdditionValue(withKey: Constants.OTP_DIGITS) ?? Constants.DEFAULT_DIGITS), - let period = Double(getAdditionValue(withKey: Constants.OTP_PERIOD) ?? Constants.DEFAULT_PERIOD) else { - // Invalid OTP digits or OTP period. - return - } - guard let generator = Generator( - factor: .timer(period: period), - secret: secretData, - algorithm: algorithm, - digits: digits) else { - // Invalid OTP generator parameters. - return - } - self.otpToken = Token(name: self.name, issuer: "", generator: generator) - } else { - // HOTP - // default: 6 digits - guard let digits = Int(getAdditionValue(withKey: Constants.OTP_DIGITS) ?? Constants.DEFAULT_DIGITS), - let counter = UInt64(getAdditionValue(withKey: Constants.OTP_COUNTER) ?? Constants.DEFAULT_COUNTER) else { - // Invalid OTP digits or OTP counter. - return - } - guard let generator = Generator( - factor: .counter(counter), - secret: secretData, - algorithm: algorithm, - digits: digits) else { - // Invalid OTP generator parameters. - return - } - self.otpToken = Token(name: self.name, issuer: "", generator: generator) - } + otpToken = TokenBuilder() + .usingName(name) + .usingSecret(getAdditionValue(withKey: Constants.OTP_SECRET)) + .usingType(getAdditionValue(withKey: Constants.OTP_TYPE)) + .usingAlgorithm(getAdditionValue(withKey: Constants.OTP_ALGORITHM)) + .usingDigits(getAdditionValue(withKey: Constants.OTP_DIGITS)) + .usingPeriod(getAdditionValue(withKey: Constants.OTP_PERIOD)) + .usingCounter(getAdditionValue(withKey: Constants.OTP_COUNTER)) + .build() } // return the description and the password strings diff --git a/passKit/Parser/Constants.swift b/passKit/Parser/Constants.swift index 96cfd19..c896bd9 100644 --- a/passKit/Parser/Constants.swift +++ b/passKit/Parser/Constants.swift @@ -30,9 +30,9 @@ public struct Constants { static let HOTP = "hotp" static let SHA256 = "sha256" static let SHA512 = "sha512" - static let DEFAULT_DIGITS = "6" - static let DEFAULT_PERIOD = "30.0" - static let DEFAULT_COUNTER = "" + static let DEFAULT_DIGITS = 6 + static let DEFAULT_PERIOD = 30.0 + static let DEFAULT_COUNTER: UInt64? = nil static let BLANK = " " static let MULTILINE_WITH_LINE_BREAK_INDICATOR = "|" diff --git a/passKit/Parser/TokenBuilder.swift b/passKit/Parser/TokenBuilder.swift new file mode 100644 index 0000000..c577ec1 --- /dev/null +++ b/passKit/Parser/TokenBuilder.swift @@ -0,0 +1,88 @@ +// +// TokenBuilder.swift +// passKit +// +// Created by Danny Moesch on 01.12.18. +// Copyright © 2018 Bob Sun. All rights reserved. +// + +import Base32 +import OneTimePassword + +class TokenBuilder { + + private var name: String = "" + private var secret: Data? + private var type: OtpType = .totp + private var algorithm: Generator.Algorithm = .sha1 + private var digits: Int? = Constants.DEFAULT_DIGITS + private var period: Double? = Constants.DEFAULT_PERIOD + private var counter: UInt64? = Constants.DEFAULT_COUNTER + + func usingName(_ name: String) -> TokenBuilder { + self.name = name + return self + } + + func usingSecret(_ secret: String?) -> TokenBuilder { + if secret != nil, let secretData = MF_Base32Codec.data(fromBase32String: secret!), !secretData.isEmpty { + self.secret = secretData + } + return self + } + + func usingType(_ type: String?) -> TokenBuilder { + self.type = OtpType(name: type) + return self + } + + func usingAlgorithm(_ algorithm: String?) -> TokenBuilder { + switch algorithm?.lowercased() { + case Constants.SHA256: + self.algorithm = .sha256 + case Constants.SHA512: + self.algorithm = .sha512 + default: + self.algorithm = .sha1 + } + return self + } + + func usingDigits(_ digits: String?) -> TokenBuilder { + self.digits = digits == nil ? nil : Int(digits!) + return self + } + + func usingPeriod(_ period: String?) -> TokenBuilder { + self.period = period == nil ? nil : Double(period!) + return self + } + + func usingCounter(_ counter: String?) -> TokenBuilder { + self.counter = counter == nil ? nil : UInt64(counter!) + return self + } + + + func build() -> Token? { + guard secret != nil, digits != nil else { + return nil + } + + switch type { + case .totp: + return period == nil ? nil : createToken(factor: Generator.Factor.timer(period: period!)) + case .hotp: + return counter == nil ? nil : createToken(factor: Generator.Factor.counter(counter!)) + default: + return nil + } + } + + private func createToken(factor: Generator.Factor) -> Token? { + guard let generator = Generator(factor: factor, secret: secret!, algorithm: algorithm, digits: digits!) else { + return nil + } + return Token(name: name, issuer: "", generator: generator) + } +} diff --git a/passKitTests/Models/PasswordTest.swift b/passKitTests/Models/PasswordTest.swift index bd34b0a..1181ad8 100644 --- a/passKitTests/Models/PasswordTest.swift +++ b/passKitTests/Models/PasswordTest.swift @@ -15,7 +15,8 @@ class PasswordTest: XCTestCase { private let PASSWORD_PATH = "/path/to/password" private let PASSWORD_URL = URL(fileURLWithPath: "/path/to/password") private let PASSWORD_STRING = "abcd1234" - private let OTP_TOKEN = "otpauth://totp/email@email.com?secret=abcd1234" + private let TOTP_URL = "otpauth://totp/email@email.com?secret=abcd1234" + private let HOTP_URL = "otpauth://hotp/email@email.com?secret=abcd1234" private let SECURE_URL_FIELD = "url" => "https://secure.com" private let INSECURE_URL_FIELD = "url" => "http://insecure.com" @@ -23,6 +24,7 @@ class PasswordTest: XCTestCase { private let USERNAME_FIELD = "username" => "some username" private let NOTE_FIELD = "note" => "A NOTE" private let HINT_FIELD = "some hints" => "äöüß // €³ %% −° && @²` | [{\\}],.<>" + private let TOTP_URL_FIELD = "otpauth" => "//totp/email@email.com?secret=abcd1234" func testUrl() { let password = getPasswordObjectWith(content: "") @@ -158,7 +160,7 @@ class PasswordTest: XCTestCase { } func testPasswordFileWithOtpToken() { - let additions = NOTE_FIELD | OTP_TOKEN + let additions = NOTE_FIELD | TOTP_URL let fileContent = PASSWORD_STRING | additions let password = getPasswordObjectWith(content: fileContent) @@ -166,22 +168,52 @@ class PasswordTest: XCTestCase { XCTAssertEqual(password.plainData, fileContent.data(using: .utf8)) XCTAssertEqual(password.additionsPlainText, additions) - XCTAssertEqual(password.otpType, OtpType.totp) + XCTAssertEqual(password.otpType, .totp) XCTAssertNotNil(password.currentOtp) } func testFirstLineIsOtpToken() { - let password = getPasswordObjectWith(content: OTP_TOKEN) + let password = getPasswordObjectWith(content: TOTP_URL) - XCTAssertEqual(password.password, OTP_TOKEN) - XCTAssertEqual(password.plainData, OTP_TOKEN.data(using: .utf8)) + XCTAssertEqual(password.password, TOTP_URL) + XCTAssertEqual(password.plainData, TOTP_URL.data(using: .utf8)) XCTAssertEqual(password.additionsPlainText, "") XCTAssertNil(password.username) XCTAssertNil(password.urlString) XCTAssertNil(password.login) - XCTAssertEqual(password.otpType, OtpType.totp) + XCTAssertEqual(password.otpType, .totp) + XCTAssertNotNil(password.currentOtp) + } + + func testOtpTokenAsField() { + let additions = TOTP_URL_FIELD.asString + let fileContent = PASSWORD_STRING | additions + let password = getPasswordObjectWith(content: fileContent) + + XCTAssertEqual(password.password, PASSWORD_STRING) + XCTAssertEqual(password.plainData, fileContent.data(using: .utf8)) + XCTAssertEqual(password.additionsPlainText, additions) + + XCTAssertEqual(password.otpType, .totp) + XCTAssertNotNil(password.currentOtp) + } + + func testOtpTokenFromFields() { + let additions = + Constants.OTP_SECRET => "secret" | + Constants.OTP_TYPE => "hotp" | + Constants.OTP_COUNTER => "12" | + Constants.OTP_DIGITS => "7" + let fileContent = PASSWORD_STRING | additions + let password = getPasswordObjectWith(content: fileContent) + + XCTAssertEqual(password.password, PASSWORD_STRING) + XCTAssertEqual(password.plainData, fileContent.data(using: .utf8)) + XCTAssertEqual(password.additionsPlainText, additions) + + XCTAssertEqual(password.otpType, .hotp) XCTAssertNotNil(password.currentOtp) } diff --git a/passKitTests/Parser/TokenBuilderTest.swift b/passKitTests/Parser/TokenBuilderTest.swift new file mode 100644 index 0000000..d072641 --- /dev/null +++ b/passKitTests/Parser/TokenBuilderTest.swift @@ -0,0 +1,214 @@ +// +// TokenBuilderTest.swift +// passKitTests +// +// Created by Danny Moesch on 01.12.18. +// Copyright © 2018 Bob Sun. All rights reserved. +// + +import Base32 +import OneTimePassword +import XCTest + +@testable import passKit + +class TokenBuilderTest: XCTestCase { + + private let SECRET = "secret" + private let DIGITS = Constants.DEFAULT_DIGITS + private let TIMER = Generator.Factor.timer(period: Constants.DEFAULT_PERIOD) + + func testNoSecret() { + XCTAssertNil(TokenBuilder().build()) + XCTAssertNil(TokenBuilder().usingSecret(nil).build()) + XCTAssertNil(TokenBuilder().usingSecret("").build()) + } + + func testDefault() { + let token = TokenBuilder() + .usingSecret(SECRET) + .build() + + XCTAssertEqual(token?.generator.secret, MF_Base32Codec.data(fromBase32String: SECRET)) + XCTAssertEqual(token?.generator.factor, TIMER) + XCTAssertEqual(token?.generator.algorithm, .sha1) + XCTAssertEqual(token?.generator.digits, DIGITS) + } + + func testName() { + [ + "some name", + "a", + "totp", + ].forEach { name in + let token = TokenBuilder() + .usingName(name) + .usingSecret(SECRET) + .build() + + XCTAssertEqual(token?.name, name) + } + } + + func testTypeNone() { + let token = TokenBuilder() + .usingSecret(SECRET) + .usingType("something") + .build() + + XCTAssertNil(token) + } + + func testTypeTotp() { + let token = TokenBuilder() + .usingSecret(SECRET) + .usingType("toTp") + .build() + + XCTAssertEqual(token?.generator.factor, TIMER) + } + + func testTypeHotp() { + let token = TokenBuilder() + .usingSecret(SECRET) + .usingType("HotP") + .usingCounter("4") + .build() + + XCTAssertEqual(token?.generator.factor, Generator.Factor.counter(4)) + } + + func testAlgorithm() { + [ + ("sha1", .sha1), + ("something", .sha1), + (nil, .sha1), + ("sha256", .sha256), + ("Sha256", .sha256), + ("sha512", .sha512), + ("sHA512", .sha512), + ].forEach { (inputAlgorithm: String?, algorithm: Generator.Algorithm) in + let token = TokenBuilder() + .usingSecret(SECRET) + .usingAlgorithm(inputAlgorithm) + .build() + + XCTAssertEqual(token?.generator.algorithm, algorithm) + } + } + + func testDigits() { + [ + (nil, nil), + (5, nil), + (6, 6), + (7, 7), + (8, 8), + (9, nil), + ].forEach { inputDigits, digits in + let token = TokenBuilder() + .usingSecret(SECRET) + .usingDigits(inputDigits == nil ? nil : String(inputDigits!)) + .build() + + XCTAssertEqual(token?.generator.digits, digits) + } + } + + func testUnparsableDigits() { + let token = TokenBuilder() + .usingSecret(SECRET) + .usingDigits("unparsable digits") + .build() + + XCTAssertNil(token) + } + + func testPeriod() { + [ + (nil, nil), + (1.2, 1.2), + (-12.0, nil), + (27.5, 27.5), + (35.0, 35.0), + (120.7, 120.7), + ].forEach { inputPeriod, period in + let token = TokenBuilder() + .usingSecret(SECRET) + .usingPeriod(inputPeriod == nil ? nil : String(inputPeriod!)) + .build() + let timer = period == nil ? nil : Generator.Factor.timer(period: period!) + + XCTAssertEqual(token?.generator.factor, timer) + } + } + + func testUnparsablePeriod() { + let token = TokenBuilder() + .usingSecret(SECRET) + .usingPeriod("unparsable period") + .build() + + XCTAssertNil(token) + } + + func testCounter() { + [ + (nil, nil), + (1, 1), + (0, 0), + (27, 27), + (120, 120), + (4321, 4321), + ].forEach { inputCounter, counter in + let token = TokenBuilder() + .usingSecret(SECRET) + .usingType("hotp") + .usingCounter(inputCounter == nil ? nil : String(inputCounter!)) + .build() + let counter = counter == nil ? nil : Generator.Factor.counter(UInt64(counter!)) + + XCTAssertEqual(token?.generator.factor, counter) + } + } + + func testUnparsableCounter() { + let token = TokenBuilder() + .usingSecret(SECRET) + .usingType("hotp") + .usingCounter("unparsable counter") + .build() + + XCTAssertNil(token) + } + + func testAllMixed() { + let builder = TokenBuilder() + .usingName("name") + .usingSecret(SECRET) + .usingAlgorithm("sha512") + .usingDigits("7") + .usingPeriod("42") + .usingCounter("12") + + let totpToken = builder.usingType("totp").build() + + XCTAssertNotNil(totpToken) + XCTAssertEqual(totpToken?.name, "name") + XCTAssertEqual(totpToken?.currentPassword?.count, 7) + XCTAssertEqual(totpToken?.generator.algorithm, .sha512) + XCTAssertEqual(totpToken?.generator.digits, 7) + XCTAssertEqual(totpToken?.generator.factor, .timer(period: 42)) + XCTAssertEqual(totpToken?.generator.secret, MF_Base32Codec.data(fromBase32String: SECRET)) + + let hotpToken = builder.usingType("hotp").build() + + XCTAssertNotNil(hotpToken) + XCTAssertEqual(hotpToken?.name, "name") + XCTAssertEqual(hotpToken?.currentPassword?.count, 7) + XCTAssertEqual(hotpToken?.generator.algorithm, .sha512) + XCTAssertEqual(hotpToken?.generator.digits, 7) + XCTAssertEqual(hotpToken?.generator.factor, .counter(12)) + XCTAssertEqual(hotpToken?.generator.secret, MF_Base32Codec.data(fromBase32String: SECRET)) + } +}