Skip to content

Commit

Permalink
Merge pull request eu-digital-green-certificates#25 from eu-digital-g…
Browse files Browse the repository at this point in the history
…reen-certificates/feature/Secure-Local-Storage

Feature/secure local storage
  • Loading branch information
ascheibal authored Apr 27, 2021
2 parents dc7ce25 + 216ede3 commit 3a16257
Show file tree
Hide file tree
Showing 17 changed files with 657 additions and 93 deletions.
37 changes: 37 additions & 0 deletions DGCAVerifier.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,20 @@
CE157F8D262E24F900FE4821 /* HCert.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE157F8C262E24F900FE4821 /* HCert.swift */; };
CE157F9B262E2A9F00FE4821 /* SwiftCBOR.CBOR.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE157F9A262E2A9F00FE4821 /* SwiftCBOR.CBOR.swift */; };
CE1BDF99262A4CD600766F97 /* X509.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE1BDF98262A4CD600766F97 /* X509.swift */; };
CE1D1EF6263597A2004C8919 /* LocalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE1D1EF5263597A2004C8919 /* LocalData.swift */; };
CE3CC93C2628A7820079FB78 /* ASN1.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE3CC93B2628A7820079FB78 /* ASN1.swift */; };
CE3CC9442628C2130079FB78 /* CBOR.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE3CC9432628C2130079FB78 /* CBOR.swift */; };
CE44798D26304D8F009A836B /* JSONSchema in Frameworks */ = {isa = PBXBuildFile; productRef = CE44798C26304D8F009A836B /* JSONSchema */; };
CE44799226306C86009A836B /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE44799126306C86009A836B /* String.swift */; };
CE44799726306C9B009A836B /* Data+Base45.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE44799626306C9B009A836B /* Data+Base45.swift */; };
CE582DC12635AE5F008F35D7 /* SecureStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE582DC02635AE5F008F35D7 /* SecureStorage.swift */; };
CE7DE7FA2625EF18007E6694 /* SwiftCBOR in Frameworks */ = {isa = PBXBuildFile; productRef = CE7DE7F92625EF18007E6694 /* SwiftCBOR */; };
CE8912E526321AA500CB92AF /* KID.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8912E426321AA500CB92AF /* KID.swift */; };
CE8912EA26321DAA00CB92AF /* SHA256.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8912E926321DAA00CB92AF /* SHA256.swift */; };
CE8912F52634C60E00CB92AF /* GatewayConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8912F42634C60E00CB92AF /* GatewayConnection.swift */; };
CE8912FB2634C6B900CB92AF /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = CE8912FA2634C6B900CB92AF /* Alamofire */; };
CE891300263570CF00CB92AF /* Enclave.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8912FF263570CF00CB92AF /* Enclave.swift */; };
CE891305263581D900CB92AF /* Home.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE891304263581D900CB92AF /* Home.swift */; };
CEA1555D262F63B30024B7AC /* EuDgcSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEA1555C262F63B30024B7AC /* EuDgcSchema.swift */; };
CEA15563262F6DAB0024B7AC /* ChildDismissedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEA15562262F6DAB0024B7AC /* ChildDismissedDelegate.swift */; };
CEA1556B262F784E0024B7AC /* SelfSizedTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEA1556A262F784E0024B7AC /* SelfSizedTableView.swift */; };
Expand Down Expand Up @@ -73,12 +79,17 @@
CE157F8C262E24F900FE4821 /* HCert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HCert.swift; sourceTree = "<group>"; };
CE157F9A262E2A9F00FE4821 /* SwiftCBOR.CBOR.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftCBOR.CBOR.swift; sourceTree = "<group>"; };
CE1BDF98262A4CD600766F97 /* X509.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = X509.swift; sourceTree = "<group>"; };
CE1D1EF5263597A2004C8919 /* LocalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalData.swift; sourceTree = "<group>"; };
CE3CC93B2628A7820079FB78 /* ASN1.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ASN1.swift; sourceTree = "<group>"; };
CE3CC9432628C2130079FB78 /* CBOR.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CBOR.swift; sourceTree = "<group>"; };
CE44799126306C86009A836B /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = "<group>"; };
CE44799626306C9B009A836B /* Data+Base45.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Base45.swift"; sourceTree = "<group>"; };
CE582DC02635AE5F008F35D7 /* SecureStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureStorage.swift; sourceTree = "<group>"; };
CE8912E426321AA500CB92AF /* KID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KID.swift; sourceTree = "<group>"; };
CE8912E926321DAA00CB92AF /* SHA256.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SHA256.swift; sourceTree = "<group>"; };
CE8912F42634C60E00CB92AF /* GatewayConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GatewayConnection.swift; sourceTree = "<group>"; };
CE8912FF263570CF00CB92AF /* Enclave.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Enclave.swift; sourceTree = "<group>"; };
CE891304263581D900CB92AF /* Home.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Home.swift; sourceTree = "<group>"; };
CEA1555C262F63B30024B7AC /* EuDgcSchema.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EuDgcSchema.swift; sourceTree = "<group>"; };
CEA15562262F6DAB0024B7AC /* ChildDismissedDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChildDismissedDelegate.swift; sourceTree = "<group>"; };
CEA1556A262F784E0024B7AC /* SelfSizedTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelfSizedTableView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -112,6 +123,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
CE8912FB2634C6B900CB92AF /* Alamofire in Frameworks */,
CE157F87262E24DE00FE4821 /* SwiftyJSON in Frameworks */,
CE44798D26304D8F009A836B /* JSONSchema in Frameworks */,
CE7DE7FA2625EF18007E6694 /* SwiftCBOR in Frameworks */,
Expand Down Expand Up @@ -159,6 +171,9 @@
CE1BDF98262A4CD600766F97 /* X509.swift */,
CE8912E426321AA500CB92AF /* KID.swift */,
CE8912E926321DAA00CB92AF /* SHA256.swift */,
CE8912F42634C60E00CB92AF /* GatewayConnection.swift */,
CE8912FF263570CF00CB92AF /* Enclave.swift */,
CE582DC02635AE5F008F35D7 /* SecureStorage.swift */,
);
path = Services;
sourceTree = "<group>";
Expand All @@ -181,6 +196,7 @@
children = (
CE13CF09262DCDDA0070C80E /* CertificateViewer.swift */,
CEA6D6EF261F8D2700715333 /* Scan.swift */,
CE891304263581D900CB92AF /* Home.swift */,
);
path = ViewControllers;
sourceTree = "<group>";
Expand Down Expand Up @@ -212,6 +228,7 @@
isa = PBXGroup;
children = (
CE157F8C262E24F900FE4821 /* HCert.swift */,
CE1D1EF5263597A2004C8919 /* LocalData.swift */,
);
path = Models;
sourceTree = "<group>";
Expand Down Expand Up @@ -299,6 +316,7 @@
CE13CEFF262DCC180070C80E /* FloatingPanel */,
CE157F86262E24DE00FE4821 /* SwiftyJSON */,
CE44798C26304D8F009A836B /* JSONSchema */,
CE8912FA2634C6B900CB92AF /* Alamofire */,
);
productName = DGCAVerifier;
productReference = CEA6D6E8261F8D2700715333 /* DGCAVerifier.app */;
Expand Down Expand Up @@ -376,6 +394,7 @@
CE13CEFE262DCC180070C80E /* XCRemoteSwiftPackageReference "FloatingPanel" */,
CE157F85262E24DE00FE4821 /* XCRemoteSwiftPackageReference "SwiftyJSON" */,
CE44798B26304D8F009A836B /* XCRemoteSwiftPackageReference "JSONSchema" */,
CE8912F92634C6B900CB92AF /* XCRemoteSwiftPackageReference "Alamofire" */,
);
productRefGroup = CEA6D6E9261F8D2700715333 /* Products */;
projectDirPath = "";
Expand Down Expand Up @@ -426,13 +445,17 @@
CE13CF0A262DCDDA0070C80E /* CertificateViewer.swift in Sources */,
CEC2C4C32625ED030056E406 /* JWK.swift in Sources */,
CEC2C4C42625ED030056E406 /* Base45.swift in Sources */,
CE891300263570CF00CB92AF /* Enclave.swift in Sources */,
CE8912EA26321DAA00CB92AF /* SHA256.swift in Sources */,
CE3CC9442628C2130079FB78 /* CBOR.swift in Sources */,
CE44799226306C86009A836B /* String.swift in Sources */,
CE8912F52634C60E00CB92AF /* GatewayConnection.swift in Sources */,
CE582DC12635AE5F008F35D7 /* SecureStorage.swift in Sources */,
CE44799726306C9B009A836B /* Data+Base45.swift in Sources */,
CE13CF0F262DD0D80070C80E /* FullFloatingPanelLayout.swift in Sources */,
CEA1556B262F784E0024B7AC /* SelfSizedTableView.swift in Sources */,
CE157F81262E1F7A00FE4821 /* Date.swift in Sources */,
CE1D1EF6263597A2004C8919 /* LocalData.swift in Sources */,
CE1BDF99262A4CD600766F97 /* X509.swift in Sources */,
CEA1555D262F63B30024B7AC /* EuDgcSchema.swift in Sources */,
CEA6D6F0261F8D2700715333 /* Scan.swift in Sources */,
Expand All @@ -441,6 +464,7 @@
CEFAD86D2625F164009AFEF9 /* Signature.swift in Sources */,
CE13CF23262DDF810070C80E /* RoundedButton.swift in Sources */,
CEA6D6EC261F8D2700715333 /* AppDelegate.swift in Sources */,
CE891305263581D900CB92AF /* Home.swift in Sources */,
CEA15563262F6DAB0024B7AC /* ChildDismissedDelegate.swift in Sources */,
CEFAD8722625F29E009AFEF9 /* String+JSON.swift in Sources */,
CEC2C4C22625ED030056E406 /* ZLib.swift in Sources */,
Expand Down Expand Up @@ -817,6 +841,14 @@
minimumVersion = 0.4.3;
};
};
CE8912F92634C6B900CB92AF /* XCRemoteSwiftPackageReference "Alamofire" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/Alamofire/Alamofire";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 5.4.3;
};
};
/* End XCRemoteSwiftPackageReference section */

