diff --git a/bin/generate_CustomSignable.php b/bin/generate_CustomSignable.php new file mode 100644 index 00000000..9230e863 --- /dev/null +++ b/bin/generate_CustomSignable.php @@ -0,0 +1,15 @@ +Chunk')->documentElement; +$signable = new CustomSignable($chunk); + +$privateKey = PEMCertificatesMock::getPrivateKey(XMLSecurityKey::RSA_SHA256, PEMCertificatesMock::SELFSIGNED_PRIVATE_KEY); +$x = $signable->sign($privateKey); +echo $x; diff --git a/composer.json b/composer.json index 6f06bb21..e541ee18 100644 --- a/composer.json +++ b/composer.json @@ -40,7 +40,7 @@ "robrichards/xmlseclibs": "^3.1.1", "simplesamlphp/assert": "~0.2.6", - "simplesamlphp/xml-common": "^0.7.1" + "simplesamlphp/xml-common": "^0.8.0" }, "require-dev": { "simplesamlphp/simplesamlphp-test-framework": "^1.0.5" diff --git a/src/Alg/Signature/AbstractSigner.php b/src/Alg/Signature/AbstractSigner.php index a5d62c70..31aa7119 100644 --- a/src/Alg/Signature/AbstractSigner.php +++ b/src/Alg/Signature/AbstractSigner.php @@ -1,20 +1,24 @@ key = $key; + $this->algId = $algId; $this->digest = $digest; $this->backend = new $this->default_backend(); $this->backend->setDigestAlg($digest); } + /** + * @return string + */ + public function getAlgorithmId(): string + { + return $this->algId; + } + + /** * @return string */ @@ -50,6 +78,15 @@ public function getDigest(): string } + /** + * @return AbstractKey + */ + public function getKey(): AbstractKey + { + return $this->key; + } + + /** * @param \SimpleSAML\XMLSecurity\Backend\SignatureBackend $backend */ @@ -67,7 +104,7 @@ public function setBackend(SignatureBackend $backend): void * * @return string The (binary) signature corresponding to the given plaintext. */ - public function sign(string $plaintext): string + final public function sign(string $plaintext): string { return $this->backend->sign($this->key, $plaintext); } @@ -81,7 +118,7 @@ public function sign(string $plaintext): string * * @return boolean True if the signature can be verified, false otherwise. */ - public function verify(string $plaintext, string $signature): bool + final public function verify(string $plaintext, string $signature): bool { return $this->backend->verify($this->key, $plaintext, $signature); } diff --git a/src/Alg/Signature/HMAC.php b/src/Alg/Signature/HMAC.php index ee003dbc..a497d86d 100644 --- a/src/Alg/Signature/HMAC.php +++ b/src/Alg/Signature/HMAC.php @@ -1,18 +1,20 @@ blacklist = $blacklist; } + + // initialize the cache for supported algorithms per known signer + if (!self::$initialized) { + foreach (self::$algorithms as $algorithm) { + self::updateCache($algorithm); + } + self::$initialized = true; + } } @@ -53,8 +85,8 @@ public function __construct(array $blacklist = null) * * @return \SimpleSAML\XMLSecurity\Alg\SignatureAlgorithm An object implementing the given algorithm. * - * @throws InvalidArgumentException If an error occurs, e.g. the given algorithm is blacklisted, unknown or the - * given key is not suitable for it. + * @throws \SimpleSAML\XMLSecurity\Exception\InvalidArgumentException If an error occurs, e.g. the given algorithm + * is blacklisted, unknown or the given key is not suitable for it. */ public function getAlgorithm(string $algId, AbstractKey $key): SignatureAlgorithm { @@ -62,59 +94,46 @@ public function getAlgorithm(string $algId, AbstractKey $key): SignatureAlgorith throw new InvalidArgumentException('Blacklisted signature algorithm'); } - // determine digest - switch ($algId) { - case Constants::SIG_RSA_SHA1: - case Constants::SIG_HMAC_SHA1: - $digest = Constants::DIGEST_SHA1; - break; - case Constants::SIG_RSA_SHA224: - case Constants::SIG_HMAC_SHA224: - $digest = Constants::DIGEST_SHA224; - break; - case Constants::SIG_RSA_SHA256: - case Constants::SIG_HMAC_SHA256: - $digest = Constants::DIGEST_SHA256; - break; - case Constants::SIG_RSA_SHA384: - case Constants::SIG_HMAC_SHA384: - $digest = Constants::DIGEST_SHA384; - break; - case Constants::SIG_RSA_SHA512: - case Constants::SIG_HMAC_SHA512: - $digest = Constants::DIGEST_SHA512; - break; - case Constants::SIG_RSA_RIPEMD160: - case Constants::SIG_HMAC_RIPEMD160: - $digest = Constants::DIGEST_RIPEMD160; - break; - default: - throw new RuntimeException('Unsupported signature algorithm'); + if (!array_key_exists($algId, self::$cache)) { + throw new InvalidArgumentException('Unknown algorithm identifier.'); } - // create instance - switch ($algId) { - case Constants::SIG_RSA_SHA1: - case Constants::SIG_RSA_SHA224: - case Constants::SIG_RSA_SHA256: - case Constants::SIG_RSA_SHA384: - case Constants::SIG_RSA_SHA512: - case Constants::SIG_RSA_RIPEMD160: - if ($key instanceof AsymmetricKey) { - return new RSA($key, $digest); - } - break; - case Constants::SIG_HMAC_SHA1: - case Constants::SIG_HMAC_SHA224: - case Constants::SIG_HMAC_SHA256: - case Constants::SIG_HMAC_SHA384: - case Constants::SIG_HMAC_SHA512: - case Constants::SIG_HMAC_RIPEMD160: - if ($key instanceof SymmetricKey) { - return new HMAC($key, $digest); - } - break; + return new self::$cache[$algId]($key, $algId); + } + + + /** + * Register a signature algorithm for its use. + * + * @note Algorithms must extend \SimpleSAML\XMLSecurity\Alg\Signature\AbstractSigner. + * + * @param string $className + */ + public static function registerAlgorithm(string $className): void + { + Assert::subclassOf( + $className, + AbstractSigner::class, + 'Cannot register algorithm "' . $className . '", must implement ' + . "\SimpleSAML\XMLSecurity\Alg\SignatureInterface.", + InvalidArgumentException::class + ); + + self::$algorithms[] = $className; + self::updateCache($className); + } + + + /** + * Update the cache with a new signer implementation. + * + * @param string $signer + */ + private static function updateCache(string $signer): void + { + /** @var \SimpleSAML\XMLSecurity\Alg\Signature\AbstractSigner $signer */ + foreach ($signer::getSupportedAlgorithms() as $algId) { + self::$cache[$algId] = $signer; } - throw new RuntimeException('Invalid type of key for algorithm'); } } diff --git a/src/Alg/SignatureAlgorithm.php b/src/Alg/SignatureAlgorithm.php index 17be1241..10deffcb 100644 --- a/src/Alg/SignatureAlgorithm.php +++ b/src/Alg/SignatureAlgorithm.php @@ -3,6 +3,7 @@ namespace SimpleSAML\XMLSecurity\Alg; use SimpleSAML\XMLSecurity\Backend\SignatureBackend; +use SimpleSAML\XMLSecurity\Key\AbstractKey; /** * An interface representing algorithms that can be used for digital signatures. @@ -11,6 +12,14 @@ */ interface SignatureAlgorithm { + /** + * Get an array with all the identifiers for algorithms supported. + * + * @return string[] + */ + public static function getSupportedAlgorithms(): array; + + /** * Get the digest used by this signature algorithm. * @@ -19,6 +28,22 @@ interface SignatureAlgorithm public function getDigest(): string; + /** + * Get the identifier of this signature algorithm. + * + * @return string The identifier of this signature algorithm. + */ + public function getAlgorithmId(): string; + + + /** + * Get the key to use with this signature algorithm. + * + * @return AbstractKey + */ + public function getKey(): AbstractKey; + + /** * Set the backend to use for actual computations by this algorithm. * diff --git a/src/Backend/HMAC.php b/src/Backend/HMAC.php index 2c580234..55703d26 100644 --- a/src/Backend/HMAC.php +++ b/src/Backend/HMAC.php @@ -62,7 +62,7 @@ public function sign(AbstractKey $key, string $plaintext): string /** * Verify a signature with this cipher and a given key. * - * @param \SimpleSAML\XMLSecurity\Key\AbstractKey $key The key to use to. + * @param \SimpleSAML\XMLSecurity\Key\AbstractKey $key The key to use to verify the signature. * @param string $plaintext The original signed text. * @param string $signature The (binary) signature to verify. * diff --git a/src/Backend/OpenSSL.php b/src/Backend/OpenSSL.php index ee9a184d..20e9b31c 100644 --- a/src/Backend/OpenSSL.php +++ b/src/Backend/OpenSSL.php @@ -80,7 +80,7 @@ public function encrypt(AbstractKey $key, string $plaintext): string $fn = 'openssl_private_encrypt'; } - $ciphertext = null; + $ciphertext = ''; if (!$fn($plaintext, $ciphertext, $key->get(), $this->padding)) { throw new RuntimeException('Cannot encrypt data: ' . openssl_error_string()); } @@ -125,7 +125,7 @@ public function decrypt(AbstractKey $key, string $ciphertext): string $fn = 'openssl_private_decrypt'; } - $plaintext = null; + $plaintext = ''; if (!$fn($ciphertext, $plaintext, $key->get(), $this->padding)) { throw new RuntimeException('Cannot decrypt data: ' . openssl_error_string()); } diff --git a/src/Constants.php b/src/Constants.php index f04e8737..58d5a7ba 100644 --- a/src/Constants.php +++ b/src/Constants.php @@ -1,11 +1,13 @@ self::DIGEST_SHA1, + self::SIG_RSA_SHA224 => self::DIGEST_SHA224, + self::SIG_RSA_SHA256 => self::DIGEST_SHA256, + self::SIG_RSA_SHA384 => self::DIGEST_SHA384, + self::SIG_RSA_SHA512 => self::DIGEST_SHA512, + self::SIG_RSA_RIPEMD160 => self::DIGEST_RIPEMD160, + ]; + + public static $HMAC_DIGESTS = [ + self::SIG_HMAC_SHA1 => self::DIGEST_SHA1, + self::SIG_HMAC_SHA224 => self::DIGEST_SHA224, + self::SIG_HMAC_SHA256 => self::DIGEST_SHA256, + self::SIG_HMAC_SHA384 => self::DIGEST_SHA384, + self::SIG_HMAC_SHA512 => self::DIGEST_SHA512, + self::SIG_HMAC_RIPEMD160 => self::DIGEST_RIPEMD160, + ]; + /** * XML & XPath namespaces and identifiers */ - public const XMLDSIGNS = 'http://www.w3.org/2000/09/xmldsig#'; - public const XMLDSIG11NS = 'http://www.w3.org/2009/xmldsig11#'; + public const NS_XDSIG = 'http://www.w3.org/2000/09/xmldsig#'; + public const NS_XDSIG11 = 'http://www.w3.org/2009/xmldsig11#'; public const XMLDSIG_ENVELOPED = 'http://www.w3.org/2000/09/xmldsig#enveloped-signature'; - public const XMLENCNS = 'http://www.w3.org/2001/04/xmlenc#'; + public const NS_XENC = 'http://www.w3.org/2001/04/xmlenc#'; public const XMLENC_ELEMENT = 'http://www.w3.org/2001/04/xmlenc#Element'; public const XMLENC_CONTENT = 'http://www.w3.org/2001/04/xmlenc#Content'; diff --git a/src/Key/AbstractKey.php b/src/Key/AbstractKey.php index 410b348c..9500e56b 100644 --- a/src/Key/AbstractKey.php +++ b/src/Key/AbstractKey.php @@ -1,5 +1,7 @@ 0x7f) { + case self::ASN1_TYPE_INTEGER: + if (ord($string) > self::ASN1_SIZE_128 - 1) { $string = chr(0) . $string; } break; - case 0x03: + case self::ASN1_TYPE_BIT_STRING: $string = chr(0) . $string; break; } $length = strlen($string); + Assert::lessThan($length, self::ASN1_SIZE_65535); - if ($length < 128) { + if ($length < self::ASN1_SIZE_128) { $output = sprintf("%c%c%s", $type, $length, $string); - } elseif ($length < 0x0100) { - $output = sprintf("%c%c%c%s", $type, 0x81, $length, $string); - } elseif ($length < 0x010000) { - $output = sprintf("%c%c%c%c%s", $type, 0x82, $length / 0x0100, $length % 0x0100, $string); - } else { - $output = null; + } elseif ($length < self::ASN1_SIZE_256) { + $output = sprintf("%c%c%c%s", $type, self::ASN1_SIZE_128 + 1, $length, $string); + } else { // ($length < self::ASN1_SIZE_65535) + $output = sprintf( + "%c%c%c%c%s", + $type, + self::ASN1_SIZE_128 + 2, + $length / 0x0100, + $length % 0x0100, + $string + ); } + return $output; } @@ -96,14 +124,14 @@ public static function fromDetails(string $modulus, string $exponent): PublicKey chunk_split( base64_encode( self::makeASN1Segment( - 0x30, + self::ASN1_TYPE_SEQUENCE, pack("H*", "300D06092A864886F70D0101010500") . // RSA alg id self::makeASN1Segment( // bitstring - 0x03, + self::ASN1_TYPE_BIT_STRING, self::makeASN1Segment( // sequence - 0x30, - self::makeASN1Segment(0x02, $modulus) . - self::makeASN1Segment(0x02, $exponent) + self::ASN1_TYPE_SEQUENCE, + self::makeASN1Segment(self::ASN1_TYPE_INTEGER, $modulus) + . self::makeASN1Segment(self::ASN1_TYPE_INTEGER, $exponent) ) ) ) diff --git a/src/Key/SymmetricKey.php b/src/Key/SymmetricKey.php index 05387126..a6f92161 100644 --- a/src/Key/SymmetricKey.php +++ b/src/Key/SymmetricKey.php @@ -1,8 +1,9 @@ certificate = $certificate; parent::__construct(openssl_pkey_get_public($this->certificate)); diff --git a/src/Signature.php b/src/Signature.php index b9e77172..ca04f8cf 100644 --- a/src/Signature.php +++ b/src/Signature.php @@ -361,7 +361,7 @@ public function addX509Certificates( if ($digest !== false) { // add certificate digest $fingerprint = base64_encode(hex2bin($cert->getRawThumbprint($digest))); - $x509DigestNode = $this->createElement('X509Digest', $fingerprint, C::XMLDSIG11NS, 'dsig11'); + $x509DigestNode = $this->createElement('X509Digest', $fingerprint, C::NS_XDSIG11, 'dsig11'); $x509DigestNode->setAttribute('Algorithm', $digest); $certDataNode->appendChild($x509DigestNode); } @@ -390,7 +390,7 @@ public function addX509Certificates( $pem_lines = explode("\n", trim($cert->getCertificate())); array_shift($pem_lines); array_pop($pem_lines); - $pem = join($pem_lines); + $pem = /** @scrutinizer ignore-call */ join($pem_lines); $x509CertNode = $this->createElement('X509Certificate', $pem); $certDataNode->appendChild($x509CertNode); } @@ -714,7 +714,7 @@ public function setPrefix($prefix): void */ public function setSignatureElement(DOMElement $element): void { - if ($element->localName !== 'Signature' || $element->namespaceURI !== C::XMLDSIGNS) { + if ($element->localName !== 'Signature' || $element->namespaceURI !== C::NS_XDSIG) { throw new RuntimeException('Node is not an XML signature'); } $this->sigNode = $element; @@ -917,7 +917,7 @@ protected function canonicalizeData( protected function createElement( string $name, string $content = null, - string $ns = C::XMLDSIGNS, + string $ns = C::NS_XDSIG, string $nsPrefix = null ): DOMElement { if ($this->sigNode === null) { @@ -1052,16 +1052,12 @@ protected function processReference(DOMElement $ref): bool $includeCommentNodes = false; $xp = XP::getXPath($ref->ownerDocument); - if ($this->idNS && is_array($this->idNS)) { - foreach ($this->idNS as $nspf => $ns) { - $xp->registerNamespace($nspf, $ns); - } + foreach ($this->idNS as $nspf => $ns) { + $xp->registerNamespace($nspf, $ns); } $iDlist = '@Id="' . $identifier . '"'; - if (is_array($this->idKeys)) { - foreach ($this->idKeys as $idKey) { - $iDlist .= " or @$idKey='$identifier'"; - } + foreach ($this->idKeys as $idKey) { + $iDlist .= " or @$idKey='$identifier'"; } $query = '//*[' . $iDlist . ']'; $dataObject = $xp->query($query)->item(0); diff --git a/src/TestUtils/PEMCertificatesMock.php b/src/TestUtils/PEMCertificatesMock.php index 8e49cff2..0aa6ffe0 100644 --- a/src/TestUtils/PEMCertificatesMock.php +++ b/src/TestUtils/PEMCertificatesMock.php @@ -77,7 +77,7 @@ public static function loadPlainCertificateFile(string $file, $sig_alg = self::A /** * @param string $hash_alg - * @param string The file to use + * @param string $file The file to use * @param string $sig_alg One of rsa|dsa * @return \SimpleSAML\XMLSecurity\XMLSecurityKey */ @@ -94,7 +94,7 @@ public static function getPublicKey( /** * @param string $hash_alg - * @param string The file to use + * @param string $file The file to use * @param string $sig_alg One of rsa|dsa * @return \SimpleSAML\XMLSecurity\XMLSecurityKey */ diff --git a/src/Utils/Security.php b/src/Utils/Security.php index 73474f56..eb0c96eb 100644 --- a/src/Utils/Security.php +++ b/src/Utils/Security.php @@ -1,18 +1,11 @@ idKeys[] = 'ID'; - - /* Locate the XMLDSig Signature element to be used. */ - /** @var \DOMElement[] $signatureElement */ - $signatureElement = XMLUtils::xpQuery($root, './ds:Signature'); - if (empty($signatureElement)) { - /* We don't have a signature element ot validate. */ - - return false; - } elseif (count($signatureElement) > 1) { - throw new Exception('XMLSec: more than one signature element in root.'); - } - $signatureElement = $signatureElement[0]; - $objXMLSecDSig->sigNode = $signatureElement; - - /* Canonicalize the XMLDSig SignedInfo element in the message. */ - $objXMLSecDSig->canonicalizeSignedInfo(); - - /* Validate referenced xml nodes. */ - if (!$objXMLSecDSig->validateReference()) { - throw new Exception('XMLsec: digest validation failed'); - } - - /* Check that $root is one of the signed nodes. */ - $rootSigned = false; - /** @var \DOMNode $signedNode */ - foreach ($objXMLSecDSig->getValidatedNodes() as $signedNode) { - if ($signedNode->isSameNode($root)) { - $rootSigned = true; - break; - } elseif ($root->parentNode instanceof DOMDocument && $signedNode->isSameNode($root->ownerDocument)) { - /* $root is the root element of a signed document. */ - $rootSigned = true; - break; - } - } - if (!$rootSigned) { - throw new Exception('XMLSec: The root element is not signed.'); - } - - /* Now we extract all available X509 certificates in the signature element. */ - $certificates = []; - foreach (XMLUtils::xpQuery($signatureElement, './ds:KeyInfo/ds:X509Data/ds:X509Certificate') as $certNode) { - $certData = trim($certNode->textContent); - $certData = str_replace(["\r", "\n", "\t", ' '], '', $certData); - $certificates[] = $certData; - } - - $ret = [ - 'Signature' => $objXMLSecDSig, - 'Certificates' => $certificates, - ]; - - return $ret; - } - - - /** - * Helper function to convert a XMLSecurityKey to the correct algorithm. + * Compute the hash for some data with a given algorithm. * - * @param \SimpleSAML\XMLSecurity\XMLSecurityKey $key The key. - * @param string $algorithm The desired algorithm. - * @param string $type Public or private key, defaults to public. - * @return \SimpleSAML\XMLSecurity\XMLSecurityKey The new key. + * @param string $alg The identifier of the algorithm to use. + * @param string $data The data to digest. + * @param bool $encode Whether to bas64-encode the result or not. Defaults to true. * - * @throws \SimpleSAML\Assert\AssertionFailedException if assertions are false - */ - public static function castKey(XMLSecurityKey $key, string $algorithm, string $type = null): XMLSecurityKey - { - $type = $type ?: 'public'; - Assert::oneOf($type, ["private", "public"]); - - // do nothing if algorithm is already the type of the key - if ($key->type === $algorithm) { - return $key; - } - - if ( - !in_array( - $algorithm, - [ - XMLSecurityKey::RSA_1_5, - XMLSecurityKey::RSA_SHA1, - XMLSecurityKey::RSA_SHA256, - XMLSecurityKey::RSA_SHA384, - XMLSecurityKey::RSA_SHA512 - ], - true - ) - ) { - throw new Exception('Unsupported signing algorithm.'); - } - - /** @psalm-suppress PossiblyNullArgument */ - $keyInfo = openssl_pkey_get_details($key->key); - if ($keyInfo === false) { - throw new Exception('Unable to get key details from XMLSecurityKey.'); - } - if (!isset($keyInfo['key'])) { - throw new Exception('Missing key in public key details.'); - } - - $newKey = new XMLSecurityKey($algorithm, ['type' => $type]); - $newKey->loadKey($keyInfo['key']); - - return $newKey; - } - - - /** - * Check a signature against a key. - * - * An exception is thrown if we are unable to validate the signature. + * @return string The (binary or base64-encoded) digest corresponding to the given data. * - * @param array $info The information returned by the validateElement() function. - * @param \SimpleSAML\XMLSecurity\XMLSecurityKey $key The publickey that should validate the Signature object. - * @throws \Exception - * - * @throws \SimpleSAML\Assert\AssertionFailedException if assertions are false + * @throws \SimpleSAML\XMLSecurity\Exception\InvalidArgumentException If $alg is not a valid + * identifier of a supported digest algorithm. */ - public static function validateSignature(array $info, XMLSecurityKey $key): void + public static function hash(string $alg, string $data, bool $encode = true): string { - Assert::keyExists($info, "Signature"); - - /** @var XMLSecurityDSig $objXMLSecDSig */ - $objXMLSecDSig = $info['Signature']; - - /** - * @var \DOMElement[] $sigMethod - * @var \DOMElement $objXMLSecDSig->sigNode - */ - $sigMethod = XMLUtils::xpQuery($objXMLSecDSig->sigNode, './ds:SignedInfo/ds:SignatureMethod'); - if (empty($sigMethod)) { - throw new Exception('Missing SignatureMethod element.'); + if (!array_key_exists($alg, Constants::$DIGEST_ALGORITHMS)) { + throw new InvalidArgumentException('Unsupported digest method "' . $alg . '"'); } - $sigMethod = $sigMethod[0]; - if (!$sigMethod->hasAttribute('Algorithm')) { - throw new Exception('Missing Algorithm-attribute on SignatureMethod element.'); - } - $algo = $sigMethod->getAttribute('Algorithm'); - - if ($key->type === XMLSecurityKey::RSA_SHA256 && $algo !== $key->type) { - $key = self::castKey($key, $algo); - } - - /* Check the signature. */ - if ($objXMLSecDSig->verify($key) !== 1) { - throw new Exception("Unable to validate Signature; " . openssl_error_string()); - } - } - - /** - * Insert a Signature node. - * - * @param \SimpleSAML\XMLSecurity\XMLSecurityKey $key The key we should use to sign the message. - * @param array $certificates The certificates we should add to the signature node. - * @param \DOMElement $root The XML node we should sign. - * @param \DOMNode $insertBefore The XML element we should insert the signature element before. - */ - public static function insertSignature( - XMLSecurityKey $key, - array $certificates, - DOMElement $root, - DOMNode $insertBefore = null - ): void { - $objXMLSecDSig = new XMLSecurityDSig(); - $objXMLSecDSig->setCanonicalMethod(XMLSecurityDSig::EXC_C14N); - - switch ($key->type) { - case XMLSecurityKey::RSA_SHA256: - $type = XMLSecurityDSig::SHA256; - break; - case XMLSecurityKey::RSA_SHA384: - $type = XMLSecurityDSig::SHA384; - break; - case XMLSecurityKey::RSA_SHA512: - $type = XMLSecurityDSig::SHA512; - break; - default: - $type = XMLSecurityDSig::SHA1; - } - - $objXMLSecDSig->addReferenceList( - [$root], - $type, - ['http://www.w3.org/2000/09/xmldsig#enveloped-signature', XMLSecurityDSig::EXC_C14N], - ['id_name' => 'ID', 'overwrite' => false] - ); - - $objXMLSecDSig->sign($key); - - foreach ($certificates as $certificate) { - $objXMLSecDSig->add509Cert($certificate, true); - } - - $objXMLSecDSig->insertSignature($root, $insertBefore); - } - - - /** - * Decrypt an encrypted element. - * - * This is an internal helper function. - * - * @param \DOMElement $encryptedData The encrypted data. - * @param \SimpleSAML\XMLSecurity\XMLSecurityKey $inputKey The decryption key. - * @param array &$blacklist Blacklisted decryption algorithms. - * @throws \Exception - * @return \DOMElement The decrypted element. - */ - private static function doDecryptElement( - DOMElement $encryptedData, - XMLSecurityKey $inputKey, - array &$blacklist - ): DOMElement { - $enc = new XMLSecEnc(); - - $enc->setNode($encryptedData); - $enc->type = $encryptedData->getAttribute("Type"); - - $symmetricKey = $enc->locateKey($encryptedData); - if (!$symmetricKey) { - throw new Exception('Could not locate key algorithm in encrypted data.'); - } - - $symmetricKeyInfo = $enc->locateKeyInfo($symmetricKey); - if (!$symmetricKeyInfo) { - throw new Exception('Could not locate for the encrypted key.'); - } - - $inputKeyAlgo = $inputKey->getAlgorithm(); - if ($symmetricKeyInfo->isEncrypted) { - $symKeyInfoAlgo = $symmetricKeyInfo->getAlgorithm(); - - if (in_array($symKeyInfoAlgo, $blacklist, true)) { - throw new Exception('Algorithm disabled: ' . var_export($symKeyInfoAlgo, true)); - } - - if ($symKeyInfoAlgo === XMLSecurityKey::RSA_OAEP_MGF1P && $inputKeyAlgo === XMLSecurityKey::RSA_1_5) { - /* - * The RSA key formats are equal, so loading an RSA_1_5 key - * into an RSA_OAEP_MGF1P key can be done without problems. - * We therefore pretend that the input key is an - * RSA_OAEP_MGF1P key. - */ - $inputKeyAlgo = XMLSecurityKey::RSA_OAEP_MGF1P; - } - - /* Make sure that the input key format is the same as the one used to encrypt the key. */ - if ($inputKeyAlgo !== $symKeyInfoAlgo) { - throw new Exception( - 'Algorithm mismatch between input key and key used to encrypt ' . - ' the symmetric key for the message. Key was: ' . - var_export($inputKeyAlgo, true) . '; message was: ' . - var_export($symKeyInfoAlgo, true) - ); - } - - /** @var XMLSecEnc $encKey */ - $encKey = $symmetricKeyInfo->encryptedCtx; - $symmetricKeyInfo->key = $inputKey->key; - - $keySize = $symmetricKey->getSymmetricKeySize(); - if ($keySize === null) { - /* To protect against "key oracle" attacks, we need to be able to create a - * symmetric key, and for that we need to know the key size. - */ - throw new Exception( - 'Unknown key size for encryption algorithm: ' . var_export($symmetricKey->type, true) - ); - } - - try { - /** - * @var string $key - * @psalm-suppress UndefinedClass - */ - $key = $encKey->decryptKey($symmetricKeyInfo); - if (strlen($key) !== $keySize) { - throw new Exception( - 'Unexpected key size (' . strval(strlen($key) * 8) . 'bits) for encryption algorithm: ' . - var_export($symmetricKey->type, true) - ); - } - } catch (Exception $e) { - /* We failed to decrypt this key. Log it, and substitute a "random" key. */ -// Utils::getContainer()->getLogger()->error('Failed to decrypt symmetric key: ' . $e->getMessage()); - /* Create a replacement key, so that it looks like we fail in the same way as if the key was correctly - * padded. */ - - /* We base the symmetric key on the encrypted key and private key, so that we always behave the - * same way for a given input key. - */ - $encryptedKey = $encKey->getCipherValue(); - if ($encryptedKey === null) { - throw new Exception('No CipherValue available in the encrypted element.'); - } - - /** @psalm-suppress PossiblyNullArgument */ - $pkey = openssl_pkey_get_details($symmetricKeyInfo->key); - $pkey = sha1(serialize($pkey), true); - $key = sha1($encryptedKey . $pkey, true); - - /* Make sure that the key has the correct length. */ - if (strlen($key) > $keySize) { - $key = substr($key, 0, $keySize); - } elseif (strlen($key) < $keySize) { - $key = str_pad($key, $keySize); - } - } - $symmetricKey->loadkey($key); - } else { - $symKeyAlgo = $symmetricKey->getAlgorithm(); - /* Make sure that the input key has the correct format. */ - if ($inputKeyAlgo !== $symKeyAlgo) { - throw new Exception( - 'Algorithm mismatch between input key and key in message. ' . - 'Key was: ' . var_export($inputKeyAlgo, true) . '; message was: ' . - var_export($symKeyAlgo, true) - ); - } - $symmetricKey = $inputKey; - } - - $algorithm = $symmetricKey->getAlgorithm(); - if (in_array($algorithm, $blacklist, true)) { - throw new Exception('Algorithm disabled: ' . var_export($algorithm, true)); - } - - /** - * @var string $decrypted - * @psalm-suppress UndefinedClass - */ - $decrypted = $enc->decryptNode($symmetricKey, false); - - /* - * This is a workaround for the case where only a subset of the XML - * tree was serialized for encryption. In that case, we may miss the - * namespaces needed to parse the XML. - */ - $xml = '' . - $decrypted . ''; - - try { - $newDoc = DOMDocumentFactory::fromString($xml); - } catch (RuntimeException $e) { - throw new Exception('Failed to parse decrypted XML. Maybe the wrong sharedkey was used?', 0, $e); - } - - /** @psalm-suppress PossiblyNullPropertyFetch */ - $decryptedElement = $newDoc->firstChild->firstChild; - if (!($decryptedElement instanceof DOMElement)) { - throw new Exception('Missing decrypted element or it was not actually a DOMElement.'); - } - - return $decryptedElement; - } - - - /** - * Decrypt an encrypted element. - * - * @param \DOMElement $encryptedData The encrypted data. - * @param \SimpleSAML\XMLSecurity\XMLSecurityKey $inputKey The decryption key. - * @param array $blacklist Blacklisted decryption algorithms. - * @throws \Exception - * @return \DOMElement The decrypted element. - */ - public static function decryptElement( - DOMElement $encryptedData, - XMLSecurityKey $inputKey, - array $blacklist = [] - ): DOMElement { - try { - return self::doDecryptElement($encryptedData, $inputKey, $blacklist); - } catch (Exception $e) { - /* - * Something went wrong during decryption, but for security - * reasons we cannot tell the user what failed. - */ -// Utils::getContainer()->getLogger()->error('Decryption failed: ' . $e->getMessage()); - throw new Exception('Failed to decrypt XML element.', 0, $e); + $digest = hash(Constants::$DIGEST_ALGORITHMS[$alg], $data, true); + if ($encode) { + $digest = base64_encode($digest); } + return $digest; } } diff --git a/src/Utils/XML.php b/src/Utils/XML.php new file mode 100644 index 00000000..e3306050 --- /dev/null +++ b/src/Utils/XML.php @@ -0,0 +1,128 @@ +ownerDocument !== null) + && ($element->ownerDocument->documentElement !== null) + && $element->isSameNode($element->ownerDocument->documentElement) + ) { + // check for any PI or comments as they would have been excluded + $current = $element; + while ($refNode = $current->previousSibling) { + if ( + (($refNode->nodeType === XML_COMMENT_NODE) && $withComments) + || $refNode->nodeType === XML_PI_NODE + ) { + break; + } + $current = $refNode; + } + if ($refNode === null) { + $element = $element->ownerDocument; + } + } + + return $element->C14N($exclusive, $withComments, $xpaths, $prefixes); + } + + + /** + * Process all transforms specified by a given Reference element. + * + * @param \SimpleSAML\XMLSecurity\XML\ds\Transforms $transforms The transforms to apply. + * @param \DOMElement $data The data referenced. + * @param bool $includeCommentNodes Whether to allow canonicalization with comments or not. + * + * @return string The canonicalized data after applying all transforms specified by $ref. + * + * @see http://www.w3.org/TR/xmldsig-core/#sec-ReferenceProcessingModel + */ + public static function processTransforms( + Transforms $transforms, + DOMElement $data + ): string { + $canonicalMethod = C::C14N_EXCLUSIVE_WITHOUT_COMMENTS; + $arXPath = null; + $prefixList = null; + foreach ($transforms->getTransform() as $transform) { + $canonicalMethod = $transform->getAlgorithm(); + switch ($canonicalMethod) { + case C::C14N_EXCLUSIVE_WITHOUT_COMMENTS: + case C::C14N_EXCLUSIVE_WITH_COMMENTS: + $inclusiveNamespaces = $transform->getInclusiveNamespaces(); + if ($inclusiveNamespaces !== null) { + $prefixes = $inclusiveNamespaces->getPrefixes(); + if (count($prefixes) > 0) { + $prefixList = $prefixes; + } + } + break; + case C::XPATH_URI: + $xpath = $transform->getXPath(); + if ($xpath !== null) { + $arXPath = []; + $arXPath['query'] = '(.//. | .//@* | .//namespace::*)[' . $xpath->getExpression() . ']'; + $arXpath['namespaces'] = $xpath->getNamespaces(); + // TODO: review if $nsnode->localName is equivalent to the keys in getNamespaces() +// $nslist = $xp->query('./namespace::*', $node); +// foreach ($nslist as $nsnode) { +// if ($nsnode->localName != "xml") { +// $arXPath['namespaces'][$nsnode->localName] = $nsnode->nodeValue; +// } +// } + } + break; + } + } + + return self::canonicalizeData($data, $canonicalMethod, $arXPath, $prefixList); + } +} diff --git a/src/Utils/XPath.php b/src/Utils/XPath.php index a23c1b75..3f06895a 100644 --- a/src/Utils/XPath.php +++ b/src/Utils/XPath.php @@ -13,21 +13,21 @@ * * @package SimpleSAML\XMLSecurity\Utils */ -class XPath extends \RobRichards\XMLSecLibs\Utils\XPath +class XPath extends \SimpleSAML\XML\Utils\XPath { /** * Get a DOMXPath object that can be used to search for XMLDSIG elements. * - * @param \DOMDocument $doc The document to associate to the DOMXPath object. + * @param \DOMNode $node The document to associate to the DOMXPath object. * * @return \DOMXPath A DOMXPath object ready to use in the given document, with the XMLDSIG namespace already * registered. */ - public static function getXPath(DOMDocument $doc) + public static function getXPath(DOMNode $node): DOMXPath { - $xp = new DOMXPath($doc); - $xp->registerNamespace('ds', C::XMLDSIGNS); - $xp->registerNamespace('xenc', C::XMLENCNS); + $xp = parent::getXPath($node); + $xp->registerNamespace('ds', C::NS_XDSIG); + $xp->registerNamespace('xenc', C::NS_XENC); return $xp; } @@ -42,7 +42,7 @@ public static function getXPath(DOMDocument $doc) * * @throws RuntimeException If no DOM document is available. */ - public static function findElement(DOMNode $ref, $name) + public static function findElement(DOMNode $ref, string $name) { $doc = $ref instanceof DOMDocument ? $ref : $ref->ownerDocument; if ($doc === null) { diff --git a/src/XML/AbstractSignedXMLElement.php b/src/XML/AbstractSignedXMLElement.php new file mode 100644 index 00000000..c46bc1f2 --- /dev/null +++ b/src/XML/AbstractSignedXMLElement.php @@ -0,0 +1,103 @@ +setStructure($xml); + $this->setSignature($signature); + } + + + /** + * Output the class as an XML-formatted string + * + * @return string + */ + public function __toString(): string + { + return $this->structure->ownerDocument->saveXML(); + } + + + /** + * Set the value of the structure-property + * + * @param \DOMElement $structure + */ + private function setStructure(DOMElement $structure): void + { + $this->structure = $structure; + } + + + /** + * Create XML from this class + * + * @param \DOMElement|null $parent + * @return \DOMElement + */ + public function toXML(DOMElement $parent = null): DOMElement + { + return $this->structure; + } + + + /** + * Create a class from XML + * + * @param \DOMElement $xml + * @return self + */ + public static function fromXML(DOMElement $xml): object + { + $original = $xml->ownerDocument->cloneNode(true); + + $signature = Signature::getChildrenOfClass($xml); + Assert::minCount($signature, 1, MissingElementException::class); + Assert::maxCount($signature, 1, TooManyElementsException::class); + + return new static($original->documentElement, array_pop($signature)); + } +} diff --git a/src/XML/CanonicalizableElementInterface.php b/src/XML/CanonicalizableElementInterface.php new file mode 100644 index 00000000..6ca5f5e5 --- /dev/null +++ b/src/XML/CanonicalizableElementInterface.php @@ -0,0 +1,36 @@ +getOriginalXML(), $method, $xpaths, $prefixes); + } + + + /** + * Serialize this canonicalisable element. + * + * @return string The serialized chunk. + */ + public function serialize(): string + { + $xml = $this->getOriginalXML(); + return $xml->ownerDocument->saveXML($xml); + } +} diff --git a/src/XML/SignableElementInterface.php b/src/XML/SignableElementInterface.php new file mode 100644 index 00000000..31b88e5f --- /dev/null +++ b/src/XML/SignableElementInterface.php @@ -0,0 +1,41 @@ +signer = $signer; + $this->keyInfo = $keyInfo; + Assert::oneOf( + $canonicalizationAlg, + [ + C::C14N_INCLUSIVE_WITH_COMMENTS, + C::C14N_EXCLUSIVE_WITHOUT_COMMENTS, + C::C14N_EXCLUSIVE_WITH_COMMENTS, + C::C14N_EXCLUSIVE_WITHOUT_COMMENTS + ], + 'Unsupported canonicalization algorithm', + InvalidArgumentException::class + ); + $this->c14nAlg = $canonicalizationAlg; + } + + + /** + * Get a ds:Reference pointing to this object. + * + * @param string $digestAlg The digest algorithm to use. + * @param \SimpleSAML\XMLSecurity\XML\ds\Transforms $transforms The transforms to apply to the object. + */ + private function getReference( + string $digestAlg, + Transforms $transforms, + DOMElement $xml, + string $canonicalDocument + ): Reference { + $id = $this->getId(); + $uri = null; + if (empty($id)) { // document reference + Assert::notNull( + $xml->ownerDocument->documentElement, + 'Cannot create a document reference without a root element in the document.', + RuntimeException::class + ); + Assert::true( + $xml->isSameNode($xml->ownerDocument->documentElement), + 'Cannot create a document reference when signing an object that is not the root of the document. ' . + 'Please give your object an identifier.', + RuntimeException::class + ); + if (in_array($this->c14nAlg, [C::C14N_INCLUSIVE_WITH_COMMENTS, C::C14N_EXCLUSIVE_WITH_COMMENTS])) { + $uri = '#xpointer(/)'; + } + } elseif (in_array($this->c14nAlg, [C::C14N_INCLUSIVE_WITH_COMMENTS, C::C14N_EXCLUSIVE_WITH_COMMENTS])) { + // regular reference, but must retain comments + $uri = '#xpointer(id(' . $id . '))'; + } else { // regular reference, can ignore comments + $uri = '#' . $id; + } + + return new Reference( + new DigestMethod($digestAlg), + new DigestValue(Security::hash($digestAlg, $canonicalDocument)), + $transforms, + null, + null, + $uri + ); + } + + + /** + * Do the actual signing of the document. + * + * Note that this method does not insert the signature in the returned \DOMElement. The signature will be available + * in $this->signature as a \SimpleSAML\XMLSecurity\XML\ds\Signature object, which can then be converted to XML + * calling toXML() on it, passing the \DOMElement value returned here as a parameter. The resulting \DOMElement + * can then be inserted in the position desired. + * + * E.g.: + * $xml = // our XML to sign + * $signedXML = $this->doSign($xml); + * $signedXML->appendChild($this->signature->toXML($signedXML)); + * + * @param \DOMElement $xml The element to sign. + * @return \DOMElement The signed element, without the signature attached to it just yet. + */ + protected function doSign(DOMElement $xml): DOMElement + { + Assert::notNull( + $this->signer, + 'Cannot call toSignedXML() without calling sign() first.', + RuntimeException::class + ); + + $algorithm = $this->signer->getAlgorithmId(); + $digest = $this->signer->getDigest(); + + $transforms = new Transforms([ + new Transform(C::XMLDSIG_ENVELOPED), + new Transform($this->c14nAlg) + ]); + + $canonicalDocument = XML::processTransforms($transforms, $xml); + + $signedInfo = new SignedInfo( + new CanonicalizationMethod($this->c14nAlg), + new SignatureMethod($algorithm), + [$this->getReference($digest, $transforms, $xml, $canonicalDocument)] + ); + + $signingData = $signedInfo->canonicalize($this->c14nAlg); + $signedData = base64_encode($this->signer->sign($signingData)); + + $this->signature = new Signature($signedInfo, new SignatureValue($signedData), $this->keyInfo); + return DOMDocumentFactory::fromString($canonicalDocument)->documentElement; + } +} diff --git a/src/XML/SignedElementInterface.php b/src/XML/SignedElementInterface.php new file mode 100644 index 00000000..26b16891 --- /dev/null +++ b/src/XML/SignedElementInterface.php @@ -0,0 +1,71 @@ +signature; + } + + + /** + * Initialize a signed element from XML. + * + * @param \SimpleSAML\XMLSecurity\XML\ds\Signature $signature The ds:Signature object + */ + protected function setSignature(Signature $signature): void + { + $this->signature = $signature; + } + + + /** + * Make sure the given Reference points to the original XML given. + */ + private function validateReferenceUri(Reference $reference, \DOMElement $xml): void + { + if ( + in_array( + $this->signature->getSignedInfo()->getCanonicalizationMethod()->getAlgorithm(), + [ + Constants::C14N_INCLUSIVE_WITH_COMMENTS, + Constants::C14N_EXCLUSIVE_WITH_COMMENTS, + ] + ) + && !$reference->isXPointer() + ) { // canonicalization with comments used, but reference wasn't an xpointer! + throw new RuntimeException('Invalid reference for canonicalization algorithm.'); + } + + $id = $this->getId(); + $uri = $reference->getURI(); + + if (empty($uri) || $uri === '#xpointer(/)') { // same-document reference + Assert::true( + $xml->isSameNode($xml->ownerDocument->documentElement), + 'Cannot use document reference when element is not the root of the document.', + RuntimeException::class + ); + } else { // short-name or scheme-based xpointer + Assert::notEmpty( + $id, + 'Reference points to an element, but given element does not have an ID.', + RuntimeException::class + ); + Assert::oneOf( + $uri, + [ + '#' . $id, + '#xpointer(id(' . $id . '))' + ], + 'Reference does not point to given element.', + RuntimeException::class + ); + } + } + + + /** + * @return \SimpleSAML\XMLSecurity\XML\SignedElementInterface + */ + private function validateReference(): SignedElementInterface + { + /** @var \SimpleSAML\XMLSecurity\XML\ds\Signature $this->signature */ + $signedInfo = $this->signature->getSignedInfo(); + $references = $signedInfo->getReferences(); + Assert::count( + $references, + 1, + 'Exactly one reference expected in signature.', + RuntimeException::class + ); + $reference = array_pop($references); + + $xml = $this->getOriginalXML(); + $this->validateReferenceUri($reference, $xml); + + $xp = XPath::getXPath($xml->ownerDocument); + $sigNode = XPath::xpQuery($xml, 'child::ds:Signature', $xp); + Assert::count( + $sigNode, + 1, + 'None or more than one signature found in object.', + RuntimeException::class + ); + $xml->removeChild($sigNode[0]); + + $data = XML::processTransforms($reference->getTransforms(), $xml); + $digest = Security::hash($reference->getDigestMethod()->getAlgorithm(), $data, false); + + if (Security::compareStrings($digest, base64_decode($reference->getDigestValue()->getRawContent())) !== true) { + throw new RuntimeException('Failed to validate signature.'); + } + + $verifiedXml = DOMDocumentFactory::fromString($data); + return static::fromXML($verifiedXml->documentElement); + } + + + /** + * Validate this element against a public key. + * + * true is returned on success, false is returned if we don't have any + * signature we can validate. An exception is thrown if the signature + * validation fails. + * + * @param \SimpleSAML\XMLSecurity\Alg\SignatureAlgorithm|null $verifier The verifier to use to verify the signature. + * If null, attempt to verify it with the KeyInfo information in the signature. + * + * @return \SimpleSAML\XMLSecurity\XML\SignedElementInterface The Signed element if it was validated. + */ + private function verifyInternal(SignatureAlgorithm $verifier): SignedElementInterface + { + /** @var \SimpleSAML\XMLSecurity\XML\ds\Signature $this->signature */ + $signedInfo = $this->signature->getSignedInfo(); + $c14nAlg = $signedInfo->getCanonicalizationMethod()->getAlgorithm(); + $c14nSignedInfo = $signedInfo->canonicalize($c14nAlg); + /** @var SignedElementInterface $ref */ + $ref = $this->validateReference(); + + if ( + $verifier->verify( + $c14nSignedInfo, // the canonicalized ds:SignedInfo element (plaintext) + base64_decode($this->signature->getSignatureValue()->getRawContent()) // the actual signature + ) + ) { + /* + * validateReference() returns an object of the same class using this trait. This means the validatingKey + * property is available, and we can set it on the newly created object because we are in the same class, + * even thought the property itself is private. + */ + /** @psalm-suppress NoInterfaceProperties */ + $ref->validatingKey = $verifier->getKey(); + return $ref; + } + throw new RuntimeException('Failed to validate signature.'); + } + + + /** + * Retrieve certificates that sign this element. + * + * @return \SimpleSAML\XMLSecurity\Key\AbstractKey|null The key that successfully validated this signature. + */ + public function getValidatingKey(): ?Key\AbstractKey + { + return $this->validatingKey; + } + + + /** + * Whether this object is signed or not. + * + * @return bool + */ + public function isSigned(): bool + { + return $this->signature !== null; + } + + + /** + * Verify the signature in this object. + * + * If no signature is present, false is returned. If a signature is present, + * but cannot be verified, an exception will be thrown. + * + * @param \SimpleSAML\XMLSecurity\Alg\SignatureAlgorithm|null $verifier The verifier to use to verify the signature. + * If null, attempt to verify it with the KeyInfo information in the signature. + * @return \SimpleSAML\XMLSecurity\XML\SignedElementInterface The object processed again from its canonicalised + * representation verified by the signature. + * @throws \SimpleSAML\XMLSecurity\Exception\NoSignatureFound if the object is not signed. + * @throws \SimpleSAML\XMLSecurity\Exception\InvalidArgumentException if no key is passed and there is no KeyInfo + * in the signature. + * @throws \SimpleSAML\XMLSecurity\Exception\RuntimeException if the signature fails to validate. + */ + public function verify(SignatureAlgorithm $verifier = null): SignedElementInterface + { + if (!$this->isSigned()) { + throw new NoSignatureFound(); + } + + $keyInfo = $this->signature->getKeyInfo(); + $algId = $this->signature->getSignedInfo()->getSignatureMethod()->getAlgorithm(); + if ($verifier === null && $keyInfo === null) { + throw new InvalidArgumentException('No key or KeyInfo available for signature verification.'); + } + + if ($verifier !== null) { + // verify using given key + // TODO: make this part of the condition, so that we support using this verifier to decrypt an encrypted key + Assert::eq( + $verifier->getAlgorithmId(), + $algId, + 'Algorithm provided in key does not match algorithm used in signature.' + ); + + return $this->verifyInternal($verifier); + } + + $factory = new SignatureAlgorithmFactory(); + foreach ($keyInfo->getInfo() as $info) { + if (!$info instanceof X509Data) { + continue; + } + + /** @var \SimpleSAML\XMLSecurity\XML\ds\X509Data $info */ + foreach ($info->getData() as $data) { + if (!$data instanceof X509Certificate) { + // not supported + continue; + } + + // build a valid PEM for the certificate + $cert = Key\X509Certificate::PEM_HEADER . "\n" . + $data->getRawContent() . "\n" . + Key\X509Certificate::PEM_FOOTER; + + $key = new Key\X509Certificate($cert); + $verifier = $factory->getAlgorithm($algId, $key); + + try { + return $this->verifyInternal($verifier); + } catch (RuntimeException $e) { + // failed to validate with this certificate, try with other, if any + } + } + } + throw new RuntimeException('Failed to validate signature.'); + } +} diff --git a/src/XML/ds/AbstractDsElement.php b/src/XML/ds/AbstractDsElement.php index 4a740195..d3c8e605 100644 --- a/src/XML/ds/AbstractDsElement.php +++ b/src/XML/ds/AbstractDsElement.php @@ -5,7 +5,7 @@ namespace SimpleSAML\XMLSecurity\XML\ds; use SimpleSAML\XML\AbstractXMLElement; -use SimpleSAML\XMLSecurity\XMLSecurityDSig; +use SimpleSAML\XMLSecurity\Constants; /** * Abstract class to be implemented by all the classes in this namespace @@ -15,7 +15,7 @@ abstract class AbstractDsElement extends AbstractXMLElement { /** @var string */ - public const NS = XMLSecurityDSig::XMLDSIGNS; + public const NS = Constants::NS_XDSIG; /** @var string */ public const NS_PREFIX = 'ds'; diff --git a/src/XML/ds/DigestMethod.php b/src/XML/ds/DigestMethod.php index 216677d0..926fb1e1 100644 --- a/src/XML/ds/DigestMethod.php +++ b/src/XML/ds/DigestMethod.php @@ -95,7 +95,8 @@ public function getElements(): array * Set the value of the elements-property * * @param \SimpleSAML\XML\Chunk[] $elements - * @throws \SimpleSAML\Assert\AssertionFailedException if the supplied array contains anything other than Chunk objects + * @throws \SimpleSAML\Assert\AssertionFailedException + * if the supplied array contains anything other than Chunk objects */ private function setElements(array $elements): void { diff --git a/src/XML/ds/KeyInfo.php b/src/XML/ds/KeyInfo.php index bf73ea2e..e8803917 100644 --- a/src/XML/ds/KeyInfo.php +++ b/src/XML/ds/KeyInfo.php @@ -146,7 +146,7 @@ public static function fromXML(DOMElement $xml): object $info[] = new Chunk($n); break; } - } elseif ($n->namespaceURI === Constants::XMLENCNS) { + } elseif ($n->namespaceURI === Constants::NS_XENC) { switch ($n->localName) { case 'EncryptedData': $info[] = EncryptedData::fromXML($n); diff --git a/src/XML/ds/Reference.php b/src/XML/ds/Reference.php index 3977f73c..cfc47b97 100644 --- a/src/XML/ds/Reference.php +++ b/src/XML/ds/Reference.php @@ -9,6 +9,7 @@ use SimpleSAML\XML\Exception\InvalidDOMElementException; use function array_pop; +use function preg_match; /** * Class representing a ds:Reference element. @@ -171,6 +172,17 @@ private function setURI(?string $URI): void } + /** + * Determine whether this is an xpointer reference. + * + * @return bool + */ + public function isXPointer(): bool + { + return !empty($this->URI) && preg_match('/^#xpointer\(.+\)$/', $this->URI); + } + + /** * Convert XML into a Reference element * diff --git a/src/XML/ds/Signature.php b/src/XML/ds/Signature.php index b7005b1c..296928df 100644 --- a/src/XML/ds/Signature.php +++ b/src/XML/ds/Signature.php @@ -7,8 +7,8 @@ use DOMElement; use SimpleSAML\Assert\Assert; use SimpleSAML\XML\Chunk; -use SimpleSAML\XML\Constants; use SimpleSAML\XML\Exception\InvalidDOMElementException; +use SimpleSAML\XMLSecurity\Constants; use function array_pop; diff --git a/src/XML/ds/SignedInfo.php b/src/XML/ds/SignedInfo.php index 8a8b2d25..eb4359b7 100644 --- a/src/XML/ds/SignedInfo.php +++ b/src/XML/ds/SignedInfo.php @@ -7,7 +7,8 @@ use DOMElement; use SimpleSAML\Assert\Assert; use SimpleSAML\XML\Exception\InvalidDOMElementException; -use SimpleSAML\XML\Chunk; +use SimpleSAML\XMLSecurity\XML\CanonicalizableElementInterface; +use SimpleSAML\XMLSecurity\XML\CanonicalizableElementTrait; use function array_pop; @@ -16,8 +17,10 @@ * * @package simplesamlphp/xml-security */ -final class SignedInfo extends AbstractDsElement +final class SignedInfo extends AbstractDsElement implements CanonicalizableElementInterface { + use CanonicalizableElementTrait; + /** @var string|null */ protected ?string $Id; @@ -36,9 +39,14 @@ final class SignedInfo extends AbstractDsElement */ protected array $references; + /** + * @var DOMElement + */ + protected ?DOMElement $xml = null; + /** - * Initialize a SignedIfno. + * Initialize a SignedInfo. * * @param \SimpleSAML\XMLSecurity\XML\ds\CanonicalizationMethod $canonicalizationMethod * @param \SimpleSAML\XMLSecurity\XML\ds\SignatureMethod $signatureMethod @@ -148,6 +156,18 @@ private function setId(?string $Id): void } + /** + * @inheritDoc + */ + protected function getOriginalXML(): DOMElement + { + if ($this->xml !== null) { + return $this->xml; + } + return $this->toXML(); + } + + /** * Convert XML into a SignedInfo instance * @@ -165,7 +185,11 @@ public static function fromXML(DOMElement $xml): object $Id = self::getAttribute($xml, 'Id', null); $canonicalizationMethod = CanonicalizationMethod::getChildrenOfClass($xml); - Assert::count($canonicalizationMethod, 1, 'A ds:SignedInfo element must contain exactly one ds:CanonicalizationMethod'); + Assert::count( + $canonicalizationMethod, + 1, + 'A ds:SignedInfo element must contain exactly one ds:CanonicalizationMethod' + ); $signatureMethod = SignatureMethod::getChildrenOfClass($xml); Assert::count($signatureMethod, 1, 'A ds:SignedInfo element must contain exactly one ds:SignatureMethod'); @@ -173,12 +197,9 @@ public static function fromXML(DOMElement $xml): object $references = Reference::getChildrenOfClass($xml); Assert::minCount($references, 1, 'A ds:SignedInfo element must contain at least one ds:Reference'); - return new self( - array_pop($canonicalizationMethod), - array_pop($signatureMethod), - $references, - $Id - ); + $signedInfo = new self(array_pop($canonicalizationMethod), array_pop($signatureMethod), $references, $Id); + $signedInfo->xml = $xml; + return $signedInfo; } diff --git a/src/XML/ds/X509Data.php b/src/XML/ds/X509Data.php index 758b2c11..851e61af 100644 --- a/src/XML/ds/X509Data.php +++ b/src/XML/ds/X509Data.php @@ -8,7 +8,6 @@ use SimpleSAML\Assert\Assert; use SimpleSAML\XML\Exception\InvalidDOMElementException; use SimpleSAML\XML\Chunk; -use SimpleSAML\XMLSecurity\XML\ds\X509Certificate; /** * Class representing a ds:X509Data element. diff --git a/src/XML/ds/XPath.php b/src/XML/ds/XPath.php index 61f90ba2..88a9de94 100644 --- a/src/XML/ds/XPath.php +++ b/src/XML/ds/XPath.php @@ -6,6 +6,7 @@ use DOMElement; use SimpleSAML\XML\Exception\InvalidDOMElementException; +use SimpleSAML\XMLSecurity\Utils\XPath as XPathUtils; use Webmozart\Assert\Assert; use function str_replace; @@ -108,12 +109,12 @@ public static function fromXML(DOMElement $xml): object Assert::same($xml->namespaceURI, self::NS, InvalidDOMElementException::class); $namespaces = []; - $xpath = new \DOMXPath($xml->ownerDocument); + $xpath = XPathUtils::getXPath($xml->ownerDocument); /** @var \DOMNode $ns */ - foreach ($xpath->query('namespace::*', $xml) as $ns) { + foreach (XPathUtils::xpQuery($xml, './namespace::*', $xpath) as $ns) { if ($xml->getAttributeNode($ns->nodeName)) { - $namespaces[str_replace('xmlns:', '', $ns->nodeName)] = - $xml->getAttribute($ns->nodeName); + // only add namespaces when they are defined explicitly in an attribute + $namespaces[$ns->localName] = $xml->getAttribute($ns->nodeName); } } diff --git a/src/XML/ec/AbstractEcElement.php b/src/XML/ec/AbstractEcElement.php index 5e1fa352..15fcf7d2 100644 --- a/src/XML/ec/AbstractEcElement.php +++ b/src/XML/ec/AbstractEcElement.php @@ -7,10 +7,9 @@ use SimpleSAML\XML\AbstractXMLElement; use SimpleSAML\XMLSecurity\Constants; - /** * Abstract class to be implemented by all the classes in this namespace - + * * @package simplesamlphp/xml-security */ abstract class AbstractEcElement extends AbstractXMLElement @@ -20,26 +19,4 @@ abstract class AbstractEcElement extends AbstractXMLElement /** @var string */ public const NS_PREFIX = 'ec'; - - - /** - * Get the namespace for the element. - * - * @return string - */ - public static function getNamespaceURI(): string - { - return static::NS; - } - - - /** - * Get the namespace prefix for the element. - * - * @return string - */ - public static function getNamespacePrefix(): string - { - return static::NS_PREFIX; - } -} \ No newline at end of file +} diff --git a/src/XML/ec/InclusiveNamespaces.php b/src/XML/ec/InclusiveNamespaces.php index dbe6bd23..5f030de9 100644 --- a/src/XML/ec/InclusiveNamespaces.php +++ b/src/XML/ec/InclusiveNamespaces.php @@ -65,7 +65,7 @@ public static function fromXML(DOMElement $xml): object { $prefixes = self::getAttribute($xml, 'PrefixList', ''); - return new self(explode(' ', $prefixes)); + return new self(array_filter(explode(' ', $prefixes))); } /** diff --git a/src/XML/xenc/AbstractEncryptionMethod.php b/src/XML/xenc/AbstractEncryptionMethod.php index fbe2e3ac..c6c7c545 100644 --- a/src/XML/xenc/AbstractEncryptionMethod.php +++ b/src/XML/xenc/AbstractEncryptionMethod.php @@ -84,7 +84,7 @@ public static function fromXML(DOMElement $xml): object foreach ($xml->childNodes as $node) { if (!$node instanceof DOMElement) { continue; - } elseif ($node->namespaceURI === Constants::XMLENCNS) { + } elseif ($node->namespaceURI === Constants::NS_XENC) { if ($node->localName === 'KeySize') { Assert::null( $keySize, @@ -232,12 +232,12 @@ public function toXML(DOMElement $parent = null): DOMElement $e->setAttribute('Algorithm', $this->algorithm); if ($this->keySize !== null) { - $keySize = $e->ownerDocument->createElementNS(Constants::XMLENCNS, 'xenc:KeySize', strval($this->keySize)); + $keySize = $e->ownerDocument->createElementNS(Constants::NS_XENC, 'xenc:KeySize', strval($this->keySize)); $e->appendChild($keySize); } if ($this->oaepParams !== null) { - $oaepParams = $e->ownerDocument->createElementNS(Constants::XMLENCNS, 'xenc:OAEPParams', $this->oaepParams); + $oaepParams = $e->ownerDocument->createElementNS(Constants::NS_XENC, 'xenc:OAEPParams', $this->oaepParams); $e->appendChild($oaepParams); } diff --git a/src/XML/xenc/AbstractXencElement.php b/src/XML/xenc/AbstractXencElement.php index f1fdf149..fd5aa38f 100644 --- a/src/XML/xenc/AbstractXencElement.php +++ b/src/XML/xenc/AbstractXencElement.php @@ -15,7 +15,7 @@ abstract class AbstractXencElement extends AbstractXMLElement { /** @var string */ - public const NS = Constants::XMLENCNS; + public const NS = Constants::NS_XENC; /** @var string */ public const NS_PREFIX = 'xenc'; diff --git a/src/XML/xenc/CarriedKeyName.php b/src/XML/xenc/CarriedKeyName.php new file mode 100644 index 00000000..e1f971d5 --- /dev/null +++ b/src/XML/xenc/CarriedKeyName.php @@ -0,0 +1,26 @@ +setContent($content); + } +} diff --git a/src/XML/xenc/CipherData.php b/src/XML/xenc/CipherData.php index 66c4d7ff..93e12d89 100644 --- a/src/XML/xenc/CipherData.php +++ b/src/XML/xenc/CipherData.php @@ -7,7 +7,7 @@ use DOMElement; use SimpleSAML\Assert\Assert; use SimpleSAML\XML\Exception\InvalidDOMElementException; -use SimpleSAML\XML\Utils as XMLUtils; +use SimpleSAML\XMLSecurity\Utils\XPath; use function array_pop; @@ -18,8 +18,8 @@ */ class CipherData extends AbstractXencElement { - /** @var string|null */ - protected ?string $cipherValue = null; + /** @var \SimpleSAML\XMLSecurity\XML\xenc\CipherValue|null */ + protected ?CipherValue $cipherValue = null; /** @var \SimpleSAML\XMLSecurity\XML\xenc\CipherReference|null */ protected ?CipherReference $cipherReference = null; @@ -28,10 +28,10 @@ class CipherData extends AbstractXencElement /** * CipherData constructor. * - * @param string|null $cipherValue + * @param \SimpleSAML\XMLSecurity\XML\xenc\CipherValue|null $cipherValue * @param \SimpleSAML\XMLSecurity\XML\xenc\CipherReference|null $cipherReference */ - public function __construct(?string $cipherValue, ?CipherReference $cipherReference = null) + public function __construct(?CipherValue $cipherValue, ?CipherReference $cipherReference = null) { Assert::oneOf( null, @@ -39,28 +39,32 @@ public function __construct(?string $cipherValue, ?CipherReference $cipherRefere 'Can only have one of CipherValue/CipherReference' ); + Assert::false( + is_null($cipherValue) && is_null($cipherReference), + 'You need either a CipherValue or a CipherReference' + ); + $this->setCipherValue($cipherValue); $this->setCipherReference($cipherReference); } /** - * Get the string value of the element inside this CipherData object. + * Get the value of the $cipherValue property. * - * @return string|null + * @return \SimpleSAML\XMLSecurity\XML\xenc\CipherValue|null */ - public function getCipherValue(): ?string + public function getCipherValue(): ?CipherValue { return $this->cipherValue; } /** - * @param string|null $cipherValue + * @param \SimpleSAML\XMLSecurity\XML\xenc\CipherValue|null $cipherValue */ - protected function setCipherValue(?string $cipherValue): void + protected function setCipherValue(?CipherValue $cipherValue): void { - Assert::nullOrRegex($cipherValue, '/[a-zA-Z0-9_\-=\+\/]/', 'Invalid data in .'); $this->cipherValue = $cipherValue; } @@ -96,15 +100,14 @@ public static function fromXML(DOMElement $xml): object Assert::same($xml->localName, 'CipherData', InvalidDOMElementException::class); Assert::same($xml->namespaceURI, CipherData::NS, InvalidDOMElementException::class); - $cv = XMLUtils::xpQuery($xml, './xenc:CipherValue'); - Assert::notEmpty($cv, 'Missing CipherValue element in '); - Assert::count($cv, 1, 'More than one CipherValue element in textContent, + empty($cv) ? null : array_pop($cv), empty($cr) ? null : array_pop($cr) ); } @@ -119,7 +122,7 @@ public function toXML(DOMElement $parent = null): DOMElement $e = $this->instantiateParentElement($parent); if ($this->cipherValue !== null) { - XMLUtils::addString($e, $this::NS, 'CipherValue', $this->cipherValue); + $this->cipherValue->toXML($e); } if ($this->cipherReference !== null) { diff --git a/src/XML/xenc/CipherValue.php b/src/XML/xenc/CipherValue.php new file mode 100644 index 00000000..4593d61c --- /dev/null +++ b/src/XML/xenc/CipherValue.php @@ -0,0 +1,26 @@ +setContent($content); + } +} diff --git a/src/XML/xenc/EncryptedKey.php b/src/XML/xenc/EncryptedKey.php index 4cc8a491..94244528 100644 --- a/src/XML/xenc/EncryptedKey.php +++ b/src/XML/xenc/EncryptedKey.php @@ -19,8 +19,8 @@ */ class EncryptedKey extends AbstractEncryptedType { - /** @var string|null */ - protected ?string $carriedKeyName; + /** @var \SimpleSAML\XMLSecurity\XML\xenc\CarriedKeyName|null */ + protected ?CarriedKeyName $carriedKeyName; /** @var string|null */ protected ?string $recipient; @@ -38,7 +38,8 @@ class EncryptedKey extends AbstractEncryptedType * @param string|null $mimeType The MimeType attribute of this object. Optional. * @param string|null $encoding The Encoding attribute of this object. Optional. * @param string|null $recipient The Recipient attribute of this object. Optional. - * @param string|null $carriedKeyName The value of the CarriedKeyName element of this EncryptedData. + * @param \SimpleSAML\XMLSecurity\XML\xenc\CarriedKeyName|null $carriedKeyName + * The value of the CarriedKeyName element of this EncryptedData. * @param \SimpleSAML\XMLSecurity\XML\xenc\EncryptionMethod|null $encryptionMethod * The EncryptionMethod object of this EncryptedData. Optional. * @param \SimpleSAML\XMLSecurity\XML\ds\KeyInfo|null $keyInfo The KeyInfo object of this EncryptedData. Optional. @@ -52,7 +53,7 @@ public function __construct( ?string $mimeType = null, ?string $encoding = null, ?string $recipient = null, - ?string $carriedKeyName = null, + ?CarriedKeyName $carriedKeyName = null, ?EncryptionMethod $encryptionMethod = null, ?KeyInfo $keyInfo = null, ?ReferenceList $referenceList = null @@ -65,20 +66,20 @@ public function __construct( /** - * Get the value of the CarriedKeyName element. + * Get the value of the CarriedKeyName property. * - * @return string|null + * @return \SimpleSAML\XMLSecurity\XML\xenc\CarriedKeyName|null */ - public function getCarriedKeyName(): ?string + public function getCarriedKeyName(): ?CarriedKeyName { return $this->carriedKeyName; } /** - * @param string|null $carriedKeyName + * @param \SimpleSAML\XMLSecurity\XML\xenc\CarriedKeyName|null $carriedKeyName */ - protected function setCarriedKeyName(?string $carriedKeyName): void + protected function setCarriedKeyName(?CarriedKeyName $carriedKeyName): void { $this->carriedKeyName = $carriedKeyName; } @@ -151,7 +152,7 @@ public static function fromXML(DOMElement $xml): object $referenceLists = ReferenceList::getChildrenOfClass($xml); Assert::maxCount($keyInfo, 1, 'Only one ReferenceList element allowed in .'); - $carriedKeyNames = XMLUtils::xpQuery($xml, './xenc:CarriedKeyName'); + $carriedKeyNames = CarriedKeyName::getChildrenOfClass($xml); Assert::maxCount($carriedKeyNames, 1, 'Only one CarriedKeyName element allowed in .'); return new self( @@ -161,10 +162,10 @@ public static function fromXML(DOMElement $xml): object self::getAttribute($xml, 'MimeType', null), self::getAttribute($xml, 'Encoding', null), self::getAttribute($xml, 'Recipient', null), - count($carriedKeyNames) === 1 ? $carriedKeyNames[0]->textContent : null, - count($encryptionMethod) === 1 ? $encryptionMethod[0] : null, - count($keyInfo) === 1 ? $keyInfo[0] : null, - count($referenceLists) === 1 ? $referenceLists[0] : null + array_pop($carriedKeyNames), + array_pop($encryptionMethod), + array_pop($keyInfo), + array_pop($referenceLists) ); } @@ -182,9 +183,7 @@ public function toXML(DOMElement $parent = null): DOMElement } if ($this->carriedKeyName !== null) { - $ckn = $e->ownerDocument->createElementNS(self::NS, self::NS_PREFIX . ':CarriedKeyName'); - $ckn->textContent = $this->carriedKeyName; - $e->appendChild($ckn); + $this->carriedKeyName->toXML($e); } if ($this->recipient !== null) { diff --git a/src/XMLSecurityDSig.php b/src/XMLSecurityDSig.php deleted file mode 100644 index 6646c77c..00000000 --- a/src/XMLSecurityDSig.php +++ /dev/null @@ -1,49 +0,0 @@ -. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * * Neither the name of Robert Richards nor the names of his - * contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, - * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN - * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * - * @copyright 2007-2017 Robert Richards - * @license http://www.opensource.org/licenses/bsd-license.php BSD License - */ - -class XMLSecurityDSig extends \RobRichards\XMLSecLibs\XMLSecurityDSig -{ -} diff --git a/tests/Alg/SignatureAlgorithmFactoryTest.php b/tests/Alg/SignatureAlgorithmFactoryTest.php index 2789cb09..e251200f 100644 --- a/tests/Alg/SignatureAlgorithmFactoryTest.php +++ b/tests/Alg/SignatureAlgorithmFactoryTest.php @@ -1,5 +1,7 @@ expectException(RuntimeException::class); - $factory->getAlgorithm('Unknown alg', $this->skey); + $this->expectException(InvalidArgumentException::class); + $factory->getAlgorithm('Unknown algorithm identifier', $this->skey); } diff --git a/tests/XML/CustomSignable.php b/tests/XML/CustomSignable.php new file mode 100644 index 00000000..5210d6c8 --- /dev/null +++ b/tests/XML/CustomSignable.php @@ -0,0 +1,162 @@ +setXML($xml); + $this->id = $id; + } + + + /** + * Get the namespace for the element. + * + * @return string + */ + public static function getNamespaceURI(): string + { + return static::NS; + } + + + /** + * Get the namespace-prefix for the element. + * + * @return string + */ + public static function getNamespacePrefix(): string + { + return static::NS_PREFIX; + } + + + /** + * Get the XML element. + * + * @return \DOMElement + */ + public function getXML(): DOMElement + { + return $this->xml; + } + + + /** + * Set the XML element. + * + * @param \DOMElement $xml + */ + private function setXML(DOMElement $xml): void + { + $this->xml = $xml; + } + + + /** + * @return string|null + */ + public function getId(): ?string + { + return $this->id; + } + + + /** + * @inheritDoc + */ + protected function getOriginalXML(): DOMElement + { + return $this->xml; + } + + + /** + * Convert XML into a CustomSignable + * + * @param \DOMElement $xml The XML element we should load + * @return \SimpleSAML\XMLSecurity\Test\XML\CustomSignable + * + * @throws \SimpleSAML\XML\Exception\InvalidDOMElementException + * if the qualified name of the supplied element is wrong + */ + public static function fromXML(DOMElement $xml): object + { + Assert::same($xml->localName, 'CustomSignable', InvalidDOMElementException::class); + Assert::same($xml->namespaceURI, static::NS, InvalidDOMElementException::class); + + $id = self::getAttribute($xml, 'id', null); + $signature = Signature::getChildrenOfClass($xml); + Assert::maxCount($signature, 1, TooManyElementsException::class); + + $customSignable = new self($xml, $id); + if (!empty($signature)) { + $customSignable->signature = $signature[0]; + } + return $customSignable; + } + + + /** + * Convert this CustomSignable to XML. + * + * @param \DOMElement|null $parent The parent element to append this CustomSignable to. + * @return \DOMElement The XML element after adding the data corresponding to this CustomSignable. + * @throws \Exception + */ + public function toXML(DOMElement $parent = null): DOMElement + { + if ($this->signer !== null) { + $signedXML = $this->doSign($this->xml); + $signedXML->insertBefore($this->signature->toXML($signedXML), $signedXML->firstChild); + return $signedXML; + } + + return $this->xml; + } +} diff --git a/tests/XML/SignableElementTest.php b/tests/XML/SignableElementTest.php new file mode 100644 index 00000000..def98d67 --- /dev/null +++ b/tests/XML/SignableElementTest.php @@ -0,0 +1,259 @@ +testedClass = CustomSignable::class; + + $this->xmlRepresentation = DOMDocumentFactory::fromFile( + dirname(dirname(__FILE__)) . '/resources/xml/custom_CustomSignable.xml' + ); + + $this->signed = DOMDocumentFactory::fromFile( + dirname(dirname(__FILE__)) . '/resources/xml/custom_CustomSigned.xml' + ); + + $certificate = file_get_contents( + dirname(dirname(__FILE__)) . '/resources/certificates/rsa-pem/selfsigned.simplesamlphp.org.crt' + ); + $certificateLines = explode("\n", trim($certificate)); + array_pop($certificateLines); + array_shift($certificateLines); + $this->certificate = join("\n", $certificateLines); + + $this->key = PrivateKey::fromFile( + dirname(dirname(__FILE__)) . '/resources/certificates/rsa-pem/selfsigned.simplesamlphp.org_nopasswd.key' + ); + } + + + /** + * Test that signing produces the expected output. + * + * In this test we try to sign an entire document, since the element is the root of it, and doesn't have an ID. + */ + public function testSigningDocument(): void + { + $customSignable = CustomSignable::fromXML($this->xmlRepresentation->documentElement); + $this->assertFalse($customSignable->isEmptyElement()); + + $factory = new SignatureAlgorithmFactory(); + $signer = $factory->getAlgorithm(Constants::SIG_RSA_SHA256, $this->key); + + $keyInfo = new KeyInfo([ + new X509Data([ + new X509Certificate($this->certificate) + ]) + ]); + + $customSignable->sign($signer, Constants::C14N_EXCLUSIVE_WITHOUT_COMMENTS, $keyInfo); + + $this->assertEquals( + $this->signed->saveXML($this->signed->documentElement), + strval($customSignable) + ); + } + + + /** + * Test that signing an element works. + * + * This test implies signing an element. Since the element itself has an ID, we use that to create our reference. + */ + public function testSigningElement(): void + { + $xml = DOMDocumentFactory::fromString( + 'Chunk' . + '' + ); + $customSignable = CustomSignable::fromXML($xml->documentElement); + $this->assertFalse($customSignable->isEmptyElement()); + + $factory = new SignatureAlgorithmFactory(); + $signer = $factory->getAlgorithm(Constants::SIG_RSA_SHA256, $this->key); + + $keyInfo = new KeyInfo([ + new X509Data([ + new X509Certificate($this->certificate) + ]) + ]); + + $customSignable->sign($signer, Constants::C14N_EXCLUSIVE_WITHOUT_COMMENTS, $keyInfo); + $signed = DOMDocumentFactory::fromFile( + dirname(dirname(__FILE__)) . '/resources/xml/custom_CustomSignedElement.xml' + ); + + $this->assertEquals( + $signed->saveXML($signed->documentElement), + strval($customSignable) + ); + } + + + /** + * Test that signing a document with comments works. + * + * This tests attempts to sign a document with comments, and verifies that the resulting reference is an xpointer + * pointing to the root of the document. + */ + public function testSigningDocumentWithComments(): void + { + $xml = DOMDocumentFactory::fromString( + 'Chunk' . + '' + ); + $customSignable = CustomSignable::fromXML($xml->documentElement); + $this->assertFalse($customSignable->isEmptyElement()); + + $factory = new SignatureAlgorithmFactory(); + $signer = $factory->getAlgorithm(Constants::SIG_RSA_SHA256, $this->key); + + $keyInfo = new KeyInfo([ + new X509Data([ + new X509Certificate($this->certificate) + ]) + ]); + + $customSignable->sign($signer, Constants::C14N_EXCLUSIVE_WITH_COMMENTS, $keyInfo); + $signed = DOMDocumentFactory::fromFile( + dirname(dirname(__FILE__)) . '/resources/xml/custom_CustomSignedWithComments.xml' + ); + + $this->assertEquals( + $signed->saveXML($signed->documentElement), + strval($customSignable) + ); + } + + + /** + * Test that signing an element with an ID including comments works. + * + * This test attempts to sign an element with an ID, using exclusive canonicalization with comments. The resulting + * reference should be an xpointer specifying the ID of the element. + */ + public function testSigningElementWithIdAndComments(): void + { + $xml = DOMDocumentFactory::fromString( + 'Chunk' . + '' + ); + $customSignable = CustomSignable::fromXML($xml->documentElement); + $this->assertFalse($customSignable->isEmptyElement()); + + $factory = new SignatureAlgorithmFactory(); + $signer = $factory->getAlgorithm(Constants::SIG_RSA_SHA256, $this->key); + + $keyInfo = new KeyInfo([ + new X509Data([ + new X509Certificate($this->certificate) + ]) + ]); + + $customSignable->sign($signer, Constants::C14N_EXCLUSIVE_WITH_COMMENTS, $keyInfo); + $signed = DOMDocumentFactory::fromFile( + dirname(dirname(__FILE__)) . '/resources/xml/custom_CustomSignedWithCommentsAndId.xml' + ); + + $this->assertEquals( + $signed->saveXML($signed->documentElement), + strval($customSignable) + ); + } + + + /** + * Test that signing an object with a document reference fails if there's no document root. + * + * This test attempts to sign a document with an element without an ID that's not marked as its root. This should + * fail since we cannot use a self-document reference (because the element is not the root), and we don't have an + * ID for the element, so we have no way to refer to it. + */ + public function testSigningDocumentWithoutRoot(): void + { + $doc = DOMDocumentFactory::create(); + $node = $doc->importNode($this->xmlRepresentation->documentElement, true); + $customSignable = CustomSignable::fromXML($node); + $factory = new SignatureAlgorithmFactory(); + $signer = $factory->getAlgorithm(Constants::SIG_RSA_SHA256, $this->key); + $customSignable->sign($signer); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Cannot create a document reference without a root element in the document.'); + $customSignable->toXML(); + } + + + /** + * Test that signing an object with a document reference fails if the object is not the document's root. + * + * This test attempts to sign an element without an ID, forcing us to use a self-document reference. However, the + * document contains another element before the one we try to sign, and that other element is marked as the root + * of the document. We cannot therefore create the self-document reference because the element we try to sign is + * not the root, and we should fail accordingly. + */ + public function testSigningWithDifferentRoot(): void + { + $doc = DOMDocumentFactory::fromString('bar'); + $node = $doc->importNode($this->xmlRepresentation->documentElement, true); + $doc->appendChild($node); + $customSignable = CustomSignable::fromXML($node); + $factory = new SignatureAlgorithmFactory(); + $signer = $factory->getAlgorithm(Constants::SIG_RSA_SHA256, $this->key); + $customSignable->sign($signer); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage( + 'Cannot create a document reference when signing an object that is not the root of the document. Please ' . + 'give your object an identifier.' + ); + $customSignable->toXML($doc->documentElement); + } +} diff --git a/tests/XML/SignedElementTest.php b/tests/XML/SignedElementTest.php new file mode 100644 index 00000000..c0d2d2bd --- /dev/null +++ b/tests/XML/SignedElementTest.php @@ -0,0 +1,216 @@ +signedDocumentWithComments = DOMDocumentFactory::fromFile( + dirname(dirname(__FILE__)) . '/resources/xml/custom_CustomSignedWithComments.xml' + )->documentElement; + + $this->signedDocument = DOMDocumentFactory::fromFile( + dirname(dirname(__FILE__)) . '/resources/xml/custom_CustomSigned.xml' + )->documentElement; + + $this->tamperedDocument = DOMDocumentFactory::fromFile( + dirname(dirname(__FILE__)) . '/resources/xml/custom_CustomSignedTampered.xml' + )->documentElement; + + $this->certificate = file_get_contents( + dirname(dirname(__FILE__)) . '/resources/certificates/rsa-pem/selfsigned.simplesamlphp.org.crt' + ); + } + + + /** + * Test creating a signed object from its XML representation. + */ + public function testUnmarshalling(): void + { + $customSigned = CustomSignable::fromXML($this->signedDocument); + + $this->assertEquals( + $this->signedDocument->ownerDocument->saveXML($this->signedDocument), + strval($customSigned) + ); + } + + + /** + * Test the verification of a signature with a given key. + */ + public function testSuccessfulVerifyingWithGivenKey(): void + { + $customSigned = CustomSignable::fromXML($this->signedDocument); + + $this->assertTrue($customSigned->isSigned()); + $signature = $customSigned->getSignature(); + $this->assertInstanceOf(Signature::class, $signature); + $sigAlg = $signature->getSignedInfo()->getSignatureMethod()->getAlgorithm(); + $this->assertEquals(Constants::SIG_RSA_SHA256, $sigAlg); + $factory = new SignatureAlgorithmFactory(); + $certificate = new X509Certificate($this->certificate); + $verifier = $factory->getAlgorithm($sigAlg, $certificate); + + $verified = $customSigned->verify($verifier); + $this->assertInstanceOf(CustomSignable::class, $verified); + $this->assertFalse($verified->isSigned()); + $this->assertEquals( + 'Chunk', + strval($verified) + ); + $this->assertEquals($certificate, $verified->getValidatingKey()); + } + + + /** + * Test the verification of a signature without passing a key, just what's in KeyInfo + */ + public function testSuccessfulVerifyingWithoutKey(): void + { + $customSigned = CustomSignable::fromXML($this->signedDocument); + + $this->assertTrue($customSigned->isSigned()); + $signature = $customSigned->getSignature(); + $this->assertInstanceOf(Signature::class, $signature); + $sigAlg = $signature->getSignedInfo()->getSignatureMethod()->getAlgorithm(); + $this->assertEquals(Constants::SIG_RSA_SHA256, $sigAlg); + $certificate = new X509Certificate($this->certificate); + + $verified = $customSigned->verify(); + $this->assertInstanceOf(CustomSignable::class, $verified); + $this->assertFalse($verified->isSigned()); + $this->assertEquals( + 'Chunk', + strval($verified) + ); + $validatingKey = $verified->getValidatingKey(); + $this->assertInstanceOf(X509Certificate::class, $validatingKey); + /** @var \SimpleSAML\XMLSecurity\Key\X509Certificate $validatingKey */ + $this->assertEquals($certificate->getCertificate(), $validatingKey->getCertificate()); + } + + + /** + * Test that verifying a tampered signature, without giving a key for verification, fails as expected. + */ + public function testVerifyingTamperedSignatureWithoutKeyFails(): void + { + $customSigned = CustomSignable::fromXML($this->tamperedDocument); + + $this->assertTrue($customSigned->isSigned()); + $signature = $customSigned->getSignature(); + $this->assertInstanceOf(Signature::class, $signature); + $sigAlg = $signature->getSignedInfo()->getSignatureMethod()->getAlgorithm(); + $this->assertEquals(Constants::SIG_RSA_SHA256, $sigAlg); + + $this->expectException(RuntimeException::class); + $this->expectDeprecationMessage('Failed to validate signature.'); + $customSigned->verify(); + } + + + /** + * Test that verifying a tampered signature with a given key fails as expected. + */ + public function testVerifyingTamperedSignatureWithKeyFails(): void + { + $customSigned = CustomSignable::fromXML($this->tamperedDocument); + + $this->assertTrue($customSigned->isSigned()); + $signature = $customSigned->getSignature(); + $this->assertInstanceOf(Signature::class, $signature); + $sigAlg = $signature->getSignedInfo()->getSignatureMethod()->getAlgorithm(); + $this->assertEquals(Constants::SIG_RSA_SHA256, $sigAlg); + $factory = new SignatureAlgorithmFactory(); + $certificate = new X509Certificate($this->certificate); + $verifier = $factory->getAlgorithm($sigAlg, $certificate); + + $this->expectException(RuntimeException::class); + $this->expectDeprecationMessage('Failed to validate signature.'); + $customSigned->verify($verifier); + } + + + + /** + * Test the verification of a signature with a given key, for an element that has comments in it. + * + * In this case, canonicalization must remove the comments, and the object resulting the verification must NOT + * have them. + */ + public function testSuccessfulVerifyingDocumentWithComments(): void + { + $customSigned = CustomSignable::fromXML($this->signedDocumentWithComments); + + $this->assertTrue($customSigned->isSigned()); + $signature = $customSigned->getSignature(); + $this->assertInstanceOf(Signature::class, $signature); + $sigAlg = $signature->getSignedInfo()->getSignatureMethod()->getAlgorithm(); + $this->assertEquals(Constants::SIG_RSA_SHA256, $sigAlg); + $factory = new SignatureAlgorithmFactory(); + $certificate = new X509Certificate($this->certificate); + $verifier = $factory->getAlgorithm($sigAlg, $certificate); + + // verify first that our dumb object normally retains comments + $this->assertEquals( + $this->signedDocumentWithComments->ownerDocument->saveXML($this->signedDocumentWithComments), + strval($customSigned) + ); + + $verified = $customSigned->verify($verifier); + $this->assertInstanceOf(CustomSignable::class, $verified); + $this->assertFalse($verified->isSigned()); + $this->assertEquals( + 'Chunk', + strval($verified) + ); + $this->assertEquals($certificate, $verified->getValidatingKey()); + } +} diff --git a/tests/XML/ds/DigestValueTest.php b/tests/XML/ds/DigestValueTest.php index 55594473..c06296a0 100644 --- a/tests/XML/ds/DigestValueTest.php +++ b/tests/XML/ds/DigestValueTest.php @@ -11,7 +11,6 @@ use SimpleSAML\XML\DOMDocumentFactory; use SimpleSAML\XMLSecurity\Test\XML\XMLDumper; use SimpleSAML\XMLSecurity\XML\ds\DigestValue; -use SimpleSAML\XMLSecurity\XMLSecurityDSig; use function dirname; use function strval; @@ -58,7 +57,7 @@ public function testMarshalling(): void public function testMarshallingNotBase64(): void { $this->expectException(AssertionFailedException::class); - $digestValue = new DigestValue('/CTj3d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI='); + new DigestValue('/CTj3d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI='); } diff --git a/tests/XML/ds/SignatureTest.php b/tests/XML/ds/SignatureTest.php index d89c1ca8..a737f7f3 100644 --- a/tests/XML/ds/SignatureTest.php +++ b/tests/XML/ds/SignatureTest.php @@ -8,7 +8,7 @@ use SimpleSAML\Test\XML\SerializableXMLTestTrait; use SimpleSAML\XML\Chunk; use SimpleSAML\XML\DOMDocumentFactory; -use SimpleSAML\XML\Utils as XMLUtils; +use SimpleSAML\XMLSecurity\Utils\XPath; use SimpleSAML\XMLSecurity\XML\ds\KeyInfo; use SimpleSAML\XMLSecurity\XML\ds\Signature; use SimpleSAML\XMLSecurity\XML\ds\SignatureValue; @@ -112,12 +112,13 @@ public function testMarshallingElementOrdering(): void ); $signatureElement = $signature->toXML(); + $xpCache = XPath::getXPath($signatureElement); - $signedInfo = XMLUtils::xpQuery($signatureElement, './ds:SignedInfo'); + $signedInfo = XPath::xpQuery($signatureElement, './ds:SignedInfo', $xpCache); $this->assertCount(1, $signedInfo); /** @psalm-var \DOMElement[] $signatureElements */ - $signatureElements = XMLUtils::xpQuery($signatureElement, './ds:SignedInfo/following-sibling::*'); + $signatureElements = XPath::xpQuery($signatureElement, './ds:SignedInfo/following-sibling::*', $xpCache); // Test ordering of Signature contents $this->assertCount(3, $signatureElements); diff --git a/tests/XML/ds/SignatureValueTest.php b/tests/XML/ds/SignatureValueTest.php index f7780706..066577b4 100644 --- a/tests/XML/ds/SignatureValueTest.php +++ b/tests/XML/ds/SignatureValueTest.php @@ -51,7 +51,8 @@ public function testMarshalling(): void 'j14G9v6AnsOiEJYgkTg864DG3e/KLqoGpuybPGSGblVTn7ST6M/BsvP7YiVZjLqJEuEvWmf2mW4DPb+pbArzzDcsLWEtNveMrw+F' . 'kWehDUQV9oe20iepo+W46wmj7zB/eWL+Z8MrGvlycoTndJU6CVwHTLsB+dq2FDa7JV4pAPjMY32JZTbiwKhzqw3nEi/eVrujJE4Y' . 'RrlW28D+rXhITfoUAGGvsqPzcwGzp02lnMe2SmXADY1u9lbVjOhUrJpgvWfn9YuiCR+wjvaGMwIwzfJxChLJZOBV+1ad1CyNTiu6' . - 'qAblxZ4F8cWlMWJ7f0KkWvtw66HOf2VNR6Qan2Ra7Q==')) + 'qAblxZ4F8cWlMWJ7f0KkWvtw66HOf2VNR6Qan2Ra7Q==' + )) ); } diff --git a/tests/XML/ds/SignedInfoTest.php b/tests/XML/ds/SignedInfoTest.php index 980dd26a..035687cd 100644 --- a/tests/XML/ds/SignedInfoTest.php +++ b/tests/XML/ds/SignedInfoTest.php @@ -89,4 +89,54 @@ public function testUnmarshalling(): void strval($signedInfo) ); } + + + /** + * + */ + public function canonicalization(\DOMElement $xml, SignedInfo $signedInfo): void + { + $this->assertEquals( + $xml->C14N(true, false), + $signedInfo->canonicalize(Constants::C14N_EXCLUSIVE_WITHOUT_COMMENTS) + ); + $this->assertEquals( + $xml->C14N(false, false), + $signedInfo->canonicalize(Constants::C14N_INCLUSIVE_WITHOUT_COMMENTS) + ); + $this->assertEquals( + $xml->C14N(true, true), + $signedInfo->canonicalize(Constants::C14N_EXCLUSIVE_WITH_COMMENTS) + ); + $this->assertEquals( + $xml->C14N(false, true), + $signedInfo->canonicalize(Constants::C14N_INCLUSIVE_WITH_COMMENTS) + ); + } + + + /** + * Test that canonicalization works fine. + */ + public function testCanonicalizaation(): void + { + $xml = DOMDocumentFactory::fromFile( + dirname(dirname(dirname(__FILE__))) . '/resources/xml/ds_SignedInfoWithComments.xml' + )->documentElement; + $signedInfo = SignedInfo::fromXML($xml); + $this->canonicalization($xml, $signedInfo); + } + + + /** + * Test that canonicalization works fine even after serializing and unserializing + */ + public function testCanonicalizationAfterSerialization(): void + { + $xml = DOMDocumentFactory::fromFile( + dirname(dirname(dirname(__FILE__))) . '/resources/xml/ds_SignedInfoWithComments.xml' + )->documentElement; + $signedInfo = unserialize(serialize(SignedInfo::fromXML($xml))); + $this->canonicalization($xml, $signedInfo); + } } diff --git a/tests/XML/ds/X509IssuerSerialTest.php b/tests/XML/ds/X509IssuerSerialTest.php index 288ab806..a9bde9cf 100644 --- a/tests/XML/ds/X509IssuerSerialTest.php +++ b/tests/XML/ds/X509IssuerSerialTest.php @@ -7,11 +7,11 @@ use DOMDocument; use PHPUnit\Framework\TestCase; use SimpleSAML\XML\DOMDocumentFactory; -use SimpleSAML\XML\Utils as XMLUtils; use SimpleSAML\XMLSecurity\Constants; use SimpleSAML\XMLSecurity\Key; use SimpleSAML\XMLSecurity\Utils\Certificate as CertificateUtils; use SimpleSAML\XMLSecurity\TestUtils\PEMCertificatesMock; +use SimpleSAML\XMLSecurity\Utils\XPath; use SimpleSAML\XMLSecurity\XML\ds\X509IssuerSerial; use SimpleSAML\XMLSecurity\XML\ds\X509IssuerName; use SimpleSAML\XMLSecurity\XML\ds\X509SerialNumber; @@ -80,11 +80,17 @@ public function testMarshallingElementOrdering(): void $X509IssuerSerial = new X509IssuerSerial($this->issuer, $this->serial); $X509IssuerSerialElement = $X509IssuerSerial->toXML(); - $issuerName = XMLUtils::xpQuery($X509IssuerSerialElement, './ds:X509IssuerName'); + $xpCache = XPath::getXPath($X509IssuerSerialElement); + + $issuerName = XPath::xpQuery($X509IssuerSerialElement, './ds:X509IssuerName', $xpCache); $this->assertCount(1, $issuerName); /** @psalm-var \DOMElement[] $X509IssuerSerialElements */ - $X509IssuerSerialElements = XMLUtils::xpQuery($X509IssuerSerialElement, './ds:X509IssuerName/following-sibling::*'); + $X509IssuerSerialElements = XPath::xpQuery( + $X509IssuerSerialElement, + './ds:X509IssuerName/following-sibling::*', + $xpCache + ); // Test ordering of X509IssuerSerial contents $this->assertCount(1, $X509IssuerSerialElements); diff --git a/tests/XML/ds/XPathTest.php b/tests/XML/ds/XPathTest.php index ce276f61..321cb349 100644 --- a/tests/XML/ds/XPathTest.php +++ b/tests/XML/ds/XPathTest.php @@ -75,5 +75,4 @@ public function testUnmarshalling(): void strval($xpath) ); } - } diff --git a/tests/XML/ec/InclusiveNamespacesTest.php b/tests/XML/ec/InclusiveNamespacesTest.php index 642e2eb7..746a747f 100644 --- a/tests/XML/ec/InclusiveNamespacesTest.php +++ b/tests/XML/ec/InclusiveNamespacesTest.php @@ -16,6 +16,7 @@ * Class \SimpleSAML\XMLSecurity\Test\XML\ec\InclusiveNamespacesTest * * @covers \SimpleSAML\XMLSecurity\XML\ec\InclusiveNamespaces + * @covers \SimpleSAML\XMLSecurity\XML\ec\AbstractEcElement * * @package simplesamlphp/xml-security */ diff --git a/tests/XML/xenc/CarriedKeyNameTest.php b/tests/XML/xenc/CarriedKeyNameTest.php new file mode 100644 index 00000000..09e71958 --- /dev/null +++ b/tests/XML/xenc/CarriedKeyNameTest.php @@ -0,0 +1,60 @@ +testedClass = CarriedKeyName::class; + + $this->xmlRepresentation = DOMDocumentFactory::fromFile( + dirname(dirname(dirname(dirname(__FILE__)))) . '/tests/resources/xml/xenc_CarriedKeyName.xml' + ); + } + + + /** + */ + public function testMarshalling(): void + { + $keyName = new CarriedKeyName('Some label'); + + $this->assertEquals( + $this->xmlRepresentation->saveXML($this->xmlRepresentation->documentElement), + strval($keyName) + ); + } + + + /** + */ + public function testUnmarshalling(): void + { + $keyName = CarriedKeyName::fromXML($this->xmlRepresentation->documentElement); + + $this->assertEquals('Some label', $keyName->getContent()); + } +} diff --git a/tests/XML/xenc/CipherDataTest.php b/tests/XML/xenc/CipherDataTest.php index c14e4307..7bacc9f8 100644 --- a/tests/XML/xenc/CipherDataTest.php +++ b/tests/XML/xenc/CipherDataTest.php @@ -10,6 +10,7 @@ use SimpleSAML\XML\Chunk; use SimpleSAML\XML\DOMDocumentFactory; use SimpleSAML\XMLSecurity\XML\xenc\CipherData; +use SimpleSAML\XMLSecurity\XML\xenc\CipherValue; use SimpleSAML\XMLSecurity\XMLSecurityDsig; use function dirname; @@ -46,7 +47,7 @@ public function setup(): void */ public function testMarshalling(): void { - $cipherData = new CipherData('c29tZSB0ZXh0'); + $cipherData = new CipherData(new CipherValue('c29tZSB0ZXh0')); $this->assertEquals( $this->xmlRepresentation->saveXML($this->xmlRepresentation->documentElement), @@ -64,6 +65,6 @@ public function testUnmarshalling(): void { $cipherData = CipherData::fromXML($this->xmlRepresentation->documentElement); - $this->assertEquals('c29tZSB0ZXh0', $cipherData->getCipherValue()); + $this->assertEquals('c29tZSB0ZXh0', $cipherData->getCipherValue()->getContent()); } } diff --git a/tests/XML/xenc/CipherValueTest.php b/tests/XML/xenc/CipherValueTest.php new file mode 100644 index 00000000..481320d7 --- /dev/null +++ b/tests/XML/xenc/CipherValueTest.php @@ -0,0 +1,72 @@ +testedClass = CipherValue::class; + + $this->xmlRepresentation = DOMDocumentFactory::fromFile( + dirname(dirname(dirname(dirname(__FILE__)))) . '/tests/resources/xml/xenc_CipherValue.xml' + ); + } + + + /** + */ + public function testMarshalling(): void + { + $cipherValue = new CipherValue('/CTj03d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI='); + + $this->assertEquals( + XMLDumper::dumpDOMDocumentXMLWithBase64Content($this->xmlRepresentation), + strval($cipherValue) + ); + } + + + /** + */ + public function testMarshallingNotBase64(): void + { + $this->expectException(AssertionFailedException::class); + new CipherValue('/CTj3d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI='); + } + + + /** + */ + public function testUnmarshalling(): void + { + $cipherValue = CipherValue::fromXML($this->xmlRepresentation->documentElement); + + $this->assertEquals('/CTj03d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI=', $cipherValue->getContent()); + } +} diff --git a/tests/XML/xenc/EncryptedDataTest.php b/tests/XML/xenc/EncryptedDataTest.php index c96a3e0f..3688f12a 100644 --- a/tests/XML/xenc/EncryptedDataTest.php +++ b/tests/XML/xenc/EncryptedDataTest.php @@ -11,6 +11,7 @@ use SimpleSAML\XML\DOMDocumentFactory; use SimpleSAML\XMLSecurity\XML\ds\KeyInfo; use SimpleSAML\XMLSecurity\XML\xenc\CipherData; +use SimpleSAML\XMLSecurity\XML\xenc\CipherValue; use SimpleSAML\XMLSecurity\XML\xenc\EncryptedData; use SimpleSAML\XMLSecurity\XML\xenc\EncryptedKey; use SimpleSAML\XMLSecurity\XML\xenc\EncryptionMethod; @@ -52,7 +53,7 @@ public function setup(): void public function testMarshalling(): void { $encryptedData = new EncryptedData( - new CipherData('iaDc7...'), + new CipherData(new CipherValue('/CTj03d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI=')), 'MyID', 'http://www.w3.org/2001/04/xmlenc#Element', 'text/plain', @@ -61,7 +62,7 @@ public function testMarshalling(): void new KeyInfo( [ new EncryptedKey( - new CipherData('nxf0b...'), + new CipherData(new CipherValue('/CTj03d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI=')), null, null, null, @@ -91,7 +92,10 @@ public function testUnmarshalling(): void $encryptedData = EncryptedData::fromXML($this->xmlRepresentation->documentElement); $cipherData = $encryptedData->getCipherData(); - $this->assertEquals('iaDc7...', $cipherData->getCipherValue()); + $this->assertEquals( + '/CTj03d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI=', + $cipherData->getCipherValue()->getContent() + ); $encryptionMethod = $encryptedData->getEncryptionMethod(); $this->assertEquals('http://www.w3.org/2001/04/xmlenc#aes128-cbc', $encryptionMethod->getAlgorithm()); diff --git a/tests/XML/xenc/EncryptedKeyTest.php b/tests/XML/xenc/EncryptedKeyTest.php index f166ad9f..d3c8248c 100644 --- a/tests/XML/xenc/EncryptedKeyTest.php +++ b/tests/XML/xenc/EncryptedKeyTest.php @@ -9,9 +9,11 @@ use SimpleSAML\Test\XML\SerializableXMLTestTrait; use SimpleSAML\XML\Chunk; use SimpleSAML\XML\DOMDocumentFactory; -use SimpleSAML\XML\Utils as XMLUtils; +use SimpleSAML\XMLSecurity\Utils\XPath; use SimpleSAML\XMLSecurity\XML\ds\KeyInfo; +use SimpleSAML\XMLSecurity\XML\xenc\CarriedKeyName; use SimpleSAML\XMLSecurity\XML\xenc\CipherData; +use SimpleSAML\XMLSecurity\XML\xenc\CipherValue; use SimpleSAML\XMLSecurity\XML\xenc\DataReference; use SimpleSAML\XMLSecurity\XML\xenc\EncryptedKey; use SimpleSAML\XMLSecurity\XML\xenc\EncryptionMethod; @@ -50,22 +52,22 @@ public function setup(): void /** - */ +- */ public function testMarshalling(): void { $encryptedKey = new EncryptedKey( - new CipherData('PzA5X...'), + new CipherData(new CipherValue('/CTj03d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI=')), 'Encrypted_KEY_ID', 'http://www.w3.org/2001/04/xmlenc#Element', 'text/plain', 'someEncoding', 'some_ENTITY_ID', - 'Name of the key', + new CarriedKeyName('Name of the key'), new EncryptionMethod('http://www.w3.org/2001/04/xmlenc#rsa-1_5'), new KeyInfo( [ new EncryptedKey( - new CipherData('nxf0b...'), + new CipherData(new CipherValue('/CTj03d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI=')), null, null, null, @@ -91,18 +93,18 @@ public function testMarshalling(): void public function testMarshallingElementOrdering(): void { $encryptedKey = new EncryptedKey( - new CipherData('PzA5X...'), + new CipherData(new CipherValue('/CTj03d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI=')), 'Encrypted_KEY_ID', 'http://www.w3.org/2001/04/xmlenc#Element', 'text/plain', 'someEncoding', 'some_ENTITY_ID', - 'Name of the key', + new CarriedKeyName('Name of the key'), new EncryptionMethod('http://www.w3.org/2001/04/xmlenc#rsa-1_5'), new KeyInfo( [ new EncryptedKey( - new CipherData('nxf0b...'), + new CipherData(new CipherValue('/CTj03d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI=')), null, null, null, @@ -119,14 +121,20 @@ public function testMarshallingElementOrdering(): void // Marshall it to a \DOMElement $encryptedKeyElement = $encryptedKey->toXML(); + $xpCache = XPath::getXPath($encryptedKeyElement); // Test for a ReferenceList - $encryptedKeyElements = XMLUtils::xpQuery($encryptedKeyElement, './xenc:ReferenceList'); + $encryptedKeyElements = XPath::xpQuery( + $encryptedKeyElement, + './xenc:ReferenceList', + $xpCache + ); $this->assertCount(1, $encryptedKeyElements); // Test ordering of EncryptedKey contents - $encryptedKeyElements = XMLUtils::xpQuery( + $encryptedKeyElements = XPath::xpQuery( $encryptedKeyElement, - './xenc:ReferenceList/following-sibling::*' + './xenc:ReferenceList/following-sibling::*', + $xpCache ); $this->assertCount(1, $encryptedKeyElements); $this->assertEquals('xenc:CarriedKeyName', $encryptedKeyElements[0]->tagName); @@ -143,7 +151,10 @@ public function testUnmarshalling(): void $encryptedKey = EncryptedKey::fromXML($this->xmlRepresentation->documentElement); $cipherData = $encryptedKey->getCipherData(); - $this->assertEquals('PzA5X...', $cipherData->getCipherValue()); + $this->assertEquals( + '/CTj03d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI=', + $cipherData->getCipherValue()->getContent() + ); $encryptionMethod = $encryptedKey->getEncryptionMethod(); $this->assertEquals('http://www.w3.org/2001/04/xmlenc#rsa-1_5', $encryptionMethod->getAlgorithm()); @@ -166,7 +177,7 @@ public function testUnmarshalling(): void $this->assertEquals('text/plain', $encryptedKey->getMimeType()); $this->assertEquals('Encrypted_KEY_ID', $encryptedKey->getID()); $this->assertEquals('some_ENTITY_ID', $encryptedKey->getRecipient()); - $this->assertEquals('Name of the key', $encryptedKey->getCarriedKeyName()); + $this->assertEquals('Name of the key', $encryptedKey->getCarriedKeyName()->getContent()); $this->assertEquals( $this->xmlRepresentation->saveXML($this->xmlRepresentation->documentElement), diff --git a/tests/XML/xenc/EncryptionMethodTest.php b/tests/XML/xenc/EncryptionMethodTest.php index 051f50c5..e55076d9 100644 --- a/tests/XML/xenc/EncryptionMethodTest.php +++ b/tests/XML/xenc/EncryptionMethodTest.php @@ -4,14 +4,12 @@ namespace SimpleSAML\XMLSecurity\Test\XML\xenc; -use DOMDocument; use PHPUnit\Framework\TestCase; use SimpleSAML\Test\XML\SerializableXMLTestTrait; -use SimpleSAML\Assert\AssertionFailedException; use SimpleSAML\XML\Chunk; use SimpleSAML\XML\DOMDocumentFactory; use SimpleSAML\XML\Exception\MissingAttributeException; -use SimpleSAML\XML\Utils as XMLUtils; +use SimpleSAML\XMLSecurity\Utils\XPath; use SimpleSAML\XMLSecurity\XML\xenc\EncryptionMethod; use SimpleSAML\XMLSecurity\Constants; @@ -70,7 +68,7 @@ public function testMarshallingWithoutOptionalParameters(): void { $em = new EncryptionMethod('http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p'); $document = DOMDocumentFactory::fromString( - '' ); @@ -95,13 +93,15 @@ public function testMarshallingElementOrdering(): void // Marshall it to a \DOMElement $emElement = $em->toXML(); + $xpCache = XPath::getXPath($emElement); + // Test for a KeySize - $keySizeElements = XMLUtils::xpQuery($emElement, './xenc:KeySize'); + $keySizeElements = XPath::xpQuery($emElement, './xenc:KeySize', $xpCache); $this->assertCount(1, $keySizeElements); $this->assertEquals('10', $keySizeElements[0]->textContent); // Test ordering of EncryptionMethod contents - $emElements = XMLUtils::xpQuery($emElement, './xenc:KeySize/following-sibling::*'); + $emElements = XPath::xpQuery($emElement, './xenc:KeySize/following-sibling::*', $xpCache); $this->assertCount(2, $emElements); $this->assertEquals('xenc:OAEPParams', $emElements[0]->tagName); @@ -145,7 +145,7 @@ public function testUnmarshallingWithoutAlgorithm(): void */ public function testUnmarshallingWithoutOptionalParameters(): void { - $xencns = Constants::XMLENCNS; + $xencns = Constants::NS_XENC; $document = DOMDocumentFactory::fromString(<< XML diff --git a/tests/resources/xml/custom_CustomSignable.xml b/tests/resources/xml/custom_CustomSignable.xml new file mode 100644 index 00000000..ac407dd5 --- /dev/null +++ b/tests/resources/xml/custom_CustomSignable.xml @@ -0,0 +1 @@ +Chunk diff --git a/tests/resources/xml/custom_CustomSigned.xml b/tests/resources/xml/custom_CustomSigned.xml new file mode 100644 index 00000000..43af1322 --- /dev/null +++ b/tests/resources/xml/custom_CustomSigned.xml @@ -0,0 +1,15 @@ +5F9s1oK6M4QD7UH4GDU9a2Otz876kwhRbvh+oGVTCoM=V5dmpyjjOLJKFNc8uBJFKDA89OsyL+0oT5EJhVyD5jYO/aO8juNE5+btW0sLbo46DbvmXxLmyZADsT4wqR8pRH6FiBwvYK0wS4FRociNh710HrEcTghR0sCasW/VQh0F6jcRqbi8PJOLpTrtBs7LZH31marO5I0Eed9RPihLSws=MIICxDCCAi2gAwIBAgIUCJ8EYI/BrvQnHt9QFGfdqgaxDmowDQYJKoZIhvcNAQEL +BQAwczElMCMGA1UEAwwcc2VsZnNpZ25lZC5zaW1wbGVzYW1scGhwLm9yZzEZMBcG +A1UECgwQU2ltcGxlU0FNTHBocCBIUTERMA8GA1UEBwwISG9ub2x1bHUxDzANBgNV +BAgMBkhhd2FpaTELMAkGA1UEBhMCVVMwIBcNMjAwNjIzMjEwMTEyWhgPMjEyMDA1 +MzAyMTAxMTJaMHMxJTAjBgNVBAMMHHNlbGZzaWduZWQuc2ltcGxlc2FtbHBocC5v +cmcxGTAXBgNVBAoMEFNpbXBsZVNBTUxwaHAgSFExETAPBgNVBAcMCEhvbm9sdWx1 +MQ8wDQYDVQQIDAZIYXdhaWkxCzAJBgNVBAYTAlVTMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQDZ3ztmRqrJm6zSJ2jNUjbxdlX3WLadthHvDkdfQDWuFIUjMkCf +O8+OWYdFP4cQWKBVnWyO+/LXFl9p9fwcSJmhmkgz5blUw0cjv8jccHw+yfVnGxGC +K2Up/Si4HE/WStgjTKr3ivAqyc0Kc/97EoIXmb96SEEb4Lat9xt6mFSbWQIDAQAB +o1MwUTAdBgNVHQ4EFgQUHhB/tFwoDOT8V1nLi5XY4CJgTTQwHwYDVR0jBBgwFoAU +HhB/tFwoDOT8V1nLi5XY4CJgTTQwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0B +AQsFAAOBgQBVqkGuiwyYMx0FH3j/qirZ2nKrjUU+p0ganzEA7kVZ97RivW5SFLHk +RjcfqDXVyqOzmr4iR8rfxI2rUjYQI1SsKacPhitiArMk2IQ+uvxZ5eNtU2yFzW4a +rQ+a2ttlZI/GF9nLvgUNFIUwaDc+PQD2rOk+ACklbAS3jkf+L3DpQg==Chunk \ No newline at end of file diff --git a/tests/resources/xml/custom_CustomSignedElement.xml b/tests/resources/xml/custom_CustomSignedElement.xml new file mode 100644 index 00000000..be6b0667 --- /dev/null +++ b/tests/resources/xml/custom_CustomSignedElement.xml @@ -0,0 +1,15 @@ +0QMzfXh0cPsgEyn3hNdzMHkFVglAjlv9DwYe9YhlQKU=zuBh9JdjIsnhM13tbGSyI8oGujRWutc8A020Ah32xxk6TXKFO2ltVFWktJib0IBt5gR3zXECtvDaRlePYo4S58eFQTYVLsnvCINBvh5tdTWZ4Rhje3wWofeax3qdprFqxhbEqX/5zwjqap1q+VURC+msJMTUiYd+X8sERb5JtRI=MIICxDCCAi2gAwIBAgIUCJ8EYI/BrvQnHt9QFGfdqgaxDmowDQYJKoZIhvcNAQEL +BQAwczElMCMGA1UEAwwcc2VsZnNpZ25lZC5zaW1wbGVzYW1scGhwLm9yZzEZMBcG +A1UECgwQU2ltcGxlU0FNTHBocCBIUTERMA8GA1UEBwwISG9ub2x1bHUxDzANBgNV +BAgMBkhhd2FpaTELMAkGA1UEBhMCVVMwIBcNMjAwNjIzMjEwMTEyWhgPMjEyMDA1 +MzAyMTAxMTJaMHMxJTAjBgNVBAMMHHNlbGZzaWduZWQuc2ltcGxlc2FtbHBocC5v +cmcxGTAXBgNVBAoMEFNpbXBsZVNBTUxwaHAgSFExETAPBgNVBAcMCEhvbm9sdWx1 +MQ8wDQYDVQQIDAZIYXdhaWkxCzAJBgNVBAYTAlVTMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQDZ3ztmRqrJm6zSJ2jNUjbxdlX3WLadthHvDkdfQDWuFIUjMkCf +O8+OWYdFP4cQWKBVnWyO+/LXFl9p9fwcSJmhmkgz5blUw0cjv8jccHw+yfVnGxGC +K2Up/Si4HE/WStgjTKr3ivAqyc0Kc/97EoIXmb96SEEb4Lat9xt6mFSbWQIDAQAB +o1MwUTAdBgNVHQ4EFgQUHhB/tFwoDOT8V1nLi5XY4CJgTTQwHwYDVR0jBBgwFoAU +HhB/tFwoDOT8V1nLi5XY4CJgTTQwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0B +AQsFAAOBgQBVqkGuiwyYMx0FH3j/qirZ2nKrjUU+p0ganzEA7kVZ97RivW5SFLHk +RjcfqDXVyqOzmr4iR8rfxI2rUjYQI1SsKacPhitiArMk2IQ+uvxZ5eNtU2yFzW4a +rQ+a2ttlZI/GF9nLvgUNFIUwaDc+PQD2rOk+ACklbAS3jkf+L3DpQg==Chunk \ No newline at end of file diff --git a/tests/resources/xml/custom_CustomSignedTampered.xml b/tests/resources/xml/custom_CustomSignedTampered.xml new file mode 100644 index 00000000..8de612ea --- /dev/null +++ b/tests/resources/xml/custom_CustomSignedTampered.xml @@ -0,0 +1,15 @@ +5F9s1oK6M4QD7UH4GDU9a2Otz876kwhRbvh+oGVTCoM=V5dmpyjjOLJKFNc8uBJFKDA89OsyL+0oT5EJhVyD5jYO/aO8juNE5+btW0sLbo46DbvmXxLmyZADsT4wqR8pRH6FiBwvYK0wS4FRociNh710HrEcTghR0sCasW/VQh0F6jcRqbi8PJOLpTrtBs7LZH31marO5I0Eed9RPihLSws=MIICxDCCAi2gAwIBAgIUCJ8EYI/BrvQnHt9QFGfdqgaxDmowDQYJKoZIhvcNAQEL +BQAwczElMCMGA1UEAwwcc2VsZnNpZ25lZC5zaW1wbGVzYW1scGhwLm9yZzEZMBcG +A1UECgwQU2ltcGxlU0FNTHBocCBIUTERMA8GA1UEBwwISG9ub2x1bHUxDzANBgNV +BAgMBkhhd2FpaTELMAkGA1UEBhMCVVMwIBcNMjAwNjIzMjEwMTEyWhgPMjEyMDA1 +MzAyMTAxMTJaMHMxJTAjBgNVBAMMHHNlbGZzaWduZWQuc2ltcGxlc2FtbHBocC5v +cmcxGTAXBgNVBAoMEFNpbXBsZVNBTUxwaHAgSFExETAPBgNVBAcMCEhvbm9sdWx1 +MQ8wDQYDVQQIDAZIYXdhaWkxCzAJBgNVBAYTAlVTMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQDZ3ztmRqrJm6zSJ2jNUjbxdlX3WLadthHvDkdfQDWuFIUjMkCf +O8+OWYdFP4cQWKBVnWyO+/LXFl9p9fwcSJmhmkgz5blUw0cjv8jccHw+yfVnGxGC +K2Up/Si4HE/WStgjTKr3ivAqyc0Kc/97EoIXmb96SEEb4Lat9xt6mFSbWQIDAQAB +o1MwUTAdBgNVHQ4EFgQUHhB/tFwoDOT8V1nLi5XY4CJgTTQwHwYDVR0jBBgwFoAU +HhB/tFwoDOT8V1nLi5XY4CJgTTQwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0B +AQsFAAOBgQBVqkGuiwyYMx0FH3j/qirZ2nKrjUU+p0ganzEA7kVZ97RivW5SFLHk +RjcfqDXVyqOzmr4iR8rfxI2rUjYQI1SsKacPhitiArMk2IQ+uvxZ5eNtU2yFzW4a +rQ+a2ttlZI/GF9nLvgUNFIUwaDc+PQD2rOk+ACklbAS3jkf+L3DpQg==knuhC \ No newline at end of file diff --git a/tests/resources/xml/custom_CustomSignedWithComments.xml b/tests/resources/xml/custom_CustomSignedWithComments.xml new file mode 100644 index 00000000..d1342f73 --- /dev/null +++ b/tests/resources/xml/custom_CustomSignedWithComments.xml @@ -0,0 +1,15 @@ +Aoq6LrKSoNxBpBqyzwcyJOrTX9lL3lc5vJVREcNpQvo=rkF/RWNL0B2Q+esER2eLGFUJeCQSYGDRo3VQ868wwzIVdeswYqj/cHz8il4bdd1AmG7Ubnmgy4O37c3QvZW++DOvEG426pJ/ueRGfxZI2vLk/dkGK5LllU16rW93bEdmyPudelAcOpjoRy/3v4o8hrB1Xj6i+/efcschCwFdj3U=MIICxDCCAi2gAwIBAgIUCJ8EYI/BrvQnHt9QFGfdqgaxDmowDQYJKoZIhvcNAQEL +BQAwczElMCMGA1UEAwwcc2VsZnNpZ25lZC5zaW1wbGVzYW1scGhwLm9yZzEZMBcG +A1UECgwQU2ltcGxlU0FNTHBocCBIUTERMA8GA1UEBwwISG9ub2x1bHUxDzANBgNV +BAgMBkhhd2FpaTELMAkGA1UEBhMCVVMwIBcNMjAwNjIzMjEwMTEyWhgPMjEyMDA1 +MzAyMTAxMTJaMHMxJTAjBgNVBAMMHHNlbGZzaWduZWQuc2ltcGxlc2FtbHBocC5v +cmcxGTAXBgNVBAoMEFNpbXBsZVNBTUxwaHAgSFExETAPBgNVBAcMCEhvbm9sdWx1 +MQ8wDQYDVQQIDAZIYXdhaWkxCzAJBgNVBAYTAlVTMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQDZ3ztmRqrJm6zSJ2jNUjbxdlX3WLadthHvDkdfQDWuFIUjMkCf +O8+OWYdFP4cQWKBVnWyO+/LXFl9p9fwcSJmhmkgz5blUw0cjv8jccHw+yfVnGxGC +K2Up/Si4HE/WStgjTKr3ivAqyc0Kc/97EoIXmb96SEEb4Lat9xt6mFSbWQIDAQAB +o1MwUTAdBgNVHQ4EFgQUHhB/tFwoDOT8V1nLi5XY4CJgTTQwHwYDVR0jBBgwFoAU +HhB/tFwoDOT8V1nLi5XY4CJgTTQwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0B +AQsFAAOBgQBVqkGuiwyYMx0FH3j/qirZ2nKrjUU+p0ganzEA7kVZ97RivW5SFLHk +RjcfqDXVyqOzmr4iR8rfxI2rUjYQI1SsKacPhitiArMk2IQ+uvxZ5eNtU2yFzW4a +rQ+a2ttlZI/GF9nLvgUNFIUwaDc+PQD2rOk+ACklbAS3jkf+L3DpQg==Chunk \ No newline at end of file diff --git a/tests/resources/xml/custom_CustomSignedWithCommentsAndId.xml b/tests/resources/xml/custom_CustomSignedWithCommentsAndId.xml new file mode 100644 index 00000000..33258e0e --- /dev/null +++ b/tests/resources/xml/custom_CustomSignedWithCommentsAndId.xml @@ -0,0 +1,15 @@ +NV/zxP9pMcTkzKYAb5UBwoLX1UTaWD085lwBFImZF2w=d/gPQffy48OzCNvPFVggbFJM3WjJdsKTJBfHYWrvMY2VdwkAEVEmDTzy21z3zXPMUA8ynrV7PkWvDf9TKxBsTcDaXb8aqWQyfrBkz+hwYcWEyMZmUNwhsaUfZK3OGfjCss7jBN7xVOzOi+KPdK+show6B+MfTMLtR+yoB2EtZ9s=MIICxDCCAi2gAwIBAgIUCJ8EYI/BrvQnHt9QFGfdqgaxDmowDQYJKoZIhvcNAQEL +BQAwczElMCMGA1UEAwwcc2VsZnNpZ25lZC5zaW1wbGVzYW1scGhwLm9yZzEZMBcG +A1UECgwQU2ltcGxlU0FNTHBocCBIUTERMA8GA1UEBwwISG9ub2x1bHUxDzANBgNV +BAgMBkhhd2FpaTELMAkGA1UEBhMCVVMwIBcNMjAwNjIzMjEwMTEyWhgPMjEyMDA1 +MzAyMTAxMTJaMHMxJTAjBgNVBAMMHHNlbGZzaWduZWQuc2ltcGxlc2FtbHBocC5v +cmcxGTAXBgNVBAoMEFNpbXBsZVNBTUxwaHAgSFExETAPBgNVBAcMCEhvbm9sdWx1 +MQ8wDQYDVQQIDAZIYXdhaWkxCzAJBgNVBAYTAlVTMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQDZ3ztmRqrJm6zSJ2jNUjbxdlX3WLadthHvDkdfQDWuFIUjMkCf +O8+OWYdFP4cQWKBVnWyO+/LXFl9p9fwcSJmhmkgz5blUw0cjv8jccHw+yfVnGxGC +K2Up/Si4HE/WStgjTKr3ivAqyc0Kc/97EoIXmb96SEEb4Lat9xt6mFSbWQIDAQAB +o1MwUTAdBgNVHQ4EFgQUHhB/tFwoDOT8V1nLi5XY4CJgTTQwHwYDVR0jBBgwFoAU +HhB/tFwoDOT8V1nLi5XY4CJgTTQwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0B +AQsFAAOBgQBVqkGuiwyYMx0FH3j/qirZ2nKrjUU+p0ganzEA7kVZ97RivW5SFLHk +RjcfqDXVyqOzmr4iR8rfxI2rUjYQI1SsKacPhitiArMk2IQ+uvxZ5eNtU2yFzW4a +rQ+a2ttlZI/GF9nLvgUNFIUwaDc+PQD2rOk+ACklbAS3jkf+L3DpQg==Chunk \ No newline at end of file diff --git a/tests/resources/xml/ds_SignedInfoWithComments.xml b/tests/resources/xml/ds_SignedInfoWithComments.xml new file mode 100644 index 00000000..43ca0a0f --- /dev/null +++ b/tests/resources/xml/ds_SignedInfoWithComments.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + /CTj03d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI= + + diff --git a/tests/resources/xml/xenc_CarriedKeyName.xml b/tests/resources/xml/xenc_CarriedKeyName.xml new file mode 100644 index 00000000..83e3ebcb --- /dev/null +++ b/tests/resources/xml/xenc_CarriedKeyName.xml @@ -0,0 +1 @@ +Some label diff --git a/tests/resources/xml/xenc_CipherValue.xml b/tests/resources/xml/xenc_CipherValue.xml index 4b2f5b24..5c4feb4c 100644 --- a/tests/resources/xml/xenc_CipherValue.xml +++ b/tests/resources/xml/xenc_CipherValue.xml @@ -1 +1,3 @@ -c29tZSB0ZXh0 + +/CTj03d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI= + diff --git a/tests/resources/xml/xenc_EncryptedData.xml b/tests/resources/xml/xenc_EncryptedData.xml index 5aeddf59..e6ac54e2 100644 --- a/tests/resources/xml/xenc_EncryptedData.xml +++ b/tests/resources/xml/xenc_EncryptedData.xml @@ -4,12 +4,12 @@ - nxf0b... + /CTj03d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI= - iaDc7... + /CTj03d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI= diff --git a/tests/resources/xml/xenc_EncryptedKey.xml b/tests/resources/xml/xenc_EncryptedKey.xml index 59101674..893a469b 100644 --- a/tests/resources/xml/xenc_EncryptedKey.xml +++ b/tests/resources/xml/xenc_EncryptedKey.xml @@ -4,12 +4,12 @@ - nxf0b... + /CTj03d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI= - PzA5X... + /CTj03d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI=