Skip to content

Commit

Permalink
Merge pull request #30 from bitmark-inc/Sang/security_audit/1_biometric
Browse files Browse the repository at this point in the history
fix(security_audit): 1_ Biometric is not event-bound
  • Loading branch information
ppupha authored Apr 26, 2024
2 parents 686289e + 42d39b5 commit eeba055
Show file tree
Hide file tree
Showing 10 changed files with 487 additions and 317 deletions.
1 change: 1 addition & 0 deletions Sources/LibAuk/Constant.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ struct Constant {

static let seed = "seed"
static let ethInfoKey = "ethInfo"
static let seedPublicData = "seedPublicData"
}
}
3 changes: 3 additions & 0 deletions Sources/LibAuk/LibAukError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public enum LibAukError: Error {
case emptyKey
case keyCreationExistingError(key: String)
case keyDerivationError
case generateSeedPublicDataError
case other(reason: String)
}

Expand Down Expand Up @@ -44,6 +45,8 @@ extension LibAukError: LocalizedError {
return "create key error: key exists"
case .keyDerivationError:
return "key derivation error"
case .generateSeedPublicDataError:
return "generate seed public data error"
case .other(let reason):
return reason
}
Expand Down
60 changes: 59 additions & 1 deletion Sources/LibAuk/Model/KeyInfo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,65 @@

import Foundation