/* Begin XCSwiftPackageProductDependency section */
Expand All @@ -840,6 +872,11 @@
package = CE7DE7F82625EF18007E6694 /* XCRemoteSwiftPackageReference "SwiftCBOR" */;
productName = SwiftCBOR;
};
CE8912FA2634C6B900CB92AF /* Alamofire */ = {
isa = XCSwiftPackageProductDependency;
package = CE8912F92634C6B900CB92AF /* XCRemoteSwiftPackageReference "Alamofire" */;
productName = Alamofire;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = CEA6D6E0261F8D2700715333 /* Project object */;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
{
"object": {
"pins": [
{
"package": "Alamofire",
"repositoryURL": "https://github.com/Alamofire/Alamofire",
"state": {
"branch": null,
"revision": "f96b619bcb2383b43d898402283924b80e2c4bae",
"version": "5.4.3"
}
},
{
"package": "FloatingPanel",
"repositoryURL": "https://github.com/SCENEE/FloatingPanel",
Expand Down
49 changes: 49 additions & 0 deletions DGCAVerifier/Models/LocalData.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
/*-
* ---license-start
* eu-digital-green-certificates / dgca-verifier-app-ios
* ---
* Copyright (C) 2021 T-Systems International GmbH and all other contributors
* ---
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ---license-end
*/
//
// LocalData.swift
// DGCAVerifier
//
// Created by Yannick Spreen on 4/25/21.
//


import Foundation

struct LocalData: Codable {
static var sharedInstance = LocalData()

var encodedPublicKeys = [String: String]()
var resumeToken: String?

static func add(encodedPublicKey: String) {
let kid = KID.from(encodedPublicKey)
let kidStr = KID.string(from: kid)

sharedInstance.encodedPublicKeys[kidStr] = encodedPublicKey
}

static func set(resumeToken: String) {
sharedInstance.resumeToken = resumeToken
}

static let storage = SecureStorage<LocalData>()
}
184 changes: 184 additions & 0 deletions DGCAVerifier/Services/Enclave.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
//
/*-
* ---license-start
* eu-digital-green-certificates / dgca-verifier-app-ios
* ---
* Copyright (C) 2021 T-Systems International GmbH and all other contributors
* ---
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ---license-end
*/
//
// Enclave.swift
// PatientScannerDemo
//
// Created by Yannick Spreen on 4/25/21.
//


import Foundation

struct Enclave {
static let encryptAlg = SecKeyAlgorithm.eciesEncryptionCofactorVariableIVX963SHA256AESGCM
static let signAlg = SecKeyAlgorithm.ecdsaSignatureMessageX962SHA512
static let symmetricKey = generateOrLoadKey(with: "symmetricKey")

static func tag(for name: String) -> Data {
"\(Bundle.main.bundleIdentifier ?? "app").\(name)".data(using: .utf8)!
}

static func generateKey(with name: String? = nil) -> (SecKey?, String?) {
let name = name ?? UUID().uuidString
let tag = Enclave.tag(for: name)
var error: Unmanaged<CFError>?
guard
let access =
SecAccessControlCreateWithFlags(
kCFAllocatorDefault,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
[.privateKeyUsage], // , .biometryCurrentSet],
&error
)
else {
return (nil, error?.takeRetainedValue().localizedDescription)
}
var attributes: [String: Any] = [
kSecAttrKeyType as String: kSecAttrKeyTypeEC,
kSecAttrKeySizeInBits as String: 256,
kSecPrivateKeyAttrs as String: [
kSecAttrIsPermanent as String: true,
kSecAttrApplicationTag as String: tag,
kSecAttrAccessControl as String: access
]
]
#if !targetEnvironment(simulator)
attributes[kSecAttrTokenID as String] = kSecAttrTokenIDSecureEnclave
#endif
guard
let privateKey = SecKeyCreateRandomKey(
attributes as CFDictionary,
&error
)
else {
return (nil, error?.takeRetainedValue().localizedDescription)
}
return (privateKey, nil)
}

static func loadKey(with name: String) -> SecKey? {
let tag = Enclave.tag(for: name)
let query: [String: Any] = [
kSecClass as String : kSecClassKey,
kSecAttrKeyType as String : kSecAttrKeyTypeEC,
kSecAttrApplicationTag as String : tag,
kSecReturnRef as String : true
]

var item: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &item)
guard
status == errSecSuccess
else {
return nil
}
return (item as! SecKey)
}

static func generateOrLoadKey(with name: String) -> SecKey? {
if let key = loadKey(with: name) {
return key
}
return generateKey(with: name).0
}

static func encrypt(data: Data, with key: SecKey) -> (Data?, String?) {
guard let publicKey = SecKeyCopyPublicKey(key) else {
return (nil, "Cannot retrieve public key.")
}
guard SecKeyIsAlgorithmSupported(publicKey, .encrypt, encryptAlg) else {
return (nil, "Algorithm not supported.")
}
var error: Unmanaged<CFError>?
let cipherData = SecKeyCreateEncryptedData(
publicKey,
encryptAlg,
data as CFData,
&error
) as Data?
let err = error?.takeRetainedValue().localizedDescription
return (cipherData, err)
}

static func decrypt(data: Data, with key: SecKey, completion: @escaping (Data?, String?) -> Void) {
DispatchQueue.global(qos: .userInitiated).async {
let (result, error) = syncDecrypt(data: data, with: key)
completion(result, error)
}
}

static func syncDecrypt(data: Data, with key: SecKey) -> (Data?, String?) {
guard SecKeyIsAlgorithmSupported(key, .decrypt, encryptAlg) else {
return (nil, "Algorithm not supported.")
}
var error: Unmanaged<CFError>?
let clearData = SecKeyCreateDecryptedData(
key,
encryptAlg,
data as CFData,
&error
) as Data?
let err = error?.takeRetainedValue().localizedDescription
return (clearData, err)
}

static func verify(data: Data, signature: Data, with key: SecKey) -> (Bool, String?) {
guard let publicKey = SecKeyCopyPublicKey(key) else {
return (false, "Cannot retrieve public key.")
}
guard SecKeyIsAlgorithmSupported(publicKey, .verify, signAlg) else {
return (false, "Algorithm not supported.")
}
var error: Unmanaged<CFError>?
let isValid = SecKeyVerifySignature(
publicKey,
signAlg,
data as CFData,
signature as CFData,
&error
)
let err = error?.takeRetainedValue().localizedDescription
return (isValid, err)
}

static func sign(data: Data, with key: SecKey, completion: @escaping (Data?, String?) -> Void) {
DispatchQueue.global(qos: .userInitiated).async {
let (result, error) = syncSign(data: data, with: key)
completion(result, error)
}
}

static func syncSign(data: Data, with key: SecKey) -> (Data?, String?) {
guard SecKeyIsAlgorithmSupported(key, .sign, signAlg) else {
return (nil, "Algorithm not supported.")
}
var error: Unmanaged<CFError>?
let signature = SecKeyCreateSignature(
key,
signAlg,
data as CFData,
&error
) as Data?
let err = error?.takeRetainedValue().localizedDescription
return (signature, err)
}
}
Loading

0 comments on commit 3a16257

Please sign in to comment.