CredentialsAndSecurityBackupResource.sys.mjs (4964B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ 4 5 import { BackupResource } from "resource:///modules/backup/BackupResource.sys.mjs"; 6 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; 7 8 const lazy = {}; 9 10 ChromeUtils.defineESModuleGetters(lazy, { 11 BackupService: "resource:///modules/backup/BackupService.sys.mjs", 12 OSKeyStore: "resource://gre/modules/OSKeyStore.sys.mjs", 13 }); 14 15 XPCOMUtils.defineLazyServiceGetter( 16 lazy, 17 "nativeOSKeyStore", 18 "@mozilla.org/security/oskeystore;1", 19 Ci.nsIOSKeyStore 20 ); 21 22 /** 23 * Class representing files needed for logins, payment methods and form autofill within a user profile. 24 */ 25 export class CredentialsAndSecurityBackupResource extends BackupResource { 26 static get key() { 27 return "credentials_and_security"; 28 } 29 30 static get requiresEncryption() { 31 return true; 32 } 33 34 async backup( 35 stagingPath, 36 profilePath = PathUtils.profileDir, 37 _isEncrypting = false 38 ) { 39 const simpleCopyFiles = [ 40 "pkcs11.txt", 41 "logins.json", 42 "logins-backup.json", 43 "autofill-profiles.json", 44 ]; 45 await BackupResource.copyFiles(profilePath, stagingPath, simpleCopyFiles); 46 47 const sqliteDatabases = ["cert9.db", "key4.db", "credentialstate.sqlite"]; 48 await BackupResource.copySqliteDatabases( 49 profilePath, 50 stagingPath, 51 sqliteDatabases 52 ); 53 54 return null; 55 } 56 57 async recover(_manifestEntry, recoveryPath, destProfilePath) { 58 // Payment methods would have been encrypted via OSKeyStore, which might 59 // have a different OSKeyStore secret than this profile (particularly if 60 // we're on a different machine). 61 // 62 // BackupService created a temporary native OSKeyStore that we can use 63 // to decrypt the payment methods using the old secret used at backup 64 // time. We should then re-encrypt those with the current OSKeyStore. 65 const AUTOFILL_RECORDS_PATH = PathUtils.join( 66 recoveryPath, 67 "autofill-profiles.json" 68 ); 69 70 const files = [ 71 "pkcs11.txt", 72 "logins.json", 73 "logins-backup.json", 74 "cert9.db", 75 "key4.db", 76 "credentialstate.sqlite", 77 ]; 78 79 if (await IOUtils.exists(AUTOFILL_RECORDS_PATH)) { 80 await this.encryptAutofillData(AUTOFILL_RECORDS_PATH); 81 files.push("autofill-profiles.json"); 82 } 83 84 await BackupResource.copyFiles(recoveryPath, destProfilePath, files); 85 86 return null; 87 } 88 89 async encryptAutofillData(AUTOFILL_RECORDS_PATH) { 90 let autofillRecords = await IOUtils.readJSON(AUTOFILL_RECORDS_PATH); 91 92 for (let creditCard of autofillRecords.creditCards) { 93 let oldEncryptedCard = creditCard["cc-number-encrypted"]; 94 if (oldEncryptedCard) { 95 let plaintextCard; 96 // We use the native OSKeyStore backend to decrypt the bytes with the 97 // original secret in order to skip authentication dialogs. 98 if ( 99 await lazy.nativeOSKeyStore.asyncSecretAvailable( 100 lazy.BackupService.RECOVERY_OSKEYSTORE_LABEL 101 ) 102 ) { 103 let plaintextCardBytes = 104 await lazy.nativeOSKeyStore.asyncDecryptBytes( 105 lazy.BackupService.RECOVERY_OSKEYSTORE_LABEL, 106 oldEncryptedCard 107 ); 108 plaintextCard = String.fromCharCode.apply(String, plaintextCardBytes); 109 } else { 110 plaintextCard = await lazy.OSKeyStore.decrypt( 111 oldEncryptedCard, 112 "backup_cc" 113 ); 114 } 115 116 // We're accessing the "real" OSKeyStore for this device here, and 117 // encrypting the card with it. 118 let newEncryptedCard = await lazy.OSKeyStore.encrypt(plaintextCard); 119 creditCard["cc-number-encrypted"] = newEncryptedCard; 120 } 121 } 122 123 await IOUtils.writeJSON(AUTOFILL_RECORDS_PATH, autofillRecords); 124 } 125 126 async measure(profilePath = PathUtils.profileDir) { 127 const securityFiles = ["cert9.db", "pkcs11.txt"]; 128 let securitySize = 0; 129 130 for (let filePath of securityFiles) { 131 let resourcePath = PathUtils.join(profilePath, filePath); 132 let resourceSize = await BackupResource.getFileSize(resourcePath); 133 if (Number.isInteger(resourceSize)) { 134 securitySize += resourceSize; 135 } 136 } 137 138 Glean.browserBackup.securityDataSize.set(securitySize); 139 140 const credentialsFiles = [ 141 "key4.db", 142 "logins.json", 143 "logins-backup.json", 144 "autofill-profiles.json", 145 "credentialstate.sqlite", 146 ]; 147 let credentialsSize = 0; 148 149 for (let filePath of credentialsFiles) { 150 let resourcePath = PathUtils.join(profilePath, filePath); 151 let resourceSize = await BackupResource.getFileSize(resourcePath); 152 if (Number.isInteger(resourceSize)) { 153 credentialsSize += resourceSize; 154 } 155 } 156 157 Glean.browserBackup.credentialsDataSize.set(credentialsSize); 158 } 159 }