public function testInstantiation() { $lrs = new RemoteLRS(); $this->assertInstanceOf('TinCan\\RemoteLRS', $lrs); $this->assertAttributeEmpty('endpoint', $lrs, 'endpoint empty'); $this->assertAttributeEmpty('auth', $lrs, 'auth empty'); $this->assertAttributeEmpty('extended', $lrs, 'extended empty'); $this->assertSame(Version::latest(), $lrs->getVersion(), 'version set to latest'); }
public function testAsVersion() { $args = ['usageType' => self::USAGE_TYPE, 'display' => ['en-US' => self::DISPLAY], 'description' => ['en-US' => self::DESCRIPTION], 'contentType' => self::CONTENT_TYPE, 'length' => self::CONTENT_LENGTH, 'sha2' => self::CONTENT_SHA2, 'fileUrl' => self::FILE_URL]; $obj = new Attachment($args); $versioned = $obj->asVersion(Version::latest()); $this->assertEquals($versioned, $args, '1.0.0'); $obj = new Attachment(['content' => self::CONTENT_STR]); $this->assertEquals($obj->asVersion(Version::latest()), ['length' => self::CONTENT_LENGTH, 'sha2' => self::CONTENT_SHA2], 'auto populated properties but content not returned'); }
public function testLatest() { $this->assertSame(Version::V101, Version::latest(), "match latest"); }
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); }