Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Synchronous backups #166

Merged
merged 25 commits into from
Oct 4, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d13a653
chore: wip
Jasonvdb Aug 8, 2023
ead2eeb
Merge branch 'master' into sync-backup
Jasonvdb Aug 21, 2023
3cbb82e
feat: ios remote backup sync
Jasonvdb Aug 22, 2023
32e9a1b
feat: ios backup encryption
Jasonvdb Aug 22, 2023
cd08d8c
feat: fastify server
Jasonvdb Aug 24, 2023
a3fe5fb
wip: slashauth
Jasonvdb Aug 24, 2023
0f9fc53
feat: list backed up files from server, full restore from server
Jasonvdb Aug 31, 2023
135127b
feat: slashauth used in backup server
Jasonvdb Sep 1, 2023
8829635
fix: using slashauth magic link
Jasonvdb Sep 4, 2023
9a5ce5e
wip: sodium
Jasonvdb Sep 6, 2023
71dd44f
feat: android backup encryption
Jasonvdb Sep 13, 2023
98df4c4
feat: android remote backup restore
Jasonvdb Sep 14, 2023
fb7111e
feat: ios signed backup uploads
Jasonvdb Sep 18, 2023
d25c176
Merge branch 'master' into sync-backup
Jasonvdb Sep 20, 2023
901d27b
feat: backup challenge/response for retrieval with node signing
Jasonvdb Sep 20, 2023
c3a9c8c
feat: backup server using storage-abstraction
Jasonvdb Sep 20, 2023
f3494da
feat: server signs client challenge
Jasonvdb Sep 21, 2023
f96e748
feat: ios validating server's signed challenge response
Jasonvdb Sep 22, 2023
467fcba
feat: use ln-verifymessagejs for server message signing
Jasonvdb Sep 25, 2023
9d9de90
feat: ios bearer token auth for restoring backups
Jasonvdb Sep 28, 2023
176497f
feat: android verifying server response on persist
Jasonvdb Sep 29, 2023
d3136e9
feat: android bearer token auth for restoring backups
Jasonvdb Sep 29, 2023
d8fb401
chore: backup documentation
Jasonvdb Oct 2, 2023
e5aa676
fix: clear previously cached bearer token on backup client setup
Jasonvdb Oct 4, 2023
ff9f5d4
Merge branch 'master' into sync-backup
Jasonvdb Oct 4, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat: ios validating server's signed challenge response
  • Loading branch information
Jasonvdb committed Sep 22, 2023
commit f96e748ead671ebaa67fd2f7dbd1a990a91d783f
5 changes: 3 additions & 2 deletions backup-server/src/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,10 @@ fastify.route({
const bearer = crypto.randomBytes(32).toString('hex');

//Valid for 30min, should only be used for doing a restore
users.set(bearer, {pubkey, expires: Date.now() + 30 * 60 * 1000});
const expires = Date.now() + 30 * 60 * 1000;
users.set(bearer, {pubkey, expires});

return {bearer};
return {bearer, expires};
}
});

Expand Down
52 changes: 43 additions & 9 deletions lib/ios/Classes/BackupClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ enum BackupError: Error {
case invalidServerResponse(Int)
case decryptFailed(String)
case signingError
case serverChallengeResponseFailed
}

extension BackupError: LocalizedError {
Expand All @@ -33,6 +34,8 @@ extension BackupError: LocalizedError {
return NSLocalizedString("Failed to decrypt backup payload. \(msg)", comment: "")
case .signingError:
return NSLocalizedString("Signing backup error", comment: "")
case .serverChallengeResponseFailed:
return NSLocalizedString("Client failed to validate server challenge response. Indicates server error or potential man in the middle attack.", comment: "")
}
}
}
Expand Down Expand Up @@ -168,11 +171,19 @@ class BackupClient {
}

static func verifySignature(message: String, signature: String, pubKey: String) -> Bool {
// Bindings.swiftVerify(msg: <#T##[UInt8]#>, sig: <#T##String#>, pk: <#T##[UInt8]#>)
return false
return Bindings.swiftVerify(
msg: [UInt8](message.data(using: .utf8)!),
sig: signature,
pk: pubKey.hexaBytes
)
}

static func persist(_ label: Label, _ bytes: [UInt8]) throws {
struct PersistResponse: Codable {
let success: Bool
let signature: String
}

guard !skipRemoteBackup else {
LdkEventEmitter.shared.send(withEvent: .native_log, body: "Skipping remote backup for \(label.string)")
return
Expand All @@ -182,26 +193,33 @@ class BackupClient {
throw BackupError.requiresSetup
}

let pubKeyHex = Data(pubKey).hexEncodedString()

let encryptedBackup = try encrypt(Data(bytes))
let hash = hash(encryptedBackup)
let hashToSign = hash(encryptedBackup)

let message = "\(signedMessagePrefix)\(hash)"
let message = "\(signedMessagePrefix)\(hashToSign)"
let signed = Bindings.swiftSign(msg: Array(message.utf8), sk: secretKey)
if let _ = signed.getError() {
throw BackupError.signingError
}

let signedHash = signed.getValue()!

//Hash of pubkey+timestamp
let clientChallenge = hash("\(pubKeyHex)\(Date().timeIntervalSince1970)".data(using: .utf8)!)

var request = URLRequest(url: try backupUrl(.persist, label))
request.httpMethod = "POST"
request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type")
request.setValue(signedHash, forHTTPHeaderField: "Signed-Hash")
request.setValue(Data(pubKey).hexEncodedString(), forHTTPHeaderField: "Public-Key")

request.setValue(pubKeyHex, forHTTPHeaderField: "Public-Key")
request.setValue(clientChallenge, forHTTPHeaderField: "Challenge")

request.httpBody = encryptedBackup

var requestError: Error?
var persistResponse: PersistResponse?
//Thread blocking, backups must be synchronous
let semaphore = DispatchSemaphore(value: 0)
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
Expand All @@ -211,9 +229,15 @@ class BackupClient {

if let httpURLResponse = response as? HTTPURLResponse {
let statusCode = httpURLResponse.statusCode
if statusCode == 200 {
return;
} else {
if statusCode == 200, let data {
do {
persistResponse = try JSONDecoder().decode(PersistResponse.self, from: data)
return
} catch {
requestError = BackupError.invalidServerResponse(0)
return
}
} else {
requestError = BackupError.invalidServerResponse(httpURLResponse.statusCode)
return
}
Expand All @@ -233,6 +257,16 @@ class BackupClient {
throw error
}

guard let persistResponse else {
throw BackupError.invalidServerResponse(0)
}

//Validate server's signed challenge response
let serverPubkey = "037e85dcfa44b4668e66fc8cc08d5c60b3813e343b4646e115f615b699662f260d"// "03a540d25c34b5a49b390a1e4aab97a3083b969ce992bebb7a949a675fc0729c7c"
guard verifySignature(message: "\(signedMessagePrefix)\(clientChallenge)", signature: persistResponse.signature, pubKey: serverPubkey) else {
throw BackupError.serverChallengeResponseFailed
}

LdkEventEmitter.shared.send(withEvent: .native_log, body: "Remote persist success for \(label.string)")
}

Expand Down