Skip to content

Commit

Permalink
New schema.
Browse files Browse the repository at this point in the history
  • Loading branch information
yspreen committed Apr 28, 2021
1 parent 6a71b07 commit be88037
Show file tree
Hide file tree
Showing 6 changed files with 305 additions and 32 deletions.
12 changes: 12 additions & 0 deletions DGCAVerifier.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@
CEC2C4C32625ED030056E406 /* JWK.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC2C4C02625ED030056E406 /* JWK.swift */; };
CEC2C4C42625ED030056E406 /* Base45.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC2C4C12625ED030056E406 /* Base45.swift */; };
CED2726026398683003D47A9 /* UIFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = CED2725F26398683003D47A9 /* UIFont.swift */; };
CED272652639A1DF003D47A9 /* VaccinationEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = CED272642639A1DF003D47A9 /* VaccinationEntry.swift */; };
CED2726D2639A403003D47A9 /* TestEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = CED2726C2639A403003D47A9 /* TestEntry.swift */; };
CED2727B2639A990003D47A9 /* RecoveryEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = CED2727A2639A990003D47A9 /* RecoveryEntry.swift */; };
CEFAD86D2625F164009AFEF9 /* Signature.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFAD86C2625F164009AFEF9 /* Signature.swift */; };
CEFAD8722625F29E009AFEF9 /* String+JSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFAD8712625F29E009AFEF9 /* String+JSON.swift */; };
CEFAD87A26271414009AFEF9 /* COSE.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFAD87926271414009AFEF9 /* COSE.swift */; };
Expand Down Expand Up @@ -115,6 +118,9 @@
CEC2C4C02625ED030056E406 /* JWK.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JWK.swift; sourceTree = "<group>"; };
CEC2C4C12625ED030056E406 /* Base45.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Base45.swift; sourceTree = "<group>"; };
CED2725F26398683003D47A9 /* UIFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIFont.swift; sourceTree = "<group>"; };
CED272642639A1DF003D47A9 /* VaccinationEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VaccinationEntry.swift; sourceTree = "<group>"; };
CED2726C2639A403003D47A9 /* TestEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestEntry.swift; sourceTree = "<group>"; };
CED2727A2639A990003D47A9 /* RecoveryEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecoveryEntry.swift; sourceTree = "<group>"; };
CEFAD86C2625F164009AFEF9 /* Signature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Signature.swift; sourceTree = "<group>"; };
CEFAD8712625F29E009AFEF9 /* String+JSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+JSON.swift"; sourceTree = "<group>"; };
CEFAD87926271414009AFEF9 /* COSE.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = COSE.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -235,6 +241,9 @@
children = (
CE157F8C262E24F900FE4821 /* HCert.swift */,
CE1D1EF5263597A2004C8919 /* LocalData.swift */,
CED272642639A1DF003D47A9 /* VaccinationEntry.swift */,
CED2726C2639A403003D47A9 /* TestEntry.swift */,
CED2727A2639A990003D47A9 /* RecoveryEntry.swift */,
);
path = Models;
sourceTree = "<group>";
Expand Down Expand Up @@ -451,10 +460,12 @@
CE13CF0A262DCDDA0070C80E /* CertificateViewer.swift in Sources */,
CEC2C4C32625ED030056E406 /* JWK.swift in Sources */,
CEC2C4C42625ED030056E406 /* Base45.swift in Sources */,
CED272652639A1DF003D47A9 /* VaccinationEntry.swift in Sources */,
CE891300263570CF00CB92AF /* Enclave.swift in Sources */,
CE8912EA26321DAA00CB92AF /* SHA256.swift in Sources */,
CE3CC9442628C2130079FB78 /* CBOR.swift in Sources */,
CE44799226306C86009A836B /* String.swift in Sources */,
CED2726D2639A403003D47A9 /* TestEntry.swift in Sources */,
CE37B643263867D700DEE13D /* SecureBackground.swift in Sources */,
CE8912F52634C60E00CB92AF /* GatewayConnection.swift in Sources */,
CE582DC12635AE5F008F35D7 /* SecureStorage.swift in Sources */,
Expand All @@ -473,6 +484,7 @@
CE13CF23262DDF810070C80E /* RoundedButton.swift in Sources */,
CEA6D6EC261F8D2700715333 /* AppDelegate.swift in Sources */,
CE891305263581D900CB92AF /* Home.swift in Sources */,
CED2727B2639A990003D47A9 /* RecoveryEntry.swift in Sources */,
CEA15563262F6DAB0024B7AC /* ChildDismissedDelegate.swift in Sources */,
CEFAD8722625F29E009AFEF9 /* String+JSON.swift in Sources */,
CEC2C4C22625ED030056E406 /* ZLib.swift in Sources */,
Expand Down
4 changes: 4 additions & 0 deletions DGCAVerifier/Extensions/Date.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,17 @@ extension Date {

static let isoFormatter = formatter(for: "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
static let dateFormatter = formatter(for: "yyyy-MM-dd")
static let dateTimeFormatter = formatter(for: "yyyy-MM-dd HH:mm '(UTC)'")

var isoString: String {
Date.isoFormatter.string(from: self)
}
var dateString: String {
Date.dateFormatter.string(from: self)
}
var dateTimeString: String {
Date.dateTimeFormatter.string(from: self)
}

init?(isoString: String) {
guard let date = Date.isoFormatter.date(from: isoString) else {
Expand Down
73 changes: 41 additions & 32 deletions DGCAVerifier/Models/HCert.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,6 @@ enum AttributeKey: String {
case testStatements
case vaccineStatements
case recoveryStatements
case vaccineShotNo = "seq"
case vaccineShotTotal = "tot"
}

enum HCertType: String {
Expand All @@ -59,19 +57,11 @@ enum HCertValidity {
case invalid
}

let identifierNames: [String: String] = [
"PP": "Passport Number",
"NN": "National Person Identifier",
"CZ": "Citizenship Card Number",
"HC": "Health Card Number",
]

let attributeKeys: [AttributeKey: [String]] = [
.firstName: ["nam", "gn"],
.lastName: ["nam", "fn"],
.firstNameStandardized: ["nam", "gnt"],
.lastNameStandardized: ["nam", "fnt"],
.gender: ["nam", "gen"],
.dateOfBirth: ["dob"],
.testStatements: ["t"],
.vaccineStatements: ["v"],
Expand Down Expand Up @@ -192,7 +182,7 @@ struct HCert {
),
]
}
return info
return info + statement.info
}

var rawData: Data
Expand All @@ -218,41 +208,60 @@ struct HCert {
[]
}

var testStatements: [JSON] {
return get(.testStatements).array ?? []
var testStatements: [TestEntry] {
return get(.testStatements)
.array?
.compactMap {
TestEntry(body: $0)
} ?? []
}
var vaccineStatements: [JSON] {
return get(.vaccineStatements).array ?? []
var vaccineStatements: [VaccinationEntry] {
return get(.vaccineStatements)
.array?
.compactMap {
VaccinationEntry(body: $0)
} ?? []
}
var recoveryStatements: [JSON] {
return get(.recoveryStatements).array ?? []
var recoveryStatements: [RecoveryEntry] {
return get(.recoveryStatements)
.array?
.compactMap {
RecoveryEntry(body: $0)
} ?? []
}
var hasLastShot: Bool {
for statement in vaccineStatements {
let no = statement[AttributeKey.vaccineShotNo.rawValue].int ?? 1
let total = statement[AttributeKey.vaccineShotTotal.rawValue].int ?? 2
if no == total {
return true
}
}
return false
var statements: [HCertEntry] {
testStatements + vaccineStatements + recoveryStatements
}
var statement: HCertEntry! {
statements.last
}
var type: HCertType {
if hasLastShot {
return .vaccineTwo
}
if !vaccineStatements.isEmpty {
if let vaccine = statement as? VaccinationEntry {
if vaccine.doseNumber == vaccine.dosesTotal {
return .vaccineTwo
}
return .vaccineOne
}
if !recoveryStatements.isEmpty {
if statement is RecoveryEntry {
return .recovery
}
return .test
}
var isValid: Bool {
return COSE.verify(rawData, with: LocalData.sharedInstance.encodedPublicKeys[kidStr] ?? "")
cryptographicallyValid && semanticallyValid
}
var cryptographicallyValid: Bool {
COSE.verify(rawData, with: LocalData.sharedInstance.encodedPublicKeys[kidStr] ?? "")
}
var semanticallyValid: Bool {
statement.isValid
}
var validity: HCertValidity {
return isValid ? .valid : .invalid
}
}

protocol HCertEntry {
var info: [InfoSection] { get }
var isValid: Bool { get }
}
83 changes: 83 additions & 0 deletions DGCAVerifier/Models/RecoveryEntry.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//
/*-
* ---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
*/
//
// TestResult.swift
// DGCAVerifier
//
// Created by Yannick Spreen on 4/28/21.
//


import Foundation
import SwiftyJSON

struct RecoveryEntry: HCertEntry {
var info: [InfoSection] {
[
InfoSection(header: "Valid Until", content: validUntil.dateString),
]
}

var isValid: Bool {
validFrom <= Date() && Date() <= validUntil
}

enum Fields: String {
case diseaseTargeted = "tg"
case firstPositiveDate = "fr"
case country = "co"
case issuer = "is"
case validFrom = "df"
case validUntil = "du"
case uvci = "ci"
}

init?(body: JSON) {
guard
let diseaseTargeted = body[Fields.diseaseTargeted.rawValue].string,
let firstPositiveDate = body[Fields.firstPositiveDate.rawValue].string,
let country = body[Fields.country.rawValue].string,
let issuer = body[Fields.issuer.rawValue].string,
let validFromStr = body[Fields.validFrom.rawValue].string,
let validUntilStr = body[Fields.validUntil.rawValue].string,
let validFrom = Date(dateString: validFromStr),
let validUntil = Date(dateString: validUntilStr),
let uvci = body[Fields.uvci.rawValue].string
else {
return nil
}
self.diseaseTargeted = diseaseTargeted
self.firstPositiveDate = firstPositiveDate
self.country = country
self.issuer = issuer
self.validFrom = validFrom
self.validUntil = validUntil
self.uvci = uvci
}

var diseaseTargeted: String
var firstPositiveDate: String
var country: String
var issuer: String
var validFrom: Date
var validUntil: Date
var uvci: String
}
92 changes: 92 additions & 0 deletions DGCAVerifier/Models/TestEntry.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
//
/*-
* ---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
*/
//
// TestResult.swift
// DGCAVerifier
//
// Created by Yannick Spreen on 4/28/21.
//


import Foundation
import SwiftyJSON

enum TestResult: String {
case detected = "260373001"
case notDetected = "260415000"
}

struct TestEntry: HCertEntry {
var info: [InfoSection] {
[
InfoSection(header: "Time of Sampling", content: sampleTime.dateTimeString),
InfoSection(header: "Test Result", content: resultNegative ? "Not Detected" : "Detected ⚠️"),
]
}

var isValid: Bool {
resultNegative && sampleTime < Date()
}

enum Fields: String {
case diseaseTargeted = "tg"
case type = "tt"
case sampleTime = "sc"
case result = "tr"
case testCenter = "tc"
case country = "co"
case issuer = "is"
case uvci = "ci"
}

init?(body: JSON) {
guard
let diseaseTargeted = body[Fields.diseaseTargeted.rawValue].string,
let type = body[Fields.type.rawValue].string,
let sampleTimeStr = body[Fields.sampleTime.rawValue].string,
let sampleTime = Date(rfc3339DateTimeString: sampleTimeStr),
let result = body[Fields.result.rawValue].string,
let testCenter = body[Fields.testCenter.rawValue].string,
let country = body[Fields.country.rawValue].string,
let issuer = body[Fields.issuer.rawValue].string,
let uvci = body[Fields.uvci.rawValue].string
else {
return nil
}
self.diseaseTargeted = diseaseTargeted
self.type = type
self.sampleTime = sampleTime
self.resultNegative = (TestResult(rawValue: result) == .notDetected)
self.testCenter = testCenter
self.country = country
self.issuer = issuer
self.uvci = uvci
}

var diseaseTargeted: String
var type: String
var sampleTime: Date
var resultNegative: Bool
var testCenter: String
var country: String
var issuer: String
var uvci: String
}
Loading

0 comments on commit be88037

Please sign in to comment.