mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-08-24 21:33:09 +09:00
169 lines
4.5 KiB
Swift
169 lines
4.5 KiB
Swift
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
import Foundation
|
|
import Shared
|
|
import FxA
|
|
import Account
|
|
import XCGLogger
|
|
import SwiftyJSON
|
|
|
|
private let log = Logger.syncLogger
|
|
|
|
/**
|
|
* Turns JSON of the form
|
|
*
|
|
* { ciphertext: ..., hmac: ..., iv: ...}
|
|
*
|
|
* into a new JSON object resulting from decrypting and parsing the ciphertext.
|
|
*/
|
|
open class EncryptedJSON {
|
|
var json: JSON
|
|
var _cleartext: JSON? // Cache decrypted cleartext.
|
|
var _ciphertextBytes: Data? // Cache decoded ciphertext.
|
|
var _hmacBytes: Data? // Cache decoded HMAC.
|
|
var _ivBytes: Data? // Cache decoded IV.
|
|
|
|
var valid: Bool = false
|
|
var validated: Bool = false
|
|
|
|
let keyBundle: KeyBundle
|
|
|
|
public init(json: String, keyBundle: KeyBundle) {
|
|
self.keyBundle = keyBundle
|
|
self.json = JSON(parseJSON: json)
|
|
}
|
|
|
|
public init(json: JSON, keyBundle: KeyBundle) {
|
|
self.keyBundle = keyBundle
|
|
self.json = json
|
|
}
|
|
|
|
// For validating HMAC: the raw ciphertext as bytes without decoding.
|
|
fileprivate var ciphertextB64: Data? {
|
|
if let ct = self["ciphertext"].string {
|
|
return Bytes.dataFromBase64(ct)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
/**
|
|
* You probably want to call validate() and then use .ciphertext.
|
|
*/
|
|
fileprivate var ciphertextBytes: Data? {
|
|
return Bytes.decodeBase64(self["ciphertext"].string!)
|
|
}
|
|
|
|
fileprivate func validate() -> Bool {
|
|
if validated {
|
|
return valid
|
|
}
|
|
|
|
defer { validated = true }
|
|
|
|
guard self["ciphertext"].isString() &&
|
|
self["hmac"].isString() &&
|
|
self["IV"].isString() else {
|
|
valid = false
|
|
return false
|
|
}
|
|
|
|
guard let ciphertextForHMAC = self.ciphertextB64 else {
|
|
valid = false
|
|
return false
|
|
}
|
|
|
|
guard keyBundle.verify(hmac: self.hmac, ciphertextB64: ciphertextForHMAC) else {
|
|
valid = false
|
|
return false
|
|
}
|
|
|
|
// I guess we called validate twice…
|
|
if self._ciphertextBytes != nil {
|
|
valid = true
|
|
return true
|
|
}
|
|
|
|
// Also verify that the ciphertext is valid base64. Do this by
|
|
// retrieving the value in a failable way, leaving the accessors
|
|
// to take the dangerous/simple path.
|
|
// We can force-unwrap self["ciphertext"] because we already checked
|
|
// it when verifying the HMAC above.
|
|
guard let data = self.ciphertextBytes else {
|
|
log.error("Unable to decode ciphertext base64 in record \(self["id"].string ?? "<unknown>")")
|
|
valid = false
|
|
return false
|
|
}
|
|
|
|
self._ciphertextBytes = data
|
|
valid = true
|
|
return valid
|
|
}
|
|
|
|
open func isValid() -> Bool {
|
|
return !json.isError() && self.validate()
|
|
}
|
|
|
|
/**
|
|
* Make sure you call isValid first. This API force-unwraps for simplicity.
|
|
*/
|
|
var ciphertext: Data {
|
|
if _ciphertextBytes != nil {
|
|
return _ciphertextBytes!
|
|
}
|
|
|
|
_ciphertextBytes = self.ciphertextBytes
|
|
return _ciphertextBytes!
|
|
}
|
|
|
|
var hmac: Data {
|
|
if _hmacBytes != nil {
|
|
return _hmacBytes!
|
|
}
|
|
//NSData(base16EncodedString: self["hmac"].asString!, options: NSDataBase16DecodingOptions.Default)
|
|
_hmacBytes = NSData(base16EncodedString: self["hmac"].stringValue, options: []) as Data
|
|
return _hmacBytes!
|
|
}
|
|
|
|
var iv: Data {
|
|
if _ivBytes != nil {
|
|
return _ivBytes!
|
|
}
|
|
|
|
_ivBytes = Bytes.decodeBase64(self["IV"].string!)
|
|
return _ivBytes!
|
|
}
|
|
|
|
// Returns nil on error.
|
|
open var cleartext: JSON? {
|
|
if _cleartext != nil {
|
|
return _cleartext
|
|
}
|
|
|
|
if !isValid() {
|
|
log.error("Failed to validate.")
|
|
return nil
|
|
}
|
|
|
|
let decrypted: String? = keyBundle.decrypt(self.ciphertext, iv: self.iv)
|
|
if decrypted == nil {
|
|
log.error("Failed to decrypt.")
|
|
valid = false
|
|
return nil
|
|
}
|
|
|
|
_cleartext = JSON(parseJSON: decrypted!)
|
|
return _cleartext!
|
|
}
|
|
|
|
subscript(key: String) -> JSON {
|
|
get {
|
|
return json[key]
|
|
}
|
|
|
|
set {
|
|
json[key] = newValue
|
|
}
|
|
}
|
|
}
|