Changeset View
Changeset View
Standalone View
Standalone View
src/applications/files/format/PhabricatorFileAES256StorageFormat.php
<?php | <?php | ||||
/** | /** | ||||
* At-rest encryption format using AES256 CBC. | * At-rest encryption format using AES256 CBC. | ||||
*/ | */ | ||||
final class PhabricatorFileAES256StorageFormat | final class PhabricatorFileAES256StorageFormat | ||||
extends PhabricatorFileStorageFormat { | extends PhabricatorFileStorageFormat { | ||||
const FORMATKEY = 'aes-256-cbc'; | const FORMATKEY = 'aes-256-cbc'; | ||||
private $keyName; | private $keyName; | ||||
private static $keyRing = array(); | |||||
public function getStorageFormatName() { | public function getStorageFormatName() { | ||||
return pht('Encrypted (AES-256-CBC)'); | return pht('Encrypted (AES-256-CBC)'); | ||||
} | } | ||||
public function canGenerateNewKeyMaterial() { | |||||
return true; | |||||
} | |||||
public function generateNewKeyMaterial() { | |||||
$envelope = self::newAES256Key(); | |||||
$material = $envelope->openEnvelope(); | |||||
return base64_encode($material); | |||||
} | |||||
public function canCycleMasterKey() { | |||||
return true; | |||||
} | |||||
public function cycleStorageProperties() { | |||||
$file = $this->getFile(); | |||||
list($key, $iv) = $this->extractKeyAndIV($file); | |||||
return $this->formatStorageProperties($key, $iv); | |||||
} | |||||
public function newReadIterator($raw_iterator) { | public function newReadIterator($raw_iterator) { | ||||
$file = $this->getFile(); | $file = $this->getFile(); | ||||
$data = $file->loadDataFromIterator($raw_iterator); | $data = $file->loadDataFromIterator($raw_iterator); | ||||
list($key, $iv) = $this->extractKeyAndIV($file); | list($key, $iv) = $this->extractKeyAndIV($file); | ||||
$data = $this->decryptData($data, $key, $iv); | $data = $this->decryptData($data, $key, $iv); | ||||
Show All 11 Lines | public function newWriteIterator($raw_iterator) { | ||||
return array($data); | return array($data); | ||||
} | } | ||||
public function newStorageProperties() { | public function newStorageProperties() { | ||||
// Generate a unique key and IV for this block of data. | // Generate a unique key and IV for this block of data. | ||||
$key_envelope = self::newAES256Key(); | $key_envelope = self::newAES256Key(); | ||||
$iv_envelope = self::newAES256IV(); | $iv_envelope = self::newAES256IV(); | ||||
return $this->formatStorageProperties($key_envelope, $iv_envelope); | |||||
} | |||||
private function formatStorageProperties( | |||||
PhutilOpaqueEnvelope $key_envelope, | |||||
PhutilOpaqueEnvelope $iv_envelope) { | |||||
// Encode the raw binary data with base64 so we can wrap it in JSON. | // Encode the raw binary data with base64 so we can wrap it in JSON. | ||||
$data = array( | $data = array( | ||||
'iv.base64' => base64_encode($iv_envelope->openEnvelope()), | 'iv.base64' => base64_encode($iv_envelope->openEnvelope()), | ||||
'key.base64' => base64_encode($key_envelope->openEnvelope()), | 'key.base64' => base64_encode($key_envelope->openEnvelope()), | ||||
); | ); | ||||
// Encode the base64 data with JSON. | // Encode the base64 data with JSON. | ||||
$data_clear = phutil_json_encode($data); | $data_clear = phutil_json_encode($data); | ||||
// Encrypt the block key with the master key, using a unique IV. | // Encrypt the block key with the master key, using a unique IV. | ||||
$data_iv = self::newAES256IV(); | $data_iv = self::newAES256IV(); | ||||
$key_name = $this->getMasterKeyName(); | $key_name = $this->getMasterKeyName(); | ||||
$master_key = self::getMasterKeyFromKeyRing($key_name); | $master_key = $this->getMasterKeyMaterial($key_name); | ||||
$data_cipher = $this->encryptData($data_clear, $master_key, $data_iv); | $data_cipher = $this->encryptData($data_clear, $master_key, $data_iv); | ||||
return array( | return array( | ||||
'key.name' => $key_name, | 'key.name' => $key_name, | ||||
'iv.base64' => base64_encode($data_iv->openEnvelope()), | 'iv.base64' => base64_encode($data_iv->openEnvelope()), | ||||
'payload.base64' => base64_encode($data_cipher), | 'payload.base64' => base64_encode($data_cipher), | ||||
); | ); | ||||
} | } | ||||
private function extractKeyAndIV(PhabricatorFile $file) { | private function extractKeyAndIV(PhabricatorFile $file) { | ||||
$outer_iv = $file->getStorageProperty('iv.base64'); | $outer_iv = $file->getStorageProperty('iv.base64'); | ||||
$outer_iv = base64_decode($outer_iv); | $outer_iv = base64_decode($outer_iv); | ||||
$outer_iv = new PhutilOpaqueEnvelope($outer_iv); | $outer_iv = new PhutilOpaqueEnvelope($outer_iv); | ||||
$outer_payload = $file->getStorageProperty('payload.base64'); | $outer_payload = $file->getStorageProperty('payload.base64'); | ||||
$outer_payload = base64_decode($outer_payload); | $outer_payload = base64_decode($outer_payload); | ||||
$outer_key_name = $file->getStorageProperty('key.name'); | $outer_key_name = $file->getStorageProperty('key.name'); | ||||
$outer_key = self::getMasterKeyFromKeyRing($outer_key_name); | $outer_key = $this->getMasterKeyMaterial($outer_key_name); | ||||
$payload = $this->decryptData($outer_payload, $outer_key, $outer_iv); | $payload = $this->decryptData($outer_payload, $outer_key, $outer_iv); | ||||
$payload = phutil_json_decode($payload); | $payload = phutil_json_decode($payload); | ||||
$inner_iv = $payload['iv.base64']; | $inner_iv = $payload['iv.base64']; | ||||
$inner_iv = base64_decode($inner_iv); | $inner_iv = base64_decode($inner_iv); | ||||
$inner_iv = new PhutilOpaqueEnvelope($inner_iv); | $inner_iv = new PhutilOpaqueEnvelope($inner_iv); | ||||
▲ Show 20 Lines • Show All 52 Lines • ▼ Show 20 Lines | final class PhabricatorFileAES256StorageFormat | ||||
public static function newAES256IV() { | public static function newAES256IV() { | ||||
// AES256 uses a 256 bit key, but the initialization vector length is | // AES256 uses a 256 bit key, but the initialization vector length is | ||||
// only 128 bits. | // only 128 bits. | ||||
$iv = Filesystem::readRandomBytes(phutil_units('128 bits in bytes')); | $iv = Filesystem::readRandomBytes(phutil_units('128 bits in bytes')); | ||||
return new PhutilOpaqueEnvelope($iv); | return new PhutilOpaqueEnvelope($iv); | ||||
} | } | ||||
public function selectKey($key_name) { | public function selectMasterKey($key_name) { | ||||
// Require that the key exist on the key ring. | // Require that the key exist on the key ring. | ||||
self::getMasterKeyFromKeyRing($key_name); | $this->getMasterKeyMaterial($key_name); | ||||
$this->keyName = $key_name; | $this->keyName = $key_name; | ||||
return $this; | return $this; | ||||
} | } | ||||
public static function addKeyToKeyRing($name, PhutilOpaqueEnvelope $key) { | |||||
self::$keyRing[$name] = $key; | |||||
} | |||||
private function getMasterKeyName() { | private function getMasterKeyName() { | ||||
if ($this->keyName === null) { | if ($this->keyName !== null) { | ||||
throw new Exception(pht('No master key selected for AES256 storage.')); | return $this->keyName; | ||||
} | } | ||||
return $this->keyName; | $default = PhabricatorKeyring::getDefaultKeyName(self::FORMATKEY); | ||||
if ($default !== null) { | |||||
return $default; | |||||
} | } | ||||
private static function getMasterKeyFromKeyRing($key_name) { | |||||
if (!isset(self::$keyRing[$key_name])) { | |||||
throw new Exception( | throw new Exception( | ||||
pht( | pht( | ||||
'No master key "%s" exists in key ring for AES256 storage.', | 'No AES256 key is specified in the keyring as a default encryption '. | ||||
$key_name)); | 'key, and no encryption key has been explicitly selected.')); | ||||
} | } | ||||
return self::$keyRing[$key_name]; | private function getMasterKeyMaterial($key_name) { | ||||
return PhabricatorKeyring::getKey($key_name, self::FORMATKEY); | |||||
} | } | ||||
} | } |