public struct KeyInfo: Codable {
public struct KeyInfo {
let fingerprint, ethAddress: String
let creationDate: Date
}

public struct SeedPublicData: Codable {
let ethAddress: String
let creationDate: Date
let name: String?
let did: String
let preGenerateEthAddress: [Int: String]
let tezosPublicKeys: [Int: String]
var _encryptionPrivateKeyBase64: String? = nil
var _accountDIDPrivateKeyBase64: String? = nil

var encryptionPrivateKey: Secp256k1.Signing.PrivateKey? {
get {
guard let base64String = _encryptionPrivateKeyBase64,
let data = Data(base64Encoded: base64String) else {
fatalError("Invalid base64 string for private key")
}
do {
return try Secp256k1.Signing.PrivateKey(rawRepresentation: data)
} catch {
fatalError("Failed to initialize private key: \(error)")
}
}

set {
if let newValue = newValue {
let privateKeyData = newValue.rawRepresentation
_encryptionPrivateKeyBase64 = privateKeyData.base64EncodedString()
} else {
_encryptionPrivateKeyBase64 = nil
}
}
}

var accountDIDPrivateKey: Secp256k1.Signing.PrivateKey? {
get {
guard let base64String = _accountDIDPrivateKeyBase64,
let data = Data(base64Encoded: base64String) else {
fatalError("Invalid base64 string for private key")
}
do {
return try Secp256k1.Signing.PrivateKey(rawRepresentation: data)
} catch {
fatalError("Failed to initialize private key: \(error)")
}
}

set {
if let newValue = newValue {
let privateKeyData = newValue.rawRepresentation
_accountDIDPrivateKeyBase64 = privateKeyData.base64EncodedString()
} else {
_accountDIDPrivateKeyBase64 = nil
}
}
}

}
8 changes: 4 additions & 4 deletions Sources/LibAuk/Model/Seed.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,26 +49,26 @@ public class Seed: Codable {
UREncoder.encode(ur)
}

convenience init(urString: String) throws {
public convenience init(urString: String) throws {
let ur = try URDecoder.decode(urString)
try self.init(ur: ur)
}

convenience init(ur: UR) throws {
public convenience init(ur: UR) throws {
guard ur.type == "crypto-seed" else {
throw LibAukError.other(reason: "Unexpected UR type.")
}
try self.init(cborData: ur.cbor)
}

convenience init(cborData: Data) throws {
public convenience init(cborData: Data) throws {
guard let cbor = try? CBOR(cborData) else {
throw LibAukError.other(reason: "ur:crypto-seed: Invalid CBOR.")
}
try self.init(cbor: cbor)
}

convenience init(cbor: CBOR) throws {
public convenience init(cbor: CBOR) throws {
guard case let CBOR.orderedMap(orderedMap) = cbor else {
throw LibAukError.other(reason: "ur:crypto-seed: CBOR doesn't contain a map.")
}
Expand Down
32 changes: 32 additions & 0 deletions Sources/LibAuk/Storage/AccessControl.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//
// AccessControl.swift
//
//
// Created by Nguyen Phuoc Sang on 27/03/2024.
//

import Foundation
import LocalAuthentication


class AccessControl {

// MARK: - Singleton
public static let shared = AccessControl()

// Policy
private var policy: LAPolicy = .deviceOwnerAuthentication

var accessible: CFString = kSecAttrAccessibleWhenUnlocked

// Reason
var reason: String = NSLocalizedString("Access your password on the keychain", comment: "")

// Context
lazy var context: LAContext = {
let mainContext = LAContext()
mainContext.touchIDAuthenticationAllowableReuseDuration = Double(5)
mainContext.localizedReason = reason
return mainContext
}()
}
37 changes: 27 additions & 10 deletions Sources/LibAuk/Storage/Keychain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
//

import Foundation
import LocalAuthentication

protocol KeychainProtocol {
@discardableResult
func set(_ data: Data, forKey: String, isSync: Bool) -> Bool
func set(_ data: Data, forKey: String, isSync: Bool, isPrivate: Bool) -> Bool
func getData(_ key: String, isSync: Bool) -> Data?
@discardableResult
func remove(key: String, isSync: Bool) -> Bool
Expand All @@ -25,16 +26,31 @@ class Keychain: KeychainProtocol {
}

@discardableResult
func set(_ data: Data, forKey: String, isSync: Bool = true) -> Bool {
func set(_ data: Data, forKey: String, isSync: Bool = true, isPrivate: Bool) -> Bool {
let syncAttr = isSync ? kCFBooleanTrue : kCFBooleanFalse
let query = [
var error: Unmanaged<CFError>?
var accessControl: SecAccessControl?
if (isPrivate) {
accessControl = SecAccessControlCreateWithFlags(
nil, // Use the default allocator.
AccessControl.shared.accessible,
[.biometryCurrentSet, .or, .devicePasscode],
&error)
}

var query = [
kSecClass as String: kSecClassGenericPassword as String,
kSecAttrSynchronizable as String: syncAttr!,
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock,
kSecAttrAccessGroup as String: LibAuk.shared.keyChainGroup,
kSecAttrAccount as String: buildKeyAttr(prefix: prefix, key: forKey),
kSecValueData as String: data
kSecAttrSynchronizable as String: syncAttr,
kSecValueData as String: data,
] as [String: Any]

if let access = accessControl {
query[kSecAttrAccessControl as String] = access
} else {
query[kSecAttrAccessible as String] = AccessControl.shared.accessible
}

SecItemDelete(query as CFDictionary)

Expand All @@ -49,14 +65,15 @@ class Keychain: KeychainProtocol {

func getData(_ key: String, isSync: Bool = true) -> Data? {
let syncAttr = isSync ? kCFBooleanTrue : kCFBooleanFalse
let context = AccessControl.shared.context
let query = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrSynchronizable as String: syncAttr!,
kSecAttrAccount as String: buildKeyAttr(prefix: prefix, key: key),
kSecReturnData as String: kCFBooleanTrue!,
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock,
kSecAttrAccessGroup as String: LibAuk.shared.keyChainGroup,
kSecMatchLimit as String: kSecMatchLimitOne
kSecAttrAccessible as String: AccessControl.shared.accessible,
kSecMatchLimit as String: kSecMatchLimitOne,
] as [String: Any]

var dataTypeRef: AnyObject?
Expand All @@ -76,9 +93,9 @@ class Keychain: KeychainProtocol {
let query = [
kSecClass as String: kSecClassGenericPassword as String,
kSecAttrSynchronizable as String: syncAttr!,
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock,
kSecAttrAccessGroup as String: LibAuk.shared.keyChainGroup,
kSecAttrAccount as String: buildKeyAttr(prefix: prefix, key: key)
kSecAttrAccount as String: buildKeyAttr(prefix: prefix, key: key),
kSecAttrAccessible as String: AccessControl.shared.accessible,
] as [String: Any]

// Delete any existing items
Expand Down
Loading

0 comments on commit eeba055

Please sign in to comment.