Example #1
0
 public function verify($options = array())
 {
     if (!isset($options['version'])) {
         $options['version'] = Version::latest();
     }
     $signatureAttachment = null;
     $signatureIndex = 0;
     foreach ($this->getAttachments() as $attachment) {
         if ($attachment->getUsageType() === self::SIGNATURE_USAGE_TYPE) {
             $signatureAttachment = $attachment;
             break;
         }
         $signatureIndex++;
     }
     if ($signatureAttachment === null) {
         return array('success' => false, 'reason' => "Unable to locate signature attachment (usage type)");
     }
     try {
         $jws = JWS::load($signatureAttachment->getContent());
     } catch (\InvalidArgumentException $e) {
         return array('success' => false, 'reason' => 'Failed to load JWS: ' . $e);
     }
     $header = $jws->getHeader();
     //
     // there is a JWS spec security issue with allowing non-RS algorithms
     // to be specified and it is against the Tin Can spec anyways so we
     // want to fail hard on non-RS algorithms
     //
     if (!in_array($header['alg'], array('RS256', 'RS384', 'RS512'), true)) {
         throw new \InvalidArgumentException("Refusing to verify signature: Invalid signing algorithm ('" . $options['algorithm'] . "')");
     }
     if (isset($options['publicKey'])) {
         $publicKeyFile = $options['publicKey'];
     } else {
         if (isset($header['x5c'])) {
             $cert = "-----BEGIN CERTIFICATE-----\r\n" . chunk_split($header['x5c'][0], 64, "\r\n") . "-----END CERTIFICATE-----\r\n";
             $cert = openssl_x509_read($cert);
             if (!$cert) {
                 return array('success' => false, 'reason' => 'failed to read cert in x5c: ' . openssl_error_string());
             }
             $publicKeyFile = openssl_pkey_get_public($cert);
             if (!$publicKeyFile) {
                 return array('success' => false, 'reason' => 'x5c failed to provide public key: ' . openssl_error_string());
             }
         } else {
             return array('success' => false, 'reason' => 'No public key found or provided for verification');
         }
     }
     if (!$jws->verify($publicKeyFile)) {
         return array('success' => false, 'reason' => 'Failed to verify signature');
     }
     $payload = $jws->getPayload();
     //
     // serializing this statement as if it was going to be
     // made into a signature should provide us with what we
     // can expect in the payload, if the two don't match then
     // the signature isn't valid, it also gives us a clone
     // that we can then manipulate without affecting the
     // original instance
     //
     // use the version from the payload as it indicates the
     // version in use when the statement was serialized to
     // begin with
     //
     $version = $payload['version'] ? $payload['version'] : Version::latest();
     $serialization = $this->serializeForSignature($version);
     //
     // remove the signature attachment before comparing the
     // serializations, if it was the only attachment and the
     // signature doesn't include the 'attachments' property
     // then unset it as well
     //
     unset($serialization['attachments'][$signatureIndex]);
     if (count($serialization['attachments']) === 0 && !isset($payload['attachments'])) {
         unset($serialization['attachments']);
     }
     //
     // authority and stored are most often populated by the LRS,
     // and presumably for signature purposes are *never* included
     // in the signature so we are safe to remove them here
     //
     unset($serialization['stored']);
     unset($serialization['authority']);
     //
     // the payload 'version' is instructive of how to serialize the
     // statement for comparison, that 'version' is not required and
     // when not set we need to remove the 'version' in the serialization
     // which will be the current latest supported by the library
     // which shouldn't be compared against what is in the signature
     //
     if (!isset($payload['version'])) {
         unset($serialization['version']);
     }
     //
     // a statement can be signed without having first provided an
     // id, in that case the id is set by the receiving LRS, so if
     // the serialization has one, presumably from retrieval from
     // an LRS, remove it so that it is not compared
     //
     // if the statement did provide an id before signing then the
     // LRS should have maintained that id, so they can be compared
     //
     if (!isset($payload['id'])) {
         unset($serialization['id']);
     }
     //
     // the same applies to timestamp
     //
     if (!isset($payload['timestamp'])) {
         unset($serialization['timestamp']);
     }
     //
     // now we can construct an object from both the payload and the
     // serialization of this instance and compare the two for a match
     // in meaning
     //
     $fromSerialization = new self($serialization);
     $comparison = $fromSerialization->compareWithSignature(new self($payload));
     if (!$comparison['success']) {
         return array('success' => false, 'reason' => 'Statement to signature comparison failed: ' . $comparison['reason']);
     }
     return array('success' => true, 'jws' => $jws);
 }