Introduce TokenBuilder to build up OTP tokens conveniently

This commit is contained in:
Danny Moesch 2018-12-01 15:53:48 +01:00 committed by Bob Sun
parent 6817f61e3b
commit 2e744a760f
6 changed files with 363 additions and 79 deletions

View file

@ -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

View file

@ -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 = "|"

View file

@ -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)
}
}