/**
  * @param AppStoreInterface $appStore
  * @param string $blob
  * @return array
  *   Decoded data.
  */
 public static function decode($appStore, $blob)
 {
     $parts = explode(Constants::PROTOCOL_DELIM, $blob, 5);
     if (count($parts) != 5) {
         throw new InvalidMessageException('Invalid message: insufficient parameters');
     }
     list($wireProt, $wireAppId, $rsaCiphertextB64, $signature, $body) = $parts;
     if ($wireProt !== self::NAME) {
         throw new InvalidMessageException('Invalid message: wrong protocol name');
     }
     $appPrivKey = $appStore->getPrivateKey($wireAppId);
     if (!$appPrivKey) {
         throw new InvalidMessageException('Received message intended for unknown app.');
     }
     $rsaCiphertext = base64_decode($rsaCiphertextB64);
     if (strlen($rsaCiphertext) !== Constants::RSA_MSG_BYTES) {
         throw new InvalidMessageException("RSA ciphertext has incorrect length");
     }
     $secret = UserError::adapt('Civi\\Cxn\\Rpc\\Exception\\InvalidMessageException', function () use($rsaCiphertext, $appPrivKey) {
         return RegistrationMessage::getRsa($appPrivKey, 'private')->decrypt($rsaCiphertext);
     });
     if (empty($secret)) {
         throw new InvalidMessageException("Invalid message: decryption produced empty secret");
     }
     $plaintext = AesHelper::authenticateThenDecrypt($secret, $body, $signature);
     return json_decode($plaintext, TRUE);
 }
Example #2
0
 /**
  * @param CertificateValidatorInterface|NULL $certValidator
  * @param string $blob
  * @return AppMetasMessage
  *   Validated message.
  * @throws InvalidMessageException
  */
 public static function decode($certValidator, $blob)
 {
     $parts = explode(Constants::PROTOCOL_DELIM, $blob, 4);
     if (count($parts) != 4) {
         throw new InvalidMessageException('Invalid message: insufficient parameters');
     }
     list($wireProt, $wireCert, $wireSig, $wireEnvelope) = $parts;
     if ($wireProt != self::NAME) {
         throw new InvalidMessageException('Invalid message: wrong protocol name');
     }
     if ($certValidator !== NULL) {
         $certValidator->validateCert($wireCert);
         $wireCertX509 = new \File_X509();
         $wireCertX509->loadX509($wireCert);
         $cn = $wireCertX509->getDNProp('CN');
         if (count($cn) != 1 || $cn[0] != Constants::OFFICIAL_APPMETAS_CN) {
             throw new InvalidMessageException('Invalid message: signed by unauthorized party');
         }
         $isValid = UserError::adapt('Civi\\Cxn\\Rpc\\Exception\\InvalidMessageException', function () use($wireCertX509, $wireEnvelope, $wireSig) {
             return AppMetasMessage::getRsaFromCert($wireCertX509)->verify($wireEnvelope, base64_decode($wireSig));
         });
         if (!$isValid) {
             throw new InvalidMessageException("Invalid message: incorrect signature");
         }
     }
     $envelope = json_decode($wireEnvelope, TRUE);
     if (empty($envelope)) {
         throw new InvalidMessageException("Invalid message: malformed envelope");
     }
     if (Time::getTime() > $envelope['ttl']) {
         throw new InvalidMessageException("Invalid message: expired");
     }
     return new AppMetasMessage($wireCert, NULL, json_decode($envelope['r'], TRUE));
 }
Example #3
0
 /**
  * Validate the signature and date of the message, then
  * decrypt it.
  *
  * @param string $secret
  * @param string $body
  * @param string $signature
  * @return string
  *   Plain text.
  * @throws InvalidMessageException
  */
 public static function authenticateThenDecrypt($secret, $body, $signature)
 {
     $keys = self::deriveAesKeys($secret);
     $localHmac = hash_hmac('sha256', $body, $keys['auth']);
     if (!self::hash_compare($signature, $localHmac)) {
         throw new InvalidMessageException("Incorrect hash");
     }
     list($jsonEnvelope, $jsonEncrypted) = explode(Constants::PROTOCOL_DELIM, $body, 2);
     if (strlen($jsonEnvelope) > Constants::MAX_ENVELOPE_BYTES) {
         throw new InvalidMessageException("Oversized envelope");
     }
     $envelope = json_decode($jsonEnvelope, TRUE);
     if (!$envelope) {
         throw new InvalidMessageException("Malformed envelope");
     }
     if (!is_numeric($envelope['ttl']) || Time::getTime() > $envelope['ttl']) {
         throw new InvalidMessageException("Invalid TTL");
     }
     if (!is_string($envelope['iv']) || strlen($envelope['iv']) !== Constants::AES_BYTES * 2 || !preg_match('/^[a-f0-9]+$/', $envelope['iv'])) {
         // AES_BYTES (32) ==> bin2hex ==> 2 hex digits (4-bit) per byte (8-bit)
         throw new InvalidMessageException("Malformed initialization vector");
     }
     $jsonPlaintext = UserError::adapt('Civi\\Cxn\\Rpc\\Exception\\InvalidMessageException', function () use($jsonEncrypted, $envelope, $keys) {
         $cipher = new \Crypt_AES(CRYPT_AES_MODE_CBC);
         $cipher->setKeyLength(Constants::AES_BYTES);
         $cipher->setKey($keys['enc']);
         $cipher->setIV(BinHex::hex2bin($envelope['iv']));
         return $cipher->decrypt($jsonEncrypted);
     });
     return $jsonPlaintext;
 }